
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或0x7A7位地址是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一端接GNDGPIO配置为上拉输入。但机械按键的抖动是物理特性必须处理。我试过纯软件延时消抖在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。第二步处理目标号码。去掉号得到1380013800011位奇数补F变成13800138000F。交换位置得到3108103800F0。在前面加上1100其中11是号码长度十六进制00是目标号码类型。最终得到11003108103800F0。第三步处理短信内容。你好的Unicode码点是4F60和597D拼起来是4F60597D。UCS2编码下每个字符两个字节所以内容长度是4个字节十六进制表示为04。最终内容部分为044F60597D。第四步拼接。把以上部分按顺序拼接0891683108100005F011003108103800F00008044F60597D。其中0008是协议标识和数据编码方式08表示UCS2编码。最终PDU字符串为0891683108100005F011003108103800F00008044F60597D发送时AT指令格式为ATCMGS长度长度是PDU字符串去掉短信中心号码部分后的字符数除以2。这里PDU总长度是38个字符短信中心号码部分占16个字符剩余22个字符除以2得到11。所以先发ATCMGS11等模块返回提示符后再发送PDU字符串并以CtrlZ0x1A结尾。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字符串里的字母必须是大写有些模块对大小写敏感。另外ATCMGS后面的长度参数是PDU数据部分的长度不包括短信中心号码这个很容易算错。4. AT指令交互时序、超时与错误处理4.1 Air780E的AT指令响应机制Air780E的AT指令交互是典型的发一条、等一条模式。你发ATCMGS11模块不会立刻返回中间可能有几十毫秒到几百毫秒的延迟。如果你用阻塞式发送必须加超时判断否则程序会卡死。我的做法是用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卡是否插好发ATCPIN?确认卡状态返回CME ERROR: 10SIM卡未插入或接触不良重新插拔SIM卡检查卡座弹片返回CME ERROR: 3网络未注册发ATCREG?确认注册状态等待或检查天线发送PDU后无CMGSPDU格式错误、长度计算错误用串口助手手动发一遍PDU对比模块返回短信发送成功但对方收不到短信中心号码错误发ATCSCA?查询当前短信中心号码我踩过最坑的一次是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。确认能通信后发ATCPIN?查SIM卡状态返回CPIN: READY说明卡正常。再发ATCREG?查网络注册返回CREG: 0,1或CREG: 0,5说明已注册。最后发ATCSQ查信号强度第一个数值在10到31之间算正常。这一步看起来简单但很多人卡在这里。我遇到过模块能返回OK但发短信失败最后发现是SIM卡没开通短信功能。所以建议先用手机给这个号码发一条短信确认卡本身能收短信。6.2 第二阶段STM32与Air780E的UART握手把Air780E接到STM32的UART上STM32的TX接Air780E的RXRX接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并等待用户重新按键。重试时要注意每次重试前要确保模块处于空闲状态。如果上一次发送卡在提示符等待状态直接发新的ATCMGS会出错。所以重试前先发一个AT确认模块响应正常或者发ESC0x1B退出当前输入状态。7.3 低功耗场景下的考虑如果你的项目是电池供电Air780E的功耗需要重点关注。Air780E在空闲时电流可能在几毫安但保持网络注册需要持续功耗。如果不需要实时在线可以在发送完短信后让模块进入休眠模式发ATCFUN0关闭射频需要发送时再ATCFUN1唤醒。但这样每次唤醒都需要重新注册网络耗时较长。STM32本身可以进入Stop模式用按键中断唤醒。唤醒后先初始化UART和Air780E再执行发送流程。OLED在不需要显示时可以关闭进一步降低功耗。这些优化在电池供电场景下很有必要但会增加代码复杂度建议先跑通基本功能再考虑。7.4 从固定短信到可变内容的扩展目前项目是发送固定内容的中文短信。如果你想发送可变内容比如传感器数据需要把数字转成字符串再转UCS2。这里推荐一个简单的方法先把数字用sprintf转成ASCII字符串再把每个ASCII字符转成UCS2码点ASCII字符的UCS2码点就是它的ASCII值前面补0。比如字符5的ASCII是0x35UCS2码点是0x0035。这样就能把任意ASCII字符串转成UCS2编码再拼进PDU里。如果内容包含中文和数字混合比如温度25度需要分别处理中文字符和ASCII字符。中文字符查Unicode表ASCII字符直接补0。拼接时注意每个字符都占两个字节长度计算要准确。整个项目跑通之后你会发现最难的不是写代码而是排查那些看起来没问题但就是不工作的细节。Air780E的AT指令手册写得很全但实际调试时电源、时序、编码格式这些地方才是真正的拦路虎。我个人的经验是先让每个模块独立工作再逐步联调每次只加一个变量。这样出问题时你能快速定位是哪个环节出了错而不是在一堆可能性里瞎猜。