尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32WB双核BLE协议栈开发实战:从入门到量产

STM32WB双核BLE协议栈开发实战:从入门到量产 STM32WB这颗料我第一次拿到手的时候第一反应是这不就是一颗带蓝牙的M4嘛。真正写代码才发现它跟你以前玩过的所有蓝牙MCU都不一样——M4和M0双核、BLE协议栈以预编译固件形式跑在M0上、主核通过共享内存和消息队列发各种HCI/ACI命令……这套东西刚上手的时候确实有点绕论坛里也经常看到有人卡在“协议栈起不来”“连不上手机”这类问题上。这篇指南就是把我从零调试到量产在STM32WB上写BLE协议栈程序踩过的坑、验证过的路径完整梳理一遍给准备入坑或者正在被坑的你一个可以直接照着走的参考。这篇文章会覆盖几个重点先讲清楚STM32WB的双核架构和协议栈加载机制——这是搞懂一切的基础然后说CubeMX工程怎么配、CPU2固件怎么烧接着是GAP/GATT这些核心API怎么调、事件怎么处理包括一个可以直接跑的自定义服务例子最后是低功耗和实战中最容易出问题的几个点。适合刚接触STM32WB的嵌入式开发者也适合那些已经从标准库迁移过来、但被双核和协议栈折腾到崩溃的同行。1. STM32WB双核架构与协议栈运行机制1.1 两颗Cortex-M内核到底怎么分工STM32WB跟普通单片机最大的不同就是它内部实际上有两颗Cortex-M核心一颗是Cortex-M4跑你的应用逻辑跟其他STM32没啥区别另一颗是Cortex-M0专门跑ST预编译好的蓝牙协议栈固件。M4是老大M0是专门跑无线协议栈的“打工仔”两者通过IPCC内部处理器通信控制器和一块共享RAM来交换数据。这个分工逻辑其实很好理解BLE协议栈对实时性要求极高广播、扫描、连接事件、重传这些时序都必须精确到几十微秒级别如果跟你主应用逻辑跑在同一颗核上一旦主循环里有个耗时操作无线链路就直接崩了。ST把协议栈放在独立核上跑M4这边就算死循环了M0上的协议栈依然能维持射频链路这就把“应用复杂度”和“射频实时性”彻底解耦了。你要做的就是在M4上通过ST提供的API往共享内存里丢命令然后等M0处理完再以事件的形式把结果丢回来。整个过程对上层是异步的你发的命令不会立刻返回结果而是过一段时间回调事件。这个“命令-事件”的异步模型是STM32WB编程跟普通单片机编程最不一样的思维方式。1.2 协议栈固件是独立的别指望M4里能编译出BLE协议栈很多新手第一次看STM32CubeWB固件包时会懵怎么协议栈源码找不到实际上BLE协议栈在STM32WB上是作为“预编译二进制”CPU2固件存在的M0核从芯片内部的Flash特定区域运行这份固件主核M4根本看不到它的源码。整个CPU2侧的软件栈分两层底层叫FUSFirmware Upgrade Services负责安全启动、固件版本管理、无线协议栈的升级上层才是Wireless Stack无线协议栈包含BLE、802.15.4的完整协议实现。你拿到的芯片出厂时可能只烧了FUS不烧无线协议栈或者烧了老版本这些都需要你通过STM32CubeProgrammer单独烧录到CPU2的Flash区。这就引出一个关键教训CPU2固件和CPU1固件是两套独立烧录的东西。很多人习惯性地只编译下载M4的应用固件结果跑起来发现HCI命令全部超时就是这个原因。实际开发流程中每次拿到新板子首先要检查CPU2的固件版本是否和你的SDK匹配这个我后面会专门讲。1.3 IPCC、共享内存和HCI/ACI命令模型M4和M0通信依赖两条路IPCC硬件中断线和共享内存。你调用hci_register_io_bus()把HCI传输层对接好之后hci_reset()、aci_gap_init()这些API发出去的命令都会被封装成标准格式通过共享内存传递到M0然后M0解析执行再回调hci_le_meta_event、hci_connection_complete_event等事件。这里有个概念要区分清楚ST的BLE协议栈API分为HCIHost Controller Interface和ACIApplication Controller Interface两组。HCI是蓝牙标准定义的主机-控制器接口处理的是链路层、连接、广播等标准操作ACI是ST自己扩展的应用接口用于GATT、GAP这类更高层的管理操作。你在代码里见到的aci_gap_init、aci_gatt_update_char_value都属于ACI而连接参数更新、扫描这类更底层的操作则是HCI命令。理解这条链路的核心在于不要指望API是“同步返回结果”的。你调用aci_gap_start_advertising返回的只是“命令是否被成功接收”而广播真正启动成功是异步事件。所以写代码时必须严格事件驱动所有状态机都靠事件回调推进主循环只管睡眠和其他应用逻辑就行。2. 开发环境搭建与工程配置2.1 CubeMX里的关键配置项STM32WB开发最正统的路径是用STM32CubeMX生成初始化代码然后再基于STM32CubeWB固件包写业务逻辑。但CubeMX里这棵配置树看着简单实际上有几个关键地方配置错了后面跑不起来我把最容易出问题的几个列出来。时钟树这块STM32WB内部有两套独立时钟域M4核跑系统时钟M0核和射频前端需要精确的32MHz时钟。必须保证HSE32外部高速32MHz晶振配置正确RF子系统才能工作。我见过有人为了省成本用内部HSI结果蓝牙传输距离和稳定性差到没法用。BLE的射频时序精度要求很高外部32MHz晶振不是可选项是必选项选型时还要注意晶振的精度最好用20ppm以内的。外设配置里IPCC是必须启用的它是M4和M0通信的中枢RCC要开启RFWKP射频唤醒和相关中断HSEM硬件信号量最好也启用避免双核同时访问共享内存时数据错乱。另外如果你要用低功耗模式电池电量检测相关的ADC通道和RTC闹钟也要提前配好这几个外设的初始化代码CubeMX可以自动生成省得手写。TrustZone是个坑。STM32WB部分型号支持TrustZone安全隔离如果CubeMX里默认开启了TrustZone那么Flash会划分成安全区和非安全区你的应用代码要放到非安全区调用协议栈API的方式也会变复杂。如果你只是做普通BLE应用建议直接把TrustZone关掉能省掉至少几个小时的折腾时间。2.2 CPU2固件的安装与烧录这一步是STM32WB开发中绕不过去的关键环节。我从实战经验来说第一次上电后可以先跑一下官方的BLE_HeartRate例程如果例程能跑通说明CPU2固件已经就绪如果卡死在初始化HCI命令上大概率就是CPU2固件版本不对或没烧录。烧录CPU2固件的方式主要有两种。第一种是用STM32CubeProgrammer的图形界面在“Firmware Upgrade”标签页选择对应的无线协议栈固件文件在STM32CubeWB固件包的Projects/STM32WB_Copro_Wireless_Binaries目录下然后点击升级。第二种是命令行方式适合量产和CI环境STM32_Programmer_CLI -c portSWD modeHOTPLUG -fwupgrade \ stm32wb5x_BLE_Stack_full_fw.bin \ start0x08097000这里要注意start地址必须和固件包里的说明一致不同版本的无线堆栈起始地址可能不同烧错了系统直接起不来。另外升级完成后CubeProgrammer会提示“CPU2 is running”这时候可以读取版本号确认一下STM32_Programmer_CLI -c portSWD modeHOTPLUG -r32 0x20030030 4实际开发中我吃过一个亏当时手头芯片出厂自带的FUS版本比较老无法烧入新版本的BLE协议栈固件必须先升级FUS再升级协议栈顺序反了就会出现烧录报错。遇到这种情况先搜一下ST的AN5185文档里面详细写了FUS和无线协议栈的版本兼容矩阵按照表格要求先刷FUS就行了。2.3 内存分配与链接脚本的坑STM32WB内部Flash虽然不算小WB55RG是1MB但M4应用和M0固件是共享这颗Flash的。CPU2的FUS和无线协议栈固件会占用从0x08097000开始的一段区域所以你写M4代码时链接脚本的Flash起始地址绝对不能从0x08000000开始必须跳过CPU2固件的区域。一般ST的例程里链接脚本已经处理好了FLASH的起始地址会指向0x08097000之后不要手贱去改。RAM同理CPU1可用的RAM区域也刨掉了CPU2要用的共享内存部分。如果你擅自修改了链接脚本最典型的症状是编译能过烧进去也正常但一调用BLE API就HardFault因为你的代码或变量把CPU2固件所在Flash覆盖了或者共享内存区被你的应用数据踩掉了。这部分的经验教训是除非你非常清楚自己在干什么否则CubeMX生成的链接脚本一个字段都不要动。如果嫌Flash不够用应该去优化M4侧的代码大小而不是去压缩CPU2的保留区。3. BLE协议栈核心编程模型与API实战3.1 hci_register_io_bus到事件回调的完整链路代码层面协议栈初始化的标准流程是先注册HCI传输层再依次复位协议栈、初始GATT、初始GAP。最基本的初始化代码大致是这样int main(void) { HAL_Init(); SystemClock_Config(); /* 注册HCI传输层IPCC方式与CPU2通信 */ hci_register_io_bus(hci_io_ipcc); /* 等待CPU2协议栈复位完成 */ while (hci_reset() ! BLE_STATUS_SUCCESS) { /* 重试直到协议栈就绪 */ } /* 初始化GATT和GAP */ aci_gatt_init(); aci_gap_init(GAP_PERIPHERAL_ROLE, 0, 0x07, service_handle, dev_name_char_handle, appearance_char_handle); /* 注册事件回调 */ hci_event_register(hci_event_callback); /* 开始广播 */ start_advertising(); while (1) { /* 主循环睡眠、处理应用逻辑 */ } }hci_register_io_bus这一步很关键它决定了M4和M0之间的物理传输方式。STM32WB上固定使用IPCC但API设计上把传输层抽象出来了后面ST也支持通过SPI外接蓝牙芯片时复用这套HCI接口。注册完后hci_reset()会通过共享内存发送复位命令这时M0那边可能还没准备好我通常会在前面加一个超时重试机制最多等3秒。事件回调是整个协议栈程序的心脏。所有协议栈往应用层推送的状态变化比如连接建立、断开、数据接收、特征值被写入都是通过同一个回调入口进来的void hci_event_callback(void *pData) { hci_event_pckt *event_pckt (hci_event_pckt *)pData; switch (event_pckt-evt) { case HCI_LE_META_EVT: process_le_meta_event((hci_le_meta_evt_t *)event_pckt-data); break; case HCI_VENDOR_SPECIFIC_EVT: process_vendor_event((hci_vendor_specific_event_t *)event_pckt-data); break; default: break; } }写事件回调时我摸索出一个好的实践不要在回调里做耗时操作比如memcpy大量数据、调用printf这些都会阻塞M0往共享内存里塞新事件。正确做法是回调里只做状态标记和必要的指针保存把真正的处理逻辑丢到主循环或低优先级任务里去。3.2 GAP层广播配置与设备可见性BLE设备之间的互相发现靠的是广播。GAP层相关的API主要处理广播数据、广播参数、连接参数这几个方面。一个典型的广播初始化流程里aci_gap_set_advertising_configuration负责配置广播参数aci_gap_set_advertising_configuration( 0, /* 广播句柄 */ GAP_ADV_IND, /* 可连接非定向广播 */ ADV_INTERVAL_MIN, /* 最小广播间隔 */ ADV_INTERVAL_MAX, /* 最大广播间隔 */ GAP_STATIC_RANDOM_ADDR, /* 地址类型 */ NULL, /* 不使用直接连接广播 */ 0, /* 不使用白名单 */ 0, /* 广播通道 */ GAP_FILTER_ANY, /* 扫描请求过滤策略 */ 0, 0, NULL /* 无额外配置 */ );广播间隔看起来是个小参数实际对功耗和连接速度影响很大。广播间隔越小设备被发现的越快但平均功耗越高。20ms的广播间隔平均电流可能要到几百微安而100ms的广播间隔可以降到几十微安。如果产品对低功耗要求高建议动态调整待机时用500ms~1s的慢广播当用户通过按键或手机App触发配对时再切换到20ms的快广播这是行业里非常通用的做法。广播数据则是通过aci_gap_set_advertising_data设置的这块的数据格式完全遵循蓝牙规范前导长度、AD类型、payload。一个包含设备名称和UUID的广播数据构造大致如下uint8_t adv_data[] { 0x02, 0x01, 0x05, /* Flags: LE General Discoverable */ 0x03, 0x03, 0x12, 0x18, /* Complete List of 16-bit Service UUIDs */ 0x05, 0x09, M, Y, D, E, V /* Complete Local Name: MYDEV */ }; aci_gap_set_advertising_data(0, GAP_ADV_DATA, sizeof(adv_data), adv_data);广播数据的组装是BLE开发中经常出错的地方常见问题包括AD Type填错、长度字节和实际数据不匹配、广播数据总长度超过31字节的BLE广播包上限。如果广播数据总长度超过31字节需要开启扩展广播——STM32WB是支持BLE 5.0的可以用aci_gap_set_advertising_configuration里的扩展广播参数来配置不过大部分应用场景用基础广播就够了毕竟扩展广播对手机兼容性还有一定要求。3.3 GATT层服务、特征与数据交互GATT层是BLE数据交互的核心。它的编程模型可以理解为你在设备上维护了一张“属性表”手机端通过发现服务、读写特征值、订阅通知来和这张表交互。在STM32WB上这张表的构建是通过aci_gatt_add_serv和aci_gatt_add_char一步步添加的。以我实际做过的一个温湿度传感器服务为例创建自定义服务的流程是/* 先添加一个自定义服务 */ aci_gatt_add_serv(UUID_TYPE_128, service_uuid, PRIMARY_SERVICE, GATT_SERVER_ATTR_MAX_NB, service_handle); /* 在服务里添加一个可读可通知的特征 */ aci_gatt_add_char(service_handle, UUID_TYPE_128, char_uuid, 2, CHAR_PROP_READ | CHAR_PROP_NOTIFY, ATTR_PERMISSION_NONE, 0, 16, 0, char_handle);这里有几个参数值得展开说。CHAR_PROP_READ | CHAR_PROP_NOTIFY定义了特征支持的操作可读意味着手机能主动来读这个值可通知意味着设备可以主动往上推数据。ATTR_PERMISSION_NONE是属性权限和安全有关如果你的产品有配对绑定需求这里要配成ATTR_PERMISSION_READ_ENCRYPTED之类的权限位。数据更新用aci_gatt_update_char_value来实现uint8_t temp_humid[2] { temperature, humidity }; aci_gatt_update_char_value(service_handle, char_handle, 0, 2, temp_humid);这个API的意思是把temp_humid数组里的2个字节更新到指定的特征值里。如果这个特征的属性里带有CHAR_PROP_NOTIFY那么调用这个API后所有已订阅该特征的手机都会自动收到通知不需要再额外写推送代码。还有两个在开发中很容易被忽略的APIaci_gatt_add_cfg和aci_gatt_update_char_value里那个0参数前者用于配置特征值的缓存后者是ValuOffset——如果你要更新的值长度超过20字节由于BLE ATT层单包最大20字节的限制必须分多次调用这个API每次偏移20字节续传。很多新手直接塞一个50字节的数组进去结果发现手机收不全就是这个原因。3.4 事件处理与状态机设计BLE编程最核心的模式就是围绕“事件”构建状态机。以从机设备为例至少有这几个状态初始化→广播中→连接中→已连接→断开回到广播。每个状态对应不同的事件驱动逻辑。从机侧最关键的几个事件HCI_LE_CONNECTION_COMPLETE_EVENT连接建立成功此时可以获取连接句柄、协商后的连接参数。HCI_DISCONNECTION_COMPLETE_EVENT连接断开需要在这里决定是重启广播还是进入休眠。ACI_GATT_ATTRIBUTE_MODIFIED_EVENT手机端写入了特征值比如通过写特征值来控制设备。ACI_GATT_UPDATE_CHAR_VALUE_EVENT通知发送完成需要开启相应的事件掩码。每个事件对应一个处理分支分支里面根据当前状态做切换这就是最简单的状态机骨架。我见过很多新手把状态机设计得过于复杂实际上BLE从机应用状态不太多用一个volatile uint8_t ble_state变量加一个swtich就足够了不要为了“架构”而上状态机框架。主循环的事件处理我通常这样组织while (1) { /* 处理应用数据采集 */ if (sensor_data_ready) { aci_gatt_update_char_value(...); sensor_data_ready 0; } /* 进入低功耗 */ enter_low_power(); }这里的技巧是主循环尽量保持空转所有业务逻辑都通过事件回调置标志位主循环检测到标志位再去执行。这样既能保证功耗又能防止阻塞协议栈事件处理。4. 连接、数据传输与低功耗优化4.1 连接参数连接间隔、从机延迟与超时时间BLE连接建立后手机的Master和你的设备Slave按固定的时间间隔Connection Interval互相唤醒收发数据。连接间隔短数据实时性高但功耗大连接间隔长功耗低但数据延迟大。STM32WB上连接间隔的单位是1.25ms比如36代表45ms。连接参数里的三个核心参数连接间隔Master与Slave每次通信的间隔范围7.5ms~4s。从机延迟Slave允许连续跳过多少个连接事件跳过期间可以不用监听。这个参数能大幅降低功耗比如连接间隔30ms从机延迟4那么Slave最长可以120ms才醒来一次。超时时间超过这个时间收不到对方的任何数据包就判定连接失联。范围100ms~32s注意超时时间必须大于连接间隔×从机延迟1×2否则容易误判掉线。主机侧发起连接时这些参数在aci_gap_create_connection里设置。从机侧如果想要修改连接参数需要调用aci_l2cap_connection_parameter_update_req向主机申请更新。这里有个老生常谈的问题Android手机对从机发起的连接参数更新请求可能不响应因为Android要求从机器在广播里带上Slave Connection Interval Range并且这个范围要覆盖请求的参数。所以最好的方式是在广播数据里就声明好你的连接参数范围然后让主机第一次连接就按你的参数来而不是连接之后再申请修改。4.2 双核低功耗模式怎么配合STM32WB的低功耗设计是双核协同的。M4进入睡眠前必须保证M0侧也没有待处理的射频任务否则M0会通过IPCC唤醒M4。实际操作中我验证过一套可靠的低功耗流程先调用hci_suspend_resume(0x00)通知协议栈准备进入低功耗等协议栈返回确认后再关掉不必要的M4外设时钟最后执行HAL_PWR_EnterSTOPMode(PWR_STOPENTRY_WFI, PWR_STOPMODE_STOP2)。唤醒后先恢复系统时钟再调用hci_suspend_resume(0x01)恢复协议栈活性。低功耗模式下如何保证还能被手机连接是个关键问题。必须在进入低功耗之前把广播打开否则设备静默手机根本扫不到。广播开启状态下M0负责在广播事件期间唤醒射频M4则可以在事件间期睡觉整体平均电流能做到几十微安级别。如果你的产品还需要在低功耗下保持RTC计时RTC要配置为异步唤醒源每秒钟或每分钟醒来一次处理任务这个也是CubeMX里就可以配好的。4.3 大包数据与流控注意点BLE单包数据载荷最大244字节BLE 5.0的Data Length Extension但前提是两端都支持DLE并且协商成功。STM32WB默认可能没有启用DLE需要在连接建立后调用hci_le_set_data_length来协商。如果要传输大数据量比如OTA固件升级建议把MTU也协商大一些通过aci_gatt_exchange_config让客户端和服务端交换MTU大小。从机发送大包数据时要注意流控如果上一次通知还没发完就又调aci_gatt_update_char_value协议栈可能返回BLE_STATUS_INSUFFICIENT_RESOURCES之类的错误。好的做法是维护一个发送队列一个特征值更新完成后通过ACI_GATT_UPDATE_CHAR_VALUE_EVENT事件确认再发下一个或者用双缓冲交替写。实测下来用双缓冲事件确认的方式能把BLE的吞吐推到接近理论极限还不丢包。5. 常见问题与排查技巧实录5.1 协议栈无法启动卡在hci_reset这个问题的出现频率最高九成原因是CPU2固件没烧或者版本不匹配。排查方法很直接先用STM32CubeProgrammer连接芯片读取CPU2侧的FUS版本和无线协议栈版本再跟STM32CubeWB固件包里的版本比对。如果版本对不上重新烧录CPU2即可。还有一种比较隐蔽的情况hci_reset()偶尔能成功但aci_gatt_init()或aci_gap_init()返回错误。这种通常是共享内存被应用任务踩了或者初始化顺序不对。把工程里的初始化代码跟官方BLE例程逐行对比一般能找到差异。另外确认一下是否有其他外设的中断优先级配置过高抢占了IPCC中断导致事件丢失。5.2 手机扫描不到设备手机扫不到设备先从三个方向排查。第一确认广播真的开了通过读取aci_gap_get_advertising_configuration回读广播状态或者在CubeMonitor-RF里看射频抓包。第二确认广播数据格式合法特别注意广播数据里不能包含重复的AD Type和过长的Local Name有些手机对格式宽松但iPhone对广播数据格式要求极度严格一个字节不对直接扫不到。第三确认没有进入低功耗后射频被关掉如果你在调试时开着STOP模式先临时关掉低功耗排除这个因素。5.3 连接后频繁断开连接后频繁断开的常见原因我总结过一张排查表现象可能原因解决方案连接后1秒内断链路层加密或配对失败检查配对模式配置确保双方支持相同的认证方式周期性断开超时时间设置不合理确认supervision timeout 连接间隔×(从机延迟1)×2距离稍远就断射频匹配电路问题或天线效率差用频谱仪测发射功率检查匹配网络元件参数断开的瞬间有CPU异常应用代码在事件回调里执行了耗时操作将数据拷贝和处理移到主循环回调里只做标记从机延迟过大导致丢包包冲突和重传机制触发适当降低从机延迟或增大重传次数连接参数问题是最常见的。我调试时习惯在连接建立后把实际的连接间隔、从机延迟、超时时间通过串口打出来用hci_le_read_connection_update_complete_event或直接在连接完成事件里查看先确认底层参数是否符合预期再去查应用层逻辑。5.4 调试工具怎么选调试STM32WB的BLE协议栈我平时用这几样串口日志打应用层状态、STM32CubeMonitor-RF看射频空口包、nRF Connect手机App模拟主机进行交互验证。其中STM32CubeMonitor-RF是ST官方提供的空口抓包工具只要一块STM32WB开发板就能当BLE嗅探器用能直接看到广播包、连接包、ATT层数据比逻辑分析仪直观得多。如果你要写上位机做联调Windows端可以考虑用C#生态里现成的BLE库比如shiny.bluetoothle这类第三方库可以直接在PC上做扫描、连接、读写特征值的客户端测试省去自己封装WinRT BLE API的麻烦。这套组合拳打下来BLE开发的大部分疑难杂症都能定位。5.5 低功耗调试容易踩的坑低功耗调试最大的坑是你以为进了STOP模式实际上没进去。STM32WB的M4进入STOP前如果还有外设时钟没关、中断没挂起、或者WFI指令执行时有pending中断系统会立即从WFI退出。用电流表实测是最靠谱的如果发现电流没降下来就逐个外设关掉再试。另一个坑跟协议栈有关M4调用hci_suspend_resume(0x00)后如果你在这个状态下还去调用其他BLE API协议栈会返回错误甚至卡死。一定要在流程上保证“低功耗期间无BLE操作”通过标志位掐断所有应用层的BLE API调用等唤醒并hci_suspend_resume(0x01)之后再恢复。我在量产固件里专门加了一个状态检查函数所有BLE API调用前都断言当前不处于挂起状态后面再也没出现过低功耗导致协议栈异常的故障。6. 实战后的几点体会和小技巧最后分享几个我在实际开发中慢慢总结出来的体会。第一STM32WB这套双核架构一开始确实不习惯但一旦理解了“命令-事件”模型写起来反而比单核跑协议栈更省心因为你不必担心协议栈时序被应用代码破坏。第二强烈建议在工程里加一个“协议栈版本/CPU2固件版本”的运行时打印每次上电先确认版本匹配能避免很多因为升级固件包导致的诡异问题。第三BLE的开发一定要有抓包工具不要靠猜STM32CubeMonitor-RF至少要有一个空口包就能看出广播有没有发出去、连接参数是否正常、断链是发生在哪一层。还有个小技巧如果你发现调用某个ACI/HCI API总是返回错误码先别急着怀疑协议栈有bug先把错误码查手册每个API下面都有错误码说明再检查参数。ST协议栈的错误提示做得还算清楚90%的错误其实是参数类型、句柄、长度不对。实在查不到去ST社区搜一下这个错误码大多数情况都有现成答案。STM32WB的BLE协议栈编程说穿了就是掌握好双核分工、熟悉事件驱动、把状态机设计利索再加上一把趁手的调试利器。希望这篇指南能帮你少走一些弯路。后面有机会我再单独写一篇关于OTA固件升级和配对绑定的实战文章那个里面坑更多也更值得展开聊。
返回列表