miniOTA:32位mcu平台OTA升级

文章目录


简介

本文基于 mOTA v2.0 进行协议移植与优化,主要实现以下目标:

  • 突破传统 OTA 升级的协议限制,支持 UART、IIC、SPI、USB、CAN 等多种协议
  • 对核心代码进行功能注释和优化
  • 模块化重构QT固件发送器,实现任意单片机之间的互升级操作

硬件平台 :正点原子 STM32F103ZET6 最小系统板
工程代码 :「miniOTA教程文档工程.zip
mOTA 仓库https://gitee.com/DinoHaw/mOTA


一、mOTA组件介绍

mOTA 是一款专为 32 位 MCU 开发的轻量级 OTA 组件,组件包含三部分:

  1. Bootloader:负责固件的下载、解析、解密、存储、更新等操作
  2. 固件打包器(Firmware_Packager):将 bin 文件打包成 fpk 固件包
  3. 固件发送器:基于不同协议的固件发送工具(如 YModem_Sender)

mOTA组件核心功能特性:

功能 说明
固件包完整性检查 自动检测固件 CRC 值,验证固件数据的准确性
固件加密 支持 AES256 加密算法,提高固件的安全性
APP 完整性检查 支持在 APP 运行前进行完整性检查,确认可运行的固件通过数据校验
断电保护 更新过程中断电,上电后仍能确保有可用固件(需配置双分区或三分区)
固件水印检查 检测固件包是否携带特殊水印,防止错误更新
固件自动更新 检测到新固件时自动开始更新
恢复出厂设置 factory 分区存放稳定版固件,支持一键恢复
无须 deinit 采用再入 bootloader 方式,免去外设 deinit 代码
功能可裁剪 通过 bootloader_config.h 实现功能裁剪和方案切换
SPI Flash 支持 支持将固件存放至 SPI flash,使用 SFUD 驱动库

软件架构:

mOTA 采用分层架构设计,从底层到顶层依次为:硬件层、硬件抽象层、驱动层、数据传输层、协议析构层、应用层。

二、快速开始

说明 :后续的所有 demo 工程都基于正点原子的 STM32F103ZET6 最小系统板。

  1. 克隆仓库
bash 复制代码
git clone https://gitee.com/DinoHaw/mOTA.git
  1. 打开example/STM32F1/bootloader_ymodem工程,修改IROM1地址,编译、烧录。
  2. 打开example/STM32F1/app_v1工程,修改IROM1地址,编译生成hex文件。
  3. 打开mOTA\tools\firmware_packager\exe下的firmware-packager.exe文件,对app_v1.bin进行打包成app_v1.fpk文件。
  4. 打开mOTA\tools\YModem_Sender\exe下的YModem_Sender.exe文件,对单片机进行OTA升级。
  5. 状态指示:
    · 绿灯闪烁:bootloader等待上位机发送数据
    · 红灯闪烁:APP正常运行
    · 按下KEY0按键进入bootloader模式,等待上位机发送数据

三、移植mOTA

3.1 UART DMA接收数据

基础配置步骤:

  1. RCC 配置:选择外部高速时钟(HSE)
  2. SYS 配置:Debug 选择 Serial Wire
  3. USART1 配置
    • 通信模式:异步(Asynchronous)
    • 波特率:根据实际需求设置(如 115200)
    • 使能中断:USART1 global interrupt
  4. DMA 配置
    • 添加 USART1_RX 的 DMA 通道
    • 模式:循环模式(Circular)
    • 数据宽度:Byte
  5. 时钟配置:主频配置为 72MHz

参考文档STM32 HAL 库实现乒乓缓存加空闲中断的串口 DMA 收发机制

  1. 使用cubemx生成工程

  2. 配置RCC、SYS

  3. 配置debug串口,注意选择异步通信模式、波特率、中断

  4. 配置DMA接收,循环模式

  5. 修改时钟配置,主频72M

生成代码后,配置乒乓缓存实现

c 复制代码
// 定义接收缓冲区
#define RX1_BUF_SIZE (200)
uint8_t USART1_Rx_buf[RX1_BUF_SIZE];

// 在main函数中调用一次DMA接收,可以放在MX_USART1_UART_Init中调用
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, USART1_Rx_buf, RX1_BUF_SIZE);

编写回调函数

c 复制代码
/* USER CODE BEGIN 0 */

#define RX1_BUF_SIZE (200)
uint8_t USART1_Rx_buf[RX1_BUF_SIZE];

// 半满、全满、空闲中断均会调用此函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  static uint8_t Rx1_buf_pos;
  static uint8_t Rx1_length;
  if (huart == &huart1)
  {
    Rx1_length = Size - Rx1_buf_pos;

    if (Rx1_length < RX1_BUF_SIZE)
    {
      HAL_UART_Transmit(&huart1, &USART1_Rx_buf[Rx1_buf_pos], Rx1_length, 0xFFFF);
      // 存入fifo
    }

    Rx1_buf_pos += Rx1_length;
    if (Rx1_buf_pos >= RX1_BUF_SIZE)
    {
      Rx1_buf_pos = 0;
    }

    // 空闲中断判断帧结束
    if (huart1.RxEventType == HAL_UART_RXEVENT_IDLE)
    {
      
    }
  }
}

/* USER CODE END 0 */

完成编译、烧录代码后,可以看到收发的数量均相等,说明DMA搬运接收的代码配置成功实现。

3.2 适配代码

准备工作

1. LED状态 :配置LED灯,表示运行状态。

低电平LED亮起,高电平熄灭,GPIO配置默认输出高电平。

2. ring buffer接收数据 :使用DMA搬运数据到ring buffer中,使用DMA目的是为了减少CPU占用,使用ring buffer目的是为了有效利用内存,下面使用lwrb实现ring buffer接收数据,详细用法可以参考官方文档

添加源文件和头文件到工程中

c 复制代码
Lib/lwrb/src/lwrb.c 
Lib/lwrb/src/lwrb_ex.c 
Lib/lwrb/include/lwrb/lwrb.h

注意:编译器需要选择6,不然会出现报错。

随后,添加lwrb初始化、读取、写入等函数。

c 复制代码
#include "lwrb/lwrb.h"

// 定义缓冲区长度
#define LWRB_UART1_RX_LEN (1024 * 2)
// 2k字节
static uint8_t Rx1_data_buf[LWRB_UART1_RX_LEN];
// 句柄
static lwrb_t Rx1_lwrb_handle;

// 初始化
void lwrb_rx1_init(void)
{
  if (!lwrb_init(&Rx1_lwrb_handle, Rx1_data_buf, LWRB_UART1_RX_LEN))
  {
    printf("lwrb_init failed\r\n");
  }
}

// 写入数据
lwrb_sz_t lwrb_rx1_write(const void *data, lwrb_sz_t size)
{
  return lwrb_write(&Rx1_lwrb_handle, data, size);
}

// 读取数据
lwrb_sz_t lwrb_rx1_read(void *data, lwrb_sz_t size)
{
  return lwrb_read(&Rx1_lwrb_handle, data, size);
}

// 获取缓冲区中当前可用的字节数
lwrb_sz_t lwrb_rx1_full(void)
{
  return lwrb_get_full(&Rx1_lwrb_handle);
}

修改HAL_UARTEx_RxEventCallback函数,将数据写入ringbuffer,添加读取结束标志位。

c 复制代码
#define RX1_BUF_SIZE (200)
uint8_t USART1_Rx_buf[RX1_BUF_SIZE];
static volatile uint8_t uart1_frame_received_flag = 0;

// 在main函数中调用一次DMA接收,可以放在MX_USART1_UART_Init中调用
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, USART1_Rx_buf, RX1_BUF_SIZE);

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  static uint8_t Rx1_buf_pos;
  static uint8_t Rx1_length;
  // 串口1 DMA接收
  if (huart == &huart1)
  {
    Rx1_length = Size - Rx1_buf_pos;

    if (Rx1_length < RX1_BUF_SIZE)
    {
      // 将数据写入ring buffer
      if (Rx1_length != lwrb_rx1_write(&USART1_Rx_buf[Rx1_buf_pos], Rx1_length))
      {
        printf("LWRB_UART1_RX overflow!\r\n");
      }
    }

    Rx1_buf_pos += Rx1_length;
    if (Rx1_buf_pos >= RX1_BUF_SIZE)
    {
      Rx1_buf_pos = 0;
    }

    // 空闲中断判断帧结束
    if (huart1.RxEventType == HAL_UART_RXEVENT_IDLE)
    {
      uart1_frame_received_flag = 1;
    }
  }
}

uint8_t get_uartx_frame_received_flag(UART_HandleTypeDef *huart)
{
  if (huart == &huart1)
  {
    return uart1_frame_received_flag;
  }
  return 0;
}

void clear_uartx_frame_received_flag(UART_HandleTypeDef *huart)
{
  if (huart == &huart1)
  {
    uart1_frame_received_flag = 0;
  }
}

main.c 中读取数据,并将读取到的数据输出。如果收发一致说明ring buffer已配置成功。

移植

  1. 复制 mOTA\source 文件夹到 miniOTA\Lib\mOTA
  2. 添加源文件到工程中
  3. 参考:README.md

Bootloader/Core:

bash 复制代码
添加 bootloader.c 进工程中,实现 bootloader 的核心功能。
添加 bootloader_port.c 进工程中,若此部分逻辑有不同,则按实际情况进行修改。bootloader 的非核心和可移植的部分,主要是通信和协议部分的代码。
添加 firmware_manage.c 进工程中,固件的管理接口层,提供了固件的所有操作接口。
添加 data_transfer.c 进工程中,数据传输层,对外提供数据发送和接收的接口。
添加 data_transfer_port.c 进工程并实现内部的公共函数,若与案例一致,则无需修改。数据传输层的移植位置,便于修改为其它通讯接口。
添加 protocol_parser.c 进工程并实现内部的公共函数,若与案例一致,则无需修改。协议析构层,实现协议的解包和封包。

Bootloader/Component:

bash 复制代码
添加 crcLib.c 进工程中,crc校验库。
添加 fal_stm32f1_flash.c 进工程中,stm32f1的flash库。
添加 perf_counter.c 进工程中
添加 aes.c 进工程中,aes库。
添加 bsp_flash.c 进工程中,flash板级支持包。
添加 bsp_key.c 进工程中,key板级支持包。
添加 bsp_timer.c 进工程中,软件timer板级支持包。

源文件:

头文件路径声明:

  1. 解决编译错误:
    如果遇到 error: #20: identifier "FLASH_SECTOR_TOTAL" is undefined 错误:
  • 修改工程的 IRAM1、IRAM2 配置
  • 确保 Flash 分区配置正确

    修改IRAM1、IRAM2

如果没有 user.h 文件,则可以根据下面内容自行创建。

c 复制代码
/**
 * \file            user.h
 * \brief           configuration of the user application
 */

/*
 * Copyright (c) 2022 Dino Haw
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * This file is part of mOTA - The Over-The-Air technology component for MCU.
 *
 * Author:          Dino Haw <347341799@qq.com>
 * Change Logs:
 * Version  Date           Author       Notes
 * v1.0     2022-11-23     Dino         the first version
 * v1.1     2022-12-04     Dino         增加 VERSION_WRITE_TO_APP
 * v1.2     2023-12-10     Dino         1. 改名为 user
 *                                      2. 剥离 bootloader 部分
 */

#ifndef __USER_H__
#define __USER_H__

/* 定义项 */
#define RTOS_USING_NONE                     0                   /* 不使用 RTOS */
#define RTOS_USING_RTTHREAD                 1                   /* RT-Thread */
#define RTOS_USING_UCOS                     2                   /* uC/OS */

/* 配置选项 */
#define ENABLE_ASSERT                       0                   /* 是否使能函数入口参数检查 */
#define ENABLE_DEBUG_PRINT                  1                   /* 是否使能调试信息打印 */
#define EANBLE_PRINTF_USING_RTT             0                   /* BSP_Print 函数是否使用 SEGGER RTT 作为输出端口 */

#define USING_RTOS_TYPE                     RTOS_USING_NONE
#define SEGGER_RTT_PRINTF_TERMINAL          0                   /* SEGGER RTT 的打印端口 */
#define MAX_NAME_LEN                        8

#define SOC_SERIES_STM32F1

#endif
  1. 修改部分源码
        在 main.h 中添加添加 BSP_Printf() 日志打印声明

        在 SysTick_Handler() 函数中为软件定时器添加一个1ms的时钟基准。

        删除 bsp_uart.h 文件和引用

         在 data_transfer_port.h 中关闭断帧检测

         在 data_transfer_port.c 中修改数据发送接口、帧结束检测接口。

data_transfer_port.c 中,注意需要删除部分内容

bootloader_port.c 中删除数据传输层初始化的函数。

bootloader_port.c 中添加代码逻辑,将数据长度、数据内容存入 _dev_rx_len_dev_rx_buff 中。

编译验证

编译步骤:

  1. 检查文件完整性:确认所有源文件已添加到工程
  2. 配置头文件路径:确认 mOTA 头文件路径配置正确
  3. 解决编译错误:根据错误提示逐一解决
  4. 生成固件:编译通过后生成 hex 文件

验证方法:

上电后状态指示:

  • 绿灯闪烁(300ms 间隔):bootloader 等待下载状态
  • 红灯闪烁(100ms 间隔):OTA 更新成功,APP 正常运行
相关推荐
qqssss121dfd2 小时前
计算机网络(第8版,谢希仁)第四章习题解答
服务器·c语言·网络·单片机·计算机网络
HarrySunCn2 小时前
大夏龙雀DX-CT511N-B实战之路-第1步
前端·单片机·物联网·iot
田甲2 小时前
STM32L051实现RTC低功耗唤醒
stm32·嵌入式硬件·实时音视频
清月电子11 小时前
杰理AC109N系列AC1082 AC1074 AC1090 芯片停产替代及资料说明
人工智能·单片机·嵌入式硬件·物联网
智嵌电子11 小时前
【笔记篇】【硬件基础篇】模拟电子技术基础 (童诗白) 第10章 模拟电子电路读图
笔记·单片机·嵌入式硬件
一颗青果12 小时前
51单片机 计算指令
单片机·嵌入式硬件·51单片机
一路往蓝-Anbo12 小时前
【第20期】延时的艺术:HAL_Delay vs vTaskDelay
c语言·数据结构·stm32·单片机·嵌入式硬件
Aaron158812 小时前
AD9084和Versal RF系列具体应用案例对比分析
嵌入式硬件·算法·fpga开发·硬件架构·硬件工程·信号处理·基带工程