on 2023-12-04 05:57 AM
The system bootloader is present on all STM32 MCUs. As the name suggests, it is located on the system memory (ROM) area of the MCU. The system bootloader is located on the read only portion of the memory and is programmed during the manufacturing phase. In popular literature, a system bootloader may be referred to as the ROM bootloader.
This article serves as an aid to the system bootloader application note (AN3155), where concrete examples are given. We focus on how to activate one of the system bootloaders, and how to call system bootloader commands through the serial terminal. In this case, we use the UART system bootloader because it is easy to set up. We want to focus our attention on how to call system bootloader commands. The good news is that the bootloader commands are similar between different interfaces. Going through this article should still help you, even if you are using different physical interfaces like CAN, SPI, etc.
First determine the requirements for booting into the system memory where the bootloader is located. To use the system bootloader, we set the BOOT0 pin to 1 as shown below in table 1. NOTE: Some STM32 series have a slightly different method of accessing the system memory. For example, option bytes might have to be changed. Be sure to see the reference manual of your MCU for how to boot into the system memory.
Table 1: Reference manual: STM32H503 boot modes
We use the Nucleo-H503RB, however, you can use other STM32 series (keep in mind the process of booting into the system bootloader is different for every STM32 series).
Table 2: User manual: The first column (CN7) shows where “BOOT0” is located on the Nucleo board. “VDD” is located on pin 5 of CN7 on the Nucleo board.
Figure 1: User manual: Nucleo-H503RB pin numbered at the back side of the MCU.
To set BOOT0 to 1, we will simply short pin "BOOT0" to pin "VDD" (locate the pins on your sample board using the figure 2 schematic). You can use the provided spare jumpers on your Nucleo board, from CN11 or CN12 to short the pins.
Figure 2: Nucleo-H503RB board pin schematic found on the STMicroelectronics website [5].
Completing the above step (jumping into the bootloader), we are now ready to use the application note AN2606 [1] to activate the system bootloader. Using table 3, we will arbitrarily (and for ease of access to the pins) pick one of the USART interfaces for system bootloader communication. In this case, we choose the USART2 bootloader as our communication interface. Notice the comment section for "USART2" - later, we transfer these settings to our serial terminal software (i.e "AccessPort").
Table 3: table shows system bootloader interfaces and their respective configurations (see AN2606 for an exhaustive list of interfaces).
For establishing communication with the bootloader via the PC, we use the Adafruit 954 USB to TTL serial cable. For this cable, there are four wires: red power, black ground, white Rx into the USB port, and green Tx out of the USB port. The power pin provides the 5 V @ 500 mA direct from the USB port. The Rx/Tx pins are 3.3 V level for interfacing with the most common 3.3 V logic level chipsets.
Table 4: Nucleo board to USB/TTL serial translator.
Nucleo board GPIO column | MCU pin location | TTL/USB serial translator wire |
CN7 | PA15 (Rx) | Green (Tx) |
CN10 | PA5 (Tx) | White (Rx) |
CN7 | VDD | Red |
CN7 | GND | Black |
Figure 3: USB to TTL translator connected to the Nucleo board. USB end connected to PC with serial terminal.
Launch the AccessPort program and click on the "configuration" tab with the yellow and green gear icon as shown. Make the settings (aside from the "Port") similar to figure 4.
You can choose any baud rate (see the accepted max and min baud rate in the application note AN3155 [2]). The rest of the settings are derived from table 3.
Figure 4: AccessPort: UART communication settings.
It is important to firstly activate the UART bootloader, before calling any bootloader commands as shown in figure 5 below. The start byte "7F" is sent from the serial terminal to activate the USART/UART bootloader. The bootloader responds with "7F 79" where "79" indicates we have now activated the USART/UART system bootloader.
Figure 5: AccessPort: On the left image, We initiate communication with THE UART bootloader by sending “7F” start byte to the MCU and the MCU responds back with “79” (ACK). Flow chart is from AN3155 [2].
After observing the "79" ack response, we are now ready to send commands to the system bootloader.
In section 5, we demonstrate the syntax of some STM32 system bootloader commands. These example commands are a supplement to the system bootloader application notes (for example: AN3155 [2]).
Note: Only a subset of the bootloader commands are demonstrated here.
Snippet 1: AccessPort terminal screen view for the "Get" command.
----------------------------------
AccessPort terminal screen:
----------------------------------
Send:
00 FF /* Comment: command; checksum: 1’s complement of the command code = FF */
Output:
79 0A 40 00 01 02 11 21 31 44 50 63 73 79
Table 5: Command output result.
Output byte number | What does the corresponding byte represent? |
Byte 1 | ACK |
Byte 2 | N = 10 = the number of bytes to follow – 1, except current and ACKs |
Byte 3 | Protocol version 0x40 = v4.0 |
Byte 4 | 0x00 get command |
Byte 5 | 0x01 get version command |
Byte 6 | 0x02 get ID command |
Byte 7 | 0x11 read memory command |
Byte 8 | 0x21 go command |
Byte 9 | 0x31 write memory command |
Byte 10 | 0x44 extended erase command |
Byte 11 | 0x50 special command |
Byte 12 | 0x63 write protect command |
Byte 13 | 0x73 write command |
Byte 14 | 0x79 ACK |
Snippet 2: AccessPort terminal screen view for the "Get version" command.
----------------------------------
AccessPort terminal screen:
----------------------------------
Send:
01 FE /* Comment: command code; checksum: 1’s complement of the command code = FE */
Output:
79 40 00 00 79
Table 6: Command output result.
Output byte number | What does the corresponding byte represent? |
Byte 1 | ACK |
Byte 2 | Protocol version (0 < version ≤ 255), example: 0x10 = version 1.0 |
Byte 3 | Option byte 1: 0x00 to keep the compatibility with the generic bootloader protocol |
Byte 4 | Option byte 2: 0x00 to keep the compatibility with the generic bootloader protocol |
Byte 5 | ACK |
Snippet 3: AccessPort terminal screen view for the "Get ID" command.
----------------------------------
AccessPort terminal screen:
----------------------------------
Send:
02 FD /* Comment: command code; checksum: 1’s complement of the command code = FD */
Output:
79 01 04 74 79
Table 7: Command output result.
Output byte number | What does the corresponding byte represent? |
Byte 1 | ACK |
Byte 2 | N = the number of bytes – 1 (N = 1 for STM32), except for current byte and ACKs |
Bytes 3-4 | PID(1) byte 3 = 0x04, byte 4 = 0xXX |
Byte 5 | ACK |
Snippet 4: AccessPort terminal screen view for the "Read Memory" command.
----------------------------------
AccessPort terminal screen:
----------------------------------
Send part 1:
11 EE /* Comment: command code; checksum: 1’s complement of the command code = EE */
Output 1:
79 /* Comment: acknowledgement */
Send part 2:
08 00 00 00 08 /* Comment: 0x08000000 address MSB to LSB; checksum: XOR: 08, 00, 00, 00 = 08 */
Output 2:
79 /* Comment: acknowledgement */
Send part 3:
0A F5 /* Comment: number of bytes to read - 11 bytes because we count from zero; checksum: 1’s complement of number of bytes to read = F5 */
Output 3:
79 00 80 00 20 F5 04 00 08 BB 03 00 /* Comment: acknowledge byte – 79; 11 bytes of read data – byte number 2 to byte number 12 */
Table 8: Command input part 1, 2, 3.
Input byte number | What does the corresponding byte represent? |
Bytes 1-2 | 0x11 + 0xEE wait for ACK |
Bytes 3 to 6 | Start address byte 3: MSB, byte 6: LSB |
Byte 7 | Checksum: XOR (byte 3, byte 4, byte 5, byte 6) Wait for ACK |
Byte 8 | The number of bytes to be read – 1 (0 < N ≤ 255); |
Byte 9 | Checksum: XOR byte 8 (complement of byte8) |
Table 9: Command output part 3.
Output byte number | What does the corresponding byte represent? |
Byte 1 | ACK |
Remaining bytes | In the case of N+1 page erase, the bootloader receives (2 x (N + 1)) bytes, each half-word containing a page number (coded on two bytes, MSB first). Then all previous byte checksums (in one byte). |
In snippet 4 above, the read command is split into three parts. Firstly, we send the command code and its checksum, which is just the 1's complement of the command code.
Secondly, we send the desired address to read (the most significant bit first) and perform a checksum. This is where the XOR of all the address bytes is performed.
In part three, we specify the number of bytes to read. It is important to remember that we count the number of bytes to read from zero, which means "0A" actually corresponds to 11 bytes. Then it performs a 1's complement checksum of "0A."
Finally, the command outputs an ACK byte "79" along with the requested bytes that was read from the memory address.
Snippet 5: AccessPort terminal screen view for the "Extended Erase Memory" command.
----------------------------------
AccessPort terminal screen:
----------------------------------
Send part 1:
44 BB /* Comment: command code, checksum: 1’s complement = BB */
Output 1:
79 /* Comment: acknowledgement */
Send part 2:
00 04 00 00 00 01 00 02 00 04 00 05 06 /* Comment: number of pages to erase coded in 2 bytes – # pages to erase are 5 because we count from zero; page numbers to erase coded in 2 bytes, with the significant bit first; checksum: XOR(00,00,00,04,00,00,00,02,00,04,00,01,00,05)=06 */
Output 2:
79 /* Comment: acknowledgement */
Table 10: Command input part 1, 2.
Input byte number | What does the corresponding byte represent? |
Byte 1 | 0x44 |
Byte 2 | 0xBB |
Bytes 3-4 | Special erase (0xFFFF, 0xFFFE, or 0xFFFD) or the number of pages to be erased (N+1 where 0 ≤ N < Maximum number of pages). |
Remaining bytes | Checksum of bytes 3-4 in case of special erase (0x00 if 0xFFFF or 0x01 if 0xFFFE, or 0x02 if 0xFFFD) or (2 x (N + 1)) bytes (page numbers coded on two bytes MSB first) and then the checksum for bytes 3-4 and all the following bytes |
Table 11: Command output part 2.
Output byte number | What does the corresponding byte represent? |
Byte 1 | ACK (erase completed) |
Snippet 5 shows the extended erase command being used. Depending on your MCU, you either have access to the "Erase Memory command" or the "Extended Erase memory" command. You can determine this by running the "Get" command to provide a list of available commands for your MCU.
Similar to the examples above, we send the command along with its 1's complement checksum. In part 2 of the command, we send the number of pages to erase (encoded in the first two bytes "00 04"). Byte 3 and byte 4 ("00 04") means we will erase 5 pages because we start the count from zero. The set of bytes (page 0: byte 5, byte 6; page 1: byte 7, byte 8; page 2: byte 9, byte 10; page 4: byte 11, byte 12; page 5: byte 13, byte 14) represent the page numbers to be erased (encoded in two byte and MSB first).
Lastly, for the checksum, we take the XOR of all the previous bytes (00, 04; 00, 00; 00, 01; 00, 02; 00, 04; 00, 05), which comes out be as shown in byte 15 ("06"). In the final output, we see an acknowledgment, indicating the memory pages were successfully erased. You may verify that the pages were erased using the "Read Memory" command or you may even use the STM32CubeProgrammer tool.