做了大半年嵌入式项目,总会被问"STM32这板子能不能直接发短信"。说真的,直接用MCU挂4G模块发短信这个需求,在设备报警、远程定位、无人值守终端里还挺高频的。我这段时间刚把手头这套"STM32 + Air780E按键发送中文短信 + OLED状态显示"的小系统调通,从硬件接线到PDU编码到踩坑排查,全程记录一下,希望对同样在做类似项目的人有帮助。
这个项目很适合刚入门物联网或正在做简单设备告警方案的朋友参考。它的核心链路并不复杂:STM32F103C8T6通过串口控制Air780E模组,按键触发发送动作,同时用0.96寸OLED把"空闲、发送中、成功、失败"这几类状态实时显示出来。看起来每个模块都是成熟方案,但真正把它们串起来、把中文短信通过PDU模式发出去,中间有不少细节只有实际调过才知道。
1. 方案定调:短信在物联网场景里怎么就绕不开
先说说我为什么选这个组合。市面上能发短信的方案其实不少,但真正放到设备端场景里筛选,选项会迅速收敛。
1.1 短信通知在设备端的不可替代性
有人可能会说,都2024年了,发个通知用微信或App推送不香吗,为什么非要短信。这里有个很现实的问题:设备端推送的接收对象往往是运维人员或非技术用户,App推送依赖于App处于活跃状态、通知权限开着、网络畅通;微信推送需要公众号模板接口或企业微信机器人,中间多了一层云平台依赖。短信不一样,只要SIM卡有信号,消息就能进手机收件箱,完全不依赖接收端装了什么软件。对设备报警这个场景来说,可靠性比什么都重要。
我搭这套系统时,实际对标的需求是"一个野外箱体被人打开或倾斜时,自动给负责人发一条中文短信"。当时对比过几个方案:
- NB-IoT模组:功耗确实低,但短信能力和运营商套餐绑定紧密,而且NB网络覆盖在部分区域并不保证短信及时性,调试时还要额外处理PSM模式下的下行唤醒问题。
- WiFi + 云平台推送:设备必须处于有路由器的环境,和"野外箱体"这个场景天然矛盾,直接排除。
- 蓝牙 + 手机转发:传输距离限制太大,做不到无人值守。
- Air780E这种4G Cat.1模组:网络成熟、短信能力完整、AT指令操作起来最直接,关键是模组本身成本已经压到了很低的水平。
所以最终定下来就是STM32做主控、Air780E做通信模组。Air780E支持Cat.1网络,具备完整的GSM/3GPP短信协议栈,通过标准AT指令(3GPP TS 27.005 / 27.007)就可以完成短信收发,对单片机一侧来说,相当于用一个UART串口就换来了完整的通信能力。
1.2 主控和模组的分工边界
这套系统里,两边各干各的,分工明确:
- STM32:负责按键检测(输入)、OLED显示(输出)、PDU报文生成(编码逻辑)、通过UART发送AT指令并解析模组返回消息(控制逻辑)。
- Air780E:负责4G网络注册、附着、短信中心协商、PDU报文封装发送、SIM卡管理等所有和运营商网络相关的底层杂活。
这个分工思路很重要。开发时千万不要试图让MCU去"理解"网络协议栈,MCU只需要把规范要求的PDU串通过AT+CMGS丢给模组,剩下的交给模组本身。这也是为什么我建议在写MCU代码之前,先用PC串口调试助把手动发一遍AT指令,把PDU报文调通了再移植到STM32上,能省一大半排查时间。
2. 硬件连接:UART电平、SIM卡、OLED和按键的接线细节
硬件是整个系统里最容易被忽视、也最容易出奇葩问题的一环。我在这里把完整接线和几个关键坑展开讲讲。
2.1 STM32与Air780E的UART通信及电平匹配
主控和模组之间就一条UART链路。我用的是STM32F103C8T6的USART2,PA2(TX)接Air780E的UART_RX(模组脚位标号一般是UART1_RXD),PA3(RX)接Air780E的UART_TXD(UART1_TXD)。通信参数固定为:115200波特率,8N1。
这里必须强调一个最容易踩的坑:模组侧引脚的IO电平不是3.3V,而是1.8V。
Air780E的主串口电平是1.8V逻辑电平,直接和STM32的3.3V引脚互连,短时间内可能能跑,但存在两个风险:一是3.3V输出灌入1.8V域,长期使用会损坏模组IO;二是电平阈值不匹配会导致串口数据随机乱码,表现为AT指令偶尔有响应、偶尔完全死掉。我第一次就这样硬接,结果串口助手能看到乱码,花了大半天才反应过来。
推荐做法有两个:
- 最简单可靠的是用一颗1.8V→3.3V的双向电平转换模块,串在TX和RX两条线上。模块很便宜,四个通道的迷你板子几块钱一片,接上就完事。
- 如果不想额外加硬件,也可以把STM32的TX用分压电阻(比如1K串2K到GND)压到1.8V左右,但模组TX输出到STM32 RX这路是1.8V输出,STM32的RX阈值一般能识别1.8V高电平(F103的VIH最低约0.36*VDD=1.98V,严格讲1.8V略低于阈值),所以自制方案只建议临时调试用。我最终在正式板上保留的是电平转换芯片方案,踏实。
另外记得GND必须共地,SIM卡单独插入Air780E模块的SIM卡座(Micro SIM或Nano SIM卡座按模块型号来),我用了一张普通的移动4G卡,短信功能正常。Air780E还需要接天线,没天线或者天线虚焊会导致网络注册不上,后面排查时会看到这个问题。
2.2 OLED与按键的IO分配
OLED我选的是最常用的0.96寸I2C接口屏,SSD1306驱动,分辨率128x64。STM32的I2C1引脚PB6(SCL)和PB7(SDA)直接接OLED的SCL和SDA,VCC接3.3V,GND共地。
这里有一个很多人忽略的点:0.9寸和0.96寸OLED虽然驱动都是SSD1306,但I2C从机地址可能不一样,常见0x3C或0x3D。一部分模块背面有电阻跳线可改地址,另一部分是固定死的。我这次的屏幕实测地址是0x3C。如果初始化后屏幕始终不亮,第一件事就是写个I2C地址扫描程序,把0x3C、0x3D都扫一遍,而不是反复查初始化代码。
按键更简单,一个轻触开关接在PB5和GND之间,STM32内部上拉打开。按下时PB5被拉到低电平,检测下降沿即可。按键建议并联一个100nF电容做硬件消抖,软件里再叠加一次延时确认,双保险。这个设计后面第三章讲状态机时会详细说。
2.3 供电与电源走线的实际注意事项
整个板子我用了USB的5V输入,经AMS1117转3.3V给STM32和OLED供电。Air780E模组我用的独立供电引脚(VBAT),建议直接接在3.3V总线上时要关注峰值电流——4G模组在搜网和注册的瞬间电流能到2A级别,稳压器如果余量不够,会出现一个很典型的现象:模组刚上电时死活AT不响应,但过了几十秒又自己好了,其实就是搜网瞬间把3.3V拉垮,MCU也跟着复位或串口信号畸形了。
稳妥的做法是:3.3V给MCU和OLED,Air780E的VBAT由3.8V/4.2V电源域供给(锂电池或独立LDO加足够滤波电容),即使手头只有3.3V,也要在VBAT附近并联470μF以上电解电容和若干个100nF陶瓷电容来扛瞬态电流。这个细节在正式产品里能决定设备稳不稳定。
3. 核心难点:中文短信的PDU编码与AT指令流程
这应该是全项目里最有含金量的部分了。中文短信本身不是把UTF-8字节直接丢给模组就行,必须走PDU(Protocol Data Unit)模式,用UCS2编码对中文做Unicode映射。
3.1 为什么中文短信必须用PDU模式
AT指令里短信有两种工作模式:TEXT模式和PDU模式。TEXT模式靠AT+CMGF=1选择,发英文、数字短信时很简单,AT+CMGS="号码"回车,然后直接输入内容,最后发Ctrl+Z(0x1A)结束。但TEXT模式对中文的支持完全依赖模组内部字符集设定,即使通过AT+CSCS把字符集切成UCS2或GB2312,不同模组间的行为差异很大,编码转换经常出现半个字、问号、乱码。
PDU模式(AT+CMGF=0)则不同,它把用户数据编码成一组十六进制字节串,由MCU自己把中文文本转成UCS2编码,模组只负责把字节按协议发到网络。只要你的编码正确,发送结果就是确定的。所以做中文短信,直接用PDU模式是唯一稳妥的选择。
UCS2编码简单说:每个汉字对应一个16位的Unicode码点,比如"测"是0x6D4B、"试"是0x8BD5,一个汉字占两个字节,传输时按高字节在前排列。为什么PDU里看到"测试"是6D4B8BD5而不是其他乱七八糟编码,就是UCS2的结果。
3.2 PDU报文逐字节拆解
下面我以"给13800138000发送'测试'"为例,把整条PDU报文手动算一遍。这样做的好处是,以后无论换什么号码、什么短信内容,你都能自己算出对应的PDU串,不用任何现成库也能把功能做出来。
标准PDU串(发送方向)格式如下:
| 字段 | 值(本例) | 说明 |
|---|---|---|
| SMSC长度 | 00 | 00表示使用SIM卡内默认短信中心 |
| PDU类型 | 11 | 0x11表示SMS-SUBMIT、含有效期限字段 |
| 消息基准 | 00 | MR,随便填00即可 |
| 目标号码长度 | 0B | 13800138000共11位数字 |
| 号码类型 | 81 | 81表示国内号码格式,91表示+86国际格式 |
| 目标号码 | 31081083000F | 11位号码两两对调后的结果 |
| 协议标识 | 00 | PID置0 |
| 数据编码方案 | F0 | F0表示UCS2编码、Class 0即时提醒;如果不需要亮屏提醒可用08 |
| 有效期限 | A7 | 相对有效期,约12小时(可省略) |
| 用户数据长度 | 04 | "测试"两个字,UCS2下共4字节 |
| 用户数据 | 6D4B8BD5 | "测"=6D4B,"试"=8BD5 |
号码为什么变成"31081083000F":把13800138000每相邻两位颠倒,奇数位最末补F。具体过程是:
13 -> 31 80 -> 08 01 -> 10 38 -> 83 00 -> 00 0 -> 0F(最后一位补F)所以完整的PDU字符串是:
0011000B8131081083000F00F0A7046D4B8BD5注意,如果你选择不设置有效期字段,PDU类型要从0x11改成0x01,并且从字符串里去掉A7,否则结构又不对了。我自己的例程里保留了A7,这样短信不会因为超时被短信中心丢弃。
3.3 AT指令发送流程
PDU串算好之后,发送流程是这样的:
AT -> 返回OK AT+CMGF=0 -> 设置PDU模式 AT+CMGS=18 -> 18是TPDU字节数,即上面PDU串去掉第一字节"00"后的十六进制字节个数 > 这里模组会返回"大于号"提示符 0011000B8131081083000F00F0A7046D4B8BD5 -> 一次性输入PDU串 0x1A -> 发送结束符(Ctrl+Z)对应的C代码核心片段大致是:
uint8_t pdu[64]; // 预先组装好的PDU报文(十六进制字节) uint16_t tpdu_len = pdu_tpdu_length; // TPDU字节数,不含短信中心地址字段 printf("AT+CMGF=0\r\n"); delay_ms(200); sprintf(cmd, "AT+CMGS=%d\r\n", tpdu_len); usart2_send_string(cmd); // 等待模组返回 '>' (0x3E) while (!got_greater_than) { /* 串口中断里置标志 */ } usart2_send_bytes(pdu, tpdu_len); usart2_send_byte(0x1A); // 等待模组返回 +CMGS: <序号> 或 ERROR这里最关键的坑是:AT+CMGS后面跟的长度,不是PDU字符串的总长度,而是TPDU部分的字节数,也就是要去掉最开头的短信中心地址长度字节"00"。上面这个例子中完整PDU串是48个十六进制字符(24字节),去掉"00"后还有23*2=46个字符?别急,我帮你理清楚:
完整PDU串"0011000B8131081083000F00F0A7046D4B8BD5"数一下是48个十六进制字符,对应24个字节。去掉开头第一字节"00",剩下的是23个字节?不对,再数一次。上面我列PDU串时一共19组字节:
00 11 00 0B 81 31 08 10 83 00 0F 00 F0 A7 04 6D 4B 8B D5总共是19个字节。字符串长度38个十六进制字符。去掉了SMSC长度字节"00"后,TPDU部分是18个字节。所以AT+CMGS=18。
为什么会产生"24字节"的错觉?因为你把"00"和"0B"之类的每个字节当作两个字符,且有些资料把短信中心地址长度以外的字段都算进TPDU的同时,又把"00"这个字符对也误算了。实际上,只要字符串中以两个十六进制字符为一组来数,去掉第一个组"00",数剩下的组数,就是AT+CMGS要填的数。这里18组,所以填18。
在我自己实现时,为了不出错,代码里不再手动数,而是让函数返回PDU总字节数和TPDU字节数,直接用变量填充:sprintf(cmd, "AT+CMGS=%d\r\n", pdu_last_len),其中pdu_last_len在组装PDU时计算,等于总长度减1。这样把所有算术交给程序,比人肉数要可靠得多。
3.4 中文转UCS2的程序实现
在STM32上做中文转UCS2,前提是MCU工程里保存的汉字字符串是什么编码。如果代码文件保存为UTF-8,需要先解析UTF-8的3字节序列再映射;如果代码文件用GBK/GB2312编码存储,可以直接从GBK码表映射到Unicode。
我实际用的是一个小技巧:在PC上把要发的短信内容预生成一个GB2312→UCS2对照表,烧进MCU的Flash里,MCU侧直接查表。因为短信内容一般是固定模板,比如"设备报警:柜门被打开,时间:xxxx",只需要转换模板里变化的部分。
如果短信内容是动态拼出来的,则推荐代码里内置一个精简映射表,覆盖常用汉字,或者干脆把需要发送的中文全部定义成Unicode转义序列。比如:
// 字符串以UCS2字节数组方式直接定义,避免运行时编码转换 const uint8_t msg_test[] = {0x6D, 0x4B, 0x8B, 0xD5}; // "测试"这种"运行时零转换"的模式在处理固定短信时最干净,因为没有编码转换就没有乱码问题。
4. 状态显示与按键触发的软件状态机
硬件和编码方案都确认后,软件结构就顺理成章了。这一节讲按键防抖、OLED驱动、以及整个系统怎么组织成一个清晰的状态机。
4.1 按键防抖与触发逻辑
按键按下的瞬间,机械触点会产生持续数毫秒到十几毫秒的抖动,信号表现为高低电平快速跳变。如果直接检测下降沿就触发发送,实际按一次可能触发好几次。
我的处理方式是:按键接在PB5上,启用EXTI外部中断下降沿触发,中断服务函数里只做一件事——把key_pressed_flag置1,然后立即软延时8~10ms后再读取一次引脚电平,如果仍是低电平,才确认是有效按下。注意软延时不能放在中断里,正确做法是中断里只置标志,主循环里处理:
volatile uint8_t key_pressed_flag = 0; void EXTI9_5_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line5) != RESET) { EXTI_ClearITPendingBit(EXTI_Line5); key_pressed_flag = 1; } } // 主循环 while (1) { if (key_pressed_flag) { HAL_Delay(10); // 再次确认 if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) { send_sms_trigger(); // 进入发送流程 } key_pressed_flag = 0; } // OLED刷新等其它任务 }按键去抖的另一层保险是硬件并联100nF电容,和软件延时配合,基本可以杜绝误触发。
4.2 OLED驱动与中文显示方案
OLED用的是SSD1306,我直接用了HAL库的I2C驱动,没有上U8g2库。原因很简单:U8g2在STM32F103上也能跑,但它带的全套字体和绘图引擎占Flash较多,而且中文显示还需要额外挂中文字库,对这个项目来说有点杀鸡用牛刀。
我的显示需求其实很简单:
- 空闲状态:显示"Ready. Press Key"
- 发送中:显示"发送中..."
- 成功:显示"发送成功"
- 失败:显示"发送失败"以及错误码
所以只需要一个能显示ASCII和少量中文的简易字模方案。我用PCtoLCD2002软件把"发送中""发送成功""发送失败"这几个词做成16x16点阵字模,存成二维数组,通过自写的oled_draw_chinese(x, y, index)函数点阵描点显示。ASCII字符用8x16的取模数据,oled_draw_char()处理。
核心显示函数结构如下:
void oled_show_status(uint8_t status) { oled_clear(); switch (status) { case STATUS_IDLE: oled_draw_string(0, 0, "Ready. Press Key"); break; case STATUS_SENDING: oled_draw_chinese(0, 0, 0); // "发" oled_draw_chinese(16, 0, 1); // "送" oled_draw_chinese(32, 0, 2); // "中" break; case STATUS_SUCCESS: // ... break; case STATUS_FAIL: // ... break; } oled_refresh(); }这里有一个经验:I2C OLED的刷新速率有限,全屏刷新大概几毫秒到十几毫秒,实时状态刷新完全够用。但不要把OLED刷新放在和串口接收同一个中断优先级的回调里,否则发送PDU长字符串时,0x1A结束符可能因为OLED刷新耗时而被串口缓冲覆盖。
4.3 状态机设计:从IDLE到SUCCESS/FAIL
整个软件里最能提现工程水平的就是这个状态机。如果直接把"按键→发AT→等返回→显示结果"写成顺序阻塞流程,一旦模组没响应或网络慢,整个系统会卡死在等待里,OLED也不刷新,按键也失效。我用一个简单但完整的状态机解决:
IDLE -> KEY_PRESSED -> SENDING -> WAIT_CMGS -> SUCCESS / FAIL -> IDLE每个状态在循环里都有明确的进入和退出条件:
- IDLE:等待按键。按下后进入SENDING,OLED显示"发送中"。
- SENDING:发送AT指令序列(AT+CMGF=0、AT+CMGS=n、PDU数据、0x1A)。每条指令之间等待模组的确认(OK或">"),用超时机制保护,比如每条指令最多等200ms。
- WAIT_CMGS:已经发了0x1A,等待模组返回
+CMGS: <序号>或ERROR或CMS ERROR。这个过程可能持续数秒到几十秒,取决于网络情况和短信中心响应。期间如果收到+CMGS: 数字,跳到SUCCESS;如果收到ERROR或CMS ERROR,跳到FAIL。 - SUCCESS/FAIL:OLED显示对应状态,延时2秒展示结果后自动回到IDLE。
这个状态下如果超时(比如20秒没收到任何返回),我把它归为FAIL并显示超时。这样至少UI不会卡死,用户能知道出问题了。
串口收发我用了两种方式:发送AT指令在主循环里做,接收用中断把模组返回的每一行存入环形缓冲区,主循环里按行解析。环形缓冲区是整个串口通信的钥匙,能避免收一个字节处理一个字节导致的丢数据。
5. 联调实录:几个容易浪费一晚上的坑
这一节把我实际调试中踩过的坑按"现象→原因→解决办法"的方式整理出来,比走一遍完整流程更能帮你避雷。
5.1 串口电平乱码与无响应
现象:STM32发AT+CMGF=0,串口助手能看到返回的却是"+++"或一堆乱码,偶尔正常返回OK,但多发几次又死掉。
原因:前面说过,Air780E的UART是1.8V电平,和STM32的3.3V不匹配。我还遇到过另一个容易混淆的情况:模块上电后默认波特率不是115200,而是自动波特率检测。如果你第一次发AT时模块还在启动注册阶段(上电后需要几秒初始化),它可能进入波特率协商,导致后续数据错位。
解决办法:把电平转换加上;上电后先等3~5秒再做任何AT通信;第一次连接时先发AT,如果返回OK代表链路正常,乱发多指令反而容易把模块弄到不可预期状态。
5.2 AT+CMGS长度计算错误
现象:发完PDU串和0x1A后,模组立刻返回ERROR,而不是+CMGS。
原因:AT+CMGS后面的长度和PDU字符数的对应关系没搞对。我调试早期自己都被绕进去过一次,把完整PDU串的字节数直接填了进去,导致报错。
解决办法:按3.3节的方法,在代码里动态计算TPDU字节数。这里额外给一个自查技巧:发送前在串口打印出长度和PDU串,长度应该是PDU字符串长度 / 2 - 1(因为字符串里每两个字符是一个字节,减去第一字节"00")。比如"0011000B8131081083000F00F0A7046D4B8BD5"是38个字符,38/2 - 1 = 18,所以填18。
5.3 中文乱码与字节序错误
现象:短信发出去了,手机上收到的是乱码,比如"娴嬭瘯"或"��"。
原因:UCS2编码的字节序搞反了。"测"是0x6D4B,如果你在数组里写成{0x4B, 0x6D},手机上就会当成另一个字符。多数情况下问题出在PC工具生成字模/编码表时,没有按大端存储,或者MCU里查表索引时取了低字节在前。
解决办法:先不要在MCU里查,用PC串口调试助手直接手发一条完整PDU,如果PC发的能收到正确中文,说明问题在MCU端编码表或字节序,逐字节对比;如果PC发的也乱码,说明PDU本身组装错了,回到3.2节一行行对。
5.4 CMS ERROR 512与网络注册慢
现象:PDU发送流程全对,但模组返回CMS ERROR: 512。
原因:512通常表示SIM卡没插入、卡坏掉、或者网络注册还没完成。这个项目里最常见的是上电后立刻发短信,模组还没搜到网。
解决办法:发送前主动查询AT+CGREG?,返回+CGREG: 0,1表示已注册到网络。如果一直返回0或2,检查天线连接和SIM卡是否插到位。我调试时曾经因为SIM卡卡座没焊好,浪费了整整一个下午,最后换了一个卡座马上好了。另外确认SIM卡没有欠费停机——这个原因极具隐蔽性,因为AT指令、PDU流程全都正常,就是不返回+CMGS。
5.5 OLED地址与显示不亮
现象:OLED初始化后屏幕空白,i2c通信程序却显示一切正常。
原因:地址问题或模组本身是128x32而不是128x64。我买过一块0.96寸的OLED,屏幕驱动芯片是SSD1306,但背板上丝印地址是0x3D,用0x3C初始化完全不亮。还有一次是初始化代码里屏幕高度写成32行,导致下半屏一直黑的。
解决办法:写一个I2C扫描函数,把总线上所有设备的7位地址打印出来,看是0x3C还是0x3D,直接改初始化宏。高度问题查一下数据手册或螺丝孔位,常见0.96寸是128x64,0.91寸/0.95寸可能是128x32。
5.6 模组返回"+"号冲突
现象:串口发送完PDU后,模组返回的不是+CMGS,而是+CME ERROR: 22之类的。
原因:某些版本的固件默认开启AT+CMEE=2,错误信息更详细但返回结构变了。另外,如果你的发送函数里用了printf格式化,遇到"%s"中的百分号没有转义也会出错。
解决办法:调试期统一设置为AT+CMEE=1,错误码是数字格式,更好解析。代码里的打印函数别用printf直接拼AT指令,用usart2_send_string这类明确函数,避免格式化误伤。
6. 从Demo到产品:代码组织与可扩展方向
当你跑通第一版"按键发短信"之后,肯定不想只停在这个演示级别。下面几个方向是我认为最值得继续投入的,每一个都足以支撑起一个新的小项目。
6.1 代码结构建议:按模块分文件
哪怕只是一个demo,也别把所有代码堆在main.c里。我最后的目录结构大致是:
Core/ Inc/ Src/ Drivers/ Air780E/ air780e_at.c // AT指令封装:AT+CMGF、AT+CMGS、AT+CGREG air780e_pdu.c // PDU组装:号码转换、UCS2编码、长度计算 air780e_ringbuf.c // 串口环形缓冲区与行解析 OLED/ oled_ssd1306.c // I2C驱动、字模显示、状态刷新 App/ app_state.c // 顶层状态机 app_key.c // 按键检测与防抖分文件的直接好处是,调试时出了问题能很快定位在哪个模块。比如air780e_pdu.c里专门做PDU编码,新增短信模板只需改这个文件;app_state.c负责状态流转,OLED显示和串口发送都通过函数回调,模块之间不直接耦合。
6.2 从"按键发送"扩展到"自动报警"
这套硬件的天然扩展方向就是:把按键触发替换成传感器触发。
比如接一个DHT11温湿度传感器,当温度超阈值时自动发送"温度异常:当前温度xx.x度";或者接一个倾斜开关,箱体被移动时自动发短信。SPL本质上只是把APP层的触发源从GPIO按键换成传感器数据判断,AT指令和PDU部分完全复用。
还有一个实用方向是短信远程查询。Air780E支持接收短信,可以AT+CNMI配置新短信提醒,用MCU解析短信内容做简单指令。比如收到"STATUS"就回一条"当前状态:正常,温度:26.5度"。这其实就是把本项目反过来跑一遍,但接收侧的中文PDU解码比发送还多一个步骤,需要从PDU里提取UCS2字节再解码成可打印字符串,适合作为进阶练习。
6.3 低功耗与供电方案
如果设备要在电池下长时间运行,Air780E需要进入PSM(省电模式)状态。简单说就是通过AT+CPSMS配置PSM参数,设备在无业务时可以让模组进入深度睡眠,电流降到微安级,需要发短信时再通过短信/电话/定时RRC唤醒。这里要注意:PSM模式下发送前必须先等待模组重新初始化,优先级比短信本身高,否则一上来就发AT会失败。我做省电模式时曾经因为唤醒等待时间不够,导致第一条短信总是发不出去,后来在唤醒后加了一个"等待+CGREG=0,1"的阻塞判断才彻底解决。
6.4 我个人在实际调试中的两个习惯
最后分享两个贯穿整个项目的习惯,可能对你有帮助。
第一,先用PC串口调试助手把PDU流程在纯串口环境下跑通,再写MCU代码。这个做法每次都能省至少半天时间。PC串口助手本质上就是"没有状态机限制的超级调试终端",你可以手动一步步发AT、发PDU、发0x1A,实时看到模组的每一条返回,把PDU串确定了再搬进MCU,错误率大减。
第二,在MCU侧把每条AT指令的返回都打出来,发送流程里加一个可开关的调试宏。平时运行不开调试输出,等到联调时打开宏,就能看到MCU发出的每条指令和模组的每条返回,排查问题时不会再"黑盒猜谜"。我在做这个项目时,最后定位CMS ERROR那个问题就是靠这个宏打印发现模组只在某次上电后才注册成功。
这套方案从硬件选型到软件调试,难度曲线对有一定单片机基础的人来说比较友好,但每一步又有足够的深度去琢磨。如果你正在做设备报警、远程通知、无人值守终端,不妨直接把上面这套代码框架拿过去改改,先把按键触发跑通再加传感器,会非常顺。