Figure 6-1 shows the code flow for this example.
USB to SPI direction:
- TinyUSB invokes tud_cdc_rx_cb() each time
the USB host sends data. The callback calls usb2spi_pushTask(), which reads up to 64 bytes
from the USB CDC interface and queues them as a task in gUSB2SPITask.
- In the main loop, when
gUSB2SPITask.fifoCount > 0, usb2spi_popTask() flushes the SPI TX FIFO, configures DMA
channel CH1 with the task buffer address and length, enables the DMA_DONE_TX interrupt,
and starts the SPI write transfer.
- The DMA_DONE_TX interrupt fires when DMA
finishes loading the TX FIFO. The handler clears and enables the TX_EMPTY interrupt. The
TX_EMPTY interrupt fires when the last byte has been clocked out. The handler calls
fifoCompleteRead() to release the task and clears spiInProgress.
SPI to USB direction:
- The SPI target device asserts the
data-ready GPIO pin (rising edge). The GPIO interrupt handler sets SPISlaveDataReady =
true and returns immediately.
- In the main loop, when SPISlaveDataReady
is set and spiInProgress is false, spi2usb_pushTask() flushes both RX and TX FIFOs,
configures DMA channel CH0 for the receive buffer, configures DMA channel CH1 to send
dummy data on MOSI, and starts both DMAs. The TX_DONE_TX interrupt is explicitly disabled
so that only DMA_DONE_RX signals completion.
- The DMA_DONE_RX interrupt fires when all
64 bytes have been received. The handler calls fifoCompleteWrite() to mark the task ready
for USB transmission, clears SPISlaveDataReady, and clears spiInProgress.
- In the main loop, when
gSPI2USBTask.fifoCount > 0, spi2usb_popTask() checks that the TinyUSB CDC TX FIFO has
sufficient space, writes the buffered data, flushes it to the host, and calls
fifoCompleteRead() to release the task.