CDC, Continued

In our previous episode, we’ve spent an unhealthy amount of time in front of a computer crafting some arcane USB code. Today, we finish what we started and finally get our STM32 to talk with a USB host.

1.1.        Application Integration

So we’ve customized ST’s USB CDC middleware… but how do you deploy it in your own STM32 application ?

That’s where all our efforts finally pay off. Integration is easy :

First, we need to safeguard our modified template source files : it would be a shame if we were to modify or STM32CubeIDE project, regenerate the code, and find out that our work has been deleted or overwritten. Luckily, there’s an easy fix :

  •         Move usbd_cdc_if.h from USB_DEVICE/App to Core/Inc
  •         Move usbd_cdc_if.c from USB_DEVICE/App to Core/Src

Due to the way the #include directives and the project’s build process are setup, the project will compile exactly the same, but now Cube’s code generator won’t ever touch your modified files. It will, however, generate new “blank” usbd_cdc_if files under USB_DEVICE/App that you will need to delete. If you don’t, you’ll get duplicate declaration errors when you try to compile.

Second, we need to make sure the vcp_service function gets called automatically and at regular intervals. As it happens, ARM Cortex-M cores feature a dedicated SysTick (System Tick) timer. This is not one of the general-purpose and very useful timers that are part of the STM32’s internal peripherals : it is a less precise unit that just lets you keep track of the passage of time. By default, SysTick triggers an interrupt once per millisecond, which is frequent enough to service our USB VCP transmissions.

Open your project’s interrupt handlers source file. It’s under Core/Src and its name looks like stm32yyxx_it.c where “yy” denotes the family your STM32 belongs to. For example it would be stm32h1xx.c for an STM32H103. Scroll down this file until your find the SysTick handler, named SysTick_Handler. Inside it, add a call to vcp_service :

void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
  // USB VCP library : background processing of data transmission
  vcp_service ();
  /* USER CODE END SysTick_IRQn 1 */
}

You will also need to #include “usbd_cdc_if.h” for the compiler to find the function.

Finally, it’s time to edit main.c :

All you will need to do is #include “usbd_cdc_if.h”. That’s because the code generator took care of everything else for you, including the initialization of the USB peripheral. Your CDC_Init_FS function will be called as part of the middleware’s own initialization.

At long last, you’re ready to write code that can talk to your PC through USB. We’ll look at how it’s done using a very simple example : a serial echo application.

1.2.        Demonstration : Serial Echo

We’ve written a lot of code, now it’s time to check if it all works or if I just wasted your time. Spoiler alert : I haven’t.

The simplest test of an RS232 port (or UART, or COM port) is the echo. Take every byte sent to the STM32 and send it back where it came from. This single test verifies that both reception and transmission work.

In your main function’s infinite loop you just need to add :

  uint8_t buf[1000];
  while (1)
  {
    	/* USER CODE END WHILE */

    	/* USER CODE BEGIN 3 */
// VCP demonstration - Echo all data received over VCP back to the host
	  int len = vcp_recv (buf, 1000);	// Read up to 1000 bytes
	  if (len > 0)		// If some data was read, send it back :
		  len = vcp_send (buf, len);
  }
  /* USER CODE END 3 */

Build and Flash your project to your STM32, then connect its USB port to your PC.

We have checked before that your STM32 was detected as a COM port, but it doesn’t hurt to check again. Besides, we’re going to need its COM port number (or tty device name, if you’re using Linux).

Next, we need software on our PC to talk to a serial device. We’ll use a simple terminal emulation program. I recommend Tera Term : it’s simple, no-frills, and unlike PuTTY it actually respects the baud rate you set. Also, unlike PuTTY, it doesn’t lose its marbles when its serial connection is lost. That’s very important if you’re going to play with VCP : any time you recompile and Flash, PuTTY will force you to click a dialog box, close it and restart it. Tera Term will just sit there and wait for your STM32 to return to active duty.

You can download Tera Term for free from many different places, I’ll let you Google it.

Once you’ve installed it, and made sure your STM32 is connected to a USB port, launch it. You should be greeted with this dialog :

Make sure you pick the right COM port and click OK. You should now be staring at a black window with a blinking cursor. Unless you’re stuck in the Matrix, don’t expect to suddenly get this :

If you do, don’t worry. Just give me five minutes to get into my black S&M leather costume and find my sunglasses and I’ll be right with you with a bunch of pills so we can discuss USB and the nature of reality.

But seriously… back to work :

By default, terminal programs do not echo your keystrokes : that feature, known as “local echo”, interferes with any real-world application of serial communications. What you type goes out the COM port, and only what comes in the COM port will be displayed. Or STM32 running the echo code will, in effect, take your keystrokes and send them back so the terminal program can display them.

Don’t be shy, type a few things on the keyboard. If they end-up on the display, congratulations, you’ve got a working virtual COM port STM32 project. And the knowledge to add this feature to any or your future projects.

If you don’t get an echo, well, this has been a very long process. Maybe you missed a step. Maybe you fell asleep at the keyboard just like Neo. Drink some water, remind your significant other(s) that you’re still alive, and then take it from the top.

1.3.        Fun With Testing

So. You’ve got some code that lets your STM32 echo keystrokes. But unless you’ve been bitten by a radioactive secretary you just know you’re not going to stress-test your STM32’s VCP just by typing. You may want to test sending a lot of data in one go and then verify that it all went through and back without errors.

The simplest way to do that is to send some ASCII art down the pipe. Because it’s an image (of sorts) you will be able to easily spot missing bytes from image distortion even as you send thousands of bytes in a row.

You can use this website : https://www.ascii-art-generator.org/ to turn any picture into ASCII art that you can then copy and paste into Tera Term. This will send the whole thing to your STM32 and display what the microcontroller received and sent back. I’m sexy so I usually use my own portrait for this :

If this works, it means both your circular FIFO’s work as they should. If it doesn’t, maybe your baud rate is too high, or maybe your Tera Term has a problem handling carriage returns (depends on your operating system) or maybe one or both of your FIFO’s doesn’t work quite right and you need to look at your code again. Too many possibilities for me to go through, but at this point you have all the knowledge you need to debug your way out.

1.4.        A Quick Summary

Just to recap everything so far and get a birds-eye view of what’s going on :

First, you’ve configured our STM32 in STM32CubeIDE to enable its USB Full Speed device peripheral, and enabled the Communication Device Class (CDC) middleware. You’ve generated the code for your project, thereby adding all sorts of source files (some of them templates) to your project.

Because the CDC middleware is very generic, you’ve customized it to add support of circular FIFO’s. You’ve also written functions to synchronize data transfer to (vcp_service) and from (CDC_Receive_FS) a USB host, using those FIFO’s. Those functions are essentially interrupt handlers and work in the background of your application, to keep CPU usage as low as possible.

You’ve also added functions to send and receive data to and from the host at the application level. Those functions merely access your circular FIFO’s and do not talk directly over USB, since only the host can do that.

Having built and flashed your project to your STM32 board, you have verified that you can actually connect to your STM32 through a virtual COM port.

This was quite a lot of work, so I think I’ll stop here for now… but there is still a lot to explore on the topic of ST’s CDC middleware. Expect another episode in the future.