STM32+Air780E按键触发中文短信发送与OLED状态显示实战

发布时间:2026/10/3 7:10:27
STM32+Air780E按键触发中文短信发送与OLED状态显示实战 1. 项目缘起与整体方案拆解按键一按短信就发到手机上而且内容还是中文——这个需求听起来简单但真动手做坑比想象的多。我最近用 STM32F103C8T6 搭配 Air780E 模组做了一套按键触发中文短信发送的小系统OLED 同步显示当前状态从硬件连线到 PDU 编码全部跑通。这篇就把整个项目的设计思路、关键细节和踩过的坑完整复盘一遍适合正在做物联网通信、远程告警、设备状态上报这类需求的朋友参考也适合刚接触 4G 模组 AT 指令的嵌入式新手。先说清楚这套系统到底在干什么。核心链路是STM32 检测按键动作通过串口向 Air780E 发送 AT 指令Air780E 完成 4G 网络注册后把一段中文文本以短信形式发到指定手机号同时 OLED 屏实时显示“正在发送”“发送成功”“发送失败”等状态。整个系统不依赖手机 App不需要云端中转按键一按直接走运营商短信通道可靠性高部署成本低。为什么选 Air780E 而不是更常见的 SIM800C 或者 EC20这是第一个要解释的选型问题。SIM800C 只支持 2G而目前很多地区 2G 基站已经退网或正在退网短信通道随时可能不通。EC20 是 Cat.4 模组性能强但价格高、功耗大对于只需要发短信这种低速率场景属于杀鸡用牛刀。Air780E 是 Cat.1 模组支持 4G LTE功耗和成本都控制得不错AT 指令集兼容性好最关键的是它对中文短信的 PDU 模式支持稳定这一点在实际调试中省了很多事。STM32 这边选 F103C8T6 是经典中的经典资源足够跑这套逻辑两个 USART 一个接模组一个留给调试打印I2C 接 OLEDGPIO 接按键绰绰有余。OLED 选 0.96 寸 I2C 接口的 SSD1306驱动简单显示状态信息足够用。整体方案定下来之后真正的工作量集中在三块串口 AT 指令的稳定交互、中文短信的 PDU 编码、OLED 状态机的设计。下面逐块拆开讲。2. 硬件连接与关键细节2.1 器件清单与接线方案先把物料列清楚避免做到一半发现少东西。器件型号数量备注主控STM32F103C8T6 最小系统板1蓝色药丸板即可4G 模组Air780E 开发板1带 SIM 卡座和天线接口显示屏0.96 寸 OLED SSD13061I2C 接口4 针按键轻触按键1配合 10k 上拉电源5V/2A 适配器1模组峰值电流较大SIM 卡已开通短信功能的卡1注意是否欠费接线部分STM32 和 Air780E 之间走串口。我用的是 USART2PA2 接模组的 RXPA3 接模组的 TX波特率 115200。OLED 走 I2C1PB6 是 SCLPB7 是 SDA。按键接 PA0配置为上拉输入按下时拉低。这里有个容易被忽略的点Air780E 的串口电平是 1.8V 还是 3.3V。Air780E 模组本身的 IO 电平是 1.8V但市面上大多数开发板已经做了电平转换对外引出的是 3.3V 电平可以直接和 STM32 对接。如果你买的是裸模组那就必须加电平转换电路否则要么通信不上要么长期运行损坏 IO。我手上这块开发板是 3.3V 电平实测直连没问题。2.2 电源处理不能省Air780E 在发射瞬间的峰值电流可以到 2A 左右这个数字很多人不当回事结果就是模组频繁掉网、重启甚至发不出短信。我一开始用 STM32 板子上的 3.3V 给模组供电现象就是模组能注册上网络但一发短信就掉线。后来单独用 5V/2A 适配器给模组供电问题立刻消失。正确的做法是模组供电和主控供电分开或者至少保证模组那一路有足够的电流余量和足够大的滤波电容。我在模组电源脚旁边并了一个 470uF 的电解电容加一个 0.1uF 的陶瓷电容前者应对低频大电流波动后者滤高频噪声。这个组合实测下来很稳。注意千万不要用 STM32 板载的 AMS1117 给 Air780E 供电1117 最大输出 1A 都不到带不动模组的发射峰值。2.3 OLED 的 I2C 地址确认SSD1306 的 I2C 地址通常是 0x78 或 0x7A取决于模块上电阻的焊接方式。我手上这块是 0x78。如果你发现 OLED 不亮第一件事就是用 I2C 扫描程序确认地址。STM32 的 HAL 库有现成的 I2C 扫描例程改几行就能用。另外0.96 寸 OLED 对 I2C 上拉电阻有要求一般模块自带上拉但如果你的走线比较长可能需要额外加 4.7k 上拉电阻。我这次走线短模块自带的够用。3. AT 指令交互与中文短信 PDU 编码3.1 Air780E 的短信发送流程Air780E 发短信有两种模式Text 模式和 PDU 模式。Text 模式发英文没问题但发中文会乱码或者直接失败因为 Text 模式对字符集的支持有限。发中文必须用 PDU 模式这是整个项目最核心的技术点。PDU 模式的全称是 Protocol Data Unit它把短信内容按照 3GPP TS 23.040 规范编码成一串十六进制字符串再通过 AT 指令发出去。中文在 PDU 里用的是 UCS2 编码每个汉字占 2 个字节也就是 4 个十六进制字符。完整的发送流程是这样的发送AT确认模组响应正常发送ATCPIN?确认 SIM 卡就绪发送ATCSQ检查信号质量发送ATCREG?确认网络注册状态发送ATCMGF0设置为 PDU 模式发送ATCMGSlength其中 length 是 PDU 串的长度不含 SMSC 部分等待模组返回提示符发送 PDU 字符串以0x1ACtrlZ结尾等待CMGS:响应确认发送成功每一步都要等模组返回预期响应才能进行下一步不能一股脑全发出去。我一开始图省事把指令连着发结果模组直接不响应了。后来改成状态机逐条发送、逐条等待稳定性大幅提升。3.2 PDU 编码的完整计算过程这是最容易出错的地方我把编码过程完整拆一遍。假设要发送的中文内容是“测试”目标号码是 13800138000。第一步确定 SMSC 号码。大多数情况下 SMSC 用默认的就行PDU 串里 SMSC 部分填00表示使用模组默认的短信中心号码。所以 PDU 串开头是00。第二步构造短信头。短信头包含 TP-MTI、TP-MMS、TP-RP、TP-UDHI、TP-SRR、TP-VPF、TP-RP 等标志位。对于普通发送短信第一个字节通常是11表示 TP-MTI01SMS-SUBMIT、TP-VPF10相对格式、TP-UDHI0无用户数据头。第二个字节是 TP-MR 消息参考号随便填00。第三步目标号码编码。号码 13800138000 先加上国际区号变成 8613800138000然后两两交换位置奇数位补 F。具体过程原号码86 13 80 01 38 00 00交换后68 31 08 10 83 00 00号码长度13 位含 86十六进制是 0D所以号码部分编码为0D68310810830000。第四步PID、DCS、VP 字段。PID 填00DCS 填08表示 UCS2 编码VP 填AA相对格式的有效期AA 表示 4 天。第五步用户数据编码。“测试”两个字的 Unicode 码点是 U6D4B 和 U8BD5转成十六进制就是6D4B8BD5。UCS2 编码下用户数据长度是字节数即 4 字节十六进制04。第六步拼接完整 PDU 串。00 11 00 0D 68310810830000 00 08 AA 04 6D4B8BD5去掉空格就是0011000D683108108300000008AA046D4B8BD5这个串的长度是 34 个字符也就是 17 个字节。发送ATCMGS17然后等提示符再发 PDU 串加 CtrlZ。3.3 代码里怎么实现 PDU 编码在 STM32 上做 PDU 编码核心是两个函数一个把手机号转成 PDU 格式一个把中文转成 UCS2 十六进制串。手机号转换的逻辑是去掉开头的 0前面加 86然后两两交换奇数位补 F。中文转换需要一张 Unicode 映射表或者直接用 GB2312 转 Unicode 的算法。我为了省事直接把要发送的中文内容预先转好十六进制存在数组里因为这套系统的短信内容是固定的不需要动态转换。如果你需要动态发送任意中文那就得在 STM32 里做 GB2312 到 Unicode 的转换这需要一张码表会占用不少 Flash。F103C8T6 只有 64KB Flash码表大概占 20KB 左右放得下但比较紧张。我的建议是如果内容固定就预编码如果内容动态考虑换 Flash 更大的型号比如 F103RCT6。// 预编码的中文短信 PDU 串示例 const char pdu_test[] 0011000D683108108300000008AA046D4B8BD5;发送时把ATCMGS17\r\n通过串口发出去等收到后再把 PDU 串和\x1A发出去。4. 软件架构与状态机设计4.1 主循环与状态划分整个软件用状态机来组织主循环里不断轮询当前状态根据状态决定下一步动作。我把状态分成这么几个STATE_IDLE空闲等待按键STATE_CHECK_MODULE检查模组是否在线STATE_CHECK_SIM检查 SIM 卡STATE_CHECK_NET检查网络注册STATE_SET_PDU设置 PDU 模式STATE_SEND_CMGS发送 CMGS 指令STATE_WAIT_PROMPT等待提示符STATE_SEND_PDU发送 PDU 数据STATE_WAIT_RESULT等待发送结果STATE_SUCCESS发送成功STATE_FAIL发送失败每个状态都有超时机制比如等待提示符超过 5 秒就判定失败回到空闲状态。这个超时非常重要否则一旦模组不响应程序就卡死了。4.2 串口接收的中断与缓冲串口接收我用的是中断加环形缓冲区的方式。每收到一个字节就存进缓冲区主循环里从缓冲区取数据做解析。这样不会因为主循环处理慢而丢数据。环形缓冲区的实现很经典两个指针一个读一个写写指针由中断更新读指针由主循环更新。缓冲区大小我开了 512 字节足够容纳模组的响应。解析响应的时候我用的是字符串匹配。比如等待OK就找缓冲区里有没有OK等待就找有没有。这里要注意模组的响应可能分多次到达所以匹配之前要确保缓冲区里已经收到了完整响应。我的做法是每次匹配前先等 100ms让数据收完。4.3 OLED 显示内容设计OLED 上我显示四行信息第一行当前状态比如Sending...、Success、Failed第二行信号质量比如CSQ: 20第三行网络注册状态比如Reg: OK第四行发送计数比如Count: 3状态更新用了一个简单的标志位状态变化时才刷新 OLED避免频繁刷屏导致 I2C 占用过多时间。SSD1306 全屏刷新一次大概需要 20ms 左右如果每轮循环都刷会拖慢整个系统的响应速度。显示中文的话需要取模软件生成字库。我用的是 PCtoLCD2002取模方式选“阴码、逐列式、顺向”生成的字库数组直接放到代码里。OLED 显示中文的原理就是把字库数据按页写入 GDDRAM每个汉字 16x16 像素占 32 个字节。5. 实操过程与关键环节记录5.1 模组上电与网络注册模组上电后先等 3 秒左右让它完成初始化然后发AT测试。如果返回OK说明串口通信正常。接着发ATCPIN?返回CPIN: READY说明 SIM 卡识别正常。再发ATCSQ返回的第二个数字是信号质量0-31 之间越大越好99 表示无信号。最后发ATCREG?返回CREG: 0,1或CREG: 0,5都表示已注册。这一步我遇到过一个坑模组刚上电时发AT没反应等几秒再发就好了。后来查资料知道 Air780E 上电后需要一段时间加载固件和搜网这段时间串口是不响应的。所以代码里上电后先延时 3-5 秒再开始发指令。5.2 发送 PDU 短信的完整时序按键按下后系统进入发送流程。我实测的完整时序是这样的发ATCMGF0等OK耗时约 50ms发ATCMGS17等耗时约 100ms发 PDU 串 \x1A等CMGS: xx和OK耗时约 3-5 秒收到CMGS: 12表示发送成功12 是消息参考号整个流程从按键到发送完成大概 5 秒左右其中大部分时间花在等待网络侧确认。OLED 上会依次显示Sending...然后变成Success。5.3 按键消抖与触发逻辑按键用外部中断或者轮询都行我用的是轮询加软件消抖。每 10ms 检测一次按键电平连续检测到 3 次低电平才认为是有效按下。这样能有效滤除机械抖动。另外加了一个发送锁发送过程中再按按键不响应避免重复发送。发送完成后解锁可以再次触发。6. 常见问题与排查技巧实录6.1 短信发不出去怎么排查这是最常见的问题我整理了一个排查顺序表现象可能原因排查方法发 AT 无响应串口接线错误或波特率不对检查 TX/RX 是否交叉确认波特率 115200返回 ERROR指令格式错误检查指令是否带 \r\n参数是否正确CMGS 后无响应PDU 串格式错误用在线 PDU 编码工具核对返回 CMS ERROR: 500网络未注册或信号差检查 CSQ 和 CREG返回 CMS ERROR: 302SIM 卡欠费或未开通短信换卡测试发送成功但收不到号码格式错误确认号码是否加了 866.2 PDU 编码错误的典型表现PDU 串错一个字符短信就发不出去而且模组返回的错误码往往很模糊。我踩过的坑包括号码长度算错、UCS2 编码时字节序搞反、PDU 串长度和 CMGS 参数不一致。最稳妥的办法是先用在线 PDU 编码工具生成一个正确的串和你的代码输出对比。如果一致说明编码逻辑没问题如果不一致逐段排查。提示PDU 串里的长度字段是十六进制比如 17 个字节要写成11不是17。这个很容易搞混。6.3 OLED 不显示或显示乱码OLED 不亮先查 I2C 地址再查供电。显示乱码通常是字库取模方式不对或者页地址计算错误。SSD1306 的 GDDRAM 是 8 页每页 128 列写入时要先设置页地址和列地址。如果页地址算错字就会显示在错误的位置。我遇到过一次 OLED 显示一半正常一半乱码查了半天发现是 I2C 速率太高100kHz 没问题400kHz 就出错。后来降到 100kHz 就稳定了。所以如果你的 OLED 走线比较长或者上拉电阻偏大建议先用 100kHz 测试。6.4 模组频繁掉网前面提过电源是主要原因。除此之外天线也很关键。Air780E 的天线接口是 IPEX 座一定要接匹配的 4G 天线不要用 2G 天线凑合。天线摆放位置也要注意尽量远离金属和电源线。如果电源和天线都没问题检查一下模组的固件版本。有些早期固件在特定网络环境下会有兼容性问题升级到最新固件通常能解决。7. 一些实操心得与扩展思路这套系统跑通之后我又做了几个小优化。一个是加了发送失败自动重试最多重试 3 次每次间隔 10 秒。另一个是把发送记录存到 STM32 的 Flash 里记录发送时间和结果方便后续查询。扩展方面这个框架可以很容易地改成其他通信方式。比如把短信换成 MQTT 上报只需要把 AT 指令部分换成 Air780E 的 MQTT 指令集上层状态机基本不用动。也可以加一个 DHT11 温湿度传感器按键触发时把当前温湿度一起发出去做成一个简单的环境告警终端。代码结构上我把模组驱动、PDU 编码、OLED 驱动分成了独立的文件主循环只负责状态调度。这样后续换模组或者换屏幕只需要改对应的驱动文件主逻辑不受影响。这个分层思路在嵌入式项目里很实用值得一开始就做好。最后说一个调试技巧在开发阶段把 STM32 的调试串口打开把每一步的 AT 指令和模组响应都打印出来。这样出问题的时候一眼就能看出卡在哪一步。等调试稳定了再把打印关掉或者精简。我整个项目调试过程中这个打印帮了大忙强烈建议你也加上。