Simple STM32 Bootloader Implementation – Bootloader Tutorial Part 3

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. This post is Simple STM32 Bootloader Implementation – Bootloader Tutorial Part 3.

We have attached the video explanation also at the bottom of this post.

Prerequisites

In this simple STM32 bootloader example, we will be using the concepts which have been explained already in the below-given tutorials. So I would request you to go through those tutorials first if you are not familiar with those topics.

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.

Bootloader and Application PlacementSo, 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 into 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. You will understand.

Simple STM32 Bootloader

Create the project

For this demonstration, I will be using the STM32CubeIDE. Frankly speaking, I don’t like this kind of IDEs 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 to generate the code for us to save some time. But if you are implementing the bootloader for your project, then I suggest you to 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.

And I have added the below code in 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, 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 are 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 as 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, then we have to tell the controller that the vector table address is 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 */

Build 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 Green LED is ON and When the application is running, the Blue LED is blinking with a 1-second delay.

Video Explanation

In our next tutorial, we will use the bootloader to update the application.

You can also read the below tutorials.

Linux Device Driver TutorialsC Programming Tutorials
FreeRTOS TutorialsNuttX RTOS Tutorials
RTX RTOS TutorialsInterrupts Basics
I2C Protocol – Part 1 (Basics)I2C Protocol – Part 2 (Advanced Topics)
STM32 TutorialsLPC2148 (ARM7) Tutorials
PIC16F877A Tutorials8051 Tutorials
Unit Testing in C TutorialsESP32-IDF Tutorials
Raspberry Pi TutorialsEmbedded Interview Topics
Reset Sequence in ARM Cortex-M4BLE Basics
VIC and NVIC in ARMSPI – Serial Peripheral Interface Protocol
Bootloader TutorialsRaspberry PI Pico Tutorials
5 1 vote
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x