I2C Interface — Tiva C
- Eslam El Hefny
- Tutorials, Tiva c
- April 10, 2025
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, notGPIOPinTypeI2C. - 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
I2CMasterErrafter 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 |