cancel
Showing results for 
Search instead for 
Did you mean: 

Problem with dynamicaly changing USB-C PDOs

Marosh
Associate III

Hello,

I am trying to make my own custom USB PD application on NucleoG0B1RE + SNK1M1 hardware.

I want to change dynamically the SNK PDOs in my application (it should be possible according to this post). Since I want it to be working with various Source adapters and I dont know their PDOs appriory I want to copy the SRC PDOs into SNK list (if they not exceed the sink power limits, like 100W). 

So I have updated the USB_USER_SERV_StoreSRCPDO() function located in usbpd_user_services.c:

 

/**
  * @brief  Store the received source PDO
  * @PAram  PortNum Port number
  * @PAram  Ptr     Pointer on the data
  * @PAram  Size    Nb of bytes to be updated in DPM
  * @retval None
  */
void USBPD_USER_SERV_StoreSRCPDO(uint8_t PortNum, uint8_t *Ptr, uint32_t Size)
{
  /*!< Storage of Received Source PDO values */
  if (Size <= (USBPD_MAX_NB_PDO * 4U))
  {
    uint8_t *rdo;
    DPM_Ports[PortNum].DPM_NumberOfRcvSRCPDO = (Size / 4U);
    /* Copy PDO data in DPM Handle field */
    for (uint32_t index = 0; index < (Size / 4U); index++)
    {
      rdo = (uint8_t *)&DPM_Ports[PortNum].DPM_ListOfRcvSRCPDO[index];
      (void)memcpy(rdo, (Ptr + (index * 4U)), (4U * sizeof(uint8_t)));

      // Copy PDO data in SINK pdo definition
      rdo = (uint8_t *)&PORT0_PDO_ListSNK[index];
      (void)memcpy(rdo, (Ptr + (index * 4U)), (4U * sizeof(uint8_t)));
    }

  }
}

 

This function should just copy the SRC PDOs to SNK. I have checked the PORT0_PDO_ListCNK struct in debbuger mode and it updates correctly. Also when I open the UCPD monitor I can see the PDOs under Sink Capabilities tab located at Port Configuration. The only problem is that I am able to request only Fixed PDOs and not APDOs. If I delete one of the APDOs in UCPD monitor, reconfigure it again with same values and send it to the target APDO request suddenly works.

 

1 ACCEPTED SOLUTION

Accepted Solutions
Guenael Cadier
ST Employee

Hi @Marosh 
In my understanding, you could have your definition of SNK PDOs covering various voltages and power. At the end, whatever those PDOs, decision about which SRC PDO to choose to build the REQUEST message is a SNK decision.
Entry point of this selection/decision in ST provided examples is the USBPD_DPM_SNK_EvaluateCapabilities() function in usbpd_dpm_user.c file. This function provides an example of implementation, but it could/should be updated by you if you want to implement another policy/strategy. You might feel the need to update SNK PDO, in order to request the "right" SRC PDO, as current example implementation is looking for a "best match" between received SRC PDOs, and defined SNK PDOs.

But you could also consider reworking the USBPD_DPM_SNK_EvaluateCapabilities() function to do what you need.
More details about this function here
Hope this helps
Regards

View solution in original post

6 REPLIES 6
Guenael Cadier
ST Employee

Hi @Marosh 
SRC and SNK PDO are not built on same structure, so you cannot copy Received PDO from the SRC, into the SNK PDO of your equipement. Please check chapters 6.4.1.2 and 6.4.1.3 of Universal Serial Bus
Power Delivery Specification.
Example for Fixed PDO :
SRC structure :

GuenaelCadier_0-1717583856592.png

SNK structure :

GuenaelCadier_1-1717583906244.png

Regards

 

Hi @Guenael Cadiery,

Thank you very much for pointing out this issue. I have missed this difference. It seemed to me that it doesn't matter since defining the APDO in UCPD monitor yielded the same SNK APDO value as the SRC APDO when checking live expressions in the debugger.
So the step to do what I want would be extracting the type, voltage range and current from the SRC_PDO_List and using this together with the info from UM2552 (pdo def) to construct the SNK PDO. Is this correct approach?

// Example how to construct SNK APDO
/* PDO 2 : SRC APDO */
((((PWR_A_50MA(1.5)) << USBPD_PDO_SNK_APDO_MAX_CURRENT_Pos) |
(((PWR_V_100MV(3)) << USBPD_PDO_SNK_APDO_MIN_VOLTAGE_Pos) |
(((PWR_V_100MV(5.9)) << USBPD_PDO_SNK_APDO_MAX_VOLTAGE_Pos)|
USBPD_PDO_TYPE_APDO
),

 

Yes, should be better.

If I understand properly your goal, you need to adjust only the "power" related fields of your SNK PDO.

Please also note that some elements shown in the snapshots of PDO structures I attached in my previous post, are only provided in the first PDO (this concerns "global" settings or feature support, that are not PDO dependent. For instance the bit indicating support of the DRP is only provided in the first PDO, and not repeated in further ones). Just to mention this.

May I ask a question : what is the purpose of updating your SNK PDOs to received SRC PDOs ? is it related to REQUEST message building   ?
(as you see, in applications ST provides, this part of code is considered as "User policy", and could be adapted as needed by your application).

Regards

Yes I am mainly interested in defining the min/max voltage and max current for the SNK PDO.
I want to use my sink as portable programmable power supply and want to support wide range of sources up to 100W. I have also thought about predefining few SNK APDOs that would cover the range. But after playing with it in UCPD monitor I have concluded that I need to copy the SRC PDOs to the SNK in order it works. Maybe it could be also achieved by changing the Request procedure but I am not sure how.

Guenael Cadier
ST Employee

Hi @Marosh 
In my understanding, you could have your definition of SNK PDOs covering various voltages and power. At the end, whatever those PDOs, decision about which SRC PDO to choose to build the REQUEST message is a SNK decision.
Entry point of this selection/decision in ST provided examples is the USBPD_DPM_SNK_EvaluateCapabilities() function in usbpd_dpm_user.c file. This function provides an example of implementation, but it could/should be updated by you if you want to implement another policy/strategy. You might feel the need to update SNK PDO, in order to request the "right" SRC PDO, as current example implementation is looking for a "best match" between received SRC PDOs, and defined SNK PDOs.

But you could also consider reworking the USBPD_DPM_SNK_EvaluateCapabilities() function to do what you need.
More details about this function here
Hope this helps
Regards

Hi @Guenael Cadier,

After some time of playing with the code, it does s what I want, so thank you again for your guidance. In the end, there was no need to copy the PDOs from SRC to SNK. I have written my custom functions with help of your advice and this sources here and here. I will just leave it here as a legacy, maybe it could come in handy to someone else in the future.

The index for the RequestSRCPDO call is found with FindSRCIndex function that is supposed to find APDO with the highest current capability (PDO_SEL_METHOD_MAX_CUR) that is also within user requested voltage and current range. Both FindSRCIndex and RequestSRCPDO functions are called in custom user app after user press the request button.

usbpd_dpm_user.c

/**
  * @brief  Request the PE to send a request message.
  * @param  PortNum     The current port number
  * @param  IndexSrcPDO Index on the selected SRC PDO (value between 1 to 7)
  * @param  RequestedVoltage Requested voltage (in MV and use mainly for APDO)
  * @param  RequestedCurrent Requested current (in MA and use mainly for APDO)
  * @retval USBPD Status
  */
USBPD_StatusTypeDef USBPD_DPM_RequestSRCPDO(uint8_t PortNum, uint8_t IndexSrcPDO, uint16_t RequestedVoltage, uint16_t RequestedCurrent)
{
	USBPD_StatusTypeDef _status = USBPD_ERROR;
	USBPD_SNKRDO_TypeDef rdo;
	USBPD_CORE_PDO_Type_TypeDef pdo_object;

	/* Initialize RDO object*/
	rdo.d32 = 0;

	USER_SERV_SNK_BuildRequestedRDO(PortNum,IndexSrcPDO, RequestedVoltage, RequestedCurrent, &rdo, &pdo_object);

	/*Send requested rdo to Policy Engine */
	_status = USBPD_PE_Send_Request(PortNum, rdo.d32, pdo_object);

    /* USER CODE END USBPD_DPM_RequestMessageRequest */
	DPM_USER_ERROR_TRACE(PortNum, _status, "REQUEST not accepted by the stack");
	return _status;
}

 

usbpd_user_services.c

/**
  * @brief  Find best SRC (A)PDO index depending on user requested voltage and selection method: max/min power, voltage, or current.
  * @param  PortNum Port number
  * @param  PtrRequestPowerDetails  Sink requested power details structure pointer
  * @param  Method  Method used to find the "best" PDO. This parameter can be one of the following values:
  *         @arg @ref PDO_SEL_METHOD_MAX_CUR
  *         @arg @ref PDO_SEL_METHOD_MIN_CUR
  * @retval Index of PDO within source capabilities message (DPM_NO_SRC_PDO_FOUND indicating not found)
  */
uint32_t USER_SERV_FindSRCIndex(uint32_t PortNum,
											USBPD_DPM_SNKPowerRequestDetailsTypeDef *PtrRequestPowerDetails,
											uint16_t Voltage_mV,
											uint16_t Current_mA,
											uint8_t Method)

{
	USBPD_PDO_TypeDef srcpdo;
	uint32_t *ptpdoarray;
	uint32_t reqvoltage = Voltage_mV;
	uint32_t reqcurrent = Current_mA;
	uint32_t nbsrcpdo;
	uint32_t allowablepower;
	uint32_t selpower;
	uint32_t allowablecurrent;
	uint32_t selcurrent;
	uint32_t curr_index = DPM_NO_SRC_PDO_FOUND;
	uint32_t temp_index;
	USBPD_USER_SettingsTypeDef *puser = (USBPD_USER_SettingsTypeDef *)&DPM_USER_Settings[PortNum];

	selcurrent     = 0;

	//Get number of source PDOs
	nbsrcpdo = DPM_Ports[PortNum].DPM_NumberOfRcvSRCPDO;
	//Get array list of SRC PDOs
	ptpdoarray = DPM_Ports[PortNum].DPM_ListOfRcvSRCPDO;

	/* Check SRC PDO value according to its type */
	for (temp_index = 0; temp_index < nbsrcpdo; temp_index++)
	{
		srcpdo.d32 = ptpdoarray[temp_index];

		switch (srcpdo.GenericPDO.PowerObject)
		{
		/* SRC Fixed Supply PDO */
		case USBPD_CORE_PDO_TYPE_FIXED:
		{
		}
		/* Augmented Power Data Object (APDO) */
		case USBPD_CORE_PDO_TYPE_APDO:
		{
			uint16_t srcmaxvoltage100mv;
			uint16_t srcminvoltage100mv;
			uint16_t srcmaxcurrent50ma;
			//Extract voltage and current limits of given SRC APDO
			srcmaxvoltage100mv = srcpdo.SRCSNKAPDO.MaxVoltageIn100mV;
			srcminvoltage100mv = srcpdo.SRCSNKAPDO.MinVoltageIn100mV;
			srcmaxcurrent50ma = srcpdo.SRCSNKAPDO.MaxCurrentIn50mAunits;

			/*Check if reqvoltage falls within SRC_APDO voltage range*/
			if ( (PWR_DECODE_100MV(srcminvoltage100mv) <= reqvoltage) && (reqvoltage <= PWR_DECODE_100MV(srcmaxvoltage100mv)) )
			{
				/*Check that reqcurrent is smaller or equal to srcmaxcurrent*/
				if ( (reqcurrent <= PWR_DECODE_50MA(srcmaxcurrent50ma)) && (reqcurrent != 0) )
				{
					/*Convert srcmaxcurrent into mV*/
					allowablecurrent = PWR_DECODE_50MA(srcmaxcurrent50ma);

					/*Find the best APDO index based on the method */
					switch(Method)
					{
					case PDO_SEL_METHOD_MAX_CUR:
						if (allowablecurrent > selcurrent)
						{
							/* Consider the current PDO the best one until now */
							curr_index = temp_index;
							selcurrent = allowablecurrent;
						}
						break;

					case PDO_SEL_METHOD_MIN_CUR:
						if ((allowablecurrent < selcurrent) || (selcurrent == 0))
						{
							/* Consider the current PDO the best one until now */
							curr_index = temp_index;
							selcurrent = allowablecurrent;
						}
						break;

					default:
						/* Default behavior: last PDO is selected */
						curr_index = temp_index;
						selcurrent = allowablecurrent;
					}
				}
			}
		}

		}
	}

	return curr_index+1;
}

void USER_SERV_SNK_BuildRequestedRDO(uint8_t PortNum,
                                                  uint16_t IndexSrcPDO,
												  uint16_t Voltage_mV, uint16_t Current_mA,
                                                  USBPD_SNKRDO_TypeDef *Rdo,
                                                  USBPD_CORE_PDO_Type_TypeDef *PtrPowerObject)
{
  uint32_t mv = Voltage_mV;
  uint32_t ma = Current_mA;
  USBPD_PDO_TypeDef  pdo;
  USBPD_SNKRDO_TypeDef rdo;
  USBPD_HandleTypeDef *pdhandle = &DPM_Ports[PortNum];

  /* Initialize RDO */
  rdo.d32 = 0;
  rdo.GenericRDO.USBCommunicationsCapable = 0; //snk_fixed_pdo.SNKFixedPDO.USBCommunicationsCapable;
                                               //Shall only be set for Sources capable of communication over the USB data lines
  rdo.GenericRDO.NoUSBSuspend             = 1; //Sinks May indicate to the Source that they would prefer to have the USB Suspend Supported flag cleared by setting
                                               //the No USB Suspend flag in a Request Message
  rdo.GenericRDO.ObjectPosition = IndexSrcPDO;

  /* Initialize PDO */
  pdo.d32 = pdhandle->DPM_ListOfRcvSRCPDO[IndexSrcPDO-1];
  *PtrPowerObject = pdo.GenericPDO.PowerObject;

  /* Build RDO*/
  switch (pdo.GenericPDO.PowerObject)
    {
      case USBPD_CORE_PDO_TYPE_FIXED:
      {
        DPM_Ports[PortNum].DPM_RequestedCurrent           = ma;
        rdo.FixedVariableRDO.OperatingCurrentIn10mAunits  = ma / 10U;
        rdo.FixedVariableRDO.MaxOperatingCurrent10mAunits = ma / 10U;
      }
      break;

      case USBPD_CORE_PDO_TYPE_APDO:
      {
        DPM_Ports[PortNum].DPM_RequestedCurrent    = ma;
        rdo.ProgRDO.ObjectPosition                 = IndexSrcPDO;
        rdo.ProgRDO.OperatingCurrentIn50mAunits    = ma / 50U;
        rdo.ProgRDO.OutputVoltageIn20mV            = mv / 20U;
      }
      break;

      default:
        break;
    }

  /*Assign request values to pdhandle*/
  pdhandle->DPM_RDOPositionPrevious = pdhandle->DPM_RDOPosition;
  pdhandle->DPM_RDOPosition = IndexSrcPDO; //pdhandle->DPM_RDOPosition  = rdo.GenericRDO.ObjectPosition;
  pdhandle->DPM_RequestedVoltage = mv;
  pdhandle->DPM_RequestedCurrent = ma;

  pdhandle->DPM_RequestDOMsg = rdo.d32;
  Rdo->d32 = pdhandle->DPM_RequestDOMsg;

}