Liuhl1998 发表于 2022-3-16 15:01

PD快充也没那么神秘CH543D(PD诱骗受电Sink解析)

本帖最后由 Liuhl1998 于 2022-8-8 18:00 编辑

随着USB PD3.1的发布,PD快充将支持最高48V电压输出,充电功率同步提升至240W,在生活中PD充电器随处可见,为人们日常提供不可或缺的便捷。如果有一款合适的“诱”电端,PD充电器就可以连续输出可调节的电压,这对于工程师来说不就相当于一个可调的电压源吗?基于CH543的评估板做了这样一个受电端,效果差不多这样:



PD快充是基于CC线通讯完成,需要遵循USB Power Delivery规范,从PD协议规范中可以了解到具体的协商过程:

由上图可以看出USB PD通讯就是Host(Source)和Power Brick(Sink)之间的数据交互。当Sink接入后(主机检测到5.1K下拉电阻的接入),主机(Source)将在100ms~200ms内会发送Source Cap(包含可提供电压档位的PDO)。
设备接收到主机发来的消息时会回复Good CRC进行响应,并在24ms内回复Request(请求需要的电压电流)。
主机收到设备发来的Request会回复Good CRC进行响应,并检测自身PDO是否支持设备Request,支持:会在24ms内回复Accept,不支持:回复Reject。
设备接收到主机发来的Accept/Reject,都会回复Good CRC进行响应。
主机若是支持设备请求,将会在450ms进行调压,调压完成后发送PS_RDY,然后等待设备回复Good CRC进行响应。


理论明白了,现在就动手来实践吧,硬件采用的是:“沁恒微电子”的CH543评估板,CH543 内置了 USB Power Delivery 控制器和 PD BMC PHY 收发器,支持 USB type C、BC、PD2.0、PD3.0、PPS,还支持 12V 高压电源,PD通讯时需要的BMC编解码、4B5B编解码、CRC校验也都有集成,这样的话用CH543实现一个PD通讯就比较简单了。

单片机原理图CC可以直接连接Type-C端口,不需要额外添加器件(CH543内置5.1K下拉),电路还是比较简单的:



我这次编写的是PD Sink受电端,那么我只需要在合适的场景下及时回复Good CRC和Request,就能完成USB PD通讯。在消息处理上,我是根据CH543 USB PD中断的特点来进行相应消息处理,软件如下:

void PD_PHY_ISR(void) interrupt INT_NO_USBPD using 1
{
      if ( PIF_RX_RST ) {/*接收到复位信息中断*/
                printf("RST ");
                PD_PHY_HRST_ISR();                //收到HRST
                PD_PHY_RX_INIT();
      }
      if ( PIF_RX_ACT ){/*数据包接受完成中断*/
            TR0 = 0;ET0 = 0;
                        PIF_RX_ACT = 0;
                if ( (UPD_INT_FG & MASK_PD_STAT) == PD_RX_SOP0 ) {      //收到HRST或SOP数据               
                        Union_Header = (_Union_Header *)PD_RX_BUF;    //强制转化                        
                                        if ( PD_PHY_STAT.WaitingGoodCRC ) {          //是否在等待GoodCRC
                                          if(Union_Header->HeaderStruct.MsgType == GoodCRC){
                                                      PD_PHY_STAT.WaitingGoodCRC = 0;                  
                                                      if(PD_PHY_STAT.SendingRequest == 1){ PD_PHY_STAT.SendingRequest = 0;}      
                                                      Send_Count = 0;
                                                      PD_PROT_ISR();                                             
                              }
                        }else {               
                                        switch(Union_Header->HeaderStruct.MsgType)
                                        {
                                                case SourceCap:
                                                               NDORcv=Union_Header->HeaderStruct.NDO;
                                                         memcpy(PD_Source,PD_RX_BUF,30);
                                                               PD_PHY_STAT.SendingRequest = 1;
                                                               break;
                                                case Request:
                                                               break;
                                                case Accept:
                                                         MsgID ++;
                                                               break;
                                                case Reject:
                                                               break;
                                                case PS_RDY:
                                                               break;
                                                case GetSrcCap:
                                                            MsgID++;
                                                         PD_PHY_STAT.SendingSourceCap = 1;
                                                   break;
                                                case GetSinkCap:
                                                               MsgID++;
                                                               PD_PHY_STAT.SendingSinkCap = 1;         
                                                               break;
                                          case SourceCap_VDM:
                                                               break;      
                                                default :
                                                break;      
                                        }
                                                mDelayuS(25);
                                          PD_PHY_STAT.SendingGoodCRC = 1;      //置发送GoodCRC标志位
                                                PD_PHY_TX_GoodCRC();                              //回复GoodCRC      
                        }      
      }else PD_PHY_RX_INIT();
}
      if ( PIF_TX_END ) {                /*数据包发送完成中断 */
                  PIF_TX_END = 0;
                        if(CCSel == 1){//发送完成关闭低压
                              CC1_CTRL &= ~bCC_LVO;      
                        }else if(CCSel == 2){
                         CC2_CTRL &= ~bCC_LVO;      
                        }            
                if ( PD_PHY_STAT.SendingGoodCRC ==1 ) {                     
                        PD_PHY_STAT.SendingGoodCRC = 0;
                        PD_PROT_ISR(); //GoodCRC发送完成,向Prot转交数据
                }else {                        /*开始接收GoodCRC*/
                                        PD_PHY_STAT.WaitingGoodCRC = 1;      
                                           mTimer_x_SetData(10000);    //5ms
                                        PD_PHY_RX_INIT();
                }
      }
}Sink端的程序编写从一个Request开始,Request请求结构如下:


下面是用CH543编写Request的代码:
void PD_PHY_TX_Request(void)
{
      UINT16 Volt_Value;
UINT8 temp=0xff;//pdo 档位
      UINT8 i;
      UINT16 Temp;
      UINT16 Data_H8;
      UINT16 Cur_Temp = 0;
      
      for (i=0;i!=NDORcv;i++)
      {
                        Union_SrcCap = (_Union_SrcCap *)&PD_Source;
                        Data_H8 = (Union_SrcCap->SrcCapStruct.DataH8);
                        if((Data_H8 >> 6 ) == 3)
                        {
                                        PPS_Flag = 1;
                                        printf("\r %d is PPS\n",(UINT16)(i+1));
                        }else{
                                                      Temp = (PD_Source >> 2)+((PD_Source & 0x0F)<<6);
                                                      Temp*=50;
                                                      if (Temp <= Volt_Value )
                                                      {
                                                                if(Temp > Cur_Temp)
                                                                {
                                                                        Cur_Temp = Temp;
                                                                        temp = i+1;
                                                                }
                                                      }
                         }
      }
      if(Temp_PDO != temp )
      {
                printf("No requested voltage! \r\n");
                printf("Request adjacent voltage! \r\n");
      }
      if(temp != 0xff)
      {
                        UPD_T_SOP = UPD_SOP0;
                        UPD_T_LEN = 6;
                        PD_TX_BUF =( (temp<<4) | 0x02);
                        PD_TX_BUF = ((PD_Source&0x03)<<2) |(PD_Source>>6) | (PD_Source &0xF0);
                        PD_TX_BUF =(PD_Source&0x03) | (PD_Source<<2);
                        PD_TX_BUF = PD_Source;
                        PD_TX_BUF = (0x10 | (MsgID << 1));
                        PD_TX_BUF =0x02 | (0xC0 & PD_Source);
                        PD_PHY_TX_INIT();
      }else{      
          printf("No Matched Volt.\r\n");
      }
}
按照PD的协商流程,可以完成PD通讯。另外在工程中增加了I/O中断来调节电压档位升降:
主程序内循环检测:
if(Gears_Plus == 1)
      {
   if(P1_5 == 1)
               {
                         PD_Request_Gears += 1;
                         Gears_Plus = 0;
               }
                if(PD_Request_Gears>5)      
               {
      PD_Request_Gears = 1;
               }                        
      }
      if(Gears_Sub == 1)
      {
               if(P1_4 == 1)
               {
                         PD_Request_Gears -= 1;
                         Gears_Sub = 0;
               }
   if(PD_Request_Gears<1)      
               {
      PD_Request_Gears = 5;
               }                        
      }
      if(Temp_PDO !=PD_Request_Gears)
      {
                Temp_PDO = PD_Request_Gears;

                printf("Temp_PDO == %d\r\n",(UINT16)Temp_PDO);
                PD_PHY_TX_Request();
      }


通过逻辑分析仪观察,PD的SINK通讯成功!


当主机发来Source Cap后,设备端点按照PD协议流程进行响应并回复Request,直到主机发送PS_RDY 结束一次PD请求调压过程,然后设备端点可以多次发送Request来请求新的电压,从上图中可以看出,多次请求成功!

至此PD Sink端的应用升降电压就实现了,另外要实现PPS调压还需要完善程序代码(PPS和固定档位的PDO处理方法有一定区别),后面会找时间继续。附件是硬件、软件资料,小伙伴们可以按需下载。



Liuhl1998 发表于 2022-3-16 16:34

本帖最后由 Liuhl1998 于 2022-3-18 18:33 编辑

叙述的是大概流程,想要深入了解的可以参考www.usb.org 下载PD协议相关手册。

cjseng 发表于 2022-3-18 15:36

楼主很厉害,不错

ahongW 发表于 2022-3-18 17:32

看完了感觉很神秘!

吃一颗草莓莓 发表于 2022-3-20 20:02

我不允许这篇贴还没有人看到!写得太好了吧!

ughbss 发表于 2022-3-22 13:42

有空我也研究下PD

Liuhl1998 发表于 2022-3-23 10:04

cjseng 发表于 2022-3-18 15:36
楼主很厉害,不错

{:tongue:}

Liuhl1998 发表于 2022-3-23 10:05

ughbss 发表于 2022-3-22 13:42
有空我也研究下PD

我这有好多资料,需要时候扣我

Liuhl1998 发表于 2022-3-23 10:06

ahongW 发表于 2022-3-18 17:32
看完了感觉很神秘!

为了篇幅 精简,牺牲了内容。

pq113_6 发表于 2022-3-25 16:15

CH549应该也支持吧?官方资料有点少,讲的不清楚,是不是要用到库?

Liuhl1998 发表于 2022-4-11 10:01

playxzy 发表于 2022-4-13 15:17

学习了

mainch 发表于 2022-5-23 14:25

谢谢楼主,有用的东西

Liuhl1998 发表于 2022-7-21 14:51

有最新代码   需要的说一声

dryy 发表于 2022-7-29 20:31

Liuhl1998 发表于 2022-7-21 14:51
有最新代码   需要的说一声

楼主请问有协议层代码可以分享一下吗

zwsam 发表于 2022-8-8 09:13

Liuhl1998 发表于 2022-8-8 17:59

dryy 发表于 2022-7-29 20:31
楼主请问有协议层代码可以分享一下吗

都在例程里了哈,需要最新的可以私我

Liuhl1998 发表于 2022-8-8 17:59

zwsam 发表于 2022-8-8 09:13


{:shy:}

stb988 发表于 2022-8-23 08:43

gentlman2006 发表于 2022-8-27 16:50

请问楼主PPS的实现了吗
页: [1] 2 3
查看完整版本: PD快充也没那么神秘CH543D(PD诱骗受电Sink解析)