I²C

Authors: Johannes Fleiner, Sebastion Ebenhöh, Eikya Lagisetti 2025 - 2026

Introduction

The Inter-Integrated Circuit (I²C) bus is a serial communication bus used to transfer data between multiple connected bus participants. Communication is performed via two serial lines, referred to as the Serial Data Line (SDA) and the Serial Clock Line (SCL). Each bus participant is assigned a unique address and operates either as a receiver or a transmitter.

Within the PicoNut project, the I²C module is implemented as an independent peripheral module and is connected to the PicoNut processor via the Wishbone bus. Software access to the I²C module is exclusively performed through the provided address-based register interface.

Specifications

Concept & Architecture

The module features a strict encapsulation of functionalities, divided into specialized subcomponents: a Wishbone Slave Interface, a central Finite State Machine (Controller) for protocol handling, and separate units for Data (SDA) and Clock (SCL) control. The system routes the CPU instructions via the Wishbone bus to the I2C modul with a unidirectional signal interface. This modul is enclosed by a VHDL wrapper that converts the internal split signals into physical bidirectional Tri-State levels (SDA, SCL) to ensure correct synthesis and electrical behavior.

../../_images/modul_global.svg

Fig. 33 Overview of the I2C Module in PicoNut Project.

Technical Specifications

Operating Mode:

  • Operates exclusively in Single-Master mode (Multi-Master is not supported).

  • Supports both Master-Transmitter and Master-Receiver functionalities.

Bus Timing:

  • Designed for Standard-Mode (SM) operation.

  • Supports data rates of up to 100 kHz.

Addressing: Supports the 7-Bit addressing standard.

Protocol Features & Error Handling

Signaling: Capable of generating Start and Stop conditions.

ACK/NACK: Performs automatic generation and verification of ACK/NACK bits after every byte transfer.

Flow Control: Supports Clock Stretching, allowing the Master to pause transmission if a Slave holds the SCL line LOW.

Error Detection: Includes detection of Acknowledge Failures (AF) when an addressed slave fails to respond.

I²C-Registers

The register layout and functionality are strongly oriented towards the STM32 I²C peripheral design. STM32F4 datasheet.

group i2c_defs

I²C register overview

Offset

Register

Name

Description

0x00

I2C_CR1

Control Register 1

Core control of the I²C peripheral (enable, START/STOP, ACK).

0x04

I2C_CR2

Control Register 2

Peripheral clock configuration and interrupt control.

0x08

I2C_OAR1

Own Address Register

Primary own address configuration (slave mode).

0x0C

I2C_OAR2

Own Address Register

Secondary own address configuration (slave mode).

0x10

I2C_DR

Data Register

8-bit data register for transmit and receive operations.

0x14

I2C_SR1

Status Register 1

Event and error flags indicating I²C bus status.

0x18

I2C_SR2

Status Register 2

Bus state and master/slave mode indication.

0x1C

I2C_CCR

Clock Control Reg.

SCL clock prescaler and speed mode configuration.

0x20

I2C_TRISE

TRISE Register

Maximum allowed SDA/SCL rise time (Standard Mode).

All registers are memory-mapped and accessed via the Wishbone bus.

Control Register 1 (I2C_CR1)

Bit

Name

Description

0

PE

Peripheral enable

8

START

Generate START condition (master mode)

9

STOP

Generate STOP condition (master mode)

10

ACK

Enable acknowledge generation

Control Register 2 (I2C_CR2)

Bits

Name

Description

5:0

FREQ[5:0]

Peripheral clock frequency in MHz (basis for timing generation)

Data Register (I2C_DR)

Bits

Name

Description

7:0

DR

Transmit address and transmit/receive data byte

Status Register 1 (I2C_SR1)

Bit

Name

Description

0

SB

START condition generated

1

ADDR

Address sent and ACK/NACK received

2

BTF

Byte transfer finished

6

RXNE

Receive buffer not empty

7

TXE

Transmit data register empty

10

AF

Acknowledge failure (NACK received)

Status Register 2 (I2C_SR2)

Bit

Name

Description

0

MSL

Master/slave mode

1

BUSY

Bus busy indication

2

TRA

Transmitter/receiver mode

Clock Control Register (I2C_CCR)

Bit

Name

Description

15

FS

I²C speed selection (0 = Standard Mode)

11:0

CCR

Clock control prescaler value

TRISE Register (I2C_TRISE)

Bits

Name

Description

5:0

TRISE

Maximum allowed SDA/SCL rise time

Submodules

The I2C module consists of the following components:

../../_images/i2c_hardware.svg

Fig. 34 Overview of the I2C Hardware modules.

SC_MODULE(m_i2c)

I2C module combining all submodules and wiring their signals.

This module integrates:

  • m_i2c_wishbone (register & Wishbone interface),

  • m_i2c_controller (I2C controller FSM),

  • m_i2c_clk_control (SCL generation, timing),

  • m_i2c_data_control (I2C data path, SDA handling).

Parameters:
  • clk[in] System clock for the I2C peripheral.

  • reset[in] Asynchronous reset, active high.

  • sda_i[in] Input sample of the SDA line (from pad).

  • sda_o[out] SDA output drive value (to pad).

  • sda_oe[out] SDA output enable (drive/open-drain control).

  • scl_i[in] Input sample of the SCL line (from pad).

  • scl_o[out] SCL output drive value (to pad).

  • scl_oe[out] SCL output enable (drive/open-drain control).

Wishbone

SC_MODULE(m_i2c_wishbone)

This SystemC module implements the Wishbone slave interface and the register file of the I2C peripheral.

It translates Wishbone bus accesses (adr/dat/we/stb/cyc/sel) into:

  • updates of I2C configuration registers (CR1, CR2, CCR, TRISE, FLTR),

  • status readback for SR1/SR2 and DR,

  • control signals for the I2C controller / data / clock submodules

Wishbone Ports (adr/dat/we/stb/cyc/sel) are included in pn_wishbone_slave_t wb_slave Connection to I2C submodules:

Parameters:
  • clear_regs_in[in] : Request to clear all I2C-related registers.

  • cr1_enable_out[out] : Drives CR1.PE (peripheral enable).

  • cr1_start_out[out] : Drives CR1.START (start generation).

  • cr1_stop_out[out] : Drives CR1.STOP (stop generation).

  • cr1_ack_out[out] : Drives CR1.ACK (ACK control for RX).

  • clear_cr1_start_in[in] : Clears CR1.START after controller has accepted it.

  • clear_cr1_stop_in[in] : Clears CR1.STOP after STOP is finished.

  • sr1_start_bit_in[in] : SR1.SB (start bit).

  • sr1_addr_bit_in[in] : SR1.ADDR (address sent/matched).

  • sr1_txe_in[in] : SR1.TXE (TX buffer empty).

  • sr1_btf_in[in] : SR1.BTF (byte transfer finished).

  • sr1_af_in[in] : SR1.AF (ACK failure).

  • sr1_rxne_in[in] : SR1.RXNE (RX buffer not empty).

  • CPU_read_SR1_out[out] : Pulse when CPU (via WB) reads SR1.

  • CPU_read_SR2_out[out] : Pulse when CPU (via WB) reads SR2.

  • sr2_msl_in[in] : SR2.MSL (master/slave).

  • sr2_busy_in[in] : SR2.BUSY (bus busy).

  • sr2_tra_in[in] : SR2.TRA (transmitter/receiver).

  • tx_data_out[out] : Data value to be transmitted.

  • dr_out[out] : Value written to DR from CPU.

  • CPU_write_DR_out[out] : Pulse when CPU writes DR.

  • CPU_read_DR_out[out] : Pulse when CPU reads DR.

  • enable_dr_write_in[in] : Data path allows DR update.

  • shift_data_in[in] : Data shifted in from SDA during RX.

  • ccr_out[out] : CCR register value (clock control).

  • trise_out[out] : TRISE register value (rise time).

Controller

SC_MODULE(m_i2c_controller)

This SystemC module implements the finite state machine (FSM) that controls the PicoNut I2C master. It observes CR1/SR1/SR2/DR register interfaces and coordinates submodules for Clock and Data Control.

Parameters:
  • clk[in] Clock input.

  • rst[in] Asynchronous reset (active high).

  • clear_regs_out[out] Clears controller-internal register flags.

  • cr1_enable_in[in] CR1.PE – peripheral enable.

  • cr1_start_in[in] CR1.START – CPU requests START.

  • cr1_stop_in[in] CR1.STOP – CPU requests STOP.

  • cr1_ack_in[in] CR1.ACK – ACK generation setting for RX.

  • clear_cr1_start_out[out] Clears CR1.START after START is executed.

  • clear_cr1_stop_out[out] Clears CR1.STOP after STOP is finished.

  • sr1_start_bit_out[out] Controls SR1.SB (start bit detected).

  • sr1_addr_bit_out[out] Controls SR1.ADDR (address matched).

  • sr1_txe_out[out] Controls SR1.TXE (data register empty).

  • sr1_btf_out[out] Controls SR1.BTF (byte transfer finished).

  • sr1_af_out[out] Controls SR1.AF (ack failure).

  • sr1_rxne_out[out] Controls SR1.RXNE (byte received).

  • CPU_read_SR2_in[in] CPU reads SR2 (clears ADDR sequence).

  • CPU_read_SR1_in[in] CPU reads SR1 (used to advance states).

  • sr2_msl_out[out] Controls SR2.MSL (master/slave mode).

  • sr2_busy_out[out] Controls SR2.BUSY (bus busy).

  • sr2_tra_out[out] Controls SR2.TRA (TX/RX mode).

  • dr_in[in] CPU-written data register value.

  • scl_ready_in[in] SCL module ready for next command.

  • init_master_out[out] Initialize master mode in clkControl.

  • scl_on_out[out] Enable SCL generation.

  • master_stretch_out[out] Enable clock stretching from master.

  • stop_finished_in[in] STOP condition finished.

  • start_finished_in[in] START condition finished.

  • sending_data_in[in] Data path is actively sending a byte.

  • ack_received_in[in] ACK received from slave.

  • nack_received_in[in] NACK received from slave.

  • send_ack_out[out] Request data path to send ACK.

  • send_nack_out[out] Request data path to send NACK.

  • start_rx_out[out] Start a receive transfer.

  • byte_loaded_in_DR_in[in] Byte transferred into DR.

  • shift_loaded_in[in] Shift register is ready.

  • generate_start_cond_out[out] Generate a START on the bus.

  • start_tx_out[out] Request start of TX transfer.

  • generate_stop_cond_out[out] Generate a STOP on the bus.

  • CPU_write_DR_in[in] CPU writes a byte into DR.

  • CPU_read_DR_in[in] CPU read access to DR.

Data Control

SC_MODULE(m_i2c_data_control)

This SystemC module implements the I2C data control. It handles data transfer on the SDA line, including sending and receiving bits, ACK/NACK handling, and providing received bytes to the data register (DR).

Parameters:
  • clk[in] Clock input.

  • reset[in] Reset (active high).

  • generate_start_cond_in[in] Request START condition.

  • stop_ready_in[in] Request STOP condition.

  • start_tx_in[in] Start transmit of one byte.

  • start_rx_in[in] Start receive of one byte.

  • tx_data_in[in] Byte to transmit (loaded into shift register).

  • send_ack_in[in] Send ACK after receiving a byte.

  • send_nack_in[in] Send NACK after receiving a byte.

  • shift_loaded_out[out] Shift register/byte phase is ready.

  • ack_received_out[out] ACK detected from slave.

  • nack_received_out[out] NACK detected from slave.

  • start_ready_out[out] START condition finished.

  • sending_data_out[out] Bits are transfered over SDA

  • byte_loaded_in_DR_out[out] Received byte is available for DR.

  • shift_data_out[out] Received byte forwarded to DR.

  • enable_dr_write_out[out] Enable write into DR.

  • data_clk_pulse_write_in[in] Bit timing pulse for driving SDA (write phase).

  • data_clk_pulse_read_in[in] Bit timing pulse for sampling SDA (read phase).

  • sda_in[in] SDA input.

  • sda_out[out] SDA output value.

Clock Control

SC_MODULE(m_i2c_clk_control)

This SystemC module handles the low-level timing and generation of the I2C clock signal (SCL). It calculates high/low periods based on CCR/TRISE register settings, manages clock stretching (synchronization), and controls the precise timing sequences for START and STOP conditions.

Parameters:
  • clk[in] Clock input.

  • reset[in] Asynchronous reset (active high).

  • ccr_in[in] CCR register value.

  • trise_in[in] TRISE register value.

  • scl_on_in[in] Enables SCL generation.

  • master_stretch_in[in] Request to force a clock stretch.

  • init_master_in[in] Initialization signal for master mode.

  • scl_ready_out[out] Indicates SCL Master is ready for START condition.

  • start_ready_in[in] Trigger to begin START condition.

  • start_finished_out[out] Indicates START sequence is completed.

  • generate_stop_in[in] Trigger to begin STOP condition.

  • stop_ready_out[out] Indicates first part of STOP generation is ready.

  • stop_finished_out[out] Indicates STOP sequence is fully completed.

  • scl_in[in] SCL line input.

  • scl_out[out] SCL line output.

  • data_clk_pulse_read_out[out] Sample trigger pulse for RX data.

  • data_clk_pulse_write_out[out] Shift trigger pulse for TX data.

Driver

group i2c_driver

I²C driver interface.

Enums

enum i2c_status_t

Status codes returned by I2C driver functions.

Values:

enumerator I2C_OK = 0

Operation successful.

enumerator I2C_ERR_INVALID = -1

Invalid argument (e.g. null pointer).

enumerator I2C_ERR_BUSY = -3

I2C bus is currently busy.

enumerator I2C_ERR_NACK = -4

NACK received from slave device.

enumerator I2C_ERR_BUS = -5

General bus error.

Functions

i2c_status_t i2c_init(i2c_t *self, uint32_t system_clk_hz)

Initializes the I2C peripheral.

Configures frequency, CCR, TRISE and enables the I2C peripheral. The driver instance must contain a valid base address and SCL speed.

Parameters:
  • self[inout] Driver instance.

  • system_clk_hz[in] System clock frequency in Hz.

Returns:

I2C_OK on success, otherwise error code.

i2c_status_t i2c_de_init(i2c_t *self)

Deinitializes the I2C peripheral.

Disables the I2C module (PE = 0). The driver instance remains valid.

Parameters:

self[inout] Driver instance.

Returns:

I2C_OK on success or I2C_ERR_INVALID.

i2c_status_t i2c_start(i2c_t *self)

Generates an I2C START condition.

Waits until the START condition is acknowledged (SB flag).

Parameters:

self[inout] Driver instance.

Returns:

I2C_OK if START succeeded, otherwise error code.

i2c_status_t i2c_send_addr(i2c_t *self, uint8_t slave_addr, bool read)

Sends a 7-bit slave address with R/W bit.

Sets and clears the ADDR flag.

Parameters:
  • self[inout] Driver instance.

  • slave_addr[in] 7-bit slave address.

  • read[in] true = read operation, false = write.

Returns:

I2C_OK or an error code.

i2c_status_t i2c_stop(i2c_t *self)

Generates an I2C STOP condition.

Releases the bus by setting STOP=1.

Parameters:

self[inout] Driver instance.

Returns:

I2C_OK or I2C_ERR_INVALID.

i2c_status_t i2c_send_data(i2c_t *self, uint8_t byte)

Sends a single data byte.

Writes to DR and waits until TXE=1.

Parameters:
  • self[inout] Driver instance.

  • byte[in] Byte to transmit.

Returns:

I2C_OK or error code.

i2c_status_t i2c_recv_data(i2c_t *self, uint8_t *byte, uint8_t size)

Receives one or more bytes from an I2C slave.

Handles ACK/NACK depending on the remaining number of bytes.

Parameters:
  • self[inout] Driver instance.

  • byte[out] Pointer to receive buffer.

  • size[in] Number of bytes to read.

Returns:

I2C_OK or error code.

i2c_status_t i2c_master_send(i2c_t *self, uint8_t slave_addr, const uint8_t *data, uint32_t size)

Performs a complete I2C write transaction.

Sequence: START -> address (write) -> data bytes -> STOP.

Parameters:
  • self[inout] Driver instance.

  • slave_addr[in] 7-bit slave address.

  • data[in] Pointer to transmit buffer.

  • size[in] Number of bytes to send.

Returns:

I2C_OK or an error code.

i2c_status_t i2c_master_recv(i2c_t *self, uint8_t slave_addr, uint8_t *data, uint32_t size)

Performs a complete I2C read transaction.

Sequence: START -> address (read) -> receive bytes → STOP.

Parameters:
  • self[inout] Driver instance.

  • slave_addr[in] 7-bit slave address.

  • data[out] Pointer to receive buffer.

  • size[in] Number of bytes to read.

Returns:

I2C_OK or an error code.

struct i2c_t
#include <i2c_driver.h>

I2C driver instance structure.

Param base_addr:

[in] Base address of the I2C peripheral.

Param scl_speed_hz:

[in] Desired SCL clock speed in Hz.

Hardware setup of the temperature demonstrator (WS2025/26)

The hardware can be wired manually using a breadboard:

  • Slave_1: SSD1306 (OLED)

  • Slave_2: BME280 or BMP280

../../_images/i2c_schematic.svg

Fig. 35 Overview of the I2C Hardware setup.