2024-06-17 03:06 AM
Hi,
I'm developing 3-port USB PD hub device with STM32 USBPD library. I have an issue with powering up device with a PD contract over USBC port when it has already plugged sinks on other ports.
Investigation over I2C bus communication show that the issue is caused by CC lines debouncing process, which lasts for 120ms and blocks DPM communication with other ports:
I have discovered that this happens in tcpc_set_power function:
/* Check tCCDebounce */
while (CAD_tVBUSDebounce < CAD_tVBUSDebounce_threshold)
{
CAD_tVBUSDebounce = HAL_GetTick() - CAD_tVBUSDebounce_start;
/* Need to check that line is still connected */
uint32_t cc1 = 0xFF, cc2 = 0xFF;
if (USBPD_BUSY == fusb305_tcpc_get_cc(Port, &cc1, &cc2))
{
/* Line has been disconnected */
return USBPD_FAIL;
}
if (CCNONE == state[Port].CC_Pin)
{
/* Line has been disconnected */
return USBPD_FAIL;
}
}
which is called by fusb305_tcpc_set_rx_state function in DPM thread.
There is a 4ms delay inside of fusb305_tcpc_get_cc which would be enough to switch to other port management and respond to capability message, but it looks like that DPM does not run type C state machine for each port in parallel (as a RTOS task) and does this sequentially.
As a result, an attached source gets GoodCRC on capabilities message, but does not get a cap request, executes HardReset and the cycle repeats.
I tried to reduce debounce time from 120ms to 30ms and this helps in case of one attached sink. But this violates USB Type C specification (min CC debounce time is 100ms) and also does not work with two attached sinks, so I would need to reduce debounce time even more.
Q1: Is there a way to run DPM Type C state machine in parallel for several ports?
Q2: Is it possible to stop Type C state machine for SRC ports until SNK port gets stable power?
2024-06-18 02:44 AM - edited 2024-06-18 02:48 AM
I investigated the code provided with the example a little bit deeper and noticed that the DPM process is controlled with ALERT task. So I managed to resolve my issue by creating a ALERT task for each of ports (both with message boxes):
osThreadDef(ALERT0, USBPD_ALERT_Task, FREERTOS_ALERT_PRIORITY, 0, FREERTOS_ALERT_STACK_SIZE);
osThreadDef(ALERT1, USBPD_ALERT_Task, FREERTOS_ALERT_PRIORITY, 0, FREERTOS_ALERT_STACK_SIZE);
osThreadDef(ALERT2, USBPD_ALERT_Task, FREERTOS_ALERT_PRIORITY, 0, FREERTOS_ALERT_STACK_SIZE);
.......
osMessageQId AlarmMsgBox[USBPD_PORT_COUNT];
osThreadId ThreadAlert[USBPD_PORT_COUNT];
.......
/**
* @brief Initialize the OS parts (task, queue,... )
* @retval USBPD status
*/
USBPD_StatusTypeDef USBPD_DPM_InitOS(void)
{
#ifdef _RTOS
#if defined(USBPD_TCPM_MODULE_ENABLED)
osMessageQDef(MsgBox, TCPM_ALARMBOX_MESSAGES_MAX, uint16_t);
AlarmMsgBox[0] = osMessageCreate(osMessageQ(MsgBox), NULL);
AlarmMsgBox[1] = osMessageCreate(osMessageQ(MsgBox), NULL);
AlarmMsgBox[2] = osMessageCreate(osMessageQ(MsgBox), NULL);
if (NULL == (ThreadAlert[0] = osThreadCreate(osThread(ALERT0), &AlarmMsgBox[0])))
{
return USBPD_ERROR;
}
if (NULL == (ThreadAlert[1] = osThreadCreate(osThread(ALERT1), &AlarmMsgBox[1])))
{
return USBPD_ERROR;
}
if (NULL == (ThreadAlert[2] = osThreadCreate(osThread(ALERT2), &AlarmMsgBox[2])))
{
return USBPD_ERROR;
}
.....
and I also adjusted alert calls for appropriate message boxes:
/**
* @brief Processing Alert from TCPC
* @retval None
*/
void USBPD_Port0_Alert(void)
{
/* check to avoid count before OSKernel Start */
if (uxTaskGetNumberOfTasks() != 0)
{
UBaseType_t uxReturn = uxQueueMessagesWaitingFromISR(AlarmMsgBox[0]);
if (uxReturn < TCPM_ALARMBOX_MESSAGES_MAX)
{
osMessagePut(AlarmMsgBox[0], (USBPD_PORT_0 << 8 | 0), osWaitForever);
}
else
{
#if defined(_TRACE)
USBPD_TRACE_Add(USBPD_TRACE_DEBUG, USBPD_PORT_0, 0, (uint8_t *) "ALERT IT LOST", sizeof("ALERT IT LOST"));
#endif /* _TRACE */
}
}
}
Resulting diagram:
Now all ports are managed in parallel.
I also replaced HAL_Delay with osDelay in TCPC driver to not block other tasks.
I don't know how clean this solution but it works so far..