ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32+Air780E按键发中文短信实战:PDU编码与AT指令避坑指南

STM32+Air780E按键发中文短信实战:PDU编码与AT指令避坑指南 按键发短信这件事看起来简单真做起来坑不少。我最近刚把一个STM32F103加Air780E的方案跑通功能很朴素按一下键模块往指定号码发一条中文短信OLED上同步显示发送状态。但就是这么一个朴素的需求从PDU编码到AT指令时序从OLED刷新到串口丢包我前后折腾了差不多一周。这篇文章就把整个过程的思路、代码和踩过的坑完整摊开讲一遍适合正在做类似物联网报警、远程通知、设备状态上报的嵌入式同行参考也适合刚接触4G模块AT指令的朋友当作一个完整的实战案例来看。1. 为什么选STM32Air780E这套组合1.1 需求拆解一个按键触发短信到底涉及哪些环节先把需求翻译成技术语言。用户按下一个物理按键系统需要完成这么几件事检测按键动作消抖、组织短信内容中文意味着要处理编码、通过串口向4G模块下发指令、等待模块返回结果、把结果反馈到OLED屏幕。这里面每一个环节单独看都不难难的是它们要串在一起稳定运行。中文短信这个点是最容易被低估的。很多人第一反应是直接发文字不就行了但短信的底层传输格式是PDUProtocol Data Unit模式中文必须经过UCS2编码再转成十六进制字符串还要计算长度、拼接短信中心号码。如果只发英文用Text模式确实简单但中文绕不开PDU。按键检测看似简单但机械按键的抖动如果不处理一次按下可能触发好几条短信这在报警场景里是灾难。OLED显示则承担了人机交互的角色让操作者知道短信到底发出去没有而不是对着一个黑盒子猜。1.2 Air780E相比传统模块的优势在哪市面上4G Cat.1模块不少我选Air780E主要看中几点。它是Cat.1制式功耗和成本比Cat.4低但速率对短信这种小数据量场景完全够用。它支持标准AT指令集上手门槛低网上资料也多。供电范围友好典型3.8V供电配合锂电池方案很自然。更关键的是它的AT指令兼容性好。发短信用的ATCMGF、ATCMGS、ATCSCA这些指令和早年2G模块的用法基本一致意味着我之前积累的PDU编码经验可以直接复用。这一点在选型时非常重要能省下大量调试时间。对比另一类方案比如用ESP32直接联网发HTTP请求到云平台再转发短信链路更长、依赖服务器、还有额外费用。对于设备本地触发、必须可靠送达的场景直接用模块发短信反而是最稳的。1.3 硬件连接与供电的隐藏细节接线本身不复杂但有几个点必须提前说清楚。Air780E的串口是1.8V电平还是3.3V电平这个一定要查手册确认。我手上这块模块的UART是3.3V兼容的可以直接和STM32的USART对接但如果遇到1.8V的版本中间必须加电平转换否则要么通信不上要么长期运行损坏IO。供电是另一个大坑。4G模块在发射瞬间的电流峰值可以冲到2A如果电源设计余量不够模块会在发送短信的瞬间掉电重启表现就是短信偶尔发不出去。我的做法是给模块单独一路LDO或者DC-DC输入电容至少470uF再并一个100uF的钽电容保证瞬态响应。STM32这边用USART2接模块波特率115200OLED用I2C1接0.96寸SSD1306。按键接在PA0配置成上拉输入按下接地。整个系统的供电我用的是5V输入经AMS1117转3.3V给MCU和OLED模块单独用一路3.8V。2. PDU编码中文短信绕不开的核心环节2.1 PDU模式与Text模式的本质区别Text模式下发短信指令里直接写明文比如ATCMGS13800138000然后跟内容。这种方式对英文和数字很友好但中文会乱码因为Text模式默认用GSM 7-bit编码根本装不下汉字。PDU模式则是把整条短信按协议规定的格式打包成一串十六进制字符。它包含了短信中心号码、目标号码、编码方式、时间戳、内容长度和内容本身。中文用UCS2编码每个汉字占2字节转成4个十六进制字符。虽然麻烦但这是中文短信唯一可靠的方式。我一开始也想偷懒用Text模式结果发出去的中文全是问号后来老老实实回到PDU。这里给个建议只要涉及中文直接上PDU别在Text模式上浪费时间。2.2 短信中心号码的格式转换PDU串的第一部分是短信中心号码格式是长度号码半字节交换类型。以中国移动的短信中心号码8613800100500为例去掉后是8613800100500共13位。如果位数是奇数末尾补F变成8613800100500F然后两两交换得到683108100005F0。前面加上长度字节0D13的十六进制和类型91表示国际号码最终是0D91683108100005F0。目标号码的处理类似。13800138000是11位奇数补F成13800138000F交换后3108108300F0前面加长度0B和类型91得到0B913108108300F0。这部分最容易出错的是半字节交换的方向。记住规则从左到右两两一组组内前后对调。写个函数自动处理别手动算手算迟早出错。2.3 UCS2编码与长度计算中文内容要转成UCS2。比如测试两个字Unicode分别是6D4B和8BD5拼起来就是6D4B8BD5。在STM32上如果用的是GBK编码的字符串需要先转成Unicode再转十六进制。我用的方案是预先在PC上把要发的中文转好直接以十六进制数组的形式存在代码里避免在MCU上跑复杂的编码转换。长度计算是另一个坑。PDU里有一个字节表示用户数据长度它指的是UCS2编码后的字节数不是字符数。测试是2个汉字UCS2后是4字节所以长度字节是04。如果算成字符数写02短信会发送失败或者内容被截断。完整的PDU串结构是这样的短信中心号码 目标号码 00协议标识08UCS2编码 时间戳可填00让模块自动生成 用户数据长度 用户数据。把这些拼起来就是最终要发给模块的内容。2.4 一个可直接复用的PDU组装函数我在代码里写了一个函数输入目标号码和UCS2十六进制字符串输出完整PDU。核心逻辑分三步处理短信中心号码、处理目标号码、拼接内容。每一步都做了奇偶判断和半字节交换。// 半字节交换辅助函数 void swap_nibbles(char *src, char *dst, int len) { for (int i 0; i len; i 2) { dst[i] src[i1]; dst[i1] src[i]; } }调用时先算好各段长度用sprintf拼接。注意缓冲区要留够一条中文短信的PDU串长度可能超过200字节别用太小的数组。提示短信中心号码因运营商和地区而异务必向运营商确认本地号码写错会导致短信发送失败但模块不一定报错。3. AT指令时序发送短信的完整交互流程3.1 模块初始化与网络注册检查模块上电后不能立刻发短信必须先确认它已经注册到网络。标准流程是先发AT确认串口通再发ATCPIN?确认SIM卡就绪然后ATCREG?查网络注册状态返回CREG: 0,1或CREG: 0,5才表示已注册。我遇到过模块返回CREG: 0,2的情况表示正在搜索网络这时候发短信必然失败。正确做法是循环查询直到注册成功再继续。加个超时机制比如30秒还没注册就报错到OLED。初始化阶段还要设置ATCMGF0进入PDU模式设置ATCSCSUCS2指定字符集。这两条指令的顺序无所谓但必须在发短信之前执行。3.2 发送指令的完整交互与等待逻辑发短信的核心指令是ATCMGSlength其中length是PDU串中用户数据长度那个字节的十进制值。发完这条指令后模块会返回一个提示符表示等待输入PDU内容。这时候把PDU串发过去最后跟一个十六进制0x1ACtrlZ表示结束。整个交互是分两步的中间必须等模块的响应。如果不等直接发PDU模块会把它当成新指令直接报错。我的做法是用状态机处理发送ATCMGS后进入等待状态收到再发PDU和0x1A然后等待CMGS:开头的成功响应。成功响应格式是CMGS: mrmr是消息参考号。如果失败会返回CMS ERROR: code。常见的错误码有500未知错误、331网络未注册、305模式错误。把这些错误码映射成可读信息显示到OLED调试时能省很多事。3.3 串口接收的缓冲与超时处理STM32的串口接收如果用中断逐字节处理很容易在模块返回长响应时丢数据。我的方案是用DMA加空闲中断一次性接收一帧数据到缓冲区再在主循环里解析。这样既不会丢字节也不会阻塞主流程。超时处理同样重要。模块有时候会因为信号问题迟迟不响应如果代码一直死等整个系统就卡住了。我给每个等待状态都设了超时比如等是5秒等CMGS是20秒。超时后重试一次再失败就报错。// 简化的状态机伪代码 switch(state) { case SEND_CMGS: uart_send(ATCMGS...\r\n); state WAIT_PROMPT; timeout 5000; break; case WAIT_PROMPT: if (rx_buffer_contains()) { uart_send(pdu_string); uart_send_byte(0x1A); state WAIT_RESULT; timeout 20000; } break; case WAIT_RESULT: if (rx_buffer_contains(CMGS:)) { oled_show(发送成功); state IDLE; } else if (rx_buffer_contains(CMS ERROR)) { oled_show(发送失败); state IDLE; } break; }3.4 常见AT指令错误码与排查方向错误码含义排查方向305模式错误检查是否设置了CMGF0331网络未注册查CREG状态检查天线500未知错误检查PDU格式重点看长度字节515短信中心号码无效核对CSCA设置21短信中心号码被拒联系运营商确认号码这张表是我调试过程中一点点攒出来的每次遇到新错误码就记下来。建议你也维护一份自己的错误码表比翻手册快得多。4. OLED状态显示的设计与刷新策略4.1 显示内容规划让操作者一眼看懂OLED只有0.96寸能显示的信息有限。我规划了三行第一行显示网络状态已注册或搜索中第二行显示最近一次发送结果发送成功或发送失败第三行显示发送计数。这样操作者不用连电脑就能判断设备是否正常工作。字体用的是6x8和8x16两种。状态文字用8x16计数用6x8。SSD1306的显存是128x64按页组织每页8行像素。写文字时要先算好坐标对应的页和列再写入字模数据。4.2 I2C刷新与主循环的配合OLED刷新不能太频繁否则占用大量CPU时间影响串口处理。我的做法是只在状态变化时刷新而不是每轮循环都刷。比如发送成功后把发送成功写进显存调用一次刷新函数之后就不管了直到下次状态变化。I2C的速率我设的是400kHz刷一屏大概几毫秒可以接受。如果发现刷新时串口丢数据说明I2C占用了太多时间可以考虑降到100kHz或者用硬件I2C加DMA。注意SSD1306的I2C地址通常是0x78写或0x7A但有些模块是0x3C或0x3D接线前先用扫描程序确认地址别想当然。4.3 中文字库的取舍显示汉字还是用英文OLED显示汉字需要字库0.96寸屏如果显示16x16的汉字一行只能放8个。我一开始想显示发送成功四个汉字后来发现字库占Flash空间而且取模麻烦最后改成英文OK和FAIL简洁明了。如果你的项目必须显示汉字建议用PCtoLCD2002取模阴码、逐列式、顺向生成的字模数组直接存到代码里。注意字库大小常用汉字几千个Flash小的MCU装不下可以只取项目需要的几十个字。4.4 按键消抖与状态机的结合按键我用的是最简单的方案检测到低电平后延时20ms再确认确认后等待按键释放释放后再延时20ms。这样能滤掉大部分抖动。更优雅的做法是用定时器每10ms扫描一次连续3次读到低电平才算按下。按键触发后不能立刻发短信要防止重复触发。我加了一个标志位发送过程中按键无效发送完成后才能再次触发。这个逻辑用状态机实现最清晰IDLE、SENDING、DONE三个状态只有IDLE时按键才有效。5. 调试过程中踩过的坑与解决思路5.1 短信偶尔发送失败电源问题的排查最开始测试时十次里有两三次发送失败错误码是500。我一度以为是PDU写错了反复检查编码逻辑没发现问题。后来用示波器看模块供电发现发送瞬间电压从3.8V掉到3.2V明显是电源带不动。换了更大容量的电容把LDO的输出电流从500mA换成1A的型号问题消失。这个坑很典型4G模块的峰值电流远超平均值电源设计必须按峰值来。如果你也遇到偶尔失败先查电源别急着改代码。5.2 串口丢数据DMA加空闲中断的救场用中断逐字节接收时模块返回的CMGS: 12这种响应经常只收到一半。原因是中断太频繁主循环处理不过来。改成DMA加空闲中断后一帧数据完整落到缓冲区问题解决。空闲中断的原理是串口在接收完一帧后总线会空闲一个字节的时间触发IDLE中断。在IDLE中断里读取DMA的剩余计数就能知道这一帧收了多少字节。这个方案在STM32上很成熟HAL库有现成的例程可以参考。5.3 PDU长度字节算错导致的静默失败有一次短信内容稍微长了一点发送就失败了但模块返回的错误码很模糊。查了半天发现是长度字节算错了我按字符数算实际应该按UCS2字节数算。一个汉字2字节10个汉字就是20字节长度字节写1420的十六进制不是0A。这个错误的隐蔽性在于短内容时字符数和字节数碰巧一致比如英文数字长内容才暴露。建议写个单元测试用不同长度的内容验证长度计算。5.4 OLED显示乱码I2C地址与初始化顺序OLED第一次点亮时全是乱码我以为是字模取错了。后来发现是I2C地址搞错了模块实际是0x3C我按0x78写的。改过来后正常显示。另一个坑是初始化顺序必须先发初始化命令序列再清屏最后写数据。如果顺序反了屏幕会显示随机内容。SSD1306的初始化序列网上有很多版本建议用模块卖家提供的兼容性最好。5.5 模块不响应AT指令波特率与流控的确认有一次换了块新模块发AT没反应。查手册发现默认波特率是9600我代码里写的115200。改成9600后正常。还有一次是模块开了硬件流控而我没接RTS/CTS导致数据发不出去。用ATIFC0,0关闭流控后解决。这两个问题提醒我拿到新模块第一件事是确认波特率和流控设置别假设它和上一块一样。6. 从能用到好用几个提升稳定性的经验6.1 增加发送重试与失败上报机制一次发送失败不代表网络有问题可能是瞬时信号波动。我在代码里加了重试逻辑失败后等3秒重发最多重试2次。三次都失败才判定为发送失败显示到OLED。重试时要注意不能简单重复发ATCMGS要先发一个ESC0x1B取消当前操作再重新开始。否则模块可能还停留在等待PDU的状态新指令会被当成PDU内容。6.2 用看门狗防止程序卡死嵌入式设备长期运行程序卡死是必须防范的。我开了独立看门狗IWDG主循环里定期喂狗。如果某个状态机卡住超过设定时间看门狗复位系统重新初始化。复位后OLED会显示重启中操作者能知道设备重启过。喂狗的位置有讲究不能放在中断里否则主循环卡死但中断还在跑看门狗永远不触发。要放在主循环的正常流程里确保主流程活着才喂狗。6.3 短信内容的灵活配置方案硬编码短信内容不灵活改一次要重新烧录。我的做法是把内容存在Flash的特定地址通过串口指令可以修改。这样现场调试时不用拆机重烧用串口助手发几条指令就能改内容。如果项目量大还可以考虑把号码和内容都存在外部EEPROM里MCU只负责读取和发送。这样不同设备可以用同一套固件靠EEPROM区分。6.4 低功耗场景下的模块休眠控制如果设备是电池供电模块不能一直开着。Air780E支持休眠用ATCFUN0进入最小功能模式需要发短信时再ATCFUN1唤醒。唤醒后要重新等待网络注册大概需要几秒到十几秒。我的做法是按键触发后才唤醒模块发送完成后延时一段时间再休眠。这样待机电流能从几十毫安降到几毫安电池寿命大幅延长。注意休眠前要确认没有未完成的AT交互否则唤醒后状态会乱。6.5 实际部署中的天线与SIM卡注意事项天线不能省。我试过用一根短线代替天线信号强度直接从20多降到个位数短信发送成功率暴跌。老老实实接上配套的4G天线放在远离金属外壳的位置。SIM卡方面物联网卡和普通手机卡都试过都能用。但物联网卡有时候需要先激活没激活的卡模块能识别但注册不上网络。另外SIM卡座要选质量好的劣质卡座接触不良会导致随机掉卡表现就是昨天还好好的今天发不出去了。这套方案我现在已经稳定运行了一段时间按键触发到OLED显示结果大概3到5秒成功率在信号良好的情况下接近100%。如果你也在做类似的项目建议先把PDU编码和AT时序这两块吃透剩下的都是工程细节。真正花时间的往往不是核心逻辑而是电源、串口、时序这些看起来不起眼的地方。
返回列表