This article is a continuation of the Series on STM32 Bootloader and carries the discussion on Bootloader design and implementation. The aim of this series is to provide easy and practical examples that anyone can understand.
In this article, we are going to learn about STM32 Bootloader Implementation. We have attached the video explanation at the bottom of this post.
Table of Contents
Prerequisites
In this simple STM32 bootloader example, we will be using the concepts that have been explained already in the tutorials given below. So I would request you to go through those tutorials first if you are not familiar with those topics.
- Bootloader Basics
- Bootloader and Application Design
- Volatile Keyword in Embedded C
- Function Pointer in C
We are providing the video format of this Bootloader series. Please check out the video playlist. You can also get the source code of this tutorial from GitHub.
Hardware Required
- STM32F767Zi Nucleo (You can use any STM32 boards. But use the addresses based on your controller).
- LED (If you don’t have onboard LED)
Simple STM32 Bootloader – Introduction
In our last post, we have designed the bootloader and application. And also we break down the implementation into multiple tasks. In this tutorial, we will implement the first task called “Write a simple bootloader that runs the application“.
What we are going to Implement in this tutorial?
In this tutorial, I am going to create two projects. One is for the bootloader and another one is for the Application. Both projects will create different binary files. We are going to place these binary files in our Flash memory.
As we discussed in the previous post, I will place the bootloader in the starting position of the flash, which is 0x08000000
. And application into 0x08040000
.
Check the below image for your understanding.
So, now what will happen when you press the RESET button? It reads the BOOT pin and as it is 0, then it starts from the flash memory.
We have placed the bootloader in the starting position. So, it will read the stack address from 0x08000000
and store that in the MSP register. Then it takes the reset handler address from 0x08000004
and jumps to that.
Then it does the all memory initialization of data segments and it calls the bootloader’s main function. In the bootloader’s main function, we will do some other operations.
In this example, I am not going to do anything except ON/OFF the green LED. Once after that, we have to call the application.
This is what we are going to implement in this tutorial. If you don’t understand it properly, then I would recommend you to see the video explanation given at the end of the tutorial, you will understand.
Simple STM32 Bootloader Implementation
Create the project
For this demonstration, I will be using the STM32CubeIDE. Frankly speaking, I don’t like this kind of IDE that automatically generates the code. I like to write everything from scratch. Then only I can able to learn properly.
But as this is just a bootloader tutorial, we will let this IDE generate the code for us to save some time. But if you are implementing the bootloader for your project, then I suggest you use it without this auto-generated code.
Create the bootloader
Please check the below video to create the project and enable GPIO (PB0) and UART 3. This will be easy to come along with me.
Once you created the project and enabled the GPIO (PB0) and UART3 (PD8 and PD9), now we will have the auto-generated code. We will start from here.
Like the C program, printf
won’t work here. We have to redirect the printf
to our debug COM port (UART3). Add the code given below to your project.
Note: Whenever you are adding your code, you must include your code between the USER CODE BEGIN and USER CODE END. If you add your code anywhere else, then it will be removed when you regenerate the code. Keep this in your mind.
/* USER CODE BEGIN Includes */ #include <stdio.h> /* USER CODE END Includes */ /* USER CODE BEGIN 4 */ /** * @brief Print the characters to UART (printf). * @retval int */ #ifdef __GNUC__ /* With GCC, small printf (option LD Linker->Libraries->Small printf set to 'Yes') calls __io_putchar() */ int __io_putchar(int ch) #else int fputc(int ch, FILE *f) #endif /* __GNUC__ */ { /* Place your implementation of fputc here */ /* e.g. write a character to the UART3 and Loop until the end of transmission */ HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; } /* USER CODE END 4 */
Now, you can use the printf
. That will be redirected to the UART3. So, you watch those prints from the serial terminal.
I have added the below code to my main.c
.
/* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ #define MAJOR 0 // BL Major version Number #define MINOR 1 // BL Minor version Number /* USER CODE END PM */ /* USER CODE BEGIN PV */ const uint8_t BL_Version[2] = { MAJOR, MINOR }; /* USER CODE END PV */ /* USER CODE BEGIN PFP */ static void goto_application( void ); /* USER CODE END PFP */ /* USER CODE BEGIN 4 */ /** * @brief Jump to application from the Bootloader * @retval None */ static void goto_application(void) { printf("Gonna Jump to Application\n"); void (*app_reset_handler)(void) = (void*)(*((volatile uint32_t*) (0x08040000 + 4U))); //__set_MSP(*(volatile uint32_t*) 0x08040000); // Turn OFF the Green Led to tell the user that Bootloader is not running HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET ); //Green LED OFF app_reset_handler(); //call the app reset handler } /* USER CODE END 4 */
What did we do in the above code?
In the above code, we have created one variable for the Bootloader version. And, we have created the goto_application
function which calls the application reset handler. We have used the function pointer and volatile keyword for this purpose.
How do we call the application? We know the vector table address (0x08040000
) of the application. In that address, the stack memory’s starting address will be present. Then in the ( 0x08040000 + 4
) address, the application reset handler’s address will be present.
Now we have the application reset handler’s address. Then using the function pointer, we can call the application’s reset handler. Simple right? Before jumping to the application, I will turn off the Green LED (PB0).
I will call this goto_application
function from the main function.
Then check the flash address in the linker script called STM32F767ZITX_FLASH.ld
file. In the memory definition, make sure you have the below address and the size present. If not, then please modify it.
/* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 512K FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 64K /* Allocating 64K for Application */ }
Now, we are ready with the bootloader. Let’s create the application.
Create the Application
Please check the below video to create the project and enable GPIO (PB7) and UART 3.
Like bootloader, we will enable the printf
by adding this code.
/* USER CODE BEGIN Includes */ #include <stdio.h> /* USER CODE END Includes */ /* USER CODE BEGIN 4 */ /** * @brief Print the characters to UART (printf). * @retval int */ #ifdef __GNUC__ /* With GCC, small printf (option LD Linker->Libraries->Small printf set to 'Yes') calls __io_putchar() */ int __io_putchar(int ch) #else int fputc(int ch, FILE *f) #endif /* __GNUC__ */ { /* Place your implementation of fputc here */ /* e.g. write a character to the UART3 and Loop until the end of transmission */ HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; } /* USER CODE END 4 */
Then we will be creating one variable for the application version and simply blink the led in the while loop with a one-second delay.
/* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ #define MAJOR 0 //APP Major version Number #define MINOR 1 //APP Minor version Number /* USER CODE END PM */ /* USER CODE BEGIN PV */ const uint8_t APP_Version[2] = { MAJOR, MINOR }; /* USER CODE END PV */ /* USER CODE BEGIN 2 */ printf("Starting Application(%d.%d)\n", APP_Version[0], APP_Version[1] ); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_GPIO_WritePin( GPIOB, GPIO_PIN_7, GPIO_PIN_SET ); HAL_Delay(1000); //1 Sec delay HAL_GPIO_WritePin( GPIOB, GPIO_PIN_7, GPIO_PIN_RESET ); HAL_Delay(1000); //1 Sec delay } /* USER CODE END 3 */
Then, check the linker script in the Application project (STM32F767ZITX_FLASH.ld
). As the starting address of the application is 0x08040000
, we must set the flash as 0x08040000
. And set the size to 512KB. Refer to the code below.
/* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 512K FLASH (rx) : ORIGIN = 0x8040000, LENGTH = 512K /* Allocating 512K for Application */ }
Once you have done this, we have to tell the controller that the vector table address has been changed to 0x08040000
. To do that, go to the application source code -> core -> Src -> system_stm32_f7xx.c.
Please uncomment the USER_VECT_TAB_ADDRESS
macro. Then, set the vector table offset. Right now offset is 0, which means, it is pointing to 0x08000000
. We have to change that to 0x08040000
. So, just writing 0x40000
is enough. Now, it will point to 0x08040000
. Refer to the below code.
#define USER_VECT_TAB_ADDRESS #if defined(USER_VECT_TAB_ADDRESS) /*!< Uncomment the following line if you need to relocate your vector Table in Sram else user remap will be done in Flash. */ /* #define VECT_TAB_SRAM */ #if defined(VECT_TAB_SRAM) #define VECT_TAB_BASE_ADDRESS RAMDTCM_BASE /*!< Vector Table base address field. This value must be a multiple of 0x200. */ #define VECT_TAB_OFFSET 0x00000000U /*!< Vector Table base offset field. This value must be a multiple of 0x200. */ #else #define VECT_TAB_BASE_ADDRESS FLASH_BASE /*!< Vector Table base address field. This value must be a multiple of 0x200. */ #define VECT_TAB_OFFSET 0x00040000U /*!< Vector Table base offset field. This value must be a multiple of 0x200. */ #endif /* VECT_TAB_SRAM */ #endif /* USER_VECT_TAB_ADDRESS */
We successfully built the Application and Bootloader!!!
I did not provide the full source code in this post. I know that you will get confused by seeing the source code. Please refer to the code that we have posted on GitHub, and refer to the video explanation.
Let’s flash the bootloader to 0x08000000
and application to 0x08040000
.
Output
Now you will see the Bootloader and Application prints in the serial terminal. When the bootloader is running, you can see that the Green LED is ON and When the application is running, the Blue LED is blinking with a 1-second delay.
Video Explanation
Please check the below video explanation.
In our next tutorial, we will use the bootloader to update the application. We have posted another variant of the Bootloader development using the STM32F103. Please check that and learn that too.
You can also read the below tutorials.
Embedded Software | Firmware | Linux Devic Deriver | RTOS
Hi, I’m SLR. I am a tech blogger and an Embedded Engineer. I am always eager to learn and explore tech-related concepts. And also, I wanted to share my knowledge with everyone in a more straightforward way with easy practical examples. I strongly believe that learning by doing is more powerful than just learning by reading. I love to do experiments. If you want to help or support me on my journey, consider sharing my articles, or Buy me a Coffee! Thank you for reading my blog! Happy learning!