SLAAES6A February   2025  – August 2025 MSPM0C1105 , MSPM0C1106 , MSPM0G3507 , MSPM0H3216

 

  1.   1
  2. 1Design Description
  3. 2Required Peripherals
  4. 3Design Steps
  5. 4Design Considerations
  6. 5Software Flowchart
  7. 6Device Configuration
  8. 7Application Code
  9. 8Porting Guide
  10. 9Revision History
  11.   Trademarks

Application Code

The initialization of the buffers, counters, enum, and flag are shown here. To change the specific values used by the I2C and SPI maximum packet size, modify the #defines in the beginning of the document, as demonstrated in the following code block.

#include "ti_msp_dl_config.h"

/* Maximum size of packet */
#define MAX_PACKET_SIZE      (4)

/* Data sent to Controller in response to Read transfer */
uint8_t gSPITxPacket[MAX_PACKET_SIZE] = {0x00};
uint8_t gI2CRxPacket[MAX_PACKET_SIZE];                               /* Data received from Controller during a Write transfer */
uint8_t gSPIRxData[MAX_PACKET_SIZE];

/* Counters for I2C RX & TX */
uint32_t gI2CTxCount;
uint32_t gI2CRxCount;

/* Counters for SPI RX & TX */
uint32_t gSPIRxCount;

/* Variable used to skip false I2C Trigger when first sending a message */
uint32_t count_skip = 0;

enum error_codes{
    NO_ERROR = 0,
    DATA_BUFFER_OVERFLOW,
    I2C_TARGET_TXFIFO_UNDERFLOW,
    I2C_TARGET_RXFIFO_OVERFLOW,
    I2C_TARGET_ARBITRATION_LOST,
    I2C_INTERRUPT_OVERFLOW
};

/* Indicates status of Bridge */
enum BridgeStates {
    I2C_RX_STATE = 0,
    SPI_TX_STATE,
    SPI_RX_STATE,
    I2C_TX_STATE
} gBridgeStates;

uint8_t gErrorStatus = NO_ERROR;

/* Flags */
bool gSpiTxReady = false;                                                   /* Flag to start SPI transfer */
bool gSpiTxOngoing = false;                                                 /* Flag to indicate SPI transfer Ongoing*/
bool gSpiRxDone = false;                                                    /* Flag to indicate SPI data has been received */
bool gSpiRxOngoing = false;                                                 /* Flag to indicate SPI data is being receive */
bool gI2cTxDone = false;                                                    /* Flag to start SPI transfer */

The main body of the application code is relatively short. First, the device and peripherals get initialized. Then, a delay occurs for the SPI TX, which is idle before starting transmission, while counters and flag values are initialized. Following up, the interrupts and events are enabled, and the main loop, which contains the bridge function, runs.

int main(void)
{
    SYSCFG_DL_init();

    /* Initialize variables to send data inside TX ISR */
    gI2CTxCount = 0;

    /* Initialize variables to receive data inside RX ISR */
    gI2CRxCount = 0;

    /* Initialize variables to receive data inside RX ISR */
    gSPIRxCount = 0;

    // Setting flags to default values
    gSpiTxReady = false;
    gSpiRxDone = false;

    /* Enabling Interrupts on I2C & SPI Modules */
    NVIC_EnableIRQ(I2C_INST_INT_IRQN);
    NVIC_EnableIRQ(SPI_INST_INT_IRQN);
    while (1) {
        bridge();
    }
}

The bridge has four states. The first state focuses on the Bridge I2C Target receiving data. The second state happens when the I2C Target received data is sent through the Bridge SPI Controller. Then, the third state happens, where the SPI Controller waits for data from the Peripheral, to finally go into the fourth state, where the Bridge I2C Target waits for a transmit request and sends the data from the SPI Peripheral.

void bridge(){
    switch (gBridgeStates) {
        case I2C_RX_STATE:
            if (gSpiTxReady){
                gBridgeStates = SPI_TX_STATE;
            }
            else {
                break;
            }
        case SPI_TX_STATE:
            gSpiTxReady = false;
            for(int i = 0; i < gI2CRxCount; i++){
                /* Transmit data out via SPI and wait until transfer is complete */
                DL_SPI_transmitDataBlocking8(SPI_INST, gSPITxPacket[i]);
            }
            gSpiTxOngoing = false;
            gBridgeStates = SPI_RX_STATE;
            break;
        case SPI_RX_STATE:
            if(gSpiRxDone){
                gSPIRxCount = 0;
                gBridgeStates = I2C_TX_STATE;
            }
            break;
        case I2C_TX_STATE:
            if(gI2cTxDone){
                gI2cTxDone = false;
                gBridgeStates = I2C_RX_STATE;
            }
            break;
        default:
            break;
    }
}

The next piece of this code is the I2C IRQ Handler. This code is used to handle the Bridge I2C Target interrupts. When the pending interrupt is an I2C Start condition detected, the counter variables and flags get set to default values. When the pending interrupt indicates that the I2C RX FIFO has data available, the received value is saved in the I2C RX Buffer if there is space left. If there is no space left, the received value gets ignored. When the pending interrupt is the I2C TX FIFO Trigger, the skip counter increases and sends data through the I2C until reaching the maximum length. If the I2C TX count reaches the maximum package count and the count skip counter is more than 1 (Assuming the first send data from the I2C target is a false trigger and already happened.), the I2C Tx Done flag becomes true. Then, if the bridge detects any errors, the error code will be transmitted through the Bridge I2C Target.

When the pending interrupt is an I2C stop condition, the device checks to see if data was received; if true (I2C data received flag is true,) the received data buffer is stored into the transmit data buffer, and the SPI TX ready flag is set to true. If no I2C data is received, the device does not send anything. This ISR also handles I2C error interrupts by assigning the appropriate error code to the error status variable (TXFIFO Underflow, RXFIFO Overflow, Target Arbitration Lost, and Interrupt Overflow.)

void I2C_INST_IRQHandler(void)
{
    static bool gI2CRxDone = false;     // Flag that indicates I2C data received
    switch (DL_I2C_getPendingInterrupt(I2C_INST)) {
        case DL_I2C_IIDX_TARGET_START:
            /* Initialize (resets) RX or TX after Start condition is received */
            gI2CTxCount = 0;
            gI2CRxCount = 0;
            gI2CRxDone   = false;
            /* Flush TX FIFO to refill it */
            DL_I2C_flushTargetTXFIFO(I2C_INST);
            break;
        case DL_I2C_IIDX_TARGET_RXFIFO_TRIGGER:
            /* Store received data in buffer */
            gI2CRxDone = true;
            while (DL_I2C_isTargetRXFIFOEmpty(I2C_INST) != true) {
                if(gI2CRxCount < MAX_PACKET_SIZE){
                    gI2CRxPacket[gI2CRxCount++] = DL_I2C_receiveTargetData(I2C_INST);
                }else{
                    /* Prevent overflow and just ignore data */
                    DL_I2C_receiveTargetData(I2C_INST);
                    gErrorStatus = DATA_BUFFER_OVERFLOW;
                }
            }
            break;
        case DL_I2C_IIDX_TARGET_TXFIFO_TRIGGER:                    // Restarts the flag
                /* Fill TX FIFO if there are more bytes to send */
                if (gI2CTxCount < MAX_PACKET_SIZE) {
                    count_skip += 1;
                    gI2CTxCount += DL_I2C_fillTargetTXFIFO(I2C_INST, &gSPIRxData[gI2CTxCount], (MAX_PACKET_SIZE - gI2CTxCount));
                    if(gI2CTxCount >= MAX_PACKET_SIZE && count_skip > 1){
                        gI2cTxDone = true;
                    }
                }
                if(gErrorStatus != NO_ERROR)
                {
                    /* Fill FIFO with error status after sending latest received
                     * byte */
                    while (DL_I2C_transmitTargetDataCheck(I2C_INST, gErrorStatus) != false);
                }
            break;
        case DL_I2C_IIDX_TARGET_STOP:
            /* If data was received, store it in SPI TX buffer */
            if (gI2CRxDone == true) {
                for (uint16_t i = 0; (i < gI2CRxCount) && (i < MAX_PACKET_SIZE); i++) {
                    gSPITxPacket[i] = gI2CRxPacket[i];
                    DL_I2C_flushTargetTXFIFO(I2C_INST);
                }
                gI2CRxDone = false;
                /* Set flag to indicate data ready for SPI TX */
                gSpiTxReady = true;
            }
            break;
        case DL_I2C_IIDX_TARGET_TXFIFO_UNDERFLOW:
            gErrorStatus = I2C_TARGET_TXFIFO_UNDERFLOW;
            break;
        case DL_I2C_IIDX_TARGET_RXFIFO_OVERFLOW:
            gErrorStatus = I2C_TARGET_RXFIFO_OVERFLOW;
            break;
        case DL_I2C_IIDX_TARGET_ARBITRATION_LOST:
            gErrorStatus = I2C_TARGET_ARBITRATION_LOST;
            break;
        case DL_I2C_IIDX_INTERRUPT_OVERFLOW:
            gErrorStatus = I2C_INTERRUPT_OVERFLOW;
            break;
        default:
            break;
    }
}

The final part of this subsystem code is the SPI IRQ Handler. The SPI IRQ handler saves received data and sets an ongoing transmission flag when the SPI transmits data. When an SPI RX interrupt is pending, the device saves the received data to the SPI RX Buffer, turns an SPI RX Ongoing flag true, and sets the SPI RX Done flag when the SPI RX counter reaches the maximum length.

void SPI_INST_IRQHandler(void)
{
    switch (DL_SPI_getPendingInterrupt(SPI_INST)) {
        case DL_SPI_IIDX_RX:
            /* Read RX FIFO */
            if(gSPIRxCount < MAX_PACKET_SIZE){
                gSpiRxOngoing = true;
                gSPIRxData[gSPIRxCount++] = DL_SPI_receiveData8(SPI_INST);
                if(gSPIRxCount >= MAX_PACKET_SIZE){
                    gSpiRxDone = true;
                    gSpiRxOngoing = false;
                }
            }
            break;
        case DL_SPI_IIDX_TX:
            gSpiTxOngoing = true;
            break;
        default:
            break;
    }
}