on
2023-11-28
03:35 AM
- edited on
2024-07-04
05:13 AM
by
Laurids_PETERSE
In this article, we implement the host side using one STM32 to program another STM32 via the USART interface by leveraging the native system bootloader. The entire project is available at our GitHub page. However, all the steps needed are explained and presented in case a different STM32 platform needs to be used as the host.
This implementation is based on two application notes, AN2606, which details each STM32 series system memory boot mode. This includes how to enter in boot mode and which pins are available for the USART port for each device. Application note AN3155 covers the USART protocol used in the STM32 bootloader.
It is worth highlighting that in this simple programmer example, the STM32’s host flash has the binary to program the other STM32. This means that the binary size is limited to the amount of flash memory available at the selected STM32 host, minus the size of the programmer application. The name IMAGE_ADDRESS is used as a reference to the starting position of the binary in the host’s internal memory.
Opening the software, click on File > New > STM32 Project
In the "Board Selector" tab, search for NUCLEO-L4R5ZI as the commercial part number, select it on the list, then click next.
Give a name to the project, all other points can be left as default, then click finish.
With this done, the graphical portion appears, so that we can set the necessary configurations on the peripherals.
First, go to the System Core category and set “Trace Asynchronous Sw” as “Debug”. Select System Wake-Up 2 and “SysTick” as the “Timebase Source.”
Going to the Connectivity category, set the USART2 configuration for our communication with the target board.
In the “Parameter Settings” tab, set the Baud Rate, Word Length, Parity and Stop Bits, according to the AN3155:
Pressing and Hold “Ctrl + left Click” on the PA2 and PA3 pins, with this we can drag and drop the pin to remap them to different GPIO location. Reallocate them as follows:
PA2 → PD5
PA3 → PD6
The GPIO Settings tab should be as follows:
Add the LPUART peripheral to print the debug messages. The configuration is shown in the image below:
Remapping the GPIO Pins of the LPUART Peripheral to PG7 and PG8, we can use the embedded STLINK’s VCOM Port to transmit the debug messages:
Add the User Button B1 so we can control when our application starts programming the target device:
Click in the PC13 pin > Select GPIO_Input
Right-click over the pins and rename them to control these pins later in the code. Rename the pin to “USER BUTTON”:
For that, click in the PD7 pin and select “GPIO_Output,” it is our BOOT0 Control pin. For the reset control pin, do the same for the PD4.
Rename PD7 as BOOT_CTRL_PIN and PD4 as NRST_CTRL_PIN.
In the Project Manager Tab, go to Code Generator and mark the checkbox to generate the peripheral initialization functions separately as a pair of ‘.c/.h’:
To finish the project settings, generate the code pressing alt + K or clicking on the gear icon:
Alright! With all hardware settings done, let us dig into the code part!
To implement our code, we will refer to the AN3155’s flowchart regarding the bootloader code sequence via the USART protocol:
This is helpful for us to understand the needed order of commands to start and keep a proper communication between target and host (programmer).
Additionally, the AN3155 also lists and describes the supported commands to be used for the bootloader through the USART protocol:
With this information in mind, you can start your implementation. You need to download the following 2 files:
These files have the functions responsible for making the target board enter in the bootloader and the entire communication process with the programmer. The files can be downloaded from the article (see attachments below) or from the GitHub page.
Great! We are almost finishing our application to program a STM32 with another STM32!
Do the same with the main.c file. You can simply add the code below, using the USER CODE sections or replace the autogenerated main.c with the one available at GitHub or in the attachments of this article.
NOTE: In the main.c file, be careful to write only in the USER CODE BEGIN and USER CODE END areas. Code out of the “USER CODE BEGIN WHILE” / “USER CODE END WHILE” section is NOT preserved in case of regenerating the project.
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "bootloader.h"
#include "stdio.h"
/* USER CODE END Includes */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
int __io_putchar(int ch) {
HAL_UART_Transmit(&hlpuart1, (uint8_t *)&ch, 1, 100);
return 0;
}
/* USER CODE END PM */
/* USER CODE BEGIN PV */
uint8_t RxBuffer[32];
stm32_cmd BootDataCmd;
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
void Boot_Process_Init (void);
/* USER CODE END PFP */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (HAL_GPIO_ReadPin(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin) == GPIO_PIN_SET)
{
/* Calls Boot Function to start programming */
Boot_Process_Init();
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE BEGIN 4 */
/* Application Function */
void Boot_Process_Init (void)
{
/* Local variables */
uint32_t scan = 0xFFFFFFFF, counter = 0;
uint32_t SIZE;
printf("***System Boot demo***\r\n");
/* Synchronize with target bootloader ###################################### */
stm32_init(&huart2,&BootDataCmd);
stm32_read_memory(&huart2, TARGET_OPB_START_ADDRESS, RxBuffer, 32);
stm32_erase_memory(&huart2,EX_EMEM_COMMAND,1,0xFF);
/* Verifies when scan variable achieved last data of the Image to be programmed */
while (scan == 0xFFFFFFFF)
{
counter++;
scan = *(uint32_t*)(FLASH_SIZE - (4*counter));
}
/* SIZE: Size of the image to be Programmed
* FLASH_SIZE: Host total flash size
* IMAGE_ADDRESS: Start address of the image to be Programmed
* FLASH_BASE:Host Flash start address
* (4*(Counter-1)): Is the amount of FLASH to achieve the final data of the Image to be programmed
*/
SIZE = (FLASH_SIZE - (IMAGE_ADDRESS - FLASH_BASE) - ((counter)*4));
/* Writes up to 256Bytes of memory, it can be from FLASH or RAM */
for(uint16_t i = 0; i < SIZE; i+=256)
{
stm32_write_memory(&huart2, (TARGET_FLASH_START_ADDRESS+i), ((uint8_t*)IMAGE_ADDRESS+i), 256);
}
/* Reads up to 256Bytes of memory, it can be from FLASH or RAM */
stm32_read_memory(&huart2, TARGET_FLASH_START_ADDRESS, RxBuffer, 32);
/* Executes the downloaded binary code */
stm32_go(&huart2, TARGET_FLASH_START_ADDRESS);
}
/* USER CODE END 4 */
To load our application project into the programmer board, we have to build the project to verify errors and warnings, and then run the application!
To build the project, press “Ctrl + b” or click in the hammer icon:
Verify that it returned 0 errors and 0 warnings:
To run the application, right-click in the Project Name > Run As > 1 STM32 C/C++ Application.
With all set, we must add a binary to be programmed in our target board. Connect the NUCLEO-L4R5ZI board to the computer, open the STM32CubeProgrammer, select its serial number and press connect.
In the “Memory & File editing” section, click the “+” button. Select Open file and load the binary file to be programmed into the target board.
Select the download address. It must be the same as previously defined in the IMAGE_ADDRESS define.
Great! All the first programming part is correctly set!
It is time to do the hardware connections to see our application successfully working!
The set-up to implement the application is as follows.
Perform the five cables connection on the boards, as table below:
NUCLEO-L4R5ZI |
NUCLEO-H503RB |
GND |
GND |
BOOT_CTRL_PIN (PD7) |
BOOT0 |
NRST_CTRL_PIN (PD4) |
NRST |
USART2_TX (PD5) |
USART2_RX (PA15) |
USART2_RX (PD6) |
USART2_TX (PA5) |
- Connect the NUCLEO-L4R5ZI Board to your PC with a USB cable type A to micro-B to STLINK connector (USB_PWR on CN1 connector);
- Connect the NUCLEO-H503 Board to your PC with a USB cable type A to C to STLINK connector (USB_STLK on CN1 connector);
NOTE: The target USART pins must be chosen according to the MCU’s bootloader pins specified by AN2606.
With both STM32 boards powered up and connected as specified above, press the User Button B1 of the NUCLEO-L4R5ZI and the code is programmed! You can debug the messages via Tera Term. To show that the program is working properly or verify error messages, you can also open a VCOM Terminal and verify the debug messages received by the host, to check command errors, NACKs received, etc.
With the STM32CubeProgrammer, you can connect to the target board and click the “+” icon. Select “Compare Memory with file” and check if the target memory content matches with the binary file downloaded.
The application should work successfully.
Hope this article was useful for your application!
Thanks for reading.
Hello, B. Montanari!
Your article met our needs!
We are migrating from the STM8S to the STM32C0 and needed to develop a stand-alone programmer for the new microcontroller for the production line, as we did for the STM8S.
The article was very well written and explained the bootloader recording process.
We validated the effectiveness of the firmware provided through a NUCLEO-L4R5ZI and successfully adapted it to the features of the STM32C0!
Thank you very much!
Example of AN3155 on the Arduino MKR WAN1300 (Murata w/STM32L072)
https://github.com/arduino-libraries/MKRWAN/tree/master/examples/MKRWANFWUpdate_standalone