☰
GSM模块短信收发实战:AT指令、PDU编码与工程避坑指南
2026/10/21 18:13:49 网站建设 项目流程

简介:GSM设备短信收发实验.doc 是一份面向移动通信课程实验的完整实验报告,适合通信工程、电子信息类学生参考,帮助理解 GSM 网络中 SMS 业务组成、短信收发流程及 AT 指令应用。报告从实验目的出发,系统梳理短消息业务的存储转发与传递确认机制,给出移动台收/发消息时 MSC、VLR、HLR 与 SMSC 的协同过程,并对比 Text、PDU 模式及 7-bit、UCS2 编码概念,可用于课程报告写作与考前复习。资源仅包含 1 个 doc 文档,压缩包大小 471KB,文件轻量、便于直接下载阅读;虽然体量不大,但内容结构完整,含实验目的、实验原理、网络结构图和关键流程说明。已有 80 人浏览/学习,适合作为实验报告格式参考,以及 GSM 短信收发机制和短消息数据格式的知识点梳理。

1. 是什么,能做什么:一份 GSM 短信实验文档背后的真实落地场景

做工业现场或物联网设备的人,多半碰过这类需求:设备联网条件差,现场没有 WiFi 也没有有线网络,但监控室就是想知道设备还活着没、有没有报警。GSM 设备短信收发实验这个题目,听起来像大学实验室里的一次练习,实际对应的却是一个长期存在的工程方案——用 GSM 模块加一张 SIM 卡,通过串口发 AT 指令来收发短信,把设备的告警信息用最朴素的方式送到运维人员手里。整套链路不依赖互联网,信号能覆盖的地方就能用,稳定性在工业场景里已经验证了很多年。

这篇笔记会围绕「GSM 设备短信收发」往下拆:先讲清楚 GSM 模块和 AT 指令是什么关系、短信的两种编码模式怎么选,再给出从接线到跑通收发的最小实验步骤,最后把供电、信号、串口、中文编码这几个最容易翻车的地方单独拎出来讲。内容适合两类人——一类是刚开始碰 GSM 模块、想把实验文档里的步骤变成真实可跑通代码的初学者;另一类是已经在做设备告警、想评估短信通道是不是比 4G 网络更可靠的工程师。全文不依赖云平台,不依赖第三方短信服务商,按这个思路做完,你手里就有一套可以自己控制的短信收发链路。

2. 硬件选型与 AT 指令原理:短信收发的两个基础前提

这一章解决的是「发短信之前必须懂什么」的问题。很多实验做不下去,不是因为代码写错,而是模块选错了、或者对 AT 指令的理解停留在「敲一句就完事」的层面。先把这两块地基打牢,后面参数怎么调、乱码怎么排查才有抓手。

2.1 GSM 模块的选型:串口透传是首选,别碰 USB 转接板带不动的方案

常见的 GSM 模块形态有三类:一类是工业级贴片模块,比如 SIM800C、SIM800A、A6 这类,引脚引出串口和电源,需要自己搭外围电路;一类是把模块做好了的开发板,板上带电源芯片、串口转 USB 芯片、天线接口和 SIM 卡座,直接用 USB 线连电脑就能开始实验;还有一类是集成了 GSM 功能的 DTU 设备,通常走协议透传,不适合做短信收发实验。

对于做「短信收发实验」这个目标,我建议选带串口转 USB 的开发板形态,理由很简单:实验阶段的关注点应该在 AT 指令和短信编解码上,不该卡在电路焊接和供电不稳。开发板把 SIM 卡座、天线、电源管理都做完了,你只需要用 USB 线连接电脑,在设备管理器里确认串口号,就可以开始了。

选型时要确认一个关键指标:模块支持的频段和运营商制式。国内用 GSM 900/1800 频段,市面上的模块基本都覆盖,但如果是面向海外项目或某些偏远地区,需要确认目标区域运营商使用的频段没有偏移。另外注意模块是否支持短信功能,有一些模块主打 GPRS 数据传输,短信功能被裁剪了,买回来才发现发不了。看数据手册时,找「SMS」和「AT+CMGS」这两个关键词。

2.2 AT 指令集是唯一的控制入口:指令格式、响应码与串口参数

GSM 模块对外暴露的控制接口就是串口,而串口上跑的协议是 AT 指令。AT 指令的格式有固定套路:每条指令以AT开头,\r(回车符)结尾。模块收到指令后会返回响应,常见的响应码有OK、ERROR、+CMGS: <编号>、+CMTI: "SM", <位置>等。

串口参数不是随便设的。模块出厂默认波特率通常是 115200 或 9600,数据位 8、无校验、停止位 1,也就是常说的 8N1。实验的第一步应该用串口助手或 minicom 先确认波特率,因为有些模块的波特率是上次使用后保存的配置,和出厂值不一样。发一条AT指令,如果能收到OK,说明串口参数和模块匹配上了。

下面是使用 Python 的 pyserial 库做基础通信测试的最小脚本:

import serial import time # 打开串口:把 /dev/ttyUSB0 换成自己电脑上识别到的串口号 ser = serial.Serial( port='/dev/ttyUSB0', baudrate=115200, # 与模块当前波特率保持一致 bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=1 # 读超时1秒,避免无限等待 ) # 发送 AT 测试指令:末尾必须带 \r,且不能多带 \n ser.write(b'AT\r') time.sleep(0.5) # 读取模块返回的全部数据 resp = ser.read(ser.in_waiting or 1).decode(errors='ignore') print(repr(resp)) # 正常情况输出:'AT\r\r\nOK\r\n'

逻辑说明:这段代码先打开串口,设置了 115200 波特率和 8N1 参数;然后发送AT\r,\r是 AT 指令的标准结束符;最后用ser.read()读取响应。time.sleep(0.5)的作用是等模块处理完指令并吐出响应,不能省略,否则会读到空数据。

参数说明:timeout=1控制读操作阻塞的最长时间。in_waiting表示接收缓冲区中已有的字节数,用它做读取长度可以避免一次性读出整段数据时的长度不确定性。如果返回的是ERROR,先检查波特率是否正确、模块电源是否正常、串口号是否选对。

2.3 短信编码:Text 模式和 PDU 模式到底怎么选

AT 指令里控制短信模式的指令是AT+CMGF。设为 0 是 PDU 模式,设为 1 是 Text 模式。Text 模式的好处是简单——发送英文短信时,直接写正文内容就行;发中文短信时,部分模块支持AT+CSMP和AT+CSCS设置字符集为 GSM 或 UCS2,但兼容性参差不齐。PDU 模式是把短信内容、目标号码、SMSC 服务中心号码统一编码成一串十六进制字符串,再通过AT+CMGS发送,任何字符都能覆盖,中文、Emoji 都能发,只是编码过程需要自己写代码。

实际工程里绝大多数选择 PDU 模式。原因一是中文短信在 Text 模式下经常出现乱码;原因二是 Text 模式下不同厂商模块对字符集的支持不一致,换个模块就要调一次,维护成本高。PDU 编码虽然看起来复杂,但只要实现了一个编码函数,全系列模块通用。

这里先用 Text 模式发送一条纯英文短信,验证模块的短信通路是否正常:

# 通过 minicom 或任意串口工具发送以下指令 # 每条指令以回车结束 AT+CMGF=1 # 切换 Text 模式,模块返回 OK AT+CMGS="13800138000" # 目标号码,模块返回 > Hello from GSM # 输入短信内容,以 Ctrl+Z 即 0x1A 结束 # 模块返回 +CMGS: <编号>,表示发送成功

参数说明:AT+CMGF=1是 Text 模式;AT+CMGS="号码"指定接收方;输入内容后发送0x1A作为结束符。如果返回ERROR或+CMS ERROR: <原因>,需要查看 SIM 卡是否欠费、号码是否输错、信号强度是否足够。Text 模式只适合快速验证通路,正式项目直接跳过这一步,走 PDU。

3. 跑通短信收发的实验步骤:从接线到收发成功

这一章的目标是让你在一小时内完成一次完整的短信收发验证。步骤颗粒度做到每一条指令能复现、每一个返回值能解释。实验环境建议用 Windows 下的串口助手或 Linux 下的 minicom,代码层面用 Python 完成收发逻辑,便于以后直接嵌入到自己的设备程序里。

3.1 接线、上电、注册网络:检查 SIM 卡状态和信号强度

开发板形态的模块直接用 USB 线接入电脑,在设备管理器或/dev目录下确认串口设备名。有些模块支持通过杜邦线引出 TTL 串口接 USB 转 TTL 工具,这种情况下要特别注意共地——模块的 GND 和 USB 转 TTL 的 GND 必须连在一起,否则串口通信会出现随机乱码。

上电后等待 10 秒左右,模块会执行网络注册流程。用串口发送AT+CREG?查询网络注册状态,返回+CREG: 0,1表示已注册到本地网络,返回+CREG: 0,0表示未注册,返回+CREG: 0,3表示注册被拒绝(通常是因为 SIM 卡欠费、被锁或不在服务区)。再发AT+CSQ查询信号强度,返回值格式是+CSQ: <rssi>,<ber>,第一项在 10 到 31 之间表示信号可用,小于 10 基本发不出短信。

AT+CREG? # 查询网络注册状态,返回 +CREG: 0,1 为正常 AT+CSQ # 查询信号强度,返回 +CSQ: 20,0 表示信号良好 AT+CPIN? # 查询 SIM 卡状态,返回 +CPIN: READY 为正常

信号强度这个参数容易被忽略。放在窗口边的模块可能信号满格,放到机柜里就只剩两三格。如果rssi低于 10,先考虑外接天线,而不是去怀疑代码。

3.2 发送英文短信:完整流程与返回码解析

上一步确认了 SIM 卡状态、网络注册状态和信号强度,接下来就可以真正发送一条短信。这里把整个流程用 Python 写完整,方便后续改成自己的自动化逻辑:

import serial import time def send_sms_text(port, phone_number, message): ser = serial.Serial(port, 115200, timeout=1) time.sleep(0.5) # 等待模块就绪 # 切换到 Text 模式 ser.write(b'AT+CMGF=1\r') time.sleep(0.3) ser.read(ser.in_waiting) # 清空响应 # 指定接收号码,等待模块返回 > 提示符 cmd = f'AT+CMGS="{phone_number}"\r'.encode() ser.write(cmd) time.sleep(0.5) # 发送内容并以 0x1A 结束 ser.write(message.encode() + b'\x1A') time.sleep(1) resp = ser.read(ser.in_waiting).decode(errors='ignore') ser.close() if '+CMGS:' in resp: return True, resp elif 'ERROR' in resp or '+CMS ERROR' in resp: return False, resp else: return False, 'no valid response' # 调用示例 ok, resp = send_sms_text('/dev/ttyUSB0', '13800138000', 'Hello from GSM module') print(ok, resp)

逻辑说明:这个函数做了四件事——切 Text 模式、输号码、发正文、读取响应。注意每次写指令后都用time.sleep等待模块处理,再用ser.read(ser.in_waiting)把响应清掉,避免上一次的残留数据干扰下一次判断。ser.in_waiting是 pyserial 提供的未读字节数,读取后自然清空缓冲区。

参数说明:\x1A是 Ctrl+Z,模块用这个字符识别短信正文的结束。返回的响应里+CMGS: 41中的 41 是模块内部的短信编号,不需要解析;真正要判断的是有没有+CMGS:前缀,有就说明模块已经把短信交给网络。从提交到对方收到还有几秒延迟,验证时别急着判定失败。

3.3 接收短信:轮询与主动上报两种方式

接收短信有两种方式。一种是轮询——不断发AT+CMGL="ALL"拉取全部短信;另一种是模块主动上报——通过AT+CNMI=2,1设置新短信到达时立即上报+CMTI: "SM",<位置>指针,然后用AT+CMGR=<位置>读取指定位置的短信。

轮询方式代码简单但效率低,适合实验验证;主动上报方式适合做实时告警系统。下面演示主动上报加解析的完整流程:

AT+CNMI=2,1,0,0,0 # 新短信主动上报,返回 OK # 收到新短信时的典型上报格式: # +CMTI: "SM",3 AT+CMGR=3 # 读取第3条短信 # 返回内容格式: # +CMGR: 1,"13800138000","23/05/20 10:30:00+32",Hello from GSM

上报格式里的字段含义依次是:短信状态、发送方号码、时间戳 T、正文内容。实验阶段可以直接用串口助手盯着看:当手机号发来短信时,模块会主动吐出一行+CMTI开头的数据,再发AT+CMGR就能读到内容。

3.4 中文短信必走 PDU 编码:从字节序列到 AT 指令的完整算法

中文短信在 Text 模式下乱码率高,PDU 模式下则完全可控。PDU 编码的核心有三段:SMSC 服务中心号码、目标号码、正文内容,每一段都有一套编码规则。正文部分要先把汉字转成 UCS2 编码,每两个字节为一组,转成十六进制字符串。

以发送「你好」为例,你的 UCS2 编码是4F60,好是597D,PDU 正文部分就是4F60597D。完整 PDU 串构造算法如下:

def encode_pdu(phone_number, message): # 1. 构造目标号码部分:奇数长度号码末尾补 F,然后每个两位数字互换位置 phone = phone_number if len(phone) % 2 != 0: phone += 'F' phone_pdu = '' for i in range(0, len(phone), 2): phone_pdu += phone[i+1] + phone[i] phone_len_hex = format(len(phone_number), '02X') phone_part = phone_len_hex + '91' + phone_pdu # 2. 构造正文部分:每个字符转 UCS2,再转十六进制 message_pdu = '' for ch in message: ucs2 = format(ord(ch), '04X') # ord() 返回 Unicode 码点 message_pdu += ucs2 message_len = len(message_pdu) // 4 # 中文字符数 # 3. 拼接完整 PDU:SMSC 为空时用 00;第一个 00 是 SMSC 长度 pdu = '00' + phone_part + '00' + format(message_len, '02X') + message_pdu return pdu # 调用示例 pdu = encode_pdu('13800138000', '你好') print(pdu) # 输出类似:00 0B 91 1833081030F0 00 04 4F60597D

参数说明:91表示号码类型为国际格式,0B是号码长度(11 位 × 2 因为半字节编码),00短信协议标识表示普通短信,00编码方式表示 UCS2,04是正文中文字符数。发送时候在AT+CMGS=<正文长度>后接 PDU 串,正文长度按字节数算,你好编码后是 4 个字节,所以填 04。

把 PDU 串发送出去的完整流程:

def send_sms_pdu(port, phone, message): pdu = encode_pdu(phone, message) ser = serial.Serial(port, 115200, timeout=1) time.sleep(0.5) ser.write(b'AT+CMGF=0\r') # 切到 PDU 模式 time.sleep(0.3) ser.read(ser.in_waiting) # CMGS 后面的数值是 PDU 串的字节长度减去 SMSC 长度(这里是 1) cmd = f'AT+CMGS={len(pdu) // 2 - 1}\r'.encode() ser.write(cmd) time.sleep(0.5) ser.write(pdu.encode() + b'\x1A') time.sleep(1) resp = ser.read(ser.in_waiting).decode(errors='ignore') ser.close() return '+CMGS:' in resp, resp

4. 短信收发实验避坑记录:供电、信号、编码、串口上报的五个大坑

这一章把实验中最常踩的坑集中写成排查清单。每一条都是实际调试中见过的真实问题,按「现象 → 原因 → 解决」的顺序记录,方便对照自查。

4.1 现象:AT 指令有回复,但发送后返回 +CMS ERROR: 512

原因:+CMS ERROR: 512在多数 GSM 模块中对应「SIM 卡未注册或信号不可用」。表面看模块上电了、AT 也 OK 了,但实际网络注册没有完成。常见触发条件是 SIM 卡欠费停机、SIM 卡没有插到位、天线没有接好导致信号强度低于最低门限。有些模块做了省电模式,休眠状态下网络注册会延迟,一上来就发短信同样会报这个错误。

解决:先发AT+CREG?确认注册状态,再发AT+CSQ确认 rssi 不低于 10。如果 rssi 低,检查天线连接和摆放位置。SIM 卡欠费问题只能通过运营商渠道确认,实验中经常遇到的是套了转接卡或者插卡不到位,重新插拔 SIM 卡后恢复。

4.2 现象:发短信过程中模块突然重启,或者串口直接无响应

原因:供电电流不足。GSM 模块在发射瞬间会拉高电流,峰值能达到 2A。如果用 USB 口直接供电,USB 口的电流上限通常只有 500mA,模块在发射瞬间电压跌落低于 3.4V 就会重启或死机。开发板虽然带电源芯片,但板载电源设计余量不够时同样会出现这个问题。

解决:改用外部 5V/2A 以上的适配器供电,或者用 3.7V/1C 放电能力以上的锂电池供电。检查电源线线径,细长的杜邦线在电流大时压降明显,尽量用短线或加粗电源线。另一个容易被忽略的点是 USB 转串口芯片和 GSM 模块共用一个电源时,模块发射瞬间会把电压拉低,导致串口芯片也掉电,表现为 USB 设备消失——这种情况必须分开供电。

4.3 现象:Text 模式发中文,接收方收到乱码

原因:Text 模式下模块使用 GSM 默认字符集,中文不属于该字符集。某些模块支持通过AT+CSCS="UCS2"切换字符集,但 UCS2 模式下的 Text 模式指令格式变了——号码和内容都要转成十六进制,处理不当反而更乱。

解决:工程上不要用 Text 模式发中文,直接走 PDU 模式。PDU 模式不依赖模块字符集设置,正文部分自己编码成 UCS2 十六进制,模块只是透传字节。如果一定要在 Text 模式下发中文,先发AT+CSCS="UCS2",然后把号码和内容都用十六进制字符串表示,但实验验证后发现这种方式在不同模块上兼容性差异较大,不做推荐。

4.4 现象:新短信上报有时能收到,有时丢失,轮询又太笨

原因:AT+CNMI=2,1设置后,模块在收到新短信时主动上报+CMTI。但问题往往出在串口数据的并发上——如果程序正在处理其他指令,模块上报的数据会混在指令响应里,如果程序只按固定长度读取,就会漏掉。

解决:接收逻辑要按「持续读取直到空闲」的模型设计。用一个缓冲区累积串口数据,每次读到数据后启动定时器,定时器超时视为一段完整数据,再对完整数据做关键字解析。不要用固定字节数读取,AT 指令的响应长度本来就不固定。下面是基于超时模型的解析逻辑:

import serial import time ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=0.1) buffer = b'' last_received = time.time() while True: data = ser.read(ser.in_waiting or 1) if data: buffer += data last_received = time.time() else: # 100ms 内没有新数据,认为当前缓冲是一段完整数据 if buffer and time.time() - last_received > 0.1: text = buffer.decode(errors='ignore') if '+CMTI:' in text: # 提取位置号,然后发 AT+CMGR 读取 sidx = text.rfind('"SM",') + 5 pos = text[sidx:sidx+3].strip('"\r\n ') ser.write(f'AT+CMGR={pos}\r'.encode()) buffer = b''

4.5 现象:PDU 发送时长度填错,模块一直返回 ERROR

原因:AT+CMGS=后面的长度参数不是「PDU 十六进制字符串的字符数」,而是「PDU 字节数减去 SMSC 长度字段所占的 1 个字节」。SMSC 为空时 PDU 串以00开头,这个00本身是 SMSC 长度,不算在 CMGS 长度里。很多示例代码直接用了整串 PDU 的长度减 1,没有考虑 SMSC 填了内容的情况。

解决:按上一章给出的公式len(pdu) // 2 - 1计算。如果 SMSC 要填具体号码,协议格式变成<SMSC长度><SMSC号码><其余部分>,此时 CMGS 长度参数是(len(pdu) // 2) - (SMSC长度字段的值 + 1)。我在实验中统一把 SMSC 留空,即 PDU 以00开头,这样的 PDU 串可以在任何运营商的网络上使用,模块会自动使用 SIM 卡内置的 SMSC 地址。

5. 进阶:把实验脚本改成可控的短信收发小服务

实验跑通了收发,离真正可用的功能还有差距——缺少重试、状态管理和信号监控。这章给出三个可以继续深挖的方向。

5.1 从一次发送到可靠发送:短信状态机与重试逻辑

实际使用中短信发送的失败率并不低,单纯+CMGS:判断只能确认模块提交给了网络,不能确认对方收到了。一个可用的发送流程应该是状态机:初始状态、等待发送、发送中、等待确认。每次发送失败后延时 10 秒重试,最多重试 3 次。如果模块返回+CMS ERROR,需要区分错误码 512(无信号)和 513(SIM 卡故障),前者等信号恢复后重试,后者直接报错人工介入。

5.2 信号强度巡检:用 CSQ 做发送前置条件检查

把AT+CSQ返回的 RSSI 值和AT+CREG?的注册状态组合成门禁条件。RSSI 低于 10 不发送,直接排队;注册状态为 0 或 2 时不发送。这套逻辑在模块移动场景、环境变化场景下能显著降低无谓的发送失败。参数值可以按项目实际调整——室内固定安装可以把门限设为 15,移动巡检设备需要放宽到 8。

5.3 新短信自动应答:一个简单的告警确认闭环

在 4.4 节的主动上报基础上,做一个自动应答:收到特定指令短信后,模块自动回一条状态确认短信。这在无人值守设备上很有用——运维在监控端发「STATUS」,设备收到后回「OK,uptime 123h」。实现上只需要在解析到正文内容后,把关键词匹配和 PDU 发送连起来。

这几步做完,实验性质的东西就有了服务化的雏形。我自己最初只在调试台上跑通收发包就收工了,后来真上了室外环境才意识到信号变化和模块重启是常态,重试和门禁条件比发送本身更值得反复打磨。回头再看这份「实验」,它的价值不在那几条 AT 指令,而在整套可靠性设计。把这个方向做完,你就理解了设备与短信通道之间真正需要磨合的环节。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询