拿到 Air780E 这款开发板那天,我第一件事没去折腾 Lua 脚本,也没急着连云平台,而是先打开串口助手敲了一条AT。在 5 分钟里把短信发出去,听起来简单,实际是验证板子好坏、SIM 卡是否有效、串口链路是否通畅最快的方式。很多人在这个环节卡住,不是模组有问题,而是根本没把通信链路跑通。这篇文章就把我反复验证过的流程完整写下来,基于 AT 指令而不是复杂固件开发,附可直接复制运行的完整代码,适合刚拿到 Air780E 开发板、想最快验证硬件和 SIM 卡的开发者参考。
1. 为什么短信发送是第一道坎,AT 指令又是最快的切入方式
很多人拿到 Air780E 的第一反应是去查 Lua 开发文档,或者想着用 C 写固件。但真实的项目经验是——第一步不是功能开发,而是确认“模组能正常通信”。短信发送正好是这个确认过程的核心测试项:它既依赖射频信号,又依赖 SIM 卡鉴权,还依赖串口数据通路,任何一个环节有问题都会直接暴露。
1.1 Air780E 是什么样的一块板子
Air780E 是合宙推出的 LTE Cat.1 模组,相比传统 2G 模组,它存活在 4G 网络生态里,支持移动、联通、电信的网络,不需要 2G/3G 退网的问题。同时它主打低功耗,适合智能表计、定位器、共享设备这类对功耗敏感的物联网场景。开发板一般把模组、天线座、SIM 卡座、电源电路、USB 转串口电路集成在一起,用一根 USB 线就能连电脑,非常省事。
Air780E 支持三种开发方式:AT 指令、Lua 脚本(LuatOS)和 C SDK。这里先说结论:如果目标只是快速验证硬件和 SIM 卡,或者给设备做一个简单的短信告警功能,AT 指令是效率最高的路径。不需要搭建交叉编译环境,不需要刷固件,一根 USB 线加一个串口助手就能开干。
1.2 三种开发方式应该怎么选
| 开发方式 | 上手成本 | 适合场景 | 主要短板 |
|---|---|---|---|
| AT 指令 | 最低 | 快速验证、简单告警、原理解析 | 复杂业务逻辑难维护 |
| LuatOS Lua 脚本 | 中等 | 业务逻辑较复杂、需要远程升级 | 需要刷固件、熟悉 Lua 语法 |
| C SDK | 较高 | 深度定制、极致性能控制 | 编译调试链路长、门槛高 |
我个人的建议是:第一次接触 Air780E,先别急着选技术路线,用 AT 指令把短信发通,建立对这块板子的直觉。等确认硬件和网络没问题了,再按项目需求选 Lua 还是 C。这样后续如果出了问题,你知道问题到底在硬件、SIM 卡、网络还是代码里,定位成本会低很多。
1.3 短信功能在设备端的真实位置
短信这个功能在物联网项目里经常被低估。它不走数据流量,只要有蜂窝信号就能收发,是成本最低的“带外通知通道”。我经手过的项目里,短信发送大概集中在三类场景:环境监测设备超过阈值后给值班人员发告警;定位器把经纬度以短信形式发到家人手机;设备首次上电后向服务器发送一条包含设备 ID 的注册短信。Air780E 上的短信功能,本质上就是在可编程的串口流程上叠加 AT 指令,链路一旦通了,后面做任何功能都有底气。
2. 物料清单与接线:第一步就把串口搞对,后面才能顺利
短信发不出去,有一半的概率根本不是短信的问题,而是电脑压根没有和模组建立起来正常对话。串口链路不通,后面全是白费功夫。
2.1 需要准备的东西
- Air780E 开发板或核心板:建议直接买带 USB 转串口电路的版本,省去手动接线的麻烦。如果你手里只有裸模组,就额外需要一个 USB 转 TTL 模块(CH340 或 CP2102 都行)。
- USB 数据线:必须是数据线而不是纯充电线,很多“USB 线不通数据”的问题就是这么来的。
- SIM 卡:一张支持 4G 网络的手机卡,或者兼容 Cat.1 的物联网卡。注意 Air780E 工作在 4G 网络,需要确认使用区域有 4G 覆盖。
- 4G 天线:确保天线连接到模组天线座。有些人觉得只是发个短信不接天线也行,实际上信号弱会导致短信发送失败或严重延迟,别省这一步。
- 串口调试软件:Windows 下用 XCOM 或 SSCOM,Linux 下用 minicom 或 picocom。后面示例代码用 Python 的 pyserial 库。
2.2 接线与供电的核心原则
用开发板的话,接线没什么好担心的——USB 线插上即可,电脑设备管理器会出现 COM 口。用裸模组就需要手工接线,记住三个原则:交叉接线、共地、供电充足。
模组的 TX 引脚接 USB 转 TTL 的 RX,模组的 RX 引脚接 USB 转 TTL 的 TX,GND 与 GND 相连。很多新手在这里直接把 TX 接 TX、RX 接 RX,结果 AT 指令完全没反应。
供电方面,Air780E 是 4G 模组,发射瞬间电流能达到 1A 以上,USB 转 TTL 模块上那点 3.3V 电压往往喂不饱它,造成启动失败或随机重启。如果你是裸模组接线调试,最好用官方 EVB 或一个能输出 3.8V~4.2V、电流余量充足的电源模块。
接好之后,在设备管理器里确认串口驱动已识别。如果出现两个 COM 口,一般 AT 指令口对应主串口,日志口是另一个,选择标注为 AT 口的那一个。
2.3 开机自检与串口链路验证
打开串口调试工具,波特率默认设 115200,然后在发送区输入AT,点击发送,如果模组回了OK,说明链路已经通了。
新板子上电时通常会在串口里打出一串开机日志,比如RDY、^MODE: 0等,这些不是错误,只是模组启动过程中的状态信息。如果发送AT之后完全没有反应,先检查波特率是不是 115200,有的固件默认是 9600,换成 9600 再试一次。如果仍然无响应,回到 2.2 核对 TX/RX 是否交叉、GND 是否连接、电源是否稳定。
这里顺便说一个我踩过的坑:串口调试工具里“发送新行”这个选项默认勾选,发送AT时会自动追加\r\n,大多数 AT 指令能兼容;但后面章节会提到,在输入短信内容时,如果还开着“发送新行”,可能会把\r\n混进短信正文里,导致内容出现多余换行。建议把“发送新行”理解成“按具体场景单独控制”,而不是全程打开。
3. 逐条 AT 指令:从信号检测到短信发送的过程拆解
AT 指令这套体系看起来很古老,但它是当前蜂窝模组最通用的控制方式。把每一条指令的意图和返回结果看懂,比直接跑通一次更有价值。
3.1 先确认信号和网络:发短信也得有网
拿到一块新板子,建议按下面的顺序做基础检查:
AT:握手,返回OK说明串口通信正常。AT+CSQ:查询信号质量。返回格式+CSQ: 23,99,第一个数字代表接收信号强度,范围 0~31,数值越大越好,小于 10 说明信号偏弱;第二个数字是误码率,99 表示未知或不可用。AT+CREG?:查询网络注册状态。返回+CREG: 0,1表示已注册到本地网络;0,5表示已注册但处于漫游状态;其他数字一般代表未注册或正在搜索。AT+CPIN?:查询 SIM 卡状态。返回+CPIN: READY说明 SIM 卡正常;如果是其他结果,说明卡没插好、卡损坏,或者卡需要输入 PIN 码。
这四条指令是蜂窝模组的“体检套餐”,如果这里都没问题,短信发送链路就已经具备了一半条件。
3.2 检查短信中心号码:容易被忽略的关键参数
短信不是直接发给对方的,而是先提交到运营商的短信中心,再有短信中心转发出去。模组必须知道短信中心的号码,否则短信无法提交。
用AT+CSCA?查询短信中心号码,正常情况下会返回类似+CSCA: "+8613800755000",145的内容。如果返回空,或者你想更换短信中心,可以用AT+CSCA="+8613800755000"这样的格式设置。不同地区、不同运营商的短信中心号码不一样,最简单的方式是把 SIM 卡插到你自己的手机里,在短信设置里找到当前短信中心号码,再填到模组里。Air780E 的 AT 手册里也会说明CSCA的用法。
3.3 设置文本模式
AT 指令下发短信有文本模式和 PDU 模式两种。PDU 模式是底层编码方式,支持中文内容,但指令格式复杂,对新手不友好;文本模式下,内容可以直接作为字符串下发,对英文和数字短信非常方便。
发送AT+CMGF=1,把模组设置成文本模式,返回OK即可。后面章节会讲到中文短信在文本模式下还需要额外处理字符集,这里先用英文或数字内容跑通链路。
3.4 完整发送短信的指令流程
核心指令是AT+CMGS="对方号码",注意号码要用英文双引号包起来。下发这条指令后,模组不会立刻返回OK,而是返回一个>提示符,意思是“短信内容输入区就绪,请开始输入”。
此时不要按回车,不要发送任何额外换行,直接输入短信内容。内容输入完成后,需要发送十六进制字节0x1A,也就是键盘上的CTRL+Z,告诉模组“内容输入结束”。随后模组会返回类似+CMGS: 123的结果,后面跟一个OK,表示这条短信已经进入发送流程。
整个流程梳理成表格:
| 步骤 | 发送内容 | 预期返回 | 说明 |
|---|---|---|---|
| 1 | AT | OK | 握手 |
| 2 | AT+CSQ | +CSQ: 21,99 | 信号正常 |
| 3 | AT+CREG? | +CREG: 0,1 | 已注册 |
| 4 | AT+CPIN? | +CPIN: READY | SIM 卡正常 |
| 5 | AT+CSCA? | +CSCA: "+86138..." | 短信中心存在 |
| 6 | AT+CMGF=1 | OK | 文本模式 |
| 7 | AT+CMGS="10086" | > | 进入输入状态 |
| 8 | Hello Air780E后发0x1A | +CMGS: 123 | 加入发送队列 |
这里有个细节:+CMGS: 123后面的数字是模组给这条短信分配的本地编号,只代表短信已经成功提交到模组或短信中心,不代表对方已经收到。短信送达是一个异步过程,这个编号的作用是帮你日志里追踪是哪条短信。
3.5 收发一体:读取短信的入门方式
短信功能不只是“发”,也经常要做“收”,比如服务器下发指令、验证码回传。如果你跑通了发送,顺便看一下接收的指令格式并不难。
AT+CNMI=2,1:开启新短信提示,有新短信进来时模组会主动上报提示。AT+CMGL="ALL":列出所有短信。AT+CMGR=<序号>:读取指定序号的短信。
接收链路和发送链路一样依赖信号和 SIM 卡状态,后续如果要做短信点对点控制,可以从这几条指令进一步扩展。
4. 完整代码:用 Python 把发短信做成可控过程
用串口助手手工点按钮能验证流程,但真实项目里不可能让人盯着屏幕敲 CTRL+Z。把这套流程写成脚本,才能真正变成“可用”的功能。
4.1 为什么选用 Python
用 Python 的原因很简单:pyserial 库安装方便、跨平台、代码直观,适合做验证和原型。如果你想最终把逻辑跑在 Air780E 上,可以先用 Python 在电脑端验证串口协议,确认逻辑没问题后再用 C 或 Lua 移植。Python 在这里的角色是“上位机调试脚本”,不是最终固件。
4.2 串口操作的核心逻辑
代码的核心逻辑就是模拟我们在串口助手里的手工操作:
打开串口 -> 发送基础检测指令 -> 发送AT+CMGS-> 等待>-> 写内容 -> 写0x1A-> 读取最终结果。
这里有一个容易写错的地方:等待>不能用 readline 阻塞,因为模组返回>时后面没有完整的换行,readline 可能一直等不到数据。更稳妥的方式是用轮询,在超时时间内循环读取串口缓冲区,直到发现>字符。
另一个容易踩坑的点是0x1A的发送方式。在 Python 里用ser.write(bytes([0x1A])),bytes([0x1A])表示单个字节,十进制值是 26,对应 ASCII 码里的 SUB,也就是 CTRL+Z。如果你误写成了ser.write('1A'.encode()),发出去的会是两个可见字符1和A,模组完全不会理解。
4.3 可以直接运行的完整 Python 代码
下面这段代码我实际跑过,配合 Air780E 开发板默认 115200 波特率可以正常工作。你需要根据自己的实际 COM 口修改SERIAL_PORT变量。
# -*- coding: utf-8 -*- import serial import time SERIAL_PORT = 'COM8' # Windows 示例,Linux 下通常是 /dev/ttyUSB0 BAUDRATE = 115200 ser = serial.Serial(SERIAL_PORT, BAUDRATE, timeout=2) def send_at(cmd, expect='OK', wait_time=1.5): """发送一条 AT 指令,并判断返回里是否包含期待的关键字""" ser.reset_input_buffer() ser.write((cmd + '\r').encode()) time.sleep(wait_time) resp = ser.read(ser.in_waiting or 1).decode(errors='ignore') print(f'命令: {cmd}') print(f'返回: {resp.strip()}') return expect in resp def wait_prompt(timeout=5): """等待模组返回 > 提示符""" deadline = time.time() + timeout buf = '' while time.time() < deadline: data = ser.read(ser.in_waiting or 1).decode(errors='ignore') if data: buf += data if '>' in buf: print('已进入短信内容输入状态') return True print('等待 > 提示符超时') return False def send_sms(phone, content): """向指定号码发送一条文本短信""" print('--- 发送短信开始 ---') # 基础链路检测,任何一步失败都应停止 send_at('AT') send_at('AT+CSQ') send_at('AT+CREG?') send_at('AT+CPIN?') send_at('AT+CMGF=1') # 下发 AT+CMGS 指令,注意用英文双引号包裹号码 ser.write(f'AT+CMGS="{phone}"\r'.encode()) if not wait_prompt(): return False # 输入短信内容后发送 0x1A 作为结束标志 ser.write(content.encode()) time.sleep(0.3) ser.write(bytes([0x1A])) # 读取模组最终返回 time.sleep(3) resp = ser.read(ser.in_waiting or 1).decode(errors='ignore') print(f'最终返回: {resp.strip()}') return '+CMGS' in resp if __name__ == '__main__': try: ok = send_sms('10086', 'Air780E test sms') print('发送成功' if ok else '发送失败,请检查上面日志') finally: ser.close()代码里做基础链路检测是有意为之,不是拖沓——如果 SIM 卡没插好、信号弱、网络没注册,后面的AT+CMGS肯定失败,提前排查比事后猜更快。
4.4 运行结果长什么样
正常运行时会输出类似下面的日志:
--- 发送短信开始 --- 命令: AT 返回: OK 命令: AT+CSQ 返回: +CSQ: 21,99 命令: AT+CREG? 返回: +CREG: 0,1 命令: AT+CPIN? 返回: +CPIN: READY 命令: AT+CMGF=1 返回: OK 已进入短信内容输入状态 最终返回: +CMGS: 9 OK 发送成功+CMGS: 9表示模组已经接受这条短信并分配了本地序号,接下来的OK代表指令执行顺利。如果看到的是ERROR或CMS ERROR,则按下一章的排查思路去查。
4.5 如果想发中文短信怎么办
文本模式下发英文和数字很简单,但发中文内容就涉及字符集问题。模组默认字符集通常不是中文,你需要手动切换。
方法是在AT+CMGF=1之后,再发送AT+CSCS="UCS2",把模组字符集切到 UCS2,然后把中文内容转成 UCS2 编码的十六进制字符串再发送。在 Python 里的转换方法是:
content_hex = content.encode('utf-16-be').hex().upper()比如“你好”转出来是4F60597D,然后把content_hex作为内容发送,而不是直接发送中文原文。
这里要特别提醒:UCS2 转换在不同模组上行为可能有差异,我在 Air780E 特定固件版本上验证过,但你在实际项目里还是要以手里固件的 AT 手册为准。如果中文不是刚需,建议先用英文或数字把链路跑通,再处理编码问题。
4.6 如果用 C 语言做,核心逻辑怎么迁移
如果你最终要在嵌入式 Linux 或单片机侧用 C 实现同样的功能,核心逻辑不用改,只需要把串口读写换成对应平台的接口。Linux 下可以用 termios 配置串口,然后open()、write()、read()即可;单片机侧则用你自己板子的串口驱动库。关键点还是那几个:命令要带\r结尾,内容发送结束后要发0x1A字节,等待>要用轮询而不是阻塞。
5. 踩坑实录:5 分钟没搞定短信,按这个顺序排查
这一章记录的是我实际调试过程中最常见的几类问题。如果你按上面流程走,5 分钟内没发出短信,不要慌,按这个顺序一条条查。
5.1 AT 指令完全没有响应
这是最让人头大的情况,但原因通常很基础:
- 串口号选错了:打开设备管理器,确认用的是 AT 口而不是日志口。
- TX/RX 接反了:裸模组接线时容易出现,TX 接 RX、RX 接 TX 才是正确姿势。
- 波特率不对:Air780E 常见默认波特率是 115200,但不同固件默认值不同,遇到无响应就换 9600 试试。
- 供电不足:4G 模组发射时电流很大,如果用的是 USB 转 TTL 模块供电,大概率会电压跌落,换个独立 4V 电源解决。
我的排查习惯是“先软件后硬件”,先试波特率,再换串口工具,如果都不行再检查接线和电源。
5.2 SIM 卡相关的报错
发送AT+CPIN?如果返回的不是READY,说明 SIM 卡链路没打通。常见情况是:
- 卡没插到位,Air780E 的卡座比较紧,插进去后轻轻按压到卡到位。
- 卡有 PIN 码保护,需要在指令里用
AT+CPIN="xxxx"解锁。 - 卡本身是坏的或者欠费停机,插手机验证一下。
- 卡不支持 4G 网络。低版本物联网卡可能需要确认是否开通了 Cat.1 接入能力。
5.3 发短信时返回 ERROR 或 CMS ERROR
发送AT+CMGS后如果返回ERROR,多半是参数格式问题,比如号码没加英文双引号、号码中间有空格。如果返回的是CMS ERROR: xxx,那是模组层面拒绝提交短信,常见的错误码有这些:
| 错误码 | 大致含义 | 排查方向 |
|---|---|---|
| 310 | SIM 卡缺失 | 检查 SIM 卡安装 |
| 320 | 短信中心地址未知 | 执行AT+CSCA?并配置短信中心 |
| 512 | 短信中心拒绝 | 检查号码格式、短信中心、SIM 卡状态 |
| 513 | 无法经短信中心发送 | 联系运营商确认短信权限 |
| 515 | 远端设备无响应 | 检查目标号码是否有效、是否停机 |
特别说下512和513,它们往往不是模组的问题,而是运营商侧拒绝。高频次群发同一内容、测试环境下的连续发送,都可能触发风控。测试时不要用同一张卡在短时间内发几十条短信,很容易被运营商限制短信功能,一旦被限制,恢复起来非常麻烦。
5.4 短信显示发送成功,但对方收不到
+CMGS: 编号出现并不代表对方已经收到短信。短信要经过短信中心转发,可能延迟几分钟。如果长时间收不到:
- 目标手机信号如何?是不是开启了骚扰拦截?
- 号码是不是空号或者停机?
- 短信网关是不是对当前短信内容有过滤?
- 号码格式是不是有问题?尝试用
+86前缀再试一次。
我个人在测试时习惯拿一个备用手机号当作测试目标,不要用自己的主号,避免收不到时难以判断是业务问题还是运营商问题。
5.5 串口助手的十六进制模式和换行坑
很多人在串口助手里手工发送短信时,内容输完了不知道怎么发结束符。记住:结束符0x1A是十六进制字节,不是文本。
在串口助手里,你需要找到“HEX 发送”或“十六进制发送”的开关,切换到该模式,然后输入1A再发送。如果开着“发送新行”,许多软件会在1A后面追加\r\n,这可能导致模组误判,短信发送失败。
用 Python 代码就没有这个问题,bytes([0x1A])是精确的单字节发送。
6. 从一条短信到完整业务:接下来往哪走
短信跑通不是终点,它应该成为你整个设备功能的一部分。最后聊聊后续扩展的几个方向。
6.1 在短信内容里拼出有效信息
真实项目里的短信内容不会是干巴巴的test,而是包含业务信息。最简单的做法是在 Python 或用 Lua 脚本里做字符串拼接,把时间戳、传感器数值、设备编号拼进去。
比如:
[20250516 14:30:00] 设备ID: A78001 温度: 25.6℃ 湿度: 68% 告警: 温度超限这样的一条短信接收方一眼就能看懂。但要注意短信长度限制:普通短信单条约 70 个汉字或 160 个英文字符,超过长度会被运营商拆分成多条发送,计费也会增加。生产环境里建议在内容设计阶段就控制长度。
6.2 从手动发送到事件驱动
短信告警最有价值的场景是“设备自己决定什么时候发”。在电脑端测试时,脚本是手动触发的;到了设备端,应该用事件驱动的方式调用发送逻辑。逻辑上大概是:
循环读取传感器: 如果温度 > 阈值: 调用发送短信函数 进入冷却时间(比如5分钟内不重复发送)冷却时间非常重要,否则传感器一旦持续超阈值,短信会像洪水一样涌向手机,既浪费短信费用,又容易被运营商风控。
6.3 功耗与稳定性的提醒
短信发送的时候,模组射频部分是瞬时高功耗状态,供电设计一定要留出余量。我自己在项目里调试时遇到过几次模组在发短信时突然重启,最后发现是电源方案输出电流不够,换了一个有余量的电源模块后问题消失。另外,如果设备是长期运行的,建议尽快从“电脑端 AT 指令调试”切换到 LuatOS 或者 CSDK 方案,利用模组自带的定时器、网络管理和降功耗机制,比外部脚本裸控可靠得多。
最后分享一个我自己的习惯:每拿到一块新的蜂窝模组,我都会先发一条AT+CMGS到运营商服务号码,把这一整套 AT 流程完整走一遍。这一步看似基础,实际上把串口、SIM 卡、信号、短信中心、内容格式全部验证了一遍,之后做任何业务逻辑心里都有底。Air780E 开发板的短信链路一旦跑通,后面再往 Lua 或 C SDK 走,你会发现很多概念都是相通的。