I²C (Inter-Integrated Circuit) is a synchronous, half-duplex, multi-master/multi-slave serial bus that uses two lines -- SCL (clock) and SDA (data) -- to communicate between a controller and one or more peripherals on the same board or module. Originally developed at Philips Semiconductors (whose semiconductor division became NXP), it is widely used for short-distance communication with sensors, EEPROMs, RTCs, and other low-to-medium speed peripherals.
In practice
I²C is a staple of embedded peripheral interfacing. A typical design connects a microcontroller to devices such as temperature sensors, IMUs, OLED displays, EEPROMs, or real-time clocks -- all sharing the same two wires. Each device is addressed by a 7-bit (or, less commonly, 10-bit) address; the 7-bit scheme provides 128 possible addresses, of which roughly 16 are reserved, yielding approximately 112 addresses available for general use -- though the practical device count also depends on address conflicts and whether reserved addresses are used in a given context. Standard-mode runs at 100 kHz; Fast-mode at 400 kHz; Fast-mode Plus at 1 MHz; and High-speed mode at 3.4 MHz, though most MCU-class peripherals support only Standard and Fast modes. See "A Working Real Time Clock (RTC) Implementation" for a practical example of integrating an I²C peripheral into a real project.
Both SDA and SCL are open-drain lines and require pull-up resistors to the supply rail. Selecting the right pull-up value is a common source of reliability issues: too high a resistance causes slow rise times that fail at higher clock rates or with longer buses; too low a value wastes power and may violate the sink-current limits of the bus drivers. A starting point for many designs is 4.7 kohm for 100 kHz and 2.2 kohm for 400 kHz, adjusted based on bus capacitance. When mixing 3.3 V and 5 V devices, a level shifter is typically required unless the specific devices involved are rated as 5 V-tolerant or otherwise support the overlapping voltage range; "A simple working I2C (TWI) level shifter" covers one practical circuit for this.
On the software side, most MCUs expose an I²C controller peripheral that handles START/STOP conditions, address transmission, and ACK/NACK detection in hardware. Drivers are usually structured around a HAL or register-level API. When porting to an RTOS or a new platform such as Apache NuttX, scanning the bus first to verify device presence is a reliable sanity check -- "How to use I2C devices in (Apache) NuttX: Scanning for Devices" demonstrates this workflow.
Common pitfalls include bus lockup (when a peripheral holds SDA low after a power glitch, incomplete transaction, or misbehaving firmware), address collisions when two devices share the same fixed address, and missing or wrong pull-ups. Bus lockup recovery typically involves bit-banging up to nine clock pulses on SCL to flush the stuck peripheral, then issuing a STOP condition -- a recovery sequence worth building into any robust I²C driver, though some failure modes or controller implementations may require additional or different steps.
Discussed on EmbeddedRelated
Frequently asked
What pull-up resistor value should I use?
It depends on bus capacitance and clock rate. A common starting point is 4.7 kohm at 100 kHz and 2.2 kohm at 400 kHz for a short PCB trace with a few devices. As bus capacitance increases (longer traces, more devices), lower resistance values are needed to maintain fast enough rise times. The I²C specification requires rise times no longer than 1000 ns (Standard) or 300 ns (Fast), which constrains the RC product of
pull-up resistance and total bus capacitance.
How do I handle two devices with the same I²C address?
Many devices expose one or more address-select pins that let you shift the address by one or two bits, giving two to four unique addresses for the same part. If the device has no address pins and you need more than one, you have three options: place them on separate I²C buses, use an I²C multiplexer/switch IC (such as the TCA9548A), or choose a different device variant with a configurable address.
What causes I²C bus lockup and how do I recover?
Lockup can occur for several reasons: a peripheral held mid-transaction during a reset or power glitch may hold SDA low, but incomplete transactions, misbehaving firmware, or other bus faults can also cause hangs. A common recovery technique is to manually clock SCL up to nine times (enough to shift out any stuck byte) until SDA is released, then issue a STOP. Many
MCU HAL libraries include a bus-recovery routine; on bare-metal targets you may need to implement it yourself using
GPIO bit-banging before re-initializing the I²C peripheral. Note that some failure modes or controller implementations may require additional or different recovery steps.
What is the difference between I²C master/slave and controller/target terminology?
They refer to the same roles. The I²C specification historically used 'master' and 'slave'; the NXP specification update in UM10204 rev. 7 (published 2021) introduced 'controller' and 'target' as preferred terms. However, older documents, many datasheets, and much existing driver code continue to use master/slave, so both sets of terms remain common in practice and are interchangeable in meaning.
When should I choose I²C over SPI?
I²C is a good fit when you need to connect multiple low-to-medium speed peripherals with minimal pin count and the two-wire bus topology is convenient.
SPI is generally preferred when you need higher throughput (it is full-duplex and typically runs at several MHz to tens of MHz), simpler driver logic, or deterministic timing -- at the cost of one dedicated chip-select line per device. Many sensors are available in both interfaces; the choice often comes down to pin budget and the speed requirements of the application.
Differentiators vs similar concepts
I²C is often compared with
SPI and
UART. Versus SPI: I²C uses only two wires regardless of the number of devices (vs. SPI's separate chip-select per device), but is half-duplex and slower in practice. SPI has no formal addressing scheme and no
ACK mechanism. Versus UART: UART is asynchronous and point-to-point; I²C is synchronous and multi-drop. I²C is also sometimes confused with SMBus, a related protocol defined by Intel that shares much of its electrical and signaling foundation with I²C but adds requirements such as timeout rules, a packet error code (PEC), and tighter electrical limits. The relationship is not a clean superset: many I²C devices are not SMBus-compliant, and many SMBus devices work on an I²C bus, but the two protocols overlap rather than one fully containing the other.