• HDMI ——CEC 协议详解以及待机唤醒 实现


    本文讲解的是基于HDMI CEC的待机唤醒方案的设计。

    目录

    cec基本介绍

    CEC协议时序:

    CEC数据帧

    cec待机唤醒介绍

    待机唤醒的处理流程和实现

    cec基本介绍

    如今常见的高清视频接口有HDMI,VGA,DP和DVI。HDMI(High-Definition Multimedia Interface)为当今主流的多媒体高速数字接口,下图为最常见的线缆引脚分布图。其中,CEC(Consumer Electronics Control)信号通过13引脚传输,作为HDMI接口的一部分。CEC总线作为控制信号被分离出来,使得在不增加数据占用宽带的情况下完成高速复杂的通信要求。

    CEC 是一套完整的单总线协议,电子设备可以借助CEC信号让使用可控制HDMI接口上所连接的装置,比如单独播放,系统待机,可以实现由单一遥控器控制所有HDMI连接的装置。最多15个设备,允许HDMI设备在没有用户干扰情况下互相命令控制。

    CEC 是与其他HDMI信号分开的电信号。这允许设备在睡眠模式下禁止其高速电路,但是可以被CEC唤醒。他是一个单独的共享总线,直接连接在设备上的所有HDMI端口间,可以流过所有完全断电的设备。

    总线是开路集电极线,有点像IIC,被动上拉至3.3V,设备拉低进行数据传输。

    与IIC相似之处:低速串行总线;采用无源上拉的集电极开路;速度受分布电容影响;接收器可以将发送的1位转换为0:发送1比特并观察是否转换为0以查看是否丢失;面向字节都附加一个应答位;特殊的启动信息

    与IIC不同之处:单线并不是两根线;以固定时序发送比特;低速串行总线(417bit/s);四个地址位;定义了动态地址分配协议;标头包括发起者和收件人地址;没有特殊停止信号,每个字节附加一个消息结束标志;没有读操作,通过获取请求获取响应帧,所有数据均从数据发送;每个设备都必须能够作为主设备传输数据;地址后字节数据有详细规定说明。

    CEC协议时序:

    bit Timing

    每个位从线拉低(下降沿)开始,保持时间表示位值,之后拉高,直至后续位开始

    正常数据位长为2.40.35ms。保持低电平0.6±0.2ms为逻辑1;保持低电平1.5±0.2ms表示逻辑0。接收器在下降沿后1.05±0.2ms对线路进行采样,然后在下降沿1.9±0.15ms开始观察下一位。

    接收者可以将传输的传输的1bt转换为0通过在下降沿后0.35s拉低总线并保持直到表示逻辑0的电平时间。这个通常用于确认传输。

    每个帧都有起始位,通过拉低总线3.7±0.2ms,然后允许上升,总持续时间为4.5±0.2ms。在观察总线空闲之后,任何设备都可以发送起始位。(通常5位时间,但成功后立即传输7位时间,以促进总线的公平共享,以及传输失败和重传之间的3位时间。)

    对于单接收消息,应答位类似于C:以1位发送,接收器将其下拉至0以确认该位字节。(根据下面给出的波形可以看出,ack位的波形为逻辑1,但是为什么逻辑分析仪解析的值为0呢?是因为接收器将其下拉至0 以确认该位字节)

    对于广播消息,应答位被反转:仍然作为1位发送,但被拒绝该字节的任何接收器下拉到0位。每个CEC帧的第一个字节包含4位源和目标地址头。如果寻址目标存在,则它确认该字节。由除标题之外的任何内容组成的帧是pig,它只检查另个设备的存在。

    地址15(1111B)用于广播地址(作为目的地)和未注册的设备(作为源),它们尚未选择不同的地址。一些设备不需要接收非广播的消息,因此可以永久使用地址15。需要接收寻址消息的设备需要自己的地址。设备通过pig它获取地址,如果ping未被确认,则设备声明它。如果确认ping,则设备尝试另个地址。

    第二个字节是操作码,它指定要执行的操作,以及后续数据字节的数量及含义。

    CEC数据帧

    cec帧结构 = 起始位+引导块+数据块

    Start(bit)+ Header Block + Data Block 1(opcode block) + Data Block 2 (operand blocks)

    注:

    Block定义:Data(8 bit) + EOM(1 bit) + ACK(1 bit)

    Header Block定义:Initiator(4 bit) + Destination(4 bit) + EOM(1 bit) + ACK(1 bit)

    所有的引导块和数据块都是10bit。

    块结构:

    帧结构:

    逻辑分析仪采集解析的一条完整的cec消息:

    cec待机唤醒介绍

    当一个CEC设备连接到CEC网络中时,其会通过ping的方式获取到自身的逻辑地址。各个设备类型的逻辑地址如下所示,其中总共有16个逻辑地址,而有些设备具有多个逻辑地址,如录音设备就有1、2、9三个不同的逻辑地址。

    待机唤醒的场景如下:

    其中,playback device 可以是碟机等设备,当TV处于待机状态时,用户可以使用碟机的遥控器或者开机键唤醒碟机后,碟机会发送或消息来唤醒TV,当source需要将输出显示在TV上时,source必须同时发送和消息。其中,消息的操作码为0x04,,的操作码为0x82。

    待机唤醒的处理流程和实现

    这里只配置接收端,在接收到cec开机信号后唤醒即可,所以下面对接受流程进行说明:

    cec初始化配置:

    本次基于极海APM32F107RC 芯片,接收CEC信号的GPIO口为:PB0

    使用外部中断 和 定时器(TMR4)外设计数的方法 实现CEC信号的接收采集

    所以初始化配置需要使能GPIO口,配置定时器和外部中断

    1. void apm_eint0_rising_config(void) //配置外部中断为上升沿触发
    2. {
    3. EINT_Config_T EINT_InitStructure2; //上升沿触发结构体
    4. EINT_InitStructure2.line = EINT_LINE_0;
    5. EINT_InitStructure2.mode = EINT_MODE_INTERRUPT;
    6. EINT_InitStructure2.trigger = EINT_TRIGGER_RISING;
    7. EINT_InitStructure2.lineCmd = ENABLE;
    8. EINT_Config(&EINT_InitStructure2);
    9. }
    10. void apm_eint0_falling_config(void)//配置外部中断为下降沿触发
    11. {
    12. EINT_Config_T EINT_InitStructure1; //下降沿触发结构体
    13. EINT_InitStructure1.line = EINT_LINE_0;
    14. EINT_InitStructure1.mode = EINT_MODE_INTERRUPT;
    15. EINT_InitStructure1.trigger = EINT_TRIGGER_FALLING;
    16. EINT_InitStructure1.lineCmd = ENABLE;
    17. EINT_Config(&EINT_InitStructure1);
    18. }
    19. //cec 初始化
    20. void apm_cec_init(void)
    21. {
    22. GPIO_Config_T gpioConfig;
    23. TMR_BaseConfig_T baseConfig;
    24. //使能时钟
    25. RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_TMR4);
    26. RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOB);
    27. //初始化GPIO_PB0,浮空输入
    28. gpioConfig.pin = GPIO_PIN_0;
    29. gpioConfig.mode = GPIO_MODE_IN_FLOATING;
    30. gpioConfig.speed = GPIO_SPEED_50MHz;
    31. GPIO_Config(GPIOB, &gpioConfig);
    32. GPIO_ConfigEINTLine(GPIO_PORT_SOURCE_B,GPIO_PIN_SOURCE_0);//设置IO口与中断的映射关系
    33. EINT_ClearStatusFlag(EINT_LINE_0);
    34. apm_eint0_falling_config();//外部中断初始为下降沿触发
    35. NVIC_EnableIRQRequest(EINT0_IRQn, 2, 1);
    36. //初始化定时器
    37. baseConfig.period = 65000;//设定计数器自动重装值,65ms更新
    38. baseConfig.division = 71;//预分频器,1M的计数频率,1us加1
    39. baseConfig.clockDivision = TMR_CLOCK_DIV_1;//设置时钟分割
    40. baseConfig.countMode = TMR_COUNTER_MODE_UP;//TIM向上计数模式
    41. TMR_ConfigTimeBase(TMR4,&baseConfig);
    42. TMR_ClearIntFlag(TMR4,TMR_INT_UPDATE);
    43. TMR_EnableInterrupt(TMR4,TMR_INT_UPDATE);//允许更新中断
    44. //使能中断分组及优先级
    45. NVIC_EnableIRQRequest(TMR4_IRQn,2,2);
    46. TMR_Enable(TMR4);//使能定时器3
    47. }

    cec信号接收处理:

    当触发外部中断并且设备处于待机状态时,根据cec波形以及时序要求解析CEC信号,通过判断opcode数据位的值 从而判断是否唤醒。

    1. //接收一个cec 帧
    2. void apm_cec_receive_frame(void)
    3. {
    4. if(EINT_ReadIntFlag(EINT_LINE_0)) //外部中断
    5. {
    6. if(apm_get_power_status() == POWER_OFF_STATUS) //当前状态不是开机
    7. {
    8. if(CEC_State == CEC_STATE_IDLE)
    9. {
    10. //如果波形状态为空闲,开启定时器3计数
    11. TMR_ConfigCounter(TMR4,0);
    12. TMR_Enable(TMR4);
    13. cec_previous_time = TMR_ReadCounter(TMR4);
    14. //SKG_INFO("cec_previous_time: %d\r\n",cec_previous_time);
    15. apm_eint0_rising_config(); //上升沿触发
    16. updatecnt = 0;
    17. CEC_State = CEC_STATE_START; //波形为起始码接收状态
    18. }
    19. else
    20. {
    21. cec_current_time = TMR_ReadCounter(TMR4); //读取当前捕获值
    22. //SKG_INFO("cec_current_time: %d\r\n",cec_current_time);
    23. if(updatecnt == 0)
    24. {
    25. //计算当前时间 //时间间隔=当前时间-前一时间
    26. cec_interval_time = abs(cec_current_time - cec_previous_time);
    27. }
    28. else
    29. {
    30. cec_interval_time = abs(0x186A0 * updatecnt - cec_previous_time);
    31. cec_interval_time += cec_current_time;
    32. }
    33. cec_previous_time = cec_current_time;//将当前时间设置为前一个时间
    34. updatecnt = 0; //计数器的范围返回0状态
    35. //SKG_INFO("cec_interval_time: %d\r\n",cec_interval_time);
    36. if(CEC_State == CEC_STATE_START)
    37. {
    38. //低电平时间在3.5ms —— 3.9ms之间
    39. if((cec_interval_time > 3500) && (cec_interval_time < 3900))
    40. {
    41. CEC_State = CEC_STATE_STARTEND; //起始结束接收状态
    42. }
    43. else
    44. {
    45. //SKG_INFO("debug 0 cec_interval_time: %d\r\n",cec_interval_time);
    46. CEC_State = CEC_STATE_IDLE;
    47. }
    48. apm_eint0_falling_config(); //下降沿触发
    49. }
    50. else if(CEC_State == CEC_STATE_STARTEND)
    51. {
    52. //起始结束的低电平区间4ms --- 1.2ms
    53. if((cec_interval_time > 300) && (cec_interval_time < 1300))
    54. {
    55. cec_byte = 0;
    56. CEC_State = CEC_STATE_HEADERLOW; //header block 数据位的低电平
    57. apm_eint0_rising_config(); //上升沿触发
    58. }
    59. else
    60. {
    61. //SKG_INFO("debug 1 cec_interval_time:%d\r\n",cec_interval_time);
    62. //SKG_INFO("AAAAAAAAAAAAAAAAAA\r\n");
    63. CEC_State = CEC_STATE_IDLE;
    64. apm_eint0_falling_config(); //如果波形采集异常,中断回到初始状态下降沿触发
    65. }
    66. }
    67. else if(CEC_State == CEC_STATE_HEADERLOW)
    68. {
    69. //逻辑1 低电平300-900
    70. if((cec_interval_time > 300) && (cec_interval_time < 900))
    71. {
    72. headerdata <<= 1;
    73. headerdata |= 1;
    74. cec_byte++;
    75. CEC_State = CEC_STATE_HEADERHIGH; //header block 数据位高电平
    76. }
    77. //逻辑0 低电平1200-1800
    78. else if((cec_interval_time > 1200) && (cec_interval_time < 1800))
    79. {
    80. headerdata <<= 1;
    81. cec_byte++;
    82. CEC_State = CEC_STATE_HEADERHIGH;
    83. }
    84. else
    85. {
    86. CEC_State = CEC_STATE_IDLE;
    87. //SKG_INFO("debug 2 cec_interval_time:%d\r\n",cec_interval_time);
    88. }
    89. apm_eint0_falling_config();//下降沿触发
    90. }
    91. else if(CEC_State == CEC_STATE_HEADERHIGH)
    92. {
    93. //如果上一次低电平的时间+当前高电平的时间在2.05ms-2.75ms,说明data bit传输正确
    94. if(2050 < (cec_pre_interval_time + cec_interval_time) < 2750)
    95. {
    96. CEC_State = CEC_STATE_HEADERLOW;
    97. }
    98. else
    99. {
    100. //SKG_INFO("debug 3 cec_interval_time:%d\r\n",cec_interval_time);
    101. CEC_State = CEC_STATE_IDLE;
    102. }
    103. if(cec_byte == 10) //header block 字节接收完成
    104. {
    105. //head_src = headerdata >> 6;
    106. head_src = headerdata >> 2;
    107. //head_src = headerdata;
    108. headerdata = 0;
    109. CEC_State = CEC_STATE_CODELOW; //data block 数据位低电平接收
    110. }
    111. apm_eint0_rising_config(); //上升沿触发
    112. }
    113. else if(CEC_State == CEC_STATE_CODELOW)
    114. {
    115. //逻辑1 低电平300-900
    116. if((cec_interval_time > 300) && (cec_interval_time < 900))
    117. {
    118. codedata <<= 1;
    119. codedata |= 1;
    120. cec_byte++;
    121. CEC_State = CEC_STATE_CODEHIGH; //data block 数据位高电平
    122. }
    123. //逻辑0 低电平1200-1800
    124. else if((cec_interval_time > 1200) && (cec_interval_time < 1800))
    125. {
    126. codedata <<= 1;
    127. cec_byte++;
    128. CEC_State = CEC_STATE_CODEHIGH;
    129. }
    130. else
    131. {
    132. //SKG_INFO("debug 4 cec_interval_time:%d\r\n",cec_interval_time);
    133. CEC_State = CEC_STATE_IDLE;
    134. }
    135. apm_eint0_falling_config();//下降沿触发
    136. }
    137. else if(CEC_State == CEC_STATE_CODEHIGH)
    138. {
    139. //如果上一次低电平的时间+当前高电平的时间在2.05ms-2.75ms,说明data bit传输正确
    140. if(2050 < (cec_pre_interval_time + cec_interval_time) < 2750)
    141. {
    142. CEC_State = CEC_STATE_CODELOW;
    143. }
    144. else
    145. {
    146. //SKG_INFO("debug 5 cec_interval_time:%d\r\n",cec_interval_time);
    147. CEC_State = CEC_STATE_IDLE;
    148. }
    149. if(cec_byte == 18)
    150. {
    151. opcode = codedata;
    152. codedata = 0;
    153. CEC_State = CEC_STATE_IDLE;
    154. }
    155. apm_eint0_rising_config(); //上升沿触发
    156. }
    157. }
    158. cec_pre_interval_time = cec_interval_time;
    159. }
    160. }
    161. }

    波形的处理流程是  使用状态标志位  的变化来表示接收的字节

    1. typedef enum
    2. {
    3. CEC_STATE_IDLE=0, //0 初始为空闲
    4. CEC_STATE_START, //1 起始位
    5. CEC_STATE_STARTEND, //2 起始位结束
    6. CEC_STATE_HEADERLOW, //3 head block 数据位低电平
    7. CEC_STATE_HEADERHIGH, //4 head block 数据位高电平
    8. CEC_STATE_CODELOW, //5 data block 数据位低电平
    9. CEC_STATE_CODEHIGH, //6 data block 数据位高电平
    10. }CEC_State_enum;

    处理过程中没有单独解析ack和eom,字节只接收两个,一个header   10位数据,一个opcode  8位数据。

    波形处理流程:

    第一次进入中断为空闲,开启定时器计数并读取当前计数值,状态改为起始位接收,中断改为上升沿触发

    第二次进入中断读取定时器的值,减去前一次计数器的值,就是起始位低电平维持的时间,判断时间是否为在协议范围,把触发改为下降沿,如果在范围内将状态改为起始位结束状态

    第三次进入中断 依然是计算出时间的差值,判断高电平维持的时间范围,在范围内则起始位接收完成,将状态改为接收header clock,把中断改为上升沿触发

    第四次进入中断  由于上一次是下降沿触发,所以时间的差值就是低电平的持续时间,判断时间的范围得到逻辑0或逻辑1,逻辑1 headerdata  

    数据处理后将状态改为HEADERHIGH  ,中断改为下降沿触发

    下一次进入中断  时间的差值就是高电平的持续时间,进入HEADERHIGH 条件判断,当前的时间差值+上一次的时间差值就是数据传输的总时间,判断总时间是否在范围,在范围将状态改为HEADERLOW,中断改为上升沿触发,接收处理下一位数据,接收的数据位达到10位,将状态改为接收data clock。Data的接收处理和header相同。接收的数据位达到18位就将数据保存,状态改为空闲。

    1. void apm_cec_ctrl_handle(void)
    2. {
    3. if(opcode == CEC_VIEW_ON || opcode == ACTIVE_SOURSE)
    4. {
    5. if(apm_get_power_status() == POWER_OFF_STATUS)
    6. {
    7. power_on();
    8. }
    9. }
    10. }

    在实际调试过程中可以根据需求和现象,调整电平时间判断范围,加入EOM位的判断作为数据传输结束的标志,加大数据的接收范围等。

  • 相关阅读:
    2023年总结:不上班的这半年!
    课程版块,外键相关
    windows环境下Git安装与远程仓库SSH Keys配置
    阿里P8大牛手撸的分布式架构文档:ZK+高可用+缓存+事务+中间件等
    Servlet中乱码解决
    重启某个节点、重启电脑服务器后,kubernetes无法运行,k8s无法运行
    Java RSA密钥从RSAPrivateKey和RSAPublicKey对象中,分别提取模和指数
    卷积神经网络CNN学习笔记-MaxPool2D函数解析
    美团面试:说说Netty的零拷贝技术?
    【Python】Window系统Python+VSCode环境安装及测试
  • 原文地址:https://blog.csdn.net/qq_58264156/article/details/133998009