I2C Communication – Complete Professional Tutorial

šŸ“Œ Introduction

Inter-Integrated Circuit (I2C) is a synchronous, multi-master, multi-slave communication protocol widely used for short-distance communication between microcontrollers and peripherals like sensors, EEPROMs, RTCs, and displays.


🧠 Key Concepts


🌐 I²C Communication Simulator

šŸ”— Open I2C Simulator

šŸ”Œ Basic I2C Connection Diagram

šŸŽžļø I2C Working Animation


āš™ļø How I2C Works

1. Start Condition

Master pulls SDA LOW while SCL is HIGH

2. Address Frame

7-bit or 10-bit address + R/W

3. ACK/NACK

Receiver pulls SDA LOW

4. Data Transfer

8-bit packets

5. Stop Condition

SDA HIGH while SCL HIGH


šŸ“¦ Data Format

| START | ADDRESS (7/10 bit) | R/W | ACK | DATA (8-bit) | ACK | STOP |

šŸ”¢ 7-bit vs 10-bit Addressing

7-bit: Most common

10-bit: Rare


šŸ“„ 8-bit vs 16-bit Register Addressing

Devices may use internal registers (8-bit or 16-bit)


šŸ” Read & Write Flow

Write

Start → Address → Register → Data → Stop

Read

Start → Address → Register → Restart → Read → Stop


🧩 ESP-IDF I2C Examples

āš™ļø Initialization

#include "driver/i2c.h"
#include "esp_err.h"
#include <string.h>

#define I2C_MASTER_SCL_IO         22
#define I2C_MASTER_SDA_IO         21
#define I2C_MASTER_PORT           I2C_NUM_0
#define I2C_MASTER_FREQ_HZ        100000
#define I2C_MASTER_TIMEOUT_MS     1000

static esp_err_t i2c_master_init(void)
{
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ,
    };

    ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_PORT, &conf));
    return i2c_driver_install(I2C_MASTER_PORT, conf.mode, 0, 0, 0);
}

šŸ”¹ 8-bit Register + 8-bit Data

esp_err_t i2c_write_reg8_data8(uint8_t dev_addr, uint8_t reg_addr, uint8_t data)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr, true);
    i2c_master_write_byte(cmd, data, true);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    return ret;
}

esp_err_t i2c_read_reg8_data8(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data)
{
    if (!data) return ESP_ERR_INVALID_ARG;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr, true);
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);
    i2c_master_read_byte(cmd, data, I2C_MASTER_NACK);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    return ret;
}

šŸ”¹ 8-bit Register + 16-bit Data

esp_err_t i2c_write_reg8_data16(uint8_t dev_addr, uint8_t reg_addr, uint16_t data)
{
    uint8_t tx[2] = { data >> 8, data & 0xFF };
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr, true);
    i2c_master_write(cmd, tx, 2, true);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    return ret;
}

esp_err_t i2c_read_reg8_data16(uint8_t dev_addr, uint8_t reg_addr, uint16_t *data)
{
    if (!data) return ESP_ERR_INVALID_ARG;
    uint8_t rx[2];
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr, true);
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);
    i2c_master_read(cmd, rx, 1, I2C_MASTER_ACK);
    i2c_master_read_byte(cmd, &rx[1], I2C_MASTER_NACK);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    if (ret == ESP_OK) *data = (rx[0] << 8) | rx[1];
    return ret;
}

šŸ”¹ 16-bit Register + 8-bit Data

esp_err_t i2c_write_reg16_data8(uint8_t dev_addr, uint16_t reg_addr, uint8_t data)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr >> 8, true);
    i2c_master_write_byte(cmd, reg_addr & 0xFF, true);
    i2c_master_write_byte(cmd, data, true);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    return ret;
}

esp_err_t i2c_read_reg16_data8(uint8_t dev_addr, uint16_t reg_addr, uint8_t *data)
{
    if (!data) return ESP_ERR_INVALID_ARG;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr >> 8, true);
    i2c_master_write_byte(cmd, reg_addr & 0xFF, true);
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);
    i2c_master_read_byte(cmd, data, I2C_MASTER_NACK);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    return ret;
}

šŸ”¹ 16-bit Register + 16-bit Data

esp_err_t i2c_write_reg16_data16(uint8_t dev_addr, uint16_t reg_addr, uint16_t data)
{
    uint8_t tx[2] = { data >> 8, data & 0xFF };
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr >> 8, true);
    i2c_master_write_byte(cmd, reg_addr & 0xFF, true);
    i2c_master_write(cmd, tx, 2, true);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    return ret;
}

esp_err_t i2c_read_reg16_data16(uint8_t dev_addr, uint16_t reg_addr, uint16_t *data)
{
    if (!data) return ESP_ERR_INVALID_ARG;
    uint8_t rx[2];
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr >> 8, true);
    i2c_master_write_byte(cmd, reg_addr & 0xFF, true);
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);
    i2c_master_read(cmd, rx, 1, I2C_MASTER_ACK);
    i2c_master_read_byte(cmd, &rx[1], I2C_MASTER_NACK);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    if (ret == ESP_OK) *data = (rx[0] << 8) | rx[1];
    return ret;
}

šŸ”¹ Multi-byte

esp_err_t i2c_read_reg8_buffer(uint8_t dev_addr, uint8_t reg_addr, uint8_t *buf, size_t len)
{
    if (!buf || !len) return ESP_ERR_INVALID_ARG;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr, true);
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);
    if (len > 1) i2c_master_read(cmd, buf, len - 1, I2C_MASTER_ACK);
    i2c_master_read_byte(cmd, &buf[len - 1], I2C_MASTER_NACK);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_PORT, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    return ret;
}

🧩 STM32 HAL Examples

šŸ”¹ 8-bit Address Mode

HAL_StatusTypeDef stm32_write_mem8_data8(uint16_t dev_addr, uint16_t mem_addr, uint8_t data)
{
    return HAL_I2C_Mem_Write(&hi2c1, dev_addr<<1, mem_addr, I2C_MEMADD_SIZE_8BIT, &data, 1, 100);
}

HAL_StatusTypeDef stm32_read_mem8_data8(uint16_t dev_addr, uint16_t mem_addr, uint8_t *data)
{
    return HAL_I2C_Mem_Read(&hi2c1, dev_addr<<1, mem_addr, I2C_MEMADD_SIZE_8BIT, data, 1, 100);
}

šŸ”¹ 8-bit Address + 16-bit Data

HAL_StatusTypeDef stm32_write_mem8_data16(uint16_t dev_addr, uint16_t mem_addr, uint16_t data)
{
    uint8_t tx[2]={data>>8,data&0xFF};
    return HAL_I2C_Mem_Write(&hi2c1, dev_addr<<1, mem_addr, I2C_MEMADD_SIZE_8BIT, tx, 2, 100);
}

HAL_StatusTypeDef stm32_read_mem8_data16(uint16_t dev_addr, uint16_t mem_addr, uint16_t *data)
{
    uint8_t rx[2];
    HAL_I2C_Mem_Read(&hi2c1, dev_addr<<1, mem_addr, I2C_MEMADD_SIZE_8BIT, rx, 2, 100);
    *data=(rx[0]<<8)|rx[1];
    return HAL_OK;
}

šŸ”¹ 16-bit Address Mode

HAL_StatusTypeDef stm32_write_mem16_data8(uint16_t dev_addr, uint16_t mem_addr, uint8_t data)
{
    return HAL_I2C_Mem_Write(&hi2c1, dev_addr<<1, mem_addr, I2C_MEMADD_SIZE_16BIT, &data, 1, 100);
}

HAL_StatusTypeDef stm32_read_mem16_data8(uint16_t dev_addr, uint16_t mem_addr, uint8_t *data)
{
    return HAL_I2C_Mem_Read(&hi2c1, dev_addr<<1, mem_addr, I2C_MEMADD_SIZE_16BIT, data, 1, 100);
}

šŸ”¹ 16-bit Address + 16-bit Data

HAL_StatusTypeDef stm32_write_mem16_data16(uint16_t dev_addr, uint16_t mem_addr, uint16_t data)
{
    uint8_t tx[2]={data>>8,data&0xFF};
    return HAL_I2C_Mem_Write(&hi2c1, dev_addr<<1, mem_addr, I2C_MEMADD_SIZE_16BIT, tx, 2, 100);
}

HAL_StatusTypeDef stm32_read_mem16_data16(uint16_t dev_addr, uint16_t mem_addr, uint16_t *data)
{
    uint8_t rx[2];
    HAL_I2C_Mem_Read(&hi2c1, dev_addr<<1, mem_addr, I2C_MEMADD_SIZE_16BIT, rx, 2, 100);
    *data=(rx[0]<<8)|rx[1];
    return HAL_OK;
}

⚔ Important Notes


šŸ› ļø Troubleshooting


šŸ”¬ Internal Handling

8-bit address → 1 byte

16-bit address → 2 bytes (MSB first)

16-bit data → MSB first


šŸ“š Summary


šŸš€ Logic Analyzer