Chapter 10 of 20

I2C Interface — Tiva C

Eslam El Hefny Apr 10, 2025 7 min read
50% done

I2C Interface — Tiva C

Overview

The TM4C123GH6PM includes four I2C modules (I2C0–I2C3), each supporting standard (100 kbps), fast (400 kbps), and fast-plus (1 Mbps) speeds. I2C is a two-wire synchronous bus (SCL for clock, SDA for data) that allows a master to communicate with up to 127 slave devices sharing the same two wires.


Beginner Level — What & Why

What is I2C?

I2C (Inter-Integrated Circuit, pronounced “I-squared-C”) is a serial bus designed for short-distance, low-speed communication between chips on the same PCB. It needs only two wires regardless of the number of connected devices — making it ideal for connecting multiple sensors to a single microcontroller.

Real-World Analogy

I2C is like a classroom where one teacher (master) speaks and calls on specific students (slaves) by name (7-bit address). Only the called student answers. All students share the same microphone (SDA) and listen to the teacher’s pacing (SCL). The teacher can ask a question (write) or listen for an answer (read).

What Problem Does It Solve?

  • Connect multiple sensors (temperature, accelerometer, OLED display) with only 2 wires
  • Reduce pin count on complex boards
  • Standardised protocol supported by thousands of sensor ICs

Key Terms

TermMeaning
SDASerial Data line — bidirectional, open-drain
SCLSerial Clock line — driven by master
STARTSDA falls while SCL is high — begins transaction
STOPSDA rises while SCL is high — ends transaction
ACKAcknowledge — slave pulls SDA low after each byte
NACKNo Acknowledge — SDA remains high, indicates error
Repeated STARTNew START without STOP — changes direction

Intermediate Level — How It Works

I2C Module Addresses

ModuleSCL PinSDA PinBase Address
I2C0PB2PB30x40020000
I2C1PA6PA70x40021000
I2C2PE4PE50x40022000
I2C3PD0PD10x40023000

Key Registers

RegisterOffsetDescription
I2CMSA0x000Master Slave Address (7-bit addr + R/W bit)
I2CMCS0x004Master Control/Status (command bits / status flags)
I2CMDR0x008Master Data Register (write data / read data)
I2CMTPR0x00CMaster Timer Period (SCL frequency)
I2CMIMR0x010Master Interrupt Mask
I2CMRIS0x014Master Raw Interrupt Status
I2CMMIS0x018Master Masked Interrupt Status
I2CMICR0x01CMaster Interrupt Clear
I2CMCR0x020Master Configuration (MFE = master enable)

I2CMCS Write bits (command):

BitNameFunction
0RUNBegin the transaction
1STARTGenerate START condition
2STOPGenerate STOP condition
3ACKGenerate ACK on receive
4HSHigh-speed mode

I2CMCS Read bits (status):

BitNameMeaning
0BUSYTransaction in progress
1ERRORError occurred
2ADRACKAddress NACK received
3DATACKData NACK received
4ARBLSTArbitration lost

TivaWare DriverLib API

#include "driverlib/i2c.h"
#include "driverlib/pin_map.h"

/* Enable I2C0 and Port B */
SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_I2C0));
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB));

/* Configure PB2=SCL, PB3=SDA */
GPIOPinConfigure(GPIO_PB2_I2C0SCL);
GPIOPinConfigure(GPIO_PB3_I2C0SDA);
GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);  // SCL = push-pull output
GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);       // SDA = open-drain

/* Initialize master at 400 kbps (fast mode) */
I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), true);  // true = 400 kbps

/* Single byte write to slave address 0x48 */
I2CMasterSlaveAddrSet(I2C0_BASE, 0x48, false);  // false = write
I2CMasterDataPut(I2C0_BASE, 0xAB);              // data byte
I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_SEND);
while (I2CMasterBusy(I2C0_BASE));
if (I2CMasterErr(I2C0_BASE) != I2C_MASTER_ERR_NONE)
    /* handle error */;

/* Single byte read from slave address 0x48 */
I2CMasterSlaveAddrSet(I2C0_BASE, 0x48, true);  // true = read
I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);
while (I2CMasterBusy(I2C0_BASE));
uint8_t data = I2CMasterDataGet(I2C0_BASE);

I2C_MASTER_CMD Values

CommandGeneratesDescription
I2C_MASTER_CMD_SINGLE_SENDSTART + STOPWrite 1 byte
I2C_MASTER_CMD_SINGLE_RECEIVESTART + STOPRead 1 byte
I2C_MASTER_CMD_BURST_SEND_STARTSTARTFirst byte of multi-byte write
I2C_MASTER_CMD_BURST_SEND_CONTMiddle bytes
I2C_MASTER_CMD_BURST_SEND_FINISHSTOPLast byte of multi-byte write
I2C_MASTER_CMD_BURST_RECEIVE_STARTSTARTFirst byte of multi-byte read
I2C_MASTER_CMD_BURST_RECEIVE_CONTMiddle bytes
I2C_MASTER_CMD_BURST_RECEIVE_FINISHSTOPLast byte of multi-byte read

Advanced Level — Deep Dive

Bare-Metal I2C Single Byte Write

#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_i2c.h"

void I2C0_WriteByte(uint8_t ui8Addr, uint8_t ui8Reg, uint8_t ui8Data)
{
    /* Wait until I2C master is not busy */
    while (HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_BUSY);

    /* Set slave address + WRITE (bit 0 = 0) */
    HWREG(I2C0_BASE + I2C_O_MSA) = (ui8Addr << 1) | 0;

    /* Put register address in data register */
    HWREG(I2C0_BASE + I2C_O_MDR) = ui8Reg;

    /* START + RUN (no STOP — more data follows) */
    HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_START | I2C_MCS_RUN;
    while (HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_BUSY);

    /* Put data byte */
    HWREG(I2C0_BASE + I2C_O_MDR) = ui8Data;

    /* RUN + STOP */
    HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_RUN | I2C_MCS_STOP;
    while (HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_BUSY);
}

Repeated START for Register Read

Many I2C sensors require a write phase (register address) followed by a repeated START and read phase:

uint8_t I2C0_ReadByte(uint8_t ui8Addr, uint8_t ui8Reg)
{
    /* Phase 1: Write register address */
    while (HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_BUSY);
    HWREG(I2C0_BASE + I2C_O_MSA) = (ui8Addr << 1) | 0;  // WRITE
    HWREG(I2C0_BASE + I2C_O_MDR) = ui8Reg;
    HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_START | I2C_MCS_RUN;
    while (HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_BUSY);

    /* Phase 2: Repeated START + READ */
    HWREG(I2C0_BASE + I2C_O_MSA) = (ui8Addr << 1) | 1;  // READ
    /* ACK=0 for single byte, STOP after this byte */
    HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_START | I2C_MCS_RUN | I2C_MCS_STOP;
    while (HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_BUSY);

    return (uint8_t)HWREG(I2C0_BASE + I2C_O_MDR);
}

SCL Frequency Configuration

SCL period = 2 × (1 + TPR) × (SCL_LP + SCL_HP) / f_clk

Standard (100 kbps): TPR = (80,000,000 / (2 × 10 × 100,000)) - 1 = 39
Fast (400 kbps): TPR = (80,000,000 / (2 × 10 × 400,000)) - 1 = 9

TivaWare calculates and sets TPR automatically via I2CMasterInitExpClk.

Gotchas

  • Pull-up resistors are mandatory — I2C lines are open-drain. Without external pull-ups (4.7 kΩ to 3.3 V), SCL and SDA will not rise.
  • SCL is push-pull on TM4C123 — configure SCL with GPIOPinTypeI2CSCL, not GPIOPinTypeI2C.
  • Clock stretching: slaves may hold SCL low to pause — the master handles this automatically.
  • Bus lockup: if a transaction is interrupted, the slave may hold SDA low. Recovery requires toggling SCL 9 times or a hardware reset.
  • Always check I2CMasterErr after each transaction — ignoring errors causes silent data corruption.

Step-by-Step Example

/*
 * i2c_mpu6050.c
 * Reads accelerometer X-axis from MPU-6050 via I2C0
 * Board  : TM4C123GXL EK LaunchPad
 * SDK    : TivaWare_C_Series-2.2.x
 * Wiring : PB2 → SCL, PB3 → SDA, 3.3V pull-ups (4.7kΩ)
 * Device : MPU-6050 (I2C address 0x68 when AD0=GND)
 */

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/i2c.h"
#include "driverlib/pin_map.h"
#include "utils/uartstdio.h"
#include "driverlib/uart.h"

#define MPU6050_ADDR   0x68
#define REG_PWR_MGMT_1 0x6B
#define REG_ACCEL_XOUT_H 0x3B

static void I2C0_Init(void);
static void I2C0_Write(uint8_t addr, uint8_t reg, uint8_t data);
static uint8_t I2C0_Read(uint8_t addr, uint8_t reg);
static void UART0_Init(void);

int main(void)
{
    /* Step 1: 80 MHz system clock */
    SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL |
                   SYSCTL_OSC_MAIN   | SYSCTL_XTAL_16MHZ);

    UART0_Init();   // for debug output
    I2C0_Init();    // I2C0 at 400 kbps

    /* Step 2: Wake up MPU-6050 (clear SLEEP bit in PWR_MGMT_1) */
    I2C0_Write(MPU6050_ADDR, REG_PWR_MGMT_1, 0x00);
    SysCtlDelay(SysCtlClockGet() / 30);  // 100 ms settling time

    UARTprintf("MPU-6050 Accelerometer Demo\r\n");

    while (1)
    {
        /* Step 3: Read high and low bytes of ACCEL_X */
        uint8_t xh = I2C0_Read(MPU6050_ADDR, REG_ACCEL_XOUT_H);
        uint8_t xl = I2C0_Read(MPU6050_ADDR, REG_ACCEL_XOUT_H + 1);
        int16_t accelX = (int16_t)((xh << 8) | xl);

        UARTprintf("ACCEL_X = %d (raw)\r\n", accelX);
        SysCtlDelay(SysCtlClockGet() / 10);  // 300 ms
    }
}

static void I2C0_Init(void)
{
    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_I2C0));
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB));

    GPIOPinConfigure(GPIO_PB2_I2C0SCL);
    GPIOPinConfigure(GPIO_PB3_I2C0SDA);
    GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);
    GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);
    I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), true);
}

static void I2C0_Write(uint8_t addr, uint8_t reg, uint8_t data)
{
    I2CMasterSlaveAddrSet(I2C0_BASE, addr, false);
    I2CMasterDataPut(I2C0_BASE, reg);
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_START);
    while (I2CMasterBusy(I2C0_BASE));
    I2CMasterDataPut(I2C0_BASE, data);
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
    while (I2CMasterBusy(I2C0_BASE));
}

static uint8_t I2C0_Read(uint8_t addr, uint8_t reg)
{
    I2CMasterSlaveAddrSet(I2C0_BASE, addr, false);
    I2CMasterDataPut(I2C0_BASE, reg);
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_START);
    while (I2CMasterBusy(I2C0_BASE));
    I2CMasterSlaveAddrSet(I2C0_BASE, addr, true);
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);
    while (I2CMasterBusy(I2C0_BASE));
    return (uint8_t)I2CMasterDataGet(I2C0_BASE);
}

static void UART0_Init(void)
{
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_UART0));
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOA));
    GPIOPinConfigure(GPIO_PA0_U0RX);
    GPIOPinConfigure(GPIO_PA1_U0TX);
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    UARTStdioConfig(0, 115200, SysCtlClockGet());
}

Summary

Key PointDetails
I2C modulesI2C0–I2C3
I2C0 pinsPB2 (SCL), PB3 (SDA)
SpeedsStandard 100 kbps, Fast 400 kbps, Fast-Plus 1 Mbps
Address space7-bit (up to 127 devices)
Pull-ups required4.7 kΩ to 3.3 V on SDA and SCL
SCL drivePush-pull (GPIOPinTypeI2CSCL)
SDA driveOpen-drain (GPIOPinTypeI2C)
Repeated STARTWrite register address, then restart for read
Error checkI2CMasterErr after every transaction

Next Chapter

Interrupt Controller (NVIC)

Share: