cancel
Showing results for 
Search instead for 
Did you mean: 

How to make custom user USBPD PPS sink application on NucleoG0B1RE + SNK1M1

Marosh
Associate III

Dear community,

I have deployed a basic USB PD sink application according to AN5418 on NucleoG0B1RE + SNK1M1 hardware. I  have also implemented a USBPD_DPM_RequestMessageRequest() function inside usbpd_dmp_user.c (according to this post), so the user can ask for power profiles in CubeMonitor-UCPD

I have also added some custom application code app.c in order to utilize encoder button and segment display. I want the user to choose relevant voltage and current value and make a request for new PPS. Yet the problem is that I am having quite hard time understanding all the different functions and underlying dependencies. Are there some further materials to understand the USBPD lib expect UM2902, and UM2552

I guess my steps are:

1. Get the SRC PDOs and limit the user voltage range

  • Extract the max and min voltages from following struct: DPM_Ports[PortNum].DPM_ListOfRcvSRCPDO[(IndexSrcPDO - 1)];
  • limit the user to choose from allowed voltage range

2. Build rdo object from user request details

  • build USBPD_SNKRDO_TypeDef rdo.d32 or rdo.ProgRDO in my case? from the USBPD_DPM_SNKPowerRequestDetailsTypeDef

3. Find the SrcPDOIndex matching the user request

  • use previously builded rdo in order to find relevant SRC APDO index, probably by using USER_SERV_FindVoltageIndex() (located in usbpd_user_services.c)

4. Request the PPS using USBPD_DPM_RequestMessageRequest() (located in usbpd_dpm_user.c)

  • use SrcPDOIndex from point 3 to make new request

github: link

 

Could you please give me some small guidance or provide me with further resources to better grasp the USB PD library as a whole?

 

Related question:

What is the point of predefining voltage and current ranges for APDOs like in STM32G0C1E-EV/.../usbpd_pdo_defs.h if we dont know what will be the ranges of actual wall adapter (SRC provider)? Or should we just use predefined ranges according to USB PD Specification on (usb.org) under 10.2.3.2.1 SPR Programmable Power Supply Voltage Ranges?

9 REPLIES 9
FBL
ST Employee

Hello @Marosh 

 

Interesting question!

First, the steps are correct:

1. Get the SRC PDOs and limit the user voltage range

2. Build RDO object from user request details

3. Find the SrcPDOIndex matching the user request

4. then request the PPS using USBPD_DPM_RequestMessageRequest() (as you mentioned located in usbpd_dpm_user.c)

 

Second, the predefined voltage and current ranges in usbpd_pdo_defs.h depends on customer application (either fixed PDO or PPS Augmented PDO) In fact; PPS is meant to charge directly battery with needed precise voltage. 

We propose this demo on G0 Eval board or x-cube-tcpp/Projects/NUCLEO-G071RB/Applications/USB_PD/SNK1M1_Sink_PPS at main · STMicroelectronics/x-cube-tcpp (github.com) to learn how to use PPS

These are common use cases that should be compliant with the USB PD specification. These predefined voltage and current ranges are used to configure the device's capabilities and can be adjusted based on your application.

When developing a product, you should consider the range of sources your device will support and define the PDOs accordingly. If you expect to support a wide range of sources, you may need to define a wider set of PDOs.

Indeed, the USBPD specification provides standard ranges for PDOs, but you can also define custom PDOs based on the specific requirements of your application and the capabilities of the sources you intend to have.

In case the source is a multiple port wall charger, it can request to the sink, its capabilities. It will help the wall charger to adapt its power strategy. Because power contract can change a long time. (if you charge a laptop, when charge finishes, you may change PDO to use a lower profile)

We suggest this App Note as well to learn more about the implementation of the VBUS control algorithm VBUS control algorithm compliant with USB Type-C and Power Delivery specifications - Application note (st.com)

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.

Marosh
Associate III

Thank you for your advice @FBL 

I have successfully extracted the src PDOs limits and I know how to build the RDO, yet after further investigation I am stuck on the 3. and 4. point (Finding voltage index and making a Request). Since both these function use function USER_SERV_SNK_EvaluateMatchWithSRCPDO() which requires predefined SNK PDOs. Yet my application is versatile and should support wide range of wall adapters up to 100W. This means I dont know the precise APDO profiles a priori and predefining multiple most common ones, only by guess, doesnt seem like the correct way to do so to me. Is there some way to define the SNK PDOs  (APDOs in my case) dynamically based on received SRC PDOs or could I somehow bypass the need to have predefined SNK PDOs by making my own custom USER_SERV_SNK_EvaluateMatchWithSRCPDO() function?
Also as it seems to me the basic USER_SERV_FindVoltageIndex() function is called within USBPD_USER_SERV_EvaluateCapabilities() which is called on the first contract establishment and later on if user request cannot be reached. Contrary to my previous believe the FindVoltageIndex() function seems to rather work like finding for example maximal SRC PDO index based on chosen MAX_PWR Method. Yet If I want to find PDO index based on user PowerRequest (voltage, current) I probably need to make my own custom function to do so as mentioned in Introduction to USB PD (9.9 SNK PDO selection policy) 

**
  * @brief  Evaluate received Capabilities Message from Source port and prepare the request message
  * @param  PortNum         Port number
  * @param  PtrRequestData  Pointer on selected request data object
  * @param  PtrPowerObjectType  Pointer on the power data object
  * @retval None
  */
void USBPD_USER_SERV_EvaluateCapa(uint8_t PortNum,
                                  uint32_t *PtrRequestData,
                                  USBPD_CORE_PDO_Type_TypeDef *PtrPowerObjectType)
{
  USBPD_PDO_TypeDef  fixed_pdo;
  USBPD_SNKRDO_TypeDef rdo;
  USBPD_HandleTypeDef *pdhandle = &DPM_Ports[PortNum];
  USBPD_USER_SettingsTypeDef *puser = (USBPD_USER_SettingsTypeDef *)&DPM_USER_Settings[PortNum];
  USBPD_DPM_SNKPowerRequestDetailsTypeDef snkpowerrequestdetails;
  uint32_t pdoindex;
  uint32_t size;
  uint32_t snkpdolist[USBPD_MAX_NB_PDO];
  USBPD_PDO_TypeDef snk_fixed_pdo;

  snkpowerrequestdetails.RequestedVoltageInmVunits = 0;
  snkpowerrequestdetails.OperatingCurrentInmAunits = 0;

  /* Find the Pdo index for the requested voltage, depending on the wanted method */
  pdoindex = USER_SERV_FindVoltageIndex(PortNum, &snkpowerrequestdetails, USER_SERV_PDO_Sel_Method);

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

  /* If no valid SNK PDO or if no SRC PDO match found
      (index>=nb of valid received SRC PDOs or function returned DPM_NO_SRC_PDO_FOUND */
  if (pdoindex >= pdhandle->DPM_NumberOfRcvSRCPDO)
  {
#if defined(_TRACE)
    uint8_t msg[] = "USBPD_USER_SERV_EvaluateCapa: could not find desired voltage";
    USBPD_TRACE_Add(USBPD_TRACE_DEBUG, PortNum, 0, (uint8_t *)msg, sizeof(msg));
#endif /* _TRACE */
    fixed_pdo.d32 = pdhandle->DPM_ListOfRcvSRCPDO[0];
    /* Read SNK PDO list for retrieving useful data to fill in RDO */
    USBPD_PWR_IF_GetPortPDOs(PortNum, USBPD_CORE_DATATYPE_SNK_PDO, (uint8_t *)&snkpdolist[0], &size);
    /* Store value of 1st SNK PDO (Fixed) in local variable */
    snk_fixed_pdo.d32 = snkpdolist[0];
    rdo.FixedVariableRDO.ObjectPosition = 1U;
    rdo.FixedVariableRDO.OperatingCurrentIn10mAunits  =  fixed_pdo.SRCFixedPDO.MaxCurrentIn10mAunits;
    rdo.FixedVariableRDO.MaxOperatingCurrent10mAunits =  fixed_pdo.SRCFixedPDO.MaxCurrentIn10mAunits;
    rdo.FixedVariableRDO.CapabilityMismatch = 1U;
    rdo.FixedVariableRDO.USBCommunicationsCapable = snk_fixed_pdo.SNKFixedPDO.USBCommunicationsCapable;
    DPM_Ports[PortNum].DPM_RequestedCurrent = puser->DPM_SNKRequestedPower.MaxOperatingCurrentInmAunits;

    pdhandle->DPM_RequestDOMsg = rdo.d32;
    *PtrPowerObjectType = USBPD_CORE_PDO_TYPE_FIXED;
    *PtrRequestData = rdo.d32;
    pdhandle->DPM_RequestedVoltage = 5000U;
    return;
  }

  USER_SERV_SNK_BuildRDOfromSelectedPDO(PortNum, pdoindex, &snkpowerrequestdetails, &rdo, PtrPowerObjectType);

  *PtrRequestData = pdhandle->DPM_RequestDOMsg;
}

I have managed to dynamicaly change the SNK PDOs according to received SRC PDOs by adjusting the
USBPD_USER_SERV_StoreSRCPDO() function:

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
	for (uint32_t index = 0; index < (Size / 4U); index++)
	{
	  rdo = (uint8_t *)&PORT0_PDO_ListSNK[index];
	  (void)memcpy(rdo, (Ptr + (index * 4U)), (4U * sizeof(uint8_t)));
	}

  }
}

 But the problem is that when I try to request specific profile in UCPD monitor, it works fine for Fixed profiles but APDO requests are rejected. If I later on delte the APDO profile and redefine it in UCPD Monitor with the same values it suddenly works. What could be causing this problem?

 

FBL
ST Employee

Hello @Marosh 


According to spec, Sink sends Sink_Capabilities Message in response to a Get_Sink_Cap Message. Typically, it cannot initiate the sending of its capabilities without a request from the source. Also, Sink's capabilities are typically declared in the form of Power Data Objects (PDOs) during the initial power negotiation phase. For that reason, if you reconfigure after flashing and send it to the target APDO request. 

Handling different types of PDOs is managed by the protocol and the devices' PD controllers, which are designed to interpret and negotiate power contracts.

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.

Hello @Marosh 

The normal way to proceed is as follows:

1- at compilation time you have define the PDOs that your sink would accept. 

Because this is linked to what your hardware supports.

The Source PDO selection should also be decided (get max voltage/ max power, min power...)

See Wiki

2- then, when contract is established your sink knows which PDOs are available from source, and requests the best match; You shouldn't modify the sink PDOs at that time.

In case you want to charge a battery, and modify the request along time, depending on the battery level, you just need to send a new request. No need to update your sink PDO for that.

Hello @Nicolas P. ,

providing the SNK supported PDOs at the start in pdo_defs.h would also make sense to me. The problem is I dont know the SRC PDOs priory. As this is the case I would like to declare for my 100W SNK general APDO as 3300mV-20000mV max_current 5000mA so it would work with wide range of ARC APDOs. Yet this was not working using the example code and UCPD monitor request profile, since the defined SNK APDO would have higher maxcurrent value then SRC and the EvaluateMatchWitchSRCPDO would fail.

Used example request function:

/**
  * @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)
  * @retval USBPD Status
  */
USBPD_StatusTypeDef USBPD_DPM_RequestMessageRequest(uint8_t PortNum, uint8_t IndexSrcPDO, uint16_t RequestedVoltage)
{
  USBPD_StatusTypeDef _status = USBPD_ERROR;
/* USER CODE BEGIN USBPD_DPM_RequestMessageRequest */
  /* To be adapted to call the PE function */
  /*       _status = USBPD_PE_Send_Request(PortNum, rdo.d32, pdo_object);*/
  //DPM_USER_DEBUG_TRACE(PortNum, "ADVICE: update USBPD_DPM_RequestMessageRequest");
  //Function declaration so one could ask for PDO with UCPD GUI
  //source: https://community.st.com/t5/stm32-mcus-other-solutions/message-rejected-for-this-port-configuration-on-the-stm32-x-cube/td-p/86358
    uint32_t voltage, allowablepower;
	USBPD_SNKRDO_TypeDef rdo;
	USBPD_PDO_TypeDef  pdo;
	USBPD_CORE_PDO_Type_TypeDef pdo_object;
	USBPD_USER_SettingsTypeDef *puser = (USBPD_USER_SettingsTypeDef *)&DPM_USER_Settings[PortNum];
	USBPD_DPM_SNKPowerRequestDetailsTypeDef request_details;
	rdo.d32 = 0;

	/* selected SRC PDO */
	pdo.d32 = DPM_Ports[PortNum].DPM_ListOfRcvSRCPDO[(IndexSrcPDO - 1)];
	voltage = RequestedVoltage;
	allowablepower = (puser->DPM_SNKRequestedPower.MaxOperatingCurrentInmAunits * RequestedVoltage) / 1000U;

	if (USBPD_TRUE == USER_SERV_SNK_EvaluateMatchWithSRCPDO(PortNum, pdo.d32, &voltage, &allowablepower))
	{
	  /* Check that voltage has been correctly selected */
	  if (RequestedVoltage == voltage)
	  {
		request_details.RequestedVoltageInmVunits    = RequestedVoltage;
		request_details.OperatingCurrentInmAunits    = (1000U * allowablepower)/RequestedVoltage;
		request_details.MaxOperatingCurrentInmAunits = puser->DPM_SNKRequestedPower.MaxOperatingCurrentInmAunits;
		request_details.MaxOperatingPowerInmWunits   = puser->DPM_SNKRequestedPower.MaxOperatingPowerInmWunits;
		request_details.OperatingPowerInmWunits      = puser->DPM_SNKRequestedPower.OperatingPowerInmWunits;

		USER_SERV_SNK_BuildRDOfromSelectedPDO(PortNum, (IndexSrcPDO - 1), &request_details, &rdo, &pdo_object);

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

 

 

Hello Marosh, 

In your case you can simply declared several APDO with different maximum current so as to be sure that one of the APDO will match with SOURCE.

Sink can select PDO with the maximum power with USER_SERV_FindVoltageIndex function so if you have several PDO matching with source it will select the one with the maximum power.

 

Best Regards,

 

Hello@LLECH.1,

You are right after playing with it for a while it seems it would be possible to define multiple PDOs based on the attached PD3_2 Specification. According to the table I could make multiple APDO definitions for 9VProg, 15VProg and 20VProg with 3A and given Voltage ranges. Even when the SRC 15VProg APDO has maximal current of 3250mA it still founded the match with 15VProg SNK APDO with 3000mA. The only problem I find is that according to following code the resulting requested current is based on currentrequestedpower which is based on max SNK current. In case that the SRC PDO introduces the PDP/20 case (4500mA for example), one doesnt know appriory the max SRC current, thus we cannot define the precise SNK max current in pdo_defs.h and we would be able to only request 3A.

So in my application where I want to request for specific current up to SRC max current alongside the requested voltage I guess I would have to modify the currentrequestedpower variable since it is later used in request message to calculate the request current as currentrequestedpower/requestedvoltage, right?

EvaluateMatchWithSRCPDO() sneak peak:

/* SNK Augmented Power Data Object (APDO) */
          case USBPD_CORE_PDO_TYPE_APDO:
          {
            uint16_t snkmaxvoltage100mv;
            uint16_t snkminvoltage100mv;
            uint16_t snkmaxcurrent50ma;

            snkminvoltage100mv = snkpdo.SRCSNKAPDO.MinVoltageIn100mV;
            snkmaxvoltage100mv = snkpdo.SRCSNKAPDO.MaxVoltageIn100mV;
            snkmaxcurrent50ma = snkpdo.SRCSNKAPDO.MaxCurrentIn50mAunits;

            /* Match if SNK APDO voltage overlaps with the SRC APDO voltage range */
            if (((srcminvoltage100mv <= snkmaxvoltage100mv) && (srcminvoltage100mv >= snkminvoltage100mv)) ||
				((snkminvoltage100mv <= srcmaxvoltage100mv) && (snkminvoltage100mv >= srcminvoltage100mv)))
			{
			  if (snkmaxcurrent50ma <= srcmaxcurrent50ma)
			  {
				if (0U != *PtrRequestedPower)
				{
				  /* A specific voltage was requested, verify it */
				  if ((PWR_DECODE_100MV(snkminvoltage100mv) <= (*PtrRequestedVoltage)) &&
					 ((*PtrRequestedVoltage) <= PWR_DECODE_100MV(snkmaxvoltage100mv)))
				  {
					currentrequestedpower = (*PtrRequestedVoltage * PWR_DECODE_50MA(snkmaxcurrent50ma))
											/ 1000U; /* mW */
					currentrequestedvoltage = (*PtrRequestedVoltage / 50U);
				  }
				}
				else
				{
				  /* No specific voltage was requested, take the maximum possible voltage:
					 min between the source max and Sink max */
				  *PtrRequestedVoltage = MIN(PWR_DECODE_100MV(srcmaxvoltage100mv),
											 PWR_DECODE_100MV(snkmaxvoltage100mv));

				  currentrequestedpower = (*PtrRequestedVoltage * PWR_DECODE_50MA(snkmaxcurrent50ma))
										  / 1000U; /* mW */
				  currentrequestedvoltage = (*PtrRequestedVoltage / 50U);
				}
			  }
			}
          }

 

 

LLECH.1
ST Employee

Hello Marosh,

I am not sure to understand your point. According to USB Power Delivery standard a sink device can draw up to 5A. This is the maximum current value you can reach. If your hardware is able to draw such current you can define your  sink APDO so it can accept a maximum current of 5A. Then whatever be the maximum current of the source you will always be able to negociate a contract with it.

 

Best Regards,

Lucas