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 🙂

19 Comments

  1. Pingback: STM32 And Custom USB HID Device? Yes Please! | Damogran Labs

  2. Thanks for sharing, good explanation and easy way to get started with USB.

  3. Hi,
    Thanks for sharing.
    I have a question related to STM32 USB CDC behaviour when compared to CP210x.
    In linux machine STM enumberated as ‘/dev/ttyACM*’ where as CP210x as ‘/dev/ttyUSB*’. I believe drivers used are different on linux kernel. Is there any way to match STM32’s USB driver to enumerate as ‘/dev/ttyUSB’ i.e. same as CP210x?

  4. Hi Domen,
    I use STM32F103RBT. What I understand is that I should generate the file ‘usbd_conf.h’ with Cube MX for STM32F103RBT to use with STM32 USB CDC lib.
    I don’t know how to use Cube MX. Could you help me?

  5. Hello, stupid question here.
    Is the STM32 can be possibly to b the host and able to connect to MCP2200?

  6. Hi there, i’m trying to implement the stm32 blue pill board as a slave on a PCB that gets controller by the user via USB, the problem is that when i plug in the board (USB (pc) to microUSB(uC)) it’s just a power supply, no com port is detected or anything, can u help?

    • Hello. If there is absolutely no reaction once device is connected to PC, I would assume one of the following issues is present:
      – you have a wrong cable (I once used some cheap power supply usb cables without data wires) or other hardware issue.
      – your device configuration (code) is invalid or CPU don’t even do initialization due some error
      – one of other 341532 possible issues 😀
      I would first try to search for any visible hardware issues, than I would program the most basic (default) USB example (CubeMX HID device) and try to get this working. Than, when you are sure that the board is OK, move on with CDC.

      p.s.: Do yourself a favor and while asking for help, describe as much details as you possibly can. What do you wish to accomplish, what have you already tried and what are the results of that, what have you changed in a given example, what did you discover when debugging code, … It is nearly impossible to help with no info.

  7. hi, i have one question.
    How can you combine USB CDC files which are created with CubeMX and other program written in sw4stm32 or Atollic TrueStudio with standard peripheral library?

    • I wouldn’t do that. Standard peripheral libraries is already outdated, and as far as I know, STM32 does not develop it anymore. Not the case with STM8, but for STM32, stick to either old StdPeripheral library or port to HAL/LowLayer.

      In case of CubeMX generated USB files, they are based on HAL, so it would be really hard to port this files to StdPeripheral lib. Maybe you could include required HAL libraries in the project, but this would take time – I wouldn’t go this way.

      You would probably re-write/port whole application to HAL/LL much faster than re-implement USB to use StdPeripheral lib. This would also alow you to regenerate project with CubeMX.

  8. If you find you can’t debug your USB:

    I’m working on a Blue Pill (stm32f103c) CDC, using the new StmCubeIDE. I couldn’t debug USB until I pulsed the D+ USB line (A12 on the Blue Pill) low with a 100 ohm resistor. This causes the link to re-register.

    Later I defined line A10 (portA pin 10) as open-drain, and pulse it low for 100ms. before the main program loop. I found some boards (Nucleo) have a transistor to do this.

  9. What’s happening if I can see it in the Device Manager but can’t open it?
    I tried adding your line to a STM32F303CTB’s CubeMX program; when I plug it in, it enumerates and shows up as a com port (Com8). It doesn’t show up in Teraterm, and if I try to open it, I get “Cannot be opened.”

    It shows up in WMI… but can’t be used.
    Also… is there a real need for CRC’s and such over USB, or is that adequately handled at a lower level (in the USB communication stack)?

  10. “When I plug it in, it enumerates and shows up as a com port (Com8). It doesn’t show up in Teraterm, and if I try to open it, I get “Cannot be opened.”

    Are you sure you have only one connection to this port (If you connect with some other software/scripts to this port, port might not be accessible, although it is listed). Another question is: is your STM responsive (can CPU handle USB stack and data, or is in your while (1) loop or some hardfault handler?

    Also… is there a real need for CRC’s and such over USB, or is that adequately handled at a lower level (in the USB communication stack)?

    The most honest answer I can give: I don’t know. But, first google look up lead me to a interesting forum topic:
    https://electronics.stackexchange.com/questions/21720/rs232-vs-usb-cdc-quality-of-service-should-messages-contain-a-checksum

    • That’s cool!
      I found the issue; evidently my “init code” (check ADC voltages, etc) was “too busy” and it wasn’t initializing correctly (too much of a gap between “plugging in” and “MX_USB_DEVICE_Init”, so the PC was getting confused).

      Pulling DM & DP low for 1sec before the MX_USB_DEVICE_Init() in main forced the computer to do an “unplug and replug” event and now it’s working well. Onwards!

      Now I’m really liking the USB… I’m not sure I’ll ever go back to normal serial:
      – Baud rate? Don’t care, but if I did, the STM32 can read what the PC wanted.
      – Which com port? Look at the USB info and find the “ThisPortIsMine” and I know which com port to open, no matter what the PC enumerates it as.
      – 2 Pins… and I get CD/RTS/CTS and everything else… and I can have multiple devices if I wanted to (so HID to send some control signals back and forth and…); no need to “decode and handle” line breaks (for a forced reset), etc.

  11. After doing a Reset in STM32F103, I verified a successful SW Reset. However, I lose the connection with the USB Host PC. I want to be able to reestablish the connection without removing/replacing the USB cables. Can you explain why and suggest any sollution?

  12. It resets the traffic (which disconnect windows), but it doesn’t do the “replug” event to cause the windows box to restart the connection. I fixed it by making the port GPIO and holding DM&DP “low” for 500ms before calling the MX_USB_DEVICE_Init() function.

    It means that every time you restart the device, it “unplugs and replugs”, but it does work. You need to let the device run without breakpoints for a couple of seconds after the USB_DEVICE_Init() so it will finalize the connection (you can put breakpoints in, but don’t stop the CPU for a little bit).

Leave a Reply