STM32 Bootloader UART – Bootloader Tutorial Part 4

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 STM32 Bootloader UART – Bootloader Tutorial Part 4.

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)

STM32 Bootloader UART

In our previous tutorial, we have just implemented the bootloader, that just runs the application. We are taking the previous source code as a base and starting this tutorial.

What we are going to implement in this tutorial?

In this tutorial, we are going to add a new feature to the bootloader which is used to load the new application. We will create one Host application (Windows Application) that sends the data to the STM32’s bootloader.

Enters to OTA mode

We will enable the USART2 for our communication and enable the Port C 13 pin as a GPIO input. Please refer to the below video to see how to enable the USART 2 and PC13.

When we press the button which is connected to the PC13, then the bootloader goes to the OTA mode and waits for the data from the Host machine. I have added the below code in the main function for this purpose.

/* Check the GPIO for 3 seconds */
GPIO_PinState OTA_Pin_state;
uint32_t end_tick = HAL_GetTick() + 3000;   // from now to 3 Seconds

printf("Press the User Button PC13 to trigger OTA update...\r\n");
do
{
  OTA_Pin_state = HAL_GPIO_ReadPin( GPIOC, GPIO_PIN_13 );
  uint32_t current_tick = HAL_GetTick();

  /* Check the button is pressed or not for 3seconds */
  if( ( OTA_Pin_state != GPIO_PIN_RESET ) || ( current_tick > end_tick ) )
  {
    /* Either timeout or Button is pressed */
    break;
  }
}while( 1 );

/*Start the Firmware or Application update */
if( OTA_Pin_state == GPIO_PIN_SET )
{
  printf("Starting Firmware Download!!!\r\n");
  /* OTA Request. Receive the data from the UART4 and flash */
  if( etx_ota_download_and_flash() != ETX_OTA_EX_OK )
  {
    /* Error. Don't process. */
    printf("OTA Update : ERROR!!! HALT!!!\r\n");
    while( 1 );
  }
  else
  {
    /* Reset to load the new application */
    printf("Firmware update is done!!! Rebooting...\r\n");
    HAL_NVIC_SystemReset();
  }
}

See the above code. We are reading the PC13 for 3 seconds. If we have pressed that button within the 3 seconds, then the bootloader enters into the OTA mode.  The function etx_ota_download_and_flash() is used to read the data from the host machine and write the firmware or application to the flash memory. Once it has successfully flashed, then we are rebooting the microcontroller using the HAL_NVIC_SystemReset() to load the new firmware or application. If we get any error in the OTA process, then we don’t run the application and we are stuck in the while(1).

Communication between PC and STM32

We have created our own protocol to transfer the OTA image to the bootloader. Please check the below image.

OTA communication

In the above image,

  • The Host is sending the OTA_START command to the STM32. Then STM32 responds with ACK or NACK.
  • If the host receives ACK, then it sends the OTA_HEADER to the STM32 with the firmware or application package’s details. The STM32 responds to that.
  • Then the host divides the firmware or application image into multiple chunks and sends it to the STM32.
  • When the host transmits everything, then finally it sends the OTA_END command.
  • The STM32 loads the new application.

The communication is fine. Now let’s see the Frame format.

Frame Format

We have created one generic frame format like below.

Generic Frame

The generic frame contains,

  • SOF – Start of the Frame (0xAA)
  • Packet type – It can be command, header, data, and response.
/*
 * Packet type
 */
typedef enum
{
  ETX_OTA_PACKET_TYPE_CMD       = 0,    // Command
  ETX_OTA_PACKET_TYPE_DATA      = 1,    // Data
  ETX_OTA_PACKET_TYPE_HEADER    = 2,    // Header
  ETX_OTA_PACKET_TYPE_RESPONSE  = 3,    // Response
}ETX_OTA_PACKET_TYPE_;
  • Data Length – The size of the data that we are going to attach in this frame excluding SOF, Packet Type, Data length, CRC32, and EOF.
  • Data to send.
  • CRC32 – Checksum of the Data.
  • EOF – End of the Frame (0xBB).

Refer to the below example formats for command, header, data, and response.

Frame Format Example

Yes, my explanation might be bad and confuse you. Please refer to the code for better understanding or check out the video.

Code – STM32 Bootloader UART Example

I am sure that you will get confused by seeing the code that I have provided in this post. Because, I have not provided you the complete source code of the Bootloader, Application, and the Host Application. So, please get the complete source code from GitHub.

Bootloader Code

I have not provided all the codes here. I have just provided the important part of the code here. Please get the complete bootloader project code from bootloader’s GitHub.

etx_ota_update.c

/*
 * etx_ota_update.c
 *
 *  Created on: 26-Jul-2021
 *      Author: EmbeTronicX
 */

#include <stdio.h>
#include "etx_ota_update.h"
#include "main.h"
#include <string.h>
#include <stdbool.h>

/* Buffer to hold the received data */
static uint8_t Rx_Buffer[ ETX_OTA_PACKET_MAX_SIZE ];

/* OTA State */
static ETX_OTA_STATE_ ota_state = ETX_OTA_STATE_IDLE;

/* Firmware Total Size that we are going to receive */
static uint32_t ota_fw_total_size;
/* Firmware image's CRC32 */
static uint32_t ota_fw_crc;
/* Firmware Size that we have received */
static uint32_t ota_fw_received_size;

static uint16_t etx_receive_chunk( uint8_t *buf, uint16_t max_len );
static ETX_OTA_EX_ etx_process_data( uint8_t *buf, uint16_t len );
static void etx_ota_send_resp( uint8_t type );
static HAL_StatusTypeDef write_data_to_flash_app( uint8_t *data,
                                        uint16_t data_len, bool is_full_image );

/**
  * @brief Download the application from UART and flash it.
  * @param None
  * @retval ETX_OTA_EX_
  */
ETX_OTA_EX_ etx_ota_download_and_flash( void )
{
  ETX_OTA_EX_ ret  = ETX_OTA_EX_OK;
  uint16_t    len;

  printf("Waiting for the OTA data...\r\n");

  /* Reset the variables */
  ota_fw_total_size    = 0u;
  ota_fw_received_size = 0u;
  ota_fw_crc           = 0u;
  ota_state            = ETX_OTA_STATE_START;

  do
  {
    //clear the buffer
    memset( Rx_Buffer, 0, ETX_OTA_PACKET_MAX_SIZE );

    len = etx_receive_chunk( Rx_Buffer, ETX_OTA_PACKET_MAX_SIZE );

    if( len != 0u )
    {
      ret = etx_process_data( Rx_Buffer, len );
    }
    else
    {
      //didn't received data. break.
      ret = ETX_OTA_EX_ERR;
    }

    //Send ACK or NACK
    if( ret != ETX_OTA_EX_OK )
    {
      printf("Sending NACK\r\n");
      etx_ota_send_resp( ETX_OTA_NACK );
      break;
    }
    else
    {
      //printf("Sending ACK\r\n");
      etx_ota_send_resp( ETX_OTA_ACK );
    }

  }while( ota_state != ETX_OTA_STATE_IDLE );

  return ret;
}

/**
  * @brief Process the received data from UART4.
  * @param buf buffer to store the received data
  * @param max_len maximum length to receive
  * @retval ETX_OTA_EX_
  */
static ETX_OTA_EX_ etx_process_data( uint8_t *buf, uint16_t len )
{
  ETX_OTA_EX_ ret = ETX_OTA_EX_ERR;

  do
  {
    if( ( buf == NULL ) || ( len == 0u) )
    {
      break;
    }

    //Check we received OTA Abort command
    ETX_OTA_COMMAND_ *cmd = (ETX_OTA_COMMAND_*)buf;
    if( cmd->packet_type == ETX_OTA_PACKET_TYPE_CMD )
    {
      if( cmd->cmd == ETX_OTA_CMD_ABORT )
      {
        //received OTA Abort command. Stop the process
        break;
      }
    }

    switch( ota_state )
    {
      case ETX_OTA_STATE_IDLE:
      {
        printf("ETX_OTA_STATE_IDLE...\r\n");
        ret = ETX_OTA_EX_OK;
      }
      break;

      case ETX_OTA_STATE_START:
      {
        ETX_OTA_COMMAND_ *cmd = (ETX_OTA_COMMAND_*)buf;

        if( cmd->packet_type == ETX_OTA_PACKET_TYPE_CMD )
        {
          if( cmd->cmd == ETX_OTA_CMD_START )
          {
            printf("Received OTA START Command\r\n");
            ota_state = ETX_OTA_STATE_HEADER;
            ret = ETX_OTA_EX_OK;
          }
        }
      }
      break;

      case ETX_OTA_STATE_HEADER:
      {
        ETX_OTA_HEADER_ *header = (ETX_OTA_HEADER_*)buf;
        if( header->packet_type == ETX_OTA_PACKET_TYPE_HEADER )
        {
          ota_fw_total_size = header->meta_data.package_size;
          ota_fw_crc        = header->meta_data.package_crc;
          printf("Received OTA Header. FW Size = %ld\r\n", ota_fw_total_size);
          ota_state = ETX_OTA_STATE_DATA;
          ret = ETX_OTA_EX_OK;
        }
      }
      break;

      case ETX_OTA_STATE_DATA:
      {
        ETX_OTA_DATA_     *data     = (ETX_OTA_DATA_*)buf;
        uint16_t          data_len = data->data_len;
        HAL_StatusTypeDef ex;

        if( data->packet_type == ETX_OTA_PACKET_TYPE_DATA )
        {
          /* write the chunk to the Flash (App location) */
          ex = write_data_to_flash_app( buf, data_len, ( ota_fw_received_size == 0) );

          if( ex == HAL_OK )
          {
            printf("[%ld/%ld]\r\n", ota_fw_received_size/ETX_OTA_DATA_MAX_SIZE, ota_fw_total_size/ETX_OTA_DATA_MAX_SIZE);
            if( ota_fw_received_size >= ota_fw_total_size )
            {
              //received the full data. So, move to end
              ota_state = ETX_OTA_STATE_END;
            }
            ret = ETX_OTA_EX_OK;
          }
        }
      }
      break;

      case ETX_OTA_STATE_END:
      {

        ETX_OTA_COMMAND_ *cmd = (ETX_OTA_COMMAND_*)buf;

        if( cmd->packet_type == ETX_OTA_PACKET_TYPE_CMD )
        {
          if( cmd->cmd == ETX_OTA_CMD_END )
          {
            printf("Received OTA END Command\r\n");

            //TODO: Very full package CRC

            ota_state = ETX_OTA_STATE_IDLE;
            ret = ETX_OTA_EX_OK;
          }
        }
      }
      break;

      default:
      {
        /* Should not come here */
        ret = ETX_OTA_EX_ERR;
      }
      break;
    };
  }while( false );

  return ret;
}

/**
  * @brief Receive a one chunk of data.
  * @param buf buffer to store the received data
  * @param max_len maximum length to receive
  * @retval ETX_OTA_EX_
  */
static uint16_t etx_receive_chunk( uint8_t *buf, uint16_t max_len )
{
  int16_t  ret;
  uint16_t index     = 0u;
  uint16_t data_len;

  do
  {
    //receive SOF byte (1byte)
    ret = HAL_UART_Receive( &huart2, &buf[index], 1, HAL_MAX_DELAY );
    if( ret != HAL_OK )
    {
      break;
    }

    if( buf[index++] != ETX_OTA_SOF )
    {
      //Not received start of frame
      ret = ETX_OTA_EX_ERR;
      break;
    }

    //Receive the packet type (1byte).
    ret = HAL_UART_Receive( &huart2, &buf[index++], 1, HAL_MAX_DELAY );
    if( ret != HAL_OK )
    {
      break;
    }

    //Get the data length (2bytes).
    ret = HAL_UART_Receive( &huart2, &buf[index], 2, HAL_MAX_DELAY );
    if( ret != HAL_OK )
    {
      break;
    }
    data_len = *(uint16_t *)&buf[index];
    index += 2u;

    for( uint16_t i = 0u; i < data_len; i++ )
    {
      ret = HAL_UART_Receive( &huart2, &buf[index++], 1, HAL_MAX_DELAY );
      if( ret != HAL_OK )
      {
        break;
      }
    }

    //Get the CRC.
    ret = HAL_UART_Receive( &huart2, &buf[index], 4, HAL_MAX_DELAY );
    if( ret != HAL_OK )
    {
      break;
    }
    index += 4u;

    //TODO: Add CRC verification

    //receive EOF byte (1byte)
    ret = HAL_UART_Receive( &huart2, &buf[index], 1, HAL_MAX_DELAY );
    if( ret != HAL_OK )
    {
      break;
    }

    if( buf[index++] != ETX_OTA_EOF )
    {
      //Not received end of frame
      ret = ETX_OTA_EX_ERR;
      break;
    }

  }while( false );

  if( ret != HAL_OK )
  {
    //clear the index if error
    index = 0u;
  }

  if( max_len < index )
  {
    printf("Received more data than expected. Expected = %d, Received = %d\r\n",
                                                              max_len, index );
    index = 0u;
  }

  return index;
}

/**
  * @brief Send the response.
  * @param type ACK or NACK
  * @retval none
  */
static void etx_ota_send_resp( uint8_t type )
{
  ETX_OTA_RESP_ rsp =
  {
    .sof         = ETX_OTA_SOF,
    .packet_type = ETX_OTA_PACKET_TYPE_RESPONSE,
    .data_len    = 1u,
    .status      = type,
    .crc         = 0u,                //TODO: Add CRC
    .eof         = ETX_OTA_EOF
  };

  //send response
  HAL_UART_Transmit(&huart2, (uint8_t *)&rsp, sizeof(ETX_OTA_RESP_), HAL_MAX_DELAY);
}

/**
  * @brief Write data to the Application's actual flash location.
  * @param data data to be written
  * @param data_len data length
  * @is_first_block true - if this is first block, false - not first block
  * @retval HAL_StatusTypeDef
  */
static HAL_StatusTypeDef write_data_to_flash_app( uint8_t *data,
                                        uint16_t data_len, bool is_first_block )
{
  HAL_StatusTypeDef ret;

  do
  {
    ret = HAL_FLASH_Unlock();
    if( ret != HAL_OK )
    {
      break;
    }

    //No need to erase every time. Erase only the first time.
    if( is_first_block )
    {

      printf("Erasing the Flash memory...\r\n");
      //Erase the Flash
      FLASH_EraseInitTypeDef EraseInitStruct;
      uint32_t SectorError;

      EraseInitStruct.TypeErase     = FLASH_TYPEERASE_SECTORS;
      EraseInitStruct.Sector        = FLASH_SECTOR_5;
      EraseInitStruct.NbSectors     = 2;                    //erase 2 sectors(5,6)
      EraseInitStruct.VoltageRange  = FLASH_VOLTAGE_RANGE_3;

      ret = HAL_FLASHEx_Erase( &EraseInitStruct, &SectorError );
      if( ret != HAL_OK )
      {
        break;
      }
    }

    for(int i = 0; i < data_len; i++ )
    {
      ret = HAL_FLASH_Program( FLASH_TYPEPROGRAM_BYTE,
                               (ETX_APP_FLASH_ADDR + ota_fw_received_size),
                               data[4+i]
                             );
      if( ret == HAL_OK )
      {
        //update the data count
        ota_fw_received_size += 1;
      }
      else
      {
        printf("Flash Write Error\r\n");
        break;
      }
    }

    if( ret != HAL_OK )
    {
      break;
    }

    ret = HAL_FLASH_Lock();
    if( ret != HAL_OK )
    {
      break;
    }
  }while( false );

  return ret;
}

For the explanation of this code, please see the video.

Application code

In the application, I have not changed much. I have updated the version to 0.2 from 0.1. And also, I have increased the Blue LED blinking delay to 5 seconds from 1 second. Please get the complete application project code from the application’s GitHub.

main.c

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define MAJOR 0   //APP Major version Number
#define MINOR 2   //APP Minor version Number
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

UART_HandleTypeDef huart3;

/* USER CODE BEGIN PV */
const uint8_t APP_Version[2] = { MAJOR, MINOR };
/* USER CODE END PV */

main()
{
  /* 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(5000);    //5 Sec delay
    HAL_GPIO_WritePin( GPIOB, GPIO_PIN_7, GPIO_PIN_RESET );
    HAL_Delay(5000);	//5 Sec delay
  }
  /* USER CODE END 3 */
}

Host Application code

Host Application starts the OTA transfer. I have created the PC application using the C language (Tested with Windows OS). Please get the complete application project code from the host application’s GitHub.

etx_ota_update_main.c

You just go through the host application code by yourself. You will easily understand.

/**************************************************
file: etx_ota_update_main.c
purpose: 
compile with the command: gcc etx_ota_update_main.c RS232\rs232.c -IRS232 -Wall -Wextra -o2 -o etx_ota_app
**************************************************/
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

#ifdef _WIN32
#include <Windows.h>
#else
#include <unistd.h>
#endif

#include "rs232.h"
#include "etx_ota_update_main.h"

uint8_t DATA_BUF[ETX_OTA_PACKET_MAX_SIZE];
uint8_t APP_BIN[ETX_OTA_MAX_FW_SIZE];

void delay(uint32_t us)
{
#ifdef _WIN32
    //Sleep(ms);
    __int64 time1 = 0, time2 = 0, freq = 0;

    QueryPerformanceCounter((LARGE_INTEGER *) &time1);
    QueryPerformanceFrequency((LARGE_INTEGER *)&freq);

    do {
        QueryPerformanceCounter((LARGE_INTEGER *) &time2);
    } while((time2-time1) < us);
#else
    usleep(us);
#endif
}

/* read the response */
bool is_ack_resp_received( int comport )
{
  bool is_ack  = false;

  memset(DATA_BUF, 0, ETX_OTA_PACKET_MAX_SIZE);

  uint16_t len =  RS232_PollComport( comport, DATA_BUF, sizeof(ETX_OTA_RESP_));

  if( len > 0 )
  {
    ETX_OTA_RESP_ *resp = (ETX_OTA_RESP_*) DATA_BUF;
    if( resp->packet_type == ETX_OTA_PACKET_TYPE_RESPONSE )
    {
      //TODO: Add CRC check
      if( resp->status == ETX_OTA_ACK )
      {
        //ACK received
        is_ack = true;
      }
    }
  }

  return is_ack;
}

/* Build the OTA START command */
int send_ota_start(int comport)
{
  uint16_t len;
  ETX_OTA_COMMAND_ *ota_start = (ETX_OTA_COMMAND_*)DATA_BUF;
  int ex = 0;

  memset(DATA_BUF, 0, ETX_OTA_PACKET_MAX_SIZE);

  ota_start->sof          = ETX_OTA_SOF;
  ota_start->packet_type  = ETX_OTA_PACKET_TYPE_CMD;
  ota_start->data_len     = 1;
  ota_start->cmd          = ETX_OTA_CMD_START;
  ota_start->crc          = 0x00;               //TODO: Add CRC
  ota_start->eof          = ETX_OTA_EOF;

  len = sizeof(ETX_OTA_COMMAND_);

  //send OTA START
  for(int i = 0; i < len; i++)
  {
    delay(1);
    if( RS232_SendByte(comport, DATA_BUF[i]) )
    {
      //some data missed.
      printf("OTA START : Send Err\n");
      ex = -1;
      break;
    }
  }

  if( ex >= 0 )
  {
    if( !is_ack_resp_received( comport ) )
    {
      //Received NACK
      printf("OTA START : NACK\n");
      ex = -1;
    }
  }
  printf("OTA START [ex = %d]\n", ex);
  return ex;
}

/* Build and Send the OTA END command */
uint16_t send_ota_end(int comport)
{
  uint16_t len;
  ETX_OTA_COMMAND_ *ota_end = (ETX_OTA_COMMAND_*)DATA_BUF;
  int ex = 0;

  memset(DATA_BUF, 0, ETX_OTA_PACKET_MAX_SIZE);

  ota_end->sof          = ETX_OTA_SOF;
  ota_end->packet_type  = ETX_OTA_PACKET_TYPE_CMD;
  ota_end->data_len     = 1;
  ota_end->cmd          = ETX_OTA_CMD_END;
  ota_end->crc          = 0x00;               //TODO: Add CRC
  ota_end->eof          = ETX_OTA_EOF;

  len = sizeof(ETX_OTA_COMMAND_);

  //send OTA END
  for(int i = 0; i < len; i++)
  {
    delay(1);
    if( RS232_SendByte(comport, DATA_BUF[i]) )
    {
      //some data missed.
      printf("OTA END : Send Err\n");
      ex = -1;
      break;
    }
  }

  if( ex >= 0 )
  {
    if( !is_ack_resp_received( comport ) )
    {
      //Received NACK
      printf("OTA END : NACK\n");
      ex = -1;
    }
  }
  printf("OTA END [ex = %d]\n", ex);
  return ex;
}

/* Build and send the OTA Header */
int send_ota_header(int comport, meta_info *ota_info)
{
  uint16_t len;
  ETX_OTA_HEADER_ *ota_header = (ETX_OTA_HEADER_*)DATA_BUF;
  int ex = 0;

  memset(DATA_BUF, 0, ETX_OTA_PACKET_MAX_SIZE);

  ota_header->sof          = ETX_OTA_SOF;
  ota_header->packet_type  = ETX_OTA_PACKET_TYPE_HEADER;
  ota_header->data_len     = sizeof(meta_info);
  ota_header->crc          = 0x00;               //TODO: Add CRC
  ota_header->eof          = ETX_OTA_EOF;

  memcpy(&ota_header->meta_data, ota_info, sizeof(meta_info) );

  len = sizeof(ETX_OTA_HEADER_);

  //send OTA Header
  for(int i = 0; i < len; i++)
  {
    delay(1);
    if( RS232_SendByte(comport, DATA_BUF[i]) )
    {
      //some data missed.
      printf("OTA HEADER : Send Err\n");
      ex = -1;
      break;
    }
  }

  if( ex >= 0 )
  {
    if( !is_ack_resp_received( comport ) )
    {
      //Received NACK
      printf("OTA HEADER : NACK\n");
      ex = -1;
    }
  }
  printf("OTA HEADER [ex = %d]\n", ex);
  return ex;
}

/* Build and send the OTA Data */
int send_ota_data(int comport, uint8_t *data, uint16_t data_len)
{
  uint16_t len;
  ETX_OTA_DATA_ *ota_data = (ETX_OTA_DATA_*)DATA_BUF;
  int ex = 0;

  memset(DATA_BUF, 0, ETX_OTA_PACKET_MAX_SIZE);

  ota_data->sof          = ETX_OTA_SOF;
  ota_data->packet_type  = ETX_OTA_PACKET_TYPE_DATA;
  ota_data->data_len     = data_len;

  len = 4;

  //Copy the data
  memcpy(&DATA_BUF[len], data, data_len );
  len += data_len;
  uint32_t crc = 0u;        //TODO: Add CRC

  //Copy the crc
  memcpy(&DATA_BUF[len], (uint8_t*)&crc, sizeof(crc) );
  len += sizeof(crc);

  //Add the EOF
  DATA_BUF[len] = ETX_OTA_EOF;
  len++;

  //printf("Sending %d Data\n", len);

  //send OTA Data
  for(int i = 0; i < len; i++)
  {
    delay(500);
    if( RS232_SendByte(comport, DATA_BUF[i]) )
    {
      //some data missed.
      printf("OTA DATA : Send Err\n");
      ex = -1;
      break;
    }
  }

  if( ex >= 0 )
  {
    if( !is_ack_resp_received( comport ) )
    {
      //Received NACK
      printf("OTA DATA : NACK\n");
      ex = -1;
    }
  }
  //printf("OTA DATA [ex = %d]\n", ex);
  return ex;
}

int main(int argc, char *argv[])
{
  int comport;
  int bdrate   = 115200;       /* 115200 baud */
  char mode[]={'8','N','1',0}; /* *-bits, No parity, 1 stop bit */
  char bin_name[1024];
  int ex = 0;
  FILE *Fptr = NULL;

  do
  {
    if( argc <= 2 )
    {
      printf("Please feed the COM PORT number and the Application Image....!!!\n");
      printf("Example: .\\etx_ota_app.exe 8 ..\\..\\Application\\Debug\\Blinky.bin");
      ex = -1;
      break;
    }

    //get the COM port Number
    comport = atoi(argv[1]) -1;
    strcpy(bin_name, argv[2]);

    printf("Opening COM%d...\n", comport+1 );

    if( RS232_OpenComport(comport, bdrate, mode, 0) )
    {
      printf("Can not open comport\n");
      ex = -1;
      break;
    }

    //send OTA Start command
    ex = send_ota_start(comport);
    if( ex < 0 )
    {
      printf("send_ota_start Err\n");
      break;
    }

    printf("Opening Binary file : %s\n", bin_name);

    Fptr = fopen(bin_name,"rb");

    if( Fptr == NULL )
    {
      printf("Can not open %s\n", bin_name);
      ex = -1;
      break;
    }

    fseek(Fptr, 0L, SEEK_END);
    uint32_t app_size = ftell(Fptr);
    fseek(Fptr, 0L, SEEK_SET);

    printf("File size = %d\n", app_size);

    //Send OTA Header
    meta_info ota_info;
    ota_info.package_size = app_size;
    ota_info.package_crc  = 0;          //TODO: Add CRC

    ex = send_ota_header( comport, &ota_info );
    if( ex < 0 )
    {
      printf("send_ota_header Err\n");
      break;
    }

    //read the full image
    if( fread( APP_BIN, 1, app_size, Fptr ) != app_size )
    {
      printf("App/FW read Error\n");
      ex = -1;
      break;
    }

    uint16_t size = 0;

    for( uint32_t i = 0; i < app_size; )
    {
      if( ( app_size - i ) >= ETX_OTA_DATA_MAX_SIZE )
      {
        size = ETX_OTA_DATA_MAX_SIZE;
      }
      else
      {
        size = app_size - i;
      }

      printf("[%d/%d]\r\n", i/ETX_OTA_DATA_MAX_SIZE, app_size/ETX_OTA_DATA_MAX_SIZE);

      ex = send_ota_data( comport, &APP_BIN[i], size );
      if( ex < 0 )
      {
        printf("send_ota_data Err [i=%d]\n", i);
        break;
      }

      i += size;
    }

    if( ex < 0 )
    {
      break;
    }

    //send OTA END command
    ex = send_ota_end(comport);
    if( ex < 0 )
    {
      printf("send_ota_end Err\n");
      break;
    }    

  } while (false);

  if(Fptr)
  {
    fclose(Fptr);
  }

  if( ex < 0 )
  {
    printf("OTA ERROR\n");
  }
  return(ex);
}

Build the application and bootloader. And only flash the Bootloader. Don’t flash the application, because we are going to update that using the bootloader.

Output

  • Press the reset button to run the bootloader.
  • Then press the user button (PC13) within 3 seconds to put the bootloader into OTA mode.
  • Now the Bootloader is waiting for the data from the Host application.
  • Open the command prompt in the host application directory and build the host application using the below command.

    gcc etx_ota_update_main.c RS232\rs232.c -IRS232 -Wall -Wextra -o2 -o etx_ota_app

  • Then run the application with two arguments. (The first argument is COMPORT number, and the second argument is Application binary path). See the example below.

.\etx_ota_app.exe 8 ..\..\Application\Debug\Blinky.bin

  • Now the OTA process starts. Wait until it finishes.
  • Once it has flashed the application, then it does automatically reboots.
  • See the Serial terminal. You can see the application version number is 0.2 and also check the Blue LED. Now it should blink with 5 seconds delay.

Video Explanation – STM32 Bootloader UART

In our next tutorial, we will add the CRC verification, SLOT 0, and SLOT 1 into the project.

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