一、项目介绍

该项目是一个基于 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 处理。这样可以避免阻塞蓝牙协议栈,也能让业务逻辑更清晰。

这篇文章主要是对项目整体框架进行一个大致的梳理,在后续文章我们会进行更加详细的回顾与介绍。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐