CH376 USB Host
The FPGC has two WCH CH376T USB host controllers on the board.
Each chip provides a single USB type-A host port and exposes its
state machine to the FPGA over an 8-wire SPI bus. The two
controllers are wired to SPI2 and SPI3 on the FPGA side.
The CH376 is an unusual peripheral by FPGC standards: rather than exposing a raw USB transceiver, it implements the entire USB host stack — enumeration, control transfers, mass-storage class support, HID class support, and even a built-in FAT12/16/32 filesystem driver — in firmware on the chip. The host MCU (or in our case the FPGC's CPU) talks to it over a small command-byte protocol that hides USB's complexity entirely.
This page describes the protocol and behaviour. The two main use cases on the FPGC today are USB keyboards (HID class) and USB mass storage (the FAT layer is bypassed in favour of raw block reads).
Bus shape
Each CH376 chip presents the same set of pins to the FPGA:
| Pin | Role |
|---|---|
CS |
chip select (active low) |
CLK |
serial clock |
SI |
host → chip (MOSI) |
SO |
chip → host (MISO) |
INT |
falling-edge interrupt to host |
RST |
active-low reset |
Internally each CH376 also has its own 12 MHz crystal and the on-chip USB transceiver lines that lead to the type-A connector. The chip handles VBUS switching for the connected device through a small load switch on the PCB.
The SPI clock can run up to roughly 12 MHz on the CH376; the FPGC runs both ports at the same divider as the slow-side SPI peripherals.
INT is wired into the FPGA's interrupt controller. The chip
asserts it whenever an asynchronous event happens (device
plugged in, transfer complete, descriptor available). The driver
acknowledges by reading the chip's interrupt status register.
Protocol shape
CH376 communication is a stream of command bytes punctuated by small data payloads. A typical exchange looks like this:
- The host pulls
CSlow. - The host writes one command byte (the opcode).
- The host writes any required parameter bytes for that command.
- The host clocks
0xFFto read back any response bytes. - The host raises
CS.
There is no concept of a 6-byte command frame as on SD or SPI
flash — every CH376 instruction has its own length defined by the
opcode itself. Opcodes occupy the range 0x01..0x6F and are
documented in the WCH datasheet.
Useful commands
Only a small subset is needed for the workloads the FPGC runs:
| Opcode | Mnemonic | Purpose |
|---|---|---|
0x01 |
GET_IC_VER |
Returns chip firmware version (sanity check on init) |
0x02 |
SET_BAUDRATE |
Reconfigures the UART side (unused — we use SPI) |
0x05 |
RESET_ALL |
Soft reset |
0x06 |
CHECK_EXIST |
Loopback test for bus integrity |
0x15 |
SET_USB_MODE |
Switches between device, host, and host-with-SOF |
0x22 |
GET_STATUS |
Reads/clears the chip's interrupt status |
0x27 |
RD_USB_DATA0 |
Streams the chip's receive buffer |
0x2C |
WR_HOST_DATA |
Loads bytes into the chip's transmit buffer |
0x45 |
ISSUE_TKN_X |
Issues a host-side USB token (IN/OUT/SETUP) |
0x4E |
DISK_CONNECT |
Probes for a connected mass-storage device |
0x53 |
DISK_READ |
Reads N 512-byte sectors from the device |
0x54 |
DISK_RD_GO |
Continues a multi-sector read |
0x55 |
DISK_WRITE |
Writes N 512-byte sectors |
0x56 |
DISK_WR_GO |
Continues a multi-sector write |
The HID-keyboard path uses a different sub-command set (Set Report,
Get Report, periodic IN polling) layered on top of ISSUE_TKN_X.
USB modes
The CH376 supports three modes, selected via SET_USB_MODE:
- Device mode — chip pretends to be a USB peripheral. Unused on the FPGC.
- Host mode — chip acts as a USB host but does not generate Start-Of-Frame packets automatically.
- Host with SOF — chip acts as a USB host and generates SOFs. Required for any class that depends on USB framing (which is most of them, including HID).
The driver puts each port into "host with SOF" during init.
Interrupts
After the host issues a command that triggers a USB transaction
(e.g. ISSUE_TKN_X), the chip processes it asynchronously and
raises its INT line when the result is available. The host reads
the chip's interrupt status register (GET_STATUS) to determine
the outcome:
| Status | Meaning |
|---|---|
USB_INT_SUCCESS |
Transaction succeeded |
USB_INT_CONNECT |
A device was plugged in |
USB_INT_DISCONNECT |
The connected device was unplugged |
USB_INT_BUF_OVER |
Buffer overflow on a read |
USB_INT_DISK_READ |
A disk read returned data ready to be drained |
USB_INT_DISK_WRITE |
The chip is ready for the next data block |
USB_INT_USB_SUSPEND |
Bus went into suspend |
0x2F |
NAK (re-issue the token, common for HID polling) |
Reading the status register also clears the interrupt for that event, freeing the chip to raise the next one.
Keyboard path
USB keyboards use the HID boot protocol: an 8-byte report describing
the modifier keys (bit-mapped) and up to six simultaneously pressed
key codes. The FPGC polls the keyboard once per frame interval by
issuing an IN token and, on USB_INT_SUCCESS, draining the report
out of the chip's receive buffer with RD_USB_DATA0. The decoded
report is then translated from HID usage codes to the FPGC's
internal key event format and posted into a per-port FIFO that the
shell and user programs consume.
Repeated NAKs (0x2F) are normal and just mean the keyboard has
nothing new to report; the driver simply waits for the next polling
window.
Disk path
USB mass-storage devices (flash drives, external SSDs in USB enclosures) also speak the bulk-transfer SCSI subset on top of USB, but the CH376's firmware abstracts that away. From the host's perspective the chip exposes a block device with three operations:
DISK_CONNECTto probe for a device,DISK_READ/DISK_RD_GOto stream sectors out,DISK_WRITE/DISK_WR_GOto stream sectors in.
Sectors are 512 bytes, the same size as on an SD card. Multi-sector
operations alternate between DISK_RD_GO (read more) or
DISK_WR_GO (the chip has accepted the previous sector — give me
the next) callbacks until the requested count is reached.
The CH376 will also expose its built-in FAT driver for filesystem-aware accesses, but the FPGC bypasses that layer entirely: the on-board filesystem is BRFS, not FAT, and the chip's FAT firmware would only get in the way.
Two ports
Both ports are fully independent — each has its own CH376 chip, its
own 12 MHz crystal, its own VBUS switch, and its own SPI controller
on the FPGA side. Software addresses them through SPI2 (USB Host
1) and SPI3 (USB Host 2). Today the keyboard runs on USB Host 1;
USB Host 2 is wired and electrically validated but no driver code
binds to it yet.