
简介本资源是一份面向嵌入式通信、移动终端开发及物联网协议研究者的CMUXChannel Multiplexer协议入门学习文档聚焦TE终端设备与MS移动台间串口复用通信机制解决多路逻辑通道在单物理链路上的可靠共存问题。文档以PDF格式呈现共1个文件大小仅65KB内容精炼、结构清晰适合作为协议解析、AT指令透传、Modem驱动开发等场景的速查参考。全文系统剖析CMUX帧结构七大核心域——起始/结束标志0xF9/0x7E、地址域虚拟串口编号映射、控制域UIH帧识别、长度域、数据域、校验域并结合真实十六进制示例F9 09 EF 13 0D 0D 0A 52 49 4E 47 0D 0A 2E F9逐字节解读帮助读者建立从协议规范到实际报文的完整理解链条。目前已有161人学习下载是初学者掌握CMUX协议原理、帧构造逻辑与典型应用场景的高性价比入门材料。1. CMUX协议不是“多路复用”的泛泛而谈它是AP与MODEM之间串口资源的硬核调度协议专治TE-MS通信中串口不够用、AT指令乱序、通道抢占翻车你手头有一块4G模组想同时跑GPS定位、发送AT指令查信号、收发短信、上传传感器数据——但模组只暴露一个UART物理口。这时候光靠“开多个线程读写串口”会直接崩AT响应错乱、GPS NMEA帧被截断、短信回执丢包。CMUXChannel Multiplexer就是为这种场景生的它不新增硬件而是在单个UART上虚拟出最多16个独立串口Channel 0~15让AP端像操作真实串口一样把GPS、AT、SMS、PPP等业务流彻底隔离。这不是Linux TTY的软复用而是嵌入式通信层的帧级协议——每个字节都带地址域、控制域、校验域连起始/结束标志都强制用0xF9基本模式或0x7E高级模式。文档里那个F9 09 EF 07 0D 0D 0A 35 F9十六进制序列就是真实设备抓到的CMUX帧它背后是AP向虚拟串口2发了一条RING\r\n.指令。如果你正在调试高通/移远/广和通模组的AT通道管理、做车载T-Box的多业务并发、或是开发支持CMUX的Linux PPP拨号守护进程这份《CMUX协议学习.pdf》就是你绕不开的底层契约书——它不讲理论推导只拆帧结构、标bit位、给实测值连“为什么地址域第3-4bit是通道号”这种玄学设计都画出了二进制对齐图。2. CMUX帧结构解剖从0xF9开始逐字节逆向为什么地址域要右对齐、UIH帧必须带长度域CMUX协议的可靠性全压在7个字段的严格定义上。它不是TCP那种可协商的协议而是嵌入式设备固件里硬编码的解析逻辑。任何字段错一位整帧就被丢弃。下面按实际抓包顺序逐字节还原这个协议的“肌肉纹理”。2.1 起始与结束标志域0xF9不是约定是硬件电平触发的硬约束CMUX帧以0xF9基本模式或0x7E高级模式开头和结尾。注意这不是软件层面的“约定”而是模组UART接收器的硬件状态机设计。当PHY层检测到连续8位为11111001时立即触发帧同步中断后续字节才进入CMUX解析流程。若用0x7E01111110则需启用比特填充Bit Stuffing机制——即数据域中每出现5个连续1自动插入0防止误判为flag。绝大多数国产4G模组默认用基本模式0xF9因为省去填充/解填充开销更适合低算力MCU。提示抓包时若看到0xF9后紧跟0xF9说明前一帧未正确结束属于异常帧应丢弃。正常帧必为F9 ... F9闭合。2.2 地址域通道号藏在bit3-bit4且最低位是bit0右对齐地址域占1字节格式为0000 C2 C1 C0C2/C1/C0为通道控制位。关键点在于bit3和bit4共同表示通道号且bit0是最低位。例如0x09→ 二进制00001001→ bit31, bit40 → 通道号 (11) | (00)2即虚拟串口20x0D→ 二进制00001101→ bit31, bit41 → 通道号 (11) | (10)3即虚拟串口3这里极易踩坑有人误将0x09当作十进制9或按左对齐解读bit位。实际硬件解析器只读bit3-bit4其余bit必须为0否则视为非法地址。通道0固定为控制信道Control Channel用于传输CMUX配置帧如SABM建立、UA确认通道1~15为用户信道User Channel对应虚拟串口1~15。2.3 控制域UIH帧才是数据载体UI帧不带长度域控制域决定帧类型和处理方式。常见值0xEF11101111→ UIH帧Unnumbered Information with Header check携带有效载荷必须有长度域和校验域用于传输AT指令、NMEA数据等。0x7F01111111→ UI帧Unnumbered Information无长度域、无校验域仅用于低优先级广播如模组主动上报CREG: 1实际项目中极少使用。0x9310010011→ SABM帧Set Asynchronous Balanced Mode控制信道专用用于请求建立某用户信道。0x5301010011→ UA帧Unnumbered Acknowledgement控制信道响应确认SABM成功。重点只有UIH帧才允许携带业务数据。若误用UI帧传AT指令模组会静默丢弃——因为它没有长度域无法判断数据边界。2.4 长度域7bit有效最大值127字节超长需分片长度域占1字节但最高位bit7恒为0仅bit0~bit6共7bit有效因此单帧最大数据长度为0x7F 127字节。例如0x07表示数据域为7字节。若业务数据如一条长AT命令超过127字节必须由AP端主动分片拆成多个UIH帧每帧长度≤127并保证同一业务流的帧按序发送CMUX本身不提供重排序依赖上层保证。实践中ATQISEND发送大文件时常因长度域超限导致模组返回ERROR此时需检查是否未分片。2.5 数据域纯载荷但需规避0xF9/0x7E防误触发数据域存放原始业务数据如0D 0D 0A\r\r\n、41 54 2B 43 47 41 54 3F 0DATCGATT?。唯一约束是数据域内禁止出现0xF9或0x7E基本/高级模式flag。若业务数据恰好含该字节如二进制传感器数据必须启用转义机制将0xF9替换为0xDB 0xDC0x7E替换为0xDB 0xDD类似PPP协议。模组收到后自动还原。未启用转义时0xF9会被解析为新帧起始导致后续数据全乱。2.6 校验域8bit累加和取反非CRC但足够可靠校验域为1字节计算方式为对地址域 控制域 长度域 数据域所有字节求8bit累加和再取反即0xFF - sum。例如地址域0x09 控制域0xEF 长度域0x07 数据域0x0D0x0D0x0A09EF070D0D0A 0x181→ 累加和取低8位0x81→ 取反0x7E但文档示例中校验域为0x35说明其计算范围可能包含或不包含某些字段不同模组厂商有差异。务必以目标模组AT手册为准通用做法是先用已知正确帧反推其校验算法再固化到代码中。3. CMUX初始化全流程从AT指令使能到虚拟串口映射三步走通TE-MS握手链路CMUX不是插上电就自动工作的协议它需要TEAP端和MS模组通过AT指令完成双向协商。整个过程本质是“建立控制信道→分配用户信道→绑定业务流”漏掉任一环虚拟串口就无法收发数据。以下基于实测高通MDM9x07平台梳理标准流程。3.1 第一步ATCMUX1使能协议并设置基础参数在模组上电、AT端口初始化完成后发送ATCMUX1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0参数含义按逗号分隔1使能CMUX模式0为禁用0基本模式flag0xF9设1为高级模式flag0x7E0控制信道编号固定为00用户信道起始编号通常从1开始后续12个0分别对应用户信道1~12的优先级0最低7最高此处全设为0表示无优先级区分注意此指令必须在模组处于CPIN: READY之后发送否则返回ERROR。部分模组如移远EC20需先执行ATQCFGcmux,1预配置。3.2 第二步SABM帧建立用户信道地址域指向目标通道使能CMUX后物理串口即切换为CMUX帧模式。此时不能再发普通AT指令所有操作需封装为CMUX帧。以建立虚拟串口1为例构造SABM帧控制信道0# 字节序列[flag][addr][ctrl][length][data][fcs][flag] # flag0xF9, addr0x00(控制信道), ctrl0x93(SABM), length0x02(2字节数据) # data0x01 0x00 → 通道1, 0x00为保留位 frame bytes([0xF9, 0x00, 0x93, 0x02, 0x01, 0x00]) # 计算校验域0x000x930x020x010x00 0x96 → 取反0x69 frame bytes([0x69, 0xF9])将frame写入物理串口。模组收到后若信道1空闲则回复UA帧F9 00 53 00 6C F9表示建立成功。关键点SABM帧必须发往控制信道地址域0x00且数据域首字节为用户信道号。若发往错误地址如0x01模组直接忽略。3.3 第三步UIH帧传输业务数据地址域切换至用户信道信道建立后即可向用户信道发数据。例如向虚拟串口1发ATCSQ?# 构造UIH帧地址域0x01控制域0xEF长度域0x08 # data bATCSQ?\r at_cmd bATCSQ?\r length_byte len(at_cmd).to_bytes(1, big)[0] # 0x08 # 校验域0x010xEF0x08sum(bATCSQ?\r) 0x010xEF0x080x410x540x2B0x430x530x510x3F0x0D 0x2A9 → 0xA9 → 取反0x56 frame bytes([0xF9, 0x01, 0xEF, length_byte]) at_cmd bytes([0x56, 0xF9])写入物理串口后模组解析地址域0x01将ATCSQ?转发至内部AT引擎并将响应如CSQ: 25,99封装为UIH帧地址域仍为0x01发回TE端。至此虚拟串口1完成闭环。3.4 验证信道状态ATCMUX?查询当前活动信道调试时可随时发送ATCMUX?模组返回CMUX: 1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0各字段含义1CMUX使能状态0当前模式0基本1控制信道状态1已建立1用户信道1状态1已建立后续12个0用户信道2~13状态0未建立若某信道状态为0说明SABM未成功或已被UA释放。4. 避坑CMUX调试中五个血泪经验从帧解析失败到模组锁死的真实现场还原CMUX协议看似简单但嵌入式环境下的时序、电平、缓冲区溢出等问题会让调试变成黑匣子。以下是我在某车载T-Box项目中踩过的5个典型坑每个都附带现象、根因和可落地的解决动作。4.1 现象物理串口收不到任何响应示波器看UART有数据但全是0xF9原因AP端未正确退出AT命令模式仍在发送ATCMUX1后的换行符\r\n导致模组将\r\n误解析为CMUX帧的数据域而\r\n不含合法地址域整帧被丢弃且模组进入静默状态。解决发送ATCMUX1后必须等待模组返回OK再关闭AT命令解析器切换为纯二进制帧收发模式。切勿在收到OK前就发SABM帧。4.2 现象SABM帧发出后模组无UA响应但ATCMUX?显示信道状态为0原因地址域字节错误。曾将用户信道1的地址域写成0x01正确误为0x10bit4-bit7置位模组解析0x10时发现bit3-bit400判定为非法地址静默丢弃。解决用逻辑分析仪捕获发送帧逐字节比对地址域二进制。确认0x0100000001bit3-bit4000x0900001001bit3-bit410。4.3 现象虚拟串口1能发AT指令但收不到响应虚拟串口2完全无反应原因UIH帧校验域计算错误。某次将校验域误算为sum(addrctrllendata)未取反导致模组校验失败丢弃所有UIH帧。但SABM/UA帧因长度短、校验巧合通过造成“信道建立成功但无法通信”的假象。解决用文档示例帧F9 09 EF 07 0D 0D 0A 35 F9反推算法09EF070D0D0A0x181→0x81→0x7E但示例校验域是0x35说明其计算范围不含地址域。最终确认该模组算法为sum(ctrllendata)取反。4.4 现象GPS数据NMEA在虚拟串口1中频繁出现乱码如$GPGGA,123456.00,变成$GP?GA,123456.00,原因NMEA数据含0xF9字节如卫星PRN号为249时ASCII249十六进制为0xF9未启用转义机制被模组误判为新帧起始。解决在AP端发送NMEA数据前遍历字节遇0xF9替换为0xDB 0xDC遇0x7E替换为0xDB 0xDD。接收端同理解转义。4.5 现象长时间运行后模组无响应AT指令超时重启模组才能恢复原因AP端未处理模组主动上报的UI帧如QIND: SIMREADY导致接收缓冲区堆积最终溢出锁死UART。CMUX协议要求TE必须消费所有帧包括广播帧。解决在接收循环中对地址域为0x00控制信道且控制域为0x7FUI帧的帧即使不处理业务也必须读取并丢弃清空缓冲区。5. Linux下CMUX驱动实战用socat虚拟出/dev/ttyCMUX0~3让现有AT程序零改造接入在Linux系统如Yocto构建的嵌入式OS中直接操作CMUX帧对应用层不友好。最佳实践是用socat创建虚拟TTY设备将CMUX帧解析/封装逻辑下沉到用户态驱动上层程序仍用open(/dev/ttyCMUX1)读写如同操作真实串口。这避免了修改所有AT库且支持多进程并发访问。5.1 构建CMUX帧解析器Python脚本实现核心状态机核心是监听物理串口如/dev/ttyS2识别0xF9帧边界提取地址域将数据域路由到对应管道。以下为精简版主循环import serial, os, select, sys from threading import Thread # 创建命名管道 /tmp/cmux_pipe_1 ~ /tmp/cmux_pipe_15 for i in range(1, 16): pipe_path f/tmp/cmux_pipe_{i} if not os.path.exists(pipe_path): os.mkfifo(pipe_path) ser serial.Serial(/dev/ttyS2, 115200, timeout0.1) buf bytearray() def parse_cmux_frame(): global buf while True: # 读取新数据 data ser.read(1024) if not data: continue buf.extend(data) # 查找帧边界0xF9 while len(buf) 2 and buf[0] 0xF9: # 找到结束标志 end_idx buf.find(b\xF9, 1) if end_idx -1: # 未找到结束继续缓存 break frame buf[:end_idx1] buf buf[end_idx1:] if len(frame) 7: # 最小帧长F9addrctrllendatafcsF9 continue # 解析地址域第1字节 addr frame[1] if addr 0x00: # 控制信道忽略或记录日志 continue if 0x01 addr 0x0F: # 用户信道1~15 channel (addr 0x0C) 2 # bit3-bit4 if channel 0: continue # 非法通道 # 提取数据域从第4字节len后开始长度为len字节 if len(frame) 5: continue data_len frame[3] if len(frame) 5 data_len 2: continue # 2为fcsF9 payload frame[4:4data_len] # 写入对应管道 pipe_path f/tmp/cmux_pipe_{channel} try: with open(pipe_path, wb) as f: f.write(payload) except OSError: pass # 管道未打开跳过 Thread(targetparse_cmux_frame, daemonTrue).start()5.2 用socat桥接管道与虚拟TTY设备启动上述脚本后用socat将命名管道映射为标准TTY设备# 创建虚拟串口 /dev/ttyCMUX1绑定到管道 /tmp/cmux_pipe_1 socat -d -d pty,raw,echo0,link/dev/ttyCMUX1,waitslave \ pipe:/tmp/cmux_pipe_1,binary,nonblock,ignoreeof # 同理创建ttyCMUX2~ttyCMUX3根据需求 socat -d -d pty,raw,echo0,link/dev/ttyCMUX2,waitslave \ pipe:/tmp/cmux_pipe_2,binary,nonblock,ignoreeofsocat参数说明pty,raw,echo0创建原始模式PTY关闭回显link/dev/ttyCMUX1符号链接到指定路径waitslave等待slave端即应用层open()连接后再启动pipe:/tmp/cmux_pipe_1,binary,nonblock从命名管道读取二进制数据非阻塞5.3 应用层调用零改造接入现有AT库此时任何AT工具均可直接使用# 发送AT指令自动封装为CMUX帧 echo -ne ATCSQ\r /dev/ttyCMUX1 # 接收响应自动从CMUX帧解包 cat /dev/ttyCMUX1 # 将输出 CSQ: 25,99甚至可运行minicom -D /dev/ttyCMUX1进行交互式调试。所有CMUX细节帧构造、校验、转义均由Python解析器和socat处理应用层完全无感。5.4 性能与稳定性加固缓冲区、超时、心跳保活生产环境需增强三点接收缓冲区扩容在Python脚本中buf初始大小设为4096避免高频数据溢出超时重连机制若ser.read()持续超时重启串口并重发SABM帧心跳保活每30秒向控制信道发0xF9 00 01 00 XX F9XX为校验防止模组因空闲关闭信道。从那以后我每次部署CMUX方案都强制走一遍ATCMUX?状态查询 socat虚拟TTY创建 echo AT /dev/ttyCMUX1三连验证确保从协议栈到应用层全链路贯通。这份PDF的价值不在它多厚而在它把0x09为什么是通道2、0xEF为什么必须配长度域这些“应该知道但没人告诉你”的细节钉死在字节层面。希望帮到你。本文还有配套的精品资源点击获取