简介:这份资源是 Quectel EC20 模块在 Linux 与 Android 平台下的 CMUX 驱动 V2.0.1 版本,面向嵌入式开发、物联网终端及车载通信方向的工程师,用于解决单一 UART 接口上数据、语音、短信等多业务并发传输的问题。压缩包共 3 个文件,包含 1 个 C 源码文件、1 个 Makefile 和 1 份 PDF 用户指南,整体约 354KB,源码可直接编译定制,Makefile 负责构建,PDF 则说明驱动的安装配置与 API 调用方式。资源围绕 GSM 0710 复用协议实现,涉及初始化、数据收发、错误处理及 Android HAL 适配等关键环节,可帮助读者理解 CMUX 时分复用机制,并快速将 EC20 模块集成到实际项目中。目前已有 349 人学习下载,适合需要打通模块多路通信能力的开发者参考。
1. 从一条多路串口需求说起:Quectel CMUX 驱动到底解决什么问题
模组只引出一路物理串口,却要同时跑 AT 命令、PPP 拨号、GNSS 数据上报,甚至还要留一条通道给固件升级——这是做 4G/LTE 模组方案时几乎绕不开的场面。Quectel 的 EC20 系列在 Linux 和 Android 上被大量集成,而Quectel_Linux&Android_CMUX_Driver_V2.0.1_Follow_ec20cmux_gsm0701这个标题指向的,正是把一路物理串口虚拟成多路逻辑通道的 CMUX(GSM 07.10 多路复用)驱动方案。它解决的核心矛盾很朴素:硬件串口不够用,但上层业务又要求各通道互不阻塞。适合正在做嵌入式 Linux 或 Android 模组集成、被串口资源卡住的工程师,尤其是那些已经能跑通 AT 但一上数据业务就抢串口的场景。
2. CMUX 与 GSM 07.10:多路复用凭什么能在一根线上跑
2.1 物理串口不够用时的三种常见解法
在动手之前,先把选型逻辑理清楚。当模组只有一路可用串口,而业务需要多路并发时,行业里常见三条路:一是换模组或换硬件,选带多路 UART 的型号,成本最高但最省心;二是用 USB 接口,EC20 本身有 USB 通道,走 USB 的 QMI/MBIM 或 ECM 模式,但很多定制板子只引了 UART;三是软件层多路复用,也就是 CMUX。CMUX 的价值在于不改硬件、不加成本,纯靠协议把一条串口切成多条逻辑通道。代价是引入协议开销和驱动复杂度,对时序和流控更敏感。我一般会先确认硬件是否真的只有一路 UART,如果 USB 可用,优先走 USB 方案,CMUX 留给 UART-only 的板子。
2.2 GSM 07.10 帧结构:CMUX 的协议底座
CMUX 不是 Quectel 自创,它遵循 3GPP 的 GSM 07.10 规范。理解帧结构是排错的前提。基本帧由标志位、地址字段、控制字段、长度字段、信息字段和校验字段组成。标志位固定为0xF9,用来界定帧边界;地址字段里的 DLCI 标识逻辑通道号,EA 位表示地址是否结束;控制字段区分信息帧(I 帧)、监控帧(S 帧)和无编号帧(U 帧),S 帧负责流控,U 帧负责通道建立和参数协商;长度字段用 EA 位标记是否还有后续字节;信息字段承载实际数据;FCS 是校验。多路复用的本质,就是驱动把上层不同业务的数据打上不同 DLCI,封装成帧后从同一物理串口发出,接收端再按 DLCI 分发回去。
2.3 逻辑通道分配:DLCI 0 与 DLCI 1 的分工
通道分配有约定俗成的规矩。DLCI 0 是控制通道,专门跑 CMUX 自身的命令,比如通道建立、流控协商、链路状态查询,它不承载业务数据。DLCI 1 通常映射为 AT 命令通道,上层发 AT 就走这里。DLCI 2 及以上一般分配给数据业务,比如 PPP。有些实现会把 GNSS 单独放一个 DLCI。分配不是死的,但驱动和模组侧必须一致,否则会出现「AT 能通、数据不通」或者「通道建立后收不到响应」的玄学现象。下面这段伪代码展示了驱动侧打开一个逻辑通道时的典型流程。
/* 打开指定 DLCI 的逻辑通道,先发 SABM 建立,再等 UA 确认 */ int cmux_open_channel(int fd, int dlci) { unsigned char frame[64]; int len = build_sabm_frame(frame, dlci); /* 构造 SABM 帧 */ write(fd, frame, len); /* 从物理串口发出 */ /* 等待模组回 UA 帧,超时则重试,重试次数一般设 3 次 */ if (wait_for_ua(fd, dlci, 1000) != 0) { return -1; /* 通道建立失败,检查 DLCI 是否被占用 */ } return 0; }这段代码的关键在build_sabm_frame和wait_for_ua。SABM 是设置异步平衡模式的 U 帧,模组收到后回 UA 表示确认。超时时间 1000ms 是经验值,太短会在模组忙时误判失败,太长会拖慢启动。重试 3 次是常见做法,再多说明链路本身有问题,该查硬件了。
2.4 流控与帧边界:为什么你的 CMUX 会丢数据
丢数据是 CMUX 最常见的翻车点。原因通常有两个:一是流控没开或没配对,物理串口的 RTS/CTS 没接好,或者驱动里没启用硬件流控,高速数据一来缓冲区就溢出;二是帧边界处理错误,接收端把两个帧粘在一起或者把一个帧拆开。GSM 07.10 用0xF9做标志位,但数据里也可能出现0xF9,所以发送端要做转义,接收端要按规则还原。很多简化实现省掉了转义,低速 AT 命令看不出问题,一上 PPP 大数据量就崩。排查时先看物理层流控,再看驱动里的转义逻辑,最后用抓包工具看原始字节流,确认帧边界是否完整。
3. 在 Linux 上把 CMUX 驱动跑起来:从内核配置到通道验证
3.1 内核串口配置与驱动编译的必调项
Linux 侧的第一步是确认串口驱动本身没问题。EC20 的 UART 通常挂在平台串口或 USB 转串口芯片下,先确认/dev/ttyS*或/dev/ttyUSB*能正常收发 AT。然后编译 CMUX 驱动,它一般以内核模块或字符设备驱动形式提供。内核配置里要打开CONFIG_SERIAL_*相关选项,如果驱动依赖tty_ldisc(线路规程),还要确认CONFIG_LDISC_AUTOLOAD没被关掉。编译命令通常是标准的模块编译流程:
# 进入驱动源码目录,指定内核源码路径 make -C /lib/modules/$(uname -r)/build M=$(pwd) modules # 加载模块,注意先加载依赖的串口驱动 sudo insmod cmux_driver.ko # 查看是否生成对应的设备节点 ls /dev/cmux*make -C指定内核构建目录,M=$(pwd)告诉内核模块源码在当前目录。加载后如果没看到/dev/cmux*,先dmesg | tail看内核日志,常见错误是符号未导出或内核版本不匹配。设备节点命名各版本可能不同,有的叫cmux0,有的按 DLCI 生成cmux_at、cmux_ppp,以实际为准。
3.2 用 cmux 工具建立通道并绑定 AT 口
驱动加载后,需要用户态工具或 ioctl 来建立逻辑通道。常见做法是提供一个cmux命令行工具,或者通过ioctl在应用里控制。下面是一个典型的通道建立和 AT 验证流程:
# 假设物理串口是 /dev/ttyS1,先配置波特率和流控 stty -F /dev/ttyS1 115200 crtscts raw -echo # 通过驱动接口建立 DLCI 1 作为 AT 通道 cmux -d /dev/ttyS1 -c 1 # 此时应出现 /dev/cmux_at 或类似节点,用它发 AT echo -e "AT\r" > /dev/cmux_at cat /dev/cmux_atstty里的crtscts开启硬件流控,raw关闭行缓冲处理,-echo防止回显干扰。cmux -c 1是建立 DLCI 1 的常见参数形式,具体选项名以驱动文档为准。发AT后如果收到OK,说明控制通道和 AT 通道都通了。收不到就回到上一章查流控和帧边界。
3.3 把 PPP 拨号挂到独立 DLCI 上
AT 通道通了,接下来把数据业务分离出去。PPP 通常挂在 DLCI 2。先建立通道,再让pppd绑定到对应设备节点:
# 建立 DLCI 2 作为数据通道 cmux -d /dev/ttyS1 -c 2 # 用 pppd 在 cmux_data 上拨号,具体 APN 按运营商填 pppd /dev/cmux_data 115200 noauth defaultroute usepeerdns \ connect 'chat -v "" "AT+CGDCONT=1,\"IP\",\"your.apn\"" OK "ATD*99#" OK'noauth表示不验证对端,defaultroute自动加默认路由,usepeerdns使用运营商下发的 DNS。chat脚本先设置 PDP 上下文再拨号。APN 必须换成实际值,否则拨号会停在ATD*99#没响应。如果 PPP 起不来但 AT 正常,重点查 DLCI 2 是否建立成功、模组是否支持在该通道跑数据。
3.4 通道并发时的日志观察与验证方法
验证多通道是否真正并发,不能只看单个通道。开三个终端,一个持续发 AT,一个跑 PPP,一个收 GNSS。观察是否互相阻塞。内核日志里看 CMUX 驱动的收发计数,如果某个 DLCI 的发送队列持续增长,说明对端没及时取走数据,可能是流控或模组侧通道没开。/proc/tty/driver/下有时能看到串口统计。我习惯用cat /dev/cmux_at配合while循环做长时间压测,跑半小时不丢响应才算稳。
4. Android 侧集成:从 HAL 到 RIL 的通道对接
4.1 Android 串口权限与 SELinux 策略
Android 比 Linux 多一层权限和 SELinux。CMUX 驱动加载后生成的设备节点,默认权限通常不允许 radio 或 rild 访问。需要改ueventd.rc或init.rc给节点设权限和属主,常见是radio:radio 0660。SELinux 方面,要给 rild 或对应进程打标签,允许它读写cmux设备。策略没配好,现象是驱动加载正常、手动echo也能通,但 rild 一启动就报 permission denied。用dmesg和logcat一起看,avc: denied会直接指出缺哪条规则。
4.2 把 CMUX 通道映射到 RIL 的 AT 通道
Android 的 RIL 默认通过一个 AT 通道和模组通信。集成 CMUX 时,要把 RIL 的 AT 通道指向 DLCI 1 对应的设备节点。这通常在rild的启动参数或board_config里配置。有些方案会写一个中间层,把 RIL 的读写转发到 CMUX 通道。关键是保证 RIL 打开的就是 AT 通道,而不是物理串口。如果 RIL 还占着物理串口,CMUX 驱动就没法接管,会出现通道建立失败或数据错乱。
4.3 数据通道与 Android 网络栈的衔接
数据通道在 Android 上一般走 PPP 或 RMNET。如果模组在 CMUX 下跑 PPP,Android 侧需要pppd或对应的数据服务绑定到 DLCI 2 节点。Android 的网络管理比桌面 Linux 复杂,要处理路由、DNS、网络能力上报。常见做法是让rild或netd配合,把 PPP 接口纳入 Android 网络框架。这一步容易出问题的是接口命名和路由表,ip route看默认路由是否指向 PPP 接口,getprop看网络状态是否上报。
4.4 用 logcat 定位通道建立失败
Android 上排错主要靠logcat。过滤RIL、CMUX、pppd关键字。通道建立失败时,先看驱动层有没有收到 SABM 的 UA 响应,再看 RIL 有没有成功打开设备节点。常见日志是open /dev/cmux_at failed: Permission denied或UA timeout。前者查权限和 SELinux,后者查物理链路和流控。logcat -b radio专门看 radio 相关日志,比主日志更聚焦。
5. 避坑与排查:CMUX 集成中最容易翻车的五件事
5.1 现象:AT 能通但 PPP 一拨就断
原因:物理串口流控没开或 RTS/CTS 没接,PPP 大数据量时缓冲区溢出。解决:确认stty里crtscts生效,硬件上 RTS/CTS 必须交叉连接,驱动里启用硬件流控。
5.2 现象:通道建立成功但收不到模组响应
原因:DLCI 分配和模组侧不一致,或者控制通道 DLCI 0 没先建立。解决:先确认 DLCI 0 正常,再按模组文档核对各业务对应的 DLCI 号,不要想当然。
5.3 现象:Android 上 rild 启动报权限错误
原因:设备节点属主或 SELinux 策略没配。解决:改ueventd.rc设radio:radio 0660,补 SELinux 规则允许 rild 读写,用dmesg | grep avc定位具体缺失项。
5.4 现象:长时间跑数据后 CMUX 驱动崩溃或丢帧
原因:帧转义处理不完整,数据里出现0xF9导致帧边界错乱;或接收缓冲区太小。解决:检查驱动转义逻辑,增大缓冲区,压测时用抓包工具确认原始字节流。
5.5 现象:驱动加载成功但找不到设备节点
原因:内核版本不匹配、符号未导出,或驱动依赖的串口驱动没先加载。解决:dmesg看具体报错,确认内核源码路径正确,按依赖顺序加载模块。
6. 进阶技巧:用脚本做 CMUX 通道的自动化压测与回归
通道跑通只是开始,真正上线前得做压测和回归。我一般写一个脚本,同时压 AT 通道和数据通道,跑够时间看稳定性。下面这个 bash 脚本做两件事:持续发 AT 并校验响应,同时统计丢包。
#!/bin/bash # CMUX 通道压测:AT 通道持续请求,统计成功率和超时 AT_DEV=/dev/cmux_at COUNT=0 FAIL=0 while [ $COUNT -lt 1000 ]; do echo -e "AT\r" > $AT_DEV # 读响应,超时 1 秒算失败 if timeout 1 cat $AT_DEV | grep -q "OK"; then : # 成功,什么都不做 else FAIL=$((FAIL+1)) fi COUNT=$((COUNT+1)) sleep 0.1 done echo "total=$COUNT fail=$FAIL"脚本里timeout 1是关键,防止cat卡死。sleep 0.1控制频率,太快会压垮模组。跑完看fail数量,超过 1% 就要查流控和缓冲区。数据通道的压测可以用ping -f或iperf配合 PPP 接口,观察吞吐和断线。回归时把不同波特率、不同流控配置都跑一遍,记录哪组最稳。我自己的习惯是每次改驱动或改硬件,先跑这个脚本十分钟,过了再上业务。CMUX 这东西,玄学问题多半是流控和帧边界,压测能提前把雷排掉。希望帮到你。
本文还有配套的精品资源,点击获取