Skip to content

USB host mode on the NUCLEO-F446ZE: U12, a mouse, a flash drive, Adafruit's TFT shield - #113

Open
teo1747 wants to merge 14 commits into
mainfrom
claude/dreamy-archimedes-v28ly5
Open

teo1747 wants to merge 14 commits into
mainfrom
claude/dreamy-archimedes-v28ly5

Conversation

@teo1747

@teo1747 teo1747 commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Follows #109 (the OTG core in device mode) with the host side, the parts a host needs on the bench, and ST's two host applications running unmodified.

What lands

  • OTG FS host mode (core/mcu/stm32/otg_fs.c), as RM0390 30 and ST's HCD driver have it:

    • the port: PPWR, connection and disconnection, PRST's reset, the port enabled at full or low speed, PSUSP/PRES, PLSTS;
    • an SOF (or a low-speed keep-alive) every HFIR frame;
    • twelve channels: SETUP/OUT/IN at the bus's bit time with toggles, ACK/NAK/STALL, TXERR, BBERR and DTERR, the CHDIS/CHENA halt with CHH, one IN token per enable, and interrupt channels once a frame on ODDFRM's parity;
    • the host FIFOs and RX statuses.

    The MCU is now both ends of its cable (mach.h usbh hooks).

  • USB device core (core/model/usb_dev.c), shared by the USB parts:

    • emblink/usb_mouse: low speed, HID boot protocol.
    • emblink/usb_flash_drive: Bulk-Only Transport plus SCSI, with the medium formatted FAT16 as from the shop; its outputs list the files.
  • U12, emblink/stmps2151str, on the NUCLEO-F446ZE: 5V to CN13's VBUS while PG6 is high. Its values come from ST's datasheet: 90 mΩ, VIL 0.8 V, VIH 2.0 V.

  • emblink/st7735_tft18, a C model of the ST7735R and its 1.8" panel:

    • 4-wire SPI;
    • the 18-bit frame memory, CASET/RASET taken as each address arrives (ST's st7735_SetCursor relies on it);
    • MADCTL and COLMOD, sleep, display, inversion, idle and partial modes, the resets;
    • outputs: the image as the module is held, a checksum, and a pixel probe.
  • emblink/adafruit_tft_shield18: the TFT, plus the joystick's ladder on A3 with resistor values from Adafruit's Eagle schematic.

Engine changes

The first host run took 18 minutes for 3 s of simulated time. Two causes, both fixed:

  • Analog islands vs fast digital edges. An analog island chopped by another part's events less than a microsecond apart (an MCU's SPI clock beside a settling supply) now lags up to 10 µs instead of stepping to every edge. It catches up before:
    • a part changes its sources,
    • an MCU or a probe reads it,
    • the end of an advance.
  • STM32 GPIO lookups.
    • The alternate-function table is indexed.
    • Peripheral name lookups are cached.
    • The WKUP mask is read once per port.
    • An MCU no longer re-reads its ports on the echo of its own outputs.

The same run now takes 2.5 minutes. test_analog's RC-under-quanta and two-island tests and the switch-bounce fixtures still hold.

Evidence

  • test_stm32f4's test_otg_host: connection, reset, full and low speed, SOF timing, a 14 µs SETUP and a 113 µs low-speed one, IN statuses and XFRC, NAK, TXERR, halt, OUT toggles, an interrupt channel once per frame, disconnection, suspend and resume.
  • Catalog fixtures:
    • usb_mouse, usb_flash_drive, U12 (4), TFT (6), shield (6);
    • the board's new usb-host-vbus.
  • CubeF4 USB_Host/HID_Standalone, unmodified, 13/13:
    • VBUS switched on through U12, the mouse enumerated at low speed, its log on the LCD;
    • SEL starts the mouse demo once the log is quiet. While the log scrolls, the main loop is busy drawing and doesn't read the joystick, as on the real board.
    • The demo's window is drawn, and reports flow as the mouse moves and clicks.
  • CubeF4 USB_Host/MSC_Standalone, unmodified, 14/14: FatFs mounts the drive, B1 writes USBHost.txt and reads it back ("FatFs data compare SUCCESS"), and the drive then holds USBHOST.TXT (40).
  • NUCLEO-F446ZE suite 26/26 locally; dev, ci-clang 52/52; docs, banned constructs and coverage clean.
  • Gallery: docs/gallery/nucleo-f446ze/host-*.png, the LCD's own frames.

CI fetches the host middleware, FatFs, stm32-st7735 and stm32-bsp-common at the commits CubeF4 pins, plus CubeF4's Adafruit_Shield BSP and Utilities Log/Fonts.

Not yet

  • The ID pin, SRP and HNP.
  • Hubs and split transactions.
  • U12's current limit and fault blanking.
  • The drive's NAND busy times, and an image file kept between runs.
  • The shield's microSD socket.
  • A workbench view of the LCD and of the USB state.
  • The USB/IP bridge.

core/model/usb_dev.c is what every USB device's controller and chapter 9
firmware do, for a part's class code: attached while VBUS is above
4.4 V, the bus reset, EP0's standard requests (descriptors, SET_ADDRESS
after its status stage, configuration, status, features, interfaces),
class and interface requests to the part, data toggles per endpoint and
halted endpoints.

emblink/usb_mouse is a low-speed HID boot mouse on it: its descriptors,
GET/SET_IDLE, GET/SET_PROTOCOL, GET_REPORT, and an interrupt IN endpoint
answering with a report when its inputs moved it or pressed a button.

The computer's port takes low-speed devices: 8-byte EP0 packets until
the descriptor says, and transactions at 1.5 Mbit/s. A host on a cable
no longer finds its own device side (an OTG core is both).

The vendor runner knows the USB host library, FatFs and the Adafruit
shield's sources, copied into CubeF4's tree where their includes expect
them; CubeF4's USB_Host HID and MSC applications build.
The STM32F446's OTG core as a host, as RM0390 30 and the HAL's HCD
driver have it: the port (PPWR, connection and disconnection, PRST's
reset, the port enabled at the device's speed, PSUSP and PRES, the
line state), SOFs every frame of HFIR at HCFG's PHY clock, and twelve
channels: SETUP, OUT and IN transactions at the bus's bit time, the
data toggles, ACK, NAK, STALL, the time-out, babble and toggle errors,
the halt (CHDIS with CHENA, then CHH), one IN token per enable,
periodic channels once a frame in the frames ODDFRM asks, the
transmit FIFOs' space and the receive FIFO's host statuses. The
machine gains the host side of its USB port, and the MCU's controller
both ends of the cable.
U12 (STMPS2151STR) joins 5V to CN13's VBUS while PG6 is high, as
MB1137's schematic draws it, so a device plugged into CN13 is powered
when the OTG core is a host; its fault output reaches PG7 through JP4
and never asserts. The part is a P-channel high-side switch behind an
active-high enable; its values are taken from memory (ST's site
refused the datasheet) and its fidelity says so.
A 1.8" 128 x 160 TFT on a Sitronix ST7735R, as a C model: its 4-wire
serial interface (bits on SCL's rising edge, D/CX with the last bit),
the 18-bit frame memory with CASET, RASET, RAMWR and MADCTL's ordering,
COLMOD's 12, 16 and 18 bits, sleep, display on and off, inversion,
idle and partial modes, SWRESET and RESX with the 5 ms reset cancel
time; the panel blank (white) while it sleeps. Its image is an output
(RGB565 in base64), with a checksum and a pixel probe.

Adafruit's shield (product 802) wires it to the Arduino header (SCK
D13, MOSI D11, CS D10, DC D8, RESET) with the five-way joystick as a
ladder on A3, its values fitted to the windows ST's BSP decodes.
ST's STMPS2141/2151/2161/2171 datasheet (Rev 3) confirms the SOT23-5L
pinout and the active-high enable, and gives the on-resistance (90 mOhm
typical, 110 mOhm maximum at 5 V), EN's levels (VIL 0.8 V, VIH 2.0 V,
the model's threshold now 1.4 V between them) and the off state's
reverse blocking. The current limit, fault blanking, thermal shutdown,
UVLO and the rise time are named with their datasheet values as what
the model does not do yet. Fixtures at VIL and VIH.
Adafruit's Eagle schematic (the original revision) gives the ladder on
A3: R8 1 kOhm to 3V3, and LEFT 10 kOhm, UP 1.0 kOhm, SEL 470 Ohm,
RIGHT 220 Ohm, DOWN 22 Ohm through BTN1 to ground: the voltages of the
fitted values, at a tenth of their impedance. The microSD socket's
lines are ported as an empty socket's leakage.
…al edges

STM32CubeF4's USB_Host/HID_Standalone runs unmodified on the
NUCLEO-F446ZE with Adafruit's 1.8" TFT shield and a USB mouse on CN13:
VBUS switched on through U12, the mouse enumerated at low speed, the log
on the LCD, SEL starting the mouse demo, reports as the mouse moves
(13/13). CI fetches the host middleware, FatFs, the st7735 component and
the BSP common headers at the commits CubeF4 pins.

The ST7735 takes a CASET or RASET cut short after its start (ST's
st7735_SetCursor relies on it), shows its memory as the module is held
(MX and MY give an upright image), takes a byte inside the reset cancel
time with a warning, and renders its frame only when it changed.

The engine: an analog island chopped by another part's events under a
microsecond apart lags up to 10 us behind instead of stepping to each,
and catches up before a part changes its sources, an MCU or a probe
reads it, and at an advance's end. The STM32's alternate-function table
is indexed, its peripherals' names cached, the WKUP pins read once per
port, and an MCU no longer re-reads its ports on its own outputs'
echoes: this run went from 18 min to 2.5 min.
emblink/usb_flash_drive: a full-speed USB mass storage device on the
shared device core, with Bulk-Only Transport (CBW, CSW, residues, the
data phase's STALL, reset recovery after an invalid CBW) and the SCSI
commands a host sends (INQUIRY, TEST UNIT READY, REQUEST SENSE, MODE
SENSE, READ FORMAT CAPACITIES, READ CAPACITY(10), READ(10), WRITE(10),
VERIFY, SYNCHRONIZE CACHE; ILLEGAL REQUEST, LBA out of range, write
protection). Its medium is formatted as from the shop: an MBR with one
FAT16 partition; its outputs list the root directory's files.

STM32CubeF4's USB_Host/MSC_Standalone runs unmodified with the shield
and the drive on CN13: FatFs mounts it, B1 writes USBHost.txt, reads it
back and compares, then lists the disk (14/14).
…des-v28ly5

# Conflicts:
#	CHANGELOG.md
#	core/model/digital.c
#	docs/STATUS.md
#	docs/gallery/README.md
A comparator's crossing found while a lagging island caught up came up
to 10 us late (the Arduino comparator sketch's fall timing, 6964 us for
6956). Before putting its steps off, the island now extrapolates its
last two solutions over the lag: if a watched pin's node may cross its
threshold, it steps as before, so crossings keep their exact time.
An MCU's port is no longer re-read on the echo of its own output only
where its GPIO model takes an output pin's level from its own drive
(the STM32F4's, which says so with esk_mach.outputs_read_back). The
ESP32-C3's GPIO reads the pad, which its I2C master watches for the
bus's state: with the echo skipped, ESP-IDF's i2c_eeprom saw a stuck
bus.
A part reads back the pad it drives (an I2C master its released SCL and
SDA), so the look-ahead before an island lags now checks every bridged
digital pin's thresholds against the extrapolated voltage, whatever the
pin's role, not only the inputs'.
ESP-IDF's i2c_eeprom timed out on its second read: the I2C lines, on
their pull-ups to 3V3, are in the supply's island, which lagged behind
the core's events between the bus's edges. An island now lags only after
1 ms without a source changed by a pin or a crossing (a part's supply
current does not count: a rail's slow load, not bus activity), nor
across one of its breakpoints. Lua models' voltage reads catch a lagging
island up, as the MCUs' analog reads and the probes do. i2c_eeprom 3/3;
the USB host demo keeps its speed (45 s for its first second).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant