eUSCI_B I2C

Description

The MSP432P401R has several instances of what is called the Enhanced Universal Serial Communication Interface, or eUSCI. The are two types of eUSCI on the MSP432P401R: eUSCI_A and eUSCI_B. The eUSCI_A module is capable of supporting both the Universal Asynchronous Receiver-Transmitter (UART) and Serial Peripheral Interface (SPI). The eUSCI_B supports SPI and the Inter-Integrated Circuit (I2C) or I2C. This course requires the use of the I2C to interact with sensors; therefore, this section only discusses the use of the eUSCI_B type modules.

The eUSCI_B I2C configuration is capable of operating in both Controller Mode and Peripheral Mode. These modes are more widely known as the Master and Slave mode, respectively; however, these terms are being slowly replaced with alternative namings. The manufacturer documentation and DriverLib functions are written with the terms Master and Slave. This documentation (the ENGR-2350 website) is written using the Controller and Peripheral names and it should be understood that these terms are interchangeable.

As there are several instances of the eUSCI_B type modules, the functions listed below require that the specific instance be specified. The first argument for all eUSCI_B DriverLib functions is the eUSCI_B module to use: uint32_t moduleInstance. The MSP432P401R has 4 eUSCI_B instances. The desired instance can be selected using the predefined values EUSCI_B0_BASE through EUSCI_B3_BASE.

Note

A rigorous implementation of I2C would likely involve employing the eUSCI_B module in interrupt mode. As this course does not require this type of implementation, the interrupt modes of the eUSCI_B are omitted from this discussion.

Controller Mode Configuration

The configuration requires the creation of a eUSCI_I2C_MasterConfig type struct. The table below shows the fields for this configuration struct.

eUSCI_I2C_MasterConfig struct fields

Field

Possible Values

.selectClockSource

EUSCI_B_I2C_CLOCKSOURCE_SMCLK

EUSCI_B_I2C_CLOCKSOURCE_ACLK

.i2cClk

0 to 4294967295

.dataRate

EUSCI_B_I2C_SET_DATA_RATE_1MBPS

EUSCI_B_I2C_SET_DATA_RATE_400KBPS

EUSCI_B_I2C_SET_DATA_RATE_100KBPS

.byteCounterThreshold

0 to 255

.autoSTOPGeneration

EUSCI_B_I2C_NO_AUTO_STOP

EUSCI_B_I2C_SET_BYTECOUNT_THRESHOLD_FLAG

EUSCI_B_I2C_SEND_STOP_AUTOMATICALLY_ON_BYTECOUNT_THRESHOLD

Field Descriptions:
  • .selectClockSource indicates what clock source the eUSCI_B module should use. Fur the purposes of this course, we will always select the “Low-speed subsystem master clock”, or SMCLK, which is set to be 24 MHz.

  • .i2cClk specifies the frequency of the clock source selected by the field .selectClockSource.

  • .dataRate defines the desired communication speed of the I2C protocol. Valid options are 1 Mbit/s (1MBPS), 400 kbit/s (400KBPS), and 100 kbit/s (100KBPS). The eUSCI_B module is capable of many more data rates; however, the DriverLib only allows for those listed.

  • .byteCounterThreshold is useful for when the number of bytes that will be transferred in each packet is known a-priori; allowing the module to perform automated operations. For the purposes of this course, the number of bytes per packet will vary; therefore, this field should be set to 0, indicating that this functionality should be disabled.

  • .autoSTOPGeneration enables or disables automatic I2C STOP signal generation. This functionality should not be used in this course.

With the configuration struct filled in, the eUSCI_B may be initialized with the function:

void I2C_initMaster( uint32_t moduleInstance , eUSCI_I2C_MasterConfig config )

The first argument to this function, moduleInstance, is the eUSCI_B module to use (see above). The second argument is the configuration struct as described above. Note that the * within this argument definition indicates that this is passed as a pointer, which is simply done by adding a & before the struct variable name to be passed.

The module can then be enabled through the function:

void I2C_enableModule( uint32_t moduleInstance )

Similarly, the module can be disabled through the function:

void I2C_disableModule( uint32_t moduleInstance )

Operation of the eUSCI_B module requires that the assocaited SCL and SDA signals on the corresponding GPIO pins <gpio_af> be enabled.

Controller Mode: Read/Write

Implementation of the eUSCI_B read and write functionality with the MSP432 serving as the controller requires a consistent series of steps. The implementation is complex, with errors being highly probable and very difficult to detect. To this end, high level functions are provided towards ease of implementation. The two functions are:

  • void I2C_writeData( uint32_t moduleInstance , uint8_t PeriphAddress , uint8_t StartReg , uint8_t * data , uint8_t len )

  • void I2C_readData( uint32_t moduleInstance , uint8_t PeriphAddress , uint8_t StartReg , uint8_t * data , uint8_t len )

As shown, both functions require the same arguments. These are:

  • uint32_t moduleInstance specifies the eUSCI_B instance to use for the communication.

  • uint8_t PeriphAddress specifies the 7-bit address of the peripheral device to communication with. This is was known historically as the slave address.

  • uint8_t StartReg is the peripheral device register to begin communication with. This is the register that is written-to or read-from first.

  • uint8_t * data is and must be an array that either stores the data to be written to the peripheral device (I2C_writeData) or is used to save the data that is read from the device (I2C_readData). Note that there is no return value for the I2C_readData() function. This array is always accessed starting from the 0th index, independent of the StartReg value.

  • uint8_t len is the number of data bytes to be written-to or read-from the peripheral device. This number does not include the pheripheral address or start register.

Examples

Below is an example for reading and writing to a peripheral device using the functions provided above.

Write Example: It is desired to write configuration values of 0x3A (register 1), 0xFF (register 2), and 0x12 (register 10) for a peripheral with a 7-bit address of 0x52 using eUSCI_B instance 3:

uint8_t arry[2]; // Array must be *at least* as long as longest write used
arry[0] = 0x3A;  // Set values to send, starting at index 0
arry[1] = 0xFF;
I2C_writeData(EUSCI_B3_BASE,0x52,1,arry,2); // send values to registers 1 and 2
arry[0] = 0x12;  // Set value to send for second transmission
I2C_writeData(EUSCI_B3_BASE,0x52,10,arry,1); // send single value to register 10

Read Example: Registers 4 through 6 must be read from the same peripheral.

uint8_t arry[3]; // Array must be *at least* 3 long for 3 register read.
I2C_readData(EUSCI_B3_BASE,0x52,4,arry,3); // read values from register 4-6
printf("4: %u\t5: %u\t6: %u\r\n",arry[0],arry[1],arry[2]); // print out values

High-Level Functions

Below are the function definitions for void I2C_writeData() and void I2C_readData(). These functions are included in the TemplateProject.zip (As-of 2024-04-03) and do not need to be copied into the main.c file.

Warning

Modifying these function during debugging; for example, adding debugging printf() statements, may cause the timing of the I2C communication to break and therefore cause the functions to break as well. In debugging, it is common for the program to get stuck in these functions. This is most commonly due to incorrect wiring of I2C devices or blown pins, not the functions themselves.

void I2C_writeData(uint32_t moduleInstance
                  ,uint8_t PeriphAddress
                  ,uint8_t StartReg
                  ,uint8_t *data
                  ,uint8_t len)
{
    I2C_setSlaveAddress(moduleInstance,PeriphAddress); // Set the peripheral address
    I2C_setMode(moduleInstance,EUSCI_B_I2C_TRANSMIT_MODE); // Indicate a write operation

    I2C_masterSendMultiByteStart(moduleInstance,StartReg); // Start the communication.
                // This function does three things. It sends the START signal,
                // sends the address, and then sends the start register.

    // This code loops through all of the bytes to send.
    uint8_t ctr;
    for(ctr = 0;ctr<len;ctr++){
        I2C_masterSendMultiByteNext(moduleInstance,data[ctr]);
    }
    // Once all bytes are sent, the I2C transaction is stopped by sending the STOP signal
    I2C_masterSendMultiByteStop(moduleInstance);

    __delay_cycles(200); // A short delay to avoid starting another I2C transaction too quickly
}

void I2C_readData(uint32_t moduleInstance
                 ,uint8_t PeriphAddress
                 ,uint8_t StartReg
                 ,uint8_t *data
                 ,uint8_t len)
{
    // First write the start register to the peripheral device. This can be
    // done by using the I2C_writeData function with a length of 0.
    I2C_writeData(moduleInstance,PeriphAddress,StartReg,0,0);

    Interrupt_disableMaster(); //  Disable all interrupts to prevent timing issues

    // Then do read transaction...
    I2C_setSlaveAddress(moduleInstance,PeriphAddress); // Set the peripheral address
    I2C_setMode(moduleInstance,EUSCI_B_I2C_RECEIVE_MODE); // Indicate a read operation
    I2C_masterReceiveStart(moduleInstance); // Start the communication. This function
                // does two things: It first sends the START signal and
                // then sends the peripheral address. Once started, the eUSCI
                // will automatically fetch bytes from the peripheral until
                // a STOP signal is requested to be sent.

    // This code loops through 1 less than all bytes to receive
    uint8_t ctr;
    for(ctr = 0;ctr<(len-1);ctr++){
        uint32_t tout_tmp = 10000;
        while(!(EUSCI_B_CMSIS(moduleInstance)->IFG & EUSCI_B_IFG_RXIFG0) && --tout_tmp); // Wait for a data byte to become available
        if(tout_tmp){
            data[ctr] = I2C_masterReceiveMultiByteNext(moduleInstance); // read and store received byte
        }else{
            data[ctr] = 0xFF;
        }
    }
    // Prior to receiving the final byte, request the STOP signal such that the
    // communication will halt after the byte is received.
    data[ctr] = I2C_masterReceiveMultiByteFinish(moduleInstance); // send STOP, read and store received byte

    Interrupt_enableMaster(); // Re-enable interrupts

    __delay_cycles(200); // A short delay to avoid starting another I2C transaction too quickly
}