all
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

Term Meaning
SDA Serial Data line — bidirectional, open-drain
SCL Serial Clock line — driven by master
START SDA falls while SCL is high — begins transaction
STOP SDA rises while SCL is high — ends transaction
ACK Acknowledge — slave pulls SDA low after each byte
NACK No Acknowledge — SDA remains high, indicates error
Repeated START New START without STOP — changes direction

Intermediate Level — How It Works

I2C Module Addresses

Module SCL Pin SDA Pin Base Address
I2C0 PB2 PB3 0x40020000
I2C1 PA6 PA7 0x40021000
I2C2 PE4 PE5 0x40022000
I2C3 PD0 PD1 0x40023000

Key Registers

Register Offset Description
I2CMSA 0x000 Master Slave Address (7-bit addr + R/W bit)
I2CMCS 0x004 Master Control/Status (command bits / status flags)
I2CMDR 0x008 Master Data Register (write data / read data)
I2CMTPR 0x00C Master Timer Period (SCL frequency)
I2CMIMR 0x010 Master Interrupt Mask
I2CMRIS 0x014 Master Raw Interrupt Status
I2CMMIS 0x018 Master Masked Interrupt Status
I2CMICR 0x01C Master Interrupt Clear
I2CMCR 0x020 Master Configuration (MFE = master enable)

I2CMCS Write bits (command):

Bit Name Function
0 RUN Begin the transaction
1 START Generate START condition
2 STOP Generate STOP condition
3 ACK Generate ACK on receive
4 HS High-speed mode

I2CMCS Read bits (status):

Bit Name Meaning
0 BUSY Transaction in progress
1 ERROR Error occurred
2 ADRACK Address NACK received
3 DATACK Data NACK received
4 ARBLST Arbitration 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

Command Generates Description
I2C_MASTER_CMD_SINGLE_SEND START + STOP Write 1 byte
I2C_MASTER_CMD_SINGLE_RECEIVE START + STOP Read 1 byte
I2C_MASTER_CMD_BURST_SEND_START START First byte of multi-byte write
I2C_MASTER_CMD_BURST_SEND_CONT Middle bytes
I2C_MASTER_CMD_BURST_SEND_FINISH STOP Last byte of multi-byte write
I2C_MASTER_CMD_BURST_RECEIVE_START START First byte of multi-byte read
I2C_MASTER_CMD_BURST_RECEIVE_CONT Middle bytes
I2C_MASTER_CMD_BURST_RECEIVE_FINISH STOP Last 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 Point Details
I2C modules I2C0–I2C3
I2C0 pins PB2 (SCL), PB3 (SDA)
Speeds Standard 100 kbps, Fast 400 kbps, Fast-Plus 1 Mbps
Address space 7-bit (up to 127 devices)
Pull-ups required 4.7 kΩ to 3.3 V on SDA and SCL
SCL drive Push-pull (GPIOPinTypeI2CSCL)
SDA drive Open-drain (GPIOPinTypeI2C)
Repeated START Write register address, then restart for read
Error check I2CMasterErr after every transaction

Next Chapter

Interrupt Controller (NVIC)

Share: