Skip to content

Commit c0f053c

Browse files
authored
Add simple UART bootloader example (#571)
* Add simple UART bootloader The sample binary flashes the LED and prints Hello, world back over the UART interface * Hardcode baud rate, as it's fixed by the bootrom * Tidy up variable names, add assets on correct chars, and improve documentation * Add verify, support Risc-V, and replace asserts on returned chars with reset_chips * Add note as to why bootloader should be absolute * Allow more startup time for booting device * Allow longer delays while running too * review fixups
1 parent 8e97d46 commit c0f053c

File tree

6 files changed

+364
-0
lines changed

6 files changed

+364
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,13 @@ App|Description
5252
[hello_anything](binary_info/hello_anything) | Uses `bi_ptr` variables to create a configurable hello_world binary - see the separate [README](binary_info/README.md) for more details
5353

5454
### Bootloaders (RP235x Only)
55+
56+
These examples all produce multiple UF2s - a bootloader UF2, and then a separate UF2 of the program that the bootloader will load and boot. To load them onto a device with empty flash, first load the bootloader UF2, then reset to BOOTSEL mode and load the program UF2. This ordering is required because the bootloaders contain embedded partition tables - see section 5.10.6 in the RP2350 datasheet for more details on those.
57+
5558
App|Description
5659
---|---
5760
[enc_bootloader](bootloaders/encrypted) | A bootloader which decrypts binaries from flash into SRAM. See the separate [README](bootloaders/encrypted/README.md) for more information
61+
[uart_boot](bootloaders/uart) | A bootloader which boots a separate RP2350 using the UART boot interface. See section 5.8 in the datasheet for more details, including the wiring requirements
5862

5963
### Clocks
6064

bootloaders/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
add_subdirectory_exclude_platforms(uart host rp2040)
2+
13
if (TARGET pico_mbedtls)
24
# older clang seem to have a segment overlap issue that confuses picotool
35
if (PICO_C_COMPILER_IS_CLANG AND CMAKE_C_COMPILER_VERSION VERSION_LESS "17.0.0")

bootloaders/uart/CMakeLists.txt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
add_executable(uart_boot
2+
uart_boot.c
3+
)
4+
5+
# pull in common dependencies
6+
target_link_libraries(uart_boot pico_stdlib hardware_flash)
7+
8+
# add partition table
9+
pico_embed_pt_in_binary(uart_boot ${CMAKE_CURRENT_LIST_DIR}/uart-pt.json)
10+
11+
# create absolute UF2, as it's a bootloader so shouldn't go in a partition
12+
pico_set_uf2_family(uart_boot "absolute")
13+
14+
# create map/bin/hex file etc.
15+
pico_add_extra_outputs(uart_boot)
16+
17+
# add url via pico_set_program_url
18+
example_auto_set_url(uart_boot)
19+
20+
21+
# Create separate binary to be loaded onto other device
22+
add_executable(uart_binary
23+
uart_binary.c
24+
)
25+
26+
# pull in common dependencies
27+
target_link_libraries(uart_binary pico_stdlib)
28+
29+
pico_set_binary_type(uart_binary no_flash)
30+
31+
# package uf2 in flash
32+
pico_package_uf2_output(uart_binary 0x10000000)
33+
34+
# create map/bin/hex/uf2 file etc.
35+
pico_add_extra_outputs(uart_binary)
36+
37+
# call pico_set_program_url to set path to example on github, so users can find the source for an example via picotool
38+
example_auto_set_url(uart_binary)

bootloaders/uart/uart-pt.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"version": [1, 0],
3+
"unpartitioned": {
4+
"families": ["absolute"],
5+
"permissions": {
6+
"secure": "rw",
7+
"nonsecure": "rw",
8+
"bootloader": "rw"
9+
}
10+
},
11+
"partitions": [
12+
{
13+
"start": "128K",
14+
"size": "32K",
15+
"families": ["rp2350-arm-s", "rp2350-riscv"],
16+
"permissions": {
17+
"secure": "rw",
18+
"nonsecure": "rw",
19+
"bootloader": "rw"
20+
}
21+
}
22+
]
23+
}

bootloaders/uart/uart_binary.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include "pico/stdlib.h"
8+
#include "hardware/uart.h"
9+
#include "hardware/structs/pads_qspi.h"
10+
#include "hardware/structs/io_qspi.h"
11+
12+
#ifndef LED_DELAY_MS
13+
#define LED_DELAY_MS 500
14+
#endif
15+
16+
// Initialize the GPIO for the LED
17+
void pico_led_init(void) {
18+
#ifdef PICO_DEFAULT_LED_PIN
19+
// A device like Pico that uses a GPIO for the LED will define PICO_DEFAULT_LED_PIN
20+
// so we can use normal GPIO functionality to turn the led on and off
21+
gpio_init(PICO_DEFAULT_LED_PIN);
22+
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
23+
#endif
24+
}
25+
26+
// Turn the LED on or off
27+
void pico_set_led(bool led_on) {
28+
#if defined(PICO_DEFAULT_LED_PIN)
29+
// Just set the GPIO on or off
30+
gpio_put(PICO_DEFAULT_LED_PIN, led_on);
31+
#endif
32+
}
33+
34+
// Set function for QSPI GPIO pin
35+
void qspi_gpio_set_function(uint gpio, gpio_function_t fn) {
36+
// Set input enable on, output disable off
37+
hw_write_masked(&pads_qspi_hw->io[gpio],
38+
PADS_QSPI_GPIO_QSPI_SD2_IE_BITS,
39+
PADS_QSPI_GPIO_QSPI_SD2_IE_BITS | PADS_QSPI_GPIO_QSPI_SD2_OD_BITS
40+
);
41+
// Zero all fields apart from fsel; we want this IO to do what the peripheral tells it.
42+
// This doesn't affect e.g. pullup/pulldown, as these are in pad controls.
43+
io_qspi_hw->io[gpio].ctrl = fn << IO_QSPI_GPIO_QSPI_SD2_CTRL_FUNCSEL_LSB;
44+
45+
// Remove pad isolation now that the correct peripheral is in control of the pad
46+
hw_clear_bits(&pads_qspi_hw->io[gpio], PADS_QSPI_GPIO_QSPI_SD2_ISO_BITS);
47+
}
48+
49+
int main() {
50+
pico_led_init();
51+
52+
// SD2 is QSPI GPIO 3, SD3 is QSPI GPIO 4
53+
qspi_gpio_set_function(3, GPIO_FUNC_UART_AUX);
54+
qspi_gpio_set_function(4, GPIO_FUNC_UART_AUX);
55+
56+
uart_init(uart0, 1000000);
57+
58+
while (true) {
59+
uart_puts(uart0, "Hello, world\n");
60+
pico_set_led(true);
61+
sleep_ms(LED_DELAY_MS);
62+
pico_set_led(false);
63+
sleep_ms(LED_DELAY_MS);
64+
}
65+
}

bootloaders/uart/uart_boot.c

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include "pico/stdlib.h"
4+
#include "hardware/uart.h"
5+
#include "pico/bootrom.h"
6+
#include "boot/picobin.h"
7+
#include "hardware/flash.h"
8+
9+
// UART defines for uart boot
10+
#define UART_ID uart1
11+
12+
// Use pins 4 and 5 for uart boot
13+
#define UART_TX_PIN 4
14+
#define UART_RX_PIN 5
15+
16+
// Use pin 3 for the RUN pin on the other chip
17+
#define RUN_PIN 3
18+
19+
20+
void reset_chip() {
21+
// Toggle run pin
22+
gpio_put(RUN_PIN, false);
23+
sleep_ms(1);
24+
gpio_put(RUN_PIN, true);
25+
}
26+
27+
28+
void uart_boot() {
29+
uint knocks = 0;
30+
char in = 0;
31+
while (true) {
32+
// Send the knock sequence
33+
uart_putc_raw(UART_ID, 0x56);
34+
uart_putc_raw(UART_ID, 0xff);
35+
uart_putc_raw(UART_ID, 0x8b);
36+
uart_putc_raw(UART_ID, 0xe4);
37+
uart_putc_raw(UART_ID, 'n');
38+
39+
if (uart_is_readable_within_us(UART_ID, 1000)) {
40+
in = uart_getc(UART_ID);
41+
if (in != 'n') {
42+
printf("Incorrect response - resetting\n");
43+
reset_chip();
44+
return;
45+
}
46+
printf("%c\n", in);
47+
break;
48+
} else {
49+
if (knocks > 10) {
50+
printf("No response - resetting\n");
51+
reset_chip();
52+
return;
53+
}
54+
printf("No response - knocking again\n");
55+
knocks++;
56+
}
57+
}
58+
59+
printf("Boot starting\n");
60+
61+
// Get partition location in flash
62+
const int buf_words = (16 * 4) + 1; // maximum of 16 partitions, each with maximum of 4 words returned, plus 1
63+
uint32_t* buffer = malloc(buf_words * 4);
64+
65+
int ret = rom_get_partition_table_info(buffer, buf_words, PT_INFO_PARTITION_LOCATION_AND_FLAGS | PT_INFO_SINGLE_PARTITION | (0 << 24));
66+
assert(buffer[0] == (PT_INFO_PARTITION_LOCATION_AND_FLAGS | PT_INFO_SINGLE_PARTITION));
67+
assert(ret == 3);
68+
69+
uint32_t location_and_permissions = buffer[1];
70+
uint32_t start_addr = XIP_BASE + ((location_and_permissions & PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_BITS) >> PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_LSB) * FLASH_SECTOR_SIZE;
71+
uint32_t end_addr = XIP_BASE + (((location_and_permissions & PICOBIN_PARTITION_LOCATION_LAST_SECTOR_BITS) >> PICOBIN_PARTITION_LOCATION_LAST_SECTOR_LSB) + 1) * FLASH_SECTOR_SIZE;
72+
printf("Start %08x, end %08x\n", start_addr, end_addr);
73+
74+
free(buffer);
75+
76+
printf("Writing binary\n");
77+
uint32_t time_start = time_us_32();
78+
uint32_t current_addr = start_addr;
79+
while (current_addr < end_addr) {
80+
uart_putc_raw(UART_ID, 'w');
81+
char *buf = (char*)current_addr;
82+
int i;
83+
for (i = 0; i < 32; i++) {
84+
uart_putc_raw(UART_ID, buf[i]);
85+
}
86+
if (!uart_is_readable_within_us(UART_ID, 500)) {
87+
// Detect hangs and reset the chip
88+
printf("Write has hung - resetting\n");
89+
reset_chip();
90+
return;
91+
}
92+
in = uart_getc(UART_ID);
93+
if (in != 'w') {
94+
printf("Incorrect response - resetting\n");
95+
reset_chip();
96+
return;
97+
}
98+
current_addr += i;
99+
}
100+
101+
uint32_t time_end = time_us_32();
102+
printf("Write took %dus\n", time_end - time_start);
103+
printf("Write complete - resetting pointer\n");
104+
105+
uart_putc_raw(UART_ID, 'c');
106+
if (!uart_is_readable_within_us(UART_ID, 500)) {
107+
// Detect hangs and reset the chip
108+
printf("Clear has hung - resetting\n");
109+
reset_chip();
110+
return;
111+
}
112+
in = uart_getc(UART_ID);
113+
printf("%c\n", in);
114+
if (in != 'c') {
115+
printf("Incorrect response - resetting\n");
116+
reset_chip();
117+
return;
118+
}
119+
120+
printf("Verifying binary\n");
121+
time_start = time_us_32();
122+
current_addr = start_addr;
123+
while (current_addr < end_addr) {
124+
uart_putc_raw(UART_ID, 'r');
125+
char *buf = (char*)current_addr;
126+
if (!uart_is_readable_within_us(UART_ID, 500)) {
127+
// Detect hangs and reset the chip
128+
printf("Verify has hung - resetting\n");
129+
reset_chip();
130+
return;
131+
}
132+
int i = 0;
133+
while (uart_is_readable_within_us(UART_ID, 10) && i < 32) {
134+
in = uart_getc(UART_ID);
135+
if (in != buf[i]) {
136+
printf("Verify has incorrect data at 0x%08x - resetting\n", current_addr - start_addr + SRAM_BASE);
137+
reset_chip();
138+
return;
139+
}
140+
i++;
141+
}
142+
if (i != 32) {
143+
printf("Verify has incorrect data size - resetting\n");
144+
reset_chip();
145+
return;
146+
}
147+
in = uart_getc(UART_ID);
148+
if (in != 'r') {
149+
printf("Incorrect response - resetting\n");
150+
reset_chip();
151+
return;
152+
}
153+
current_addr += i;
154+
}
155+
156+
time_end = time_us_32();
157+
printf("Verify took %dus\n", time_end - time_start);
158+
printf("Verify complete - executing\n");
159+
160+
uart_putc_raw(UART_ID, 'x');
161+
if (!uart_is_readable_within_us(UART_ID, 500)) {
162+
// Detect hangs and reset the chip
163+
printf("Execute has hung - resetting\n");
164+
reset_chip();
165+
return;
166+
}
167+
in = uart_getc(UART_ID);
168+
printf("%c\n", in);
169+
if (in != 'x') {
170+
printf("Incorrect response - resetting\n");
171+
reset_chip();
172+
return;
173+
}
174+
}
175+
176+
177+
int main()
178+
{
179+
stdio_init_all();
180+
181+
// Set up our UART for booting the other device
182+
uart_init(UART_ID, 1000000);
183+
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
184+
gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
185+
186+
// Set up run pin
187+
gpio_init(RUN_PIN);
188+
gpio_set_dir(RUN_PIN, GPIO_OUT);
189+
190+
// Reset chip
191+
reset_chip();
192+
193+
int attempts = 0;
194+
char splash[] = "RP2350";
195+
char hello[] = "Hello, world";
196+
197+
while (true) {
198+
char buf[500] = {0};
199+
int i = 0;
200+
while (uart_is_readable(UART_ID) && i < sizeof(buf)) {
201+
char in = uart_getc(UART_ID);
202+
printf("%c", in);
203+
buf[i] = in;
204+
i++;
205+
}
206+
if (i > 0) {
207+
printf(" ...Read done\n");
208+
}
209+
char *ptr = memchr(buf, splash[0], sizeof(buf));
210+
if (ptr && strncmp(ptr, splash, sizeof(splash) - 1) == 0) {
211+
printf("Splash found\n");
212+
uart_boot();
213+
attempts = 0;
214+
} else {
215+
ptr = memchr(buf, hello[0], sizeof(buf));
216+
if (ptr && strncmp(ptr, hello, sizeof(hello) - 1) == 0) {
217+
printf("Device is running\n");
218+
attempts = 0;
219+
} else {
220+
if (attempts > 3) {
221+
printf("Device not running - attempting reset\n");
222+
reset_chip();
223+
attempts = 0;
224+
} else {
225+
printf("Device not running - waiting\n");
226+
attempts++;
227+
}
228+
}
229+
}
230+
sleep_ms(1000);
231+
}
232+
}

0 commit comments

Comments
 (0)