February 27, 2025

Black Pill boards - USB experiments with Hydra - part 3, circa 2021

The plot thickens!

Back in my notes from December, 2020 I see these notes:

"when I fire this up on my STM32F411 board and my linux system sees it,
linux likes what it sees and hooks up a driver and calls this /dev/ttyACM0.
I can put together a little loop to send a hello message once a second,
run picocom on /dev/ttyACM0 and I see output.
This is a surprise. I didn't think this ever got this far. I wonder if we can repeat this. I hit the reset button on my F411 and in the linux log I see:
Feb 28 15:35:38 trona kernel: usb 4-1.1.1: new full-speed USB device number 39 using ehci-pci
Feb 28 15:35:38 trona kernel: usb 4-1.1.1: New USB device found, idVendor=0483, idProduct=5740, bcdDevice= 2.00
Feb 28 15:35:39 trona kernel: usb 4-1.1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
Feb 28 15:35:39 trona kernel: usb 4-1.1.1: Product: STM32 Virtual ComPort in FS Mode
Feb 28 15:35:39 trona kernel: usb 4-1.1.1: Manufacturer: ACME bar and grill
Feb 28 15:35:39 trona kernel: usb 4-1.1.1: SerialNumber: 00000000050C
Feb 28 15:35:39 trona kernel: cdc_acm 4-1.1.1:1.0: ttyACM0: USB ACM device
Wow! Well that certainly corresponds to my notes from 2020.

I hit the reset button again, then quickly type "picocom /dev/ttyACM0" and see:

5 hello
6 hello
7 hello
8 hello
This corresponds to code in main.c -- namely in usb_test_1() where I call the following in a loop:
usb_printf ( "%d hello\n", count );
So the code is not just enumerating, it is acting as a fully functional virtual COM port.

How this show starts

Let's look again at this call in usbF4/library/usbd_core.c
USBD_Init(&USB_OTG_dev, USB_OTG_FS_CORE_ID, &USR_desc, &USBD_CDC_cb, &USR_cb);
We have a hot clue in the "ACME bar and grill" manufacturer string. Without a doubt, this is something I hacked into place. We find this in usbF4/vcp/usbd_desc.c
// #define USBD_MANUFACTURER_STRING        (uint8_t*)"STMicroelectronics"
#define USBD_MANUFACTURER_STRING        (uint8_t*)"ACME bar and grill"
This ends up in this, which is referenced in the USBD_Init() call above:
USBD_DEVICE USR_desc =
{
  USBD_USR_DeviceDescriptor,
  USBD_USR_LangIDStrDescriptor,
  USBD_USR_ManufacturerStrDescriptor,
  USBD_USR_ProductStrDescriptor,
  USBD_USR_SerialStrDescriptor,
  USBD_USR_ConfigStrDescriptor,
  USBD_USR_InterfaceStrDescriptor,
};

How do we send those messages?

I call usb_printf():
usb_printf ( "%d hello\n", count );
This is in Hydra/serial.c and is a "wrapper" that calls usb_puts() which is in usbF4/usb.c. This is code I wrote. It calls:
(void) VCP_DataTx ( ubuf, len );

We can write, but can we read?

Alongside of usb_puts() in usb.c I also see usb_read(), which calls:
VCPGetBytes ( buf, len );
A comment there indicates that this never blocks and will just continuously return 0 if there is no data. There must be a way to check for data, or get notified by an interrupt if data arrives. I see a call to this in the currently unused usb_test_2() in main.c.

Whenever I type a character to picocom, I see these messages on the F411 console:

USBint - Rx level
Endpoint 0, read packet 1 bytes from FIFO
USBint - Rx level
USBint = OUT Endpoint 1 xfer complete
VCP_DataRx 1
- EP 1 StartXfer 64 bytes
We know then when a character arrives, so we should be able to figure out a way to pass that character to our code in main.c.
Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org