想象一下你家里有几十个智能灯泡,突然发现有个安全漏洞需要修复。如果每个灯泡都要拆下来用USB线刷固件,这工作量简直让人崩溃。这就是OTA(空中升级)技术存在的意义——让设备像手机APP一样可以无线更新。
我在2018年第一次接触OTA项目时就踩过大坑。当时给客户做的智能门锁出现BUG,不得不派工程师全国跑现场升级,光差旅费就花了十几万。后来我们给第二代产品加上了蓝牙OTA功能,同样的问题只需要推送个升级包就能解决。
蓝牙OTA相比WiFi方案有几个独特优势:
我经手过的项目中,这几个组合最稳:
最近帮客户调试时发现,AT32的FLASH擦写速度比同价位STM32快30%,特别适合频繁OTA的场景。不过STM32的生态更成熟,新手建议先从STM32入手。
以Keil MDK为例,关键配置点:
c复制// 检查开发环境是否正常
#include "ble_gap.h"
void check_env(void) {
if(ble_stack_init() != NRF_SUCCESS) {
printf("蓝牙协议栈初始化失败!");
while(1);
}
}
OTA服务UUID建议采用自定义128位UUID,避免和标准服务冲突。我常用的特征值结构:
| 特征值 | 属性 | 作用 |
|---|---|---|
| FIRMWARE_VERSION | 只读 | 获取当前固件版本 |
| OTA_CONTROL | 写 | 开始/暂停/继续OTA |
| OTA_DATA | 写无响应 | 传输固件数据包 |
c复制// 蓝牙服务定义示例
static ble_uuid128_t ota_service_uuid = {
.uuid128 = {0x12,0x34,...} // 自定义UUID
};
static ble_gatts_char_md_t char_md;
static ble_gatts_attr_md_t attr_md;
// ...(具体属性配置代码)
蓝牙MTU通常只有20字节,必须实现可靠的分包机制。我的经验是:
实测发现,不加流控的话,STM32F103接收大文件会丢包率达到15%。后来改用下面这个结构就稳了:
c复制#pragma pack(1)
typedef struct {
uint16_t start_flag; // 0xAA55
uint16_t pkg_index;
uint8_t data[16];
uint16_t crc;
} ota_package_t;
#pragma pack()
我推荐使用这种内存布局:
code复制0x08000000 ┌─────────────┐
│ Bootloader │ 16KB
0x08004000 ├─────────────┤
│ Firmware A │ 主程序区
0x08020000 ├─────────────┤
│ Firmware B │ 备份区
0x0803C000 ├─────────────┤
│ OTA Config │ 4KB
0x08040000 └─────────────┘
关键点:
去年有个客户产品被恶意固件攻击,后来我们加入了这些防护:
c复制// 安全跳转示例
__asm void jump_to_app(uint32_t addr) {
LDR SP, [R0] // 加载新堆栈指针
LDR PC, [R0, #4] // 加载复位地址
}
STM32的FLASH写入有个坑:必须先擦除整页。通过这两个技巧可以提速:
实测AT32F407的FLASH写入速度优化前后对比:
| 方案 | 写入1MB耗时 | 平均电流 |
|---|---|---|
| 原始方案 | 28s | 45mA |
| 优化方案 | 9s | 32mA |
BLE广播时功耗可以做到15μA以下,关键配置:
c复制void enter_low_power(void) {
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后需要重新初始化时钟
SystemClock_Config();
}
上周刚帮学员解决的典型问题:
现象:升级到50%必定失败
排查:
解决方案:
其他高频问题:
如果想做得更专业,可以考虑:
最近用CubeMonitor发现个隐藏问题:STM32在FLASH写入时会产生2.4GHz频段干扰。解决方法是在关键代码段加屏蔽罩,或者改用AT32的独立Flash控制器。