UART are neat : it’s very easy to connect to a UART. But you know what’s become even easier since the nineties ? Connecting a USB cable between your PC and your target.
If you’ve been using a NUCLEO so far, you might be asking yourself : “but wait, haven’t I been using USB until now ?”
You’ve actually been using a UART. The ST-LINK probe on your NUCLEO acts as a very convenient USB-to-UART bridge. What I intend to show you now is how to use USB natively on your STM32 as the communication channel between STM Shell and your terminal.
It goes without saying (but I’ll say it anyway) : for this particular trick your target STM32 needs to include a USB device controller peripheral. And the board it’s soldered on, quite obviously, needs a USB connector and the electronics that go with it. The STM32F303 I’ve been using as an example until now does not have USB. You’ll need a different target, so it’s time for a…
1. Buyer’s Guide
Unless you have a specific target in mind, perhaps your own board design, I’m going to assume you’re just here to learn and you do not want to overspend on tools. So I can suggest two types of cheap targets :
First, some larger NUCLEO boards carry a USB-capable STM32 and provide a dedicated USB connector that is separate from the ST-LINK USB connector. If you see a NUCLEO with more than one Micro-USB port, that’s what I’m talking about :
On this NUCLEO-144 you can see the ST-LINK USB connector on the left. The second USB connector, on the right and below the Ethernet RJ-45, is connected to the STM32 itself.
Note that on many boards, that USB connector will support OTG (USB “On The Go”) meaning the connector is a Micro-AB and the electronics support operation as both USB host and USB device. You’ll need to study the board’s schematic to make sure you’re using it correctly, but that’s beyond the scope of this page. Perhaps I’ll write about it in the USB section of this site.
On the plus side, using a NUCLEO means you have complete support in STM32CubeIDE. However, those larger NUCLEO cost around 30 Euros.
The second option is much cheaper (around 3 Euros). It’s the “Blue Pill” :
This is a minimalist STM32F103 module that carries a USB connector. There are several variants of this design, with different connectors (Mini or Micro) but the one thing they have in common is their lack of on-board ST-LINK probe. You need to provide your own. Any NUCLEO (except the smallest ones) can be used as a probe for these modules. If you look again at the photo of the large NUCLEO, you’ll see its probe section has a header for an external target.
Those Blue Pills are easy to understand and work with, they don’t have the thousand solder bridges you’d find on any NUCLEO and they do provide more I/O than the NUCLEO-F303K8 we’ve been using so far. However…
BUYER BEWARE : Blue Pills are made in China, which also counterfeits STM32 microcontrollers.
There’s no sugar-coating this, we’re engineers and not diplomats : China encourages the theft of intellectual property either directly or through government inaction. As a result, and because the STM32F103 is one of ST’s cheapest and most popular products, it has been faked and copied to a large extent.
This has been covered by various websites, blogs and forums so I won’t repeat everything. Rather I encourage you to read this Hackaday article :
https://hackaday.com/2020/10/22/stm32-clones-the-good-the-bad-and-the-ugly/
And this page provides tools and photos to help you identify a fake STM32F103 :
https://github.com/keirf/Greaseweazle/wiki/STM32-Fakes
My view is that those modules are so cheap you should buy yours from at least two different vendors to mitigate the risk of getting sent a fake. They can also be bought from sites like Amazon, where you can get a refund if necessary.
Let’s be clear on one thing : I do not condone buying knock-offs. I do not recommend buying knock-offs. This page will focus on implementing SMT Shell on the Blue Pill only because this module is extremely popular and statistically there’s a high probability you already own some. If that weren’t the case, I’d focus only on NUCLEO.
If you intend to use a NUCLEO, I have a page dedicated to running STM Shell on STM32H7 NUCLEO. The more powerful STM32 introduce some complexities you need to learn about, and learn how to work with (or around). As for the rest of this page, it focuses on Blue Pill but could still be a useful read.
2. The Gist
You’re going to configure and program your STM32’s USB device controller to act as a virtual COM port or VCP and use that interface with STM Shell. Thus, you’ll still be able to use the same terminal emulator (such as PuTTY) but with two advantages :
- The higher speed of USB compared to a UART
- Sparing a UART for something more important
Think of this as squeezing value out of a peripheral you might not have used otherwise.
I’ve already written extensively about VCP on STM32, because it’s not a trivial subject. Long story short, I have designed a library for that, and you will use it in synergy with STM Shell. Once again, you’ll be writing so little code you’ll start to wonder if it’s possible to develop software without coding.
3. Project Creation
Assuming you’re not looking to add USB shell support to one of your existing projects, you’ll want to create a new project. The Blue Pill isn’t an ST board, so you won’t find it in CubeIDE’s “new project” wizard. You’ll need to target its microcontroller, which is the STM32F103C8T6.
When you open your new .ioc file, you’ll be greeted with a completely blank pinout diagram. Filling it in requires reading the Blue Pill’s schematic. You’ll find all the necessary information on this very useful website :
https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill.html
To use STM Shell over USB you’ll need to configure the following :
- The clock source
- The SWD interface
- The USB device controller peripheral
- Optionally, the GPIO that drives the module’s user LED
Let’s go over each.
Under “System Core”, “RCC”, you can setup the external clock sources of your STM32. The high-speed oscillator is the main clock source. While there’s also an internal RC oscillator, it is not precise enough to meet USB requirements. Side note : some STM32 do have a dedicated 48 MHz RC oscillator tailor-made for USB.
Choose “Crystal/Ceramic Resonator” because the Blue Pill comes with an 8 MHz crystal.
The debug interface is configured in the “SYS” block, just under RCC.
Select “Serial Wire” and while you’re here, set the Timebase Source to “SysTick”. This generates a 1 KHz interrupt that is used (notably) by the HAL_Delay delay function. On some chips it’s set by default but for some reason that’s not the case here.
Search for pin PC13 in the pinout view (it’s in the upper left corner) and configure it as a GPIO Output. Then go to “GPIO”, again under “System Core”. Find the PC13 entry, click on it, and set its user label to “LED” (the name used by the STM Shell demo “shell_pfs.c”). Also, set the pin to “open drain” as, on the Blue Pill, this pin connects to the LED’s cathode.
You’ll find the USB device controller under “Connectivity”, “USB”. There’s not a lot to do here :
The STM32F103 can only operate as a Full-Speed (12 Mb/s) USB device.
You also need to enable the USB middleware required for Virtual COM Port operation. That’s under “Middleware and Software Packs”, “USB_DEVICE” :
Leave all other parameters to their default values.
Finally, setup the clock tree in the “Clock Configuration” tab :
You’ll need to set the input frequency to 8 MHz, the PLL Source Mux to “HSE”, increase the PLL multiplier to 9 to generate 72 MHz. Note the “USB Prescaler” : this should be set to 1.5 to produce 48 MHz.
(In case you’re new to USB, your typical Full-Speed USB controller runs on 48 MHz which is 4 times the bit rate on the bus. That means your PLL must output 48 or 72 MHz if you intend to use USB on an STM32F103)
At this point, you can save and generate the code for your project. Unless, of course, you want to setup other peripherals specific to your application.
4. Clone the Libraries
You need to clone two of my libraries into your project. Follow the links for detailed instructions :
If this is your first time, I suggest first cloning and testing the VCP library in isolation, then once you know it’s working you can tie it to STM Shell.
5. The Plumbing
My VCP library is quite different from a UART. That means you need to get creative when coding the STM Shell portability layer.
As a reminder, STM Shell expects two functions from you :
- A buffer transmission function, that should ideally use DMA to avoid blocking the execution of your application.
- A single-byte read function, that should ideally use interrupts to keep the shell idle (and your application running) until the user hits a key on their terminal.
My VCP library doesn’t provide interrupts and the STM32 USB doesn’t support DMA, it has its own FIFO mechanisms. So how do we reconciliate both libraries ?
Well it’s quite easy, actually. Even easier than using a UART ! How many times do I have to tell you I’m lazy ? I wouldn’t write libraries that are difficult to use, trust me.
Let’s start with transmission. When using a UART, you need to override two functions :
- shell_out, from STM Shell, to start a DMA transfer and flag the UART as busy.
- HAL_UART_TxCpltCallback, from the HAL, to release the busy flag upon transfer completion.
Therefore, quite naturally, you should write :
void shell_out (char *buff, int length) { vcp_send ((uint8_t*) buff, length); }
Under the hood, vcp_send will copy “buff” to its own transmit FIFO and return immediately. You may recall that a USB connection is entirely under the control of the host, and our STM32 is a device. That means the data sent with vcp_sent isn’t actually “sent”, it’s just buffered on the STM32 until the host reads it. That part of the process is out of your hands.
From your perspective, sending data with vcp_send is immediate. That means you don’t need to set the shell’s busy flag. And if you don’t set that flag, you don’t need to clear it either, which means there’s no “transfer complete” callback to override.
Things are equally simple on the reception side. When using a UART, you need to override two functions :
- shell_get_byte, from STM Shell, to read one byte using an interrupt.
- HAL_UART_RxCpltCallback, from the HAL, that fires when the byte arrives.
I haven’t implemented an interrupt-based API for my VCP library, but you don’t need one. Keep in mind that STM Shell is a state machine, and shell_get_byte is called during the input state (and also during the idle state). So it will get called repeatedly until the state machine transitions to the command line parser. And that means you can write this :
void shell_get_byte (char *c) { int rv = vcp_recv ((uint8_t*) c, 1); if (rv == 1) shell_in (shell_state.c); // Feed the byte to the shell }
vcp_recv reads the specified number of bytes (here, just one) from the VCP library’s internal receive FIFO and returns the number of bytes read. When the host sends the user’s latest keystroke, it shall return 1. We can test that to process the keystroke. This also works if the FIFO contains multiple bytes, of course (we’re only asking for one).
If the FIFO is empty, nothing happens. STM Shell will just keep calling this function.
Again, no need to override a callback.
And that’s it, you now have a really cheap microcontroller module that carries a shell you can access through USB.
6. What’s Next
At the start of this page I mentioned using a large NUCLEO instead of a Blue Pill. Since that can be more complex, and perhaps more intimidating, I’m going to show you how to do it on a particularly “nasty” model, the NUCLEO-H743ZI.