Getting started with USB Type-C only Source

Target description

This tutorial aims to help you to:

  • use the X-NUCLEO-SRC1M1 shield that includes a TCPP02-M18 protection circuit and provides a USB Type-C® connector
  • create a USB legacy 3A@5V Type C Source application with the NUCLEO-F446RE board that does not include any UCPD peripheral and the X-NUCLEO-SRC1M1 shield by using STM32CubeIDE software

Prerequisites

  • Computer with Windows 7 (or higher)

Hardware

  • NUCLEO-F446RE (tested on rev C-04) [1]
  • X-NUCLEO-SRC1M1 shield [2]
  • A USB-PD sink device to test our USB Source device (it can be the sink created in this wiki article, or a USB Type-C® mobile phone or device)
  • USB cable Type-A to Mini-B
  • USB Type-C® to Type-C® cable

Software

  • STM32CubeIDE (tested with V1.9.0) [3]
  • X-CUBE-TCPP MCU Firmware Package (BSP) [4]

Literature

  • UM1724 NUCLEO-F446RE User Manual
  • UM2973 X-NUCLEO-SRC1M1 User Manual


Create a USB-PD Source Device

Clock.png Total 50min

1 Creating the project

Clock.png 5min

Open STM32CubeIDE and create a new STM32 project. As a target selection, choose the NUCLEO-F446RE from the Board Selector Tab

USBnoPD 0 Newproject.png


Click "Next", then enter your project name. Leave the other fields as default and click "Finish".

USBnoPD 1 Newproject.png


When prompted for initializing peripherals with their default mode, click No.

2 Configuring the system

Clock.png 15min

At this point, your project is created and you are left with the STM32CubeMX view. The next steps, deal with the peripherals configuration and the options required for the project.

2.1 Clear the pinout

To start from a blank configuration, click on the Pinout menu and select Clear Pinouts. This resets the pinouts in the pinout view.

USBPD 0-pinoutConf.png


2.2 Configure the system timebase

For this simple example, we use SysTick as the system timebase. In the System Core section, select SYS and change the Timebase Source to SysTick.

USBnoPD 0 sysConf v2.png


2.3 Configure GPIO

Three GPIO are used.

  • Click left on the pin PC8 and select GPIO_Output. With a right click select Enter user label to name it as ENABLE. This output drives the TCPP02 chip enable input.
  • Click left on the pin PA5 and select GPIO_Output. With a right click select Enter user label to name it as GREEN_LED. This output drives the green led on the Nucleo board to indicate that Vbus is ON.
  • Click left on the pin PC4 and select GPIO_EXTI4, and with a right click select Enter user label to name it as 'FLGN'. This interrupt input receives any TCPP02 fault flag.

in the System Core section, select GPIO, and then change PC4 mode to "External interrupt mode with falling edge detection".

USBnoPD 0 GPIOConf v2.png


Finally, in the NVIC tab, activate the EXTI lines four interrupts

USBnoPD 1 GPIOConf v2.png


2.4 Configure ADC peripheral

Five signals must be monitored to ensure proper and safe USB 3A-5V delivery:
CC1 and CC2 lines voltage, Vbus and Vprovider voltages and Iana, the current through Vbus.
The ADC needs to be configured:
In the pin view, with a left click, select the pin PC0 and configure it as ADC1 Channel 10 (CC2).
Repeat the operation for PC1 as ADC1 Channel 11 (IANA); for PA0 as ADC1 Channel 0 (VBusc); for PA1 as ADC1 Channel 1(Vprov); and for PA4 as ADC1 Channel 4 (CC1).

Note: it is not mandatory to set their name as each value is stored in a table by the DMA in function of its rank.

USBnoPD 0 ADCConf.png


2.4.1 ADC parameters configuration

In the Analog section click on ADC and select, in the Parameter Settings tab:

  • Scan conversion mode: Enabled
  • Continuous conversion mode: Enabled
  • Number of conversions: 5
USBnoPD 1 ADCConf v2.png

Define for each ADC input its channel and 84 cycles sampling time

  • Rank 1: CC2: Channel 10
  • Rank 2: CC1: Channel 4
  • Rank 3: Vbus: Channel 0
  • Rank 4: Iana: Channel 11
  • Rank 5: Vprov: Channel 1
USBnoPD 2 ADCConf.png


2.4.2 DMA Configuration for ADC1

in the System Core section, select the DMA, tab DMA2 click on "Add" and select ADC1 in the DMA2 request column.

USBnoPD 0 DMAConf.png


Set the DMA mode to: Circular

USBnoPD 1 DMAConf.png


Then back to the ADC1 parameters configuration, set DMA Continuous Requests to Enabled

USBnoPD 3 ADCConf.png


2.5 Configure TIM2 timer

in the Timers section, select timer2: "TIM2", affect the "Internal clock" as clock source. In the Parameter Settings tab,

  • The Internal clock division to No division; Then the timer peripheral frequency is 84MHz
  • Set the Prescaler value to 2099; Then the timer counter frequency is 84 / (2099+1) = 40kHz
  • The counter period to 39; Then the timer period is 40kHz / (39+1) = 1ms
  • Auto-reload preload to Enable
USBnoPD 0 Tim2Conf2.png


Finally, select the NVIC Settings tab to enable the TIM2 global interrupt

USBnoPD 1 Tim2Conf.png


2.6 Configure I2C peripheral

As the X-NUCLEO-SRC1M1 shield includes a TCPP02-M18 that communicates via I2C, we need to enable the I2C peripheral in our project.

In the Connectivity section, enable I2C1 peripheral, in I2C mode. Leave the configuration as default, as the X-NUCLEO-SRC1M1 BSP reconfigures it.

USBnoPD 0 I2CConf.png


Note: We need to enable the I2C1 peripheral in the CubeMX view for code generation to include the I2C drivers.

2.7 Check the clock configuration

Under Clock Configuration main tab.

USBnoPD 0 CLKConf2.png


3 Configure the project

Clock.png 5min

Under the Project Manager main tab, and the Advanced Settings tab, as we do not need I2C initialization functions (handled by the BSP drivers), uncheck Generate Code for the MX_I2C1_Init.

USBnoPD 0 ConfProjet.png


4 Add BSP to the project

Clock.png 5min

Board support package (BSP) for the X-NUCLEO-SRC1M1 shield needs to be added to the project. The files need to be copied manually in the project's folder.
Get the latest BSP from GitHub x-cube-tcpp.

Manually copy the two following folders (X-NUCLEO-SRC1M1 & tcpp0203):

x-cube-tcpp
└── Drivers
    └── BSP
        ├── 📁X-NUCLEO-SRC1M1
        └── Components
            └── 📁tcpp0203

Into:

<ProjectFolder>
└── Drivers
    └── BSP
        ├── 📁X-NUCLEO-SRC1M1
        └── Components
            └── 📁tcpp0203

Then, create a file named ".extSettings" at the project's root folder (be careful with to the dot character in the filename) and fill in with the following code:

[ProjectFiles]
HeaderPath=Drivers/BSP/X-NUCLEO-SRC1M1;Drivers/BSP/Components/tcpp0203
[Others]
Define=TCPP0203_SUPPORT;USBPD_CONFIG_MX;USE_STM32F4XX_NUCLEO
HALModule=
[Groups]
Drivers/BSP/X-NUCLEO-SRC1M1=Drivers/BSP/X-NUCLEO-SRC1M1/src1m1_usbpd_pwr.c;Drivers/BSP/X-NUCLEO-SRC1M1/src1m1_bus.c
Drivers/BSP/Components/tcpp0203=Drivers/BSP/Components/tcpp0203/tcpp0203.c;Drivers/BSP/Components/tcpp0203/tcpp0203_reg.c

This file is used to tell the code generator to include the BSP files when generating the project.

Info white.png Information
You can double-click the code zones to select it all, then copy it with Ctrl+c.

5 Generate code

Clock.png 5min

Save your file with Ctrl+s and select generate code if prompted. You can also generate code from the STM32CubeIDE menu, clicking on Project/Generate Code, or by pressing Alt+K.

USBPD 0-projGen.png



In this project, different folders can be found:

  • The Core folder contains the source files for the core of the project.
  • The Drivers folder contains the HAL drivers for the STM32, and the BSP for the Nucleo board and X-NUCLEO-SRC1M1 shield.

The Drivers folder in the Explorer view of the project must contain the BSP folders added earlier.

USBnoPD 0 Tree2.png


6 Complete application

Clock.png 15min

Now that the peripherals are initialized by STM32CubeMX, some minimum level of the application needs to be added:

  • src1m1_conf.h file needs to be created from its template, and added to the project
  • User code needs to be added in several files

6.1 Add and modify SRC1M1 configuration file

In the Drivers/BSP/X-NUCLEO-SRC1M1 folder you will find src1m1_conf_template.h. Copy it to Core/Inc folder, and rename it src1m1_conf.h. It is the configuration file used for the X-NUCLEO-SRC1M1 BSP.

Change BSP_USBPD_PWR_DONT_WAIT_VBUSOFF_DISCHARGE value to 1:

#define BSP_USBPD_PWR_DONT_WAIT_VBUSOFF_DISCHARGE   1u /* Set to 1 to not wait for vbus discharge in VBUSOff function */

6.2 Modification in main.h

Info white.png Information
You can double-click the code zones to select it all, then copy it with Ctrl+c.

Add the following code between the /* USER CODE BEGIN ET-END ET*/ tags:

/* USER CODE BEGIN ET */
typedef enum
{
  USBnoPD_CC1 = 0u,
  USBnoPD_CC2
} USBnoPD_CCTypeDef;

typedef enum
{
  USBnoPD_State_DETACHED = 0u, /* IDLE, nothing connected         */
  USBnoPD_State_ATTACHING,     /* Attachment ongoing - debouncing */
  USBnoPD_State_ATTACHED,      /* Attached                        */
  USBnoPD_State_DETACHING,     /* Detachment ongoing - debouncing */
  USBnoPD_State_DISCHARGING,   /* Vbus discharge ongoing          */
  USBnoPD_State_FAULT          /* Hardware fault                  */
} USBnoPD_StatesTypeDef;

typedef enum
{
  USBnoPD_ADC_Index_CC1 = 0u, /* CC1 index in adc buffer    */
  USBnoPD_ADC_Index_CC2,      /* CC2 index in adc buffer    */
  USBnoPD_ADC_Index_VBUSC,    /* VBus index in adc buffer   */
  USBnoPD_ADC_Index_ISENSE,   /* Isense index in adc buffer */
  USBnoPD_ADC_Index_VPROV     /* Vprov index in adc buffer  */
} USBnoPD_ADCBufIDTypeDef;
/* USER CODE END ET */

Add the following code between the /* USER CODE BEGIN EC-END EC*/ tags:

/* USER CODE BEGIN EC */
#define USBNOPD_ADC_USED_CHANNELS     5u      /* Number of used ADC channels                                   */

#define USBNOPD_CC_VOLTAGE_MAXRA      800u    /* CC line Max voltage when Ra is connected (in mV)              */
#define USBNOPD_CC_VOLTAGE_MINRD      850u    /* CC line Min voltage when connected to Rd (in mV)              */
#define USBNOPD_CC_VOLTAGE_MAXRD      2450u   /* CC line Max voltage when connected to Rd (in mV)              */
#define USBNOPD_CC_VOLTAGE_MINOPEN    2750u   /* CC line Minimum voltage when not connected (in mV)            */
#define USBNOPD_VBUS_VOLTAGE_MAX      5500u   /* Vbus Maximum allowed voltage (in mV)                          */
#define USBNOPD_VPROV_VOLTAGE_MIN     4500u   /* Vprov Minimum voltage (in mV)                                 */
#define USBNOPD_VSAFE_VOLTAGE_MAX     100u    /* Vbus safe voltage to end vbus discharge (in mV)               */

#define USBNOPD_SRC1M1_NORA           0u      /* No voltage divider on CC lines                                */
#define USBNOPD_SRC1M1_NORB           0u      /* No voltage divider on CC lines                                */

#define USBNOPD_DEBOUNCE_ATTACH_TICKS 120u    /* Number of ticks needed to complete attaching state debouncing */
#define USBNOPD_DEBOUNCE_DETACH_TICKS 10u      /* Number of ticks needed to complete detaching state debouncing */

#define ADC_FULL_SCALE                0x0FFFu /* Maximum digital value of the ADC output (12 Bits)             */
/* USER CODE END EC */

Add the following code between the /* USER CODE BEGIN EFP-END EFP*/ tags:

/* USER CODE BEGIN EFP */
void USBnoPD_ProcessADC(void);
void USBnoPD_IncrementDebounceCount(void);
void USBnoPD_TCPPFaultHandling(void);
/* USER CODE END EFP */

6.3 Modification in main.c

Add the following code between the /*USER CODE BEGIN Includes-END Includes*/ tags:

/* USER CODE BEGIN Includes */
#include "src1m1_usbpd_pwr.h"
/* USER CODE END Includes */

Add the following code between the /*USER CODE BEGIN PV-END PV*/ tags :

/* USER CODE BEGIN PV */
USBnoPD_StatesTypeDef USBnoPD_State = USBnoPD_State_DETACHED;
uint16_t USBnoPD_adc_buffer[USBNOPD_ADC_USED_CHANNELS] = {0};
uint16_t USBnoPD_adc_converted_buffer[USBNOPD_ADC_USED_CHANNELS] = {0};
uint16_t USBnoPD_debounce_counter = 0;
uint8_t USBnoPD_activeCC = USBnoPD_CC1; /* Default */
/* USER CODE END PV */

Add the following code between the /*USER CODE BEGIN PFP-END PFP*/ tags:

/* USER CODE BEGIN PFP */
static uint32_t USBnoPD_TCPP0203_ConvertADCDataToVoltage(uint32_t ADCData, uint32_t Ra, uint32_t Rb);
static int32_t USBnoPD_TCPP0203_ConvertADCDataToCurrent(uint32_t ADCData, uint32_t Ga, uint32_t Rs);
static void USBnoPD_StateMachineRun(void);
/* USER CODE END PFP */


Add the following code between the /*USER CODE BEGIN 2-END 2*/ tags :

  /* USER CODE BEGIN 2 */
  HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_RESET);
  HAL_GPIO_WritePin(ENABLE_GPIO_Port, ENABLE_Pin, GPIO_PIN_SET);

  BSP_USBPD_PWR_Init(USBPD_PWR_TYPE_C_PORT_1);
  BSP_USBPD_PWR_SetPowerMode(USBPD_PWR_TYPE_C_PORT_1, USBPD_PWR_MODE_NORMAL);

  HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&USBnoPD_adc_buffer, USBNOPD_ADC_USED_CHANNELS);
  /* USER CODE END 2 */

Add the following code between the /*USER CODE BEGIN 3-END 3*/ tags :

    /* USER CODE BEGIN 3 */

    /* Run the state machine */
    USBnoPD_StateMachineRun();
  }
  /* USER CODE END 3 */

Add the following code between the /*USER CODE BEGIN 4-END 4*/ tags :

/* USER CODE BEGIN 4 */
/**
  * @brief  Calculate the VBUS voltage level corresponding to ADC raw converted data.
  * @note   Voltage level is measured though a voltage divider
  *  Example :
  *  Voltage -- Ra ----.-   ^
  *                    |    |
  *                    Rb   | vadc = VBUS * Rb /(Ra + Rb)
  *                    |    |
  *                   GND   |
  * vadc = raw_data * (ADC_FULL_SCALE / VDD)
  * @param  ADCData  ADC raw converted data (resolution 12 bits)
  * @param  Ra       value of Ra resistance
  * @param  Rb       value of Rb resistance
  * @retval analog voltage (unit: mV)
  */
static uint32_t USBnoPD_TCPP0203_ConvertADCDataToVoltage(uint32_t ADCData, uint32_t Ra, uint32_t Rb)
{
  uint32_t voltage;
  uint32_t vadc;

  /* Convert ADC RAW data to voltage */
  vadc = (ADCData * VDD_VALUE) / ADC_FULL_SCALE;

  /* If no Ra or Rb are defined, return vadc directly */
  if ((Ra == 0u) && (Rb == 0u))
  {
    voltage = vadc;
  }
  else
  {
    /* Avoid dividing by zero */
    if (Rb == 0u)
    {
      voltage = 0u;
    }
    else
    {
      /* Apply voltage divider */
      voltage = vadc * (Ra + Rb) / Rb;
    }
  }

  return voltage;
}

/**
  * @brief  Calculate the VBUS current level corresponding to ADC raw converted data.
  * @note   ADC measurement provides measurement on IANA pin.
  * @param  ADCData  ADC raw converted data (resolution 12 bits)
  * @param  Ga       value of TCPP0X Iana gain in V/V
  * @param  Rs       value of shunt resistor in milliohm
  * @retval VBUS analog current (unit: mA)
  */
static int32_t USBnoPD_TCPP0203_ConvertADCDataToCurrent(uint32_t ADCData, uint32_t Ga, uint32_t Rs)
{
  int32_t current;
  uint32_t vadc;

  /* Avoid dividing by zero */
  if ((Ga == 0u) || (Rs == 0u))
  {
    current = 0u;
  }
  else
  {
    vadc = (ADCData * VDD_VALUE) / ADC_FULL_SCALE;
    current = (int32_t)((vadc * 1000u) / (Ga * Rs));
  }

  return current;
}

/**
  * @brief  Process the ADC values and update USBnoPD_adc_converted_buffer with measured values.
  * @param  none
  * @retval none
  */
void USBnoPD_ProcessADC(void)
{
  /* Local buffer for filtered RAW ADC values */
  static uint16_t USBnoPD_adc_buffer_filtered[USBNOPD_ADC_USED_CHANNELS];

  /* Perform ADC Filtering */
  for (uint8_t i = 0u; i < USBNOPD_ADC_USED_CHANNELS; i++)
  {
    USBnoPD_adc_buffer_filtered[i] = (USBnoPD_adc_buffer_filtered[i] + USBnoPD_adc_buffer[i]) >> 1u;
  }

  /* Update the voltage buffer by converting the filtered values */
  USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] =
    USBnoPD_TCPP0203_ConvertADCDataToVoltage(USBnoPD_adc_buffer_filtered[USBnoPD_ADC_Index_CC1],
                                                                         USBNOPD_SRC1M1_NORA,
                                                                         USBNOPD_SRC1M1_NORB);
  USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] =
    USBnoPD_TCPP0203_ConvertADCDataToVoltage(USBnoPD_adc_buffer_filtered[USBnoPD_ADC_Index_CC2],
                                                                         USBNOPD_SRC1M1_NORA,
                                                                         USBNOPD_SRC1M1_NORB);
  USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_VBUSC] =
    USBnoPD_TCPP0203_ConvertADCDataToVoltage(USBnoPD_adc_buffer_filtered[USBnoPD_ADC_Index_VBUSC],
                                                                         SRC1M1_VSENSE_RA,
                                                                         SRC1M1_VSENSE_RB);
  USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_ISENSE] =
    USBnoPD_TCPP0203_ConvertADCDataToCurrent(USBnoPD_adc_buffer_filtered[USBnoPD_ADC_Index_ISENSE],
                                                                         SRC1M1_ISENSE_GA,
                                                                         SRC1M1_ISENSE_RS);
  USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_VPROV] =
    USBnoPD_TCPP0203_ConvertADCDataToVoltage(USBnoPD_adc_buffer_filtered[USBnoPD_ADC_Index_VPROV],
                                                                         SRC1M1_VSENSE_RA,
                                                                         SRC1M1_VSENSE_RB);
}

/**
  * @brief  Increment debounce counter, used in timer IRQHandler
  * @param  none
  * @retval none
  */
void USBnoPD_IncrementDebounceCount(void)
{
  USBnoPD_debounce_counter++;
}

/**
  * @brief  Handle a fault raised by the TCPP (OCP), used by EXTI IRQHandler
  * @param  none
  * @retval none
  */
void USBnoPD_TCPPFaultHandling(void)
{
    /* Cut VBUS */
  BSP_USBPD_PWR_VBUSOff(USBPD_PWR_TYPE_C_PORT_1);
  BSP_USBPD_PWR_VBUSDischargeOn(USBPD_PWR_TYPE_C_PORT_1);

  /* Turn off green LED to indicate source is OFF */
  HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_RESET);

  /* Go to Fault state */
  USBnoPD_State = USBnoPD_State_FAULT;
}

/**
  * @brief  Main state machine
  * @param  none
  * @retval none
  */
static void USBnoPD_StateMachineRun(void)
{
  switch(USBnoPD_State)
  {
    case USBnoPD_State_DETACHED:      /* IDLE, nothing connected         */
      /* Transition to next state */
      if (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_VPROV] > USBNOPD_VPROV_VOLTAGE_MIN)
      {
        /* Connection detected on CC1 */
        if ((USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] > USBNOPD_CC_VOLTAGE_MINRD) &&
            (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] < USBNOPD_CC_VOLTAGE_MAXRD) &&
            (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] < USBNOPD_CC_VOLTAGE_MAXRA ||
             USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] > USBNOPD_CC_VOLTAGE_MINOPEN))
        {
          /* Reset debouncing counter */
          USBnoPD_debounce_counter = 0u;

          /* Start the debouncing timer */
          HAL_TIM_Base_Start_IT(&htim2);

          /* Active CC is now CC1 */
          USBnoPD_activeCC = USBnoPD_CC1;

          /* Go to attaching state */
          USBnoPD_State = USBnoPD_State_ATTACHING;
        }
        /* Connection detected on CC2 */
        else if ((USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] > USBNOPD_CC_VOLTAGE_MINRD) &&
                 (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] < USBNOPD_CC_VOLTAGE_MAXRD) &&
                 (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] < USBNOPD_CC_VOLTAGE_MAXRA ||
                  USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] > USBNOPD_CC_VOLTAGE_MINOPEN))
        {
          /* Reset debouncing counter */
          USBnoPD_debounce_counter = 0u;

          /* Start the debouncing timer */
          HAL_TIM_Base_Start_IT(&htim2);

          /* Active CC is now CC2 */
          USBnoPD_activeCC = USBnoPD_CC2;

          /* Go to attaching state */
          USBnoPD_State = USBnoPD_State_ATTACHING;
        }
      }
      break;

    case USBnoPD_State_ATTACHING:     /* Attachment ongoing - debouncing */
      /* If a glitch is detected, reset debounce counter and go to detached state*/
      if (((USBnoPD_activeCC == USBnoPD_CC1) &&
           ((USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] > USBNOPD_CC_VOLTAGE_MAXRD) ||
            (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] < USBNOPD_CC_VOLTAGE_MINRD))) ||
          ((USBnoPD_activeCC == USBnoPD_CC2) &&
           ((USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] > USBNOPD_CC_VOLTAGE_MAXRD) ||
            (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] < USBNOPD_CC_VOLTAGE_MINRD))))
      {
        /* Reset debouncing counter */
        USBnoPD_debounce_counter = 0u;

        /* Stop the debouncing timer */
        HAL_TIM_Base_Stop_IT(&htim2);

        /* Go to attaching state */
        USBnoPD_State = USBnoPD_State_DETACHED;
      }
      /* If debouncing is over */
      else if (USBnoPD_debounce_counter >= USBNOPD_DEBOUNCE_ATTACH_TICKS)
      {
        /* Reset debouncing counter */
        USBnoPD_debounce_counter = 0u;

        /* Stop the debouncing timer */
        HAL_TIM_Base_Stop_IT(&htim2);

        /* Turn ON Vbus */
        BSP_USBPD_PWR_VBUSDischargeOff(USBPD_PWR_TYPE_C_PORT_1);
        BSP_USBPD_PWR_VBUSOn(USBPD_PWR_TYPE_C_PORT_1);

        /* Turn on green LED to indicate source is ON */
        HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_SET);

        /* Go to attached state */
        USBnoPD_State = USBnoPD_State_ATTACHED;
      }
      break;

    case USBnoPD_State_ATTACHED:      /* Attached                        */
      /* If we detect a fault on Vbus or Vprov and
         CC voltage still correspond to an attached state */
      if (((USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_VBUSC]  > USBNOPD_VBUS_VOLTAGE_MAX) ||
           (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_VPROV] < USBNOPD_VPROV_VOLTAGE_MIN))&&
          (((USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] > USBNOPD_CC_VOLTAGE_MINRD) &&
            (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] < USBNOPD_CC_VOLTAGE_MAXRD))||
           ((USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] > USBNOPD_CC_VOLTAGE_MINRD) &&
            (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] < USBNOPD_CC_VOLTAGE_MAXRD))))
      {
        /* Cut VBUS */
        BSP_USBPD_PWR_VBUSOff(USBPD_PWR_TYPE_C_PORT_1);
        BSP_USBPD_PWR_VBUSDischargeOn(USBPD_PWR_TYPE_C_PORT_1);

        /* Turn off green LED to indicate source is OFF */
        HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_RESET);

        /* Go to fault state */
        USBnoPD_State = USBnoPD_State_FAULT;
      }
      /* If a detachment is detected */
      else if (((USBnoPD_activeCC == USBnoPD_CC1) &&
                (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] > USBNOPD_CC_VOLTAGE_MAXRD)) ||
               ((USBnoPD_activeCC == USBnoPD_CC2) &&
                (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] > USBNOPD_CC_VOLTAGE_MAXRD)))
      {
        /* Reset debouncing counter */
        USBnoPD_debounce_counter = 0u;

        /* Start the debouncing timer */
        HAL_TIM_Base_Start_IT(&htim2);

        /* Go to detaching state */
        USBnoPD_State = USBnoPD_State_DETACHING;
      }
      break;

    case USBnoPD_State_DETACHING:     /* Detachment ongoing - debouncing */
      /* Detach abort */
      if (((USBnoPD_activeCC == USBnoPD_CC1) &&
           (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] < USBNOPD_CC_VOLTAGE_MAXRD)) ||
          ((USBnoPD_activeCC == USBnoPD_CC2) &&
           (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] < USBNOPD_CC_VOLTAGE_MAXRD)))
      {
        /* Reset debouncing counter */
        USBnoPD_debounce_counter = 0u;

        /* Stop the debouncing timer */
        HAL_TIM_Base_Stop_IT(&htim2);

        /* Go back to attached state */
        USBnoPD_State = USBnoPD_State_ATTACHED;
      }
      /* If debouncing is over */
      else if (USBnoPD_debounce_counter >= USBNOPD_DEBOUNCE_DETACH_TICKS)
      {
        /* Reset debouncing counter */
        USBnoPD_debounce_counter = 0u;

        /* Stop the debouncing timer */
        HAL_TIM_Base_Stop_IT(&htim2);

        /* Turn OFF Vbus */
        BSP_USBPD_PWR_VBUSOff(USBPD_PWR_TYPE_C_PORT_1);
        BSP_USBPD_PWR_VBUSDischargeOn(USBPD_PWR_TYPE_C_PORT_1);

        /* Turn off green LED to indicate source is OFF */
        HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_RESET);

        /* Go to discharging state */
        USBnoPD_State = USBnoPD_State_DISCHARGING;
      }
      break;

    case USBnoPD_State_DISCHARGING:   /* Vbus discharge after detach ongoing */
      /* If Vbus is considered measured as 0v */
      if (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_VBUSC]  < USBNOPD_VSAFE_VOLTAGE_MAX)
      {
        /* Reset debouncing counter */
        USBnoPD_debounce_counter = 0u;

        /* Stop the debouncing timer */
        HAL_TIM_Base_Stop_IT(&htim2);

        /* Stop Vbus discharge */
        BSP_USBPD_PWR_VBUSDischargeOff(USBPD_PWR_TYPE_C_PORT_1);

        /* Turn off green LED to indicate source is OFF */
        HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_RESET);

        /* Go to detached state */
        USBnoPD_State = USBnoPD_State_DETACHED;
      }
      break;

    case USBnoPD_State_FAULT:         /* Hardware fault                  */
      /* In case of a fault, do nothing until a detach is detected and Vbus is at 0v */
      if ((((USBnoPD_activeCC == USBnoPD_CC1) &&
            (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC1] > USBNOPD_CC_VOLTAGE_MINOPEN)) ||
           ((USBnoPD_activeCC == USBnoPD_CC2) &&
            (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_CC2] > USBNOPD_CC_VOLTAGE_MINOPEN))) &&
          (USBnoPD_adc_converted_buffer[USBnoPD_ADC_Index_VBUSC]  < USBNOPD_VSAFE_VOLTAGE_MAX))
      {
        /* Stop Vbus discharge */
        BSP_USBPD_PWR_VBUSDischargeOff(USBPD_PWR_TYPE_C_PORT_1);

        /* Go to detached state */
        USBnoPD_State = USBnoPD_State_DETACHED;
      }
      break;

  default:                            /* Should not happen               */
    /* Cut VBUS */
    BSP_USBPD_PWR_VBUSOff(USBPD_PWR_TYPE_C_PORT_1);
    BSP_USBPD_PWR_VBUSDischargeOn(USBPD_PWR_TYPE_C_PORT_1);

    /* Turn off green LED to indicate source is OFF */
    HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_RESET);

    /* Go to fault state */
    USBnoPD_State = USBnoPD_State_FAULT;
    break;
  }
  /* 1ms delay between each state machine iterations */
  HAL_Delay(1);
}
/* USER CODE END 4 */

6.4 Modification in stm32f4xx_it.c

In the TIM2 IRQ Handler, add the following code between the /*USE CODE BEGIN TIM2_IRQn 0-END TIM2_IRQn 0*/ tags:

  /* USER CODE BEGIN TIM2_IRQn 0 */

  /* Increment the debounce counter */
  USBnoPD_IncrementDebounceCount();

  /* USER CODE END TIM2_IRQn 0 */

In the EXTI4 IRQ Handler, add the following code between the /*USE CODE BEGIN EXTI4_IRQn 0-END EXTI4_IRQn 0*/ tags:

  /* USER CODE BEGIN EXTI4_IRQn 0 */

  /* Handle TCPP FLGn fault flag (active LOW) */
  USBnoPD_TCPPFaultHandling();

  /* USER CODE END EXTI4_IRQn 0 */

In the DMA2 IRQ Handler, add the following code between the /*USE CODE BEGIN DMA2_Stream0_IRQn 0-END DMA2_Stream0_IRQn 0*/ tags:

  /* USER CODE BEGIN DMA2_Stream0_IRQn 0 */

  /* Perform ADC processing */
  USBnoPD_ProcessADC();

  /* USER CODE END DMA2_Stream0_IRQn 0 */

7 Configure the shield's jumpers

Place jumpers on the X-NUCLEO-SRC1M1 shield as shown in the picture. tcenter

On the NUCLEO-F446, place a link between PA3 (CN10 - 37) and PC4 (CN10 - 34).

This X-NUCLEO-SRC1M1 shield default configuration allows SINK to source up to 0.5A @ 5V.
Plug an external 5V source with current capability >0.6A into the green "source" connector.
The current sense resistor R4 is 7mOhms, then TCPP02 current protection level is 6A. Refer to TCPP02-M18 datasheet. [5]

With this configuration, the board is powered by the ST-Link of the Nucleo board.
If you want to power your system from the external power supply connected to the "source" terminal, and not from the ST-Link, add the JP1 jumpers between 1-2 and 3-4.

Info white.png Information

To increase the solution current capability to 3A @ 5V,

  • Remove R35 and place it on SH19
  • Remove R39 and place it on SH21
  • Replace R4 sense resistor (initially 7mOhms) with a 10mOhms resistor

Next, plug an external 5V source with current capability > 4.5A into the green "source" connector.

In SRC1M1_conf.h change SRC1M1_ISENSE_RS value from 7 milliohms to 10 milliohms:

#define SRC1M1_ISENSE_RS                            10u  /* Current measure shunt resistor in milliohm */

8 Compile and run the application

The compilation must be performed without errors or warnings.
Build the application, clicking on the Built Button.png button (or select Project/Build Project).
Run the application, clicking on the DownloadRun Button.png button (or select Run/Run)

9 Evaluate the application

Clock.png 5min

Vbus is active 120 ms after plugging an USB Type-C source device.
Vbus is immediately shut down and placed in safe mode (0V) when unplugging the sink device.
During connection, if an overcurrent or malfunction is detected, the Source is placed in safe mode (0V) and needs a disconnection/reconnection to restart.



You can find other applicative examples on GitHub: x-cube-tcpp

10 References