Target description
This tutorial will help you to:
- Use the X-NUCLEO-SNK1M1 or X-NUCLEO-USBPDM1 shield that includes a TCPP01-M12 protection circuit and provides a USB Type-C® connector
- Create a USB-PD Sink Device with the NUCLEO-G071RB board and the X-NUCLEO-SNK1M1 or X-NUCLEO-USBPDM1 shieldby using STM32CubeIDE software
Prerequisites
- Computer with Windows 7 (or higher)
- Computer with Windows 7 (or higher)
Hardware
Software
Literature
- UM2324 NUCLEO-G071RB User Manual
- UM2658 X-NUCLEO-USBPDM1 User Manual
- UM2773 X-NUCLEO-SNK1M1 User Manual
- AN5418 How to build a simple USB-PD sink application with STM32CubeMX
- How to Create a STM32G0 USB-PD Device with STM32CubeIDE.
Create a USB-PD Sink Device
Total 45min
1. Creating the project
5min
Open STM32CubeIDE and create a New STM32 Project. As a target selection, choose the STM32G071RB from the MCU/MPU Selector Tab
Click "Next", then enter you project's name. Leave the other fields as default and click "Finish".
2. Configuring the system
At this point your project is created and you are left with the STM32CubeMX view.
In the next steps, we will configure the peripherals and options needed for the project.
2.1. Configure UCPD peripheral
In the Connectivity tab, select the UCPD1 peripheral and enable it in sink mode. Under the NVIC Settings tab, enable UCPD global interrupts.
Under the DMA Settings tab, add UCPD1_RX and UCPD1_TX DMA requests.
2.2. Configure FreeRTOS Middleware
In the Middleware section, enable FreeRTOS with CMSIS_V1 interface. Under the Config Parameters tab, change "TOTAL_HEAP_SIZE" to 7000 bytes.
Then, under the Include Parameters tab, Enable eTaskGetState include definition.
2.3. Configure USBPD Middleware
In the Middleware section, enable USBPD with the following configuration:
- Port Configuration: Port 0: UCPD1
- Stack Configuration: PD3 Full Stack
- Timer service Source: TIM1
Under the PDO General Definitions tab, verify the following configuration:
- Number of Sink PDOs for port 0: 1
- Port 0 Sink PDO 1 0x26019096 (correspond to a 5V / 1.5A, dual-role data sink)
The following table is extracted from USB Power Delivery Specification, Table 6-14 Fixed Supply PDO - Sink. Used values and associated decoding for this project have been added to the table.
Bit(s) | Description | Used value | Decoding |
---|---|---|---|
B31..30 | Fixed supply | 00b | Fixed |
B29 | Dual-Role Power | 0b | No |
B28 | Higher capability | 0b | No |
B27 | Unconstrained Power | 0b | No |
B26 | USB Communications Capable | 0b | No |
B25 | Dual-Role Data | 1b | Yes |
B24..23 | Fast Role Swap support | 00b | No |
B22..20 | Reserved - Shall de set to zero | 0b | No |
B19..10 | Voltage in 50mV units | 0001100100b | 5V |
B9..0 | Maximum current in 10mA units | 0010010110b | 1.5A |
2.4. Configure ADC peripheral
In order for the Power Delivery stack to work, VBUS needs to be monitored. To do it, an ADC needs to be configured to measure the VBUS voltage.
In the Analog section, enable ADC1 peripheral channel 9 (connected to pin PB1). Configure the ADC with the following configuration:
- Clock prescaler : Synch / 4
- Continuous conversion mode: Enabled
- Overrun behavior: Overrun data overwritten
- Sampling time Common 1 & 2: 160.5 Cycles
Then, under the GPIO Settings tab, rename PB1 pin to VSENSE, a more descriptive name for this pin, used for VBUS monitoring:
Under the User Constants tab, Add a constant named VDDA_APPLI of value 3300.
2.5. Configure Clocks
Under the Clock Configuration main tab, change system clock mux to PLLCLK. It will set HCLK clock to 64MHz.
2.6. [OPTIONAL] Configure Tracer for debug
2.6.1. Configure LPUART
On the STM32G0 Nucleo-64 board, the Virtual COM port connected to the ST-LINK is the LPUART1.
In the Connectivity section, enable LPUART1 in Asynchronous mode, and baudrate 921600 bauds. Leave the rest as default.
In the pinout view, left click PA2 and PA3 to remap them to LPUART1_TX and LPUART1_RX.
Under the DMA Configuration tab, add a request for LPUART1_TX. Use DMA1 channel 3.
Finally, under the NVIC Settings tab, enable LPUART1 global interrupts.
2.6.2. Configure embedded tracer
In the Utilities section, select TRACER_EMB and use LPUART1 as the trace source.
Then, go back to the USBPD middleware configuration and check the Tracer Source checkbox.
2.6.3. Configure UCPD monitor firmware responder for debug
The firmware interactive stack responder can be activated if interaction with the USB-PD stack is needed, using the UCPD monitor tool STM32CubeMonUCPD. In the Utilities section, enable GUI_INTERFACE, then enter free text to describe the board.
3. Configure project
5min
Under the Project Manager main tab, configure the minimum stack size to 0xC00 under the Project tab. This is a first value, which can be tuned later, depending on the application needs.
Under the Advanced Settings tab, change LPUART driver to LL.
4. Generate code
Save your file with Ctrl+s and select generate code if prompted. You can also generate code from the STM32CubeIDE menu by clicking Project/Generate Code, or by pressing Alt+K.
A warning appears, informing that a proper HAL timebase is not defined. It is safer to use a dedicated timer as a HAL timebase source.
For this demonstration, the below warning can be ignored by clicking Yes.
5. Simple USB-PD application
10min
Now that the peripherals are initialized by STM32CubeMX, some minimum level of the application needs to be added:
- ADC needs to be calibrated, and conversion needs to start.
- Handlers for the interrupts needs to be completed, in order to wake up the UCPD peripheral.
- BSP_USBPD_PWR_VBUSGetVoltage function needs to be completed with the right coefficient depending on the VBUS divider bridge.
- USBPD_DPM_SNK_EvaluateCapabilities function needs to be completed to answer one source capability message.
- TCPP01‑M12 dead battery pin needs to be disabled, GPIO driven HIGH, to see the source Rp, or the jumper has to be set on the shield.
5.1. Modification in main.c
Add the following code between the /* USER CODE BEGIN-END ADC1_Init 2 */ tags:
/* USER CODE BEGIN ADC1_Init 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start(&hadc1);
/* USER CODE END ADC1_Init 2 */
5.2. Modification in stm32g0xx_it.c
Add the following code between the /* USER CODE BEGIN-END SysTick_IRQn 0 */ tags:
/* USER CODE BEGIN SysTick_IRQn 0 */
#if defined( _GUI_INTERFACE)
GUI_TimerCounter(); /* needed with GUI_Interface */
#endif
/* USER CODE END SysTick_IRQn 0 */
5.3. Modification in usbpd_dpm_user.c
Add the following code USBPD_DPM_GetDataInfo function:
case USBPD_CORE_DATATYPE_SNK_PDO: /*!< Handling of port Sink PDO, requested by get sink capa*/
USBPD_PWR_IF_GetPortPDOs(PortNum, DataId, Ptr, Size);
*Size *= 4;
break;
Add the following code between the /* USER CODE BEGIN-END USBPD_DPM_SNK_EvaluateCapabilities */ tags:
/* USER CODE BEGIN USBPD_DPM_SNK_EvaluateCapabilities */
USBPD_SNKRDO_TypeDef rdo;
/* Initialize RDO */
rdo.d32 = 0;
/* Prepare the requested pdo */
rdo.FixedVariableRDO.ObjectPosition = 1;
rdo.FixedVariableRDO.OperatingCurrentIn10mAunits = 50;
rdo.FixedVariableRDO.MaxOperatingCurrent10mAunits = 50;
rdo.FixedVariableRDO.CapabilityMismatch = 0;
*PtrPowerObjectType = USBPD_CORE_PDO_TYPE_FIXED;
*PtrRequestData = rdo.d32;
/* USER CODE END USBPD_DPM_SNK_EvaluateCapabilities */
5.4. Modification in usbpd_pwr_user.c
Add the following code between the /* USER CODE BEGIN-END include */ tags:
/* USER CODE BEGIN include */
#include "main.h"
/* USER CODE END include */
Add the following code between the /* USER CODE BEGIN-END BSP_USBPD_PWR_VBUSGetVoltage */ tags:
/* USER CODE BEGIN BSP_USBPD_PWR_VBUSGetVoltage */
/* Check if instance is valid */
int32_t ret = BSP_ERROR_NONE;
if ((Instance >= USBPD_PWR_INSTANCES_NBR) || (NULL == pVoltage))
{
ret = BSP_ERROR_WRONG_PARAM;
*pVoltage = 0;
}
else
{
uint32_t val;
val = __LL_ADC_CALC_DATA_TO_VOLTAGE( VDDA_APPLI, \
LL_ADC_REG_ReadConversionData12(ADC1), \
LL_ADC_RESOLUTION_12B); /* mV */
/* X-NUCLEO-USBPDM board is used */
/* Value is multiplied by 5.97 (Divider R6/R7 (40.2K/200K) for VSENSE) */
val *= 597;
val /= 100;
*pVoltage = val;
}
return ret;
/* USER CODE END BSP_USBPD_PWR_VBUSGetVoltage */
6. Configure the shield's jumpers
Place jumpers on the X-NUCLEO-SNK1M1 shield as shown in the picture.
If you are instead using the X-NUCLEO-DRP1M1 shield, please follow this configuration:
7. Compile and run the application
The compilation must be performed without error or warnings.
Build the application by clicking on the button (or select Project/Build Project).
Run the application by clicking on the button (or select Run/Run)
8. Establish the first explicit contract
5min
With your application running on the board, launch STM32CubeMonitor-UCPD application.
The user's board must appear in the list when clicking "Refresh list of connected boards", so double click on the
corresponding line (or click "NEXT").
Note: The ComPort may be different. It depends on the number of boards installed on the computer. Then double click on the desired UCPD port, here Port 0, or select it and click "NEXT".
Click on the TRACES button in the bottom right corner to get protocol traces. You can then plug a power delivery source into the USB Type-C® receptacle of the X-NUCLEO-SKN1M1 shield. The screen may look like this:
The figure above shows the communication between the STM32G0 and the power delivery source on the right panel. It is possible to verify the correct sequence to reach an explicit contract:
- The capabilities are sent by the source (IN green message).
- The request is sent by the STM32G0 (OUT orange message).
- The ACCEPT and the PS_RDY are sent by the source (IN green message).
- The contract negotiation ends by the POWER_EXPLICIT_CONTRACT notification (blue message).
For more details on how to use this tool, refer to UM2468. And for more details on the protocol, refer to UM2552. Note that this trace is very helpful for debugging and application development.
9. [OPTIONAL] Advanced USB-PD application
From this point, you built the most simple sink possible, which doesn't handles matching the sink and source PDO. This must be done with user code. This is done in USBPD_DPM_SNK_EvaluateCapabilities function. For now, this function does nothing more than to ask for the first available source PDO, which is 5V.
In order to select a specific PDO (for example 9V), you must select the corresponding source PDO number. This can be done with user code that matches the sink PDO defined previously in CubeMX, and the corresponding available source PDO.
This section provide the code of a possible implementation of this system.
You will need to:
- Add all the sink PDO you want to support in cubeMX
- Add the user code provided below in your application
9.1. Add the supported sink PDO
Re-open the CubeMX view. In the Middleware section, for the USBPD middleware, under the PDO General Definitions tab, add the number of PDO you want your sink to support. For this example, we defined 3PDO, corresponding to a simple sink configuration of 5V/1.5A, 9V/1.5A and 15V/1.5A. This was done by setting the PDO to:
- 0x00019096 (5V 1.5A)
- 0x0002D096 (9V 1.5A)
- 0x0004B096 (15V 1.5A)
9.2. Modification in usbpd_dpm_user.h
Add the following code between the /* USER CODE BEGIN-END Typedef */ tags:
/* USER CODE BEGIN Typedef */
typedef struct
{
uint32_t DPM_RDOPosition; /*!< RDO Position of requested DO in Source list of capabilities */
uint32_t DPM_RDOPositionPrevious; /*!< RDO Position of requested DO in Source list of capabilities */
uint32_t DPM_RequestedVoltage; /*!< Value of requested voltage */
uint32_t DPM_RequestedCurrent; /*!< Value of requested current */
uint32_t DPM_RequestDOMsg;
uint32_t DPM_NumberOfRcvSRCPDO;
uint32_t DPM_ListOfRcvSRCPDO[8];
USBPD_PPSSDB_TypeDef DPM_RcvPPSStatus; /*!< PPS Status received by port partner */
USBPD_SKEDB_TypeDef DPM_RcvSNKExtendedCapa; /*!< SNK Extended Capability received by port partner */
} USBPD_HandleTypeDef;
/* USER CODE END Typedef */
9.3. Modification in usbpd_pdo_defs.h
Add the following code between the /* USER CODE BEGIN-END typedef */ tags:
/* USER CODE BEGIN typedef */
/**
* @brief USBPD Port PDO Structure definition
*/
typedef struct
{
uint32_t *ListOfPDO; /*!< Pointer on Power Data Objects list, defining
port capabilities */
uint8_t *NumberOfPDO; /*!< Number of Power Data Objects defined in ListOfPDO
This parameter must be set at max to @ref USBPD_MAX_NB_PDO value */
} USBPD_PortPDO_TypeDef;
/**
* @brief USBPD Port PDO Storage Structure definition
*/
typedef struct
{
USBPD_PortPDO_TypeDef SourcePDO; /*!< SRC Power Data Objects */
} USBPD_PWR_Port_PDO_Storage_TypeDef;
/* USER CODE END typedef */
9.4. Modification in usbpd_dpm_user.c
Add the following code between the /* USER CODE BEGIN-END Private_Typedef */ tags:
/* USER CODE BEGIN Private_Typedef */
/** @brief Sink Request characteristics Structure definition */
typedef struct
{
uint32_t RequestedVoltageInmVunits; /*!< Sink request operating voltage in mV units */
uint32_t MaxOperatingCurrentInmAunits; /*!< Sink request Max operating current in mA units */
uint32_t OperatingCurrentInmAunits; /*!< Sink request operating current in mA units */
uint32_t MaxOperatingPowerInmWunits; /*!< Sink request Max operating power in mW units */
uint32_t OperatingPowerInmWunits; /*!< Sink request operating power in mW units */
} USBPD_DPM_SNKPowerRequestDetails_TypeDef;
/* USER CODE END Private_Typedef */
Add the following code between the /* USER CODE BEGIN-END Private_Define */ tags:
/* USER CODE BEGIN Private_Define */
#define DPM_NO_SRC_PDO_FOUND 0xFFU /*!< No match found between Received SRC PDO
and SNK capabilities */
/* USER CODE END Private_Define */
Add the following code between the /* USER CODE BEGIN-END Private_Variables */ tags:
/* USER CODE BEGIN Private_Variables */
USBPD_HandleTypeDef DPM_Ports[USBPD_PORT_COUNT];
/* USER CODE END Private_Variables */
Add the following code between the /* USER CODE BEGIN-END USBPD_USER_PRIVATE_FUNCTIONS_Prototypes */ tags:
/* USER CODE BEGIN USBPD_USER_PRIVATE_FUNCTIONS_Prototypes */
static void DPM_SNK_BuildRDOfromSelectedPDO(uint8_t PortNum, uint8_t IndexSrcPDO,
USBPD_DPM_SNKPowerRequestDetails_TypeDef* PtrRequestPowerDetails,
USBPD_SNKRDO_TypeDef* Rdo, USBPD_CORE_PDO_Type_TypeDef *PtrPowerObject);
static uint32_t DPM_FindVoltageIndex(uint32_t PortNum,
USBPD_DPM_SNKPowerRequestDetails_TypeDef* PtrRequestPowerDetails);
/* USER CODE END USBPD_USER_PRIVATE_FUNCTIONS_Prototypes */
Add the following code between the /* USER CODE BEGIN-END USBPD_DPM_SetDataInfo */ tags, in the switch:
/* Case Received Source PDO values Data information :
*/
case USBPD_CORE_DATATYPE_RCV_SRC_PDO: /*!< Storage of Received Source PDO values */
if (Size <= (USBPD_MAX_NB_PDO * 4))
{
uint8_t* rdo;
DPM_Ports[PortNum].DPM_NumberOfRcvSRCPDO = (Size / 4);
/* Copy PDO data in DPM Handle field */
for (uint32_t index = 0; index < (Size / 4); index++)
{
rdo = (uint8_t*)&DPM_Ports[PortNum].DPM_ListOfRcvSRCPDO[index];
(void)memcpy(rdo, (Ptr + (index * 4u)), (4u * sizeof(uint8_t)));
}
}
break;
Add the following code between the /* USER CODE BEGIN-END USBPD_DPM_SNK_EvaluateCapabilities */ tags (replace the previously added code):
/* USER CODE BEGIN USBPD_DPM_SNK_EvaluateCapabilities */
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_SNKPowerRequestDetails_TypeDef snkpowerrequestdetails;
uint32_t pdoindex, size;
uint32_t snkpdolist[USBPD_MAX_NB_PDO];
USBPD_PDO_TypeDef snk_fixed_pdo;
/* USBPD_DPM_EvaluateCapabilities: Port Partner Requests Max Voltage */
/* Find the Pdo index for the requested voltage */
pdoindex = DPM_FindVoltageIndex(PortNum, &snkpowerrequestdetails);
/* 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)
USBPD_TRACE_Add(USBPD_TRACE_DEBUG, PortNum, 0, (uint8_t *) "PE_EvaluateCapability: could not find desired voltage", sizeof("PE_EvaluateCapability: could not find desired voltage"));
#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 = 1;
rdo.FixedVariableRDO.OperatingCurrentIn10mAunits = fixed_pdo.SRCFixedPDO.MaxCurrentIn10mAunits;
rdo.FixedVariableRDO.MaxOperatingCurrent10mAunits = puser->DPM_SNKRequestedPower.MaxOperatingCurrentInmAunits / 10;
rdo.FixedVariableRDO.CapabilityMismatch = 1;
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 = 5000;
return;
}
DPM_SNK_BuildRDOfromSelectedPDO(PortNum, pdoindex, &snkpowerrequestdetails,&rdo, PtrPowerObjectType);
*PtrRequestData = pdhandle->DPM_RequestDOMsg;
/* USER CODE END USBPD_DPM_SNK_EvaluateCapabilities */
Add the following code between the /* USER CODE BEGIN-END USBPD_USER_PRIVATE_FUNCTIONS */ tags:
/* USER CODE BEGIN USBPD_USER_PRIVATE_FUNCTIONS */
/**
* @brief Examinate a given SRC PDO to check if matching with SNK capabilities.
* @param PortNum Port number
* @param SrcPDO Selected SRC PDO (32 bits)
* @param PtrRequestedVoltage Pointer on Voltage value that could be reached if SRC PDO is requested (only valid if USBPD_TRUE is returned) in mV
* @param PtrRequestedPower Pointer on Power value that could be reached if SRC PDO is requested (only valid if USBPD_TRUE is returned) in mW
* @retval USBPD_FALSE of USBPD_TRUE (USBPD_TRUE returned in SRC PDO is considered matching with SNK profile)
*/
uint32_t USBPD_DPM_SNK_EvaluateMatchWithSRCPDO(uint8_t PortNum, uint32_t SrcPDO, uint32_t* PtrRequestedVoltage, uint32_t* PtrRequestedPower)
{
USBPD_PDO_TypeDef srcpdo, snkpdo;
uint32_t match = USBPD_FALSE;
uint32_t nbsnkpdo;
uint32_t snkpdo_array[USBPD_MAX_NB_PDO];
uint16_t i, srcvoltage50mv, srcmaxcurrent10ma;
uint16_t snkvoltage50mv, snkopcurrent10ma;
uint32_t maxrequestedpower, currentrequestedpower;
uint32_t maxrequestedvoltage, currentrequestedvoltage;
/* Retrieve SNK PDO list from PWR_IF storage : PDO values + nb of u32 written by PWR_IF (nb of PDOs) */
USBPD_PWR_IF_GetPortPDOs(PortNum, USBPD_CORE_DATATYPE_SNK_PDO, (uint8_t*)snkpdo_array, &nbsnkpdo);
if (0 == nbsnkpdo)
{
return(USBPD_FALSE);
}
/* Set default output values */
maxrequestedpower = 0;
maxrequestedvoltage = 0;
/* Check SRC PDO value according to its type */
srcpdo.d32 = SrcPDO;
switch(srcpdo.GenericPDO.PowerObject)
{
/* SRC Fixed Supply PDO */
case USBPD_CORE_PDO_TYPE_FIXED:
srcvoltage50mv = srcpdo.SRCFixedPDO.VoltageIn50mVunits;
srcmaxcurrent10ma = srcpdo.SRCFixedPDO.MaxCurrentIn10mAunits;
/* Loop through SNK PDO list */
for (i=0; i<nbsnkpdo; i++)
{
currentrequestedpower = 0;
currentrequestedvoltage = 0;
/* Retrieve SNK PDO value according to its type */
snkpdo.d32 = snkpdo_array[i];
switch(snkpdo.GenericPDO.PowerObject)
{
/* SNK Fixed Supply PDO */
case USBPD_CORE_PDO_TYPE_FIXED:
{
snkvoltage50mv = snkpdo.SNKFixedPDO.VoltageIn50mVunits;
snkopcurrent10ma = snkpdo.SNKFixedPDO.OperationalCurrentIn10mAunits;
/* Match if :
SNK Voltage = SRC Voltage
&&
SNK Op Current <= SRC Max Current
Requested Voltage : SNK Voltage
Requested Op Current : SNK Op Current
Requested Max Current : SNK Op Current
*/
if ( (snkvoltage50mv == srcvoltage50mv)
&&(snkopcurrent10ma <= srcmaxcurrent10ma))
{
currentrequestedpower = (snkvoltage50mv * snkopcurrent10ma) / 2; /* to get value in mw */
currentrequestedvoltage = snkvoltage50mv;
}
break;
}
/* SNK Augmented Power Data Object (APDO) */
case USBPD_CORE_PDO_TYPE_APDO:
break;
default:
break;
}
if (currentrequestedpower > maxrequestedpower)
{
match = USBPD_TRUE;
maxrequestedpower = currentrequestedpower;
maxrequestedvoltage = currentrequestedvoltage;
}
}
break;
/* Augmented Power Data Object (APDO) */
case USBPD_CORE_PDO_TYPE_APDO:
{
uint16_t srcmaxvoltage100mv, srcmaxcurrent50ma;
srcmaxvoltage100mv = srcpdo.SRCSNKAPDO.MaxVoltageIn100mV;
srcmaxcurrent50ma = srcpdo.SRCSNKAPDO.MaxCurrentIn50mAunits;
/* Loop through SNK PDO list */
for (i=0; i<nbsnkpdo; i++)
{
currentrequestedpower = 0;
currentrequestedvoltage = 0;
/* Retrieve SNK PDO value according to its type */
snkpdo.d32 = snkpdo_array[i];
switch(snkpdo.GenericPDO.PowerObject)
{
case USBPD_CORE_PDO_TYPE_FIXED:
/* No match */
break;
/* SNK Augmented Power Data Object (APDO) */
case USBPD_CORE_PDO_TYPE_APDO:
{
uint16_t snkmaxvoltage100mv, snkminvoltage100mv, snkmaxcurrent50ma;
snkminvoltage100mv = snkpdo.SRCSNKAPDO.MinVoltageIn100mV;
snkmaxvoltage100mv = snkpdo.SRCSNKAPDO.MaxVoltageIn100mV;
snkmaxcurrent50ma = snkpdo.SRCSNKAPDO.MaxCurrentIn50mAunits;
/* Match if voltage matchs with the APDO voltage range */
if ((PWR_DECODE_100MV(snkminvoltage100mv) <= (*PtrRequestedVoltage))
&& ((*PtrRequestedVoltage) <= PWR_DECODE_100MV(snkmaxvoltage100mv))
&& (snkmaxcurrent50ma <= srcmaxcurrent50ma))
{
if (0 != *PtrRequestedPower)
{
currentrequestedpower = (*PtrRequestedVoltage * PWR_DECODE_50MA(snkmaxcurrent50ma)) / 1000; /* to get value in mw */
currentrequestedvoltage = (*PtrRequestedVoltage / 50);
}
else
{
*PtrRequestedVoltage = MIN(PWR_DECODE_100MV(srcmaxvoltage100mv), PWR_DECODE_100MV(snkmaxvoltage100mv));
currentrequestedpower = (*PtrRequestedVoltage * PWR_DECODE_50MA(snkmaxcurrent50ma)) / 1000; /* to get value in mw */
currentrequestedvoltage = (*PtrRequestedVoltage / 50);
}
}
}
break;
default:
break;
}
if (currentrequestedpower > maxrequestedpower)
{
match = USBPD_TRUE;
maxrequestedpower = currentrequestedpower;
maxrequestedvoltage = currentrequestedvoltage;
}
}
}
break;
default:
return(USBPD_FALSE);
}
if (maxrequestedpower > 0)
{
*PtrRequestedPower = maxrequestedpower;
*PtrRequestedVoltage = maxrequestedvoltage * 50; /* value in mV */
}
return(match);
}
/**
* @brief Find PDO index that offers the most amount of power and in accordance with SNK capabilities.
* @param PortNum Port number
* @param PtrRequestPowerDetails Sink requested power details structure pointer
* @retval Index of PDO within source capabilities message (DPM_NO_SRC_PDO_FOUND indicating not found)
*/
static uint32_t DPM_FindVoltageIndex(uint32_t PortNum, USBPD_DPM_SNKPowerRequestDetails_TypeDef* PtrRequestPowerDetails)
{
uint32_t *ptpdoarray;
USBPD_PDO_TypeDef pdo;
uint32_t voltage, reqvoltage, nbpdo, allowablepower, maxpower;
uint32_t curr_index = DPM_NO_SRC_PDO_FOUND, temp_index;
USBPD_USER_SettingsTypeDef *puser = (USBPD_USER_SettingsTypeDef *)&DPM_USER_Settings[PortNum];
allowablepower = 0;
maxpower = 0;
reqvoltage = 0;
voltage = 0;
/* Search PDO index among Source PDO of Port */
nbpdo = DPM_Ports[PortNum].DPM_NumberOfRcvSRCPDO;
ptpdoarray = DPM_Ports[PortNum].DPM_ListOfRcvSRCPDO;
/* search the better PDO in the list of source PDOs */
for(temp_index = 0; temp_index < nbpdo; temp_index++)
{
pdo.d32 = ptpdoarray[temp_index];
if(USBPD_CORE_PDO_TYPE_APDO == pdo.GenericPDO.PowerObject)
{
curr_index = temp_index;
voltage = reqvoltage = MIN(5900, pdo.SRCSNKAPDO.MaxVoltageIn100mV*100);
maxpower = reqvoltage * pdo.SRCSNKAPDO.MaxCurrentIn50mAunits*50;
PtrRequestPowerDetails->MaxOperatingCurrentInmAunits = pdo.SRCSNKAPDO.MaxCurrentIn50mAunits*50;
PtrRequestPowerDetails->OperatingCurrentInmAunits = pdo.SRCSNKAPDO.MaxCurrentIn50mAunits*50;
PtrRequestPowerDetails->MaxOperatingPowerInmWunits = 0;
PtrRequestPowerDetails->OperatingPowerInmWunits = maxpower;
PtrRequestPowerDetails->RequestedVoltageInmVunits = reqvoltage;
return curr_index;
}
else
{
/* check if the received source PDO is matching any of the SNK PDO */
allowablepower = 0;
if (USBPD_TRUE == USBPD_DPM_SNK_EvaluateMatchWithSRCPDO(PortNum, pdo.d32, &voltage, &allowablepower))
{
/* choose the "better" PDO, in this case only the distance in absolute value from the target voltage */
if (allowablepower >= maxpower)
{
// /* Add additional check for compatibility of this SRC PDO with port characteristics (defined in DPM_USER_Settings) */
// if ( (voltage >= puser->DPM_SNKRequestedPower.MinOperatingVoltageInmVunits)
// &&(voltage <= puser->DPM_SNKRequestedPower.MaxOperatingVoltageInmVunits)
// &&(allowablepower <= puser->DPM_SNKRequestedPower.MaxOperatingPowerInmWunits))
// {
/* consider the current PDO the better one until now */
curr_index = temp_index;
maxpower = allowablepower;
reqvoltage = voltage;
// }
}
}
}
}
if (curr_index != DPM_NO_SRC_PDO_FOUND)
{
PtrRequestPowerDetails->MaxOperatingCurrentInmAunits = puser->DPM_SNKRequestedPower.MaxOperatingCurrentInmAunits;
PtrRequestPowerDetails->OperatingCurrentInmAunits = (1000 * maxpower)/voltage;
PtrRequestPowerDetails->MaxOperatingPowerInmWunits = puser->DPM_SNKRequestedPower.MaxOperatingPowerInmWunits;
PtrRequestPowerDetails->OperatingPowerInmWunits = maxpower;
PtrRequestPowerDetails->RequestedVoltageInmVunits = reqvoltage;
}
return curr_index;
}
/**
* @brief Build RDO to be used in Request message according to selected PDO from received SRC Capabilities
* @param PortNum Port number
* @param IndexSrcPDO Index on the selected SRC PDO (value between 0 to 6)
* @param PtrRequestPowerDetails Sink requested power details structure pointer
* @param Rdo Pointer on the RDO
* @param PtrPowerObject Pointer on the selected power object
* @retval None
*/
void DPM_SNK_BuildRDOfromSelectedPDO(uint8_t PortNum, uint8_t IndexSrcPDO, USBPD_DPM_SNKPowerRequestDetails_TypeDef* PtrRequestPowerDetails,
USBPD_SNKRDO_TypeDef* Rdo, USBPD_CORE_PDO_Type_TypeDef *PtrPowerObject)
{
uint32_t mv = 0, mw = 0, ma = 0, size;
USBPD_PDO_TypeDef pdo;
USBPD_SNKRDO_TypeDef rdo;
USBPD_HandleTypeDef *pdhandle = &DPM_Ports[PortNum];
USBPD_USER_SettingsTypeDef *puser = (USBPD_USER_SettingsTypeDef *)&DPM_USER_Settings[PortNum];
uint32_t snkpdolist[USBPD_MAX_NB_PDO];
USBPD_PDO_TypeDef snk_fixed_pdo;
/* Initialize RDO */
rdo.d32 = 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];
/* Set common fields in RDO */
pdo.d32 = pdhandle->DPM_ListOfRcvSRCPDO[0];
rdo.GenericRDO.USBCommunicationsCapable = snk_fixed_pdo.SNKFixedPDO.USBCommunicationsCapable;
if (USBPD_SPECIFICATION_REV2 < DPM_Params[PortNum].PE_SpecRevision)
{
rdo.FixedVariableRDO.UnchunkedExtendedMessage = DPM_Settings[PortNum].PE_PD3_Support.d.PE_UnchunkSupport;
DPM_Params[PortNum].PE_UnchunkSupport = USBPD_FALSE;
/* Set unchuncked bit if supported by port partner;*/
if (USBPD_TRUE == pdo.SRCFixedPDO.UnchunkedExtendedMessage)
{
DPM_Params[PortNum].PE_UnchunkSupport = USBPD_TRUE;
}
}
/* If no valid SNK PDO or if no SRC PDO match found (index>=nb of valid received SRC PDOs */
if ((size < 1) || (IndexSrcPDO >= pdhandle->DPM_NumberOfRcvSRCPDO))
{
/* USBPD_DPM_EvaluateCapabilities: Mismatch, could not find desired pdo index */
#ifdef _TRACE
USBPD_TRACE_Add(USBPD_TRACE_DEBUG, PortNum, 0, (uint8_t*)"DPM_SNK_BuildRDOfromSelectedPDO: Pb in SRC PDO selection", sizeof("DPM_SNK_BuildRDOfromSelectedPDO: Pb in SRC PDO selection"));
#endif /* _TRACE */
rdo.FixedVariableRDO.ObjectPosition = 1;
rdo.FixedVariableRDO.OperatingCurrentIn10mAunits = pdo.SRCFixedPDO.MaxCurrentIn10mAunits;
rdo.FixedVariableRDO.MaxOperatingCurrent10mAunits = puser->DPM_SNKRequestedPower.MaxOperatingCurrentInmAunits / 10;
rdo.FixedVariableRDO.CapabilityMismatch = 1;
rdo.FixedVariableRDO.USBCommunicationsCapable = snk_fixed_pdo.SNKFixedPDO.USBCommunicationsCapable;
DPM_Ports[PortNum].DPM_RequestedCurrent = puser->DPM_SNKRequestedPower.MaxOperatingCurrentInmAunits;
/* USBPD_DPM_EvaluateCapabilities: Mismatch, could not find desired pdo index */
pdhandle->DPM_RequestDOMsg = rdo.d32;
return;
}
/* Set the Object position */
rdo.GenericRDO.ObjectPosition = IndexSrcPDO + 1;
rdo.GenericRDO.NoUSBSuspend = 1;
/* Extract power information from Power Data Object */
pdo.d32 = pdhandle->DPM_ListOfRcvSRCPDO[IndexSrcPDO];
*PtrPowerObject = pdo.GenericPDO.PowerObject;
/* Retrieve request details from SRC PDO selection */
mv = PtrRequestPowerDetails->RequestedVoltageInmVunits;
ma = PtrRequestPowerDetails->OperatingCurrentInmAunits;
switch(pdo.GenericPDO.PowerObject)
{
case USBPD_CORE_PDO_TYPE_FIXED:
{
/* USBPD_DPM_EvaluateCapabilities: Mismatch, less power offered than the operating power */
ma = USBPD_MIN(ma, puser->DPM_SNKRequestedPower.MaxOperatingCurrentInmAunits);
mw = (ma * mv)/1000; /* mW */
DPM_Ports[PortNum].DPM_RequestedCurrent = ma;
rdo.FixedVariableRDO.OperatingCurrentIn10mAunits = ma / 10;
rdo.FixedVariableRDO.MaxOperatingCurrent10mAunits = ma / 10;
if(mw < puser->DPM_SNKRequestedPower.OperatingPowerInmWunits)
{
/* USBPD_DPM_EvaluateCapabilities: Mismatch, less power offered than the operating power */
rdo.FixedVariableRDO.MaxOperatingCurrent10mAunits = puser->DPM_SNKRequestedPower.MaxOperatingCurrentInmAunits / 10;
rdo.FixedVariableRDO.CapabilityMismatch = 1;
}
}
break;
case USBPD_CORE_PDO_TYPE_APDO:
{
DPM_Ports[PortNum].DPM_RequestedCurrent = ma;
rdo.ProgRDO.ObjectPosition = IndexSrcPDO + 1;
rdo.ProgRDO.OperatingCurrentIn50mAunits = ma / 50;
rdo.ProgRDO.OutputVoltageIn20mV = mv / 20;
}
break;
default:
break;
}
pdhandle->DPM_RequestDOMsg = rdo.d32;
pdhandle->DPM_RDOPosition = rdo.GenericRDO.ObjectPosition;
Rdo->d32 = pdhandle->DPM_RequestDOMsg;
/* Get the requested voltage */
pdhandle->DPM_RequestedVoltage = mv;
}
/* USER CODE END USBPD_USER_PRIVATE_FUNCTIONS */
You can now re-generate the code, build it and run it. Your application will ask for the highest voltage matching PDO (voltage/current supported by the source and the sink).
You can modify this user code further in order for example to choose the highest power PDO instead of the highest voltage.
You can find other applicative examples on GitHub: x-cube-tcpp
10. References