SSDA014 September 2026 MSPM0G5117 , MSPM0G5187
USB to I2C:
/********************************************************
* Function name : usb2i2c_pushTask
* Description : USB -> I2C. Read data from USB CDC interface and push
* into gUSB2I2CTask queue. Each task holds one USB frame
* (up to 64 bytes). Called from tud_cdc_rx_cb callback.
* Parameter : None (modifies gUSB2I2CTask)
* Return : None
**********************************************************/
void usb2i2c_pushTask(void)
{
// Check space for new task
if(gUSB2I2CTask.fifoCount >= I2C_TX_BUF_SIZE) return;
// Get current task slot in queue
TxElement_t *pTask = &gUSB2I2CTask.fifoPointer[gUSB2I2CTask.fifoIn];
// Read up to 64 bytes from USB CDC
uint32_t cnt = tud_cdc_n_read(0, pTask->buffer, 64);
if(cnt == 0) return; // Skip if no data
pTask->length = cnt;
// Advance queue pointer and mark task as ready
fifoCompleteWrite(&gUSB2I2CTask, I2C_TX_BUF_SIZE);
}/********************************************************
* Function name : usb2i2c_popTask
* Description : USB -> I2C. Pop task from queue and transmit data through
* I2C to slave device. Waits if I2C TX already in progress.
* Called from main loop when fifoCount > 0.
* Parameter : None (modifies gUSB2I2CTask, i2cInProgress)
* Return : None
**********************************************************/
void usb2i2c_popTask(void)
{
if(i2cInProgress) return;
if(gUSB2I2CTask.fifoCount == 0) return;
// Current process task
TxElement_t *pTask = &gUSB2I2CTask.fifoPointer[gUSB2I2CTask.fifoOut];
/* Setup DMA and start I2C write */
DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t)(pTask->buffer));
DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t)(&I2C_0_INST->i2cc->TXDATA));
DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, pTask->length);
DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID);
DL_I2CC_startTransfer(I2C_0_INST, I2C_ADDRESS, DL_I2CC_DIRECTION_TX, pTask->length);
i2cInProgress = true;
}
I2C to USB:
/********************************************************
* Function name : i2c2usb_pushTask
* Description : I2C -> USB. Read data from I2C slave and queue for USB transmission
* Triggered when slave device signals data ready via GPIO pin.
* Called from GPIO interrupt handler.
* Parameter : None (modifies gI2C2USBTask)
* Return : None
**********************************************************/
void i2c2usb_pushTask(void)
{
// Check space for new task
if (gI2C2USBTask.fifoCount >= USB_TX_BUF_SIZE) return;
if(i2cInProgress) return;
// Current process task
TxElement_t *pTask = &gI2C2USBTask.fifoPointer[gI2C2USBTask.fifoIn];
pTask->length = USB_FRAME_PACKAGE;
/* Setup DMA and initiate I2C read */
DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)(&I2C_0_INST->i2cc->RXDATA));
DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)(pTask->buffer));
DL_DMA_setTransferSize(DMA, DMA_CH0_CHAN_ID, USB_FRAME_PACKAGE);
DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);
DL_I2CC_startTransfer(I2C_0_INST, I2C_ADDRESS, DL_I2CC_DIRECTION_RX, USB_FRAME_PACKAGE);
i2cInProgress = true;
}/********************************************************
* Function name : i2c2usb_popTask
* Description : I2C -> USB. Pop task from queue and transmit data through
* USB CDC interface. All data in one task is sent in same USB
* frame. Checks if USB TX buffer has space. Called from main
* loop when fifoCount > 0.
* Parameter : None (modifies gI2C2USBTask)
* Return : None
**********************************************************/
void i2c2usb_popTask(void)
{
if(gI2C2USBTask.fifoCount == 0) return; // Queue empty
TxElement_t *pTask = &gI2C2USBTask.fifoPointer[gI2C2USBTask.fifoOut];
if(tud_cdc_n_write_available(0) >= pTask->length){ // USB FIFO has space
uint32_t written = tud_cdc_n_write(0, pTask->buffer, pTask->length);
if(written == pTask->length){ // Entire task sent atomically
tud_cdc_n_write_flush(0);
fifoCompleteRead(&gI2C2USBTask, USB_TX_BUF_SIZE);
}
}
}
I2C Interrupt Handler:
//--------------------------------------------------------------------+
// I2C Interrupt Handler
//--------------------------------------------------------------------+
void I2C_0_INST_IRQHandler(void) {
switch(DL_I2CC_getPendingInterrupt(I2C_0_INST)) {
case DL_I2CC_IIDX_EVENT1_DMA_DONE:
// TX: DMA transfer to I2C FIFO complete
// IMPORTANT: Don't pop task yet - STOP condition still in progress
DL_I2CC_clearInterruptStatus(I2C_0_INST, DL_I2CC_INTERRUPT_STOP);
DL_I2CC_enableInterrupt(I2C_0_INST, DL_I2CC_INTERRUPT_STOP);
break;
case DL_I2CC_IIDX_STOP:
// TX complete: I2C STOP condition detected - transmission finished
// Only enabled when I2C Host write
DL_I2CC_disableInterrupt(I2C_0_INST, DL_I2CC_INTERRUPT_STOP);
fifoCompleteRead(&gUSB2I2CTask, I2C_TX_BUF_SIZE);
i2cInProgress = false;
break;
case DL_I2CC_IIDX_EVENT2_DMA_DONE:
// RX complete: DMA received 64 bytes from I2C slave
fifoCompleteWrite(&gI2C2USBTask, USB_TX_BUF_SIZE);
i2ctargetDataReady = false; // Clear slave notification flag
i2cInProgress = false;
break;
case DL_I2CC_IIDX_NACK:
// I2C Error: Slave did not acknowledge address
i2cInProgress = false;
__BKPT(0); // Halt for debugging - indicates I2C communication failure
break;
default:
break;
}
}
Overflow Handling:
void tud_cdc_rx_cb(uint8_t itf)
{
if (gUSB2I2CTask.fifoCount < I2C_TX_BUF_SIZE) {
usb2i2c_pushTask();
} else {
/* Queue full: discard to unblock TinyUSB RX buffer */
uint8_t dummyBuf[64];
tud_cdc_n_read(itf, dummyBuf, 64);
}
}