ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FM150 5G模组AT指令环境搭建与联网自检实战

FM150 5G模组AT指令环境搭建与联网自检实战

简介:面向5G模组开发与调试人员的广和通FG150 & FM150 Series AT命令手册,聚焦5G NR、LTE、WCDMA、TD-SCDMA与GSM等多制式模组的指令查询和功能配置,适合物联网终端、工业自动化及智能交通场景下的驱动开发、联调测试与故障排查参考。包内为1个PDF文档,约1.79MB,正文共241页,版本V3.6.3,适用FG150-AE、FM150-NA、FM150-AE等型号;内容按网络设置、模组状态、数据传输和系统命令分类,收录AT+CGMI、AT+CGMM、AT+CGMR、AT+CSQ、AT+CGATT等常用指令示例,并保留版本变更记录与参数说明,目录层级清晰,便于按功能检索。已有589人学习下载。读者可借此系统掌握模组厂商信息查询、固件版本读取、信号强度判断和网络附着状态检查等基础操作,也能沿目录定位APN配置、低功耗模式、小区锁定等进阶命令的参数与返回码,为实际项目中的指令调试、联调记录、问题定位及团队技术培训提供可直接查阅的原始依据。

1. 一块插上没反应的 FM150:AT 指令环境要先装对什么

板子焊好、USB 线插上、串口终端一开,屏幕上跳出来的是一串问号,切到十六进制看全是 0x00。这是很多人第一次接 FM150 模组的开局。FM150 AT 指令手册 241 页,V3.6.3,2020-02-20 定版,适用 FG150-AE、FM150-NA、FM150-AE 三款型号,命令从 +CGMI 这种身份查询一路排到 +GTCELLLOCK 这种小区锁定。手册本身不讲主机侧装什么、串口怎么配、哪条命令在你的固件上会回 ERROR。装不对最直接的表现是:端口能打开,发什么都不回;或者回一堆 OK,但 +CGATT? 永远是 0。这套环境适合手上有模组和载板、准备做 5G 数据链路联调的开发者,也适合接手别人半成品工程、要把 AT 通道重新捋一遍的人。

2. 版本与环境对齐:V3.6.3 手册该配哪套工具链

装 AT 环境最容易翻车的地方不在命令本身,在于手册、固件、主机工具三方版本没有对齐。手册封面写 V3.6.3,不代表你手里那颗模组跑的就是 V3.6.3 对应的固件;主机侧串口工具默认参数不对,也照样能打开端口但一个字符都收不到。这一章先把三样东西摆到桌面上对齐,再动手装。

2.1 型号先对:手册适用范围只有这三款

手册开头的 Applicability 表只有三行,FG150-AE Series、FM150-NA Series、FM150-AE Series。这三个型号共享同一套命令集文档,但射频指标、支持的频段、部分私有命令的默认取值并不一致。拿 FM150-NA 的模组去套 AE 版本的参数默认值,最常见的后果是某些 +GT 开头的私有命令回 ERROR 或者行为对不上。

判断自己手上是哪一款,别只看包装标签,两个办法最稳:

# 方法一:看模组屏蔽罩丝印,型号通常直接印在上面 # 方法二:上电后读模组自报的型号,比标签可信 printf 'AT+CGMM\r' > /dev/ttyUSB2 printf 'AT+CGMI\r' > /dev/ttyUSB2

+CGMM返回 Model ID,+CGMI返回 Manufacturer ID,手册 3.1.1 到 3.1.4 明确列了这两组命令的长短两种写法(+CGMI/+GMI、+CGMM/+GMM),返回内容一致,短写法老固件兼容性更好。参数上没有什么可调的,注意发送结尾用\r,有些固件对\r\n也能识别,但统一用\r最不容易出问题。

型号确认完之后再确认固件版本,用AT+CGMR读 Revision。这一步的目的是:手册 V3.6.3 的变更记录里,很多命令是分批次加进去的,如果你的固件版本比手册老,手册里写着的一些命令在设备上根本不存在。这不是手册错,是版本没对齐。

2.2 主机侧软件清单:四样东西,别多装

AT 调试不需要 IDE,也不需要厂商上位机,装多了反而互相抢串口。我一般固定这四样:

组件用途版本建议备注
USB 串口驱动让系统认到模组的 CDC-ACM 口随系统内核或厂商提供Linux 内核 5.x 以上基本自带
picocom 或 minicom交互式发命令、看回显picocom 3.1+ / minicom 2.7+二选一,别同时开同一个口
Python 3 + pyserial脚本化批量验证Python 3.8+ / pyserial 3.5+最后一章的自检脚本要用
手册电子版查命令语法和返回码V3.6.3变更记录那一节要重点看

装 picocom 和 pyserial 的命令在 Debian/Ubuntu 系上就两条:

sudo apt update sudo apt install -y picocom python3-pip pip3 install pyserial

picocom轻量,退出快捷键是Ctrl+A再Ctrl+X,比 minicom 少一层菜单。pyserial是为后面写自检脚本准备的,人工敲十几条命令容易漏,脚本跑一遍最省事。如果你的机器是离线环境,这两个包提前下好 deb 或 whl,别到现场才发现装不上。

2.3 串口参数:115200 8N1,流控必须关

模组 AT 口的默认参数基本是 115200、8 数据位、无校验、1 停止位、无硬件流控。这里最容易踩的坑是流控:有些串口工具默认打开 RTS/CTS,而载板上 RTS/CTS 根本没引出来或者没接,结果是端口能打开、能发数据、但收不到任何返回,看起来像模组死机。

用 stty 手动设一遍,比在工具里点选更可控:

# 关闭流控、设成 115200 8N1、关闭回显转换 stty -F /dev/ttyUSB2 115200 cs8 -cstopb -parenb -crtscts raw -echo # 确认设置生效 stty -F /dev/ttyUSB2 -a | grep -E 'speed|cs8|crtscts'

-crtscts是关硬件流控的关键项,raw关掉终端的行缓冲和特殊字符处理,避免把\r吃掉。-echo关的是主机侧回显,模组自己的回显由ATE命令控制,两者别搞混。改完 stty 之后如果还是不通,先换一根确认能传数据的 USB 线,劣质线只通电不传数据的情况不少见。

2.4 端口枚举:一个模组为什么冒出好几个 ttyUSB

USB 复合设备上电后通常会枚举出多个串口节点,/dev/ttyUSB0到/dev/ttyUSB3都可能出现,分别对应 AT 命令口、诊断日志口、数据口、GPS/NMEA 口。插拔顺序不同,编号会变,写死在脚本里迟早出事。

查一下每个口对应什么:

# 看插入时的内核日志,能看出每个口注册的顺序和用途 dmesg | grep -i -E 'ttyUSB|cdc_acm' | tail -20 # 用 udevadm 看端口的物理路径,插不同 USB 口路径会变 udevadm info -q path -n /dev/ttyUSB2

稳妥做法是写一条 udev 规则把 AT 口固定成/dev/ttyFM150,脚本和工具都指向这个名字:

# /etc/udev/rules.d/99-fm150.rules # 依据 USB 设备的 vid/pid 与接口号绑定,换 USB 口也不会变名 SUBSYSTEM=="tty", ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0104", ATTRS{bInterfaceNumber}=="02", SYMLINK+="ttyFM150", MODE="0666"

idVendor/idProduct用lsusb查你实际设备的取值,bInterfaceNumber对应 AT 口所在接口号,不同固件枚举顺序会有差异,先用dmesg确认。SYMLINK+="ttyFM150"建软链,MODE="0666"省掉每次 sudo 的麻烦。规则写完sudo udevadm control --reload && sudo udevadm trigger,重新插拔一次生效。

3. 打通第一条 AT 通道:串口工具安装与最小回环验证

端口和参数都对齐之后,这一步的目标很明确:让AT两个字回车之后返回OK。听起来简单,实际上这一步卡住的人最多,因为它同时涉及权限、端口选择、流控、回显四件事,任何一件不对都表现为「没反应」。

3.1 Linux 侧装 picocom 与串口权限

普通用户默认不在 dialout 组里,直接开串口会 Permission denied。加组这一步做完必须重新登录才生效,很多人加完组发现还是不行,就是因为终端会话没重开。

# 把当前用户加入 dialout 组,获得串口读写权限 sudo usermod -aG dialout $USER # 重新登录后确认组已生效 groups | grep dialout # 用 picocom 打开 AT 口,参数一次写全 picocom -b 115200 -d 8 -p n -s 1 --flow n /dev/ttyFM150

-b 115200波特率,-d 8数据位,-p n无校验,-s 1一位停止位,--flow n显式关流控。这套参数和上一章的 stty 是等价的两条路径,picocom 会在打开时自己设一遍,所以更推荐直接用 picocom 而不是裸 stty。

进去之后敲AT回车。正常返回:

AT OK

注意这里AT是模组回显出来的(默认ATE1开着),OK是结果码。如果你的终端里看不到自己敲的字符,说明回显被关了或者终端本地回显没开;如果连OK都没有,往下看第 3.3 节和第五章。

3.2 Windows 侧端口确认与串口工具参数

Windows 上没有 picocom,用设备管理器看 COM 号,再用任意串口助手。参数页三项必须对:波特率 115200、数据位 8、停止位 1、校验 None、流控 None。流控这一项很多助手默认是「硬件」,不改就收不到数据。

发命令时注意两点:一是结尾要勾选「发送新行」或者手动加 CR,二是十六进制和文本模式别搞混。有的助手在十六进制模式下会自动补\r\n,而模组对\r\n\r\n这种多余空行有时会返回ERROR,看起来像命令语法错。

确认 COM 号别靠猜,插拔一次看设备管理器里哪个口消失又出现。如果设备管理器里出现的是带黄色感叹号的未知设备,先装驱动,别急着怀疑模组。

3.3 回显与结果码:ATE、ATQ、ATV 三个开关

手册 4.1 节的 V、Q、E 三条命令,决定你屏幕上看到的东西。这三条命令看起来是格式问题,实际排错时非常关键——回显关掉之后,脚本解析返回会简单很多。

AT // 基础连通性测试,回 OK 说明通道通了 ATE0 // 关闭命令回显,之后敲命令不再重复显示 ATE1 // 重新打开回显,交互调试时用 ATQ0 // 打开结果码(默认),命令执行完回 OK/ERROR ATQ1 // 关闭结果码,只回数据不回 OK,脚本模式慎用 ATV1 // 结果码用文本形式 OK/ERROR(默认) ATV0 // 结果码用数字形式 0/4,老上位机兼容用

ATE0是脚本化调试第一个该发的命令,关了回显之后每条命令的返回就是「数据 + 结果码」,正则匹配起来干净。ATQ1要谨慎,关了结果码之后你无法区分「命令成功但没数据」和「命令失败」,排错阶段不建议开。ATV0只在对端程序写死了数字结果码时用,日常调试保持ATV1。

这三条命令都是立即生效但不跨重启的(具体是否持久化取决于固件),重启模组后回到默认值。如果你的脚本依赖ATE0,每次连接后都要重发一次,别指望它记住。

3.4 用 +CLAC 建能力基线,别信记忆

手册 3.1.14 里有一条+CLAC,列出当前固件支持的全部 AT 命令。这条命令的价值在于:它返回的是你这颗模组真实支持的命令列表,不是手册上写的列表。两者对不上的时候,以+CLAC为准。

# 把支持的命令列表完整抓下来存成基线文件 printf 'AT+CLAC\r' > /dev/ttyFM150 sleep 3 cat /dev/ttyFM150 > fm150_cmdlist_baseline.txt # 查某条私有命令在不在支持列表里 grep -i 'GTCELLLOCK' fm150_cmdlist_baseline.txt grep -i 'GTAUTODHCP' fm150_cmdlist_baseline.txt

+CLAC的返回是一大段多行文本,直接cat串口会截断,所以先重定向到文件再处理。sleep 3是给模组吐完整个列表留时间,列表长的固件可能要更久,可以按需加到 5 秒。

这一步的意义是建立基线:以后遇到「手册里明明有这条命令,发下去却回 ERROR」,先grep一下基线文件。如果基线里没有,说明这条命令不在你的固件里,不是语法问题;如果基线里有但发下去还是 ERROR,那才是参数或状态问题。这个习惯能省掉大量翻手册的时间。

顺便把版本信息也一起存进基线,后面换模组、换固件的时候有对照:

# 一次抓齐身份信息,作为这台设备的档案 { printf 'AT+CGMI\r'; sleep 0.5; printf 'AT+CGMM\r'; sleep 0.5; \ printf 'AT+CGMR\r'; sleep 0.5; printf 'AT+CGSN\r'; sleep 0.5; } > /dev/ttyFM150 sleep 2 cat /dev/ttyFM150 > fm150_identity.txt

每条命令之间留 0.5 秒间隔,是为了不让模组把两条命令的返回混在一起。+CGSN返回的是产品序列号(IMEI),和+CFSN的工厂序列号不是一回事,做资产登记时别填错字段。

4. 把模组注册上网络:SIM、CFUN、APN 与数据拨号

AT 通道通了只是第一步,从「能对话」到「能上网」中间还有 SIM 自检、射频开关、PDP 上下文、拨号四道关。这一章按真实联调顺序走一遍,每道关都给可执行的命令和判断标准。

4.1 SIM 链路自检:先把卡认出来

模组认不到卡,后面所有网络命令都是白费。手册 3.1.13 有+GTUSIM,专门用来检查 USIM 卡状态,配合+CCID读卡号、+CPIN?查 PIN 状态。

AT+GTUSIM? // 查 USIM 卡检测状态,确认卡在位 AT+CCID // 读 ICCID,返回一串 19~20 位数字 AT+CPIN? // 查 PIN 码状态,正常返回 +CPIN: READY AT+CIMI // 读 IMSI,15 位数字,能读出来说明卡被正确识别

+CCID和+CIMI这两条是判断卡是否真被读到的硬指标:前者是卡本身,后者是运营商签约信息。两条都能返回正常数值,说明卡座、电平、协议这一整条链路没问题。如果+CPIN?返回+CPIN: SIM PIN,说明卡设了 PIN 码,需要先AT+CPIN="1234"解锁;返回+CPIN: NOT INSERTED就是物理层没接触好。

热插拔相关的两条命令在手册 4.1.7 和 4.1.8:+MSMPD控制 SIM 卡热插拔检测的使能,+SIMSWAPCFG设置热插拔检测的有效电平。载板设计上 SIM 检测脚是高有效还是低有效,必须和+SIMSWAPCFG配的一致,配反了的表现是卡明明插着却报未插入。这块参数别凭感觉,查载板原理图。

双卡场景用手册 4.1.16 的+GTDUALSIM切换。变更记录里明确写了这条命令的「Persistent to yes」,也就是设置会掉电保存,切换完重启仍然有效。这个特性在调试时容易造成困惑——你以为重启会回到默认卡槽,实际上没有。

4.2 射频与功能态:CFUN 和飞行模式

SIM 认到之后开射频。手册 4.1.4 的+CFUN是总开关,+CFUN=1全功能,+CFUN=0最小功能(射频关),+CFUN=4关射频但保持 SIM 供电。联调时我习惯先用+CFUN=4确认 SIM 和串口都正常,再切+CFUN=1开射频,这样能把「卡的问题」和「射频的问题」分开。

AT+CFUN? // 查当前功能态,正常联网时应该是 +CFUN: 1 AT+CFUN=4 // 关射频,保留 SIM 供电,用于隔离排查 AT+CFUN=1 // 全功能,开射频 AT+GTFMODE? // 查硬件飞行模式状态 AT+GTFMODE=1 // 打开硬件飞行模式,射频关闭 AT+CPWROFF // 软关机,关机前建议先执行

+GTFMODE(手册 4.1.9)是硬件飞行模式,和+CFUN的软件功能态是两套机制,可能同时存在。射频打不开的时候两条都要查,只看+CFUN会漏。+CPWROFF(手册 4.1.10)是软关机命令,直接断电有概率丢配置,尤其是刚写完持久化参数的时候,先+CPWROFF再断电是稳妥做法。

低功耗相关的还有+SLPMODE(4.1.11)、+GTWAKE(4.1.12)、+GTLPMMODE(4.1.13)、+GTPMTIME(4.1.14)、+WRIM(4.1.15)。做数据透传联调时如果发现「隔一段时间没数据就断」,大概率是进了睡眠,先把+SLPMODE关掉排除掉这个变量,再回头调功耗。

4.3 APN 与 PDP 上下文:+CGDCONT 是根

APN 配不对,+CGATT可能是 1(附着成功)但数据通不了。标准路径是+CGDCONT定义 PDP 上下文,鉴权参数用+CGAUTH或$QCPDPP,厂商私有的+GTMPDN用来增改 PDP 定义。

AT+CGDCONT=1,"IP","cmnet" // 定义 cid=1,PDP 类型 IP,APN 填运营商给的 AT+CGDCONT? // 回读全部上下文,确认 cid 和 APN 没写串 AT+CGAUTH=1,1,"user","pass" // cid=1 使用 PAP 鉴权,填用户名密码 AT$QCPDPP=1,1,"user","pass" // 私有写法,部分固件用这条设鉴权

+CGDCONT第一个参数是 cid,第二个是 PDP 类型(IP、IPV6、IPV4V6),第三个是 APN 字符串。cid 不要和已有上下文冲突,先AT+CGDCONT?看一眼占用情况再写。PDP 类型选错的表现是能附着但拿不到地址,IPV4V6在只支持单栈的网络下也会出问题,联调阶段先用IP最稳。

+CGAUTH和$QCPDPP是两条不同路径,前者是标准命令,后者是私有命令。变更记录里$QCPDPP在 V3.6.2 加过、又在 V3.6.3 的某次修订里被删掉、后来$qcrmcall和$qcpdpp又出现在新增列表里。这种反复说明什么?说明不同固件分支上这两条命令的支持情况不一致。所以第 3.4 节用+CLAC建基线这一步在这里就派上用场了:先确认命令在不在,再决定用哪条。

4.4 拨号与数据面:私有命令的版本差异最大

拨号这块是版本差异最集中的地方。手册变更记录里能清楚看到几条命令的反复:

命令变更记录里的动作影响
+GTAUTODHCPV3.6.3 早期删除,后又新增自动获取 IP 的行为在不同固件上不一致
+GTAUTOCONNECTV3.6.3 新增老固件上不存在,发了回 ERROR
+GTRNDISV3.6.3 修改参数顺序或取值有调整
$QCRMCALLV3.6.2 新增,cid 描述后改拨号参数含义随版本变
+GTMPDNV3.6.3 新增私有 PDP 管理,老固件没有

这就是为什么我一直强调先看变更记录再看正文。手册正文写的是最新状态,你的固件可能是中间某个版本,两头对不上就会怀疑人生。

常见做法是先走标准路径拨号,不行再上私有命令:

AT+CGATT? // 确认已附着,返回 +CGATT: 1 AT$QCRMCALL=1,1 // 私有拨号,起 cid=1 的数据通道 AT+GTRNDIS=1,1 // 开启 RNDIS 网卡模式,参数以固件为准 AT+GTAUTOCONNECT=1 // 开启自动拨号,重启后自动连 AT+GTAUTODHCP=1 // 自动获取 IP,注意版本差异

$QCRMCALL的两个参数大致是「动作」和「cid」,具体取值各固件分支有差异,先用AT$QCRMCALL=?查一次支持的参数范围再发。+GTRNDIS是让模组以网卡形态出现,配合主机的 RNDIS 驱动用;不做网卡模式、走 PPP 的话这条不用发。

+GTAUTOCONNECT和+GTAUTODHCP这一对是给「设备上电就要自动联网」的场景用的,配好之后不用主机发拨号命令。但正因为是自动的,出问题时反而难查——你不知道它什么时候拨的、拨成功没有。调试阶段建议先手动拨通,确认整条链路没问题,最后再开自动,别一上来就开。

4.5 状态轮询:用哪几条命令判断「到底通没通」

联调时最常问的一句话是「现在通了吗」。判断标准不能只看一条命令,我一般按这个顺序看:

AT+CSQ // 信号质量,返回 +CSQ: 20,99 之类,rssi 99 表示无信号 AT+CGATT? // 附着状态,+CGATT: 1 表示已附着 AT+GTSTATIS? // 流量与连接统计,V3.6.3 新增 AT+GTCELLSTATUS? // 服务小区状态,V3.6.3 新增 AT+GTSRVSTATUS? // 服务状态,V3.6.3 新增 AT+GTEPSMOBINFO? // EPS 移动性信息,V3.6.3 新增 AT+GTCAINFO? // 载波聚合信息,V3.6.3 新增

+CSQ返回两个值,第一个是信号强度(0~31,99 表示未知或无信号),第二个是误码率。信号强度 10 以下基本别指望稳定传输,先解决天线和位置问题,别在软件上折腾。

后面五条+GT开头的状态命令都是 V3.6.3 才加的,老固件上没有。这正好又是一次版本校验:如果AT+GTSTATIS?回 ERROR 而AT+CLAC里也没有,说明你的固件版本低于 V3.6.3 的某个修订点,状态查询只能退回+CSQ和+CGATT?这两条基础命令。

5. 避坑与排查:五条血泪记录

这一章的五条都是联调现场真实会遇到的,按「现象 → 原因 → 解决」写。遇到问题先对照这里的顺序排查,比从头翻 241 页快得多。

5.1 现象:串口打开正常,敲 AT 一个字符都不回

原因通常有三个层次。最外层是流控:工具默认开了 RTS/CTS,硬件上没接,模组认为主机没准备好,不吐数据。中间层是端口选错:打开的是诊断口或数据口而不是 AT 口,那些口不响应 AT 命令。最内层是模组处于睡眠状态,+SLPMODE开着且没有唤醒信号。

解决顺序:先用dmesg确认插拔时枚举出的端口列表,逐个试AT,找到真正响应 AT 的那个;再用stty -F /dev/ttyXXX -crtscts或工具里关流控;最后查+SLPMODE和+GTWAKE,如果配了睡眠唤醒,需要拉 RI 或 DTR 唤醒再发命令。逐层排除比乱试参数快。

5.2 现象:能回 OK,但 +CGATT? 长期是 0

+CGATT: 0表示未附着,但能回 OK 说明串口和命令解析都没问题,问题在注册链路上。常见原因按概率排:SIM 没被真正识别(+CPIN?不是 READY)、天线没接或接触不良(+CSQ是 99)、射频被关(+CFUN是 0 或 4、+GTFMODE是 1)、APN 写错导致 PDP 激活失败、所在位置没有对应频段覆盖。

排查用二分法:先AT+CPIN?和AT+CCID确认卡,再AT+CSQ确认信号,再AT+CFUN?和AT+GTFMODE?确认射频开着,最后AT+CGDCONT?核对 APN。这四步下来基本能定位到具体哪一环。注意+CFUN=4是关射频但 SIM 供电,这个状态下+CGATT?一定是 0,别把调试时自己设的状态当成故障。

5.3 现象:手册里写着的命令发下去回 ERROR

先别改参数,先确认这条命令在不在你的固件里。用手册 3.1.14 的+CLAC抓一份列表grep一下。如果不在,就是固件版本低于手册版本,这条路走不通,换等效命令或者升级固件。

如果在列表里还是 ERROR,看三种情况:一是命令有前置状态要求,比如没附着就发拨号命令;二是参数格式对不上,AT+XXX=?查一下支持的参数范围;三是这条命令在当前 SIM 或网络下不支持,比如某些运营商网络下私有命令的行为会不同。

还有一种隐蔽情况:命令本身没错,但被上一条命令的残留状态干扰。比如刚发过ATQ1关了结果码,后面所有命令都不回 OK,看着像失败其实成功了。遇到「批量都是 ERROR」先查AT&V或ATQ?看全局设置有没有被改过。

5.4 现象:高频发命令时丢字符、日志串行

现象是返回里偶尔少几个字符,或者两条命令的返回粘在一起。原因基本是两条命令之间间隔太短,模组的命令解析器还没处理完上一条,下一条就进来了。

解决是给命令之间加固定间隔,脚本里每条之间至少 100ms,状态查询类命令给到 300ms 以上。另外确认单条命令的结尾只有一个\r,\r\n\r\n会被当成空命令处理,模组回一个ERROR,混在正常返回里很难发现。

硬件层面也要看:USB 线太长、没有屏蔽、靠近开关电源,都会造成偶发丢字节。换根短线试一次,能排除掉不少玄学问题。

5.5 现象:参数配好当时生效,断电重启就还原

AT 命令的参数持久化行为并不统一。手册变更记录里专门标注了某些命令「Persistent to yes」(比如+GTDUALSIM),说明它掉电保存;没标注的默认不保存,重启回默认值。

解决办法是:查手册里该命令的参数说明有没有持久化标记,没有标记的就在每次上电的初始化流程里重新下发一遍。别指望发一次就永久生效。

另一个坑是直接断电。部分参数写入是异步的,命令回了 OK 只代表收到,不代表写进存储。稳妥做法是改完参数等 1~2 秒,再发AT+CPWROFF软关机,等模组真正关断后再断电。这个习惯能避免很多「明明配了却丢了」的问题。

6. 装完怎么验证:一份能跑通的 12 步自检脚本

人工敲命令验证的问题是容易漏步、容易看错返回值。我习惯写一个 pyserial 脚本,把关键命令串起来跑一遍,每条都给期望值,跑完输出一份通过/失败清单。脚本本身不复杂,价值在于每次换模组、换固件、换载板都能五分钟内给出结论。

#!/usr/bin/env python3 # FM150 AT 指令自检脚本,按联调顺序逐条验证 import serial, time, re, sys PORT = "/dev/ttyFM150" # udev 固定后的软链,换机器不用改 BAUD = 115200 # (命令, 期望出现的正则, 说明) CHECKS = [ ("AT", r"^OK", "基础连通"), ("ATE0", r"^OK", "关闭回显"), ("AT+CGMI", r"\w+", "厂商ID"), ("AT+CGMM", r"FM150|FG150", "型号匹配"), ("AT+CGMR", r"\d+\.\d+", "固件版本"), ("AT+CGSN", r"\d{15}", "IMEI 15位"), ("AT+CCID", r"\d{18,20}", "ICCID"), ("AT+CPIN?", r"\+CPIN:\s*READY", "SIM 就绪"), ("AT+CSQ", r"\+CSQ:\s*(?!99)\d+", "信号非99"), ("AT+CFUN?", r"\+CFUN:\s*1", "射频全功能"), ("AT+CGATT?", r"\+CGATT:\s*1", "已附着"), ("AT+CGDCONT?", r"\+CGDCONT:", "PDP 上下文存在"), ] def send(ser, cmd, wait=0.5): ser.reset_input_buffer() ser.write((cmd + "\r").encode()) # 结尾只发 \r,不要 \r\n time.sleep(wait) # 给模组处理时间,状态类命令可加长 return ser.read(ser.in_waiting or 1).decode(errors="ignore") def main(): ser = serial.Serial(PORT, BAUD, timeout=2, rtscts=False, dsrdtr=False) # 显式关流控 failed = [] for cmd, pattern, desc in CHECKS: resp = send(ser, cmd) ok = re.search(pattern, resp, re.M) is not None print(f"[{'PASS' if ok else 'FAIL'}] {desc:<12} {cmd:<16} -> {resp.strip()[:60]}") if not ok: failed.append((cmd, desc, resp.strip())) ser.close() print(f"\n通过 {len(CHECKS)-len(failed)}/{len(CHECKS)}") sys.exit(1 if failed else 0) if __name__ == "__main__": main()

脚本逻辑有几处值得说明。rtscts=False和dsrdtr=False是在串口层显式关流控,比依赖工具默认值可靠;ser.write((cmd + "\r").encode())只发单个\r,避免多余空行被当成错误命令;ser.reset_input_buffer()在每条命令前清一次接收缓冲,防止上一条的残留数据污染判断。

CHECKS表里的正则都是宽松匹配,因为不同固件的返回格式在细节上有差异,比如+CSQ后面可能有空格也可能没有。(?!99)这个负向断言专门用来排除「有信号字段但值为 99(无信号)」的假通过,这是最容易被误判的一条。

执行顺序是有讲究的:AT和ATE0打底,然后身份信息、SIM、信号、射频、附着、PDP 逐层往上。任何一层 FAIL,后面的结果都不可信,先修前面那层再看后面。

12 条全过之后,还有两条建议手工确认一次,脚本不太好判:

确认项命令判断标准
数据面是否真的通AT$QCRMCALL=1,1后主机侧ping能拿到地址且能通外网
持久化是否生效配完参数后AT+CPWROFF再上电关键参数仍是配置值

我把这套脚本放在工程目录里,每次换载板、升级固件、改 APN 都先跑一遍,跑通了再动业务代码。从那以后我每次上电联调都强制走一遍这 12 条,再没出现过「以为通了其实没通、查了两小时发现是 SIM 没插好」这种低级返工。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进