首页 / 资讯中心 / 文章详情

HDMI-CEC协议解析:地址分配、消息结构与联动排障

HDMI-CEC协议解析:地址分配、消息结构与联动排障 ★ FEATURED ARTICLE
简介这份PPT聚焦HDMI-CEC技术及标准面向音视频接口开发、嵌入式软硬件调试和消费电子协议学习人员帮助读者打通从HDMI物理层传输到CEC应用层交互的知识链路。内容由HDMI的TMDS差分传输、DDC显示数据通道、HPD热插拔检测等基础机制切入进一步讲解CEC总线定时格式、开始位与数据位时序、Header/Opcode/Operand信息结构以及EOM、ACK控制位的含义。应用部分覆盖逻辑地址分配、Normal CEC与Standby CEC功能并结合常用控制命令说明设备间交互的实现思路。压缩包内为1个pptx文件约428KB按原理、底层处理、应用层三个模块组织便于快速查阅和复习。已有1063人学习适合作为协议入门梳理、项目联调参考和面试知识点复盘材料。1. 一条 417 bit/s 的慢线凭什么统治整个客厅按下电视遥控器的音量键响的是功放按电源键播放器和音箱一起进入待机。这套配合背后没有 Wi-Fi、没有蓝牙只有一根 HDMI 线束里夹着的单芯线。这条线的名字叫 HDMI-CEC它的位宽是 2.4 毫秒理论速率不到 420 bit/s——比今天任何一条无线广播包都慢几个数量级。反直觉的地方在于HDMI 的主干 TMDS 是彻头彻尾的单向管道源设备只管把像素往外推根本不知道下游有没有人在看。可只要插上电设备之间就能互相点名、抢地址、发命令在没有中心主机的前提下形成一张自洽的网络。这套机制解决的正是“单向视频 多个遥控器”的死结。值得搞懂它是因为 CEC 的绝大多数现场问题都来自它的设计前提共享总线、地址靠抢、无重传、命令靠广播。理解这些前提才能解释同一个盒子为什么在 A 电视上活蹦乱跳、换到 B 电视上就是不动。2. 线束解剖TMDS、DDC、HPD 与 CEC 各自在传什么一根 HDMI 线里其实并行跑着四套互不相干的系统。把它们分开看后面所有的时序和地址问题才有落脚点TMDS 负责搬像素DDC 负责交换身份HPD 负责通知“我插上了”CEC 负责让设备之间说话。四者电气特性完全不同TMDS 是差分低压CEC 是单端开漏上拉混在一起看只会越看越糊涂。2.1 TMDS 四对差分线R、G、B 与 HV 是怎么挤进 10 bit 的HDMI 用 TMDSTime Minimized Differential Signal传数据本质是差分传动两根引脚之间的电压差决定这一位是 0 还是 1共模噪声因为同时作用在两根线上而被抵消掉。线束里有四对差分线三对跑数据、一对跑时钟每个时钟周期内每条数据通道搬 10 bit。关键在于这 10 bit 怎么来的。视频被拆成 R、G、B、H、V 五种信号三个数据通道分别对应 R、G、B但每个通道的 10 bit 里并不是 10 bit 全是像素——其中 8 bit 是有效数据另外 2 bit 是控制位或附加位。B 通道的这 2 bit 用来编码行同步 H 和场同步 V这就是“HV 编码在 B 信号通道里传输”的含义。R、G 通道的那 2 bit 平时是控制位在数据周期里就被拿去塞音频采样包和其他辅助数据。这么设计的原因很直接如果给 H、V 单独拉线接口引脚数立刻膨胀而消费电子的连接器成本是按针算的。把同步信号折叠进已有的数据通道代价是编码逻辑复杂一点收益是整根线只要 19 个引脚。这是典型的“用硅片换铜线”的取舍后面 CEC 把整套控制协议压到一根线上是同一种思路的延续。2.2 DDC 与 E-EDID物理地址被藏在哪一段字节里DDCDisplay Data Channel本质是一条 I2C 总线走的是和 TMDS 完全不同的两根线。接收端sink把一段 E-EDID 数据挂在自己的 I2C 从地址上源端上电后去读读到的是“我支持什么分辨率、什么色深、什么音频格式”。物理地址就藏在这段数据里。EDID 中会有一个厂商自定义数据块CEC 的物理地址以两个字节的形式写在其中四个半字节分别对应四级拓扑。源设备读完 EDID 就知道自己在这张网络里的坐标不需要额外握手。# 从 HDMI VSDB 的两个字节还原 4 段式物理地址 def parse_physical_address(byte_a: int, byte_b: int) - str: # 高字节的两个 nibble 是 A、B 段低字节的两个 nibble 是 C、D 段 a (byte_a 4) 0x0F b byte_a 0x0F c (byte_b 4) 0x0F d byte_b 0x0F return f{a}.{b}.{c}.{d} # 0.0.0.0 - 电视自己 # 1.0.0.0 - 电视的第 1 个 HDMI 输入口 # 1.1.0.0 - 该口下级设备的第 1 个输出口 # F.F.F.F - 尚未获得物理地址未接入或读取失败参数只有两个字节但它们决定了设备后续的全部行为拿到的如果不是F.F.F.F设备才有资格去申请逻辑地址如果读到的是全 F说明 EDID 没读通或者 HPD 没起来这时候去发任何 CEC 命令都是白发。注意物理地址是拓扑位置不是设备身份。同一个盒子插在电视的 1 号口是1.0.0.0插到 2 号口就变成2.0.0.0它自己没有任何发言权。2.3 HPD 与 CEC 的时序关系热插拔那一刻谁先动HPDHot Plug Detect是一根单向电平线由接收端驱动。源端靠它判断“对面插上了没有”接收端拉高 HPD 就等于在说“我准备好了你可以来读 EDID 了”。这三个动作有严格顺序HPD 拉高 → 源端读 EDID 拿到物理地址 → 设备启动 CEC 地址申请流程。顺序颠倒会出问题比如 HPD 还没稳定就去读 EDID读回来的可能是半截数据甚至全 0设备会以为自己没有物理地址于是永远不申请逻辑地址在总线上彻底隐身。反过来HPD 抖动也是现场最常见的故障源。线材接触不良、供电瞬间跌落、某些设备的 HPD 驱动能力偏弱都会让 HPD 出现毫秒级的毛刺。源端如果对毛刺不做滤波就会反复走“重新读 EDID → 重新申请地址 → 重新广播”的流程从电视的角度看就是这台设备反复上线离线遥控器按下去时好时坏。常见的实现思路是在 HPD 输入上加几十毫秒的去抖并且在重新初始化之前先判断物理地址有没有真的变化。3. 身份是怎么抢来的物理地址发现与逻辑地址分配CEC 总线上没有主机没有 DHCP也没有仲裁服务器。每个设备必须自己搞清楚两件事我在拓扑的哪个位置物理地址以及我代表哪种功能逻辑地址。前者从 EDID 读后者靠在总线上试探着抢。这套机制叫 Physical Address Discovery Process它是整个 CEC 能用起来的地基。3.1 物理地址的树0.0.0.0 到 F.F.F.F 的拼接规则物理地址是 16 bit写成四个用点分隔的十六进制半字节例如2.1.0.0。第一位标识“第几级”往后每一位标识“这一级的第几个口”。电视自己永远占0.0.0.0它是这棵树的根。电视的第 n 个 HDMI 输入口物理地址是n.0.0.0。这个口下面挂的设备如果有多个 HDMI 输出口它转发出去的第 m 个口就是n.m.0.0再往下一级继续扩展。四个半字节意味着拓扑最多五层设备根 四级这是刻意的限制。层数越多物理地址的语义越难维护而且 CEC 的逻辑地址总共只有 16 个深拓扑根本没有足够的地址可分。用固定长度换解析简单是这套协议一以贯之的风格地址解析就是四次移位加掩码任何 8 位单片机都能在几个指令周期内完成。物理地址还有一个特殊值F.F.F.F含义是“我没有物理地址”。设备读到这个值时必须闭嘴——不申请逻辑地址、不响应轮询、不广播。这个约定保证了网络上不会出现身份不明的幽灵节点。3.2 Polling Message发一个只有 Header 的包去试探地址拿到物理地址之后设备要申请一个和自己的设备类型匹配的逻辑地址。申请之前必须先确认这个地址有没有被占手段就是 Polling Message。Polling Message 是 CEC 里最短的一帧只有 Header Block没有 Opcode也没有操作数EOM 位置 1 表示“到此结束”。Header 的低 4 位填上想试探的逻辑地址高 4 位填自己的逻辑地址。发出去之后看 ACK 位如果没人应答ACK 保持为 1说明地址空闲如果被某个设备拉低成 0说明这个地址已经有人了。/* 构造一条 Polling Message只有 HeaderEOM 置 1 */ static void cec_build_polling(uint8_t *buf, uint8_t initiator, uint8_t candidate) { /* byte0高 4 位源逻辑地址低 4 位目的逻辑地址 */ buf[0] (uint8_t)((initiator 4) | (candidate 0x0F)); /* byte1bit0 是 EOMbit1 是 ACK * 发送时 ACK 固定写 1接收方若存在会把总线拉低回读即变 0 */ buf[1] 0x01; /* EOM 1, ACK 1 */ /* 只发 2 个字节共 20 bit不含起始位 */ }这里有三个容易忽略的细节。第一ACK 位是“发起者写 1、跟随者拉低”所以发送缓冲区必须支持回读硬件要在发完之后把这根线的实际电平采样回来否则你永远不知道有没有人应答。第二试探用的源地址不能是 15未注册地址否则对方无法判断是谁在问。第三试探失败不代表地址永久可用两次试探之间要留间隔避免和别人的申请流程撞车。常见的实现思路是设备按自己类型的地址清单依次试探第一个没人应答的就占下来如果清单里全部被占退到 14Specific Use这个通用地址连 14 也被占就只能用 15Unregistered此时它仍然能收广播但不能作为独立功能被点名。3.3 Report Physical Address 广播与映射表地址抢到之后设备要做第二步向全网广播自己的物理地址与逻辑地址的绑定关系用的命令是Report Physical Address。这条命令的目的地址是 15广播操作数带三个字节两个字节物理地址、一个字节设备类型。广播的意义在于让所有设备维护一张映射表。电视收到之后就知道“1.0.0.0这个位置上是播放设备逻辑地址 4”后续要做路由切换时直接按物理地址找设备而不是靠猜。这张表的维护是持续的不是一次性的。设备断电再上电、换口、重新协商都会重新广播一次。收到广播的设备要覆盖旧记录而不是追加否则表会越滚越大。更麻烦的是设备离线CEC 没有“注销”命令一个设备拔掉之后它的记录会一直留在别人的表里直到有人主动去 Polling 那个逻辑地址发现无人应答。所以成熟的实现会在收到Set Stream Path或者路由切换请求时先对目标地址做一次 Polling确认在线再继续。3.4 逻辑地址的数量限制与“一个功能一个地址”逻辑地址总共 16 个其中有一半是固定分配给特定设备类型的数量还有上限。逻辑地址设备类型最大数量0电视11 / 2 / 9录制设备33 / 6 / 7 / 10调谐器44 / 8 / 11播放设备35音频系统114通用115未注册 / 广播不限12 / 13保留—这个限制直接决定了一条设计原则一个逻辑地址代表一个功能不是代表一台物理设备。如果一台机器同时是播放器和音频系统——比如带功放功能的播放器——它必须占两个逻辑地址用两个身份分别响应不同的命令。为什么不干脆用物理地址寻址因为命令的语义是面向功能的。“切换到播放源”这句话说的是“让那个负责播放的设备出来”而不是“让1.0.0.0这个位置上的东西出来”。功能寻址让命令可以跨拓扑复用代价就是地址数量成了稀缺资源这也是为什么 15 号地址只能收广播、不能主动发起点对点交互。4. 一次 CEC 消息的解剖位定时、10 bit 块与 EOM/ACKCEC 的物理层极其朴素一根线开漏输出外部上拉到 3.3V空闲时高电平任何设备拉低就是 0。没有时钟线没有片选所有人共享同一条介质。所有的可靠性设计都堆在了位定时和帧结构上看懂这两层抓波形时才能一眼判断问题出在物理层还是协议层。4.1 开始位与数据位的低电平窗口0 和 1 靠什么区分一帧的起点是一个开始位它由两段构成一段低电平持续时间 a加一段高电平总长 b。数据位同样是“先低后高”但低电平的宽度不同逻辑 0 的低电平窗口宽逻辑 1 的低电平窗口窄而两种位的总长度完全一致。开始位 低 3.7ms 高 0.8ms 4.5ms 逻辑 0 低 1.5ms 高 0.9ms 2.4ms 逻辑 1 低 0.6ms 高 1.8ms 2.4ms上述为规范给出的典型值实际器件允许一定容差。这套编码的巧妙之处在于所有数据位的总长度相同所以接收端不需要从数据里恢复时钟只要在每位固定时刻采样一次就够。采样点落在低电平窗口内读到低就是 0读到高就是 1。逻辑 0 的低窗口比逻辑 1 宽一倍多给采样点留足了容差余量。代价是慢。2.4ms 一位一个字节加两个控制位就是 24ms一帧最长的命令要将近 400ms 才发得完。这个速度对遥控器按键来说够用但任何需要“实时”的控制都别指望它。注意逻辑 0 和逻辑 1 的区别只在低电平持续时间的比例不在总长度。如果你的逻辑分析仪上看到总长度不一致的位那不是 CEC 数据是干扰或者别的协议串进来了。4.2 线与仲裁为什么低逻辑地址总能赢CEC 是开漏总线多个设备同时驱动时输出 0 会覆盖输出 1线上的实际电平是所有人输出的“与”。发送方在驱动总线的同时必须回读总线如果我发的是 1读回来却是 0说明有人在同时发 0我立刻停发退出。这就是仲裁的全部机制没有令牌没有时隙分配。退出的一方会等到总线空闲后重发而赢得仲裁的一方继续把这一帧发完。由于逻辑地址小的设备在 Header 里先发出自己的地址位地址越小、越早出现 0 的设备越容易赢——这就是“低逻辑地址优先”的物理来源。电视的逻辑地址是 0所以它永远能抢到总线。这套机制的好处是零开销坏处是延迟不可预测。当总线繁忙时一个低优先级设备可能要等好几帧才能发出去。所以 CEC 的应用层命令普遍被设计成幂等和可重发的Standby发两次没有副作用Active Source重复广播也只是让电视再切一次源。4.3 Header / Opcode / Operand16 个块的上限从哪来一帧完整的 CEC 消息由三种块组成一个 Header Block、一个 Opcode Block、若干 Operand Block。每个块都是 10 bit结构完全一致。Header Block 是唯一结构特殊的块前 8 bit 拆成两个 4 bit 字段1 到 4 位是发起者逻辑地址5 到 8 位是目的逻辑地址。Opcode Block 和 Operand Block 的 8 bit 就是纯粹的数据操作码告诉对方“这是什么命令”操作数告诉对方“参数是多少”。帧长上限是 16 个块也就是 160 bit约 384 毫秒。这个上限不是随便定的块数太多接收端的缓冲区要变大而且一帧占用总线的时间太长会拖垮其他设备。16 个块刚好够表达所有标准命令——最长的Report Physical Address也只需要 Header 加 Opcode 加 3 个操作数一共 5 个块。/* 把一个 8 bit 数据编码成 10 bit 块的位序列不含起始位 */ static void cec_encode_block(uint8_t data, int eom, int ack, uint8_t *out10) { for (int i 0; i 8; i) { /* 高位先出这是 CEC 与很多串行总线的共同点 */ out10[i] (data (7 - i)) 0x01; } out10[8] eom ? 1 : 0; /* 第 9 位EOM标识是否为最后一块 */ out10[9] ack ? 1 : 0; /* 第 10 位ACK发起者固定写 1 */ }参数说明data是这一块的 8 bit 有效数据eom只在最后一块置 1ack在发送侧永远填 1。接收侧解析时把前 8 位拼回字节第 9 位判断帧是否结束第 10 位判断这一块有没有人接收。4.4 EOM 与 ACK一个由发起者写、跟随者翻的位EOMEnd Of Message的语义很简单0 表示后面还有块1 表示这是最后一块。接收方靠它知道什么时候可以停下来处理不需要提前知道帧长。这个设计让 CEC 的帧长是自描述的发送方可以按需拼装操作数。ACK 的设计更微妙。它由发起者在发送时置 1接收方如果正确收到这一块就在第 10 位的时隙把总线拉低把它变成 0。发起者回读这个位就知道这一块有没有被接收到。这套机制的坑在于**ACK 是逐块确认不是整帧确认。**一个 5 块的命令中间任何一块没人应答发起者都应该中止后续发送。很多实现偷懒发完就不管了结果就是接收端收到半截命令行为完全不可预测。更麻烦的是 CEC 没有 NAK 的概念——ACK 位保持 1 既可能是“没人理我”也可能是“对方收到了但是拒绝”两者无法区分。也没有 CRC没有序列号没有重传机制。可靠性完全依赖物理层的低速率和短距离2.4ms 的位宽意味着即使有几毫秒的干扰也很难把一整位翻转掉。这是用“慢”换“稳”的典型交易也解释了为什么 CEC 只能走在线束内部稍微长一点的线就开始出问题。5. 排查命令发下去了设备为什么不动CEC 最让人抓狂的地方是它没有错误码。命令发出去了ACK 也回来了但电视就是不切源。这时候光看日志没用必须按“地址是否正确 → 命令是否合法 → 时序是否到位”的顺序往下剥。这一章把应用层功能和现场故障放在一起讲因为绝大多数“功能不生效”本质上是命令组合错了而不是协议栈坏了。5.1 One Touch Play 的三条命令必须齐One Touch Play 是 CEC 认证里唯一被强制要求的功能含义是“让电视显示我自己的输出”。它由三条命令组成缺一不可。Active Source携带两个字节的物理地址目的地址是 15广播作用是告诉全网“现在轮到我了”。Image View On和Text View On是点对点发给电视目的地址 0作用是请求电视把画面切过来——前者面向图形界面后者面向文字层具体走哪条取决于设备类型。三条命令的顺序有讲究。Active Source必须先发它是广播让所有设备更新路由状态Image View On随后明确请求电视动作。反过来发电视可能收到唤醒请求时还不知道该切到哪个输入于是什么都不做。这也是排查的第一个分叉点如果电视不切源先确认三条命令是不是都发了。很多移植代码只实现了Active Source在部分电视上能用换一台就失效——因为那台电视的实现依赖Image View On才触发切换。5.2 Routing Control 与 System Standby广播还是点对点Routing Control 是一组命令的集合负责在多个输入源之间切换显示。Active Source、Inactive Source、Request Active Source、Set Stream Path、Routing Change、Routing Information各自承担不同角色Request Active Source是电视主动问“现在谁是活跃源”Set Stream Path是电视命令某个物理地址的设备接管画面Routing Change 则是设备之间互相通知拓扑变化。System Standby 只有一条命令Standby但它有两种发法广播给 15所有设备一起待机点对点发给某个逻辑地址只有那台设备待机。电视遥控器按电源键时通常发广播但如果网络里有设备希望被单独唤醒就得用点对点。Standby 场景下的 CEC 实现和正常工作时不一样。设备主控断电后CEC 控制器往往靠待机电源继续活着只保留极少数命令的响应能力Standby、Give Device Power Status、Report Power Status以及作为唤醒源使用的Image View On/Text View On。这段逻辑如果和主程序的唤醒路径耦合在一起就会出现“关机后又被自己唤醒”的玄学现象。5.3 五条踩坑记录现象一设备上电后电视始终不切到该输入口。原因只发了Active Source没发Image View On或者两条命令之间间隔太短电视还没处理完广播就收到了点对点请求。 解决三条命令齐发Active Source和Image View On之间留出至少一帧的间隔约 400ms并确认目的地址分别是 15 和 0。现象二两台同类型设备行为随机有时都能用有时只有一台能用。原因上电时没做 Polling直接假定自己该占某个逻辑地址结果两台设备占了同一个地址ACK 冲突导致行为不可预测。 解决严格走“Polling 试探 → 占用 → 广播Report Physical Address”流程Polling 失败就按地址清单顺序退到下一个候选地址。现象三电视遥控器按键对功放无效但功放自己的遥控器正常。原因Remote Control Pass Through 没启用或者User Control Pressed的操作数格式不对——这条命令需要两个操作数第一个是 UI 命令码第二个是按键码少一个字节接收端会整条丢弃。 解决核对按键码表确认两个操作数都在并且按下和释放成对发送User Control Pressed后必须跟User Control Released否则接收端会一直认为按键处于按住状态。现象四待机后设备自己又开机了。原因把Image View On或Active Source无条件当成唤醒源而电视在开机扫描网络时会广播Request Active Source某些实现会误触发。 解决区分“上电初始化”和“从待机唤醒”两条路径唤醒源只接受点对点发给自己的Image View On广播类命令在待机态一律忽略。现象五整条总线偶尔全死所有设备都不响应。原因某台设备的 CEC 引脚一直拉低没有释放可能是固件卡死也可能是上拉电阻缺失导致空闲电平不是高。 解决先用万用表量空闲时 CEC 对地的电压正常应该是接近 3.3V如果接近 0逐个拔设备定位。固件层面要保证异常复位时释放总线别把引脚配成强推挽输出。5.4 最小验证集三条命令判断链路是否通排查不要一上来就抓波形先用三条命令做分层验证。命令目的地址能验证什么Polling Message候选逻辑地址物理层是否通、地址是否被占Get CEC Version/CEC Version点对点对方是否在线、协议版本是否兼容Give Device Power Status/Report Power Status点对点应用层是否活着、当前是开机还是待机Polling 通说明电气层没问题版本查询有回复说明对方协议栈在跑电源状态查询有回复说明应用层没死。三步都过还是功能不生效问题就一定在命令组合或者电视端的实现差异上这时候再去抓波形才有意义。6. 设计取舍为什么 CEC 选择了慢、共享、无重传把 CEC 和同期的其他控制总线放在一起看会发现它几乎在每一个维度上都做了“保守”的选择速率低到 417 bit/s介质是完全共享的单线没有重传没有加密没有中心仲裁甚至连 CRC 都省了。这些选择单看每一条都像是缺陷合在一起却刚好匹配它的使用场景——一根几米长、埋在电视柜后面的线连接着几个消费电子设备用户对延迟的容忍度是几百毫秒对可靠性的要求是“按十次能成九次”。慢换来了什么2.4ms 的位宽让接收端不需要时钟恢复一个采样点就能判决8 位单片机的定时器就能搞定同时它天然抗干扰几十微秒的毛刺根本翻不动一位。共享换来了什么任何一个设备都能当发起者不需要主机不需要额外的片选线插上就能说话。无重传换来了什么协议栈极简ACK 位就是全部的状态机没有超时重传队列没有序列号管理。这三点加起来才让 CEC 能塞进一颗几毛钱的 MCU 里成为 HDMI 接口的标配而不是选配。代价也很清楚它不适合传任何有实时性要求的东西不适合长线不适合多主机高并发更不能承载任何安全相关的控制——任何人都能在总线上伪造Standby协议层面没有任何防护。理解这一点就不会试图用 CEC 去做“可靠的设备管理”而是把它当成一个“尽力而为的遥控器扩展”。验证这套机制最直接的办法是抓波形。用一个能解 CEC 的逻辑分析仪看开始位的低电平宽度是不是 3.7ms 量级看每个数据位的总长是不是稳定在 2.4ms看第 9 位 EOM 和第 10 位 ACK 有没有按预期翻转。波形对了但功能不对问题在应用层波形本身就不对问题在电气层或者地址分配。这个二分法能省掉大量盲猜。那次调试我印象很深一台播放设备死活不切源日志显示Active Source发出去了ACK 也回来了我盯着应用层代码看了半天最后抓波形才发现Image View On被排在了Active Source前面电视收到唤醒请求时路由表还没更新直接忽略了。从那以后每次上电序列我都强制走一遍“Polling →Report Physical Address→Active Source→Image View On”这个固定顺序不管对面是什么电视。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站