☰
STM32驱动Air780E发送中文短信:PDU编码与OLED状态显示实战
2026/10/3 13:25:00 网站建设 项目流程

1. 从一块吃灰的Air780E说起:这个项目到底要解决什么

手头攒了一堆模块,Air780E大概是那种"买的时候雄心壮志,到手之后吃灰半年"的典型代表。它便宜、能上网、支持短信和语音,但真要用起来,很多人卡在第一步——怎么让一块STM32去指挥它干活。这个项目的核心目标很朴素:用STM32通过AT指令控制Air780E,按下按键后发送一条中文短信,同时把发送状态实时显示在OLED屏幕上。

听起来简单,但里面藏着几个容易翻车的点。第一,Air780E默认走的是USB或者串口AT指令通道,STM32得用UART跟它对话,电平匹配和波特率协商不能出错。第二,中文短信不能直接发字符串,必须走PDU编码,把中文转成Unicode再拼成PDU格式,否则模块收到一堆乱码或者直接报错。第三,OLED显示状态看似简单,但I2C地址冲突、刷新频率、中文字库占用Flash这些问题,实际调试时会一个个冒出来。

这个项目适合谁?如果你已经玩过STM32的GPIO和UART,能看懂HAL库的基本调用,想找一个"有实际输出、能摸到实物反馈"的小项目练手,那这个方向非常合适。它不像纯点灯那么无聊,也不像复杂RTOS项目那样劝退,刚好卡在"有点挑战但能搞定"的区间。整个链路是:按键触发 → STM32组PDU → UART发AT指令 → Air780E执行 → 状态回传 → OLED刷新。每一环都有坑,但每一环也都有成熟的解决套路。

我实测下来,整个项目从接线到跑通,如果提前知道几个关键细节,半天就能搞定;如果不知道,可能卡三天都在怀疑模块坏了。下面我把整个流程拆开,重点讲那些文档里不会写、但实际调试一定会遇到的东西。

2. 硬件选型与接线:为什么这些细节决定成败

2.1 Air780E的供电不是"接上就行"

Air780E在发送短信的瞬间,电流会有一个明显的脉冲。官方手册里写的典型工作电流可能在几十毫安级别,但发射瞬间的峰值电流可以冲到2A左右。如果你直接用STM32板子上的3.3V LDO给它供电,大概率会遇到"模块能注册网络但一发短信就重启"的现象。

我的做法是给Air780E单独一路供电,用一块能稳定输出3.8V到4.2V、峰值电流不低于2A的DC-DC模块。注意Air780E的供电范围通常在3.4V到4.2V之间,标称3.8V最稳。如果你手头只有5V电源,千万别直接怼上去,必须加降压。另外,电源走线要粗,滤波电容要就近放,100uF的钽电容并联一个0.1uF的陶瓷电容,能明显减少发射时的电压跌落。

提示:调试阶段可以用USB转TTL模块给Air780E供电,但那个电流能力通常不够,发短信时容易掉电重启。建议一开始就用独立电源,省得后面排查半天以为是代码问题。

2.2 UART交叉连接与电平匹配

STM32和Air780E之间走UART,接线是TX接RX、RX接TX,这个大家都知道。但容易忽略的是电平匹配。Air780E的UART电平是1.8V还是3.3V,取决于具体型号和配置。我手头这块Air780E的UART默认是3.3V电平,和STM32F103的UART可以直接对接。但如果你用的是1.8V电平的版本,中间必须加电平转换芯片,否则要么通信不稳定,要么直接烧掉模块的RX引脚。

波特率方面,Air780E默认通常是115200,但有些固件版本可能是9600。我建议先用USB转TTL模块接电脑,用串口助手发一条AT,确认模块回应OK,并且确认当前波特率。确认之后再接到STM32上,这样能排除一大半通信问题。

2.3 OLED的I2C地址与上拉电阻

0.96寸OLED模块常见的有SSD1306和SH1106两种驱动芯片,I2C地址通常是0x78或0x7A(7位地址是0x3C或0x3D)。如果你买的是那种四针模块,板上一般已经带了上拉电阻,直接接STM32的I2C引脚就行。但如果你用的是裸屏或者自己画的板子,I2C的SCL和SDA必须各接一个4.7K到10K的上拉电阻到3.3V,否则I2C总线拉不起来,OLED一片黑。

我遇到过一种情况:OLED和Air780E共用一路3.3V电源,Air780E发射时电压跌落导致OLED复位,屏幕闪烁或者花屏。解决办法很简单,OLED的供电单独走一路LDO,或者在OLED的VCC和GND之间并一个100uF电容。这个细节在调试时很容易被忽略,因为单独测试OLED时一切正常,一旦和Air780E联动就出问题。

2.4 按键的消抖与触发方式

按键用普通的轻触开关就行,一端接GPIO,一端接GND,GPIO配置为上拉输入。但机械按键的抖动是物理特性,必须处理。我试过纯软件延时消抖,在main循环里检测到低电平后延时20ms再确认,效果可以,但会阻塞主循环。更好的做法是用定时器中断做10ms周期的扫描,连续两次检测到低电平才确认按下。这样既不阻塞,又能可靠消抖。

如果你想让按键触发更灵活,可以加一个状态机:按键按下后进入"发送中"状态,此时忽略新的按键,直到Air780E返回发送结果再恢复。这样能避免连续按键导致AT指令队列混乱。

3. PDU编码:中文短信绕不过去的那道坎

3.1 为什么中文短信必须用PDU模式

Air780E支持两种短信模式:Text模式和PDU模式。Text模式发英文没问题,但发中文时,模块会把中文字符当成乱码处理,因为Text模式默认走的是GSM 7-bit编码,根本不支持中文。PDU模式则允许你指定编码方式,中文走UCS2编码,每个字符用两个字节表示,这样就能正确传输。

PDU模式的本质是把短信的所有信息——包括短信中心号码、目标号码、编码方式、短信内容——打包成一串十六进制字符串,通过AT指令发给模块。模块收到后解析这串数据,再按照GSM协议发给网络。所以你需要自己完成这个打包过程,STM32的Flash和RAM都有限,不能直接跑一个完整的PDU编码库,得根据实际需求裁剪。

3.2 PDU编码的完整计算过程

假设你要发送的目标号码是13800138000,短信内容是"你好",短信中心号码是+8613800100500。PDU编码的步骤如下:

第一步,处理短信中心号码。去掉+号,得到8613800100500。判断长度是奇数还是偶数,这里是13位,奇数,需要在末尾补F变成8613800100500F。然后每两个字符交换位置,得到683108100005F0。最后在前面加上短信中心号码的长度(包括91这个国际格式标识),即0891683108100005F0。

第二步,处理目标号码。去掉+号,得到13800138000,11位,奇数,补F变成13800138000F。交换位置得到3108103800F0。在前面加上1100,其中11是号码长度(十六进制),00是目标号码类型。最终得到11003108103800F0。

第三步,处理短信内容。"你好"的Unicode码点是4F60和597D,拼起来是4F60597D。UCS2编码下,每个字符两个字节,所以内容长度是4个字节,十六进制表示为04。最终内容部分为044F60597D。

第四步,拼接。把以上部分按顺序拼接:0891683108100005F0+11003108103800F0+0008+04+4F60597D。其中0008是协议标识和数据编码方式,08表示UCS2编码。最终PDU字符串为:

0891683108100005F011003108103800F00008044F60597D

发送时,AT指令格式为AT+CMGS=<长度>,长度是PDU字符串去掉短信中心号码部分后的字符数除以2。这里PDU总长度是38个字符,短信中心号码部分占16个字符,剩余22个字符,除以2得到11。所以先发AT+CMGS=11,等模块返回>提示符后,再发送PDU字符串并以Ctrl+Z(0x1A)结尾。

3.3 在STM32上实现PDU编码的实用技巧

在STM32上做PDU编码,最头疼的是字符串拼接和十六进制转换。我的做法是定义一个足够大的字符数组作为缓冲区,比如char pdu_buf[128],然后写几个辅助函数:

  • int hex_to_char(int val):把0到15转成'0'到'9'或'A'到'F'。
  • void byte_to_hex(uint8_t byte, char *out):把一个字节转成两个十六进制字符。
  • void str_to_ucs2(const char *str, char *out):把UTF-8字符串转成UCS2十六进制字符串。这里需要一张Unicode映射表,或者直接用STM32的Flash存一个简化的GB2312到Unicode的转换表。如果只发固定几条短信,可以直接把Unicode码点硬编码在数组里,省去转换表的开销。

我实测发现,如果短信内容包含数字或英文字母,UCS2编码下每个字符仍然占两个字节,所以"你好123"的长度是5个字符,UCS2编码后是10个字节。计算长度时一定要按字符数算,不是按字节数。

注意:PDU字符串里的字母必须是大写,有些模块对大小写敏感。另外,AT+CMGS后面的长度参数是PDU数据部分的长度,不包括短信中心号码,这个很容易算错。

4. AT指令交互:时序、超时与错误处理

4.1 Air780E的AT指令响应机制

Air780E的AT指令交互是典型的"发一条、等一条"模式。你发AT+CMGS=11,模块不会立刻返回>,中间可能有几十毫秒到几百毫秒的延迟。如果你用阻塞式发送,必须加超时判断,否则程序会卡死。我的做法是用UART接收中断加环形缓冲区,主循环里轮询缓冲区,判断是否收到预期的响应字符串。

常见的响应有几种:OK表示指令执行成功,ERROR表示指令格式错误或执行失败,>表示模块准备好接收PDU数据,+CMGS: <mr>表示短信已提交,+CMTI: "SM",<index>表示收到新短信。你需要根据当前状态判断收到的是哪种响应,再决定下一步动作。

4.2 发送短信的完整状态机

我把整个发送流程设计成一个状态机,用枚举表示:

typedef enum { SMS_IDLE, SMS_SEND_AT, SMS_WAIT_OK, SMS_SEND_CMGS, SMS_WAIT_PROMPT, SMS_SEND_PDU, SMS_WAIT_RESULT, SMS_DONE, SMS_ERROR } sms_state_t;

每个状态对应一个动作和超时时间。比如SMS_WAIT_OK状态下,如果500ms内没收到OK,就跳到SMS_ERROR,并在OLED上显示"模块无响应"。SMS_WAIT_PROMPT状态下,如果2秒内没收到>,说明模块可能没准备好,重试一次或者报错。

这种状态机的好处是逻辑清晰,不会因为某一步卡住导致整个程序假死。而且OLED可以实时显示当前状态,调试时一眼就能看出卡在哪一步。

4.3 常见AT指令错误与排查方法

现象可能原因排查方法
发AT无回应波特率不对、接线错误、模块未启动用USB转TTL单独测试模块,确认波特率和回应
返回ERROR指令格式错误、SIM卡未识别检查SIM卡是否插好,发AT+CPIN?确认卡状态
返回+CME ERROR: 10SIM卡未插入或接触不良重新插拔SIM卡,检查卡座弹片
返回+CME ERROR: 3网络未注册发AT+CREG?确认注册状态,等待或检查天线
发送PDU后无+CMGSPDU格式错误、长度计算错误用串口助手手动发一遍PDU,对比模块返回
短信发送成功但对方收不到短信中心号码错误发AT+CSCA?查询当前短信中心号码

我踩过最坑的一次是SIM卡没插好,模块返回+CME ERROR: 10,我以为是PDU编码问题,折腾了半天才发现是卡座接触不良。所以遇到错误先查硬件,再查软件,这个顺序能省很多时间。

5. OLED状态显示:让调试过程可视化

5.1 OLED驱动选择与移植

0.96寸OLED常用的驱动库有u8g2和SSD1306自写驱动。u8g2功能强大但占用Flash较多,STM32F103C8T6只有64KB Flash,如果还要跑PDU编码和AT指令解析,建议用精简版的SSD1306驱动。我用的是一份只支持基本字符和数字显示的驱动,占用不到4KB Flash,足够显示状态信息。

移植时主要改三个地方:I2C的写字节函数、延时函数、初始化命令序列。I2C可以用硬件I2C,也可以用软件模拟。硬件I2C速度快但STM32的I2C外设有时候会卡死,软件模拟更稳定但占用CPU时间。我最终选了软件模拟I2C,因为OLED刷新频率不高,软件模拟完全够用,而且不用担心I2C死锁。

5.2 显示内容的布局设计

OLED屏幕只有128x64像素,能显示的信息有限。我把它分成三行:第一行显示当前状态,比如"IDLE"、"SENDING"、"OK"、"ERROR";第二行显示Air780E的响应摘要,比如"OK"或"CME ERROR";第三行显示信号强度或网络注册状态。这样调试时不用接串口,直接看屏幕就能知道程序跑到哪一步了。

如果你想让显示更丰富,可以用小字体显示更多行,但0.96寸屏上小字体辨识度不高,我建议还是用16x16的字体,保证清晰度。中文字库如果不需要显示中文,可以只保留ASCII字符,能省不少Flash。

5.3 OLED刷新与Air780E发射的相互干扰

前面提到过,Air780E发射时电压跌落可能导致OLED复位。除了硬件上加电容,软件上也可以做优化:在发送短信前先把OLED清屏,发送完成后再刷新状态。这样即使OLED因为电压波动复位,也不会显示乱码,最多黑屏一下,发送完成后重新初始化即可。

另外,OLED的I2C通信和Air780E的UART通信是独立的,不会互相干扰。但如果你把OLED和Air780E接在同一路电源上,电源噪声会通过电源线耦合到I2C信号上,导致OLED显示异常。我的做法是OLED用STM32板上的3.3V LDO供电,Air780E用独立的DC-DC,两者共地但不共电源,这样干扰最小。

6. 联调实录:从点灯到发短信的完整踩坑过程

6.1 第一阶段:确认Air780E能独立工作

拿到Air780E后,先别急着接STM32。用USB转TTL模块接电脑,串口助手打开对应端口,波特率115200,发AT,看是否返回OK。如果没反应,换波特率试试9600或57600。确认能通信后,发AT+CPIN?查SIM卡状态,返回+CPIN: READY说明卡正常。再发AT+CREG?查网络注册,返回+CREG: 0,1或+CREG: 0,5说明已注册。最后发AT+CSQ查信号强度,第一个数值在10到31之间算正常。

这一步看起来简单,但很多人卡在这里。我遇到过模块能返回OK但发短信失败,最后发现是SIM卡没开通短信功能。所以建议先用手机给这个号码发一条短信,确认卡本身能收短信。

6.2 第二阶段:STM32与Air780E的UART握手

把Air780E接到STM32的UART上,STM32的TX接Air780E的RX,RX接TX,共地。写一个最简单的测试程序:STM32每隔1秒发一次AT\r\n,然后把接收到的数据通过另一个UART打印到串口助手。如果能看到OK,说明UART通信正常。

这里容易出问题的是换行符。AT指令通常以\r\n结尾,有些模块只认\r,有些只认\n。Air780E我实测下来\r\n最稳。另外,STM32的UART发送要用阻塞模式还是中断模式?调试阶段用阻塞模式最简单,但正式运行时建议用中断或DMA,避免发送时阻塞主循环。

6.3 第三阶段:PDU编码的验证

在STM32上实现PDU编码后,先别急着发短信。把生成的PDU字符串通过串口打印出来,和手动计算的PDU对比。我建议先用一个在线的PDU编码工具生成标准PDU,然后和你代码生成的PDU逐字符对比。如果一致,再发给模块。如果不一致,检查是短信中心号码处理错了,还是号码长度计算错了,还是UCS2编码错了。

我踩过一个坑:短信中心号码的长度计算。PDU里短信中心号码前面的08表示后面跟着8个字节的数据,包括91这个国际格式标识。如果你的短信中心号码是+8613800100500,去掉+后是13位,补F后是14位,加上91后是16位,也就是8个字节,所以长度是08。这个计算如果错了,模块会直接返回ERROR。

6.4 第四阶段:OLED显示与整体联调

当UART通信和PDU编码都验证通过后,把OLED加进来。先单独测试OLED显示,确认能正常显示字符。然后把OLED刷新逻辑嵌入到状态机中,每个状态切换时更新显示内容。最后把按键加进来,按下按键触发一次发送流程。

联调时最容易出现的问题是时序冲突。比如OLED刷新时I2C占用时间较长,导致UART接收中断被延迟,错过了模块的响应。解决办法是把OLED刷新放在状态机的空闲状态执行,或者降低OLED刷新频率,只在状态变化时刷新,而不是定时刷新。

另一个问题是电源干扰。Air780E发射时,如果OLED和它共用电源,OLED可能会闪烁或复位。我最终把OLED的电源单独走了一路LDO,问题彻底解决。如果你不想改硬件,可以在软件上做容错:OLED初始化失败时重试几次,或者检测到显示异常时重新初始化。

7. 几个让项目更稳的进阶思路

7.1 用环形缓冲区管理AT指令响应

如果你的项目后续要加接收短信、打电话等功能,AT指令的响应会变得复杂。建议一开始就用环形缓冲区管理UART接收数据,主循环里解析缓冲区内容,而不是在中断里直接处理。这样能避免中断处理时间过长,也能方便地实现超时重传。

环形缓冲区的实现很简单:定义一个数组和两个指针,一个指向写位置,一个指向读位置。UART接收中断里只负责把数据写入缓冲区并移动写指针,主循环里从读指针开始查找预期的响应字符串。如果找到,处理并移动读指针;如果没找到且超时,报错。

7.2 短信发送失败的重试机制

短信发送不是100%成功的,网络拥塞、信号弱、短信中心繁忙都可能导致失败。我建议加一个简单的重试机制:如果发送失败,等待2秒后重试,最多重试3次。如果3次都失败,在OLED上显示"FAIL"并等待用户重新按键。

重试时要注意,每次重试前要确保模块处于空闲状态。如果上一次发送卡在>提示符等待状态,直接发新的AT+CMGS会出错。所以重试前先发一个AT确认模块响应正常,或者发ESC(0x1B)退出当前输入状态。

7.3 低功耗场景下的考虑

如果你的项目是电池供电,Air780E的功耗需要重点关注。Air780E在空闲时电流可能在几毫安,但保持网络注册需要持续功耗。如果不需要实时在线,可以在发送完短信后让模块进入休眠模式,发AT+CFUN=0关闭射频,需要发送时再AT+CFUN=1唤醒。但这样每次唤醒都需要重新注册网络,耗时较长。

STM32本身可以进入Stop模式,用按键中断唤醒。唤醒后先初始化UART和Air780E,再执行发送流程。OLED在不需要显示时可以关闭,进一步降低功耗。这些优化在电池供电场景下很有必要,但会增加代码复杂度,建议先跑通基本功能再考虑。

7.4 从固定短信到可变内容的扩展

目前项目是发送固定内容的中文短信。如果你想发送可变内容,比如传感器数据,需要把数字转成字符串再转UCS2。这里推荐一个简单的方法:先把数字用sprintf转成ASCII字符串,再把每个ASCII字符转成UCS2码点(ASCII字符的UCS2码点就是它的ASCII值前面补0)。比如字符'5'的ASCII是0x35,UCS2码点是0x0035。这样就能把任意ASCII字符串转成UCS2编码,再拼进PDU里。

如果内容包含中文和数字混合,比如"温度25度",需要分别处理中文字符和ASCII字符。中文字符查Unicode表,ASCII字符直接补0。拼接时注意每个字符都占两个字节,长度计算要准确。

整个项目跑通之后,你会发现最难的不是写代码,而是排查那些"看起来没问题但就是不工作"的细节。Air780E的AT指令手册写得很全,但实际调试时,电源、时序、编码格式这些地方才是真正的拦路虎。我个人的经验是:先让每个模块独立工作,再逐步联调,每次只加一个变量。这样出问题时,你能快速定位是哪个环节出了错,而不是在一堆可能性里瞎猜。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询