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 🙂

37 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).

  13. Hi,

    Re:Unable to Transmit the Data (USB CDC ) to PC (STM32L476RG Eval Board).

    I am trying to transmit the data(USB CDC) over PC but I didn’t see any message printing on Serial Putty Console. Do I need to change any configuration settings. May I know , why transmission was not happening.. I am trying to dig into deep. so could you anyone try to help me out to overcome this Problem.I also followed your suggestion Loop back test. I tried but No Data Transmit was happened.

    • Hello Lakshmikanth. For anyone to help you, you would need to make a little effort and explain:
      – What does work? Where does your code stuck (did you try to debug it?)? Does it transmit anything or is ti completely dead?
      – Can you receive anything from some other board/device?
      – …
      If you need help, we are happy to help you – but we won’t guess what is going on or what you did or didn’t do.
      Learn how to ask for help and how to write an issue report. https://www.tomasvotruba.cz/blog/2019/07/08/7-tips-to-write-flawless-issue-report-on-github/

      • Hi Domen,

        Thank you for your quick response.

        1) What are the steps I have been followed ?

        As per below link I followed the steps

        https://st–c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006HxsN&d=%2Fa%2F0X0000000b2Y%2FTPX_NFqVFfweAISP1YO3PDDPHXqpPClIHVYTgaJcmEY&asPdf=false%27

        https://community.st.com/s/question/0D50X0000BI0cxESQR/unable-to-transmit-the-data-usb-cdc-to-pc-stm32l476rg-eval-board

        Steps:
        I generated code in CubeMx as per above link.
        I have added little bit code to Transmit the data to PC as mention below.
        – Enable LED pin (GPIO_SET)
        – Transmit The data (CDC_Transmit Function) CDC_Transmit_FS(UserTxBuffer,7);
        – Disable the LED pin(GPIO_RESET)

        2) What does work?

        Till Now I haven’t seen any Data Transmit(From Board STM32L476RG) to PC (Putty)

        3) Where does your code stuck (did you try to debug it?)?

        1) I tried to put in debug mode (In CubeMX enable SW in Debug options).
        2) I can able to see in the log ,it’s enter into CDC_Transmit Function and displayed as shown in below syntax.
        3) Debug_write(“Message Transmitted”,(uint16_t)20);
        i.e Message Trasmitted was displayed on Serial wire viewer on both STLINK Utiity/Atollic IDE (SWO output)
        4) I enabled USBD_DEBUG_LEVEL 3.Find the Below log for reference.

        [2.160] read(): Rx: $vFlashWrite:80051b0:\001\004\002\010\000\020\020\020\004\020\020\020\002\020\020\020\010ERROR: \000Invalid Device handle\000Invalid Class handle\000USBD SWO test1\000DEBUG : \000USBD SWO test2\000End\000\000\000\000\000\000\000\000\000\001\002\003\004\006\007\010\011\000\000\000\000\001\002\003\004\000\377\377\001\000@\015\003\000\377\032\006\000\0005\014\000@B\017\000\377\377\036\000\000\011=\000\000\022z\000\000}\004\377\000\0006n\001\000H\377\001\000l\377\002Message received\012\000CDC Receive\000Message Transmitted\000CDC Transmit\000Message:Buf\000\000\002\001\002CDC Config\000CDC Interface\000STMicroelectronics\000STM32 Virtual ComPort\000\000\000\000\377\001\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000…
        .
        [3.469] spawnCubeProgrammer(): 19:04:05:899 STLinkUSBDriver.dll loaded

        5) I have seen in the Log (Invalid Device/Class).. I noticed the it’s unable to find the Device/class.

        4) Does it transmit anything or is it completely dead?

        I could see any data transmission on PC (Putty)

        – Can you receive anything from some other board/device?
        I couldn’t receiving any data from board/device.

  14. Hi Domen,

    Thank you for response and giving some good instructions.
    Please find the below link for reference which has mention cleared.

    https://github.com/lakshmikanth36/STM32-USB-CDC-Data-Transmission-/blob/master/steps.txt

    • Hi Domen,

      Actually, I tried to debug the USB CDC Communication.
      The Function CDC_Transmit_FS() => always return USBD_BUSY state.
      Need to Find the overcome of USBD_BUSY state.could you please help me out of this problem. I am looking forward for your reply

      • Well, I must respond in the same way as with first comment – if you need help, make an effort and describe what is going on in details, what you already tried, …
        I’m sorry, I didn’t notice your previous comment was sent to spam.

        I generated code in CubeMx as per above link.

        I can’t offer help for samples that are not part of this blog post. It is hard to advise for a specific problems that are not this project specific. I can only offer (if at all) generic answers, which you could probably google faster than you read this :).

        2) What does work?

        First, try to achieve that your board is registered by PC. Do you have correct drivers? In order to send/receive data, you must be sure your device is properly enumerated by PC OS.

        I tried to put in debug mode (In CubeMX enable SW in Debug options

        Logging is good. BUT, make sure your extensive logging does not cause delays – you should not block CPU to periodicaly handle USB stack.

        5) I have seen in the Log (Invalid Device/Class).. I noticed the it’s unable to find the Device/class.

        This is your homework. 🙂 Research what could go wrong when device needs to be enumerated by PC. You must see it in device manager, as it is displayed in this blog post.

  15. Hi Domen,

    Thank you for your prompt response.

    Hope you did’nt notice the Previous comment which was I mention that the Function CDC_Transmit_FS() => always return USBD_BUSY state.
    That why Data Transmission was not happening..

    https://github.com/lakshmikanth36/STM32-USB-CDC-Data-Transmission-/blob/master/usb_log_capture.txt
    https://github.com/lakshmikanth36/STM32-USB-CDC-Data-Transmission-/blob/master/CDC_Return_USBD_BUSY.txt

  16. Hi Domen,

    The issue is resolved after connecting with new USB daughter board along with USB Type A Cable. Actually I have seen below reference readme.txt of different controller Board from that I got to know.

    STM32Cube_FW_L4_V1.14.0\Projects\NUCLEO-L452RE\Applications\USB_Device\CDC_Standalone\readme.txt

    Thank you for your support.

  17. Hi Domen,
    In my project, I convert STM32 USB CDC to WINUSB Device with HAL library and now I want to use virtual com port too. But the problem is in a moment just an application running, so can I edit some points in descriptor or something to choose which application I wanna use?
    Thanks!

  18. Hi Domen,
    I have a problem with my own implementation of USB CDC device, so I find your example code and ran it, but get the same error I had before.
    The problem is that my USB device is not recognized by my PC (“Unknow Device” error 43), I tested in WXP, W7 and W10 PCs.
    I think I have a kind of HW problem here because I have tested the SW a LOT without results, I am using a STM32L100C-DISCO board and a USB cable.

    I have some HW related questions if you please could help me on this:
    1 – Did you connect STM32L100-DISCO PA11_DM and PA12_DP directly to USB cable – (green) and + (white) wires?
    2 – Did you use external pullpup resistor connected between DP and Vcc?
    3 – Did you connected +,- and GND USB cable wires only, no Vcc right?
    4 – I know PC driver is needed for USB enumeration, but it is needed for USB board description? I mean, even without a driver I should see the device description (“STM32 Virtual ComPort”) and no “Unknow Device” right?
    I will be very grateful if you could helm me on this.
    Thanks.

    • Hello Jose,
      1. Yes. Make sure you have a good shielded usb cable and that unshielded part of cable that connects to PCB is as short as possible (< 1cm).
      2. I belive I have. Read this and add pull-up:
      https://www.beyondlogic.org/usbnutshell/usb2.shtml
      I’ve even found a picture of test setup back then:
      https://i0.wp.com/damogranlabs.com//wp-content/gallery/damogran-labs-custom-usb-hid/IMAG0038.jpg?ssl=1
      3.Yes. Vcc is not needed since you power your board (probably) over onboard debug USB. Note that good GND is very important. Keep GND and data +/- connectors as close as possible and make sure the contact is good.
      4. No, I think you need the drivers in order to communicate with STM32 implementation of the USB.

      My advice: first make sure sort out all hardware settings and work with some basic default workspace (project).

      • Hi Domen,

        Thanks a lot.
        Could you please post a link to the windows driver you used to test your code?
        I just want to make sure I am testing your code using exactly the same environment you have.

        Regards.

  19. Since you already have your ST development board operationable, I assume you already have the necessary drivers.
    Do ‘official’ CubeMX generated examples work? If not, you probably have some HW issues. If they do work, did you change anything(!) in my example code?

  20. Hi Domen,
    I have been working with my STM32L100-DISCO board for some other apps without a problem, recently I did a driver code for an OLED display and also I coded an app for SDCard read and write, I am not an expert but have some experience with the board an CubeMX, but now I am trying to test a USB app and have an “Unknow Device (error 43)” error and can’t find the problem so far.
    That’s why I appreciate a lot your example code, because I could contrast my code with yours having the security that your code works.
    Regarding your question, I had to change your code clock setup, because I have not a crystal o resonator, I am using the 8MHz HSE clock provided in the board, and multiplying it for 12 and divided by 3, the result is a USB clock of 48MHz, so it should work this way.
    I also did a blinking led in my code (yours already have a button code) just to be sure that code is working, also coded a PA_11 and PA_12 port output toggle test app just to be sure both ports are working.
    I looked at PA_11 and PA_12 using an oscilloscope and looks like no data from the microcontroller is present, just the data from the PC, it is hard to be sure of that because both are present in the same lines but looks like if the board were not connected at all, I mean I saw the same data on the cable wires with and without the board.
    At this moment I asked my business partner to test another STM32L100C-DISCO board he has, using your example and/or mi test code to see if it works for him because I think the problem is either my board or something in my HW because I also have tested using some other PCs and windows.
    In all the USB code examples I have found or all the examples I saw in the web using the CubeMx the PC recognizes the USD device at once, nobody has complaint of “Unknow Device” error.

    • Hej Jose. The last part:
      In all the USB code examples I have found or all the examples I saw in the web using the CubeMx the PC recognizes the USD device at once, nobody has complaint of “Unknow Device” error.
      Start here. Create a bare minimum USB example with CubeMX and try to make it work. If you stuck there, try other board and other USB port on a PC.
      Make sure you don’t have any blocking code segments (delays or long running functions). The best is just to use some verified project, that is not mine. 🙂

Leave a Reply