1. 项目背景与使用场景
1.1 EC20模块在嵌入式项目里的角色
EC20是移远通信推出的LTE Cat 4全网通模块,在嵌入式开发圈子里出镜率极高。无论是工控网关、边缘计算盒子、充电桩计费终端、安防视频传输设备,还是车载终端,只要涉及“设备需要上网”这个需求,EC20基本是首选项之一。它的优势很直接:支持国内三大运营商的全网通频段,下行速率最高150Mbps、上行50Mbps,对绝大多数物联网业务来说完全够用,而且封装兼容性做得好,LCC封装能直接贴片,也有Mini PCIe封装适合做插卡式模组。
在嵌入式Linux平台上使用EC20,移远官方提供了一套拨号工具,就是标题里的quectel-CM。这个工具的作用相当于给模块做一次完整的“上网初始化”:检测模块状态、校验SIM卡、配置APN、发起拨号、分配IP、设置路由和DNS,最终在系统里生成一个可用的网络接口(通常叫wwan0或ethX)。没有这个工具,模块单纯上电只会枚举出几个串口设备,并不会主动建立数据连接。
我在实际项目里见过不少新入行的同事,第一次拿到EC20开发板时,以为插上模块、接通USB就能直接ping通外网,结果折腾一整天发现ifconfig里根本没有新网卡。原因很简单:EC20不是一个即插即用的USB网卡,它需要经过一套QMI协议与模组交互,把数据通道打通之后,上层网络才会真正可用。quectel-CM就是把这套流程自动化的工具。
1.2 quectel-CM和STM32等MCU开发的区别
这里必须单独把“STM32 + EC20”这个组合拎出来说,因为它是非常多硬件工程师会踩的岔路口。很多人在网上搜到“stm32 ec20”的关键词,以为MCU开发也和嵌入式Linux一样需要跑quectel-CM,其实不是。
MCU场景下,STM32通过串口直连EC20的AT命令通道,完全走的是AT指令流程。核心动作包括:AT+CPIN?查SIM卡状态、AT+CREG?查网络注册、AT+CGDCONT=1,"IP","cmnet"设置APN上下文、AT+QIACT=1激活PDP上下文、AT+QIOPEN建立Socket连接,之后就走AT+QISEND和AT+QIRD收发数据。整个过程相当于人为复刻了quectel-CM在Linux里做的事情,但是基于指令流逐条执行,没有内核网络协议栈参与。你需要自己处理TCP/UDP连接、心跳、粘包,业务代码全在MCU里。
而Linux环境下,EC20的Modem口会在USB枚举时挂载出几个ttyUSB设备,其中有一个是QMI口。quectel-CM正是通过这个口,使用QMI协议和模块协商出一条网络通路,然后把数据映射到内核的wwan0网卡上。之后上层用的就是标准socket编程,和以太网设备没有本质区别。这是两种完全不同的开发范式,先分清楚自己手里的平台是Linux还是MCU,再决定学哪套方案,能少走很多弯路。
2. quectel-CM启动流程逐段拆解
2.1 工具启动前的准备工作
很多人拿到quectel-CM直接双击运行(或者嵌入式板子上直接执行),发现报错或日志异常,第一反应是工具坏了。其实工具本身很稳定,问题大多出在启动前的外部条件上。
第一步是确认模块有没有被系统正确枚举。在Linux下,接上EC20之后用ls /dev/ttyUSB*查看,正常会看到ttyUSB0到ttyUSB3四个设备,分别对应AT口、Modem口、QMI口、ADB/日志口。不同固件版本枚举顺序可能有差异,但基本固定。如果只有一个口冒出来,说明驱动没加载完整,常见原因包括内核缺少option或qmi_wwan驱动、USB接线不良、模块供电不足。
第二步是确认拨号模式。EC20支持多种工作模式,包括QMI、ECM、RNDIS、PPP等。quectel-CM默认走QMI通道,这也是移远官方推荐的方式。如果你之前用ECM模式调试过模块,设备节点会有所不同,需要先通过AT指令切回QMI模式:AT+QCFG="usbnet",2,然后重新插拔模块。这个参数很关键,usbnet设为0是PPP、1是ECM、2是QMI。经常有人发现quectel-CM提示“Cannot open /dev/ttyUSB2”,多半是模式不对。
第三点是SIM卡状态。建议在任何软件操作之前,先用串口工具直接连AT口发送AT+CPIN?,如果返回+CPIN: READY,再继续;如果返回ERROR或者+CME ERROR,说明SIM卡没插好、卡槽方向不对、或者SIM卡被写入了PIN码锁。这一步能帮你把问题范围缩小一大半。
2.2 从设备检测到拨号成功的完整链路
quectel-CM的运行逻辑并不复杂,把它拆成几个阶段看,理解起来非常清晰。
第一阶段是设备检测。工具启动后会遍历/ dev目录下的ttyUSB设备,寻找符合QMI能力的节点。它通过向候选设备发送QMI请求,读取模块的IMEI、固件版本、SIM卡状态等基本信息。日志里如果能看到类似“Find /dev/ttyUSB2”或“Get card status”的输出,说明设备检测已通过。
第二阶段是SIM卡和网络注册检查。工具会确认SIM卡的IMSI、CCID信息,并检查是否已经注册上运营商网络。这个阶段对应的日志字段通常是“Waiting for network register”或者“Check registration state”。我遇到过模块一直卡在这里不往下走的情况,原因是SIM卡欠费和天线没接好,虽然这两个问题表面上毫无关联,但最终都表现为网络无法注册。
第三阶段是APN配置。工具使用你传入的APN参数或者模块出厂配置,通过QMI WDS接口设置接入点。不同运营商、不同业务类型的APN不一样:公网卡通常是cmnet、ctnet、3gnet,内网卡和物联专网卡则由运营商单独分配。APN设置错误最典型的后果是拨号能成功,但网络完全不通,或者只能访问部分内网地址。
第四阶段是发起数据连接。工具调用WDS Start Network接口,请求模块激活PDP上下文。这一步成功之后,模块会获得一个IP地址和对应的DNS地址。日志里出现“Get IP address”和对应的IP数值,就说明数据面已经通了。
第五阶段是本地网络配置。这是quectel-CM的收尾工作:把获取到的IP绑定到wwan0网卡上、设置默认路由、写入DNS配置。到这里,Linux系统层面才真正具备访问外网的能力。很多人看到前面全部成功,最后连不上网,问题往往就出在路由或DNS配置上,这正好引出了后面要重点剖析的114.114.114.114问题。
2.3 拨号日志怎么读才不蒙圈
我刚用quectel-CM的时候也被日志搞晕过,因为它的输出格式并不统一,有些版本打的是调试信息,有些是状态变更,不好好读很容易误判。
有几种典型日志需要区分对待。以“[OK]”结尾的通常是命令执行成功的确认,比如“AT+QCFG? [OK]”说明AT指令回执正常。带“ERROR”字样的是有环节出了问题,但问题可大可小,比如AT指令偶发超时返回ERROR并不代表模块挂了,可能是USB休眠导致的短暂无响应。还有一类是进度提示,比如“Waiting for network register”只是告诉你它在等待,不代表异常。
实战中建议这样看日志:先用时间戳对一遍流程,确认执行到哪个阶段失败;再抓关键错误码,比如QMI请求返回的错误码0x03是设备无响应、0x0B是APN拒绝、0x12是sim卡未就绪;最后结合串口AT口的实时日志做交叉验证。如果你要排查一个拨号问题,务必备好三样东西:quectel-CM的完整输出、/dev/ttyUSB0的AT口日志、dmesg内核日志。三者对照,绝大多数问题都能定位到具体环节。
3. 核心实操:从零完成一次EC20拨号
3.1 交叉编译quectel-CM
quectel-CM的源码在GitHub上有多个历史版本,移远官方也维护着固定的发布仓库。编译本身不难,但嵌入式交叉编译有几个小地方要留意。
工具源码依赖libqmi库,所以编译前需要准备好libqmi的交叉编译库。如果你用的是Buildroot或Yocto,建议直接把quectel-CM加进包管理系统里,它们已经有现成的recipe,省去手动处理依赖的麻烦。如果是手动编译,步骤大致是:先交叉编译libqmi,再把libqmi的头文件和库路径传给quectel-CM的Makefile。命令大致是:
./configure --host=arm-linux-gnueabihf --prefix=$PWD/out CC=arm-linux-gnueabihf-gcc make make install编译完成后会生成quectel-CM可执行文件,丢到板子的/usr/bin目录即可。需要注意一点:有些移植版本对glibc版本敏感,如果你镜像里的glibc版本比较老,建议找对应版本的quectel-CM源码编译,不要盲目用网上现成的二进制包,不然会出现“No such file or directory”或者段错误这类诡异问题。
3.2 常用参数与拨号命令
quectel-CM的参数不多,但每个都值得认真对待。
最基本的用法是直接运行quectel-CM,工具会自动检测模块并使用模块内部配置的APN。实际项目中更多是显式指定参数,最常用的是-s指定APN。例如联通的物联网卡,执行:
quectel-CM -s cpiot如果你的卡是专网卡,还需要配置用户名和密码,用-u和-p参数。有的企业内网APN还要求设置鉴权方式,需要用到-1参数指定PAP或CHAP。工具还支持-4强制走IPv4、-6走IPv6,以及-r参数重启模块后再拨号。
还有一种比较实用的场景是DHCP模式。quectel-CM默认把拨号获得的IP配置到网卡上,但它也支持协商一个IP地址后自动启动udhcpc获取,通过-d参数开启。我自己更推荐手动指定IP,因为DHCP模式依赖板子上的udhcpc环境,出问题时会多一个排查变量。
3.3 开机自启与断线重连
产品化项目里,quectel-CM大多需要开机自启,不能依赖人工登录执行。在systemd系统里,写一个service文件就能搞定:
[Unit] Description=EC20 Quectel CM Dial After=network.target [Service] Type=simple ExecStart=/usr/bin/quectel-CM -s cmnet Restart=always RestartSec=5 [Install] WantedBy=multi-user.target重点说一下Restart=always这个参数。4G网络在电梯、隧道、地下停车场这些场景下信号波动非常剧烈,模块随时可能掉线,如果没有自动重启机制,设备就会一直维持“断网但不自知”的状态。quectel-CM本身具备一定重连能力,但加上systemd的进程级守护会更可靠。实测下来,在弱信号环境下,5秒重启间隔是个不错的折中,太短会导致模块来不及恢复,太长会浪费恢复时间。
4. 深入解读:quectel-CM会把DNS写成114.114.114.114吗
4.1 源码里的DNS处理逻辑
这个热搜词特别有意思:“quectel-cm是否会将114.114.114.114写dns”。我猜提问的人一定是在拨号后发现/etc/resolv.conf里的DNS地址从运营商的自动分配变成了114.114.114.114,而且业务联调时碰到了一堆域名解析问题。
先说结论:是的,在某些版本、某些条件下,quectel-CM确实会把系统默认DNS设置或覆盖为114.114.114.114。这不是操作失误,而是源码里的一段兜底逻辑在起作用。
查看quectel-CM的源码可以发现,在拨号成功后,工具会通过QMI WDS接口获取运营商下发的DNS地址。正常流程里,它会把获取到的DNS写到resolv.conf里。但代码里有一段处理是:如果模块下发的DNS地址异常(全为0.0.0.0或者获取失败),工具会fallback到一组公共DNS。不同版本选的公共DNS不完全一样,有的版本是114.114.114.114,有的版本是114.114.114.114加8.8.8.8的组合。这就是问题来源。
这种兜底逻辑从程序健壮性角度是合理的,运营商信号不佳、DNS服务器临时不可用,确实可能拿不到有效DNS。但对开发者来说,这个行为非常隐晦,因为你看到的现象只是“为什么我的resolv.conf被改了”,可能完全想不到是quectel-CM干的。
4.2 哪些情况下会触发公共DNS兜底
根据我实际排查过的案例,触发条件主要有以下几种。
最常见的是模块在弱信号下或者刚开机还没完全注册到网络时,quectel-CM就已经开始拨号流程,此时模块返回的DNS上下文可能是空的,工具走兜底逻辑写入公共DNS。这种情况下你会发现一个矛盾现象:IP和路由配置都正常,ping运营商内网地址能通,但解析外部域名时走的却是公共DNS。如果你的设备还需要访问内网特定域名,那问题会更明显,内网域名解析全部失败。
其次是部分物联专网卡的场景。这类SIM卡的APN上下文里,运营商在下发DNS时可能只下发内网DNS,或者出于某些配置原因没有携带公共DNS字段。quectel-CM获取不到有效DNS,就会触发兜底。更特殊的是有些企业专网根本不允许公网DNS解析,兜底一写进去,整个域名解析链路就乱了。
还有一种是代码版本差异。某些旧版本quectel-CM并没有严格判断“是否获取到有效DNS”,只要拨号成功就会直接写入预置的114.114.114.114。这种问题在新版本中有所收敛,但如果你手里的源码是几年前的项目里扒出来的,中招概率很高。
4.3 正确的DNS接管方案
知道了原因,解决起来就有针对性了。我推荐按优先级做三件事。
第一件事,升级quectel-CM到较新版本。新版本源码里对DNS下发的判断更严谨,至少会等模块返回真正的DNS上下文。不过这不能完全解决问题,因为弱信号下的空DNS现象再新版工具里依然可能触发兜底。
第二件事,从源码层面修改默认DNS。在quectel-CM源码里找到DNS fallback相关的定义,把114.114.114.114改成你自己希望的内网DNS,或者干脆把兜底路径去掉。源码修改的挑战是每升级一次工具版本就要重新改一次,运维上不算省事。
第三件事,也是最推荐的:拨号成功后,在业务启动脚本里主动改写resolv.conf。因为quectel-CM只是它在启动时设置一次DNS,之后不会再持续监控或刷新。比如你可以写一个脚本,在quectel-CM拨号成功后等待几秒,然后执行:
echo "nameserver 10.0.0.1" > /etc/resolv.conf同时把这个脚本放到systemd服务的ExecStartPost里。这样无论quectel-CM写了什么,最终生效的都是你指定的DNS。不过要注意别在quectel-CM启动进程还没结束时抢写resolv.conf,否则可能被它的收尾操作覆盖,最好用sleep加条件等待来处理。
5. 常见问题与排查技巧实录
5.1 设备枚举异常
我在多个项目里都遇过EC20插上之后枚举不出完整ttyUSB设备的情况,处理思路一般按顺序排查。
首先是硬件层面。EC20在数据收发瞬间的峰值电流能到2A甚至更高,如果供电能力不足,模块会反复重启,表现就是USB设备不断枚举、断开、再枚举。这时候用万用表量模块供电引脚的电压,如果在数据传输时电压跌落明显,基本就能确定是供电问题。解决方案是换更大电流的DCDC电源芯片,并在模块输入端并联470uF以上电解电容加上百uF钽电容组合。这个经验对MCU和Linux平台同样适用。
其次是驱动层面。Linux下EC20依赖option和qmi_wwan驱动,如果内核没有把这两个编译进去,或者模块的VID/PID没能被驱动识别,枚举就会失败。可以先modprobe option、modprobe qmi_wwan,再看看dmesg里usb核心识别到了什么信息。还有一种隐蔽情况是USB线缆质量差,信号完整性不过关,表现为时好时坏,换根短线往往立竿见影。
5.2 SIM卡和网络注册类问题
SIM卡问题经常被当成软件问题排查,实际上大多数是接触和配置问题。
先在AT口发AT+CPIN?,如果返回+CME ERROR: SIM not inserted,先别急着怀疑系统,把卡拔出来重新插。模块的卡槽和手机不太一样,很多开发板用的翻盖卡槽,卡没推到位或者方向反了的情况太常见了。如果确认插好了还是这个错误,用橡皮擦一下SIM卡金属触点,氧化层会导致接触不良。
如果CPIN正常,但AT+CREG?一直返回+CREG: 0,2,说明模块已注册但被拒绝,大概率是运营商侧问题。遇到欠费停机、SIM卡绑定的APN不存在、该卡被限制使用数据服务,都是这个表现。
在quectel-CM的日志里,这类问题通常表现为一直停留在“Waiting for network register”阶段。用串口直接连AT口看CREG变化会更直观。有些模块在有信号但注册被拒时,CREG会反复在0、2、3之间跳,这时候要重点查卡本身的状态而不是天线信号。
5.3 拨号成功但上不了网
这是最让人头疼的一类问题,因为从quectel-CM的角度看,所有步骤都执行完了,IP也有了,路由也配了,但实际业务就是不通。我总结下来有三个高频根因。
第一个是路由配置被覆盖。有些镜像的网络管理服务(比如NetworkManager)会在quectel-CM配好默认路由之后抢占,把默认路由指到空的eth0上,导致wwan0的数据包发不出去。查一下ip route,如果默认路由的主出口不是wwan0,那就手动调整metric,让wwan0的路由优先级更高,或者干脆停掉NetworkManager。
第二个是防火墙拦截。嵌入式板子如果跑了iptables规则,默认FORWARD或OUTPUT链策略是DROP,即便路由正确也一样上不了网。这种问题非常隐蔽,因为quectel-CM日志完全正常,TCP握手卡在SYN_SENT状态。排查时直接iptables -L看一下策略,或者临时把策略改成ACCEPT做对照测试。
第三个是MTU不匹配。LTE网络的MTU通常建议设置在1400到1430之间,如果系统网卡MTU保持默认1500,碰到某些运营商网络会导致大包被丢弃,小包正常,表现是ping网关通、ping域名超时或者访问网页卡死。用ip link set wwan0 mtu 1400做一次对照测试就能确认。
5.4 常见问题速查表
我把日常支持里最常遇见的若干问题整理成了一张速查表,方便现场排查时快速对照。
| 现象 | 可能原因 | 排查命令/手段 | 解决办法 |
|---|---|---|---|
| 枚举不出ttyUSB设备 | 供电不足/驱动缺失/USB线材劣质 | dmesg查看USB枚举日志 | 更换电源、加载驱动、换短线 |
| 模块反复重启 | 电源跌落 | 万用表监测供电引脚电压 | 增强供电、加大电容 |
| AT口无响应 | 波特率不对/模块死机 | AT指令测试 | 确认115200波特率、拉PWRKEY复位 |
| SIM卡报错 | 卡没插好/欠费 | AT+CPIN? | 重插卡、联系运营商 |
| 一直等待网络注册 | 天线问题/注册被拒 | AT+CREG? | 检查天线、确认卡状态 |
| 拨号成功但ping不通 | 路由/防火墙/MTU | ip route、iptables -L | 调整路由优先级、放行防火墙、降低MTU |
| resolv.conf被改 | quectel-CM DNS兜底 | cat /etc/resolv.conf | 业务脚本里强制覆盖DNS |
| 掉线后恢复慢 | 缺少自动重连 | journalctl查看systemd日志 | 配置Restart=always |
5.5 STM32场景的独立坑点
如果是STM32加EC20的玩法,有几个坑是Linux平台完全碰不到,但在MCU开发里反复出现的。
第一个是串口缓冲和流控。EC20的AT口和Socket数据口如果共用同一个UART,高速收发时很容易丢数据,尤其当STM32的串口FIFO太浅或者开启了流控但线没接RTS/CTS的时候。建议HAL库工程里给串口开DMA加空闲中断,不然数据稍微一多,粘包和断包的坑会耗掉你一整天。
第二个是模块波特率协商问题。很多EC20模块默认波特率是115200,但有些固件出厂是9600。STM32代码里如果没有做波特率自适应,基本AT指令都能成功,但一遇到大数据量传输就开始乱码。还有一点要注意:模块进入低功耗模式后再唤醒,串口可能短暂不可用,代码里要加多发几次AT指令做唤醒确认。
第三个是Socket连接管理。EC20模块侧最多同时支持数十个Socket连接,但MCU侧通常一个数据通道就够用了。重点是你需要自己处理重连逻辑,不像Linux上有quectel-CM帮你盯着。我一般的做法是开一个定时任务,每30秒发一次心跳AT+QIURC到串口,查询模块是否有URC上报,如果连续三次没响应就重启模块并重新建链。
6. 项目中的实际经验与建议
6.1 电源设计不能省,这是所有问题之源
很多EC20的问题,表面上是软件故障,根子上全是供电压垮的。我在调试阶段就犯过这样的错误:用开发板自带的USB口给模块供电,刚开始一切正常,一旦建立数据连接,电流一上来,模块就自动重启。而且这个现象在日志里表现得很“随机”,时而拨号到一半断掉,时而上电就枚举失败,排查了很半天才想到去量电源波形。
如果你的产品是电池供电,还要额外注意DW等电压跌落检测。EC20的工作电压范围3.3V到4.3V,推荐是3.8V左右,电压低于3.3V就会触发欠压保护。电池电量低的时候,4G模块发射瞬间的大电流会把电压瞬间拉低,直接导致模块重启。做产品设计时,建议预留一个低电量提示功能,在电压过低时提前警告用户。
6.2 天线和信号质量对调试效率的影响
很多新手容易低估天线的影响。我曾遇到过一个客户,quectel-CM所有的流程都正常,但数据传输速率一直是几十kbps,换了另一张SIM卡也一样。最后发现是外置天线增益不够,再加上设备外壳金属遮蔽,信号强度一直在-110dBm以下。
你可以在AT口用AT+CSQ查询信号强度,返回值一般在0到31之间,建议不要低于12。低于10的话,就算拨号成功,掉线率和延迟都会显著上升。注意CSQ反映的是信号质量总分,不代表它一定是信号弱,也可能是干扰导致的误码率偏高。排查这类问题时,先换一个外置高增益天线放置在无遮挡的位置做对照,是最快的排除法。
6.3 日志和监控机制要提前设计好
无论是Linux还是MCU平台,日志系统一定要在项目早期就搭建好。MCU端至少要能把AT指令和模块返回完整记录到Flash里,Linux端则建议使用syslog或journalctl把quectel-CM的输出统一收集起来。这样到了现场出问题时,你能直接拿到第一手数据,不用让用户帮忙复现半天还说不清楚现象。
我见过一些产品,现场出了问题,远程登录一看,quectel-CM进程已经死掉了,/var/log里空空如也。这种时候排查完全靠猜。后来我在所有项目的quectel-CM service里都配了标准输出重定向,再结合systemd的journald,总算能做到故障可追溯。无线网络调试本来就充满不确定性,日志就是你的照妖镜。
6.4 最后再分享一个调试技巧
我在排查EC20网络问题时,经常会在串口AT口和网络侧同时做交叉验证。具体来说,quectel-CM跑拨号的同时,我会另开一路串口终端连到ttyUSB0,实时丢AT指令去看模块的真实状态。比如quectel-CM日志里显示拨号失败,但AT口返回+CGACT: 1,说明PDP上下文其实已经激活了,问题可能出在qmi和内核网卡之间的数据通道协商上。反过来也一样,AT口都没激活,quectel-CM却在试图配置IP,这多半是模块固件和工具版本不匹配。
另外可以多利用模块自带的AT+QCFG="nwscanmode"和AT+QCFG="band"这类指令,强制模块锁定在指定网络频段。在弱信号或者运营商网络异常的场景下,频段扫描耗时长,锁定频段能明显缩短拨号时间。这个技巧在拔测(现场信号极差的情况下)时尤其有效,有时候能帮你在别人都掉线的时候保住业务通路。