OTA项目回顾(一)
一、项目介绍
该项目是一个基于 ESP32-C3 的 BLE OTA 桥接器。
它的核心作用是:让手机或上位机通过 BLE 连接 ESP32-C3,再由 ESP32-C3 通过 UART 与下游 MCU 通信,从而完成控制指令转发、日志透传,以及 ESP32-C3 自身或下游 MCU 的固件升级。
二、项目背景
在很多嵌入式产品中,主控 MCU 本身可能没有无线能力,或者现场调试时不方便频繁连接串口、拆机烧录。所以该项目的设计目标就是在主控 MCU 旁边增加一个 ESP32-C3,让它承担无线入口的角色:
- 手机通过 BLE 发现并连接设备。
- 手机向 BLE characteristic 写入控制命令或 OTA 数据。
- ESP32-C3 把 BLE 数据转换成 UART 协议帧,转发给下游 MCU。
- 下游 MCU 的日志、状态或响应也可以通过 UART 回传给 ESP32-C3。
- ESP32-C3 再通过 BLE notify 把数据返回给手机。
这样一来,用户不需要直接接触下游 MCU 的串口,也可以进行无线调试和升级。
三、项目的大致流程
| 初始化函数 | 作用 |
|---|---|
board_init() |
初始化 NVS,用来存放 BLE 地址、产品信息等非易失数据 |
com_uart_init() |
初始化 UART1,建立 ESP32-C3 与下游 MCU 的通信链路 |
bt_main_init() |
初始化 BLE controller、Bluedroid 协议栈和 GATT 服务 |
esp_log_set_vprintf() |
重定向日志输出,让日志可以通过 BLE trace 服务发送 |
runloop_init() |
创建一个 FreeRTOS Queue + Task 的轻量事件循环 |
application_init() |
注册 ctrl、trace、ota 三个业务 profile 的回调 |
这里的初始化顺序并不是随意的,BLE 服务需要依赖 NVS 和蓝牙协议栈,业务 profile 又依赖 BLE service 已经创建完成;UART 也要尽早初始化,因为后续控制命令和 DFU 都依赖它。
四、BLE服务划分
项目定义了三个主要 BLE GATT 服务。
| 服务 | UUID | 作用 |
|---|---|---|
| CTRL | 0x3A10 |
设备控制,例如名称、查找、杂项命令 |
| TRACE | 0x3A20 |
日志输出、shell 命令、内部日志 |
| OTA | 0x3AF0 |
固件升级数据传输和升级控制 |
每个服务下面又有多个 characteristic。
例如 OTA 服务:
| Characteristic | UUID | 方向 | 作用 |
|---|---|---|---|
| version | 0x3AF1 |
Read | 读取 OTA 服务版本 |
| data | 0x3AF2 |
Write Without Response + Notify | 传输 OTA 数据包 |
| ctrl | 0x3AF3 |
Write Without Response + Notify | 发送应用升级、重启等控制命令 |
这里要理解一个 BLE 关键点:BLE GATT 并不是“直接发字节流”,而是围绕 service 和 characteristic 组织数据。
手机端写入某个 characteristic,本质上是在告诉设备:“我要对这个功能点发送数据。”设备端再根据 characteristic 的 handle 或 UUID 分发到不同业务逻辑。
五、UART自定义协议
ESP32-C3 与下游 MCU 之间使用自定义 UART 帧格式:
| magic | channel | length | data | crc16 |
| 0xaa | 1 byte | 2 byte | n | 2 byte |
其中 channel 用来区分数据要交给谁处理:
| Channel | 作用 |
|---|---|
CHANNEL_ESP |
给 ESP32-C3 自己处理的控制命令 |
CHANNEL_BLE |
需要转发回 BLE characteristic 的数据 |
CHANNEL_BT/WIFI/ETH |
预留通道 |
这个协议的意义是:把 UART 上的一条字节流变成可校验、可路由的数据帧。
如果没有 magic,接收端很难在异常数据后重新找到帧头;如果没有 length,接收端不知道该读多少数据;如果没有 crc16,接收端无法判断数据是否在传输过程中损坏;如果没有 channel,所有业务都混在一起,扩展会很困难。
六、OTA设计主线
项目最核心的能力是 OTA。
它支持两类升级目标:
| 类型 | 作用 |
|---|---|
OTA_TYPE_ESP |
升级 ESP32-C3 自己的 app 分区 |
OTA_TYPE_FW_APP |
把下游 MCU 固件写入 ESP32-C3 的 fw_app 分区,再通过 UART DFU 写入下游 MCU |
分区表中可以看到:
ota_0 app ota_0 1536K
ota_1 app ota_1 1536K
fw_app 0x40 0x00 512K
这说明项目采用了两级升级思路:
手机 / 上位机
|
| BLE OTA
v
ESP32-C3 Flash
|-- ota_0 / ota_1:ESP32-C3 自身固件
|-- fw_app:下游 MCU 固件缓存区
|
| UART DFU
v
下游 MCU Flash
OTA 协议不是简单地连续写数据,而是一个状态机:
IDLE
|
| START: type, size, crc32
v
DATA
|
| 分包传输,单包 CRC16,整包 CRC32
v
FINISH
|
| 设置启动分区或触发 DFU
v
IDLE
这种设计的优点是:
- 可以避免 BLE 传输中丢包后整包失败。
- 可以通过 offset 和 size 精确请求下一段数据。
- 单包使用 CRC16 快速发现局部错误。
- 整包使用 CRC32 保证最终固件完整性。
- 超时和重试机制可以提升弱连接环境下的可靠性。
七、 FreeRTOS 在项目中的角色
这个项目不是裸机 while 循环,而是典型的 RTOS 事件驱动结构。
主要用到了:
| FreeRTOS 机制 | 在项目中的用途 |
|---|---|
| Task | UART 接收任务、BLE notify 任务、OTA 处理任务 |
| Queue | 跨任务传递 BLE notify、OTA 消息、runloop 任务 |
| Semaphore | 等待 BLE service 创建完成、控制 UART 接收暂停 |
| Timer | OTA 超时、升级完成后延迟重启 |
这里有一个很重要的嵌入式设计思想:不要在 BLE 回调或串口接收流程里做复杂耗时逻辑。
BLE GATT write 回调收到数据后,项目通常只是把数据复制到队列里,然后由专门的 OTA task 处理。这样可以避免阻塞蓝牙协议栈,也能让业务逻辑更清晰。
这篇文章主要是对项目整体框架进行一个大致的梳理,在后续文章我们会进行更加详细的回顾与介绍。
更多推荐


所有评论(0)