cancel
Showing results for 
Search instead for 
Did you mean: 

How to program a STM32 with another STM32 using embedded bootloader

B.Montanari
ST Employee

Programming a STM32 with another STM32

Summary

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.

Introduction

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.

1. Hardware and software requirements

Hardware: 

  • NUCLEO-L4R5ZI, acting as the host.
  • Any STM32 as target device, we use the NUCLEO-H503RB.
  • 1 USB Cable type micro-B (to NUCLEO-L4R5ZI).
  • 1 USB Cable type C (to NUCLEO-H503RB).
  • Cable jumpers.

Software: 

2. Development

Creating the project from scratch

Opening the software, click on File > New > STM32 Project

BMontanari_0-1700601401216.png

In the "Board Selector" tab, search for NUCLEO-L4R5ZI as the commercial part number, select it on the list, then click next.

BMontanari_1-1700601401221.png

Give a name to the project, all other points can be left as default, then click finish.

BMontanari_2-1700601401222.png

With this done, the graphical portion appears, so that we can set the necessary configurations on the peripherals.

2.1 Project settings

2.1.1 Pinout & configuration tab

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.”

 

BMontanari_3-1700601401224.png

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:

BMontanari_4-1700601401228.png

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

BMontanari_5-1700601401234.png

The GPIO Settings tab should be as follows:

BMontanari_6-1700601401235.png

Add the LPUART peripheral to print the debug messages. The configuration is shown in the image below:

 

BMontanari_7-1700601401239.png

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:

BMontanari_8-1700601401239.png

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”:

BMontanari_32-1700601664604.png
Now, add 2 pins to control the target’s BOOT0 and NRST pins:

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.

BMontanari_33-1700601738232.png

Rename PD7 as BOOT_CTRL_PIN and PD4 as NRST_CTRL_PIN.

BMontanari_34-1700601807697.png

 

2.1.2 Project Manager Tab

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’:

BMontanari_35-1700601827680.png

 To finish the project settings, generate the code pressing alt + K or clicking on the gear icon:

 BMontanari_36-1700601845906.png

Alright! With all hardware settings done, let us dig into the code part!

3. Let's Code!

To implement our code, we will refer to the AN3155’s flowchart regarding the bootloader code sequence via the USART protocol:

AN3155, page 5AN3155, page 5

 


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:

AN3155, page 7AN3155, page 7

 With this information in mind, you can start your implementation. You need to download the following 2 files:

  • bootloader.h
  • bootloader.c

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 */

 

4. Running the Application

4.1 How to load the application

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:

BMontanari_0-1700604705614.png

Verify that it returned 0 errors and 0 warnings:

BMontanari_2-1700604705616.png

To run the application, right-click in the Project Name > Run As > 1 STM32 C/C++ Application.

BMontanari_3-1700604705618.png

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. 

BMontanari_4-1700604705619.png


In the “Memory & File editing” section, click the “+” button. Select Open file and load the binary file to be programmed into the target board.

BMontanari_5-1700604705623.png

Select the download address. It must be the same as previously defined in the IMAGE_ADDRESS define.

BMontanari_8-1700605009422.png

Great! All the first programming part is correctly set!

It is time to do the hardware connections to see our application successfully working!

4.2 Hardware connections   

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.

4.3 Final Step

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.

BMontanari_9-1700605054857.png

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.

 

BMontanari_10-1700605064258.png


The application should work successfully. 

Hope this article was useful for your application!

Thanks for reading. 

5. References

  1. https://www.st.com/resource/en/application_note/an2606-stm32-microcontroller-system-memory-boot-mode-stmicroelectronics.pdf
  2. https://www.st.com/resource/en/application_note/an3155-usart-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf
  3. NUCLEO-L4R5ZI - STM32 Nucleo-144 development board with STM32L4R5ZI MCU, supports Arduino, ST Zio and morpho connectivity - STMicroelectronics






Comments
Fabio7777
Associate

 

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

Version history
Last update:
‎2024-07-04 05:13 AM
Updated by: