资讯动态

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

发布时间:2026/10/8 5:04:22 来源:尧图企业网站定制
简介该资源为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_LinuxAndroid_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 个 DLCIDLCI 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每个逻辑通道一个实例里面有stateCLOSED、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 是 bionictermios结构体有些字段和 glibc 不一样但这份 V2.0.1 已经做了适配直接ndk-build或者用 Android.mk 编就行。编译前检查Makefile里的-DANDROID宏如果目标平台是 Android必须加上否则打开设备节点的路径会错。# Linux 下编译 make clean make CCarm-linux-gnueabihf-gcc CFLAGS-O2 -Wall -DANDROID0 # 生成 gsm0710muxd 可执行文件参数说明CC换成你的交叉编译器-DANDROID0表示 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 口通常是ttyUSB2PPP 口是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用来发 ATttyGSM2对应 DLCI 2给 PPPttyGSM3给 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-ec20chat-ec20脚本内容# /etc/ppp/chat-ec20 ABORT BUSY ABORT ERROR ABORT NO CARRIER AT OK ATE0 OK ATCGDCONT1,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持续发ATCSQ另一个跑pppd拨号。如果两边都正常说明 CMUX 的时分复用在工作。这时候用ifconfig ppp0看流量同时 AT 通道还能回信号质量。如果 PPP 一拨通 AT 就卡死多半是流控没开或者帧大小设得太小导致控制通道被挤占。把-n改成 4frame_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发 ATttyGSM2给数据。但不同 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用ATQGPSLOC按需查询而不是持续输出第二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确认串口参数省得半夜被叫起来查丢包。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑