cancel
Showing results for 
Search instead for 
Did you mean: 

How to create basic USBPD sink application with STM32G431KB?

ardnew
Associate III

I purchased a NUCLEO-G431KB to begin and test firmware development of my project, using the latest STM32CubeIDE with the STM32Cube_FW_G4_V1.1.0 library BSP.

Following several USBPD library examples of a sink application -- and some considerable configuration to FreeRTOS and the initial STM32Cube-generated code -- I am able to build the firmware, flash the target, and debug via ST-Link running on the little NUCLEO device. 

I can verify the USBPD middleware thread and the TRACER_EMB utility thread are being created by FreeRTOS. But the main USBPD process that listens for USB Type-C cable attach/detach seems to hang and never return (and it never calls any of the installed callback functions). This function is part of the CAD module I believe and is named USBPD_CAD_Process(). 

Unfortunately this function is implemented in the library core USBPDCORE_PD3_FULL_CM4_wc32.a (binary distribution), so I don't have any visibility into why it is hanging or not calling my callback functions. And I can't find any good documentation on troubleshooting this area of USBPD library either.

Also a side note -- I'm not real certain how to actually use the TRACER_EMB utility, and the STM32CubeMon-UCPD application does not detect any boards present when I have the target running.

--

EDIT:

My hardware setup is as follows. Equipment:

  1. NUCLEO-G431KB ( https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-nucleo-boards/nucleo-g431kb.html )
  2. USB 3.1/3.2 Type-C female receptacle breakout ( https://www.saikosystems.com/web/p-86-usb-type-c-female-receptacle-breakout-board-v20.aspx )
  3. Apple 87W USB-C Power Adapter ( https://www.apple.com/shop/product/MNF82LL/A/87w-usb-c-power-adapter )

Configuration:

  • Both (1) and (2) are placed on a breadboard. 
  • Jumper wires connecting all VBUS pins of (2) are connected to the open power rail on the breadboard. 
  • Jumper wires connecting all GROUND pins of (2) are connected to the open ground rail on the breadboard. 
  • Jumper wire connecting pin CC1 of (2) is connected to the UCPD_CC1 pin of (1).
  • Jumper wire connecting pin CC2 of (2) is connected to the UCPD_CC2 pin of (1).

With a digital multimeter attached to the open power and ground rails on the breadboard, I can verify that 5 V (default power profile per USB spec) is present when both the NUCLEO (1) is running my USBPD firmware, and the power adapter is plugged into the Type-C receptacle (2). 

If I disconnect and reconnect the power adapter to (2) while (1) is NOT running my USBPD firmware, the 5 V supply is NOT present again until the NUCLEO (1) target resets and begins running the firmware again. Therefore, I am fairly certain the USBPD library is functioning correctly to some extent, it just doesn't seem to be communicating with the main application.

1 ACCEPTED SOLUTION

Accepted Solutions
Yohann M.
ST Employee

Dear Andrew,

I have a question linked to your VBUS connnection. How to do you measure it?

In our reference design, I added a voltage divider on VSENSE and map it on ADC channel on our MCU.

For instance, on our NUCLEO-G474RE, we use a TCPP01-M12 extenstion board and read the VBUS thanks to the VSENSE pin connected to our ADC:

0690X00000As1RGQAZ.jpg

Please find a message sequence chart for SINK connection:

0690X00000As1UAQAZ.png

"Is VBUS On?" is done through functions:

=> ManageStateAttachedWait_SNK -> CAD_Check_VBus -> HW_IF_PWR_GetVoltage -> BSP_PWR_VBUSGetVoltage

And then in 'usbpd_pwr_user.c', when CAD detects a connection, it should wait for getting VBUS at 5V before starting PD stack thanks to the following function:

uint32_t  BSP_PWR_VBUSGetVoltage(uint32_t PortId)
{
/* USER CODE BEGIN BSP_PWR_VBUSGetVoltage */
    uint32_t voltage;
 
    voltage = __LL_ADC_CALC_DATA_TO_VOLTAGE(VDDA_APPLI,  ((uint32_t)LL_ADC_REG_ReadConversionData12(VSENSE_ADC_INSTANCE)), LL_ADC_RESOLUTION_12B); /* mV */
 
    /* STM32G474E_EVAL board is used */
    /* Value is multiplied by 5.97 according to board measurments.
       Theorically, it should have been 5.97 (Divider R6/R7 (40.2K/200K) for VSENSE */
    voltage *= 597U;
    voltage /= 100U;
 
    return voltage;
/* USER CODE END BSP_PWR_VBUSGetVoltage */
}

Could you please check in your side if you respect the above condition?

Regards

Yohann

View solution in original post

11 REPLIES 11
Yohann M.
ST Employee

Dear Andrew,

I have a question linked to your VBUS connnection. How to do you measure it?

In our reference design, I added a voltage divider on VSENSE and map it on ADC channel on our MCU.

For instance, on our NUCLEO-G474RE, we use a TCPP01-M12 extenstion board and read the VBUS thanks to the VSENSE pin connected to our ADC:

0690X00000As1RGQAZ.jpg

Please find a message sequence chart for SINK connection:

0690X00000As1UAQAZ.png

"Is VBUS On?" is done through functions:

=> ManageStateAttachedWait_SNK -> CAD_Check_VBus -> HW_IF_PWR_GetVoltage -> BSP_PWR_VBUSGetVoltage

And then in 'usbpd_pwr_user.c', when CAD detects a connection, it should wait for getting VBUS at 5V before starting PD stack thanks to the following function:

uint32_t  BSP_PWR_VBUSGetVoltage(uint32_t PortId)
{
/* USER CODE BEGIN BSP_PWR_VBUSGetVoltage */
    uint32_t voltage;
 
    voltage = __LL_ADC_CALC_DATA_TO_VOLTAGE(VDDA_APPLI,  ((uint32_t)LL_ADC_REG_ReadConversionData12(VSENSE_ADC_INSTANCE)), LL_ADC_RESOLUTION_12B); /* mV */
 
    /* STM32G474E_EVAL board is used */
    /* Value is multiplied by 5.97 according to board measurments.
       Theorically, it should have been 5.97 (Divider R6/R7 (40.2K/200K) for VSENSE */
    voltage *= 597U;
    voltage /= 100U;
 
    return voltage;
/* USER CODE END BSP_PWR_VBUSGetVoltage */
}

Could you please check in your side if you respect the above condition?

Regards

Yohann

Nicolas P.
ST Employee

Hello,

which STM32CubeMx version are you using ?

You can have a look at this video, even if it is targeted to STM32G0, based on STM32CubeMX 5.4.0.

The detailed application note giving all the details is coming very soon.

Nicolas

Thank you very much for such great detail! I was attempting to measure VBUS by setting the following option in the device configuration tool in STM32CubeIDE:

Under Pinout & Configuration > System Core > SYS; for option Power Voltage Detector In, I selected External Input Analog Voltage, but I wasn't wiring anything from the Type-C port to that pin on the MCU, as I didn't realize it was necessary for basic cable detection (I assumed it only needed the CC pins).

But looking at your message sequence chart for SNK, I now see why it is necessary! I also see that I'm using the wrong mechanism for VBUS measurement. According to the video linked by @Nicolas P​ in the other answer, it suggests the USBPD stack actually depends on defining the user label of an ADC pin to "VSENSE". So this is how a given ADC input is associated with the USBPD stack?

I am positive I do not correctly implement the VBUS measurement in BSP_PWR_VBUSGetVoltage. I will implement the required ADC configuration and VBUS measurement routines, and I will copy your same voltage divider and attach VBUS to the ADC input. I can use my multimeter to finesse the multipliers appropriately.

I don't believe I have the necessary components to implement proper CC-short-to-VBUS protection (not sure what components I need or how I would do that anyway), like your TCPP01-M12 offers. I'll need to do more research for that.

I will make these changes when I get home tonight after work and report back my findings. Again, thanks a bunch.

The video you linked was fantastic -- very, very insightful!

I am using STM32CubeIDE 1.1.0, build 4551_20191014-1140. I am not using a standalone installation of STM32CubeMX, just the version integrated with CubeIDE, which is STM32CubeMX 5.4.0 build 20191009-1541.

I have a few questions regarding the video:

  1. When configuring FreeRTOS, how do you determine the heap size TOTAL_HEAP_SIZE? The example sets it to 5000 bytes seemingly arbitrarily.
  2. When configuring USBPD default SNK PDOs, you have to provide the PDO as a 32-bit word. Are there any tools or resources to assist constructing this word? The example changes the initial value of SNK PDO 1 from 0x26019096 to 0x02019096, but it isn't clear what either of these mean.
  3. When enabling the debug trace, the example employs an LPUART. However, I cannot use the LPUART on the STM32G431KB because those pins are reserved for the ST-Link USART2 interface. I can re-assign the USART2 pins to other GPIO pins, but I'm afraid I will lose ST-Link functionality by doing that. Is that true? And the LPUART cannot be reassigned to any other GPIO pins.
    1. Alternatively, can I just use the regular USART1 interface for the debug trace? Specifically, will that have any negative impact on the functionality of STM32CubeMon-UCPD?
  4. In the GUI_INTERFACE configuration, the HWBoardVersionName looks like a string I can define however I choose. But for PDTypeName, the example uses the value MB1360. Is this value important? What does it mean?

Thanks a bunch!

Again, all the details around the parameters are currently being written in an application note.

About :

1- Correct. This is arbitrarily set, not trying to optimize anything. This is not the goal here.

We could also get rid of FreeRTOS but this is not yet possible with CubeMx.

2- About the PDOs. It is planned to be able to edit them directly in CubeMX.

Even if some values are entered here, they are not used by the very basic application after. It is only a way to indicate to the user that he will have to complete them later.

0x02019096 means 5000mV/ 1500mA, dual role data, without Fast Role swap, see table 6-14 in document Universal Serial Bus Power Delivery Specification, Revision 3.0, Version 2.0, August 28 2019 for more information

3- As you wrote : yes, you can use USART1. STM32CubeMon-UCPD can use any UART.

4- For the HWBoardVersionName and the PDTypeName can be what you want. MB1360 has been set because it is the board reference of the NUCLEO-G071RB

Thank you so much for your time and attention. All great answers. I will keep my eye out for the upcoming app note

Great progress with your help -- now the CAD module is correctly calling my callbacks, is able to detect the correct voltage on VBUS, and is able to detect cable attach/detach events!

One thing I had to change from the video example was Timebase Source. If SysTick is used, the system will block during the UCPD interrupt ISR, which calls HAL_Delay, which in turn depends on SysTick incrementing, but that can never happen because the SysTick ISR has a lower priority than UCPD ISR. So I just changed Timebase Source to use TIM6, whose interrupt priority was maximal.

One issue I'm having now seems related. The UCPD ISR has a higher priority than allowed by the ISR-safe API of FreeRTOS. So configASSERT will fail at the following call stack location:

vPortValidateInterruptPriority() at port.c:754 0x800c380	
xQueueGenericSendFromISR() at queue.c:947 0x800c7dc	
osMessagePut() at cmsis_os.c:1,124 0x800b91c	
USBPD_DPM_CADTaskWakeUp() at usbpd_dpm_core.c:352 0x8014b8c	
PORTx_IRQHandler() at usbpd_hw_if_it.c:174 0x800e930	
USBPD_PORT0_IRQHandler() at usbpd_hw_if_it.c:39 0x800e6c4	
UCPD1_IRQHandler() at stm32g4xx_it.c:247 0x80075a8	
<signal handler called>() at 0xfffffffd	

The relevant source code reads:

	void vPortValidateInterruptPriority( void )
	{
		uint32_t ulCurrentInterrupt;
		uint8_t ucCurrentPriority;
 
		/* Obtain the number of the currently executing interrupt. */
		__asm volatile( "mrs %0, ipsr" : "=r"( ulCurrentInterrupt ) :: "memory" );
 
		/* Is the interrupt number a user defined interrupt? */
		if( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER )
		{
			/* Look up the interrupt's priority. */
			ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ];
 
			/* The following assertion will fail if a service routine (ISR) for
			an interrupt that has been assigned a priority above
			configMAX_SYSCALL_INTERRUPT_PRIORITY calls an ISR safe FreeRTOS API
			function.  ISR safe FreeRTOS API functions must *only* be called
			from interrupts that have been assigned a priority at or below
			configMAX_SYSCALL_INTERRUPT_PRIORITY.
 
			Numerically low interrupt priority numbers represent logically high
			interrupt priorities, therefore the priority of the interrupt must
			be set to a value equal to or numerically *higher* than
			configMAX_SYSCALL_INTERRUPT_PRIORITY.
 
			Interrupts that	use the FreeRTOS API must not be left at their
			default priority of	zero as that is the highest possible priority,
			which is guaranteed to be above configMAX_SYSCALL_INTERRUPT_PRIORITY,
			and	therefore also guaranteed to be invalid.
 
			FreeRTOS maintains separate thread and ISR API functions to ensure
			interrupt entry is as fast and simple as possible.
 
			The following links provide detailed information:
			http://www.freertos.org/RTOS-Cortex-M3-M4.html
			http://www.freertos.org/FAQHelp.html */
			configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
		}
		...
	}

I will continue adjusting priorities to see if I can resolve this.

After making that change, I am now getting a strange error. When I leave the application running for around 30 seconds, the system Hard Fault interrupt is triggered. Presumably related to the new system timer I set:

HardFault_Handler() at stm32g4xx_it.c:96 0x800751a	
<signal handler called>() at 0xfffffff1	
HAL_TIM_IRQHandler() at stm32g4xx_hal_tim.c:3,187 0x800a534	
TIM6_DAC_IRQHandler() at stm32g4xx_it.c:235 0x800757a	
<signal handler called>() at 0xfffffffd	
prvPortStartFirstTask() at port.c:267 0x800bf88	
xPortStartScheduler() at port.c:379 0x800c096	

This interrupt is called repeatedly (since it serves as Timebase source) for a period of time without any error. It only causes a hard fault after letting the system run for a while...

I suspect a problem with stack size. I suggest to increase it.