Here is the bootloader code, let’s go through it and explain it step by step:

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "flash.h"
#include "srecord.h"

#define USER_APPLICATION_BASE_ADDRESS 0x8004000U

uint8_t srec_data[16];

void __svc(1)   EnablePrivilegedMode(void );
void __SVC_1    (void){
        __disable_irq();
        __set_CONTROL((__get_CONTROL( ))& 0xFFFFFFFE);  // enter priv mode
        __enable_irq();
}

static void BootJump(uint32_t *Address)
{
  
  /* 1. Make sure, the CPU is in privileged mode. */
  if (CONTROL_nPRIV_Msk & __get_CONTROL())
  {  /* not in privileged mode */
    EnablePrivilegedMode();
  }
  
  /* 2. Disable all enabled interrupts in NVIC. */
  NVIC->ICER[0] = 0xFFFFFFFF;
  NVIC->ICER[1] = 0xFFFFFFFF;
  NVIC->ICER[2] = 0xFFFFFFFF;
  NVIC->ICER[3] = 0xFFFFFFFF;
  NVIC->ICER[4] = 0xFFFFFFFF;
  NVIC->ICER[5] = 0xFFFFFFFF;
  NVIC->ICER[6] = 0xFFFFFFFF;
  NVIC->ICER[7] = 0xFFFFFFFF;
  
  /* 3. Disable all enabled peripherals which might generate interrupt requests,
        and clear all pending interrupt flags in those peripherals. Because this
        is device-specific, refer to the device datasheet for the proper way to
        clear these peripheral interrupts.  */
  
  
  /* 4. Clear all pending interrupt requests in NVIC. */
  NVIC->ICPR[0] = 0xFFFFFFFF;
  NVIC->ICPR[1] = 0xFFFFFFFF;
  NVIC->ICPR[2] = 0xFFFFFFFF;
  NVIC->ICPR[3] = 0xFFFFFFFF;
  NVIC->ICPR[4] = 0xFFFFFFFF;
  NVIC->ICPR[5] = 0xFFFFFFFF;
  NVIC->ICPR[6] = 0xFFFFFFFF;
  NVIC->ICPR[7] = 0xFFFFFFFF;
  
  /* 5. Disable SysTick and clear its exception pending bit, if it is used in the bootloader, e. g. by the RTX.  */
  SysTick->CTRL = 0;
  SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;
  
  /* 6. Disable individual fault handlers if the bootloader used them. */
  SCB->SHCSR &= ~(SCB_SHCSR_USGFAULTENA_Msk |
                  SCB_SHCSR_BUSFAULTENA_Msk |
                  SCB_SHCSR_MEMFAULTENA_Msk);
  
  /* 7. Activate the MSP, if the core is found to currently run with the PSP. */
  if( CONTROL_SPSEL_Msk & __get_CONTROL())
  {  /* MSP is not active */
    __set_CONTROL(__get_CONTROL() & ~CONTROL_SPSEL_Msk);
  }
  
  /*8. Load the vector table address of the user application into SCB->VTOR register.
       Make sure the address meets the alignment requirements of the register. */
  SCB->VTOR = (uint32_t)Address;
  
  /* 9. Set the MSP to the value found in the user application vector table. */
  __set_MSP( Address[0]);

  /* 10. Set the PC to the reset vector value of the user application via a function call. */
  ((void(*)(void))Address[1])();
  
}

/* UART related  */
void uart_init(void)
{
    /* Enable GPIOA clock */
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    
    /* Enable Tx and Rx pins alternate functions */
    /* PA2 is TX, PA3 is RX */
    GPIOA->MODER |= (2 << 2*2) + (2 << 2*3);
    GPIOA->AFR[0] |= (7 << 8) + (7 << 12);
    GPIOA->OSPEEDR |= (3 << 4) + (3 << 6);
    
    /* Enable USART2 clock */
    RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
    
    /* RM0383 page 504 */
    /* 1. Enable the USART by writing the UE bit in USART_CR1 register to 1. */
    USART2->CR1 |= USART_CR1_UE;
    
    /* 2. Program the M bit in USART_CR1 to define the word length. 8 bits */
    USART2->CR1 &= ~(USART_CR1_M);
    
    /* 3. Program the number of stop bits in USART_CR2. 1 stop bit */
    USART2->CR2 &= ~(USART_CR2_STOP_0 + USART_CR2_STOP_1);
    
    /* 4. Select DMA enable (DMAT) in USART_CR3 if Multi buffer Communication is to take
          place. Configure the DMA register as explained in multibuffer communication. */
    
    /* 5. Select the desired baud rate using the USART_BRR register. 115200 */
    //USART1->BRR |= (138 << 4) + (14);
    USART2->BRR |= (8 << 4) + (11); /* 115200 */
    //USART2->BRR |= (104 << 4) + (3); /* 9600 */
    
    /* 6. Set the TE bit in USART_CR1 to send an idle frame as first transmission. */
    USART2->CR1 |= USART_CR1_TE;
    
    /* 7. Write the data to send in the USART_DR register (this clears the TXE bit). Repeat this
          for each data to be transmitted in case of single buffer. */
    
    /* 8. After writing the last data into the USART_DR register, wait until TC=1. This indicates
          that the transmission of the last frame is complete. This is required for instance when
          the USART is disabled or enters the Halt mode to avoid corrupting the last
          transmission. */
    while(!(USART2->SR & USART_SR_TC));
    
    /* Enable receiver */
    USART2->CR1 |= USART_CR1_RE;
}

uint8_t uart_receive(void)
{
    uint8_t uart_rxd;
    while(!(USART2->SR & USART_SR_RXNE));
    uart_rxd = USART2->DR;
    return uart_rxd;
}

void uart_transmit(uint8_t data)
{
    USART2->DR = data;
    while(!(USART2->SR & USART_SR_TC));
}

void blue_button_init(void)
{
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;  /* Enable port a clock */
    GPIOA->MODER &= ~0x01;                /* PA0 is input */
}

uint8_t blue_button_read(void)
{
    uint8_t blue_button_data = 0;
    
    if (GPIOA->IDR & 0x00000001)
    {
        blue_button_data = 1;
    }
    else
    {
        blue_button_data = 0;
    }
    
    return blue_button_data;
}

uint8_t ascii_to_number(uint8_t ascii_input)
{
    uint8_t number_output = 0;
    
    switch (ascii_input)
    {
      case 'S':
      {
        break;
      }
      case '0':
      {
        number_output = 0x00;
        break;
      }
      case '1':
      {
        number_output = 0x01;
        break;
      }
      case '2':
      {
        number_output = 0x02;
        break;
      }
      case '3':
      {
        number_output = 0x03;
        break;
      }
      case '4':
      {
        number_output = 0x04;
        break;
      }
      case '5':
      {
        number_output = 0x05;
        break;
      }
      case '6':
      {
        number_output = 0x06;
        break;
      }
      case '7':
      {
        number_output = 0x07;
        break;
      }
      case '8':
      {
        number_output = 0x08;
        break;
      }
      case '9':
      {
        number_output = 0x09;
        break;
      }
      case 'A':
      {
        number_output = 0x0A;
        break;
      }
      case 'B':
      {
        number_output = 0x0B;
        break;
      }
      case 'C':
      {
        number_output = 0x0C;
        break;
      }
      case 'D':
      {
        number_output = 0x0D;
        break;
      }
      case 'E':
      {
        number_output = 0x0E;
        break;
      }
      case 'F':
      {
        number_output = 0x0F;
        break;
      }
      case 'a':
      {
        number_output = 0x0A;
        break;
      }
      case 'b':
      {
        number_output = 0x0B;
        break;
      }
      case 'c':
      {
        number_output = 0x0C;
        break;
      }
      case 'd':
      {
        number_output = 0x0D;
        break;
      }
      case 'e':
      {
        number_output = 0x0E;
        break;
      }
      case 'f':
      {
        number_output = 0x0F;
        break;
      }
      default:
      {
        break;
      }
  }
    
  return number_output;
    
}

int main(void) {
  
    blue_button_init();
        
    if (blue_button_read())
        {
        uart_init();
        
        uart_transmit('B');
        uart_transmit('o');
        uart_transmit('o');
        uart_transmit('t');
        uart_transmit(' ');
        uart_transmit('M');
        uart_transmit('o');
        uart_transmit('d');
        uart_transmit('e');
        uart_transmit('!');
        uart_transmit(0x0d);
        uart_transmit(0x0a);
        
        uint8_t uart_receive_buffer[100];
        uint8_t uart_rxd_old = 0x00;
        uint8_t uart_rxd = 0x00;
        int i = 0;
        int j = 0;
        uint32_t flash_address = 0;
        uint32_t srec_data_count = 0;
        uint32_t data_count = 0;
        
        Flash_Init();
        Flash_Erase_Sector(1);
    
        /* Receive SREC while "S7" is reached */
        while((uart_receive_buffer[1] != '7') & (uart_receive_buffer[1] != '0'))
        {
            i = 0;
            uart_rxd_old = 0x00;
            uart_rxd = 0x00;
            /* Receive over UART while CR, LF is sent */
            do
            {
                uart_rxd_old = uart_rxd;
                uart_rxd = uart_receive();
                if((uart_rxd != 0x0d) & (uart_rxd != 0x0a))
                {
                    uart_receive_buffer[i] = uart_rxd;
                }
                //uart_transmit(uart_rxd);
                i++;
            }
            while(((uart_rxd_old != 0x0d) & (uart_rxd != 0x0a)));
            
            /* Writting SREC data to FLASH */
            if ((uart_receive_buffer[0] == 'S') | (uart_receive_buffer[0] == 's'))    /* A valid SREC data */
            {
                if (uart_receive_buffer[1] == 0x33)              /* A 32 bit addressing */
                {
                    srec_data_count = ((uart_receive_buffer[2] - 0x30) << 4) + (uart_receive_buffer[3] - 0x30);
                    data_count = srec_data_count - 5;
                    flash_address = (ascii_to_number(uart_receive_buffer[4])  << 28) +
                                    (ascii_to_number(uart_receive_buffer[5])  << 24) +
                                    (ascii_to_number(uart_receive_buffer[6])  << 20) +
                                    (ascii_to_number(uart_receive_buffer[7])  << 16) +
                                    (ascii_to_number(uart_receive_buffer[8])  << 12) +
                                    (ascii_to_number(uart_receive_buffer[9])  <<  8) +
                                    (ascii_to_number(uart_receive_buffer[10])  << 4) +
                                    (ascii_to_number(uart_receive_buffer[11])      );
                          Flash_Write_x32(flash_address,      (ascii_to_number(uart_receive_buffer[12]) <<  4) +
                                                              (ascii_to_number(uart_receive_buffer[13]) <<  0) +
                                                              (ascii_to_number(uart_receive_buffer[14]) << 12) +
                                                              (ascii_to_number(uart_receive_buffer[15]) <<  8) +
                                                              (ascii_to_number(uart_receive_buffer[16]) << 20) +
                                                              (ascii_to_number(uart_receive_buffer[17]) << 16) +
                                                              (ascii_to_number(uart_receive_buffer[18]) << 28) +
                                                              (ascii_to_number(uart_receive_buffer[19]) << 24));
                        Flash_Write_x32(flash_address + 0x04, (ascii_to_number(uart_receive_buffer[20]) <<  4) +
                                                              (ascii_to_number(uart_receive_buffer[21]) <<  0) +
                                                              (ascii_to_number(uart_receive_buffer[22]) << 12) +
                                                              (ascii_to_number(uart_receive_buffer[23]) <<  8) +
                                                              (ascii_to_number(uart_receive_buffer[24]) << 20) +
                                                              (ascii_to_number(uart_receive_buffer[25]) << 16) +
                                                              (ascii_to_number(uart_receive_buffer[26]) << 28) +
                                                              (ascii_to_number(uart_receive_buffer[27]) << 24));
                        Flash_Write_x32(flash_address + 0x08, (ascii_to_number(uart_receive_buffer[28]) <<  4) +
                                                              (ascii_to_number(uart_receive_buffer[29]) <<  0) +
                                                              (ascii_to_number(uart_receive_buffer[30]) << 12) +
                                                              (ascii_to_number(uart_receive_buffer[31]) <<  8) +
                                                              (ascii_to_number(uart_receive_buffer[32]) << 20) +
                                                              (ascii_to_number(uart_receive_buffer[33]) << 16) +
                                                              (ascii_to_number(uart_receive_buffer[34]) << 28) +
                                                              (ascii_to_number(uart_receive_buffer[35]) << 24));
                        Flash_Write_x32(flash_address + 0x0C, (ascii_to_number(uart_receive_buffer[36]) <<  4) +
                                                              (ascii_to_number(uart_receive_buffer[37]) <<  0) +
                                                              (ascii_to_number(uart_receive_buffer[38]) << 12) +
                                                              (ascii_to_number(uart_receive_buffer[39]) <<  8) +
                                                              (ascii_to_number(uart_receive_buffer[40]) << 20) +
                                                              (ascii_to_number(uart_receive_buffer[41]) << 16) +
                                                              (ascii_to_number(uart_receive_buffer[42]) << 28) +
                                                              (ascii_to_number(uart_receive_buffer[43]) << 24));
                }
            }
        }
        
        uart_transmit('F');
        uart_transmit('l');
        uart_transmit('a');
        uart_transmit('s');
        uart_transmit('h');
        uart_transmit(' ');
        uart_transmit('W');
        uart_transmit('r');
        uart_transmit('i');
        uart_transmit('t');
        uart_transmit('e');
        uart_transmit(' ');
        uart_transmit('D');
        uart_transmit('o');
        uart_transmit('n');
        uart_transmit('e');
        uart_transmit('!');
        uart_transmit(0x0d);
        uart_transmit(0x0a);
            
        Flash_Deinit();
        
        uart_transmit('F');
        uart_transmit('l');
        uart_transmit('a');
        uart_transmit('s');
        uart_transmit('h');
        uart_transmit(' ');
        uart_transmit('D');
        uart_transmit('e');
        uart_transmit('i');
        uart_transmit('n');
        uart_transmit('i');
        uart_transmit('t');
        uart_transmit(' ');
        uart_transmit('D');
        uart_transmit('o');
        uart_transmit('n');
        uart_transmit('e');
        uart_transmit('!');
        uart_transmit(0x0d);
        uart_transmit(0x0a);
        
    }
  
/* Jump to application code space */      
  BootJump((uint32_t*)USER_APPLICATION_BASE_ADDRESS);
  
}

Pretty long, right? That is because the bootloader code is placed inside the main function, which needs explanations.

First, we define the start address of the user application in the flash memory space (See STM32 Bootloader Design – Part 1 article for details about the memory space ):

#define USER_APPLICATION_BASE_ADDRESS 0x8004000U

Then there is the prototype of a function for putting the CPU in privileged mode so we can use all instructions of the ARM core:

void __svc(1)   EnablePrivilegedMode(void );
void __SVC_1    (void){
        __disable_irq();
        __set_CONTROL((__get_CONTROL( ))& 0xFFFFFFFE);  // enter priv mode
        __enable_irq();
}

The next function is BootJump which takes as parameter the flash address, where the application code is located. We will call it at the end of the bootloader code. More details on it can be found in this article on Keil’s webpage.

The next few functions are easy to follow:

  • void uart_init(void) – UART initialization function for communication with the PC.
  • uint8_t uart_receive(void) – a polling function for getting a character over UART.
  • void uart_transmit(uint8_t data) – function for sending data over UART. We will use it to display on the PC a feedback information about what the microcontroller is doing.
  • uint8_t blue_button_init(void) – sets the GPIO where the blue button is connected to an input.
  • uint8_t blue_button_read(void) – this function checks if the blue button on the development kit is pressed. We will use it in the main function – if it’s pressed after power up then the bootloader code is executed, otherwise, the code jumps to the application code.
  • uint8_t ascii_to_number(uint8_t ascii_input) – this is a simple function that converts letters to hexadecimal numbers – at the end, the Srecord file is completed with numbers and letters and we have to convert it to numbers only.

And finally, we are at the main function. It looks big, but it’s easy to understand:

  1. If the blue button is pressed, the code enters into the mode, where the new code will be received over UART and written to the flash. “Boot Mode!” message is sent over UART as feedback.
  2. Next, we define some variables that will be used through the code.
  3. Flash_Init(); and Flash_Erase_Sector(1); erase the content of sector 1 of the flash. Remember this is the sector with the application code.
  4. We process the received data until S7 field is received (this is the start address termination field, for more information check https://en.wikipedia.org/wiki/SREC_(file_format) ).
  5. Then we collect all the data until the end of the line, process the data and this repeats.
  6. The next big chunk of code that starts after the comment line /* Writing SREC data to FLASH */ is responsible for writing the data to the flash in sector 1. At the moment only 32-bit addressing is implemented, but 16 and 24 bit are easy to add if necessary. The big summing lines are necessary because the code is transferred with the most significant literal first, but is written in the other way in the flash.
  7. After the S7 field is received, the microcontroller sends “Flash Write Done!” and “Flash Deinit Done!” over UART to show that data processing was completed.
  8. The last piece of code is the BootJump((uint32_t*)USER_APPLICATION_BASE_ADDRESS); function call, that jumps to the application code. We can now test it with both Srecord files we created and see that one of them controls the blue LED and the other one controls the red LED.

To check the code we need a PC terminal emulator. I picked up the one from this link – https://sites.google.com/site/terminalbpp/. It might be quite old, but it works pretty well. What is very important is that there is an option to send files – we need it to transfer the Srecord files we generated in the previous article (STM32 Bootloader Design – Part 2).

Figure 1 Terminal window

To test the firmware you can compile it and flash it to the development kit. Then hold the blue button and reset the microcontroller with the black button. Then release the blue button. This will enter the bootloader code and “Boot Mode!” is shown on the PC. Select “Send file” and upload one of the Srecord files. The last two messages are shown and the LED starts blinking. Do the same with the other Srecord file and you will see that the other LED is now blinking. Figure 1 shows the screenshot of the messages. Figure 2 shows the test setup.

Figure 2 Test Setup – STM32F411 discovery kit and FT2232H breakout board for implementing the UART communication

All the source codes and precompiled Srecord files can be found in Open4Tech GitHub repositories at:

https://github.com/open4tech/Application

https://github.com/open4tech/Bootloader

Figure 3 Blue LED application code running
Figure 4 Red LED application code running

Was this article helpful?