Skip to content

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.

The FPGC uses the CH376 exclusively as a generic USB host controller — primarily for USB HID keyboards. The chip's built-in FAT filesystem and mass-storage commands are unused; the FPGC has its own SPI flash and SD card slot with BRFS for storage.

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 to dedicated FPGA GPIO pins, readable via MMIO (FPGC_CH376_TOP_NINT and FPGC_CH376_BOT_NINT). The chip asserts INT (active low) whenever an asynchronous event happens (device plugged in, device unplugged, transfer complete). 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:

  1. The host pulls CS low.
  2. The host writes one command byte (the opcode).
  3. The host writes any required parameter bytes for that command.
  4. The host clocks 0x00 to read back any response bytes.
  5. 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. A minimum 1.5 µs inter-command delay is required after sending the command byte before sending/receiving data.

Useful commands

The USB host keyboard driver uses the following subset:

Initialisation & diagnostics

Opcode Mnemonic Purpose
0x01 GET_IC_VER Returns chip firmware version (sanity check)
0x05 RESET_ALL Soft reset (wait ~35 ms for completion)
0x06 CHECK_EXIST Loopback test — send a byte, get its bitwise NOT back
0x15 SET_USB_MODE Switch between disabled, host, host+SOF, bus-reset
0x04 SET_USB_SPEED Select full-speed (12 Mbps) or low-speed (1.5 Mbps)

Connection detection

Opcode Mnemonic Purpose
0x16 TEST_CONNECT Query connection status (connected/disconnected/ready)
0x0A GET_DEV_RATE Check if connected device is low-speed
0x22 GET_STATUS Read and clear the chip's interrupt status

USB transactions

Opcode Mnemonic Purpose
0x27 RD_USB_DATA0 Read received data from chip's USB buffer
0x2C WR_HOST_DATA Write data to chip's USB transmit buffer
0x4E ISSUE_TKN_X Issue a USB token (IN/OUT/SETUP) with explicit toggle
0x4F ISSUE_TOKEN Issue a USB token (simpler, toggle managed by chip)

Simplified enumeration

Opcode Mnemonic Purpose
0x45 SET_ADDRESS Set USB device address (simplified SET_ADDRESS)
0x46 GET_DESCR Get device/config descriptor (simplified control xfer)
0x49 SET_CONFIG Set USB configuration (simplified SET_CONFIGURATION)
0x13 SET_USB_ADDR Tell CH376 which device address to talk to

Toggle & retry control

Opcode Mnemonic Purpose
0x0B SET_RETRY Configure NAK retry behavior and timeout retries
0x1C SET_ENDP6 Set RX data toggle (DATA0/DATA1 synchronisation)
0x1D SET_ENDP7 Set TX data toggle

USB modes

The CH376 supports several modes, selected via SET_USB_MODE:

Mode Code Description
0 0x00 Disabled (default after reset)
4 0x04 USB host, disabled (no auto-detect)
5 0x05 USB host, enabled — auto-detects connect/disconnect
6 0x06 USB host, enabled + generates SOF packets
7 0x07 USB host, bus reset — holds USB bus in reset state

The driver initialises each port in mode 5 (host enabled, no SOF). In this mode the CH376 automatically detects device connect/disconnect and asserts INT to notify the CPU — no polling required.

During device enumeration, the driver briefly enters mode 7 (bus reset) to reset the USB bus, then switches to mode 6 (host with SOF) to generate the Start-Of-Frame packets required by most USB devices. The device stays in mode 6 until disconnected.

Interrupts

The CH376 asserts its INT pin (active low) for asynchronous events. The host reads GET_STATUS to determine the cause and clear the interrupt:

Status byte Name Meaning
0x14 USB_INT_SUCCESS USB transaction completed successfully
0x15 USB_INT_CONNECT A device was plugged in
0x16 USB_INT_DISCONNECT The connected device was unplugged
0x17 USB_INT_BUF_OVER Buffer overflow on a read
0x18 USB_INT_USB_READY Device initialised (address assigned)

For failed transactions, bits 3-0 encode the USB response:

  • 0x2A — device returned NAK (normal for HID polling when no new data is available)
  • 0x2E — device returned STALL

The BDOS driver checks the INT pin in the main loop. When asserted, it reads the status to handle connect/disconnect. During keyboard polling (timer ISR, every 10 ms), failed IN transactions return NAK status and are silently ignored.

Keyboard path

USB keyboards use the HID boot protocol: an 8-byte report describing the modifier keys (bit-mapped byte 0) and up to six simultaneously pressed key codes (bytes 2–7; byte 1 is reserved).

Enumeration

When the CH376 asserts INT with status USB_INT_CONNECT, the driver enumerates the device:

  1. Bus reset — enter mode 7, hold for 50 ms, switch to mode 6.
  2. Speed detectionGET_DEV_RATE determines full/low speed.
  3. Device descriptorGET_DESCR retrieves the 18-byte device descriptor.
  4. Set addressSET_ADDRESS assigns address 1 to the device.
  5. Set configurationSET_CONFIG activates configuration 1.
  6. Config descriptor — manual control transfer reads the config descriptor (up to 64 bytes) and parses it for an HID boot keyboard interface and its interrupt IN endpoint.
  7. SET_PROTOCOL — class-specific control transfer forces boot protocol mode (guaranteed 8-byte reports).
  8. SET_IDLE(0) — tells the device to only send reports when key state changes.
  9. SET_RETRY — configures the CH376 for immediate NAK notification (no infinite retry), so the polling ISR never blocks.

Polling

A hardware timer fires every 10 ms and calls the keyboard poll function. This issues an IN token to the device's interrupt endpoint using ISSUE_TKN_X with a 10 ms timeout. Three outcomes:

  • USB_INT_SUCCESS — new report available. The 8-byte report is read via RD_USB_DATA0, translated from HID keycodes to the FPGC's internal event format, and pushed into a 64-entry FIFO. Key repeat is handled in software.
  • NAK (0x2A) — no new data; the ISR returns immediately.
  • Error / timeout — returned as a negative status; the main loop handles reconnection.

Disconnect

When the INT pin asserts with status USB_INT_DISCONNECT, the driver clears the keyboard state (FIFO, key bitmap, repeat state). No chip reset is needed — the CH376 stays in mode 5 and will generate a new USB_INT_CONNECT when a device is plugged in again.

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 as CH376_SPI_TOP (SPI_USB_0) and CH376_SPI_BOTTOM (SPI_USB_1).

Today the keyboard runs on the bottom port (CH376_SPI_BOTTOM). The top port is wired and electrically validated but no driver code binds to it yet.