ARTICLE DETAIL

资讯详情

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

STM32+Air780E实战:AT指令与PDU编码实现中文短信收发

STM32+Air780E实战:AT指令与PDU编码实现中文短信收发 1. 从一块吃灰的Air780E说起这个项目到底在做什么手里攒了几块Air780E模组一直没找到合适的场景用起来。直到有天家里老人说手机屏幕太小看短信费劲问我能不能搞个大屏收短信的小盒子。这个需求听起来简单但真动手做起来涉及的东西比想象中多STM32要驱动OLED做本地显示Air780E要处理中文短信的收发两者之间还得通过AT指令稳定通信。整套东西跑通之后我发现它其实是一个很典型的MCU蜂窝模组组合项目掌握了这套框架换成4G温湿度上报、远程继电器控制、GPS定位回传思路都是通的。这个项目的核心链路是这样的STM32F103C8T6作为主控通过UART和Air780E通信用AT指令控制模组发送中文短信同时STM32通过I2C驱动一块0.96寸OLED实时显示模组状态、信号强度、发送结果。按键用来触发发送动作避免上电就发短信这种尴尬情况。关键词里提到的PDU就是中文短信编码的核心——因为GSM短信协议原生只支持7位ASCII中文必须走PDU模式用UCS2编码这是整个项目里最容易翻车的地方。适合谁来参考这篇内容如果你已经会点STM32的GPIO和UART用过HAL库但没怎么碰过蜂窝模组和AT指令那这篇基本是为你写的。如果你已经用过ESP8266的AT指令那Air780E的套路会让你觉得很熟悉但短信相关的PDU编码和中文处理仍然值得单独拎出来讲。我会把踩过的坑、参数怎么算、代码怎么组织都摊开来说尽量让你少走弯路。2. 硬件选型与接线为什么是这套组合2.1 STM32F103C8T6作为主控的取舍选F103C8T6不是因为它最强而是因为它最稳。这个芯片的UART资源够用3个USARTI2C也有Flash 64KB、RAM 20KB跑这个项目绰绰有余。更关键的是它的资料和例程铺天盖地HAL库支持成熟出问题好查。有人会问为什么不直接用Air780E的串口透传功能让PC或者树莓派来发可以但那样就失去了独立设备的意义。用STM32的好处是整套东西可以脱机运行插上电源就能工作不需要额外的上位机。时钟配置上我用的是外部8MHz晶振倍频到72MHz。这个频率对UART波特率精度有帮助尤其是跑115200的时候内部RC振荡器的误差可能导致通信不稳定。如果你手头只有内部时钟建议把波特率降到9600牺牲速度换稳定。2.2 Air780E的供电与开机时序Air780E是4G Cat.1模组峰值电流能到2A左右这是很多人第一次用会忽略的点。如果你用STM32板子上的3.3V LDO给它供电大概率会在模组注册网络的时候复位。正确做法是给模组单独一路供电比如用MP1584或者SY8089这类能出2A以上的DC-DC输入接5V输出调到3.8V左右Air780E的典型工作电压是3.4V~4.2V3.8V是甜点。开机时序也要注意。Air780E的PWRKEY引脚需要拉低一段时间再释放才能开机典型值是拉低500ms以上。我一开始用GPIO直接拉低100ms模组根本没反应后来查手册改成拉低1秒才稳定开机。如果你不想用GPIO控制也可以直接把PWRKEY接地上电自动开机但这样就没法做软关机了。2.3 OLED的I2C接线与地址确认0.96寸OLED我用的是SSD1306驱动I2C接口4针VCC、GND、SCL、SDA。这里有个坑市面上很多0.96寸OLED的I2C地址是0x787位地址0x3C左移一位但也有部分是0x7A。如果你写代码时地址填错屏幕就是一片黑什么反应都没有。确认方法很简单用I2C扫描程序跑一遍看哪个地址有应答。接线方面SCL和SDA各接一个4.7kΩ上拉电阻到3.3V。有些模块板载已经带了上拉那就不要再外加否则总线电容太大反而影响通信。我实测下来STM32的I2C在400kHz速率下线长超过20cm就容易出错所以OLED尽量靠近主控板放。模块引脚STM32引脚备注Air780ETXDPA10 (USART1_RX)模组发MCU收Air780ERXDPA9 (USART1_TX)MCU发模组收Air780EPWRKEYPB0拉低开机OLEDSCLPB6 (I2C1_SCL)4.7k上拉OLEDSDAPB7 (I2C1_SDA)4.7k上拉按键一端PA0另一端接地内部上拉3. AT指令与PDU编码中文短信的真正难点3.1 Air780E的短信相关AT指令梳理Air780E的AT指令集和常见的SIM800C、SIM7600有相似之处但细节有差异。发短信的核心指令是ATCMGS但在它之前要做一系列配置。我整理了一份实际用到的指令清单AT // 测试通信返回OK说明串口通了 ATE0 // 关闭回显避免返回数据里混入发送内容 ATCMGF0 // 设置为PDU模式0是PDU1是文本模式 ATCSCSUCS2 // 设置字符集为UCS2中文必须 ATCMGSlength // 发送短信length是PDU串的长度不含SMSC部分 PDU串 // 收到提示后输入PDU数据 CtrlZ (0x1A) // 发送结束符这里要特别说明ATCMGF0和ATCMGF1的区别。文本模式1看起来简单直接发字符串就行但它对中文的支持极差很多模组在文本模式下发中文会乱码或者直接失败。PDU模式0虽然要自己编码但兼容性最好中文、英文、数字都能正确处理。我建议直接上PDU模式一次搞懂后面换模组也不用重学。3.2 PDU串的组成结构拆解一条完整的PDU串由几个部分组成以发送你好为例最终生成的PDU大概是这样的0011000B9168xxxxxxxxxx0008AA4F60597D拆开看00SMSC地址长度00表示用默认短信中心11PDU类型11表示发送短信00消息参考号0B目标号码长度这里是11位手机号91号码类型91表示国际格式68xxxxxxxxxx目标号码需要做奇偶位交换00协议标识08编码方式08表示UCS2中文AA有效期4F60597D用户数据你好的UCS2编码号码的奇偶位交换是第一个容易出错的地方。比如手机号13812345678去掉国家码后是8613812345678但PDU里要写成683118325476F8这种形式——每两个数字交换位置如果是奇数位就补F。我第一次手写这个的时候把顺序搞反了短信发出去对方收到的是乱码号码。3.3 中文UCS2编码的转换逻辑中文转UCS2是PDU编码的核心。每个中文字符在Unicode里占2个字节UCS2就是直接取这两个字节的十六进制。比如你的Unicode是U4F60UCS2编码就是4F60好是U597D编码是597D。所以你好的UCS2就是4F60597D。在STM32上做这个转换有两种思路。一种是在代码里硬编码一个Unicode映射表但中文字符太多不现实。另一种是用GB2312转Unicode的查表法或者直接让上位机把中文转成UCS2字符串再传给STM32。我采用的是第三种在STM32里内置一个精简的GB2312到Unicode的转换表只覆盖常用汉字约3500字占用Flash大概7KB左右对F103C8T6的64KB Flash来说可以接受。如果你不想在MCU里做转换还有一个取巧的办法把要发送的中文预先转成UCS2十六进制字符串存在代码里按键触发时直接拼接到PDU串里。这样代码简单但灵活性差适合固定内容的场景。3.4 短信长度计算与分段发送PDU模式下ATCMGS后面的length参数是PDU串的长度单位是字节数的一半因为PDU是十六进制字符串两个字符代表一个字节。计算方法是(PDU串字符数 - SMSC部分字符数) / 2。SMSC部分就是开头到11之前的那段。中文短信每条最多70个字符UCS2编码下140字节/2。超过70个字就要分段发送每条短信的PDU头里要加UDH用户数据头标明这是第几条、共几条。这部分比较复杂我建议先实现单条发送跑通之后再考虑长短信。实际使用中70个字对大多数通知场景已经够了。4. 代码组织从串口收发到OLED刷新4.1 UART中断接收与AT响应解析STM32和Air780E的通信我用的是一收一发的方式发送AT指令后等待模组返回根据返回值判断下一步。这里的关键是UART接收要用中断或者DMA不能用阻塞式轮询否则会丢数据。我的做法是开一个1KB的环形缓冲区UART每收到一个字节就存进去主循环里检查缓冲区里有没有OK、ERROR或者这些关键标志。Air780E的响应有时候会分多次到达比如先回ATCMGS的提示等你发了数据再回CMGS: xx和OK所以解析逻辑要能处理这种分阶段的情况。// 简化的响应等待函数 uint8_t wait_for_response(char *expect, uint32_t timeout_ms) { uint32_t start HAL_GetTick(); while ((HAL_GetTick() - start) timeout_ms) { if (strstr((char *)rx_buffer, expect) ! NULL) { memset(rx_buffer, 0, sizeof(rx_buffer)); return 1; } } return 0; }这个函数看起来简单但实际用的时候要注意rx_buffer在中断里被写入主循环里读取要做好临界区保护。我一开始没加保护偶尔会出现字符串比较到一半被中断打断的情况导致误判。4.2 按键消抖与发送状态机设计按键我用的是PA0配置成内部上拉、下降沿触发的外部中断。但机械按键有抖动直接在中断里发短信会触发多次。我的做法是在中断里只置一个标志位主循环里做20ms的延时确认确认按下后再进入发送流程。发送流程我设计成一个状态机避免在中断或者延时里做太多事情状态动作超时处理IDLE等待按键无CHECK_MODULE发AT测试模组2秒无响应则报错SET_PDU_MODE发ATCMGF02秒无响应则报错SEND_SMS发ATCMGS和PDU10秒无响应则报错WAIT_RESULT等待CMGS或ERROR15秒超时DISPLAY更新OLED显示结果无状态机的好处是每个状态只做一件事超时了能回到IDLE重新来不会卡死。OLED在每个状态切换时刷新一次显示当前进度用户能直观看到正在发送还是发送成功。4.3 OLED显示内容的布局与刷新策略0.96寸OLED分辨率是128x64能显示4行16字8x16字体或者8行21字6x8字体。我的布局是这样的第一行模组状态READY / SENDING / ERROR第二行信号强度CSQ值0-31越大越好第三行发送结果SMS OK / SMS FAIL第四行按键提示PRESS KEY刷新策略上我没有用全屏刷新而是只刷新变化的区域。全屏刷新一次要传1KB数据I2C在400kHz下大概要20ms频繁刷新会拖慢主循环。用局部刷新每次只更新一行时间降到5ms以内对状态机的响应速度影响很小。SSD1306的显存是分页的128x64分成8页每页8行像素。写数据时先设页地址和列地址再连续写该页的字节。HAL库的I2C传输函数可以直接用但要注意每次传输前发命令字节0x00命令或0x40数据。5. 实测中踩过的坑与排查过程5.1 模组开机后不响应AT指令第一次上电OLED亮了但发AT指令没有任何返回。排查过程是这样的先用示波器看Air780E的TXD引脚发现上电后一直高电平没有数据输出。怀疑是模组没开机测PWRKEY引脚发现我拉低的时间只有100ms而手册要求至少500ms。改成拉低1秒后模组TXD开始有数据但返回的是乱码。乱码的原因是波特率不匹配。Air780E默认波特率是115200但我STM32的USART1初始化时写成了9600。改过来之后AT指令正常返回OK。这里提醒一句如果你不确定模组当前波特率可以发ATIPR?查询或者用ATIPR115200强制设置。5.2 中文短信发送成功但对方收到乱码这个问题卡了我最久。现象是ATCMGS返回CMGS: xx和OK说明模组认为发送成功了但对方收到的是一串问号或者乱码。排查方向有三个PDU编码是否正确、UCS2转换是否正确、短信中心号码是否正确。我先用在线PDU编解码工具验证了我生成的PDU串发现你好的UCS2部分是对的但号码部分的奇偶位交换搞错了。我把8613812345678直接写进去了没有做奇偶交换。正确做法是去掉86剩下13812345678然后两两交换变成3118325476F8前面再加上86变成861318325476F8。改过来之后对方收到的就是正常中文了。还有一个隐藏坑ATCSCSUCS2这条指令必须发否则模组可能用默认的GSM字符集处理导致中文被截断。我一开始漏了这条短信能发出去但内容不全。5.3 OLED显示闪烁与I2C总线锁死OLED在刷新的时候偶尔会闪一下严重的时候整个屏幕卡住不动。用逻辑分析仪抓I2C波形发现SCL被拉低后一直没有释放总线锁死了。原因是STM32的I2C外设在某些异常情况下会进入死锁状态尤其是当从机OLED在传输过程中被复位或者干扰时。解决办法有两个一是加I2C总线恢复函数在初始化时如果检测到SCL或SDA被拉低就手动发送9个时钟脉冲让总线复位二是降低I2C速率从400kHz降到100kHz。我两个都做了之后连续跑24小时没有再出现锁死。// I2C总线恢复 void i2c_bus_recovery(void) { GPIO_InitTypeDef gpio {0}; // 切换SCL和SDA为普通GPIO gpio.Pin GPIO_PIN_6 | GPIO_PIN_7; gpio.Mode GPIO_MODE_OUTPUT_OD; gpio.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, gpio); // 发送9个时钟 for (int i 0; i 9; i) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); HAL_Delay(1); } // 重新初始化I2C MX_I2C1_Init(); }5.4 发送短信时模组电流拉低导致STM32复位这个问题很隐蔽每次按按键发短信STM32就重启一次。一开始以为是软件跑飞了后来用万用表测3.3V轨发现发短信瞬间电压从3.3V掉到2.8V。原因是Air780E在发射时电流突增和STM32共用了一路LDOLDO带不动导致电压跌落。解决方案是给Air780E单独供电或者至少在电源入口加一个大电容1000uF以上做缓冲。我最后是用了一块独立的5V转3.8V的DC-DC给模组STM32继续用原来的3.3V LDO问题彻底解决。这个坑提醒我射频模组的供电绝对不能和主控共用否则调试到怀疑人生。6. 从能跑到好用几个值得加的优化6.1 信号质量检测与发送前预判Air780E注册网络后可以用ATCSQ查询信号强度返回值是0-3131最好99表示无信号。我在发送短信前先查一次CSQ如果小于10就在OLED上提示信号弱让用户换个位置再试。这个预判能避免很多发送失败的无效尝试。CSQ的返回值格式是CSQ: 18,99第一个数字是信号强度第二个是误码率99表示不可用。解析的时候用sscanf提取第一个数字就行。6.2 发送结果的重试机制短信发送失败的原因很多网络拥塞、短信中心忙、信号瞬断。我的做法是失败后自动重试2次每次间隔5秒。重试的时候OLED显示RETRY 1/2让用户知道系统在工作。如果3次都失败才显示FAIL并等待用户再次按键。重试机制要注意不要无限重试否则可能被运营商判定为异常发送。3次以内是比较安全的范围。另外重试前最好重新查一次CSQ确认信号没问题再发。6.3 低功耗场景下的模组休眠控制如果这个设备是电池供电Air780E的功耗就不能忽视。正常工作电流大概20mA发射时2A待机时也有几mA。如果不需要一直在线可以用ATCFUN0让模组进入最小功能模式功耗降到1mA以下。需要发短信时再ATCFUN1唤醒等注册网络后再发。不过Air780E从CFUN0唤醒到能发短信大概需要10-15秒这个延迟要能接受。如果是按键触发发送用户按下后等15秒才发出去体验不太好。折中方案是让模组保持在线但关闭不必要的功能比如GPS、蓝牙如果模组支持的话。6.4 固件升级与参数配置的持久化项目跑通之后我加了一个小功能把短信中心号码、目标号码这些参数存在STM32的Flash里通过串口命令可以修改不用每次改代码重新烧录。STM32F103的Flash擦写寿命是1万次左右存参数这种低频操作完全够用。存储的地址选在Flash最后一页0x0800FC00写之前先擦除整页再按半字写入。读取的时候直接指针访问就行。这个做法比外挂EEPROM简单也省了一个元件。7. 这套框架还能怎么扩展把STM32Air780EOLED这套组合跑通之后你会发现它其实是一个通用的蜂窝通信本地显示平台。把短信发送换成MQTT上报就是远程环境监测把按键触发换成定时器触发就是定时数据回传把OLED换成TFT彩屏就能显示更丰富的信息。我接下来打算在这个基础上加一个DHT11温湿度传感器让设备定时把温湿度通过短信发到手机上同时OLED显示当前数值。硬件上只需要再接一根DHT11的数据线PA1软件上加一个定时器和DHT11的读取函数就行。如果你手头也有吃灰的Air780E不妨从这个项目开始把AT指令和PDU编码这两块硬骨头啃下来后面再做其他蜂窝项目就会顺很多。最后分享一个调试小技巧在开发阶段把STM32收到的所有AT响应都通过另一个串口打印到PC上用串口助手看。这样你能清楚地知道模组到底回了什么比盯着OLED猜要高效得多。等逻辑稳定了再把调试串口关掉。
返回列表