ARTICLE DETAIL

资讯详情

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

STM32+Air780E+OLED:按键发送中文短信与PDU编码实战

STM32+Air780E+OLED:按键发送中文短信与PDU编码实战 1. 项目缘起与整体方案拆解按键发短信这件事听起来像是功能机时代的老古董但在工业现场、远程告警、老人看护、农业大棚这些场景里它依然是最朴素也最可靠的一种通信手段。我这次要做的就是拿一块 STM32 最小系统板配上一片 Air780E 的 4G Cat.1 模组再加一块 0.96 寸的 OLED 屏实现“按一下键发一条中文短信屏幕上同步显示发送状态”的完整闭环。核心关键词就四个STM32、Air780E、OLED、AT 指令而真正让大多数人卡住的是中文短信背后的 PDU 编码。先说清楚这个项目能干什么。你手头有一块 STM32F103C8T6 或者类似的板子接上 Air780E 模组再挂一块 I2C 的 OLED按下按键之后模组把一条预设好的中文短信发到指定手机号OLED 上实时显示“正在发送”“发送成功”或者“发送失败”。它解决的是“设备侧主动告警”的问题——不需要联网服务器不需要云平台一条短信直达。适合谁看适合已经会点 STM32 基础、能点灯能串口打印、想往物联网通信方向迈一步的朋友也适合做毕设、做工业报警器、做远程抄表的工程师拿来当参考。为什么选 Air780E 而不是别的模组这是有讲究的。市面上常见的 4G 模组里Air780E 是 Cat.1 制式功耗和成本都比 Cat.4 低而且它原生支持 AT 指令串口透传逻辑简单中文短信走 PDU 模式也很成熟。相比之下某些模组虽然便宜但 AT 指令集不完整发中文短信时容易踩坑。OLED 选 0.96 寸 I2C 版本是因为它只要两根线不占用太多 IOSTM32F103C8T6 这种引脚紧张的芯片最合适。整个方案的核心思路就是STM32 当大脑负责按键扫描、状态机调度、OLED 刷新Air780E 当嘴巴负责把 PDU 编码后的短信发出去OLED 当脸面负责把过程可视化。这里有个关键决策点短信内容怎么组织直接发 ASCII 英文最简单但标题要求中文就必须用 PDU 模式。PDU 是“Protocol Data Unit”的缩写中文短信在 GSM 协议里不能直接以明文发送必须经过 UCS2 编码再拼成 PDU 串。很多人第一次接触 PDU 会觉得头大其实拆开看就是几段固定格式加一串十六进制。我后面会详细讲怎么算。另一个决策点是按键触发方式。我试过轮询和外部中断两种最后选了轮询加软件消抖。原因很简单外部中断在按键抖动时容易误触发而轮询放在主循环里配合 20ms 的消抖延时稳定得多。OLED 刷新也不能放在中断里I2C 时序对时间敏感中断里刷屏容易导致通信失败。整体硬件连接是这样的STM32 的 USART2 接 Air780E 的串口波特率 115200I2C1 接 OLED地址通常是 0x78按键接在 PA0下拉输入按下为高电平。电源部分要注意Air780E 在发射瞬间电流能冲到 2A所以供电必须给足最好单独用一路 3.8V 的 DC-DC不要直接从 STM32 的 3.3V LDO 上取电否则一发短信就复位。2. 核心细节解析与实操要点2.1 Air780E 的 AT 指令交互逻辑Air780E 上电之后串口会输出一堆启动日志这时候不要急着发指令等它稳定下来先发一个AT看它回不回OK。这是最基本的握手。如果连AT都不回先查波特率Air780E 默认是 115200但有些固件版本是 9600我遇到过两次折腾了半天才发现是波特率不对。握手成功之后要依次配置几个关键参数。第一ATCMGF0把短信模式设成 PDU 模式。注意ATCMGF1是文本模式只能发英文发中文会乱码或者直接失败。第二ATCSCSUCS2设置字符集为 UCS2这一步很多人会漏掉结果 PDU 串拼出来是对的但模组解析出错。第三ATCSMP17,167,0,8设置短信参数其中最后一个参数 8 表示 UCS2 编码。这个指令里的 17 是 FO 参数167 是 VP 有效期0 是 PID8 是 DCS。DCS 设为 8 就是告诉模组“我要用 UCS2 发中文”。发短信的指令是ATCMGSlength这里的 length 是 PDU 串去掉开头两个字符后的长度也就是 TPDU 的长度不是整个 PDU 串的长度。这个细节坑了无数人。举个例子如果你的 PDU 串是0011000D916831...那么 length 就是总长度除以 2 再减去 1。因为 PDU 串是十六进制字符串两个字符代表一个字节而ATCMGS要的是字节数且不包含 SMSC 地址部分。发完ATCMGSlength之后模组会回一个提示符这时候才能把 PDU 串发进去最后以CtrlZ0x1A结尾。注意CtrlZ是十六进制的 0x1A不是字符串“CtrlZ”。我在代码里用USART_SendData(USART2, 0x1A)直接发这个字节。如果发的是CtrlC0x1B就是取消发送。2.2 中文短信的 PDU 编码计算PDU 编码是這個项目里最硬核的部分。我把它拆成四段来看SMSC 地址段、TPDU 首字节、目标号码段、短信内容段。SMSC 地址段通常写00表示使用模组默认的短信中心号码。如果你要指定就写07加号码长度加91加号码。我一般直接用00省事。TPDU 首字节是11或0111表示有状态报告01表示没有。我选11因为 OLED 上要显示发送状态有状态报告更靠谱。目标号码段稍微复杂。假设手机号是13800138000先算长度11 位十六进制是0B。然后加91表示国际格式。接着把号码两两颠倒13 80 01 38 00 00变成31 08 10 83 00 F0。注意最后如果奇数位补F。所以号码段就是0B91加3108108300F0。短信内容段是 UCS2 编码。每个中文字符对应一个 Unicode 码点比如“你”是4F60“好”是597D。把每个字符的 Unicode 码点转成十六进制字符串拼起来就是内容段。内容长度是字符数乘以 2再转成十六进制。比如“你好”两个字长度是 4 个字节十六进制04。把这些拼起来完整的 PDU 串就是0011000B913108108300F00008044F60597D。等等这里有个00和08是什么00是 TP-PID08是 TP-DCS表示 UCS2。这两个字节不能漏。所以完整 PDU 是0011000B913108108300F00008044F60597D。长度计算去掉开头的00剩下11000B913108108300F00008044F60597D数一下字符数是 38 个字符除以 2 得 19 字节。所以ATCMGS19。我在代码里写了一个pdu_encode函数输入手机号和中文内容输出 PDU 串和长度。这个函数我调了整整一个下午才跑通主要坑在号码颠倒和长度计算上。2.3 OLED 状态显示的设计OLED 用 I2C 驱动我选的是 SSD1306 芯片0.96 寸128x64 分辨率。驱动库我用的是自己精简过的 HAL 库版本没有用现成的 u8g2因为 u8g2 占 Flash 太大STM32F103C8T6 只有 64KB Flash塞进去有点紧张。显示内容分三行第一行显示“短信发送”第二行显示当前状态比如“正在发送...”“发送成功”“发送失败”第三行显示重试次数或者错误码。状态切换的时候我只刷新变化的那一行不全屏刷新这样 I2C 占用时间短不会阻塞主循环。OLED 初始化的时候要注意0.9 寸和 0.96 寸的 I2C 地址可能不一样0.96 寸通常是 0x780.9 寸有些是 0x7A。我手里这块 0.96 寸的地址是 0x78。如果你买的是 0.9 寸先扫 I2C 地址确认一下。另外OLED 的供电最好是 3.3V有些模块标称 5V 兼容但长期跑 5V 容易烧。显示汉字需要取模。我用的是 PCtoLCD2002 软件取模方式选“阴码逐列式顺向”每个汉字 16x16 像素占 32 个字节。把取模数据放到一个数组里显示的时候按列写入。这里有个小技巧把常用的状态文字提前取好模比如“正在发送”“发送成功”“发送失败”存成常量数组用的时候直接索引不用动态取模。3. 实操过程与核心环节实现3.1 硬件连接与供电检查先把硬件接起来。STM32F103C8T6 的 PA2 接 Air780E 的 RXDPA3 接 TXDGND 共地。OLED 的 SCL 接 PB6SDA 接 PB7VCC 接 3.3VGND 共地。按键一端接 PA0另一端接 3.3VPA0 配置成下拉输入。Air780E 的 VCC 接 3.8V这个 3.8V 我用的是单独的 DC-DC 模块输入 5V输出 3.8V最大电流 3A。供电检查这一步不能省。我第一版直接拿 STM32 板子上的 3.3V 给 Air780E 供电结果一按按键发短信STM32 就复位OLED 黑屏。后来用示波器看Air780E 发射瞬间把 3.3V 拉到了 2.1VSTM32 的 BOR 复位了。换成独立 3.8V 供电之后问题消失。所以记住Air780E 的供电必须独立且电流能力不低于 2A。串口线也要注意Air780E 的串口是 1.8V 电平还是 3.3V 电平我这款是 3.3V 电平可以直接接 STM32。如果你买的是 1.8V 版本中间要加电平转换。不确定的话先看模组丝印或者问卖家。3.2 STM32 端代码框架代码分四个模块串口驱动、OLED 驱动、按键扫描、PDU 编码。串口驱动用 HAL 库的HAL_UART_Receive_IT开中断接收把模组返回的数据存到一个环形缓冲区里。主循环里解析缓冲区找OK、ERROR、CMGS:这些关键字。OLED 驱动我写了三个函数OLED_Init、OLED_ShowString、OLED_Refresh。OLED_ShowString支持 ASCII 和汉字汉字用索引查表。OLED_Refresh把显存刷到屏幕我用的页地址模式每次刷一页8 页刷完。按键扫描放在SysTick中断里每 10ms 扫一次连续 3 次读到高电平才确认按下。确认按下后置一个标志位key_pressed主循环检测到这个标志位就启动发送流程。发送流程是一个状态机IDLE-SEND_AT-WAIT_OK-SEND_CMGF-WAIT_OK-SEND_CMGS-WAIT_PROMPT-SEND_PDU-WAIT_RESULT-DONE。每个状态等模组回复超时 3 秒就跳到ERROR状态OLED 显示“发送失败”然后重试一次。3.3 PDU 编码函数的实现细节pdu_encode函数的输入是手机号字符串和中文内容字符串输出是 PDU 串和长度。我一步步写第一步处理手机号。把手机号的每个字符转成数字然后两两交换。如果长度是奇数最后补F。前面加0B91其中0B是长度91是国际格式。第二步处理内容。把每个中文字符的 Unicode 码点取出来转成 4 位十六进制字符串拼起来。内容长度是字符数乘以 2转成 2 位十六进制。第三步拼 TPDU。1100 号码段 0008 内容长度 内容段。第四步拼 SMSC。00 TPDU。第五步算长度。PDU 串总字符数除以 2再减去 1就是ATCMGS的参数。我举个例子手机号13800138000内容“你好”。手机号处理13 80 01 38 00 00- 交换 -31 08 10 83 00 F0前面加0B91得到0B913108108300F0。内容处理“你”是4F60“好”是597D拼起来4F60597D长度04。TPDU 是11000B913108108300F00008044F60597D。完整 PDU 是0011000B913108108300F00008044F60597D。长度去掉开头00剩下 38 个字符除以 2 得 19。所以发ATCMGS19。这个函数我封装成void pdu_encode(char *phone, char *content, char *pdu_out, int *len_out)在 STM32 上跑没问题因为字符串不长栈空间够用。3.4 发送流程的代码实现主循环里检测到key_pressed标志后先清标志OLED 显示“正在发送...”然后进入状态机。状态机用switch-case实现每个 case 里发对应的 AT 指令然后等回复。发AT的时候我直接调HAL_UART_Transmit发字符串AT\r\n然后等 500ms检查缓冲区里有没有OK。如果有进入下一步如果没有重发一次再没有就报错。发ATCMGF0和ATCSCSUCS2类似都是发指令等OK。发ATCMGS19之后等模组回。这个是提示符不是OK。我在缓冲区里找字符找到之后发 PDU 串最后发0x1A。发完 PDU 之后模组会回CMGS: mr和OK表示发送成功。如果回ERROR或者CMS ERROR就是失败。我在缓冲区里找CMGS:和OK找到就显示“发送成功”否则显示“发送失败”。这里有个细节模组返回的数据可能分多次到达所以缓冲区要够大我设了 256 字节。解析的时候用strstr找关键字不要用strcmp因为返回的数据前后可能有其他字符。3.5 OLED 显示刷新与状态同步OLED 刷新我放在主循环里每 100ms 刷一次。状态变量sms_state有四个值STATE_IDLE、STATE_SENDING、STATE_SUCCESS、STATE_FAIL。根据这个变量决定显示什么文字。显示“正在发送...”的时候我加了一个小动画每 500ms 切换一次“.”的数量从“.”到“..”到“...”这样用户知道系统没死机。这个动画用SysTick计数实现不占用额外定时器。显示“发送成功”之后保持 3 秒然后自动回到STATE_IDLEOLED 显示“按按键发送短信”。显示“发送失败”之后保持 5 秒然后回到STATE_IDLE同时允许用户再按一次重发。OLED 的显存我用了 1024 字节的数组OLED_Refresh的时候按页写入。I2C 速率设成 400kHz刷一屏大概 20ms不影响主循环。4. 常见问题与排查技巧实录4.1 模组不回复 AT 指令这是最常见的问题。先查波特率Air780E 默认 115200但有些固件是 9600。如果波特率对查串口线有没有接反TX 接 RXRX 接 TX。如果线也对查供电用万用表量模组 VCC 是不是 3.8V发射瞬间会不会掉。我遇到过供电不足导致模组反复重启串口一直输出启动日志根本不进 AT 模式。换独立供电之后解决。还有一个坑Air780E 上电后需要几秒钟初始化如果你上电就发AT它可能还没准备好。我在代码里加了 5 秒延时等模组稳定后再发AT。4.2 中文短信发送失败如果模组回ERROR先检查ATCMGF0和ATCSCSUCS2有没有设对。然后检查 PDU 串的长度计算ATCMGS的参数是不是 TPDU 长度。我踩过的坑是长度算成了整个 PDU 串的长度结果模组一直回ERROR。后来用串口助手手动发对比长度才发现问题。另一个坑是CtrlZ发成了字符串。在代码里HAL_UART_Transmit发的是字节数组0x1A是一个字节不是两个字符1和A。如果你用printf发0x1A那就错了。还有短信中心号码如果设错也会失败。我一般用00让模组用默认的如果默认的不对就手动设ATCSCA8613800100500这个号码各地不同问运营商。4.3 OLED 不显示或显示乱码OLED 不显示先查 I2C 地址。用HAL_I2C_IsDeviceReady扫一下地址 0x78 或 0x7A。如果地址对但不显示查初始化序列SSD1306 的初始化命令不能少特别是0xAE关显示、0xD5时钟分频、0xA8复用率、0x8D电荷泵这几个。电荷泵不打开屏幕不亮。显示乱码通常是取模方式不对。PCtoLCD2002 里选“阴码逐列式顺向”如果选成“阳码”或者“逆向”显示就是乱的。另外汉字取模是 16x16ASCII 是 8x16显示的时候坐标要算对不然会重叠。4.4 按键误触发按键误触发一般是消抖没做好。我在SysTick里每 10ms 扫一次连续 3 次读到高电平才确认这样 30ms 内的抖动都滤掉了。如果还误触发加一个 100ms 的冷却时间发完短信之后 100ms 内不响应按键。还有一种情况是按键接的上拉或下拉不对。我用的下拉输入按键接 3.3V按下为高。如果你接 GND就要配上拉输入按下为低。配置错了按键一直触发或者一直不触发。4.5 常见问题速查表问题现象可能原因排查方法解决方案模组不回 AT波特率不对换 9600 试改代码波特率模组不回 AT供电不足量 VCC 电压独立 3.8V 供电发中文失败PDU 长度错手动算长度用 TPDU 长度发中文失败字符集不对查 CSCS设 UCS2OLED 不亮电荷泵没开查初始化加 0x8D 命令OLED 乱码取模方式错查取模设置阴码逐列顺向按键误触发消抖不够加延时30ms 消抖发送后复位电源跌落示波器看独立供电4.6 实操心得与避坑技巧第一个心得PDU 编码不要手算写个函数在 PC 上先验证。我用 Python 写了一个小脚本输入手机号和内容输出 PDU 串和长度跟 STM32 上的函数对比确保一致。这样调试的时候心里有底。第二个心得串口调试助手是你的好朋友。先用串口助手手动发 AT 指令确认模组能发中文短信再把流程搬到 STM32 上。这样能把硬件问题和代码问题分开。第三个心得OLED 显示状态的时候不要显示太多文字三行足够。第一行标题第二行状态第三行错误码。错误码用数字比如E01表示模组无响应E02表示 PDU 错误E03表示发送失败。这样排查的时候一目了然。第四个心得Air780E 的固件版本不同AT 指令集可能有差异。我遇到过一款固件不支持ATCSMP的第四个参数后来换成ATCMGF0加ATCSCSUCS2就够了。所以如果指令报错先查固件手册。第五个心得STM32 的串口接收中断里不要做太多事只把数据存进缓冲区解析放在主循环。中断里做strstr会阻塞太久导致丢数据。第六个心得如果项目要长期运行加一个看门狗。我在主循环里喂IWDG防止模组死机导致程序卡死。模组死机的时候STM32 可以复位模组通过控制模组的 RESET 引脚。这个项目我从画板子到调通前前后后花了大概一周时间其中 PDU 编码占了一半。现在回头看最难的不是技术本身而是把每个细节都抠清楚。短信发送成功那一刻OLED 上跳出“发送成功”四个字那种成就感还是挺实在的。如果你也在做类似的东西希望这些经验能帮你少走点弯路。
返回列表