1. 项目缘起与整体方案拆解按键一按短信就发到手机上而且内容还是中文——这个需求听起来简单但真动手做过的朋友应该清楚里面藏着好几个容易翻车的点。我最近刚把一套基于 STM32 和 Air780E 的方案跑通OLED 上能实时看到模块状态、信号强度、发送结果整个过程踩了不少坑也积累了一些在常规文档里找不到的经验。这篇就把整个项目的设计思路、硬件选型、AT 指令交互、PDU 中文编码、OLED 显示驱动这几个核心环节完整拆一遍代码和配置都会给到能直接复现的程度。先说清楚这个项目适合谁看。如果你手上有 STM32 开发板F103 系列就够用想通过 4G 模块实现远程短信通知比如做环境监测报警、设备状态上报、远程控制反馈这类场景那这套方案可以直接抄。如果你只是想学 AT 指令怎么和 MCU 配合、PDU 格式怎么编码中文、OLED 怎么在 HAL 库下驱动这篇也能给你一条完整的实践路径。整个项目不依赖操作系统裸机跑逻辑清晰新手跟着做也能通。核心关键词先摆出来STM32、Air780E、OLED、AT 指令、PDU。这五个词基本覆盖了项目的全部技术面。STM32 负责主控逻辑和按键扫描Air780E 负责蜂窝网络通信和短信收发OLED 负责人机交互状态显示AT 指令是 MCU 和模块之间的“对话语言”PDU 则是中文短信能正确编码发送的关键格式。方案选型上我做了几个关键决策这里解释一下为什么这么选。主控用 STM32F103C8T6原因很简单资源够用、资料多、价格便宜串口和 GPIO 完全满足需求没必要上更高端的型号。通信模块选 Air780E是因为它支持 LTE Cat.1覆盖国内主流频段AT 指令集兼容性好而且功耗和成本比 Cat.4 模块低不少对于短信这种低速率业务绰绰有余。显示用 0.96 寸 OLEDSSD1306 驱动I2C 接口分辨率 128x64显示几行状态信息足够I2C 只占两个引脚接线简单。短信编码用 PDU 模式而不是 Text 模式因为 Text 模式发中文会乱码这是很多人第一次做短信功能时最容易踩的坑。整个系统的数据流是这样的按键按下后STM32 检测到电平变化通过串口向 Air780E 发送 AT 指令序列依次完成模块初始化检查、短信模式设置、PDU 编码发送同时把每一步的状态同步刷新到 OLED 上。模块返回的响应由 STM32 解析判断成功或失败最终在 OLED 上给出明确的结果提示。这个流程看起来线性但实际调试中串口收发时序、指令响应等待、PDU 长度计算这几个地方都需要仔细处理。2. 硬件连接与核心细节解析2.1 硬件清单与接线方案先把物料列清楚避免做到一半发现少东西。主控用 STM32F103C8T6 最小系统板通信模块用 Air780E 开发板注意是带串口引出的版本显示用 0.96 寸 I2C OLED另外需要一个按键、若干杜邦线以及一张已经激活的物联网卡或普通 SIM 卡。供电方面要特别注意Air780E 在发射瞬间电流能到 2A 左右所以千万别用 STM32 板子上的 3.3V 直接给模块供电必须单独给模块供 5V 或 3.8V且电源要能扛住瞬时大电流。接线方案我整理成表格方便对照STM32 引脚连接目标说明PA9 (USART1_TX)Air780E RXDMCU 发送模块接收PA10 (USART1_RX)Air780E TXDMCU 接收模块发送PB6 (I2C1_SCL)OLED SCLI2C 时钟线PB7 (I2C1_SDA)OLED SDAI2C 数据线PA0按键一端按键另一端接 GND3.3VOLED VCCOLED 供电GNDOLED GND / 模块 GND共地必须接这里有个细节要强调STM32 和 Air780E 必须共地否则串口通信会不稳定甚至完全收不到数据。我一开始图省事只接了 TX、RX 没接 GND结果模块毫无响应排查了半天才想起来是地线没连。另外 Air780E 的串口电平是 3.3V和 STM32 匹配不需要电平转换直接对接即可。2.2 串口通信参数与初始化要点串口是整个项目的数据命脉参数配置错了后面全白搭。Air780E 默认串口波特率是1152008 位数据位1 位停止位无校验。STM32 这边用 USART1配置成相同的参数。用 HAL 库初始化时重点检查这几个字段BaudRate 设为 115200WordLength 设为 UART_WORDLENGTH_8BStopBits 设为 UART_STOPBITS_1Parity 设为 UART_PARITY_NONEMode 设为 UART_MODE_TX_RX。接收部分我建议用中断接收 环形缓冲区的方式而不是阻塞式轮询。原因很实际AT 指令的响应长度不固定有的返回就几个字节有的返回一大串如果用 HAL_UART_Receive 阻塞接收很容易因为等待超时把主循环卡死。用中断逐字节接收存到缓冲区里主循环再根据结束标志去解析这样按键扫描和 OLED 刷新都不会被阻塞。具体做法是每收到一个字节就触发中断把数据塞进环形缓冲区同时检测是否收到 “\r\n” 结尾或者超时收到完整响应后置一个标志位。注意Air780E 上电后需要一段时间才能注册到网络通常 10 到 30 秒不等。这段时间内发 AT 指令可能没响应所以初始化流程里要加一个等待和重试机制别一上电就急着发短信。2.3 OLED 显示驱动与 I2C 地址确认OLED 用 SSD1306 驱动芯片I2C 地址通常是 0x788 位地址或 0x3C7 位地址具体看模块背面标注。HAL 库下驱动 OLED核心是写命令和写数据两个函数通过 I2C 发送控制字节区分0x00 表示后面跟的是命令0x40 表示后面跟的是数据。初始化序列包括关闭显示、设置时钟分频、设置多路复用率、设置显示偏移、设置起始行、设置对比度、设置扫描方向、设置显示模式等这些寄存器配置在 SSD1306 数据手册里都有网上也有大量现成的初始化数组可以直接用。显示内容我规划成四行第一行显示模块状态比如 “NET:OK” 或 “NET:WAIT”第二行显示信号强度CSQ 值第三行显示短信发送状态“SEND:OK” 或 “SEND:FAIL”第四行显示按键次数或最近一次操作结果。字体用 6x8 或 8x16 都行中文显示需要取模但状态信息用英文和数字就够了省去取模的麻烦。如果你确实想在 OLED 上显示中文那就需要用字模软件生成点阵数据这个后面在常见问题里会提。3. AT 指令交互与 PDU 中文编码实操3.1 Air780E 初始化指令序列模块上电后不能直接发短信得先走一遍初始化流程确认模块活着、卡正常、网络注册成功。我用的指令序列如下每一步都等模块返回 “OK” 再往下走AT // 测试模块是否响应 ATE0 // 关闭回显避免响应里混入发送内容 ATCPIN? // 查询 SIM 卡状态返回 CPIN: READY 表示正常 ATCSQ // 查询信号强度返回值第一个数在 10-31 之间算良好 ATCREG? // 查询网络注册状态返回 CREG: 0,1 或 0,5 表示已注册 ATCMGF0 // 设置短信为 PDU 模式这是中文短信的关键 ATCSCSGSM // 设置字符集这里重点说两个地方。ATE0 一定要发否则模块会把每条指令原样回显你的解析逻辑会被干扰。ATCMGF0 是 PDU 模式的开关如果设成 1 就是 Text 模式Text 模式发英文没问题但发中文会直接乱码或者发送失败因为 Text 模式不支持 Unicode 编码。很多人第一次做中文短信失败就是卡在这一步。信号强度 CSQ 的返回值格式是CSQ: rssi,berrssi 范围 0-31数值越大信号越好99 表示无信号。我实测下来rssi 低于 10 的时候发送短信容易失败所以 OLED 上显示信号强度很有必要能帮你快速判断是不是网络问题。3.2 PDU 编码原理与中文短信构造PDU 是短信的底层传输格式全称 Protocol Data Unit。它把短信内容、目标号码、编码方式、时间戳等信息打包成一串十六进制字符串。中文短信必须用 UCS2 编码每个中文字符占 2 个字节对应一个 4 位十六进制数。比如 “你好” 的 Unicode 是 4F60 597D在 PDU 里就写成 “4F60597D”。一条完整的 PDU 串由这几部分组成短信中心号码长度和号码、PDU 类型、目标号码长度和号码、协议标识、数据编码方案、有效期、用户数据长度、用户数据。听起来复杂但实际构造时有固定套路。我以发送 “你好” 到号码 13800138000 为例拆解一下构造过程。短信中心号码一般存在 SIM 卡里可以用ATCSCA?查询假设返回8613800100500。号码需要做奇偶位交换处理去掉开头的 “”得到 “8613800100500”长度 13 位是奇数末尾补 “F” 变成 “8613800100500F”然后两两交换得到 “683108100005F0”。前面加上长度字节 “0D”13 的十六进制就是短信中心号码部分。目标号码同样处理“13800138000” 长度 11 位补 “F” 变 “13800138000F”交换后得 “3108103800F0”前面加长度 “0B”。PDU 类型用 “11” 表示发送短信、相对格式。协议标识 “00”数据编码方案 “08” 表示 UCS2 编码。有效期 “00” 表示默认。用户数据长度是 UCS2 编码后的字节数“你好” 是 4 个字节所以是 “04”。用户数据就是 “4F60597D”。拼起来就是0D683108100005F011000B3108103800F0000800044F60597D。发送时用ATCMGS长度指令长度是 PDU 串去掉短信中心号码部分后的字符数除以 2然后等模块返回 “” 提示符再把 PDU 串发过去最后发 CtrlZ0x1A结束。提示PDU 长度计算很容易出错建议在代码里写一个函数自动计算不要手动数。长度算错会导致模块返回 ERROR 或者短信发送失败。3.3 按键触发与发送流程代码实现按键用 PA0配置成上拉输入按下时接地产生下降沿。我用外部中断或者定时器轮询都行这里用简单的轮询加消抖每 20ms 检测一次连续两次检测到低电平才认为按键有效。按键触发后置一个发送标志主循环检测到标志后执行发送流程。发送流程的伪代码逻辑如下void send_sms_task(void) { if (!send_flag) return; send_flag 0; // 步骤1检查网络状态 if (!check_network()) { oled_show_status(NET:FAIL); return; } oled_show_status(NET:OK); // 步骤2构造PDU串 char pdu[256]; int pdu_len build_pdu(13800138000, 你好, pdu); if (pdu_len 0) { oled_show_status(PDU:ERR); return; } // 步骤3发送ATCMGS指令 char cmd[32]; sprintf(cmd, ATCMGS%d\r\n, pdu_len); uart_send_string(cmd); if (!wait_response(, 5000)) { oled_show_status(SEND:FAIL); return; } // 步骤4发送PDU串和结束符 uart_send_string(pdu); uart_send_byte(0x1A); if (wait_response(CMGS:, 10000)) { oled_show_status(SEND:OK); } else { oled_show_status(SEND:FAIL); } }wait_response函数的作用是在超时时间内不断检查接收缓冲区里有没有目标字符串有就返回成功超时返回失败。这个函数的超时时间设置很关键ATCMGS等待 “” 提示符给 5 秒够了但发送完 PDU 后等待CMGS:响应要给足 10 秒因为网络传输需要时间。3.4 OLED 状态刷新与界面布局OLED 刷新我放在主循环里每 500ms 更新一次避免频繁刷屏导致 I2C 占用过高。显示内容分四个区域用OLED_ShowString函数在指定坐标写入。坐标规划第一行 y0第二行 y16第三行 y32第四行 y48每行 16 像素高刚好占满 64 像素。状态变量用一个全局结构体管理包含网络状态、信号强度、发送结果、按键计数。主循环里根据这些变量的值决定显示什么字符串。比如网络状态为 1 显示 “NET:OK”为 0 显示 “NET:WAIT”。信号强度直接显示 CSQ 数值。发送结果用 0/1/2 分别表示空闲、成功、失败。这里有个实用技巧OLED 刷新时不要整屏清除再重写那样会闪。正确做法是每个区域固定宽度写入新内容前先用空格覆盖旧内容或者直接写入等长字符串覆盖。我一开始用整屏清除结果屏幕一直在闪后来改成局部覆盖就稳定了。4. 常见问题排查与避坑经验实录4.1 模块无响应或指令返回 ERROR这是最常见的问题排查思路按顺序来。先确认供电用万用表量模块 VCC 和 GND 之间电压是否稳定在 3.8V 或 5V发射瞬间有没有大幅跌落。如果电压跌到 3V 以下模块会重启或死机。再确认串口接线TX 和 RX 有没有接反这是新手最容易犯的错。然后确认波特率Air780E 默认 115200但有些版本可能是 9600可以发 “AT” 多试几个波特率。最后确认 SIM 卡卡有没有插好、有没有欠费、有没有激活。如果发ATCMGS返回 ERROR大概率是 PDU 串有问题。检查短信中心号码是否正确、目标号码奇偶交换有没有做、UCS2 编码有没有算错、长度字段是否匹配。我建议先把 PDU 串在电脑上用串口助手手动发一遍确认能成功再放到代码里这样能快速定位是编码问题还是代码问题。4.2 中文短信收到后显示乱码乱码基本只有一个原因编码方式不对。确认ATCMGF0已经设置确认 PDU 里的数据编码方案字段是 “08”UCS2确认用户数据部分确实是 Unicode 编码的十六进制。如果这三点都对那可能是接收方手机的问题但概率极低。还有一种情况是你发的是英文但用了 UCS2 编码接收方可能显示异常英文用 GSM 7-bit 编码更合适但为了统一处理中文全部用 UCS2 也没问题只是长度会翻倍。4.3 OLED 不亮或显示异常先查 I2C 地址用 I2C 扫描程序扫一下总线上有哪些设备确认 OLED 地址是 0x78 还是 0x3C。再查接线SCL 和 SDA 有没有接反上拉电阻有没有很多模块自带不用外接。如果屏幕亮但显示花屏检查初始化序列是否完整特别是对比度设置和显示模式设置。如果显示内容错位检查坐标计算和字体宽度是否匹配。我遇到过一种情况OLED 能显示但偶尔闪一下后来发现是 I2C 速率太高降到 100kHz 就稳定了。HAL 库默认 I2C 速率是 100kHz如果你改过时钟配置导致 I2C 速率变化记得检查一下。4.4 短信发送成功但对方延迟收到这是网络侧的问题不是你的代码问题。短信走的是信令通道网络拥堵时延迟几秒到几十秒都正常。如果延迟特别长检查信号强度CSQ 低于 10 就换个位置或者加天线。另外确认短信中心号码是否正确号码错了短信会一直排队发不出去。4.5 常见问题速查表现象可能原因排查方法模块无响应供电不足、接线错误、波特率不对量电压、查 TX/RX、试不同波特率ATCMGS 返回 ERRORPDU 格式错误、长度不对手动发 PDU 验证、检查长度计算中文乱码未设 PDU 模式、编码方案错误确认 ATCMGF0、检查 08 字段OLED 不亮I2C 地址错、接线反、初始化缺失扫描 I2C 地址、检查接线、补全初始化发送成功但延迟网络拥堵、信号弱查 CSQ、换位置、加天线按键无反应消抖不够、引脚配置错加消抖逻辑、检查上拉输入配置4.6 几个让我印象深刻的坑第一个坑是串口接收缓冲区溢出。我一开始用固定长度数组接收结果模块返回的响应偶尔超过数组长度导致数据被截断解析失败。后来改成环形缓冲区大小设 512 字节问题解决。第二个坑是PDU 长度计算把短信中心号码也算进去了导致ATCMGS的长度参数偏大模块一直返回 ERROR。正确做法是长度只算目标号码之后的部分。第三个坑是OLED 刷新和串口接收冲突因为 I2C 和 UART 都在主循环里操作如果 OLED 刷新时间太长会错过串口数据。解决办法是把 OLED 刷新拆成多次小操作或者用 DMA 刷屏减少 CPU 占用。还有一个经验调试阶段一定要把模块返回的原始数据打印出来不管是通过串口助手还是 OLED 显示看到原始响应才能准确判断问题。我习惯在代码里加一个调试开关打开后把所有 AT 交互记录到数组里需要时通过串口输出到电脑查看。5. 项目扩展与个人实操体会这套基础框架跑通之后能扩展的方向很多。比如把按键触发改成定时触发做成定时上报设备状态或者接入传感器温度、湿度、烟雾超过阈值自动发短信报警还可以加一个接收短信的功能解析对方回复的指令实现远程控制。OLED 显示也可以做得更丰富加图标、进度条、中文提示只要取模到位都能显示。我在实际使用中发现Air780E 的稳定性相当不错连续跑了一周没出现掉线或死机。但要注意散热模块长时间工作在发射状态会发热如果放在密闭盒子里建议留散热孔。另外 SIM 卡座的质量参差不齐有些便宜卡座接触不良会导致随机性的网络注册失败这个在选物料时要注意。最后分享一个小技巧如果你觉得每次改 PDU 编码都要重新编译烧录太麻烦可以把目标号码和短信内容存在 STM32 的 Flash 里通过串口指令动态修改这样调试阶段不用反复烧录。具体做法是定义一个结构体存号码和内容上电时从 Flash 读取收到修改指令时写入 Flash 并重启生效。这个思路在需要频繁改内容的场景下能省不少时间。