ARTICLE DETAIL

资讯详情

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

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

STM32+Air780E中文短信发送实战:PDU编码与OLED显示 把STM32和4G模组Air780E拼在一起做一个按键发送中文短信、OLED实时显示状态的小装置听起来难度不大真正写代码的时候才发现最磨人的不是STM32本身而是中文短信那套PDU编码以及按键、串口、OLED三者之间的时序配合。我最近刚把一个这样的项目完整调通从硬件接线、PDU编码、代码框架到调试踩坑记录一篇完整的经验帖给正在做物联网短信告警、毕业设计或者只是想玩一下Air780E的开发者做个参考。这个项目的定位很清楚STM32做主控负责按键检测、OLED显示和AT指令调度Air780E只负责4G入网和短信收发。整套跑通以后按一下按键几秒内对方手机就能收到一条中文短信OLED屏幕上会依次显示待机、发送中、发送成功或者失败原因。对嵌入式入门的同学来说它几乎覆盖了GPIO中断、定时器消抖、串口通信、I2C驱动、字符编码这几大块基础技能既有业务闭环又有典型的技术难点拿来练手非常合适。1. 项目拆解与整体方案1.1 五个硬件角色各干什么先把标题里的五个关键词拆开看。STM32是整个系统的大脑所有业务逻辑都跑在它上面读取按键状态、刷新OLED、生成短信内容、调度串口发送。Air780E是合宙的一款4G Cat.1模组负责网络接入和短信通道它本身不参与业务决策只执行AT指令。按键是输入设备它触发“发送短信”这个动作。中文短信是这个项目真正有技术含量的部分英文短信可以直接用7bit编码怼上去中文必须转成UCS2编码并按PDU格式组包。OLED是反馈设备让用户看到当前处于什么状态没有它按键按下之后你完全不知道系统是在工作还是卡死了。这五个部分其实对应了嵌入式开发的几个基本功GPIO输入输出、外部中断、定时器、UART串口通信、I2C驱动、字符编码转换。所以这个项目虽然看起来只是“发条短信”实际上你能学到的东西相当多。尤其是Air780E这一类4G模组它的AT指令交互逻辑和老的GSM模块基本一致以后换成其他模组也能很快上手。1.2 为什么选AT指令方案而不是LuatOS单芯片Air780E本身支持两种玩法。一种是刷LuatOS固件直接用Lua脚本在模组内部把按键、OLED、短信全部写完这时候STM32基本可以退休整个项目一颗Air780E就够。另一种是刷AT固件Air780E老老实实当一个网络外设所有控制逻辑由外部MCU通过串口发AT指令完成。这个项目标题既然是“STM32Air780E”那天然走的是AT方案STM32必须存在。从学习角度讲AT方案更有价值因为它把“主控MCU”和“通信模组”的边界划得很清楚你对串口协议、命令超时、状态机这些概念会有非常直观的体会。以后你想把这个4G模块换成NB-IoT模组、WiFi模组只要AT指令相似主控代码改动很小。LuatOS方案在量产降成本时确实香但如果你想练STM32就别省这一步。对比项STM32 Air780E(AT)Air780E Luatos单芯片开发语言C / HAL库Lua脚本成本多一颗MCU略高单芯片最低学习价值GPIO/中断/串口/I2C全覆盖偏向脚本业务逻辑模组可替换性高AT指令通用低绑死LuatOS生态适合场景学习、毕设、已有STM32平台产品量产、快速原型如果你选了AT方案还有一件事要确认Air780E出厂可能是LuatOS固件也可能已经是AT固件拿到手先发AT测一下如果回复OK就是AT固件不回就先去合宙的文档中心把AT固件刷进去。我用的是官方下载的AT固件版本后续所有指令都基于这个环境。1.3 系统数据流与状态划分画一条数据流出来理解整个项目。开机后STM32初始化OLED、串口、GPIO然后循环读取模组信号强度。用户按下按键按键引脚产生下降沿触发外部中断中断里只做一件事设置一个KEY_FLAG标志位。主循环检测到标志后把系统状态切到“发送中”OLED显示SENDING。接着STM32通过UART2向Air780E发一串AT指令其中最重要的一条是ATCMGS长度模组收到后返回提示符STM32立刻把PDU十六进制字符串和结束字节0x1A发过去。模组入网、短信中心处理都需要时间这个等待过程不能干等所以要靠串口接收中断和超时机制来判断返回结果。收到CMGS: 编号说明成功收到ERROR说明失败OLED对应更新SMS OK或者错误码系统回到待机状态。这里最忌讳的做法是在按键中断里直接调用HAL_Delay消抖、然后在中断里把整条短信发完。嵌入式中断里只做“置标志位”这件事具体逻辑全部放主循环这是基本素养。短信发送是一个耗时的异步过程从AT指令到模组返回再到网络侧下发整个过程几百毫秒甚至更久如果全塞在中断里系统直接卡死。2. 硬件准备与连接2.1 物料清单与核心板选择我的配置是STM32F103C8T6最小系统板、Air780E核心板、0.96寸SSD1306 OLEDI2C四针、一个轻触按键、一张能收发短信的4G SIM卡、5V/2A的电源。STM32板子用USB转串口下载程序Air780E核心板直接用USB线供电OLED从STM32的3.3V取电按键占一个PA0引脚一共没几根线。这里必须提醒一句Air780E模组的IO电平是1.8V跟STM32的3.3V电平不是直接兼容的。如果你是老手自己画底板、加电平转换芯片没有问题如果你是新手强烈建议直接买Air780E核心板合宙官方核心板已经把USB转串口、SIM卡座、天线座、电平转换全做好你只需要把核心板的UART引脚引出来给STM32就行。裸模组看着便宜但焊接SIM卡座、天线、电平转换这一套下来成本和时间反而更高。2.2 STM32与Air780E、OLED的接线表接线遵循“串口交叉”原则STM32的TX要接模组的RXSTM32的RX接模组的TX。OLED走I2CSCL和SDA分别接STM32的PB6和PB7我用的是软件模拟I2C这样以后换引脚不用改硬件配置。模块STM32引脚对端引脚说明Air780E核心板PA2 (USART2_TX)UART_RXDSTM32发送AT指令Air780E核心板PA3 (USART2_RX)UART_TXDSTM32接收模组应答SSD1306 OLEDPB6SCL软件I2C时钟SSD1306 OLEDPB7SDA软件I2C数据按键PA0按键另一脚接GND内部上拉按下为低电平所有模块GNDGND必须共地OLED模块如果带四针引出一般是VCC、GND、SCL、SDA四根线注意别把SCL和SDA接反。SSD1306的I2C地址默认是0x3C也有少部分是0x3A如果你的OLED怎么初始化都没反应先写一个I2C扫描程序确认地址。上拉电阻方面很多OLED模块板载已经做了如果你的模块是纯屏没有板载上拉SCL和SDA各接一个4.7k电阻到3.3V。2.3 按键电路与Air780E开机时序按键电路本身很简单一个轻触按键一端接PA0一端接GNDSTM32内部把PA0配置成上拉输入平时读到高电平按下变低电平。内部上拉电阻大约几十k欧姆在实验室环境够用如果现场电磁干扰比较厉害建议外部再加一个4.7k上拉电阻按下时波形更干净。Air780E上电流程值得单独说一下。模组不是一上电就能收发AT指令它有一个开机时序PWRKEY引脚需要被拉低一段时间通常几百毫秒到2秒模组才会开机开机后串口才会输出就绪信息。我用Air780E核心板的时候直接按板上的PWRKEY按键开机。如果你想实现STM32全自动控制可以用一个GPIO通过三极管控制PWRKEY拉低。开机后别急着发短信先发AT等它回OK再发ATCPIN?确认SIM卡已经识别ATCSQ看信号强度这才是完整的开机检测流程。3. 中文短信PDU编码从手算到代码3.1 中文短信为什么必须用PDU编码短信内容的编码方式有好几种最基础的是GSM 7bit能表示英文字母和数字一条短信160个字符。中文不在这个字符集里所以必须用UCS2编码也就是UTF-16BE每个汉字占2个字节一条短信最多70个汉字。光把内容编码成UCS2还不够短信协议里需要把目标手机号、编码格式、内容长度这些都打包成一种约定的格式这个格式就是PDU。很多新手第一次见PDU串会觉得很玄乎它其实就是一串十六进制字节有一堆字段SMSC短信中心、TP-MTI消息类型、目标号码、PID协议标识、DCS编码方案、UDL用户数据长度、UD内容。你可以把它理解成寄快递时的快递单收件人地址、包裹类型、重量、内容物全部写在一个面单上。短信中心拿到这个面单才知道把包裹送到哪、怎么解析里面的内容。PDU编码是整个项目最容易出错的地方但动手算一遍之后就彻底通了。3.2 手机号转换加86、两两反转、补FPDU里的电话号码不是按普通文本存的它是BCD编码也就是每个字节存两位数字而且两位数字的顺序要反转。目标手机号13512345678要转成国际格式先在前面加86变成8613512345678然后从开头两个一组分组86 | 13 | 51 | 23 | 45 | 67 | 8。分组后每组内部两个数字互换位置86转成6813转成3151转成1523转成3245转成5467转成76。最后剩下一个单独的8要在后面补一个F凑成两位变成8F。所以13512345678经过转换后是68 31 15 32 54 76 8F。这里有几个高频错误点我在调试时全踩过。第一个是忘了加86号码长度不对短信中心解析不了。第二个是反转方向搞反转成了86 13 51 23 45 67 8F这种原样输出。第三个是补F的位置有的新手把F补在最前面或者中间最后一位必须是补F。只要号码转换错了发送结果大概率是ERROR或者对方收到乱码。再看另一个例子加固一下记忆。13901234567加86变成8613901234567分组86 | 13 | 90 | 12 | 34 | 56 | 7反转后是68 31 09 21 43 65 7F。注意90反转后是09不要丢掉前面的0PDU串里这个位置的0必须保留。3.3 汉字转UCS2编码接下来把短信内容“测试”转成UCS2。在PC上打开Python执行一行命令就出结果测试.encode(utf-16-be).hex()输出是6d4b8bd5所以“测”对应6D 4B“试”对应8B D5。如果你不想开Python网上也有很多Unicode查码工具输入汉字直接给十六进制。对于固定短信内容我建议直接在PC上把所有要发的短信用这种方式预编码好在STM32端用const数组存着这样MCU完全不用做编码运算代码简单、运行也快。如果短信内容要动态变化比如后面想加上温湿度值就需要在单片机上做一个UTF-16BE编码函数。你可以先把数字转成ASCII码再把每个ASCII字符的高字节填0拼进内容里。UCS2编码下ASCII字符也是2字节比如字母A就是0x00 0x41。也就是说发一条“温度25.5度”这类短息内容里中文字符和数字字符都要统一按UCS2处理只编码中文不编码数字最后一样是乱码。3.4 构建完整PDU串并算对长度现在我们给“发送短信到13512345678内容是测试”构建完整PDU。按字段顺序排下来序号字节含义000SMSC长度字段为0表示使用网络默认短信中心101TP-MTISMS-SUBMIT提交类型200MR消息参考号30D目标号码的BCD长度13位491号码类型国际格式5-1168 31 15 32 54 76 8F转换后的目标号码1200PID协议标识普通短信1308DCS编码方案UCS21404UDL用户数据长度4字节15-186D 4B 8B D5“测试”的UCS2编码把这些字节拼成一个十六进制字符串就是0001000D916831153254768F0008046D4B8BD5这一串是38个字符也就是19个字节。AT指令里要填长度ATCMGS19这个长度是字节数不是字符数。很多人在这里翻车明明发送的字符串是38个字符结果在ATCMGS里填了38模组直接回ERROR。如果你的PDU内容变了就用strlen(hex字符串) / 2算一下字节数。有一点需要注意不同AT固件对长度是否包含最前面的SMSC长度字段定义略有差异实测Air780E按整个字符串一半填没问题如果模组报错把长度减1再试一次通常就能通过。3.5 完整AT指令序列和时间顺序短信发送的AT指令顺序是固定的不可能一条指令发完必须一步一步来每一条都要等模组返回OK再发下一条。完整的交互过程如下发AT 收OK 发ATE0 收OK 发ATCMGF0 收OK 发ATCSQ 收CSQ: 18,0 发ATCGREG? 收CGREG: 0,1 发ATCMGS19 收 发0001000D916831153254768F0008046D4B8BD5 然后立刻发0x1A十六进制字符不是字符串1A 收CMGS: 42看到提示符之后要尽快把PDU内容发出去但也不能一股脑把下一轮AT指令一起塞进去。0x1A是CtrlZ表示PDU数据结束这个字节必须用十六进制发送如果你在串口助手里手动测试就勾选HEX发送输入1A再发送。收到CMGS: 42说明短信中心已经接收42是短信中心分配给这条短信的编号。如果收到ERROR就要看具体错误码排查PDU格式、信号、SIM卡状态。命令之间为什么必须等返回因为模组和短信中心是异步交互的你在它处理上一条命令时发下一条它可能直接丢弃或者返回乱码。这一点在PC上用串口助手手动验证时体会最深手速一快就出错。4. STM32代码实现与驱动逻辑4.1 OLED初始化、取模与汉字显示OLED我用的是SSD1306控制器0.96寸128x64I2C接口。初始化时要发一串配置命令设置显示开关、电荷泵、对比度、内存地址模式这些参数。网上随便搜都能找到标准的初始化序列我用的关键命令如下// SSD1306 初始化命令子集 0xAE, // 关闭显示 0x20, 0x00, // 水平地址模式 0xC8, // COM扫描方向 0x40, // 显示起始行 0x81, 0xCF, // 设置对比度 0xA1, // 段重映射 0xA6, // 正常显示 0xA8, 0x3F, // 多路复用比 1/64 0xD3, 0x00, // 显示偏移 0xD5, 0x80, // 时钟分频 0xDA, 0x12, // COM引脚配置 0x8D, 0x14, // 开启电荷泵 0xAF, // 开启显示这些命令不用死记照抄就能用。真正容易踩坑的是汉字显示。SSD1306本身没有字库所有字符都是以点阵形式写进去的。英文和数字可以用8x16的ASCII点阵汉字至少16x16点阵一屏128x64最多显示4行汉字。我用的是PCtoLCD2002工具取模方式选择“阴码、逐列式、逆向、高位在前”生成16x16点阵数组然后在代码里按页写入OLED显存。如果你发现OLED显示中文出现雪花一样的乱码90%是取模参数的“逐列式/逐行式”和你写入函数不匹配。比如取的是逐列式写入函数却按逐行式填充字符看起来就是破碎的。我自己遇到过好几次解决办法就是固定一组取模参数后代码只按那一种参数写不要混用网上抄来的零散函数。刚开始调试建议先用英文“SEND OK”这类简单的显示跑通后再上中文字库减少变量。4.2 按键消抖与事件触发按键接在PA0上配置成外部中断下降沿触发。但机械按键按下时触点会有几毫秒到几十毫秒的抖动表现为引脚电平在0和1之间快速跳变如果不在中断里做处理一次按下可能被识别成多次触发短信就会重复发送。我的做法是“中断置标志 定时器消抖”。外部中断触发后不立即认为是有效按键而是启动一个20ms定时器定时时间到后再读一次PA0电平如果仍然是低电平才确认真按键。这样比在中断里用HAL_Delay硬等要文明得多因为Delay会阻塞整个系统OLED刷新和串口接收全部停下。// 定时器每10ms进一次中断 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { static uint8_t cnt 0; if (key_pressed) { cnt; if (cnt 2) // 20ms电平稳定 { if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET) { key_flag 1; // 置位发送标志 } key_pressed 0; cnt 0; } } }外部中断里只把key_pressed置位具体电平判断放到定时器里定时器里确认后再把key_flag置位主循环检测到key_flag才进入短信发送流程。这套消抖逻辑看起来绕了一点但好处是任何环节都不阻塞系统的实时性有保障。如果不用中断纯靠主循环轮询加20ms软件延时大多数场合也能用只是不够优雅。4.3 串口接收与AT应答解析STM32通过USART2和Air780E通信波特率1152008N1。发送AT指令直接用HAL_UART_Transmit阻塞发送没问题因为指令本身很短。难点在于接收模组的异步返回这必须用中断接收加环形缓冲区的思路。我建了一个环形缓冲区接收模组的串口数据在接收中断里把每个字节填进缓冲区主循环里再去匹配关键字。这样无论模组是返回OK、还是CMGS: 42数据都不会丢。uint8_t rx_buf[256]; volatile uint16_t rx_cnt 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART2) { rx_buf[rx_cnt] rx_data; if (rx_cnt sizeof(rx_buf)) rx_cnt 0; HAL_UART_Receive_IT(huart2, rx_data, 1); } }判断AT命令是否成功我写了一个简单的等待函数在超时时间内不断查找环形缓冲区中是否出现目标字符串uint8_t at_wait(const char *expect, uint32_t timeout_ms) { uint32_t start HAL_GetTick(); while (HAL_GetTick() - start timeout_ms) { // 在rx_buf中查找expect if (strstr((char*)rx_buf, expect) ! NULL) { return 1; } } return 0; // 超时 }这里有个容易忽略的点Air780E开机后可能主动上报一些URC消息比如网络注册状态CFUN: 1、CGEV等这些字符会混在缓冲区里。所以解析AT结果时不要简单地判断“缓冲区收到了东西”而是要明确匹配OK、、CMGS、ERROR这几个关键字。我一开始就是没注意URC主循环看到缓冲区有数据就以为成功了结果OLED显示SMS OK其实对方根本没收到短信。4.4 短信发送主流程代码核心发送函数逻辑很直白先按PDU串的长度发ATCMGSlen等待模组回再发送PDU十六进制字符串紧接着发0x1A最后等CMGS或ERROR。这里我把PDU串直接用const数组定义因为内容是固定的不占用RAM。const char pdu_hex[] 0001000D916831153254768F0008046D4B8BD5; const uint16_t pdu_len sizeof(pdu_hex) / 2 - 1; // 减掉结尾的\0 void send_sms(void) { char cmd[32]; uint8_t ctrl_z 0x1A; OLED_ShowString(0, 0, SENDING...); sprintf(cmd, ATCMGF0\r\n); uart_send(cmd); at_wait(OK, 2000); sprintf(cmd, ATCMGS%d\r\n, pdu_len); uart_send(cmd); if (!at_wait(, 3000)) { OLED_ShowString(0, 0, NO RESP); return; } uart_send((char*)pdu_hex); // 发送PDU十六进制字符串 uart_send_byte(ctrl_z); // 发送结束字节 if (at_wait(CMGS, 10000)) { OLED_ShowString(0, 0, SMS OK); } else { OLED_ShowString(0, 0, FAIL); } }注意pdu_len的计算。pdu_hex字符串长度是38个字符加结尾的\0一共39字节sizeof返回39除以2取整再减1得到19这个正好是PDU字节数。细节明明很简单实际写代码时非常容易算错我建议你写完以后加一句printf或者用调试器看一眼这个变量的值确认是19再往下跑。5. 调试实录与常见问题排查5.1 OLED不亮、花屏的排查思路OLED不亮先量电压VCC是不是3.3VGND有没有接好。电压正常就确认I2C地址写一个扫描程序把0x3C和0x3A都试一遍。初始化之后发一条全屏填充命令如果全屏能亮说明驱动基本没大问题。如果全屏亮但显示字符是花的重点查取模参数PCtoLCD2002里阴码/阳码、逐列式/逐行式、高位在前/低位在前这三组参数必须和写入函数保持一致。还有一个容易被忽略的点OLED电源不要和Air780E的VBAT混在一起Air780E瞬时电流很大会把OLED供电拉低花屏和闪烁往往就是这么来的。5.2 短信发不出去从信号到PDU逐项排查短信发不出去先查环境条件。用ATCPIN?查SIM卡识别状态返回READY才说明卡被识别如果返回ERROR大概率是SIM卡没放好或者卡槽接触不良。查ATCSQ看信号强度返回值范围0到31第一项越小信号越差低于10基本很难发出去。查ATCGREG?第二个数字为1或5才表示已注册上4G网络。网络条件都正常才轮到PDU格式问题。CME ERROR: 500这类错误通常跟PDU参数有关优先检查ATCMGS的长度、号码反转、DCS是不是08。如果PDU格式完全正确但依然失败还要看短信中心。发ATCSCA?查询当前短信中心号码空的话就手动设置一个归属地的短信中心号码。Air780E默认走网络侧短信中心大多数时候不配也能发但极端情况下会卡在这一步。5.3 对方收到乱码的原因短信能收到但显示乱码基本可以断定问题出在编码。最典型的原因是DCS没有设置成08。DCS08告诉短信中心用户数据按UCS2解析如果你用了08以外的值系统可能按7bit或者8bit去解析2字节一组的汉字结果自然全乱。第二个原因是PDU内容里的汉字本身转码转错了把“测试”转成了别的值。第三个原因是短信内容里混入了没有转成UCS2的数字或字母比如内容部分是中文UCS2部分数字又直接用了ASCII编码不统一手机解析出来就乱。建议先用手机给这个号码发一条普通短信排除接收端手机自身的问题。5.4 按键误触和重复发送按键按下一次短信发了三条这种情况我在学生项目里见过太多次。用示波器抓PA0引脚按下瞬间能看到一串毛刺脉冲这就是抖动。外部中断对每个下降沿都会响应一次按键可以触发好几次中断。解决办法就是软件消抖加“释放检测”。我看到你按下等20ms再确认一次电平确认后只置一次标志位然后要检测到按键释放、引脚回到高电平才允许下一次触发。这样即使一直按住按键也不会重复发送最多发一条。现象可能原因解决办法OLED不亮I2C地址不对、没上拉、电源不足扫描地址确认上拉独立供电OLED花屏取模参数与写入函数不匹配统一阴码/逐列式/逆向/高位在前短信全部失败SIM卡没识别、信号差、PDU长度错查CPIN/CSQ核对CMGS长度短信收到乱码DCS不是08、编码混用、UTF16转错统一UCS2检查PDU内容字段按一次发多条按键抖动、无释放检测20ms消抖等释放再允许下次触发模组串口无响应波特率不对、没开机、TX/RX接反发AT验证检查PWRKEY时序5.5 模组上电不开机或者串口无响应最后说一个特别容易被新手忽略的问题Air780E串口没输出先别怀疑模块坏了先检查你到底有没有让它开机。Air780E不是一上电就工作的PWRKEY必须有一段拉低动作。你可以用官方USB串口工具连接核心板上电后按一下板上的PWRKEY按键然后马上在串口助手里发AT看有没有返回OK。如果电脑串口助手正常换成STM32就没反应优先检查STM32和Air780E之间存在共地问题TX和RX是不是交叉接了。另外Air780E开机之后不要立刻发ATCMGS最好等2秒左右让它先完成网络附着。快速连续初始化会让模组抛出一堆URC消息你解析AT结果时会被这些无关字符串干扰逻辑判断很容易错乱。我在代码里开机后固定延时3秒再走业务流程实测稳定性有明显提升。最后分享两个我自己调这个项目时觉得最值钱的经验。第一强烈建议先把Air780E用USB转串口接到电脑用串口助手把AT、CMGF、CMGS整条链路在PC上手动跑通确认PDU串没问题再写STM32代码。不要一上来就STM32和模组联调出问题的时候你根本分不清是硬件接线问题、PDU编码问题还是代码逻辑问题。第二PDU组包这一步不要手拼写一个小工具函数输入手机号和短信内容直接输出十六进制字符串和长度。我后来把生成PDU的Python脚本留在项目目录里改短信内容时跑一下脚本复制结果进const数组就行几分钟完事。剩下的就靠你实际动手去踩坑了。
返回列表