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

Type-C PD快充基础讲解 + 实例(开源),第1张

现阶段快充的技术应用非常广泛,快充适配器在我们身边也随处可见,那快充是如何实现的呢?是不是有很多小伙伴存在这类的疑惑。


那我们今天就一起来会会它,看一看快充是不是如我们想的那般神秘。



另外,我们身边有各种各样的受电设备需要不同的功率充电或运作,这时候往往需要对应着功率来配置一个适配器。


那如果我们能随心所欲的去“控制”快充适配器输出对应的电压,不就能用一个快充适配器来满足不同功率设备的供电吗?这样一来就能解决原装适配器带来的限制,不再为原装适配器的丢失以及损坏而烦恼。



 并且将快充的技术应用在产品中,可以摆脱设备充电时效率慢及时间长的难题,也可以满足无电池设备的功率问题。


那下文我将针对最近比较流行的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中断的特点来进行相应消息处理,软件如下:

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端请求电压是最为重要的,那么该如何来请求电压呢?里面将会涉及到怎样的知识点?下图就详细讲解了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的代码:

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[2+(4*i)];  
			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[3+(i<<2)] >> 2)+((PD_Source[4+(i<<2)] & 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[5] =( (temp<<4) | 0x02);
			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);
			PD_TX_BUF[3] =(PD_Source[3+((temp - 1)<<2)]&0x03) | (PD_Source[2+((temp - 1)<<2)]<<2);
			PD_TX_BUF[2] = PD_Source[2+((temp - 1)<<2)];
			PD_TX_BUF[1] = (0x10 | (MsgID << 1));
			PD_TX_BUF[0] =0x02 | (0xC0 & PD_Source[0]);
			PD_PHY_TX_INIT();
	}else{	
	  printf("No Matched Volt.\r\n");
	}
}

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


软件代码如下:

	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;
		 }			 
	}
	

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

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


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

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/568644.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)

保存