3. CEC协议栈详解

好,咱们今天来啃一块硬骨头——CEC协议栈。说实话,这玩意儿刚接触的时候,我也觉得挺绕的。但搞懂了之后你会发现,它其实就是一套“客厅里的指挥系统”。

CEC的全称是Consumer Electronics Control,消费电子控制。说白了,就是让HDMI线上的设备能互相“说话”。你按一下电视遥控器,功放自动开机、蓝光机开始播放、电视切换到对应输入源——背后全是CEC在干活。

我个人习惯把CEC协议栈分成三层来看:物理层、数据链路层、应用层。今天咱们重点讲数据链路层和应用层,也就是帧结构、寻址机制、逻辑地址分配,还有常用的命令集。

3.1 CEC帧结构:数据是怎么打包的?

CEC通信用的是单线双向串行总线,速率很慢,只有400bps左右。你别嫌慢,控制指令就那么几个字节,够用了。

一个完整的CEC帧长这样:

起始位 + 10位数据块 + 10位数据块 + ... + 10位数据块

每个10位数据块的结构是:

  • 1位起始位:逻辑0,表示开始传输
  • 8位数据:真正的信息内容
  • 1位EOM:End of Message,1表示这是最后一个数据块
  • 1位ACK:应答位,接收方拉低表示收到

嗯,这里要注意:CEC的ACK机制是“线与”的。也就是说,总线上任何一个设备都可以拉低ACK位。如果没人应答,那ACK位就是高电平,发送方就知道“没人理我”。

关键点:CEC帧的最小单位是“数据块”,不是字节。每个数据块10位,其中有效数据只有8位。别搞混了。

我刚开始做CEC调试的时候,用逻辑分析仪抓波形,对着协议手册一个一个数位,数得眼睛都快瞎了。后来发现,其实大部分逻辑分析仪都有CEC解码插件,直接帮你解析好了。嗯,工具要用对。

3.2 CEC寻址机制:设备之间怎么找到对方?

CEC用的是逻辑地址,不是物理地址。每个设备在CEC总线上有一个唯一的逻辑地址,范围是0到15,一共16个地址。

但注意,不是所有地址都能随便用。CEC协议规定了每个地址对应什么类型的设备:

逻辑地址 设备类型 说明
0 TV(电视) 通常作为根设备
1 Recording Device 1 比如机顶盒、录像机
2 Recording Device 2 第二个录像设备
3 Tuner 1 调谐器/电视棒
4 Playback Device 1 蓝光机、游戏机
5 Audio System 功放、回音壁
6 Tuner 2 第二个调谐器
7 Tuner 3 第三个调谐器
8 Playback Device 2 第二个播放设备
9 Recording Device 3 第三个录像设备
10 Tuner 4 第四个调谐器
11 Playback Device 3 第三个播放设备
12 Reserved 保留
13 Reserved 保留
14 TV(备用) 某些电视用这个
15 Unregistered 未注册/广播地址

你想想看,一个客厅里最多也就五六台设备,16个地址完全够用。但问题来了——设备怎么知道自己该用哪个地址?

答案是:通过物理地址和逻辑地址的映射。HDMI有一个EDID(扩展显示标识数据)机制,里面包含了物理地址信息。CEC设备上电后,会先读取EDID,然后根据物理地址和自身设备类型,向总线申请一个逻辑地址。

避坑指南:我曾经遇到过一台蓝光机,死活拿不到逻辑地址。查了半天,发现是EDID里的物理地址写错了。HDMI线缆的DDC通道有问题,导致EDID读取失败。所以,CEC调不通的时候,先检查物理层和EDID。

3.3 CEC命令集:设备之间说什么?

CEC的命令集大概有70多条,但常用的也就十几条。我把它们分成几类:

3.3.1 系统控制类

  • Standby:让设备进入待机。电视按关机键,所有CEC设备一起关。
  • Active Source:声明自己是当前活跃源。比如你打开蓝光机,它就会广播这个消息。
  • Set Stream Path:切换HDMI输入源。电视收到这个命令,会自动切到对应端口。

3.3.2 设备控制类

  • User Control Pressed:模拟用户按键。比如音量+、方向键、确认键。
  • User Control Released:按键释放。这两个命令通常成对出现。
  • Give Device Power Status:查询设备电源状态。返回是“开”、“关”还是“正在启动”。

3.3.3 菜单控制类

  • Menu Request:请求设备显示菜单。
  • Menu Status:报告菜单状态(显示中/已关闭)。
  • Give OSD Name:查询设备的OSD名称。比如电视上显示“蓝光播放器”。

每个命令的格式是固定的:

发起方地址 + 目标方地址 + 操作码 + 操作数

举个例子,电视(地址0)想让功放(地址5)静音:

0x05 0x44 0x42
// 0x05: 目标地址是5(功放)
// 0x44: 发起方地址是0(电视),加上命令标志
// 0x42: 操作码,表示"User Control Pressed"
// 后面还要跟操作数,表示具体按键

看着有点乱是吧?别急,我教你一个记忆方法:CEC命令的头部字节,高4位是目标地址,低4位是源地址。比如0x05,高4位是0,低4位是5,表示“从地址0发给地址5”。

实战经验:我在做CEC兼容性测试时发现,不同品牌的设备对命令的响应速度差异很大。索尼的电视响应很快,但某些品牌的功放会有200ms以上的延迟。所以,发送命令后一定要加超时处理,别死等。

3.4 逻辑地址分配流程:设备是怎么“注册”的?

这个流程我建议你用一张图来理解:

CEC逻辑地址分配流程 设备上电 读取EDID获取物理地址 发送 Polling Message 探测目标地址是否被占用 收到ACK? 地址被占用? 是 尝试下一个地址 按优先级顺序 循环尝试 否 地址分配成功 注册到CEC总线 发送 Report Physical Address 广播自己的物理地址和逻辑地址 进入正常工作模式 监听并响应CEC命令 所有地址尝试失败 地址分配失败 设备无法参与CEC通信

流程其实不复杂:

  1. 上电:设备启动,读取EDID获取物理地址。
  2. 探测:设备向总线发送Polling Message,探测自己想要的逻辑地址是否被占用。
  3. 判断:如果收到ACK,说明地址被占用了,换下一个地址再试。
  4. 注册:如果没收到ACK,说明地址空闲,设备就占用这个地址。
  5. 广播:设备发送Report Physical Address命令,告诉所有设备“我是谁,我在哪”。

这里有个细节:Polling Message其实就是一个只有头部没有数据的帧。发送方把目标地址设为自己想占用的地址,如果没人应答,就说明地址空闲。

注意:CEC协议规定,设备在分配地址时,必须按照优先级顺序尝试。比如电视优先尝试地址0,功放优先尝试地址5。如果优先级最高的地址被占了,才尝试次优先级的。这样做是为了避免地址冲突。

我记得有一次调试,发现一台回音壁总是拿不到地址。抓日志一看,它一直在尝试地址5,但功放已经占了。回音壁的固件写死了只尝试地址5,没有做fallback。这就是典型的固件bug。

3.5 实战中的常见问题

最后,我总结几个CEC调试中经常踩的坑:

  • 物理层问题:CEC信号是5V的,但有些设备只支持3.3V。电平不匹配会导致通信失败。我建议用示波器量一下CEC引脚的电压。
  • 总线冲突:CEC是单线总线,多个设备同时发送会冲突。协议有仲裁机制,但实现不好的设备会“死锁”。
  • 命令超时:CEC协议规定,设备必须在100ms内响应命令。但有些老设备响应很慢,需要加超时重试。
  • 逻辑地址冲突:如果两个设备用了同一个逻辑地址,总线会乱套。这时候需要断电重启,让设备重新分配地址。

我的调试工具清单:逻辑分析仪(带CEC解码)、USB转CEC适配器(比如Pulse-Eight的)、HDMI CEC分析仪。如果你只是做应用层开发,用USB转CEC适配器就够了。但要是做底层驱动,逻辑分析仪是必须的。

好了,CEC协议栈的核心内容就这些。帧结构、寻址机制、逻辑地址分配、命令集——这四个东西搞明白了,CEC对你来说就没有秘密了。下一节咱们会讲CEC的物理层实现和电气特性,到时候再深入聊。


可可精品资料库