当前位置:   article > 正文

Type-C PD快充基础讲解 + 实例(开源)_ch543

ch543

现阶段快充的技术应用非常广泛,快充适配器在我们身边也随处可见,那快充是如何实现的呢?是不是有很多小伙伴存在这类的疑惑。那我们今天就一起来会会它,看一看快充是不是如我们想的那般神秘。
另外,我们身边有各种各样的受电设备需要不同的功率充电或运作,这时候往往需要对应着功率来配置一个适配器。那如果我们能随心所欲的去“控制”快充适配器输出对应的电压,不就能用一个快充适配器来满足不同功率设备的供电吗?这样一来就能解决原装适配器带来的限制,不再为原装适配器的丢失以及损坏而烦恼。
 并且将快充的技术应用在产品中,可以摆脱设备充电时效率慢及时间长的难题,也可以满足无电池设备的功率问题。那下文我将针对最近比较流行的USB PD快充进行讲解,并演示如何控制适配器输出不同的电压。
   下面我先会简易的讲解一下PD通讯过程,有兴趣想要深入了解USB PD协议的小伙伴可以到 www.usb.org 下载PD协议相关手册。
1、首先来了解PD通讯数据的基本结构:Preamble + SOP + Message + CRC + EOP。如下图:
(1) Preamble:64bit连续的0/1数据的前导码(0开头,1结尾,频率303kHZ左右)。
(2) SOP:同步码1/2/3组成(包含类型:SOP,SOP`,SOP``),告知是在与Cable/Sink通讯。
(3) Message:Header + Object 用于传递命令、请求、响应。
(4) CRC:CRC-32 用于对消息的校验。
(5) EOP:“01101”用于作为结束标志。

2、PD通讯数据的基本结构我们了解了,那么PD通讯物理层是如何收发消息的呢?废话就不多说了,接下来我将会从接受/发送两部分进行讲解。
  先来说下如何发送消息,首先将数据Data和校验码CRC进行4b5b编码后通过BMC发送到CC线上,这就完成PD消息的发送,是不是很简单,也没那么神秘吧!如下图:


 

 至于接收消息刚好和发送消息反过来,当检测到CC上有消息到来时,物理层会通过BMC进行接收,然后检查SOP类型,最后进行4b5b解码,将会得到数据Data和CRC校验码。如下图:

3、讲到这里应该有小伙伴开始对4b5b编解码、BMC编解码很疑惑吧?其实 4b5b是PD通讯规定的数据手段,这样能有效的避免数据在传输时造成传输错误。BMC编解码则是在数据4b5b转换的基础上进行电平的翻转(0不变,1翻转)。那么数据是如何进行4b5b转换呢?USB PD手册中给出了解答,如下图:

通过上述简易的分析,可以了解USB PD收发消息的基本概念,能得出PD通讯是需要基于一些硬性条件,比如:需要4b5b编解码、BMC发送、消息校验等。至于博主为什么会在众多单片机产品中选择CH543而不是其它单片机呢?是因为 CH543内置了USB PD收发器PHY和USB Power Delivery控制器,另外PD通讯时是可以自动BMC编解码、4B5B编解码、CRC校验。这样一来不仅能大量的节约代码空间,还能减轻工程带来的难度(嘿嘿,因为不用进行4B5B转码,不用CRC校验,也不用编写前导包。这些都好折磨人,现在可就轻松了不少)。

  并且CH543是利用USB PD中断来处理PD消息,这一功能是很多单片机不具备的,一般的单片机是通过在while(1)循环中采用查询的方式进行收发消息,这样不仅大量的占用主函数代码空间,同时还会造成代码阅读上不必要的困扰,这样对比一下CH543还是蛮香的。 

接下来进入主题了,来看下硬件方面的设计吧,在硬件方面使用了CH543主控MCU的两个引脚(P10:CC1和P11:CC2),就可以轻松实现PD Sink端(是不是觉得不可思议),工程使用的demo板是从www.wch.cn那里申请,有兴趣的小伙伴可以去看看。硬件原理图如下:

 现在就来看下效果:

 硬件方面讲完了,下面来看一下软件方面的处理,我这次编写的是PD Sink受电端,有兴趣的小伙伴可以根据PD快充通讯具体流程(如下图),反推出写出Source供电端(可以做个简易版的适配器)。另外还可以查阅PD 相关资料进行拓展编写VDM、PPS消息等。本文代码将会在文章最后打包奉上(包含:Source、Sink、VDM)。

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

  1. void PD_PHY_ISR(void) interrupt INT_NO_USBPD using 1
  2. {
  3. if ( PIF_RX_RST ) {/*接收到复位信息中断*/
  4. printf("RST ");
  5. PD_PHY_HRST_ISR(); //收到HRST
  6. PD_PHY_RX_INIT();
  7. }
  8. if ( PIF_RX_ACT ){/*数据包接受完成中断*/
  9. TR0 = 0; ET0 = 0;
  10. PIF_RX_ACT = 0;
  11. if ( (UPD_INT_FG & MASK_PD_STAT) == PD_RX_SOP0 ) { //收到HRST或SOP数据
  12. Union_Header = (_Union_Header *)PD_RX_BUF; //强制转化
  13. if ( PD_PHY_STAT.WaitingGoodCRC ) { //是否在等待GoodCRC
  14. if(Union_Header->HeaderStruct.MsgType == GoodCRC){
  15. PD_PHY_STAT.WaitingGoodCRC = 0;
  16. if(PD_PHY_STAT.SendingRequest == 1){ PD_PHY_STAT.SendingRequest = 0;}
  17. Send_Count = 0;
  18. PD_PROT_ISR();
  19. }
  20. }else {
  21. switch(Union_Header->HeaderStruct.MsgType)
  22. {
  23. case SourceCap:
  24. NDORcv=Union_Header->HeaderStruct.NDO;
  25. memcpy(PD_Source,PD_RX_BUF,30);
  26. PD_PHY_STAT.SendingRequest = 1;
  27. break;
  28. case Request:
  29. break;
  30. case Accept:
  31. MsgID ++;
  32. break;
  33. case Reject:
  34. break;
  35. case PS_RDY:
  36. break;
  37. case GetSrcCap:
  38. MsgID++;
  39. PD_PHY_STAT.SendingSourceCap = 1;
  40. break;
  41. case GetSinkCap:
  42. MsgID++;
  43. PD_PHY_STAT.SendingSinkCap = 1;
  44. break;
  45. case SourceCap_VDM:
  46. break;
  47. default :
  48. break;
  49. }
  50. mDelayuS(25);
  51. PD_PHY_STAT.SendingGoodCRC = 1; //置发送GoodCRC标志位
  52. PD_PHY_TX_GoodCRC(); //回复GoodCRC
  53. }
  54. }else PD_PHY_RX_INIT();
  55. }
  56. if ( PIF_TX_END ) { /*数据包发送完成中断 */
  57. PIF_TX_END = 0;
  58. if(CCSel == 1){ //发送完成关闭低压
  59. CC1_CTRL &= ~bCC_LVO;
  60. }else if(CCSel == 2){
  61. CC2_CTRL &= ~bCC_LVO;
  62. }
  63. if ( PD_PHY_STAT.SendingGoodCRC ==1 ) {
  64. PD_PHY_STAT.SendingGoodCRC = 0;
  65. PD_PROT_ISR(); //GoodCRC发送完成,向Prot转交数据
  66. }else { /*开始接收GoodCRC*/
  67. PD_PHY_STAT.WaitingGoodCRC = 1;
  68. mTimer_x_SetData(10000); //5ms
  69. PD_PHY_RX_INIT();
  70. }
  71. }
  72. }

 因为作为Sink端请求电压是最为重要的,那么该如何来请求电压呢?里面将会涉及到怎样的知识点?下图就详细讲解了PD Request数据各个位的作用。有兴趣的小伙伴可以查阅USB PD协议对应着理解。

根据上图提示,进行软件编写Request,因为Request包含一个请求项Data Object,所以NDO固定为1(表示Header之后有几个字节的数据)。
   Message ID是根据消息次序来决定(一般Sink发送Request为起始0,后续Sink发送时ID自动加一,回复Good CRC除外),Message ID最大为7,若超过则从0重新计数。
PorPwrRole表示电源角色,Source为1,Sink为0。
Rev2表示PD的版本信息,0x01表示PD2.0,0x10表示PD3.0。
   PortDataRole表示数据角色,根据需求来填写主从,1表示主,0表示从。
   MessageType表示此项内容是什么类型数据,0x02表示Request,0x01表示GoodCRC。
   Objpos 表示请求SourceCap档位中的第几项,1:第一项、2:第二项、、、、
   Capability Mismatch表示能力不匹配,0:匹配,1:不匹配。
   USB Communications 表示USB通讯能力,按需求配置。
   No USB Suspend 表示没有USB挂起,0:有USB挂起,0:没有USB挂起。
   Unchunked Extended Message 未分块的扩展信息。
   Operation Current in 10mA units表示请求电压的电流大小。
   Max Operation Current in 10mA units表示最大承受电流。
 下面是用CH543编写Request的代码:

  1. void PD_PHY_TX_Request(void)
  2. {
  3. UINT16 Volt_Value;
  4. UINT8 temp=0xff; //pdo 档位
  5. UINT8 i;
  6. UINT16 Temp;
  7. UINT16 Data_H8;
  8. UINT16 Cur_Temp = 0;
  9. for (i=0;i!=NDORcv;i++)
  10. {
  11. Union_SrcCap = (_Union_SrcCap *)&PD_Source[2+(4*i)];
  12. Data_H8 = (Union_SrcCap->SrcCapStruct.DataH8);
  13. if((Data_H8 >> 6 ) == 3)
  14. {
  15. PPS_Flag = 1;
  16. printf("\r %d is PPS\n",(UINT16)(i+1));
  17. }else{
  18. Temp = (PD_Source[3+(i<<2)] >> 2)+((PD_Source[4+(i<<2)] & 0x0F)<<6);
  19. Temp*=50;
  20. if (Temp <= Volt_Value )
  21. {
  22. if(Temp > Cur_Temp)
  23. {
  24. Cur_Temp = Temp;
  25. temp = i+1;
  26. }
  27. }
  28. }
  29. }
  30. if(Temp_PDO != temp )
  31. {
  32. printf("No requested voltage! \r\n");
  33. printf("Request adjacent voltage! \r\n");
  34. }
  35. if(temp != 0xff)
  36. {
  37. UPD_T_SOP = UPD_SOP0;
  38. UPD_T_LEN = 6;
  39. PD_TX_BUF[5] =( (temp<<4) | 0x02);
  40. PD_TX_BUF[4] = ((PD_Source[3+((temp - 1)<<2)]&0x03)<<2) |(PD_Source[2+((temp - 1)<<2)]>>6) | (PD_Source[4+((temp - 1)<<2)] &0xF0);
  41. PD_TX_BUF[3] =(PD_Source[3+((temp - 1)<<2)]&0x03) | (PD_Source[2+((temp - 1)<<2)]<<2);
  42. PD_TX_BUF[2] = PD_Source[2+((temp - 1)<<2)];
  43. PD_TX_BUF[1] = (0x10 | (MsgID << 1));
  44. PD_TX_BUF[0] =0x02 | (0xC0 & PD_Source[0]);
  45. PD_PHY_TX_INIT();
  46. }else{
  47. printf("No Matched Volt.\r\n");
  48. }
  49. }

 相信很多小伙伴都有发现PD进行充电时,充电电压并不是一直保持不变的,而是可以动态切换请求电压,那是如何进行动态切换的呢?动态请求电压会出现什么样的现象?针对这一个疑惑,我就利用IO中断来对请求的档位进行加减,实现这样现象。软件代码如下:

  1. if(Gears_Plus == 1)
  2. {
  3. if(P1_5 == 1)
  4. {
  5. PD_Request_Gears += 1;
  6. Gears_Plus = 0;
  7. }
  8. if(PD_Request_Gears>5)
  9. {
  10. PD_Request_Gears = 1;
  11. }
  12. }
  13. if(Gears_Sub == 1)
  14. {
  15. if(P1_4 == 1)
  16. {
  17. PD_Request_Gears -= 1;
  18. Gears_Sub = 0;
  19. }
  20. if(PD_Request_Gears<1)
  21. {
  22. PD_Request_Gears = 5;
  23. }
  24. }

 通过验证动态请求电压是可行的(如下图),这样的话各位小伙伴可以利用快充适配器来得到一个动态电压(5V、9V、12V、15V、20V),提供给不同的用电设备使用,也可以将USB PD快充的技术应用到自己的产品中,不就能摆脱了传统充电时间长和功率小的问题了嘛!

至此PD Sink端的应用就要告一段落了,附件是硬件、软件资料,小伙伴们可以按需下载。

PD快充也没那么神秘(开源资料) - 单片机论坛,单片机技术交流论坛 - 21ic电子技术开发论坛

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/179120
推荐阅读
相关标签
  

闽ICP备14008679号