1. Implementation Concept of OTA Based on On-Chip Flash
OTA (Over-The-Air) essentially allows devices to receive new firmware during operation and write it to on-chip Flash, ultimately replacing the old firmware. There are two key points to implement OTA:
1. Receive new firmware;
2. Write the new firmware to Flash.
On the STM32F103C8T6 chip, the built-in Flash has a size of 64KB with a page size of 1KB, starting at address 0x0800_0000 and ending at address 0x0801_0000.

Since the APP size generated by this example project is 18KB, the on-chip Flash is simply divided into an APP running area of 32KB and an OTA file storage area of 32KB. (Other projects can partition according to their specific needs)

The OTA implementation concept involves storing a flag in the last page of Flash to indicate whether there is a program that needs to be updated. The generated OTA file is downloaded to partition 2 via the serial port, and then the OTA file is moved to the APP partition, setting the flag to 1. After the transfer is complete, the OTA partition is cleared, the flag is reset to 0, and the device is rebooted to complete the update. If the MCU loses power during the OTA file transfer and reboots, it checks the flag and finds an OTA file, then moves the OTA file to the APP partition, clears the OTA partition, and reboots to complete the update.
2. Implementation of General Interface for On-Chip Flash
flash_on_chip.c
#include "flash_on_chip.h"
#include "spi.h"
/**
* @brief Erase the specified range of Flash addresses
* @param addr Starting address (must be page-aligned, i.e., a multiple of 1KB)
* @param len Length of bytes to erase
* @retval 0 Success
* -1 Parameter error (out of bounds)
* -2 Address not aligned
* -3 Erase failed
*/
int flash_erase(uint32_t addr, uint32_t len) {
if (len == 0) return -1;
if (addr < FLASH_BASE_ADDR || (addr + len) > FLASH_END_ADDR) return -1;
if (addr % FLASH_PAGE_SIZE != 0) return -2; // Must be page-aligned
uint32_t end = addr + len;
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef er = {0};
uint32_t err = 0;
uint32_t first_page = (addr - FLASH_BASE_ADDR) / FLASH_PAGE_SIZE;
uint32_t last_page = (end - FLASH_BASE_ADDR + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE;
for (uint32_t p = first_page; p < last_page; ++p) {
er.TypeErase = FLASH_TYPEERASE_PAGES;
er.PageAddress = FLASH_BASE_ADDR + p * FLASH_PAGE_SIZE;
er.NbPages = 1;
if (HAL_FLASHEx_Erase(&er, &err) != HAL_OK) {
HAL_FLASH_Lock();
return -3;
}
}
HAL_FLASH_Lock();
return 0;
}
/**
* @brief Write data to Flash (half-word programming)
* @param addr Starting address for writing (must be 2-byte aligned)
* @param data Buffer of data to write
* @param len Length of data (in bytes)
* @retval 0 Success
* -1 Parameter error (out of bounds)
* -2 Address not aligned
* -3 Write failed
*/
int flash_write(uint32_t addr, const uint8_t *data, uint32_t len) {
if (len == 0) return 0;
if (addr < FLASH_BASE_ADDR || (addr + len) > FLASH_END_ADDR) return -1;
if (addr % 2 != 0) return -2; // Must be half-word aligned
HAL_FLASH_Unlock();
for (uint32_t i = 0; i < len; i += 2) {
uint16_t half = data[i] | ((i + 1 < len ? data[i + 1] : 0xFF) << 8);
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr + i, half) != HAL_OK) {
HAL_FLASH_Lock();
return -3;
}
}
HAL_FLASH_Lock();
return 0;
}
/**
* @brief Read data from Flash at the specified address
* @param addr Starting address
* @param buf Target buffer
* @param len Number of bytes to read
* @retval 0 Success
* -1 Parameter error (out of bounds)
*/
int flash_read(uint32_t addr, uint8_t *buf, uint32_t len) {
if (len == 0) return 0;
if (addr < FLASH_BASE_ADDR || (addr + len) > FLASH_END_ADDR) return -1;
const uint8_t *src = (const uint8_t *)addr;
for (uint32_t i = 0; i < len; i++) {
buf[i] = src[i];
}
return 0;
}
flash_on_chip.h
#ifndef __FLASH_ON_CHIP_H
#define __FLASH_ON_CHIP_H
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#define FLASH_BASE_ADDR (0x08000000UL) // APP starting address
#define APP_LIMIT (0x08008000UL) // End address of APP usage area
#define STAGING_BASE (0x08008000UL) // Starting address for OTA file storage
#define FLASH_END_ADDR (0x08010000UL) // End address for OTA files
#define PAGE_SIZE (1024U) // Size of a single page of Flash
#define Flage_PAGE_ADDR (0x0800FC00UL) // Last page for storing flag
int flash_write(uint32_t addr, const uint8_t *data, uint32_t len);
int erase_staging(uint32_t addr, uint32_t len);
#endif
General Interface Simple Serial Read/Write Test
This is a series; subsequent parts will cover complete implementations of serial OTA without bootloader, serial OTA with bootloader, and remote OTA implementation between ESP32 and STM32. If you are interested, please click to follow.