STM32 Bootloader Source Code – Bootloader Part 5

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 Source Code – Bootloader Tutorial Part 5.

We have attached the video explanation also at the bottom of this post. We have posted another variant of the Bootloader development using the STM32F103. Please check that and learn that too.

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)

Features Added in this tutorial

In our previous tutorial, we were writing the application into the actual application flash area (0x08040000). So, I took that as a base and added the below features on top of that.

  • CRC Verification
  • Added two slots to store the application (slot 0 and slot 1).
  • Assigned one sector to hold the configurations and slot information.

I would recommend you to see the tutorial video rather than reading this. Because you will understand easily without a doubt.

Memory arrangement

I have divided the flash memory into multiple segments. I have allocated one 128KB sector to store the configurations. And this Application area remains the same as our previous tutorial. Then one 512KB block for slot 0 and another 512KB for slot 1. So, here I am going to use two slots to store the two versions of the application. Please refer to the below image.

Source Code

First, enable the CRC in your project configuration like below and generate the code.

STM32 Application Source Code

In the application, I have not changed much. I have updated the version to 0.3 from 0.2. And also, I have decreased the Blue LED blinking delay to 200 milliseconds from 5 seconds. Please get the complete application project code from the application’s GitHub. For the explanation of this code, please see the video.

main.c

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define MAJOR 0   //APP Major version Number
#define MINOR 3   //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(200);    //200ms delay
    HAL_GPIO_WritePin( GPIOB, GPIO_PIN_7, GPIO_PIN_RESET );
    HAL_Delay(200);  //200ms delay
  }
  /* USER CODE END 3 */
}

STM32 Bootloader Source 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 the bootloader’s GitHub.

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "etx_ota_update.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

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

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

CRC_HandleTypeDef hcrc;

UART_HandleTypeDef huart2;
UART_HandleTypeDef huart3;

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

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART3_UART_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_CRC_Init(void);
/* USER CODE BEGIN PFP */
static void goto_application( void );
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  MX_USART2_UART_Init();
  MX_CRC_Init();
  /* USER CODE BEGIN 2 */
  // Turn ON the Green Led to tell the user that Bootloader is running
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET );    //Green LED ON
  printf("Starting Bootloader(%d.%d)\r\n", BL_Version[0], BL_Version[1] );
  //HAL_Delay(2000);   //2sec delay for nothing


  //Read the reboot cause and act accordingly
  printf("Reading the reboot reason...\r\n");

  ETX_GNRL_CFG_ *cfg          = (ETX_GNRL_CFG_*) (ETX_CONFIG_FLASH_ADDR);
  bool          goto_ota_mode = false;

  switch( cfg->reboot_cause )
  {
  case ETX_NORMAL_BOOT:
    {
      /*
       * It is a normal boot. So, do nothing here.
       */
      printf("Normal Boot\r\n");
      break;
    }
  case ETX_OTA_REQUEST:
  case ETX_FIRST_TIME_BOOT:
    {
      /*
       * Application has requested for the OTA update or this is the first
       * time boot. So, don't wait for the user to press the button.
       * Directly go to OTA mode.
       */
      printf("First time boot / OTA Request...\r\n");
      printf("Going to OTA mode...\r\n");
      goto_ota_mode = true;
      break;
    }
  case ETX_LOAD_PREV_APP:
    {
      //TODO: Implement
      break;
    }
  default:
    /* should not get here */
    break;
  };

  /* 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( !goto_ota_mode );

  /*Start the Firmware or Application update */
  if( ( OTA_Pin_state == GPIO_PIN_SET ) || ( goto_ota_mode ) )
  {
    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();
    }
  }

  //Load the updated app, if it is available
  load_new_app();

  // Jump to application
  goto_application();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART2|RCC_PERIPHCLK_USART3;
  PeriphClkInitStruct.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
  PeriphClkInitStruct.Usart3ClockSelection = RCC_USART3CLKSOURCE_PCLK1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief CRC Initialization Function
  * @param None
  * @retval None
  */
static void MX_CRC_Init(void)
{

  /* USER CODE BEGIN CRC_Init 0 */

  /* USER CODE END CRC_Init 0 */

  /* USER CODE BEGIN CRC_Init 1 */

  /* USER CODE END CRC_Init 1 */
  hcrc.Instance = CRC;
  hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_ENABLE;
  hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_ENABLE;
  hcrc.Init.InputDataInversionMode = CRC_INPUTDATA_INVERSION_NONE;
  hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_DISABLE;
  hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_BYTES;
  if (HAL_CRC_Init(&hcrc) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CRC_Init 2 */

  /* USER CODE END CRC_Init 2 */

}

/**
  * @brief USART2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

/**
  * @brief USART3 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART3_UART_Init(void)
{

  /* USER CODE BEGIN USART3_Init 0 */

  /* USER CODE END USART3_Init 0 */

  /* USER CODE BEGIN USART3_Init 1 */

  /* USER CODE END USART3_Init 1 */
  huart3.Instance = USART3;
  huart3.Init.BaudRate = 115200;
  huart3.Init.WordLength = UART_WORDLENGTH_8B;
  huart3.Init.StopBits = UART_STOPBITS_1;
  huart3.Init.Parity = UART_PARITY_NONE;
  huart3.Init.Mode = UART_MODE_TX_RX;
  huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart3.Init.OverSampling = UART_OVERSAMPLING_16;
  huart3.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart3.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart3) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART3_Init 2 */

  /* USER CODE END USART3_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);

  /*Configure GPIO pin : PC13 */
  GPIO_InitStruct.Pin = GPIO_PIN_13;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /*Configure GPIO pin : PB0 */
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

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

/**
  * @brief Jump to application from the Bootloader
  * @retval None
  */
static void goto_application(void)
{
  printf("Gonna Jump to Application\r\n");

  void (*app_reset_handler)(void) = (void*)(*((volatile uint32_t*) (ETX_APP_FLASH_ADDR + 4U)));

  //__set_MSP(*(volatile uint32_t*) ETX_APP_FLASH_ADDR);

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

  /* Reset the Clock */
  HAL_RCC_DeInit();
  HAL_DeInit();
  __set_MSP(*(volatile uint32_t*) ETX_APP_FLASH_ADDR);
  SysTick->CTRL = 0;
  SysTick->LOAD = 0;
  SysTick->VAL = 0;

  /* Jump to application */
  app_reset_handler();    //call the app reset handler
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

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;
/* Slot number to write the received firmware */
static uint8_t slot_num_to_write;
/* Configuration */
ETX_GNRL_CFG_ *cfg_flash   = (ETX_GNRL_CFG_*) (ETX_CONFIG_FLASH_ADDR);

/* Hardware CRC handle */
extern CRC_HandleTypeDef hcrc;

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_slot( uint8_t slot_num,
                                             uint8_t *data,
                                             uint16_t data_len,
                                             bool is_first_block );
static HAL_StatusTypeDef write_data_to_flash_app( uint8_t *data, uint32_t data_len );
static uint8_t get_available_slot_number( void );
static HAL_StatusTypeDef write_cfg_to_flash( ETX_GNRL_CFG_ *cfg );

/**
  * @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;
  slot_num_to_write    = 0xFFu;

  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);

          //get the slot number
          slot_num_to_write = get_available_slot_number();
          if( slot_num_to_write != 0xFF )
          {
            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 )
        {
          bool is_first_block = false;
          if( ota_fw_received_size == 0 )
          {
            //This is the first block
            is_first_block = true;

            /* Read the configuration */
            ETX_GNRL_CFG_ cfg;
            memcpy( &cfg, cfg_flash, sizeof(ETX_GNRL_CFG_) );

            /* Before writing the data, reset the available slot */
            cfg.slot_table[slot_num_to_write].is_this_slot_not_valid = 1u;

            /* write back the updated config */
            ret = write_cfg_to_flash( &cfg );
            if( ret != ETX_OTA_EX_OK )
            {
              break;
            }
          }

          /* write the chunk to the Flash (App location) */
          ex = write_data_to_slot( slot_num_to_write, buf+4, data_len, is_first_block );

          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");

            printf("Validating the received Binary...\r\n");

            uint32_t slot_addr;
            if( slot_num_to_write == 0u )
            {
              slot_addr = ETX_APP_SLOT0_FLASH_ADDR;
            }
            else
            {
              slot_addr = ETX_APP_SLOT1_FLASH_ADDR;
            }

            //Calculate and verify the CRC
            uint32_t cal_crc = HAL_CRC_Calculate( &hcrc, (uint32_t*)slot_addr, ota_fw_total_size);
            if( cal_crc != ota_fw_crc )
            {
              printf("ERROR: FW CRC Mismatch\r\n");
              break;
            }
            printf("Done!!!\r\n");

            /* Read the configuration */
            ETX_GNRL_CFG_ cfg;
            memcpy( &cfg, cfg_flash, sizeof(ETX_GNRL_CFG_) );

            //update the slot
            cfg.slot_table[slot_num_to_write].fw_crc                 = cal_crc;
            cfg.slot_table[slot_num_to_write].fw_size                = ota_fw_total_size;
            cfg.slot_table[slot_num_to_write].is_this_slot_not_valid = 0u;
            cfg.slot_table[slot_num_to_write].should_we_run_this_fw  = 1u;

            //reset other slots
            for( uint8_t i = 0; i < ETX_NO_OF_SLOTS; i++ )
            {
              if( slot_num_to_write != i )
              {
                //update the slot as inactive
                cfg.slot_table[i].should_we_run_this_fw = 0u;
              }
            }

            //update the reboot reason
            cfg.reboot_cause = ETX_NORMAL_BOOT;

            /* write back the updated config */
            ret = write_cfg_to_flash( &cfg );
            if( ret == ETX_OTA_EX_OK )
            {
              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;
  uint32_t cal_data_crc = 0u;
  uint32_t rec_data_crc = 0u;

  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;
      }
    }

    if( ret != HAL_OK )
    {
      break;
    }

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

    //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;
    }

    //Calculate the received data's CRC
    cal_data_crc = HAL_CRC_Calculate( &hcrc, (uint32_t*)&buf[4], data_len);

    //Verify the CRC
    if( cal_data_crc != rec_data_crc )
    {
      printf("Chunk's CRC mismatch [Cal CRC = 0x%08lX] [Rec CRC = 0x%08lX]\r\n",
                                                   cal_data_crc, rec_data_crc );
      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,
    .eof         = ETX_OTA_EOF
  };

  rsp.crc = HAL_CRC_Calculate( &hcrc, (uint32_t*)&rsp.status, 1);

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

/**
  * @brief Write data to the Slot
  * @param slot_num slot to be written
  * @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_slot( uint8_t slot_num,
                                             uint8_t *data,
                                             uint16_t data_len,
                                             bool is_first_block )
{
  HAL_StatusTypeDef ret;

  do
  {

    if( slot_num >= ETX_NO_OF_SLOTS )
    {
      ret = HAL_ERROR;
      break;
    }

    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 Slot %d Flash memory...\r\n", slot_num);
      //Erase the Flash
      FLASH_EraseInitTypeDef EraseInitStruct;
      uint32_t SectorError;

      EraseInitStruct.TypeErase     = FLASH_TYPEERASE_SECTORS;
      if( slot_num == 0 )
      {
        EraseInitStruct.Sector        = FLASH_SECTOR_7;
      }
      else
      {
        EraseInitStruct.Sector        = FLASH_SECTOR_9;
      }
      EraseInitStruct.NbSectors     = 2;                    //erase 2 sectors
      EraseInitStruct.VoltageRange  = FLASH_VOLTAGE_RANGE_3;

      ret = HAL_FLASHEx_Erase( &EraseInitStruct, &SectorError );
      if( ret != HAL_OK )
      {
        printf("Flash Erase Error\r\n");
        break;
      }
    }

    uint32_t flash_addr;
    if( slot_num == 0 )
    {
      flash_addr = ETX_APP_SLOT0_FLASH_ADDR;
    }
    else
    {
      flash_addr = ETX_APP_SLOT1_FLASH_ADDR;
    }

    for(int i = 0; i < data_len; i++ )
    {
      ret = HAL_FLASH_Program( FLASH_TYPEPROGRAM_BYTE,
                               (flash_addr + ota_fw_received_size),
                               data[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;
}

/**
  * @brief Return the available slot number
  * @param none
  * @retval slot number
  */
static uint8_t get_available_slot_number( void )
{
  uint8_t   slot_number = 0xFF;

  /* Read the configuration */
  ETX_GNRL_CFG_ cfg;
  memcpy( &cfg, cfg_flash, sizeof(ETX_GNRL_CFG_) );
  /*
   * Check the slot is valid or not. If it is valid,
   * then check the slot is active or not.
   *
   * If it is valid and not active, then use that slot.
   * If it is not valid, then use that slot.
   *
   */

   for( uint8_t i = 0; i < ETX_NO_OF_SLOTS; i++ )
   {
     if( ( cfg.slot_table[i].is_this_slot_not_valid != 0u ) || ( cfg.slot_table[i].is_this_slot_active == 0u ) )
     {
       slot_number = i;
       printf("Slot %d is available for OTA update\r\n", slot_number);
       break;
     }
   }

   return slot_number;
}


/**
  * @brief Write data to the Application's actual flash location.
  * @param data data to be written
  * @param data_len data length
  * @retval HAL_StatusTypeDef
  */
static HAL_StatusTypeDef write_data_to_flash_app( uint8_t *data, uint32_t data_len )
{
  HAL_StatusTypeDef ret;

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

    //Check if the FLASH_FLAG_BSY.
    FLASH_WaitForLastOperation( HAL_MAX_DELAY );

    // clear all flags before you write it to flash
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR |
                FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR);

    printf("Erasing the App 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 )
    {
      printf("Flash erase Error\r\n");
      break;
    }

    for( uint32_t i = 0; i < data_len; i++ )
    {
      ret = HAL_FLASH_Program( FLASH_TYPEPROGRAM_BYTE,
                               (ETX_APP_FLASH_ADDR + i),
                               data[i]
                             );
      if( ret != HAL_OK )
      {
        printf("App Flash Write Error\r\n");
        break;
      }
    }

    if( ret != HAL_OK )
    {
      break;
    }

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

    //Check if the FLASH_FLAG_BSY.
    FLASH_WaitForLastOperation( HAL_MAX_DELAY );

  }while( false );

  return ret;
}

/**
  * @brief Load the new app to the app's actual flash memory.
  * @param none
  * @retval none
  */
void load_new_app( void )
{
  bool              is_update_available = false;
  uint8_t           slot_num;
  HAL_StatusTypeDef ret;

  /* Read the configuration */
  ETX_GNRL_CFG_ cfg;
  memcpy( &cfg, cfg_flash, sizeof(ETX_GNRL_CFG_) );

  /*
   * Check the slot whether it has a new application.
   */

   for( uint8_t i = 0; i < ETX_NO_OF_SLOTS; i++ )
   {
     if( cfg.slot_table[i].should_we_run_this_fw == 1u )
     {
       printf("New Application is available in the slot %d!!!\r\n", i);
       is_update_available               = true;
       slot_num                          = i;

       //update the slot
       cfg.slot_table[i].is_this_slot_active    = 1u;
       cfg.slot_table[i].should_we_run_this_fw  = 0u;

       break;
     }
   }

   if( is_update_available )
   {
     //make other slots inactive
     for( uint8_t i = 0; i < ETX_NO_OF_SLOTS; i++ )
     {
       if( slot_num != i )
       {
         //update the slot as inactive
         cfg.slot_table[i].is_this_slot_active = 0u;
       }
     }

     uint32_t slot_addr;
     if( slot_num == 0u )
     {
       slot_addr = ETX_APP_SLOT0_FLASH_ADDR;
     }
     else
     {
       slot_addr = ETX_APP_SLOT1_FLASH_ADDR;
     }

     //Load the new app or firmware to app's flash address
     ret = write_data_to_flash_app( (uint8_t*)slot_addr, cfg.slot_table[slot_num].fw_size );
     if( ret != HAL_OK )
     {
       printf("App Flash write Error\r\n");
     }
     else
     {
       /* write back the updated config */
       ret = write_cfg_to_flash( &cfg );
       if( ret != HAL_OK )
       {
         printf("Config Flash write Error\r\n");
       }
     }
   }
   else
   {
     //Find the active slot in case the update is not available
     for( uint8_t i = 0; i < ETX_NO_OF_SLOTS; i++ )
     {
       if( cfg.slot_table[i].is_this_slot_active == 1u )
       {
         slot_num = i;
         break;
       }
     }
   }

   //Verify the application is corrupted or not
   printf("Verifying the Application...");

   FLASH_WaitForLastOperation( HAL_MAX_DELAY );
   //Verify the application
   uint32_t cal_data_crc = HAL_CRC_Calculate( &hcrc, (uint32_t*)ETX_APP_FLASH_ADDR, cfg.slot_table[slot_num].fw_size );
   FLASH_WaitForLastOperation( HAL_MAX_DELAY );

   //Verify the CRC
   if( cal_data_crc != cfg.slot_table[slot_num].fw_crc )
   {
     printf("ERROR!!!\r\n");
     printf("Invalid Application. HALT!!!\r\n");
     while(1);
   }
   printf("Done!!!\r\n");
}

/**
  * @brief Write the configuration to flash
  * @param cfg config structure
  * @retval none
  */
static HAL_StatusTypeDef write_cfg_to_flash( ETX_GNRL_CFG_ *cfg )
{
  HAL_StatusTypeDef ret;

  do
  {
    if( cfg == NULL )
    {
      ret = HAL_ERROR;
      break;
    }

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

    //Check if the FLASH_FLAG_BSY.
    FLASH_WaitForLastOperation( HAL_MAX_DELAY );

    //Erase the Flash
    FLASH_EraseInitTypeDef EraseInitStruct;
    uint32_t SectorError;

    EraseInitStruct.TypeErase     = FLASH_TYPEERASE_SECTORS;
    EraseInitStruct.Sector        = FLASH_SECTOR_4;
    EraseInitStruct.NbSectors     = 1;                    //erase only sector 4
    EraseInitStruct.VoltageRange  = FLASH_VOLTAGE_RANGE_3;

    // clear all flags before you write it to flash
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR |
                FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR);

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

    //write the configuration
    uint8_t *data = (uint8_t *) cfg;
    for( uint32_t i = 0u; i < sizeof(ETX_GNRL_CFG_); i++ )
    {
      ret = HAL_FLASH_Program( FLASH_TYPEPROGRAM_BYTE,
                               ETX_CONFIG_FLASH_ADDR + i,
                               data[i]
                             );
      if( ret != HAL_OK )
      {
        printf("Slot table Flash Write Error\r\n");
        break;
      }
    }

    //Check if the FLASH_FLAG_BSY.
    FLASH_WaitForLastOperation( HAL_MAX_DELAY );

    if( ret != HAL_OK )
    {
      break;
    }

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

  return ret;
}

Host Application code

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];

static const uint32_t crc_table[0x100] = {
  0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 
  0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD, 
  0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, 
  0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 
  0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, 
  0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, 
  0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, 
  0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A, 
  0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, 
  0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 
  0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, 
  0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, 
  0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, 
  0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 
  0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, 
  0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4, 
};

uint32_t CalcCRC(uint8_t * pData, uint32_t DataLength)
{
    uint32_t Checksum = 0xFFFFFFFF;
    for(unsigned int i=0; i < DataLength; i++)
    {
        uint8_t top = (uint8_t)(Checksum >> 24);
        top ^= pData[i];
        Checksum = (Checksum << 8) ^ crc_table[top];
    }
    return Checksum;
}

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 )
    {
      if( resp->crc == CalcCRC(&resp->status, 1) )
      {
        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          = CalcCRC( &ota_start->cmd, 1);
  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          = CalcCRC( &ota_end->cmd, 1);
  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          = CalcCRC( (uint8_t*)ota_info, sizeof(meta_info));
  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;

  //Calculate and Copy the crc
  uint32_t crc = CalcCRC( data, data_len);
  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);

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

    //Send OTA Header
    meta_info ota_info;
    ota_info.package_size = app_size;
    ota_info.package_crc  = CalcCRC( APP_BIN, app_size);

    ex = send_ota_header( comport, &ota_info );
    if( ex < 0 )
    {
      printf("send_ota_header Err\n");
      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.

Output

  • Erase the complete chip using the STM32 ST-LINK Utility.
  • Then flash the Bootloader that we have built.
  • It will be waiting for the data from the host.
  • 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 automatically reboots.
  • See the Serial terminal. You can see the application version number is 0.3 and also check the Blue LED. Now it should blink with a 200 milliseconds delay.

Video Explanation

Please check our video explanation below.

In our next tutorial, we will update the application wirelessly without the help of a PC. 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.

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
STM32F7 Bootloader TutorialsRaspberry PI Pico Tutorials
STM32F103 Bootloader TutorialsRT-Thread RTOS Tutorials
Zephyr RTOS Tutorials - STM32Zephyr RTOS Tutorials - ESP32
AUTOSAR TutorialsUDS Protocol Tutorials
Product ReviewsSTM32 MikroC Bootloader Tutorial
VHDL Tutorials
Subscribe
Notify of
guest

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

14 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Table of Contents