PIC32MZ USB: What they don't want you to know

This document is intended as a supplement to the "datasheet" for the PIC32MZ USB controller. I've only messed with device mode so this is all exclusive to it.

Overview

The USB controller for the PIC32MZ is reasonably nice in that it does most of the transaction processing (SETUP, IN, OUT, DATA0 etc packets) in hardware and gives you just the USB data packets. You can set up up to 7 receive and 7 transmit endpoint. (Endpoint 0 has its own special hardware). Note that the endpoint number in hardware does not need to match the USB endpoint number (specified in the endpoint descriptor).

The USB controller has its own RAM, which I think is 4096x64 bits large. As far as I can tell you have to allocate FIFOs yourself in this RAM. The datasheet mentions "dynamic FIFO sizing" but I can't figure out what it is or does.

Note that USB reset will reset a lot of the registers and you should do most initialisation in the USB reset interrupt handler.

Registers — annotations

This is a selection of notes and corrections to the register list (pretty much the only thing in the datasheet).

Note that some bits have different names in xc.h for reasons I don't know.

Registers relevant to your interestsRegisters you can ignore (probably)
USBCSR0
USBCSR1
USBCSR2
USBCSR3
USBE0CSR0
USBE0CSR2
USBEnCSR0
USBEnCSR1
USBEnCSR2
USBEnCSR3
USBFIFO*
USBOTG
USBFIFOA
USBDPBFD
USBCRCON

If you use DMA:
USBDMAINT
USBDMA*C
USBDMA*A
USBDMA*N
USBE0CSR3
USBHWVER
USBINFO
USBEOFRST
USBE*TXA
USBE*RXA
USBE*RPC
USBTMCON1
USBTMCON2
USBLPMR1
USBLPMR2

USBCSR2: Note that connect/disconnect interrupts only apply to the host. The device has to use VBUS if it wants to find out it's been disconnected (and then wait for USB reset to consider itself connected again).

USBCSR3: The testbits are useful to implement the USB 2.0 test modes (see USB 2.0 spec). Not that I'm aware of anything that actually uses these modes. The ENDPOINT field selects the endpoint for the following registers:

USBE0CSR0:

USBE0CSR2:

USBIENCSR0: this register corresponds to the TX endpoint

USBIENCSR1: this register corresponds to the RX endpoint

USBIENCSR2: this register corresponds to the TX endpoint (except for RXCNT)

USBIENCSR3: this register corresponds to the RX endpoint (except for TXFIFOSZ)

USBOTG:

USBFIFOA: the endpoint this register corresponds to is set by the ENDPOINT field (see above)

USBDMA*C:

Control transfers

(Using the notation from xc.h, not the datasheet)

To implement control transfers right you need to keep a state machine in the driver that matches the state in the hardware. Each interrupt potentially causes a transition. If an interrupt comes in with STALLED bit set, clear the bit and ignore the interrupt. If SETEND is set, the host aborted the request (except in the zero length case, see below). Set SETENDC, return to idle state and check for RXRDY (without waiting for another interrupt).

Idle state: An interrupt with RXRDY set corresponds to the arrival of a setup packet. Read the packet from the FIFO, parse it according to the USB spec and

  1. if wLength=0, this is zero length control transfer. Process the request.
    If the request is successful, set RXRDYC and DATAEND and go to the zero length status state.
    If the request is unsuccessful, set SENDSTALL and stay in the idle state.

  2. if bit 7 is set in bmRequestType, this is a control read. Process the request, keeping the response in a buffer.
    If the request is successful, set RXRDYC and go to the transmit packet state.
    If the request is unccessful, set SENDSTALL and stay in the idle state.

  3. otherwise, this is a control write. Set RXRDYC and go to the receive packet state.

Transmit packet state: Copy the packet into the transmit FIFO. If there are more packets, set TXRDY and go to state "wait for transmit interrupt". If there are no more packets, set TXRDY and DATAEND and go to state "wait for status".

Wait for transmit interrupt: An interrupt (which isn't marked by any special bits!) confirms the transmission of the packet. Go back to transmit packet state.

Wait for status: An interrupt marks the completion of status stage. Complete any "post-status" operations (if the request has any) and go back to idle state.

Zero length status state: Identical to "wait for status", but ignore SETEND if it is set (set SETENDC, though).

Receive packet state: An interrupt with RXRDY corresponds to the arrival of a data packet. Remove the packet from the FIFO.

  1. If the packet has fewer than the maximum number of bytes (usually 64 bytes) or wLength bytes have been sent in total, this is the last packet. In that case, process the request. If the request is successful, set RXRDYC and DATAEND and go to the "wait for status" state.
    If the request is unsuccessful, set SENDSTALL and go to the idle state.

  2. Otherwise, set RXRDYC and stay in the state.

USB Notes

These are just some USB "gotchas" that aren't necessarily specific to the PIC32MZ.