首页 / 资讯中心 / 文章详情

STM32上TencentOS tiny移植MQTT与ESP8266 AT模组对接实战

STM32上TencentOS tiny移植MQTT与ESP8266 AT模组对接实战 ★ FEATURED ARTICLE
SIM7600CE还是一个比较老的模块当时我记得在某开发者那边拿的样片说是兼容市面上常见的64位平台。我在STM32F407上最开始怎么调都注册不了网络后来看手册发现要先通过AT指令确认模块的供电电压和SIM卡检测逻辑我这块板子的SIM卡槽接触不太好经常在开机后识别不到SIM卡表现为“ATCPIN?返回ERROR用回形针把卡托金属面稍微顶了一下就好了。所以遇到模块连不上网先别急着怀疑代码查一下SIM卡座的物理接触这个坑我帮别人排查过好几次。下面正式说移植流程。3.1 第一步确认启动文件和中断向量表TencentOS tiny在Cortex-M4上的移植核心就是把操作系统的那几个系统调用和上下文切换代码跟你的硬件平台绑起来。Keil工程里要确保启动文件选择的是跟你芯片匹配的那个版本我用的是startup_stm32f40xx.s这个文件里面默认只开了几个中断比如PendSV和SysTick。如果是从其他工程拷过来的一定要检查PendSV_Handler和SysTick_Handler这两个中断向量有没有被注释掉否则操作系统一跑就进HardFault。我当时就是图省事直接拿了一个裸机LED工程的启动文件结果编译能过一运行就死在HardFault_Handler里调试器一看PC指针跑到了一个奇怪的地址。后来跟TencentOS tiny的官方移植示例对照才发现是启动文件里PendSV_Handler没开。这个问题在文档里写得很隐晦属于典型的“编译正常但运行必死”的坑。3.2 第二步配置tos_config.h裁剪组件TencentOS tiny不像FreeRTOS那样所有功能默认全开而是通过一个统一的tos_config.h头文件来做组件裁剪。这个文件在arch目录下每个芯片平台都有一份默认配置。你需要重点检查这几个宏定义TOS_CFG_MMHEAP_EN内存堆管理使能MQTT组件会动态分配内存必须打开而且TOS_CFG_MMHEAP_SIZE要留够官方默认是8192字节如果你要同时开两个MQTT连接或者订阅的主题比较多建议改到16384。TOS_CFG_MQTT_EN这个就是MQTT组件的总开关编译之前必须先置1。TOS_CFG_AT_ENAT指令框架的使能如果你用的是ESP8266这类AT模组这个必须打开后面会讲它跟MQTT的关系。TOS_CFG_PWR_MGR_EN低功耗管理可以先关掉省得调试时莫名其妙进入睡眠。很多人移植不成功就是因为在tos_config.h里把TOS_CFG_MODULE_EN置0了结果MQTT相关API链接不到。这个文件改完以后要同步到编译路径下有些工程是拷贝到用户目录的改错文件等于白改。3.3 第三步选择MQTT移植路线TencentOS tiny的MQTT组件本身只实现了MQTT协议栈的客户端逻辑它不关心底层数据是通过Wi-Fi还是4G出去的。所以你要解决的是“操作系统跟远端MQTT Broker之间的数据通道”问题有两条路可以走第一条是“原生模组直连”即在支持TCP/IP协议栈的模组上比如ESP8266的AT固件或者SIM7600的AT命令用AT指令建立TCP连接然后把MQTT组件的报文从这个TCP连接里发出去。这种方式需要实现TencentOS tiny的AT框架跟MQTT组件之间的对接层用现成的参考代码。第二条是“以太网协议栈”如果你的板子上有以太网PHY比如LAN8720那就用LwIP协议栈MQTT组件把数据交给LwIP的TCP socket看起来就更像标准做法。但这种方式对新手不够友好涉及LwIP的移植和内存池配置工程量更大。我这次走的是第一条路ESP8266 AT模组一个是因为手头正好有另一个是因为TencentOS tiny原生的MQTT接入例程里AT框架的对接代码已经写好了很少需要改动。后面第四章我会详细说这条路上最容易出错的地方。3.4 第四步创建任务并初始化MQTT客户端TencentOS tiny的MQTT使用方式跟PAHO MQTT嵌入式C库很像先初始化客户端结构体然后建立连接再循环处理收发。但它在任务模型上做了适配你不能在一个死循环里干等而是要把MQTT的处理过程放进一个独立的任务里让操作系统来调度。我建了一个优先级为3的任务栈大小给到了1024字节注意这是字的单位在Keil里配置时别搞混因为MQTT组件在处理消息时需要递归调用栈太小会溢出。任务代码大概是这样的逻辑初始化AT模组、联网、再初始化MQTT客户端然后进入一个while循环调用连接函数、订阅、发布最后循环调用yield或keepalive处理函数。实际调试时我发现把MQTT客户端的初始化放在任务函数里比放在main函数里更稳因为TencentOS tiny的任务栈跟主栈是分开的如果你把大结构体定义成局部变量而任务栈又不够大就会直接踩到未定义内存表现为随机死机。3.5 第五步处理MQTT事件回调TencentOS tiny的MQTT组件允许你注册一系列回调函数比如连接成功、连接断开、订阅确认、消息到达等。回调函数在组件内部上下文执行不要在回调里做耗时操作比如别在消息到达回调里直接调用printf打印长字符串这会阻塞操作系统调度。我在调试时吃过这个亏消息一多整个系统就卡顿后来改成用消息队列把收到的数据POST到另一个处理任务里就顺畅了。4. ESP8266 AT适配层整个移植里最容易被忽视的环节说句实话TencentOS tiny的MQTT组件本身移植难度不高真正折磨人的是AT模组的适配。因为MQTT组件发出的数据包是二进制的里面包含MQTT固定头、可变头、有效载荷这些字节流要原封不动地通过AT指令发给模组。4.1 AT模组透传模式的切换逻辑ESP8266的AT固件有两种模式普通AT命令模式和透传模式。MQTT报文是不能一条一条用AT指令发出去的因为每条AT指令都有响应延迟和结尾换行符报文会被拆得七零八落。所以正确做法是先用ATCIPSTART建立TCP连接然后ATCIPMODE1进入透传模式再用ATCIPSEND触发发送之后所有写入串口的字节都会直接进TCP连接。这就有个关键问题进入透传模式以后AT指令和MQTT数据包之间如何区分TencentOS tiny的AT框架用了标志位加状态机的机制在使用MQTT组件发送数据之前会先发退出透传切换回AT指令模式然后再发ATCIPSEND重新进入透传。如果这两个过程时序不对模组就会莫名其妙返回EAT_ERROR而且大概率之后的整个会话都废了。4.2 串口缓冲区与数据流控AT模组跟MCU之间通过串口通信串口的接收缓冲区如果太小模组一次性发回几百字节的MQTT报文时就会丢掉大半。TencentOS tiny的AT模块默认接收缓冲区是512字节对于QoS1的PINGRESP和PUBLISH报文来说勉强够用但如果Broker下发了一个包含长Topic和长Payload的消息512字节就明显不够了。我把AT接收缓冲区改到了1024字节同时在串口中断里做环形队列防止中断服务函数里耗时过长。另外一个容易被忽略的细节是Hardware Flow ControlESP8266的CTS/RTS引脚如果不接而串口速率又配到了115200以上波特率误差会导致某些模块偶尔丢字节表现就是MQTT心跳超时断连。后面排查问题时一定要先排除这个。4.3 参考代码的错误修复我在用TencentOS tiny ESP8266示例工程时发现它的AT发送函数里有个关于“发送完毕后查询是否成功”的逻辑在模组固件版本不同时会有差异。某款ESP8266模组在CIPSEND后不会返回SEND OK而是直接返回空行导致代码认为发送失败连接被关闭。这个问题的处理方式是不检查SEND OK而是等一小段时间比如100ms后直接发下一个包。这样做虽然不完美但实测稳定性反而更高。如果你用的是自己适配的模组务必在抓包工具里确认模组的实际响应格式否则会在这种细节上耗掉一整天。5. 移植过程中的三个关键验证步骤从点灯到Broker连接代码写完之后不要急着往业务逻辑上堆功能。我习惯分三步走把每一步都验证透了再继续这样出了问题能快速定位。5.1 验证操作系统调度正常先只跑一个LED翻转任务确保TencentOS tiny的调度、SysTick和空闲任务都正常。如果LED每秒翻转的频率跟代码设置的延时一致说明操作系统核心已经跑通了。这一步别跳过很多移植问题在第一步就能暴露。我见过有人在RTOS都没有跑起来的情况下直接调MQTT最后定位到是os延时函数没生效重复造了一整天的轮子。5.2 验证AT模组联网与MQTT连接接着单独跑一个AT指令测试任务依次执行查询模组版本、信号强度、网络注册、接SIM卡状态最后再用一个简单的TCP connect测试能否连上某个服务器。如果TCP能通说明模组网络没问题然后用MQTT客户端去连接Broker。我在这一步用了一个小巧的本地Broker工具电脑上跑着MQTT组件的Broker地址填电脑的局域网IP。当你看到客户端状态从“正在连接”变成“已连接”时心里那块石头才算真正落地。5.3 验证发布/订阅消息流连接成功只是第一步还要验证消息流。我在任务里同时订阅了一个主题并向另一个主题周期性发布数据然后让Broker端回发一条消息。如果MCU能在订阅回调里收到这条消息并翻转LED说明整个数据链路是通的。在这个测试里我遇到了一个经典问题发布QoS1消息后Broker应答的PUBACK被ESP8266断开之后MQTT组件重连再续传。这里需要理清楚MQTT的QoS语义QoS1是至少一次不是恰好一次。如果你的业务不能容忍重复消息要么选用QoS0要么在应用层做去重。6. 一个经典的断连排查案例MQTT连接15秒后反复掉线这个案例我印象很深也最能体现整个移植过程中的排查思路。当时在某开发板上跑MQTT连接能建立起来但总是每隔15秒左右就断开日志里显示“Connection lost”重连之后又是15秒。一开始我以为是网络不稳定但电脑上同一Broker的其他客户端都好好地挂着。然后把波特率从9600调到115200情况依旧。用串口监听工具抓模组的AT交互日志发现模组偶尔会返回ERROR排查到定位之后确认是发送缓冲区溢出了。原来我在任务里用了一个局部数组缓存待发送的MQTT报文数组长度是128而某些订阅消息的主题加负载已经超过了这个长度写入时越界把相邻的缓冲区数据覆盖了导致报文损坏连接被Broker判定为协议错误强制断开。这个案例给了两个经验一是在嵌入式环境里凡是涉及协议报文打包缓冲区长度一定要根据最坏情况来算而不是平均数二是排查断连问题时先把串口日志打开把每个MQTT报文进出都记录下来通过Broker端日志和模组日志对照很快就能定位问题出在哪一层。7. 一些实操层面的使用心得移植跑通之后我还在实际项目中用了一段时间这期间积累了一些使用心得分享出来给后来人参考。关于主题的层级设计在资源受限的MCU上不要照搬云端的“长而丰富”的命名习惯。主题越短报文头越小。如果Topic很长每次PUBLISH光可变头部就要占不少字节流量和内存都不划算。如果涉及多个设备建议用“产品类型/设备ID/功能”三级结构设备ID可以放在ClientID里不必拼在Topic里这样能省不少字节。关于心跳间隔的设定MQTT的KeepAlive机制是双刃剑设得太短会频繁发PINGREQ浪费流量电量设得太长则可能在中途已经断网后很久才发现。一般建议是服务器超时时间1.5倍KeepAlive至少要大于你的TCP层探测时间。在Wi-Fi网络里我通常设在30到60秒但在4G模块上会适当调短到15秒左右因为4G链路断开时模组自己不一定能及时感知。关于日志打印的坑我调试时喜欢在MQTT回调里打印调试信息但printf串口输出有个隐性问题——它本身会消耗不少CPU时间尤其是在115200波特率下打印一长串hex数据整个任务会被阻塞很久。后来我专门写了一个非阻塞的日志模块把日志通过DMA输出CPU占用一下子就下来了。如果项目对实时性有要求这点最好提前做。关于任务优先级TencentOS tiny的调度是抢占式的MQTT任务不要设成最高优先级否则当它陷入socket读阻塞时其他紧急任务比如按键响应会被饿死。我的做法是让MQTT任务优先级比关键业务任务低一级同时在MQTT连接函数里设置超时时间避免无限阻塞。8. 移植完成之后你还能做什么MQTT组件跑通只是一个起点后续能玩的东西反而更有意思。比如你可以把传感器数据周期性地发布出去然后用收发消息控制板载外设也可以基于TencentOS tiny的队列机制把MQTT收上来的控制指令分发到不同的业务模块里去处理。如果你有兴趣深入可以看看MQTT 5.0协议文档了解会话过期、用户属性等新特性然后对比一下TencentOS tiny的MQTT组件是基于MQTT 3.1.1实现的两者之间的差异在哪里。遇到问题时不要急着找资料先自己读代码配合抓包工具往往比到处搜有用得多。最后再分享一个小技巧如果你用的是某云平台的定制Broker要特别注意它的鉴权方式有些平台需要在MQTT CONNECT报文的Username和Password字段里填特定的组合而TencentOS tiny的MQTT组件初始化结构体里预留了这些字段只是有些参考代码没把这几个字段填全导致连接总是被服务器拒绝。把文档里给出的鉴权三元组ClientID、Username、Password逐一对应上连接问题就迎刃而解了。希望这篇移植记录能帮你少走一些弯路。嵌入式开发这东西很多弯弯绕绕确实要自己踩过才能记住但别人走过的坑能避就避省下来的时间都值得。
阅读完成 · 觉得有帮助?
咨询建站