This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C123GH6PM: TM4C123GH6PM/EK-TM4C123GXL I2C Programming Example Request for Sending Multiple Bytes to a Slave

Part Number: TM4C123GH6PM
Other Parts Discussed in Thread: EK-TM4C123GXL

Hello,

New student/user and recently purchased the EK-TM4C123GXL Tiva C Series Launchpad board. I would like to configure the microcontroller as an I2C master and use it to write (2) data bytes (3 if you include the main slave address) to a single slave device. There are multiple sub addresses that need to be written with data on this device. I am using CCS V10.

  • Here is an example function that writes values to the registers on a MPU6050 chip.

    void MPU6050WriteRegister(uint8_t reg, uint8_t value)
    {
        I2CMasterSlaveAddrSet(MPU6050_I2C_BASE, MPU6050_I2C_ADDRESS, false);
        I2CMasterDataPut(MPU6050_I2C_BASE, reg);
        I2CMasterControl(MPU6050_I2C_BASE, I2C_MASTER_CMD_BURST_SEND_START);
        while(I2CMasterBusy(MPU6050_I2C_BASE));
        I2CMasterDataPut(MPU6050_I2C_BASE, value);
        I2CMasterControl(MPU6050_I2C_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
        while(I2CMasterBusy(MPU6050_I2C_BASE));
    }
    

  • Here is the project from which I copied that function example. It is for specific hardware that has an MPU6050 attached to I2C3. Use the CCS "File" -> "Import" feature to import this project into your workspace directly from the .zip file.

    2476.MPU6050-2.zip

  • How do I use this form of syntax in CCS: 

    SYSCTL->RCGCGPIO|= 0x20;

    without getting the error: 

    "error #20: identifier "SYSCTL" is undefined"

    Thanks

  • The proper answer is: "Don't use that syntax". We refer to that syntax as direct register modification (DRM). It requires an in-depth knowledge of the peripheral. We have provided TivaWare library functions to do most peripheral operations that are required. The library functions are tested, proven and much easier to understand than trying to write your own functions using DRM. See item #4 in the post: https://e2e.ti.com/support/microcontrollers/other/f/other-microcontrollers-forum/695568/faq-read-before-posting-tm4c-forum-general-guidelines

  • The data structure consists of the main device address, the sub address, and the actual value to be written. Thank you 

  • I am not really sure how to give you a more simple example that achieves what you want to do. The function MPU6050WriteRegister() does what you asked. It sends the I2C slave address with write bit set, sends the register address and then sends the register data. You need to identify which I2C peripheral you are using. This example uses I2C3 on pins PD0 and PD1. The configuration of that module is done in lines 87 through 110 of main.c (most lines are comments).  You need to identify the slave address of your I2C device. This is only 7 bits long. If in the data sheet of the device it shows an 8 bit address with the R/W bit being the least significant bit, right shift that value one place before using it in the TivaWare functions. In this example the slave address is 0x68, which is referenced as MPU6050_I2C_ADDRESS (#define in line 38). The two parameters of the function are the address register you want to write and the data you want to write.

    If this still does not make sense, please ask specific questions about where you are confused. 

  • Does the datasheet for this product use DRM to configure I2C communication? Section 16.4, specifically

  • Section 16.4 of the datasheet describes the registers that must be written to configure and initialize the I2C module. Compare that description to the simple call to the function I2CMasterInitExpClk(). (Line 109 in the file main.c in the example given earlier.) This function and others are described in C:\ti\TivaWare_C_Series-2.1.4.178\docs\SW-TM4C-DRL-UG-2.1.4.178.pdf chapter 16. If you want to see how I2CMasterInitExpClk() works, look at the source in the file: C:\ti\TivaWare_C_Series-2.1.4.178\driverlib\i2c.c

  • I have setup the I2C peripheral as well as pin mapping and my code is below. I would now need to send/write multiple bytes to configure multiple sub addresses on the slave device. An example of this would be the following:

    Slave address: 0x55, Slave sub-address: 0x25, Data: 0x05;

    Slave address: 0x55, Slave sub-address: 0x27, Data: 0x03;

    Slave address: 0x55, Slave sub-address: 0x29, Data: 0x08;

    .

    .

    etc.

    Also, is there anything that can be added to ensure that the slave doesn't receive any junk or unknown values?

    CODE:

    int main()
    {

    SysCtlClockSet(SYSCTL_SYSDIV_4|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ);

    //Enable I2C Module 0
    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);

    //enable GPIO peripheral that contains I2C 0
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);

    // Configure the pin muxing for I2C0 functions on port B2 and B3.
    GPIOPinConfigure(GPIO_PB2_I2C0SCL);
    GPIOPinConfigure(GPIO_PB3_I2C0SDA);

    // Select the I2C function for these pins.
    GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);
    GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);

    //Initialize the Master and Slave
    I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), false);

    //Identify Slave Address
    I2CMasterSlaveAddrSet(I2C0_BASE, 0x55, false);

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    //Send Data Bytes to Slave Sub Addresses as described above???? ///

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    return 0;
    }

  • Try something like this:

    #include <stdint.h>
    #include <stdbool.h>
    #include "inc/hw_types.h"
    #include "inc/hw_memmap.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/gpio.h"
    #include "driverlib/i2c.h"
    #include "driverlib/pin_map.h"
    
    void I2CWriteRegister(uint8_t reg, uint8_t value);
    
    int main()
    {
    
        SysCtlClockSet(SYSCTL_SYSDIV_4|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ);
    
        //Enable I2C Module 0
        SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
    
        //enable GPIO peripheral that contains I2C 0
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    
        // Configure the pin muxing for I2C0 functions on port B2 and B3.
        GPIOPinConfigure(GPIO_PB2_I2C0SCL);
        GPIOPinConfigure(GPIO_PB3_I2C0SDA);
    
        // Select the I2C function for these pins.
        GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);
        GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);
    
        //Initialize the Master and Slave
        I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), false);
    
        //Identify Slave Address
        I2CMasterSlaveAddrSet(I2C0_BASE, 0x55, false);
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
        //Send Data Bytes to Slave Sub Addresses as described above???? ///
        I2CWriteRegister(0x25, 0x05);
        I2CWriteRegister(0x27, 0x03);
        I2CWriteRegister(0x29, 0x08);
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
        
        return 0;
    }
    
    void I2CWriteRegister(uint8_t reg, uint8_t value)
    {
        I2CMasterDataPut(I2C0_BASE, reg);
        I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_START);
        while(I2CMasterBusy(I2C0_BASE));
        I2CMasterDataPut(I2C0_BASE, value);
        I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
        while(I2CMasterBusy(I2C0_BASE));
    }
    
    

    To avoid unwanted receive errors on the slave make sure you have proper pull-up resistors and proper routing of the SCL and SDA lines to avoid electrical cross talk.

    I2C basics: https://www.ti.com/lit/an/slva704/slva704.pdf

    I2C signal integrity: https://www.ti.com/lit/an/slyt770/slyt770.pdf

  • Thank you! Is there something needed to be placed in the uint8_t spots? In other words, is it a generic value?

  • Are you talking about the function definition? 

    void I2CWriteRegister(uint8_t reg, uint8_t value)
    

    The "uint8_t" defines the type of variable. It declares that the variables "reg" and "value" are both unsigned 8-bit integers. It is the same as "unsigned char". To be more proper I should have identified the constants as unsigned as below:

        //Send Data Bytes to Slave Sub Addresses as described above???? ///
        I2CWriteRegister(0x25u, 0x05u);
        I2CWriteRegister(0x27u, 0x03u);
        I2CWriteRegister(0x29u, 0x08u);
    

    but in this case it really does not make a difference.

  • The clock speed is set to 100KHz in this example. The speed is set by the call to:

      I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), false);

    You can change the speed to 400KHz by changing the last parameter from "false" to "true". This is described in SW-TM4C-DRL-UG-2.2.0.295.pdf in your TivaWare docs directory.

  • The pullup resistors are also a function of the capacitance on the lines. Assuming 70pF or less, use something between 1K and 5K Ohms. Here is a paper with the math and graphs.

    https://www.ti.com/lit/an/slva689/slva689.pdf

  • Would I be changing the frequency or bit rate? In other words, if the argument is set to "true", both the clock (Hz) and the bit rate (bps) equal 400K?

  • Yes, the clock frequency and the bit rate would both be 400K.

  • O-Scope waveform is lagging, almost as if the rise time is long is too long. Reducing the OHMS on the lines helps but not completely. Are the lines configured by default as open-drain? No slave device is connected at this point, which may or may not be the issue. If not, how can the waveform be analyzed properly?

  • Yes, when used as I2C lines, they are automatically open drain. Can you post the oscilloscope picture? Can you post a schematic of your circuit? 

  • Schematic is the same as in the documentation. Both SDA and SCL lines are pulled-up to 3.3V with resistors between 1K and 5K. The lower the resistance, the nicer the clock square wave appears on the scope.

    My waveform looks like the orange waveform in Figure 2 in this PDF: The wave is a bit curved going near the top

    https://www.ti.com/lit/an/slyt770/slyt770.pdf?ts=1615902243312&ref_url=https%253A%252F%252Fwww.google.com%252F

    Again, lowering the resistance corrects it more, raising the resistance worsens it.

    I also added a while(1) in code to have it continuously send the values to see it on the scope. Is this the issue?

  • If it looks like the waveform of figure 2, you are good. Measure the rise time from 10% to 90%, 0.3V to 3.0V (or your I2C peripheral may use 20% to 80%, 0.6V to 2.7V).  The waveform is not supposed to be a square wave.

  • Is there any way to slow it down enough to actually see the data on the scope? I can see the clk better than the data. Maybe implementing some delays?

  • No, the way to see the rise time of the SDA line is to use a digital scope and trigger the scope on the rising edge of the data in single sweep mode. 

    Typically the SCL line and SDA line have very close to the same capacitance and using the same value of pull-up resistor works.

    If you need assistance using the scope I suggest you reach out to your professor or teaching assistant. That is not something I can do remotely on the forum. Also my job is to help you understand TI products. This question is more about basic electronics skills.

  • When running the above code, I get an error.c file pop up when trying to debug.saying program execution aborted, however, when I include a while(1) in the:

    void I2CWriteRegister(uint8_t reg, uint8_t value)
    {
    I2CMasterDataPut(I2C0_BASE, reg);
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_START);
    while(I2CMasterBusy(I2C0_BASE));
    I2CMasterDataPut(I2C0_BASE, value);
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
    while(I2CMasterBusy(I2C0_BASE));

    I get the program to run endlessly. Is this because without the while loop, the master didn't detect the slave on the I2C bus or are there interrupts that are needed to be created in the code? Again, no slave is connected as I want to analyze the waveforms on the scope. In the debug window, I also get: Stellaris In circuit Debug Interface/CORTEX M4_0 (Suspended HW Breakpoint)

    #include <stdint.h>
    #include <stdbool.h>
    #include "inc/hw_types.h"
    #include "inc/hw_memmap.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/gpio.h"
    #include "driverlib/i2c.h"
    #include "driverlib/pin_map.h"
    
    void I2CWriteRegister(uint8_t reg, uint8_t value);
    
    int main()
    {
    
        SysCtlClockSet(SYSCTL_SYSDIV_4|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ);
    
        //Enable I2C Module 0
        SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
    
        //enable GPIO peripheral that contains I2C 0
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    
        // Configure the pin muxing for I2C0 functions on port B2 and B3.
        GPIOPinConfigure(GPIO_PB2_I2C0SCL);
        GPIOPinConfigure(GPIO_PB3_I2C0SDA);
    
        // Select the I2C function for these pins.
        GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);
        GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);
    
        //Initialize the Master and Slave
        I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), false);
    
        //Identify Slave Address
        I2CMasterSlaveAddrSet(I2C0_BASE, 0x55, false);
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
        //Send Data Bytes to Slave Sub Addresses
        I2CWriteRegister(0x25, 0x05);
        I2CWriteRegister(0x05, 0x08);
        I2CWriteRegister(0x1C, 0x06);
        I2CWriteRegister(0x27, 0x03);
        I2CWriteRegister(0x29, 0x08);
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
        
        return 0;
    }
    
    void I2CWriteRegister(uint8_t reg, uint8_t value)
    {
        I2CMasterDataPut(I2C0_BASE, reg);
        I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_START);
        while(I2CMasterBusy(I2C0_BASE));
        I2CMasterDataPut(I2C0_BASE, value);
        I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
        while(I2CMasterBusy(I2C0_BASE));
    }

  • I don't recognize the message:

    I get an error.c file pop up when trying to debug.saying program execution aborted,

    Without an infinite loop at the end of the main function, the code should return from main and hit a breakpoint at the symbol C$$EXIT in the run time support library file pre_init.c. If you do not have the CCS debugger attached, the code will execute one more instruction and end in an infinite loop at exit.c. 

    It is normal for embedded systems to have an infinite loop in main.

  • I apologize, the error file that appeared was exit.c and displayed: 

    /************************************************/
    /* ABORT - ABNORMAL PROGRAM TERMINATION         */
    /************************************************/

    This only happens when I do not include a while(1) like below:

    void I2CWriteRegister(uint8_t reg, uint8_t value)
    {
    while(1){
    I2CMasterDataPut(I2C0_BASE, reg);
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_START);
    while(I2CMasterBusy(I2C0_BASE));
    I2CMasterDataPut(I2C0_BASE, value);
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
    while(I2CMasterBusy(I2C0_BASE));
    }
    }

    I'm not sure if this is because the master is trying to find the slave on the I2C bus, when the slave device isn't connected in the circuit
  • This is normal. If you return from the function main(), it calls exit() which calls abort(). This is not related to not having a slave on the I2C bus.