资讯动态

串口短信猫开发实战:AT指令、PDU编码与批量收发避坑指南

发布时间:2026/10/15 16:09:42 来源:尧图企业网站定制
简介串口短信猫.rar 面向需要在工业自动化、远程监控与报警系统中集成短信收发能力的开发者尤其适合刚接触 GSM 模块、希望快速完成串口通信验证的编程人员。压缩包整体约 17.96MB内含配置说明、测试源码、调试方法与测试程序等资料源码部分覆盖 C、Python、Java 等多种语言示例演示串口初始化、AT 命令发送、设备响应读取及短信处理逻辑可帮助读者对照搭建自己的收发系统。资源围绕 RS232 或 USB 串口连接展开涉及波特率 9600、数据位 8、停止位 1、无校验等常见参数设置并通过检查网络状态、读取 SIM 卡信息等 AT 命令完成基本功能验证。常见问题文档汇总通信异常与命令执行失败等故障现象及解决思路测试程序则支持模拟收发便于提前发现潜在问题。目前已有 149 人学习适合作为串口短信猫开发与调试的入门参考。1. 串口短信猫.rar 里到底装了什么从 AT 指令到批量收发的完整链路很多人第一次拿到「串口短信猫.rar」这种压缩包第一反应是解压找 exe双击然后发现要么报错、要么界面简陋得让人怀疑人生。我当年也这样折腾一下午没发出去一条短信最后才发现问题根本不在软件而在于我没搞懂串口短信猫的本质——它就是一个通过 RS232 或 USB 转串口芯片挂到电脑上的 GSM 模块所有收发短信的动作底层都是往串口写 AT 指令。压缩包里通常包含三类东西串口调试工具或自研上位机、AT 指令测试脚本、以及一份可能写得不太全的说明文档。真正值钱的不是那个界面而是它背后那套「串口参数 AT 指令 编码格式」的组合拳。这篇文章面向的是需要把短信收发能力集成进自己系统的开发者比如做设备告警、验证码下发、批量通知的场景。我会把串口短信猫从硬件识别、串口配置、AT 指令交互、PDU 编码、批量任务调度到常见翻车点按我实际落地过的路径讲一遍。你不需要那个 rar 里的具体软件照着这套逻辑用任意串口助手加几十行代码就能跑通。读完你至少能判断这东西能不能接进你的业务、要调哪些参数、坑会出在哪。2. 串口短信猫的硬件识别与串口参数配置先让电脑认得出它2.1 串口短信猫的三种常见形态与驱动差异市面上能见到的串口短信猫物理形态基本逃不出三类。第一类是纯串口版DB9 接口直接插主板原生 COM 口工控机上最常见优点是稳定、不挑系统缺点是现在笔记本基本没这口。第二类是 USB 转串口版内部用 CH340、CP2102、PL2303 这类芯片插上后系统多出一个虚拟 COM 口这是绝大多数人手里的形态。第三类是 USB 直接枚举成 Modem 设备走的是 CDC ACM 或者厂商私有驱动设备管理器里显示为调制解调器而不是 COM 口。这三类的区别直接决定你后面怎么打开端口。纯串口和 USB 转串口你拿到的是一个 COM 号用串口库直接 open 就行。USB Modem 形态则可能需要走系统 API 或者特定驱动暴露的接口。我一般会先在设备管理器里确认它到底以什么身份出现再决定用哪套代码。如果是 CH340 这类芯片Windows 10 以后基本免驱Linux 下内核也自带 ch341 驱动插上就能看到 /dev/ttyUSB0。提示同一台机器插多个 USB 转串口设备时COM 号可能每次开机都变。生产环境建议用芯片唯一 ID 在 udev 或系统层面绑定固定名称别在代码里写死 COM3。2.2 用命令行和串口助手确认端口可用在写任何业务代码之前先用最笨的办法确认硬件是活的。Windows 下可以用模式命令看端口列表Linux 下直接列 tty 设备。下面这段 bash 在 Linux 和 macOS 上都能跑用来观察插拔前后设备节点的变化。# 插拔前先记录当前串口设备 ls /dev/ttyUSB* /dev/ttyACM* 2/dev/null # 插入短信猫后再执行一次对比多出来的节点 ls /dev/ttyUSB* /dev/ttyACM* 2/dev/null # 查看该设备的驱动和 VID/PID 信息 udevadm info -a -n /dev/ttyUSB0 | grep -E idVendor|idProduct|driver这段命令的逻辑是先建立基线再对比增量最后确认驱动绑定情况。参数上ttyUSB 通常是 USB 转串口芯片ttyACM 是 CDC ACM 类设备。如果插上后什么都没多出来先换线、换口再查驱动别急着怀疑模块坏了。确认端口存在后用串口助手以 9600、8N1、无流控打开发一条AT正常应该回OK。这一步是整个链路的地基地基没打牢后面所有代码都是白写。2.3 串口参数里最容易被忽略的三个设置串口参数看起来就波特率、数据位、停止位、校验位、流控这几项但实际踩坑最多的往往是下面三个。第一是波特率短信猫常见的有 9600、115200、57600部分模块支持自动波特率但自动协商经常在冷启动时失败我一般手动固定成模块手册标称值。第二是流控很多模块默认要硬件流控 RTS/CTS你代码里没开发长指令就会丢字符表现为偶尔成功偶尔失败非常玄学。第三是换行符AT 指令结尾必须是\r有些模块还要求\r\n只发\n或者不发换行模块根本不搭理你。我习惯在代码里把这三个参数做成可配置项而不是写死。因为不同批次、不同厂家的模块这三项都可能不一样。下面是一个 Python 串口初始化的片段用 pyserial把关键参数都暴露出来。import serial # 串口短信猫初始化参数按模块手册调整 ser serial.Serial( port/dev/ttyUSB0, # 端口Windows 下换成 COM3 这类 baudrate9600, # 波特率常见 9600/115200 bytesizeserial.EIGHTBITS, parityserial.PARITY_NONE, stopbitsserial.STOPBITS_ONE, timeout2, # 读超时避免 readline 永久阻塞 rtsctsFalse, # 硬件流控模块要求时才开 dsrdtrFalse ) # 发一条最基本的 AT确认链路 ser.write(bAT\r) resp ser.read(64) print(resp) # 期望看到 b\r\nOK\r\n逻辑说明timeout 必须设否则模块不回数据时你的线程就挂死在那里。rtscts 默认关但如果发现发长指令丢字符第一个要试的就是把它打开。write 的内容结尾必须是\r这是 AT 指令的硬性约定。read 的长度给 64 字节是经验值够读回 OK 或错误码正式代码里应该按行读并做超时重试。3. 用 AT 指令跑通短信收发从文本模式到 PDU 模式3.1 文本模式发短信的最小指令序列短信猫刚上手建议先用文本模式Text Mode发一条纯英文短信把链路跑通。文本模式的指令序列很短但每一步的返回都要检查不能发完就不管。下面这段 Python 演示了完整流程包括设置短信中心号码、设置文本模式、发短信。import serial import time ser serial.Serial(/dev/ttyUSB0, 9600, timeout2) def send_at(cmd, wait1): ser.write((cmd \r).encode()) time.sleep(wait) return ser.read(256).decode(errorsignore) # 1. 确认模块在线 print(send_at(AT)) # 2. 查询短信中心号码确认 SIM 卡已注册 print(send_at(ATCSCA?)) # 3. 设置为文本模式 print(send_at(ATCMGF1)) # 4. 指定目标号码等待模块返回 提示 print(send_at(ATCMGS13800138000, wait1)) # 5. 发送内容结尾用 CtrlZ (0x1A) 表示结束 ser.write(bHello from serial modem\x1a) time.sleep(3) print(ser.read(256).decode(errorsignore))逻辑说明ATCMGF1切文本模式ATCMGS进入发送状态模块回后你才能写正文正文以0x1A结束。参数上目标号码要用引号包起来国内号码直接写 11 位。如果第 4 步没等到通常是短信中心号码没设对或者 SIM 卡没注册上网络。文本模式只适合纯英文和数字中文会乱码这是它最大的局限。3.2 PDU 模式为什么绕不开中文短信的编码真相文本模式发中文收到的是一串问号或者乱码因为文本模式默认走 GSM 7bit 编码中文根本不在这个字符集里。要发中文必须切 PDU 模式ATCMGF0自己把短信内容编码成 PDU 串。PDU 的本质是把短信中心号码、目标号码、编码方式、时间戳、正文全部打包成十六进制字符串。中文用 UCS2 编码每个字符两字节。很多人卡在 PDU 编码上是因为网上抄来的代码只处理了英文中文一进去就错。下面这段 Python 用现成的编码逻辑把中文短信转成 PDU重点是 UCS2 部分。def phone_to_pdu(phone): # 目标号码转 PDU 半字节交换格式 pdu 91 %02X % len(phone) for i in range(0, len(phone), 2): if i 1 len(phone): pdu phone[i1] phone[i] else: pdu F phone[i] return pdu def text_to_ucs2(text): # 中文按 UCS2 编码每字符两字节 return text.encode(utf-16-be).hex().upper() def build_pdu(phone, text, sca8613800100500): sca_pdu 00 # 用 SIM 卡内短信中心这里置 00 first 11 # 11 表示有状态报告UCS2 编码 tp_len len(text_to_ucs2(text)) // 2 pdu sca_pdu first 00 phone_to_pdu(phone) 00 \ %02X % tp_len text_to_ucs2(text) return pdu pdu_str build_pdu(13800138000, 设备告警温度过高) print(pdu_str) # 发送时用 ATCMGS长度长度是 PDU 串去掉短信中心部分后的字节数逻辑说明phone_to_pdu做的是半字节交换这是 PDU 格式的硬性要求。text_to_ucs2用 utf-16-be 得到大端 UCS2正好对应 PDU 里的中文编码。first字节里的11表示同时要状态报告和用 UCS2如果你不需要状态报告改成01。发送时ATCMGS后面的长度参数是 PDU 串总长度减去短信中心号码部分的字节数算错这个长度模块直接回 ERROR。这块是血泪经验长度差一个字节都不行。3.3 收短信主动查询和自动上报两种模式收短信有两种玩法。第一种是主动查询用ATCMGLALL列出所有短信适合定时轮询的场景。第二种是自动上报用ATCNMI配置新短信到达时直接通过串口推给你适合实时性要求高的场景。我一般先用主动查询把逻辑跑通再切自动上报。主动查询的返回是 PDU 串需要自己解析。解析比编码更烦因为要处理短信中心号码长度、时间戳、编码标志位。下面这段代码演示怎么从ATCMGL的返回里提取正文。def parse_pdu(pdu): # 简化解析跳过短信中心定位正文 idx 0 sca_len int(pdu[idx:idx2], 16) idx 2 sca_len * 2 idx 2 # 跳过 first octet phone_len int(pdu[idx:idx2], 16) idx 2 phone_bytes (phone_len 1) // 2 * 2 idx phone_bytes idx 2 # 跳过协议标识 dcs int(pdu[idx:idx2], 16) idx 2 idx 14 # 跳过时间戳 7 字节 text_len int(pdu[idx:idx2], 16) idx 2 body pdu[idx:idx text_len * 2] if dcs 0x08: # UCS2 return bytes.fromhex(body).decode(utf-16-be) return bytes.fromhex(body).decode(ascii, errorsignore) # 假设从 ATCMGL 返回里拿到一行 PDU print(parse_pdu(0791...))逻辑说明解析的关键是每一步的偏移量都要算准短信中心长度、号码长度、时间戳都是变长的。dcs 字段决定编码方式0x08 是 UCS20x00 是 GSM 7bit。这段代码做了简化实际生产里要处理长短信拼接UDH那是另一个坑。自动上报模式用ATCNMI2,1,0,0,0新短信会以CMTI形式通知你你再根据索引去读比轮询省资源。4. 批量短信任务的调度与限速别把模块当服务器用4.1 串口短信猫的吞吐边界在哪里很多人以为短信猫能像 HTTP 接口一样每秒发几十条实际完全不是。一条短信从发指令到模块返回CMGS确认正常要 3 到 8 秒取决于网络注册状态和短信中心响应速度。也就是说单模块单卡的理论上限就是每分钟十几条。你硬压任务进去模块的发送队列会溢出表现为大量 ERROR 或者干脆不响应。我实测过的边界是连续发送时每条之间至少留 5 秒间隔模块能稳定工作几小时不出错。如果间隔压到 2 秒前几十条可能正常后面就开始随机失败。这个数字不是绝对的和模块型号、信号强度、运营商都有关但量级上不会差太多。所以批量任务的第一原则是限速第二原则是失败重试第三原则是别指望单模块扛高并发。4.2 用队列加令牌桶做发送限速限速的实现方式我一般用队列加令牌桶。任务进队列消费者按固定速率取令牌拿到令牌才发。下面这段 Python 用简单的 sleep 加队列模拟生产里可以换成 Redis 队列加定时任务。import queue import threading import time task_q queue.Queue() RATE_LIMIT 5 # 每条间隔 5 秒 def worker(ser): while True: phone, text task_q.get() try: # 这里调用前面封装好的发送函数 send_sms(ser, phone, text) print(fsent to {phone}) except Exception as e: print(ffailed {phone}: {e}) task_q.put((phone, text)) # 简单重试生产要加次数上限 finally: task_q.task_done() time.sleep(RATE_LIMIT) # 启动消费者 t threading.Thread(targetworker, args(ser,), daemonTrue) t.start() # 生产任务 for i in range(100): task_q.put((1380013800%d % i, 告警通知))逻辑说明RATE_LIMIT 是核心参数按模块实际能力调别贪快。重试逻辑这里写得简单生产环境必须加最大重试次数和失败落库否则一条坏号码会无限循环。队列的好处是解耦你的业务系统只管往里塞发送节奏由消费者控制。如果任务量真的很大正确做法是加模块加卡而不是压榨单模块。4.3 多模块并行时的端口隔离与任务分配当你有多个短信猫时任务分配和端口隔离就变得重要。每个模块独立一个串口、独立一个消费者线程任务按模块健康状态分发。健康状态可以用定期发AT探测连续失败就摘除该模块。下面是一个简单的模块池管理思路。class ModemPool: def __init__(self, ports): self.modems [] for p in ports: ser serial.Serial(p, 9600, timeout2) self.modems.append({ser: ser, healthy: True, port: p}) def get_healthy(self): # 返回当前健康模块轮询分配 for m in self.modems: if m[healthy]: yield m def health_check(self): for m in self.modems: try: m[ser].write(bAT\r) resp m[ser].read(32) m[healthy] bOK in resp except Exception: m[healthy] False逻辑说明健康检查要定期跑比如每 30 秒一次别太频繁否则干扰正常发送。get_healthy 用生成器做轮询简单但够用。生产里还要考虑模块重启、SIM 卡欠费、信号丢失这些情况健康检查只能覆盖串口层面的问题网络层面的要靠ATCREG?查注册状态。5. 串口短信猫避坑清单那些让我加班到凌晨的故障5.1 发中文乱码编码标志位和长度算错现象英文短信正常中文发出去对方收到乱码或者半截。原因通常是两个一是ATCMGF没切到 0还在文本模式二是 PDU 里 first octet 的编码标志没设成 UCS2或者正文长度按字符数算而不是字节数。解决确认ATCMGF0first octet 用11或01长度用len(text.encode(utf-16-be)) // 2。这个坑我踩过不止一次每次都是长度差一两个字节。5.2 模块偶尔不响应流控和电源是元凶现象发指令十次里有一两次没返回重启又好了。原因多半是硬件流控没开模块缓冲区满了丢字符或者 USB 供电不足模块在发射瞬间电流拉高导致掉线。解决先开 rtscts 试不行就换带独立供电的 USB Hub再不行用万用表量模块供电电压发射时别低于 4.5V。这个问题的玄学程度很高因为表现随机但根因基本就这两个。5.3 短信堆积在 SIM 卡存储满导致新短信丢失现象收短信收着收着就收不到了查ATCPMS?发现 SIM 卡存储满了。原因是你读了短信但没删模块默认存在 SIM 卡里容量通常只有几十条。解决每次读完短信后执行ATCMGD索引删除或者配置ATCPMS把存储切到模块内存。我一般读完就删避免堆积。这个坑在长时间运行的系统里特别常见前期测试量小发现不了。5.4 目标号码格式错误86 和本地号码混用现象发送返回 ERROR或者对方收不到。原因号码格式不统一有的带 86有的不带PDU 编码时长度算错。解决统一成不带国家码的本地格式或者统一带 86 并在编码时正确处理。PDU 里号码长度字段是号码位数不是字节数带 86 时位数要算对。这个坑的隐蔽性在于同一个号码有时能发有时不能其实是格式在不同代码路径里不一致。5.5 串口被占用多个进程抢同一个端口现象程序启动报 Permission denied 或者串口打开失败。原因上一个进程没正常退出端口没释放或者你开了两个程序同时读同一个口。解决确保单进程独占退出时显式 closeLinux 下可以用lsof /dev/ttyUSB0查占用。Windows 下更麻烦有时候要拔插设备才能释放。生产环境建议用守护进程管理别手动起多个实例。6. 把短信猫接进业务系统的进阶做法状态机与可观测性前面讲的都是单次收发真正上线要解决的是「长期稳定运行」和「出问题能查」。我的做法是给短信猫封装一个状态机把空闲、发送中、等待确认、失败重试这几个状态显式管理起来再配上日志和指标。状态机的核心是每个状态有超时超时就回退到空闲并记录避免卡死。from enum import Enum class State(Enum): IDLE 0 SENDING 1 WAIT_ACK 2 RETRY 3 class SmsModem: def __init__(self, ser): self.ser ser self.state State.IDLE self.retry 0 def tick(self): if self.state State.IDLE: # 取任务切 SENDING pass elif self.state State.SENDING: # 写 ATCMGS切 WAIT_ACK设超时 pass elif self.state State.WAIT_ACK: # 读返回成功回 IDLE失败切 RETRY pass elif self.state State.RETRY: if self.retry 3: self.retry 1 self.state State.SENDING else: self.state State.IDLE self.retry 0逻辑说明状态机的好处是每个环节都有明确的超时和转移条件不会因为某一步没返回就永久挂起。retry 上限设 3 次超过就放弃并记录避免坏任务拖垮整个队列。tick 方法由定时器驱动比如每 500 毫秒跑一次。可观测性方面我至少会记录四类信息每条短信的发送时间、目标号码、状态、耗时。这些落到日志或者时序数据库里出问题时能快速定位是模块问题、网络问题还是号码问题。下面这张表是我实际用的关键指标你可以按需增减。指标名含义正常范围异常时先查send_duration单条发送耗时3 到 8 秒超过 15 秒查信号注册success_rate发送成功率95% 以上低于 90% 查 SIM 卡和余额at_timeout_countAT 超时次数接近 0升高查流控和供电sim_storage_usedSIM 卡短信占用低于 80%满了就删旧短信最后说一个我自己的习惯任何短信猫项目上线前先跑 24 小时压测用真实号码发几百条观察成功率曲线和模块温度。压测能暴露 90% 的坑剩下的 10% 靠日志和状态机兜底。这套东西不复杂但需要耐心调参数。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑