news 2026/10/8 5:04:18

EC20 CMUX驱动实战:GSM 07.10多路复用与Linux/Android适配

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EC20 CMUX驱动实战:GSM 07.10多路复用与Linux/Android适配

简介:该资源为Quectel EC20模块在Linux与Android平台下的CMUX驱动包V2.0.1版,面向嵌入式开发、物联网终端及车载通信方向的工程师,用于解决单一UART接口上数据、语音、短信等多业务并发传输的问题。压缩包共3个文件,约354KB,包含一份C语言驱动源码(gsm0710muxd_bp.c)、一份Makefile编译脚本以及一份PDF用户指南,分别对应驱动实现、编译构建与配置说明,便于开发者快速集成与二次定制。资源围绕CMUX时分复用机制展开,涵盖Linux内核适配、Android HAL层对接、唤醒与睡眠控制、数据流收发及错误处理等关键环节,并附有API调用与测试验证思路。目前已有349人学习下载,适合需要在不增加硬件接口的前提下实现多任务并行通信的开发者参考,可帮助理解EC20模块在双平台下的驱动结构与移植要点。

1. 为什么 EC20 的 CMUX 驱动值得单独拆一遍

如果你手上有 EC20 模组,跑在 Linux 或者 Android 上,只用一个 UART 却想同时跑 AT 指令、PPP 拨号、GNSS 数据回传,那你迟早会撞上 CMUX。EC20 本身支持 GSM 07.10 多路复用,但模组只给了协议,真正让它在系统里跑起来的是驱动和守护进程。这份 Quectel_Linux&Android_CMUX_Driver_V2.0.1 就是干这个的:它把一路物理串口虚拟成多个逻辑通道,让数据、语音、短信、GPS 各走各的道,互不打架。包里带了gsm0710muxd_bp.c、Makefile和一份用户指南 PDF,源码结构不复杂,但坑点集中在通道映射、流控和 Android 的 HAL 适配上。适合谁?做嵌入式 Linux 项目、车载 T-Box、工业网关的兄弟,尤其是那些被“AT 口被 PPP 占死”折磨过的。下面我按实际复现顺序,把这份驱动从编译到跑通拆开讲。

2. 先搞懂 GSM 07.10 与 CMUX 的通道映射逻辑

2.1 物理串口怎么变成多条逻辑通道

CMUX 的核心是 GSM 07.10 协议里的帧结构。它把一路 UART 上的字节流切成一个个帧,每个帧带地址字段(DLCI),用来区分不同通道。EC20 默认支持 4 个 DLCI:DLCI 0 是控制通道,DLCI 1 通常给 AT 指令,DLCI 2 给 PPP 或者数据业务,DLCI 3 可以给 GNSS 或短信。驱动要做的事,就是把这些 DLCI 映射成 Linux 下的/dev/ttyGSMx或者 Android 下的/dev/gsmttyx设备节点。你打开/dev/ttyGSM1写 AT,实际上数据被封装成 DLCI 1 的帧,从物理串口发出去;模组回帧,驱动再解出 DLCI 1 的内容,吐给对应的逻辑通道。这个过程对上层应用透明,但前提是驱动得正确解析帧头、校验和,还得处理帧边界。

常见做法是:驱动里维护一个状态机,收到 0x7E 开始标志就进入帧解析,遇到转义字符 0x7D 要做异或 0x20 还原。gsm0710muxd_bp.c里这部分逻辑写得比较直白,你可以直接看gsm0710_input函数。参数上,frame_size一般设 64 或 128 字节,太大增加延迟,太小增加开销。EC20 的 UART 波特率建议 115200 起步,如果跑 PPP 加 GNSS 并发,最好上 921600,不然帧排队会明显。

2.2 驱动源码里几个关键结构体

打开gsm0710muxd_bp.c,先找struct gsm0710_context。它保存了物理串口的 fd、各个 DLCI 的状态、帧重组缓冲区。另一个重要的是struct gsm0710_dlci,每个逻辑通道一个实例,里面有state(CLOSED、OPENING、OPEN)、mtu、rx_buffer。驱动初始化时会调用gsm0710_open,把物理串口配成 raw 模式,关掉回显和流控,然后启动一个select或者poll循环。这里有个细节:EC20 的硬件流控(RTS/CTS)必须打开,否则高速率下丢帧是家常便饭。代码里通过termios设置CRTSCTS,如果你用的串口驱动不支持,就得降速或者改用软件流控,但软件流控在 CMUX 下容易和帧转义冲突,不推荐。

// 摘自 gsm0710muxd_bp.c 的串口初始化片段 struct termios tio; tcgetattr(fd, &tio); tio.c_cflag |= CRTSCTS; // 硬件流控,EC20 高速率必备 tio.c_cflag &= ~CSIZE; tio.c_cflag |= CS8; // 8 数据位 tio.c_cflag &= ~PARENB; // 无校验 tio.c_cflag &= ~CSTOPB; // 1 停止位 tio.c_iflag &= ~(IXON | IXOFF | IXANY); // 关软件流控 cfsetispeed(&tio, B921600); cfsetospeed(&tio, B921600); tcsetattr(fd, TCSANOW, &tio);

这段代码的逻辑是:先拿到当前串口属性,强制打开硬件流控,数据位 8、无校验、1 停止位,这是 GSM 07.10 的标准配置。软件流控必须关掉,因为 XON/XOFF 字符(0x11/0x13)可能出现在帧数据里,导致误触发。波特率设 921600 是为了给多通道留余量,如果你只跑 AT 和短信,115200 也够。改完tcsetattr立即生效。注意:有些 USB 转串口芯片在 921600 下不稳定,得看具体硬件。

2.3 编译前必须确认的内核选项

这份驱动是用户态守护进程,不是内核模块,所以不依赖内核配置,但它需要ppp和tun支持。Linux 下编译很简单,Makefile里指定了CC和CFLAGS,你只需要确认交叉编译工具链路径对。Android 下稍微麻烦,因为 Android 的 libc 是 bionic,termios结构体有些字段和 glibc 不一样,但这份 V2.0.1 已经做了适配,直接ndk-build或者用 Android.mk 编就行。编译前检查Makefile里的-DANDROID宏,如果目标平台是 Android,必须加上,否则打开设备节点的路径会错。

# Linux 下编译 make clean make CC=arm-linux-gnueabihf-gcc CFLAGS="-O2 -Wall -DANDROID=0" # 生成 gsm0710muxd 可执行文件

参数说明:CC换成你的交叉编译器,-DANDROID=0表示 Linux 平台。如果编译报错找不到sys/ioctl.h,检查工具链的 sysroot 是否完整。Android 下用mm或者ndk-build,生成的二进制推到/system/bin,记得给可执行权限。

3. 在 Linux 上把 CMUX 跑起来:从加载到拨号

3.1 启动守护进程与虚拟通道生成

编译出gsm0710muxd后,先别急着插模组。把 EC20 的 USB 口插上,确认内核识别出option驱动或者qcserial,一般会出现/dev/ttyUSB0到/dev/ttyUSB3。EC20 的 AT 口通常是ttyUSB2,PPP 口是ttyUSB3,但不同固件版本可能不一样,用ls /dev/ttyUSB*和cat /sys/kernel/debug/usb/serial/...确认。然后启动 muxd:

# 假设物理 AT 口是 /dev/ttyUSB2,波特率 921600 ./gsm0710muxd -d /dev/ttyUSB2 -b 921600 -n 4 -p /var/run/gsm0710muxd.pid

参数含义:-d指定物理串口,-b波特率,-n最大 DLCI 数量(EC20 用 4 就够),-p写 pid 文件。启动后,驱动会创建/dev/ttyGSM0到/dev/ttyGSM3。ttyGSM0是控制通道,一般不用;ttyGSM1对应 DLCI 1,用来发 AT;ttyGSM2对应 DLCI 2,给 PPP;ttyGSM3给 GNSS 或短信。你可以用microcom -D /dev/ttyGSM1发AT测试,如果返回OK,说明通道映射正确。

提示:如果/dev/ttyGSM*没出现,先看 muxd 日志。常见原因是物理串口被其他进程占用,比如 ModemManager 会自动抓走 ttyUSB2。用systemctl stop ModemManager临时停掉,或者把模组加入 ModemManager 的黑名单。

3.2 用 pppd 在 DLCI 2 上拨号

AT 通道通了之后,拨号走/dev/ttyGSM2。写一个简单的 pppd 脚本:

# /etc/ppp/peers/ec20-cmux /dev/ttyGSM2 921600 noauth debug nodetach ipcp-accept-local ipcp-accept-remote connect '/usr/sbin/chat -v -f /etc/ppp/chat-ec20'

chat-ec20脚本内容:

# /etc/ppp/chat-ec20 ABORT "BUSY" ABORT "ERROR" ABORT "NO CARRIER" "" AT OK ATE0 OK AT+CGDCONT=1,"IP","cmnet" OK ATD*99# CONNECT ""

逻辑说明:先发AT确认通道活着,然后关回显,设置 APN 为cmnet(根据你的运营商改),最后拨*99#。CONNECT表示 PPP 链路建立。启动pppd call ec20-cmux,如果看到local IP address和remote IP address,说明拨号成功。注意:CMUX 下 PPP 的 MRU 建议设 1500 以下,因为帧封装会增加开销,太大容易分片。

3.3 并发验证:AT 与 PPP 同时跑

真正的考验是并发。开两个终端,一个microcom -D /dev/ttyGSM1持续发AT+CSQ,另一个跑pppd拨号。如果两边都正常,说明 CMUX 的时分复用在工作。这时候用ifconfig ppp0看流量,同时 AT 通道还能回信号质量。如果 PPP 一拨通 AT 就卡死,多半是流控没开或者帧大小设得太小导致控制通道被挤占。把-n改成 4,frame_size在代码里改成 128,重新编译再试。

4. Android 平台适配:HAL 层与权限的坑

4.1 Android 下设备节点路径与权限

Android 虽然内核是 Linux,但设备节点权限管得严。gsm0710muxd在 Android 上跑,创建的/dev/ttyGSM*默认属主是 root,普通应用访问不了。你需要改ueventd.rc或者用chmod脚本在 init 阶段改权限。常见做法是在init.rc里加:

# 在 init.rc 的 on boot 阶段 chmod 0660 /dev/ttyGSM1 chown radio radio /dev/ttyGSM1 chmod 0660 /dev/ttyGSM2 chown radio radio /dev/ttyGSM2

这样 radio 组的进程(比如 RIL)才能打开。另外 Android 的gsm0710muxd需要CAP_NET_ADMIN权限来操作 PPP,建议用setcap或者直接以 root 跑。如果集成到系统里,把 muxd 做成一个 native service,在init.rc里service段启动,seclabel设成u:r:gsm0710muxd:s0,否则 SELinux 会拦。

4.2 与 RIL 的通道分配

Android 的 RIL 通常用ttyGSM1发 AT,ttyGSM2给数据。但不同 Android 版本 RIL 库对通道号有硬编码,你得看hardware/ril/reference-ril/里的AT_CHANNEL和PPP_CHANNEL定义。如果 RIL 默认找/dev/ttyUSB2,你得改ril.cpp或者用property_set覆盖。更稳妥的办法是让 muxd 创建符号链接:ln -s /dev/ttyGSM1 /dev/ttyUSB2,这样 RIL 不用改。但注意,符号链接要在 muxd 启动后创建,顺序不能反。

# 在 muxd 启动脚本里加 while [ ! -e /dev/ttyGSM1 ]; do sleep 1; done ln -sf /dev/ttyGSM1 /dev/ttyUSB2 ln -sf /dev/ttyGSM2 /dev/ttyUSB3

这段脚本等ttyGSM1出现后,把原来的ttyUSB2链接过去。这样上层 RIL 以为自己在用物理口,实际走的是 CMUX 逻辑通道。参数上,sleep 1是轮询间隔,别设太短,否则 CPU 占用高。

4.3 功耗管理:睡眠与唤醒

Android 设备对功耗敏感,EC20 在 CMUX 下如果一直保持物理串口活跃,待机电流会偏高。驱动里有个gsm0710_suspend和gsm0710_resume,通过ioctl控制。你可以在init.rc里监听earlysuspend事件,调echo mem > /sys/power/state之前先让 muxd 进入低功耗模式。具体做法是发一个自定义ioctl给 muxd,让它关闭物理串口的接收,只保留控制通道的心跳。唤醒时再打开。注意:如果 PPP 正在传输,不能 suspend,否则丢包。常见做法是加一个引用计数,有数据业务时禁止睡眠。

5. 避坑排查:通道错乱、丢帧与编译报错

5.1 现象:AT 通道返回乱码或者无响应

原因:物理串口波特率不匹配,或者流控没开导致帧丢失。EC20 的 UART 默认可能是 115200,但有些固件是 921600,你设错了就全是乱码。另外,如果CRTSCTS没打开,高速率下模组发的帧会被截断,驱动解析失败,表现为 AT 无响应。

解决:先用stty -F /dev/ttyUSB2 115200 raw -echo手动测 AT,确认波特率。然后在 muxd 启动参数里改-b。流控必须开,检查termios里CRTSCTS是否置位。如果硬件不支持流控,降速到 115200 并减小frame_size到 64。

5.2 现象:PPP 拨号成功但很快断线

原因:CMUX 的控制通道(DLCI 0)和 PPP 通道(DLCI 2)争抢物理串口,如果控制通道的心跳包太频繁,会挤占 PPP 的带宽。另外,PPP 的lcp-echo-interval设得太短,在 CMUX 下延迟增大,容易误判断线。

解决:把lcp-echo-interval改成 30 秒,lcp-echo-failure改成 5。在 muxd 代码里把控制通道的轮询间隔从 1 秒改成 5 秒。如果还断,检查物理串口的错误计数cat /proc/tty/driver/usbserial,看有没有 CRC 错误。

5.3 现象:Android 下 muxd 启动即崩溃

原因:SELinux 策略不允许 muxd 打开/dev/ttyUSB2或者创建/dev/ttyGSM*。Android 的avc: denied日志会打在dmesg里。

解决:抓dmesg | grep avc,根据拒绝的权限写te规则。常见的是allow gsm0710muxd serial_device:chr_file { open read write ioctl }。如果不想改策略,临时用setenforce 0验证,但生产环境必须补规则。

5.4 现象:编译报错undefined reference to 'cfsetispeed'

原因:交叉编译工具链的 libc 版本太老,或者_GNU_SOURCE没定义。cfsetispeed在termios.h里,但有些精简 libc 不提供。

解决:在Makefile的CFLAGS里加-D_GNU_SOURCE,或者直接用cfsetspeed替代。如果还不行,手动实现波特率设置,通过ioctl的TCSETS传termios2结构体。

5.5 现象:多个 DLCI 同时打开时内核报ttyGSMx: buffer overflow

原因:驱动里每个 DLCI 的接收缓冲区太小,或者select循环里没有及时读走数据。EC20 在 GNSS 开启时数据量很大,如果rx_buffer只有 1024 字节,很快就满。

解决:把gsm0710_dlci里的rx_buffer改成动态分配,初始 4096 字节,满时翻倍。或者在gsm0710_input里加流控,当缓冲区超过 80% 时,向模组发FC帧暂停发送。EC20 支持 GSM 07.10 的流控命令,具体看用户指南 PDF 第 3.2 节。

6. 进阶:用 CMUX 同时跑 GNSS 与数据回传的调优技巧

GNSS 和数据回传同时跑,是 CMUX 最吃力的场景。EC20 的 GNSS 数据从 DLCI 3 出来,默认是 NMEA 0183 格式,每秒一次。如果你还用 DLCI 2 跑 PPP 上传传感器数据,物理串口的带宽会被占满。我一般会做三件事:第一,把 GNSS 的 NMEA 输出频率从 1Hz 降到 0.2Hz,用AT+QGPSLOC按需查询,而不是持续输出;第二,PPP 的 MTU 从 1500 降到 1280,减少分片;第三,在 muxd 里给 DLCI 3 设一个较低的优先级,当 DLCI 2 有数据时,先发 DLCI 2 的帧。

具体调优参数可以参照下面这张表:

参数默认值调优值说明
物理串口波特率115200921600给多通道留余量
frame_size64128减少帧头开销,但增加延迟
GNSS 输出频率1Hz0.2Hz降低带宽占用
PPP MTU15001280避免分片
控制通道轮询间隔1s5s减少对数据通道的干扰
DLCI 3 优先级默认低数据通道优先

改完这些,用iperf跑一下 PPP 的吞吐,同时cat /dev/ttyGSM3看 GNSS 有没有丢句。如果 NMEA 语句完整,PPP 吞吐在 500kbps 以上,就算调通了。最后提醒一句:每次改完frame_size和波特率,都要重新编译 muxd,别只改启动参数,因为帧缓冲区大小是编译期决定的。从那以后我每次上电前都强制走一遍stty确认串口参数,省得半夜被叫起来查丢包。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 5:03:44

语法高亮管理工具caveman:Emacs里统一规则、tree-sitter与正则的配置实战

第一次看到“caveman”这个词,我脑子里跳出的是石器时代拿着棍子追野猪的画面,接着想到的是程序员圈里那个“原始人调试法”——不整花活,先用最朴素的打印输出把逻辑跑通再说。后来在Emacs的配置社区里频繁碰到这个词,才发现它还…

作者头像 李华
网站建设 2026/10/8 5:03:41

Agent-Reach:解决多Agent协同中工具调用触达失败与链路雪崩的实践

凌晨两点十分,值班群突然炸了。用户的财务核对Agent在链式调用内部ERP工具时连续触达失败,自动重试又把下游对账Agent的队列全部挤满,最后整个多Agent任务全链路崩掉。那晚我在日志里翻了三个小时,最终发现根因根本不是模型能力问…

作者头像 李华
网站建设 2026/10/8 5:03:26

Codex安装失败根源:Windows信任链与证书验证机制解析

1. 项目概述:Codex 微软商店安装失败,不是“软件坏了”,而是系统信任链断了 Codex 这个名字最近在开发者圈子里反复刷屏,但很多人点开微软商店搜索“Codex”后,看到的不是绿色的“获取”按钮,而是一行灰字…

作者头像 李华
网站建设 2026/10/8 5:03:07

LangChain Agent 切面钩子实战:解耦日志、鉴权与限流

1. 为什么要在 LangChain Agent 里引入切面钩子1.1 从一个真实的痛点说起我最早做 Agent 项目的时候,业务代码和通用逻辑是搅在一起的。一个典型的工具调用函数长这样:先打一行日志,再判断一下用户权限,然后做参数校验&#xff0c…

作者头像 李华
网站建设 2026/10/8 5:03:06

在线小说阅读平台毕设实战:SpringBoot+Vue+MySQL全栈开发与避坑指南

简介:采用 Java、Spring Boot、Vue 和 MySQL 技术栈开发的在线小说阅读平台,完整源码与数据库文件,面向需要毕业设计、课程设计或期末大作业参考的学生及开发者。项目功能完善,包含用户注册登录、小说检索浏览、阅读进度保存、章节…

作者头像 李华
网站建设 2026/10/8 5:02:18

Unity 2D横版闯关游戏模板:SunnyLand工程拆解与手感调参实战

简介:这是一份面向2D横版闯关游戏爱好者与Unity入门开发者的《SunnyLand电脑版》可运行游戏资源包,适合想体验高口碑独立游戏、或通过拆解成品学习Unity项目结构的读者。压缩包为zip格式,整体约64.19MB,内含游戏主程序、Unity运行…

作者头像 李华