STM32 MCU OTA 升级办法2

利用现有API,如何实现从耳机端通过uart通信,获取MCU OTA升级的bin文件,拿到完整bin之后,校验ok,再升级。请给出程序设计思路,以及具体实现

1.设计思路

在应用固件中用 UART DMA + Idle 中断接收耳机发送的 OTA 数据流。

协议三段:META(固件大小+CRC32)、DATA(连续数据流)、END(结束)。MCU边接收边写入暂存区 0x10000,并同步计算 CRC32。

接收完整 size 后计算的 CRC32 与 META 提供一致则:

写 DFU 标记到 0x1E000(DFU_/OK__)

触发复位,Bootloader 启动后搬运到 0x2000 并清标记

不一致则擦除暂存区并返回 NACK,不升级。

2.实现要点

PCLK1=16 MHz 已确认;不影响本流程。

Flash 暂存区:0x10000,大小 56KB(与 Boot 搬运一致)。

DFU 标记地址:0x1E000。Boot 已实现搬运与校验。

使用 HAL_UARTEx_ReceiveToIdle_DMA 和 HAL_UARTEx_RxEventCallback 进行分包接收。

新增 OTA 接收与校验模块(CRC32)

c 复制代码
#pragma once
#include <stdint.h>
#include <stdbool.h>

typedef struct {
    uint32_t size;           // 固件总长度(字节)
    uint32_t version;        // 可选版本
    uint32_t crc32_storage;  // 耳机端整体 CRC32
} ota_meta_t;

void ota_init(void);
void ota_on_uart_bytes(const uint8_t* data, uint16_t len); // 在 UART Rx 回调中调用

// 可选:外部查询状态
bool ota_is_in_progress(void);
bool ota_last_result_ok(void);
c 复制代码
#include "ota_fw_update.h"
#include "stm32c0xx_hal.h"
#include "usart.h"  // 使用 huart1
#include <string.h>

#define DFU_MARK_ADDR   (0x1E000u)
#define STAGING_ADDR    (0x10000u)
#define FLASH_PAGE_SIZE (0x800u)     // 2KB 页
#define STAGING_SIZE    (56*1024u)   // 与 boot 搬运一致

// DFU 标记魔数
#define DFU_   ('D' + ('F' << 8) + ('U' << 16) + ('_' << 24))
#define OK__   ('O' + ('K' << 8) + ('_' << 16) + ('_' << 24))

typedef enum {
    OTA_IDLE = 0,
    OTA_WAIT_META,
    OTA_RECV_DATA,
    OTA_WAIT_END,
    OTA_DONE,
    OTA_FAIL
} ota_state_t;

static ota_state_t s_state = OTA_IDLE;
static ota_meta_t s_meta = {0};
static uint32_t s_crc = 0xFFFFFFFFu;
static uint32_t s_written = 0;
static uint32_t s_flash_addr = STAGING_ADDR;
static bool s_last_ok = false;

// 简易 CRC32 (Poly 0xEDB88320)
static uint32_t crc32_update(uint32_t crc, const uint8_t *data, uint32_t len) {
    crc = ~crc;
    for (uint32_t i = 0; i < len; ++i) {
        crc ^= data[i];
        for (int j = 0; j < 8; ++j)
            crc = (crc >> 1) ^ (0xEDB88320u & (-(int32_t)(crc & 1)));
    }
    return ~crc;
}

static void flash_erase_range(uint32_t addr, uint32_t bytes) {
    FLASH_EraseInitTypeDef erase = {0};
    uint32_t page_err = 0;
    erase.TypeErase = FLASH_TYPEERASE_PAGES;
    erase.Page      = addr / FLASH_PAGE_SIZE;
    erase.NbPages   = (bytes + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE;
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
    HAL_FLASHEx_Erase(&erase, &page_err);
    HAL_FLASH_Lock();
}

static bool flash_write_chunk(const uint8_t *buf, uint32_t len) {
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
    uint32_t i = 0;
    while (i + 8 <= len) {
        uint64_t qw;
        memcpy(&qw, buf + i, 8);
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, s_flash_addr, qw) != HAL_OK) {
            HAL_FLASH_Lock();
            return false;
        }
        s_flash_addr += 8;
        i += 8;
    }
    if (i < len) {
        uint8_t tail[8] = {0};
        memcpy(tail, buf + i, len - i);
        uint64_t qw;
        memcpy(&qw, tail, 8);
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, s_flash_addr, qw) != HAL_OK) {
            HAL_FLASH_Lock();
            return false;
        }
        s_flash_addr += 8;
    }
    HAL_FLASH_Lock();
    return true;
}

static void write_dfu_mark(void) {
    // 擦除标记页并写 DFU_/OK__
    FLASH_EraseInitTypeDef erase = {0};
    uint32_t page_err = 0;
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
    erase.TypeErase = FLASH_TYPEERASE_PAGES;
    erase.Page      = DFU_MARK_ADDR / FLASH_PAGE_SIZE;
    erase.NbPages   = 1;
    HAL_FLASHEx_Erase(&erase, &page_err);
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, DFU_MARK_ADDR,     DFU_);
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, DFU_MARK_ADDR + 4, OK__);
    HAL_FLASH_Lock();
}

// 简单协议:
// META 帧: "FWUP" + size(4) + version(4) + crc32(4)  共 16 字节
// DATA 流: 紧随其后 size 字节原始数据(可分多次到达)
// END 帧: "FWED" 4 字节
static const uint8_t k_meta_magic[4] = {'F','W','U','P'};
static const uint8_t k_end_magic[4]  = {'F','W','E','D'};

void ota_init(void) {
    s_state = OTA_WAIT_META;
    s_last_ok = false;
    s_crc = 0xFFFFFFFFu;
    s_written = 0;
    s_flash_addr = STAGING_ADDR;
    // 擦除暂存区(56KB)
    flash_erase_range(STAGING_ADDR, STAGING_SIZE);
}

bool ota_is_in_progress(void) { return s_state == OTA_RECV_DATA || s_state == OTA_WAIT_META || s_state == OTA_WAIT_END; }
bool ota_last_result_ok(void) { return s_last_ok; }

// 在 UART Rx 回调中将收到的字节流喂入此函数
void ota_on_uart_bytes(const uint8_t* data, uint16_t len) {
    if (len == 0) return;

    switch (s_state) {
    case OTA_WAIT_META:
        if (len < 16 || memcmp(data, k_meta_magic, 4) != 0) {
            s_state = OTA_FAIL;
            return;
        }
        memcpy(&s_meta.size,          data + 4, 4);
        memcpy(&s_meta.version,       data + 8, 4);
        memcpy(&s_meta.crc32_storage, data + 12, 4);
        if (s_meta.size == 0 || s_meta.size > STAGING_SIZE) {
            s_state = OTA_FAIL;
            return;
        }
        s_state = OTA_RECV_DATA;
        break;

    case OTA_RECV_DATA: {
        // 分批写入并更新 CRC
        s_crc = crc32_update(s_crc, data, len);
        s_written += len;
        if (!flash_write_chunk(data, len)) {
            s_state = OTA_FAIL;
            return;
        }
        if (s_written >= s_meta.size) {
            s_state = OTA_WAIT_END; // 等待结束魔数
        }
        break;
    }

    case OTA_WAIT_END:
        if (len >= 4 && memcmp(data, k_end_magic, 4) == 0) {
            // 校验一致则标记并复位
            if (s_written == s_meta.size && s_crc == s_meta.crc32_storage) {
                write_dfu_mark();
                s_last_ok = true;
                s_state = OTA_DONE;
                // 告知耳机 ACK
                const uint8_t ack[4] = {'O','T','A','K'};
                HAL_UART_Transmit(&huart1, (uint8_t*)ack, sizeof(ack), 50);
                // 复位进入 Boot 搬运
                NVIC_SystemReset();
            } else {
                s_last_ok = false;
                s_state = OTA_FAIL;
                const uint8_t nack[4] = {'O','T','A','N'};
                HAL_UART_Transmit(&huart1, (uint8_t*)nack, sizeof(nack), 50);
                // 清理暂存区
                flash_erase_range(STAGING_ADDR, STAGING_SIZE);
            }
        }
        break;

    default:
        break;
    }
}

2.启用 UART DMA + Idle 回调,转发到 OTA 模块

使用现有 HAL API(usart.c 中的 huart1),在初始化后启动空闲接收;在 HAL_UARTEx_RxEventCallback 中将数据喂给 ota_on_uart_bytes。

c 复制代码
// ...existing code...
#include "ota_fw_update.h"
// ...existing code...

// 简易接收缓冲
static uint8_t s_uart_rx_buf[256];

void USART1_OTA_Start(void)
{
    // 启动 UART1 DMA Idle 接收(CubeMX 要确保 DMA 已配置)
    HAL_UARTEx_ReceiveToIdle_DMA(&huart1, s_uart_rx_buf, sizeof(s_uart_rx_buf));
    // 禁用半传输中断,减少回调次数
    __HAL_DMA_DISABLE_IT(huart1.hdmarx, DMA_IT_HT);
}

// UART Idle 回调:把接收到的有效字节喂入 OTA 状态机
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart == &huart1 && Size > 0) {
        ota_on_uart_bytes(s_uart_rx_buf, Size);
        // 继续接收
        HAL_UARTEx_ReceiveToIdle_DMA(&huart1, s_uart_rx_buf, sizeof(s_uart_rx_buf));
        __HAL_DMA_DISABLE_IT(huart1.hdmarx, DMA_IT_HT);
    }
}
// ...existing code...

3.在应用入口处初始化 OTA 接收

在 System 初始化完成后调用 ota_init() 和 USART1_OTA_Start()。你可以在主循环或任务中根据条件(如耳机指令)再启动 OTA;下面示例直接启用。

c 复制代码
// ...existing code...
#include "ota_fw_update.h"
// ...existing code...
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  // ...existing code...
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();

  // 启动 OTA 会话(也可根据耳机侧协商后再调用)
  ota_init();
  extern void USART1_OTA_Start(void);
  USART1_OTA_Start();

  // ...existing code...
  while (1) {
    // 你的应用逻辑
  }
}
// ...existing code...

交互与异常处理

耳机侧发送顺序:META(16B) → DATA(size B,可分包) → END(4B)。

MCU 每帧可回发简单 ACK/NACK(示例里在 END 时回发)。

若 UART 中途断开或 size 超出 56KB,状态机进入 FAIL 并擦除暂存区。

成功后写 DFU 标记并复位,Boot 会把 0x10000 搬到 0x2000(你的 boot 已实现:擦 28 页、拷贝 56KB、验证后清标记)。

可选增强

将 CRC32 改为 SHA‑256(更强一致性保障)。

为 DATA 分包加序号与重传机制(提升鲁棒性)。

OTA 过程中暂停其他外设操作,确保 Flash 编程不被打断。

相关推荐
brave and determined2 小时前
传感器学习(day15):无人超市:从技术狂欢到理性回归,RFID的发展
单片机·嵌入式硬件·rfid·数字电路·nfc·嵌入式设计·近场通讯
wotaifuzao3 小时前
I2C通信--深度解析与未来发展
单片机·嵌入式硬件·物联网·信息与通信·i2c
mastercoder--4 小时前
速通51单片机————点亮LED与控制LED
单片机·嵌入式硬件·51单片机
CS Beginner5 小时前
【ESP32】Keil搭建ESP32-C3环境
单片机·esp32
无垠的广袤5 小时前
【FPB-RA6E2 开发板】Zephyr 串口打印 DHT11 温湿度
c++·单片机·串口通信·开发板·瑞萨·传感器·dht11
曾浩轩6 小时前
跟着江协科技学STM32之3-4按键控制LED&光敏传感器控制蜂鸣器
科技·stm32·嵌入式硬件
尼喃6 小时前
单节锂电池充电芯片核心选型,高可靠性充电方案技术精要
单片机·51单片机·芯片
兆龙电子单片机设计6 小时前
【STM32项目开源】STM32单片机数字电子秤系统
stm32·单片机·物联网·开源·毕业设计
d111111111d7 小时前
逻辑最清晰的STM32F1/F4标准库工程创建
stm32·单片机·嵌入式硬件