STM32 USB CDC

By now almost all of my projects included some sort of communication with PC, mostly with external USB-UART bridge chip like MCP2200 or CP210x. Although this devices were mostly prototypes and/or single products, such approach added unwanted costs and since almost all uC that I used already have USB peripheral embedded, there is no excuse not to use it.
So, here it is, STM32 USB CDC  – communication with PC over USB, generated with STM32CubeMX on STM32L100 discovery board. 


STM32CubeMX UDB CDC middleware selection

To generate basic USB CDC device with CubeMX, follow this previous post, but change Middleware USB profile to CDC. Also, set endpoint size to 64 bytes. More about endpoints later.

Again, USB files contains “settings for CDC” in this files:

  1. USB device library/Middlewares
    • usbd_core.c (provides all USB device core functions)
    • usbd_ioreq.c, usbd_ctlreq.c (provides everything for endpoint controls and USB request)
    • usbd_cdc.c (provides everything to interact with USB host, descriptors, data IO …)
  2. Application files:
    • usbd_conf.c (provides low layer / HAL functions)
    • usbd_device.c (provides initializing function called from main())
    • usbd_desc.c (provides USB device descriptors)
    • usbd_cdc_if.c (provides application read/write functions)

More about file hierarchy can be found in STM32Cube USB device library user manual.

Files to modify CDC to our custom needs are:

  1. USB VID/PID in usbd_desc.c 
  2. USB configuration defines in usbd_conf.h
    This is set with CubeMX so don’t change this defines.
  3. USB struct handle in usbd_device.c 
  4. Descriptors in usbd_cdc.c
    You don’t really need to change anything for now, but there are many descriptor fields that can be modified.
    As it is stated at the top of the .c file, generated CDC contains 2 data endpoints (IN and OUT) and 1 control endpoint. Endpoints could be seen as buffers inside devices, that have a specific purpose. 

    Note:
    Endpoint naming is host-based; meaning, on our STM32, IN endpoint actually means data direction from uC to PC (PC IN endpoint). usbd_cdc.h contains CDC packet/endpoint sizes

If we build&upload the code, Device Manager shows “USB Serial Device (COMx)” under “Ports (COM & LPT)”. More about drivers below.

To test our USB communication, we will do a simple echo device. Everything that comes into device over USB CDC (from PC) is echoed back to PC.


Receive data

There is a function (generated by CubeMX) at the beginning of peripherals initialization:

MX_USB_DEVICE_Init();

and in there:

USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);

While “hUsbDeviceFS” is our struct of all USB device data, “USBD_Interface_fops_FS” is struct of callback pointers to functions (Init, DeInit, Control, Receive), in our case:

USBD_CDC_ItfTypeDef USBD_Interface_fops_FS ={
 CDC_Init_FS,
 CDC_DeInit_FS,
 CDC_Control_FS,
 CDC_Receive_FS
};

Which means that “CDC_Receive_FS()” is our “packet received” callback and is called automatically from USB stack, containing pointer to buffer with received data and length of received data.
“CDC_Receive_FS()” can be found in “usbd_cdc_if.c”

Send data

In the same file we can also find “CDC_Transmit_FS()” (which further calls “USBD_CDC_SetTxBuffer()” and “USBD_CDC_TransmitPacket()” that actually sends data over endpoint, but check USB state before transmitting).


So, in order to echo back all data sent from host (PC), we just need to add this line to our “CDC_Receive_FS()”:

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len){
/* USER CODE BEGIN 6 */
CDC_Transmit_FS(Buf, *Len); // ADD THIS LINE to echo back all incoming data
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
 
return (USBD_OK);
/* USER CODE END 6 */
}

Note: This can work only if endpoint sizes are equal/large enough.

To test it, open serial terminal, send some data and see if it is received back.
Since this data is not send directly to UART like USB-UART converter, baud rate is actually not important, which is great, one less concern.

This is basically it. Everything on top of that is just a more fancy/controlled/reliable version of this. 

Here is a .zip of this example project in Keil.

Note: There is an official example in CubeMX firmware library under
“Projects\STM32 xxx \Applications\USB_Device\CDC_Standalone”,
which is USB to UART converter that can also set/get UART parameters (like baudrate, parity, …) over CDC control endpoint.


Note about the drivers

After upload, open any serial terminal (like Putty) and find your serial device in Device Manager under “Ports (COM & LPT)” named “USB Serial Device (COMx)”. That is, if windows can find a suitable driver for your device.

STM32 USB CDC drivers device recognition

Windows already provide standard (native) drivers for CDC devices  – “Usbser.sys”, but is automatically loaded only above Win 7.
In order to be able to communicate with STM32 USB CDC devices on Win != 10, you would need ST VCP drivers: http://www.st.com/en/development-tools/stsw-stm32102.html

Note: VID/PID and drivers and OS device recognition. Even more about how does USB recognition work can be found here.


Upgrade

Now, since we have our basic USB communication without additional USB-UART/whatever converter chip, we can implement it in some protocol. Complex or simple like our SDP. Get the idea 🙂