Making a basic USB CDC project for STM32F4Discovery

This tutorial shows how to create a basic USB communication device using the STM32F4Discovery board. The USB protocol stack implementation is contained in the STM32CubeF4 firmware packageand is included in our latest STM32 package.

Before you begin, install VisualGDB 5.0 or later and get the latest version of the STM32 BSP via Embedded Tools Manager in Visual Studio Tools menu.

  1. Start Visual Studio, open VisualGDB Embedded Project Wizard:01-usbprj
  2. The first page of the wizard allows selecting the project type. Proceed with the default settings:02-binary
  3. Select your STM32 device on the second page of the wizard:03-stm32f407
  4. Specify the PLL configuration for your board. You can find the values by running the STM32CubeMX code generator or looking up the ST samples. You can modify the values later as well.04-sample
  5.  Select OpenOCD on the debug method page. Connect your
    board and press ‘Detect’ to have your ST-Link interface
    detected. Press ‘Test settings’ to verify the connection to
    the board:05-openocd
  6. Press “Finish” to create the project. Build it by pressing Ctrl-Shift-B: 06-buildIf you want to modify the PLL values now, simply edit the SystemClock_Config() function.
  7. Connect your board using both USB sockets. Note that the mini-USB socket is needed for JTAG debugging and the micro-USB socket is connected to the STM32F407 microcontroller itself:07-usb
  8. Press Ctrl-F5 to program the device without debugging it. The Device Manager should now show 2 instances of the port. Each instance corresponds to one of 2 interfaces reported by the ST library (see the definition of USBD_CDC_CfgFSDesc):08-port
  9. Although the ST USB library implements the standard USB CDC class and Windows has drivers for it, it will not recognize the device without an inf file specifying which driver to use. Download and install the STM32 VCP drivers to get Windows to recognize your device. If the device is still not recognized, try explicitly installing the ST driver for the “USB composite device” that is the parent for both virtual ComPort devices in the “Devices by connection” mode:drv
  10. Take a note of the COM port number assigned to the device:09-com4
  11. Now we will test our firmware. Go to VisualGDB Project Properties and enable the raw terminal on the device COM port. Set the mode to ‘text only and enable character echoing:10-terminal
  12. Build your project and press F5 to start debugging. Open the raw terminal and type something there. See how the ‘you typed’ lines are sent by the device. You can also set breakpoints, step and inspect variable values like when debugging normal programs:11-echo
  13. Now we will use the Code Map feature to quickly visualize the functions involved in receiving the data over USB. Right-click on VCP_read() and select “Show on Code Map”:12-codemap
  14. Right-click on VCP_Read in Code Map and select “Show called functions”, then right-click on USB_CDC_ReceivePacket and select “Show referenced data”. You will see that a field called RxBuffer that is involved in receiving the data via USB:13-rxbuf
  15. Right-click on RxBuffer and select “show functions referencing this”. This will reveal the USBD_CDC_DataOut function:14-dataout
  16. Double-click on USBD_CDC_DataOut and VisualGDB will open the function in the code. Right-click and select “Find all references” to find that the function is specified as a field in the USBD_ClassTypedef structure:15-USBD_CDC
  17. Navigate to the structure definition and add the DataOut field to CodeMap:16-field
  18. Use the “Show functions referencing this” and “Show functions calling this” to reveal the path from the interrupt handler to the DataOut field:17-datapathIf the USB functionality is not working, the data path could help pinpoint the problem. E.g. if VCP_read() never returns, the first thing would be to check if USB_LL_DataOutStage is ever called. If no, setting breakpoints in the interrupt handler and other functions on that path would help figuring out why the control never reaches USBD_LL_DataOutStage.
  19. Finally we will demonstrate how to use the USB CDC interface with the standard library functions like printf(). Replace the loop in main() with the following:
    while (!g_VCPInitialized) {} 
    
    for (;;)
    {
       int val = 0;
        printf("Enter a number: ");
        scanf("%d", &val);
        printf("%d = 0x%x\r\n", val, val);
    }
  20. If you try to build your project now, the default implementations of the I/O functions will use the debug-only semihosting interface to handle functions like printf(). We can override this behavior by providing our own implementations for the functions below:
    #include<sys/stat.h>
    
    extern"C"
    {
       int _fstat (int fd, struct stat *pStat)
        {
            pStat->st_mode = S_IFCHR;
           return 0;
        }
    
       int _close(int)
        {
           return -1;
        }
    
       int _write (int fd, char *pBuffer, int size)
        {
           return VCP_write(pBuffer, size);
        }
    
       int _isatty (int fd)
        {
           return 1;
        }
    
       int _lseek(int, int, int)
        {
           return -1;
        }
    
       int _read (int fd, char *pBuffer, int size)
        {
           for (;;)
            {
               int done = VCP_read(pBuffer, size);
               if (done)
                   return done;
            }
        }
    }
  21. Now build your project and press F5 to start debugging. Note that the first ‘enter a number’ line will be lost as it’s printed before VisualGDB connects to the COM port. Type in some numbers and observe the output:18-output
  22. Congratulations! If you see the output as in this example, you have created a fully functional USB device. Note how the FLASH footprint of the firmware has increased significantly after we started using printf(). Use the Embedded Memory Explorer to find which of the functions occupy most of the memory:19-flash
  23. You can reconfigure various frameworks used by your project (such as USB stack) via the Embedded Frameworks page of VisualGDB Project Properties:20-frameworks