Skip to content

Commit 00af391

Browse files
committed
Add function docs, add separate universal README, and fixup main README
Also switch to board_type instead of is_w so the functions are clearer
1 parent ab408f8 commit 00af391

File tree

4 files changed

+96
-14
lines changed

4 files changed

+96
-14
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,14 @@ App|Description
408408

409409
These are examples of how to build universal binaries which run on RP2040, and RP2350 Arm & RISC-V.
410410
These require you to set `PICO_ARM_TOOLCHAIN_PATH` and `PICO_RISCV_TOOLCHAIN_PATH` to appropriate paths, to ensure you have compilers for both architectures.
411+
These are designed for dragging & dropping onto a device, so may not load as expected when using `picotool`.
412+
See the separate [README](universal/README.md) for more details of how these work.
411413

412414
App|Description
413415
---|---
414-
[blink_universal](universal/CMakeLists.txt#L126) | Same as the [blink](blink) example, but universal.
416+
[blink_universal](universal/blink_universal) | A universal blink which works for all Pico-series and Pico W-series boards.
415417
[hello_universal](universal/hello_universal) | The obligatory Hello World program for Pico (USB and serial output). On RP2350 it will reboot to the other architecture after every 10 prints.
416-
[nuke_universal](universal/CMakeLists.txt#L132) | Same as the [nuke](flash/nuke) example, but universal. On RP2350 runs as a packaged SRAM binary, so it is written to flash and copied to SRAM by the bootloader.
418+
[nuke_universal](universal/CMakeLists.txt) | Same as the [nuke](flash/nuke) example, but universal.
417419

418420
### USB Device
419421

universal/CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ include(ExternalProject)
2020
#
2121
# The build will output a TARGET.bin file which can be written using picotool, and a
2222
# TARGET.uf2 file which can be dragged and dropped onto the device in BOOTSEL mode
23+
#
24+
# If SEPARATE_RP2040 is set, the RP2040 binary will not be included in the block loop,
25+
# so the RP2040 UF2 file will only contain the RP2040 binary, and the RP2350 UF2 file will
26+
# contain the combined binary excluding the RP2040 binary.
2327
function (add_universal_target TARGET SOURCE)
2428
set(zeroValueArgs SEPARATE_RP2040)
2529
set(oneValueArgs SOURCE_TARGET PADDING PACKADDR BOARD_RP2040 BOARD_RP2350)
@@ -169,7 +173,9 @@ add_universal_target(blink_universal
169173
SOURCE_TARGET blink_universal
170174
BOARD_RP2040 universal
171175
BOARD_RP2350 universal
172-
SEPARATE_RP2040 PLATFORMS "rp2040;rp2350-arm-s" # Skip RISC-V and keep RP2040 separate, as wifi firmware takes up lots of space
176+
# Skip RISC-V and keep RP2040 separate, as wifi firmware takes up lots of space
177+
PLATFORMS "rp2040;rp2350-arm-s"
178+
SEPARATE_RP2040
173179
)
174180

175181
# nuke binary - is no_flash, so needs to be sent to SRAM on RP2040

universal/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Universal Examples
2+
3+
These examples show ways to load the same code onto different chips, and package
4+
it in such a way that the bootrom only executes the code compatible with that chip.
5+
6+
## Universal Binary vs Universal UF2
7+
8+
There is a difference between a **Universal Binary** and a **Universal UF2**,
9+
for the purposes of these examples:
10+
- A **Universal Binary** is a `.bin` file that can be loaded into flash (or sram) and executed,
11+
allowing RP2040 and RP2350 (Arm & RISC-V) to run from identical flash contents.
12+
- A **Universal UF2** is multiple individual `.uf2` files with different family IDs
13+
concatenated together to create a single `.uf2` file. When dragged & dropped onto a device,
14+
only the file with a family ID corresponding to that device will be loaded onto it, and the
15+
rest of the files will be ignored.
16+
17+
A **Universal Binary** can be packaged into a UF2 file for loading onto a device. However,
18+
as there isn't a common family ID between RP2040 and RP2350, you would have to package it into a **Universal UF2** with two copies (using `rp2040` and `absolute` family IDs), thus creating a **Universal UF2** of a **Universal Binary**.
19+
20+
## How Universal Binaries work
21+
22+
Universal binaries must be recognised by both the RP2040 and RP2350 bootroms. Therefore, they need the following structure for flash binaries:
23+
- RP2040 boot2
24+
- Required by the RP2040 bootrom
25+
- RP2040 binary containing an embedded block
26+
- The embedded block contains an `IGNORED` item due to RP2350-E13, but you can use an RP2040
27+
`IMAGE_DEF` item instead if not using RP2350-A2 chips
28+
- RP2350 Arm binary containing an embedded block
29+
- In addition to the RP2350 `IMAGE_DEF` item, this embedded block contains a
30+
`ROLLING_WINDOW_DELTA` item to translate this binary to the start of flash for execution
31+
- RP2350 RISC-V binary containing an embedded block
32+
- Same as the Arm one
33+
34+
All of the embedded blocks are linked into one big block loop.
35+
36+
These are then booted by the respective bootroms:
37+
- **RP2040** - sees the boot2 at the start and uses that to execute the RP2040 binary, as
38+
RP2040 has no support for embedded blocks.
39+
- **RP2350** - sees the block loop and parses it to find the correct embedded block to boot
40+
from (Arm vs RISC-V). It then translates the flash address according to the
41+
`ROLLING_WINDOW_DELTA` so that the binary containing that embedded block is at the start of the
42+
flash address space, and executes from there.
43+
44+
For no_flash binaries the RP2040 boot2 is omitted as the bootrom just executes from the start
45+
of SRAM, and instead of `ROLLING_WINDOW_DELTA` items the RP2350 binaries use `LOAD_MAP` items,
46+
to copy the code in SRAM to the correct location for execution rather than using address
47+
translation.
48+
49+
## How you should use them
50+
51+
For most use cases, **Universal UF2s** are the best option to use. They will only load into
52+
flash the code that runs on that device. The [blink_universal](blink_universal) example uses a
53+
Universal UF2 for that reason, as the Wi-Fi firmware is quite large. **Universal Binaries**
54+
are only currently useful when the commonality of having a single `.bin` file for programming
55+
outweighs the disadvantage of the extra flash usage.

universal/blink_universal/blink_universal.c

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,21 @@
1212
#define LED_DELAY_MS 250
1313
#endif
1414

15-
bool detect_is_w_using_adc(void) {
15+
enum BOARD_TYPE {
16+
BOARD_TYPE_PICO,
17+
BOARD_TYPE_PICO_W,
18+
BOARD_TYPE_UNKNOWN,
19+
};
20+
21+
// Detects if PICO_VSYS_PIN is actually connected to the VSYS voltage divider,
22+
// to determine the board type.
23+
// Also checks that the LED pin is low, which should be the case for both
24+
// Pico-series and Pico W-series boards.
25+
// This will work provided that the board is being powered from VSYS (i.e. it
26+
// is using the onboard voltage regulator).
27+
// This method is documented in section 2.4 of Connecting to the Internet with
28+
// Raspberry Pi Pico W-series (https://pip.raspberrypi.com/documents/RP-008257-DS).
29+
enum BOARD_TYPE detect_board_type(void) {
1630
adc_init();
1731
adc_gpio_init(PICO_VSYS_PIN);
1832
adc_select_input(PICO_VSYS_PIN - ADC_BASE_PIN);
@@ -25,15 +39,20 @@ bool detect_is_w_using_adc(void) {
2539
bool value = gpio_get(PICO_DEFAULT_LED_PIN);
2640

2741
if (value == 0 && voltage < 0.1) {
28-
return true;
42+
// Pico W-series board
43+
return BOARD_TYPE_PICO_W;
44+
} else if (value == 0) {
45+
// Pico-series board
46+
return BOARD_TYPE_PICO;
2947
} else {
30-
return false;
48+
// Unknown board
49+
return BOARD_TYPE_UNKNOWN;
3150
}
3251
}
3352

3453
// Perform initialisation
35-
int pico_led_init(bool is_w) {
36-
if (is_w) {
54+
int pico_led_init(enum BOARD_TYPE board_type) {
55+
if (board_type == BOARD_TYPE_PICO_W) {
3756
return cyw43_arch_init();
3857
} else {
3958
// A device like Pico that uses a GPIO for the LED will define PICO_DEFAULT_LED_PIN
@@ -45,22 +64,22 @@ int pico_led_init(bool is_w) {
4564
}
4665

4766
// Turn the led on or off
48-
void pico_set_led(bool led_on, bool is_w) {
49-
if (is_w) {
67+
void pico_set_led(bool led_on, enum BOARD_TYPE board_type) {
68+
if (board_type == BOARD_TYPE_PICO_W) {
5069
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on);
5170
} else {
5271
gpio_put(PICO_DEFAULT_LED_PIN, led_on);
5372
}
5473
}
5574

5675
int main() {
57-
bool is_w = detect_is_w_using_adc();
58-
int rc = pico_led_init(is_w);
76+
enum BOARD_TYPE board_type = detect_board_type();
77+
int rc = pico_led_init(board_type);
5978
hard_assert(rc == PICO_OK);
6079
while (true) {
61-
pico_set_led(true, is_w);
80+
pico_set_led(true, board_type);
6281
sleep_ms(LED_DELAY_MS);
63-
pico_set_led(false, is_w);
82+
pico_set_led(false, board_type);
6483
sleep_ms(LED_DELAY_MS);
6584
}
6685
}

0 commit comments

Comments
 (0)