I2C (Inter-Integrated Circuit) is a synchronous serial bus on two wires — SDA (data) and SCL (clock) — where several devices live together, each identified by a 7-bit address. Where UART is limited to point-to-point, I2C networks a large number of devices onto those same two wires. In MicroPython you reach it through the I2C class of the [[micropython-modules-en|machine]] module, which includes a built-in scanner (scan()), where Arduino asks for a dedicated sketch. It is the bus of the more advanced sensors (BMP280, MPU6050), of displays (SSD1306 OLED) and of clocks (DS3231).

What is it for?

Three roles. Multiplexing several sensors onto 2 pins, precious when GPIO pins are scarce. Connecting a display without tying up pins, an I2C OLED asking for only 2. Reading from and writing to utility components (RTC, external EEPROM, GPIO expander).

Step by step

Four steps: create the I2C object, wire it with pull-ups, scan the addresses, read a device.

1. Create the I2C object

The Pico has two I2C controllers (I2C0, I2C1), assignable to compatible pins:

from machine import I2C, Pin
i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)

(If you need arbitrary pins: SoftI2C, emulated in software.)

2. Wire it with pull-ups

I2C needs pull-up resistors to VCC on SDA and SCL (≈ 4.7 kΩ, or 2.2 kΩ at 3.3 V). Most modules include them: nothing to add for a first try with a single module.

ModulePico
VCC3.3 V (or 5 V depending on the module)
GNDGND
SDAGP4
SCLGP5

I2C bus wiring on a Pico: SDA on GP4, SCL on GP5, pull-ups to 3.3 V|600

With several modules, their pull-ups end up in parallel: the equivalent resistance drops and the bus can no longer pull back up. You then keep a single active pair (see the “too many” pitfall).

Pull-ups in parallel: on the left a single module (one pull-up drives SDA, the bus goes down to 0 properly); on the right several modules whose parallel pull-ups drop the equivalent resistance so the bus no longer holds.|620

3. Scan the addresses (built in)

Before any application code, i2c.scan() lists the addresses that answer — no need for a separate scanner sketch:

from machine import I2C, Pin
i2c = I2C(0, scl=Pin(5), sda=Pin(4))
print([hex(a) for a in i2c.scan()])     # e.g. ['0x76']

If the list is empty: check the wiring (SDA/SCL not swapped, pull-ups, supply). Typical addresses: SSD1306 OLED = 0x3C, BMP280 = 0x76/0x77, DS3231/MPU6050 = 0x68.

4. Read a device with its library

Once the address is confirmed, install the driver (see libraries), import it, read. At the lowest level the raw API is i2c.readfrom(addr, n) / i2c.writeto(addr, buf) / i2c.readfrom_mem(addr, reg, n), but a library wraps up those accesses almost every time.

Example — Reading a BMP280 (pressure and temperature)

Library: a MicroPython bmp280 driver (through Thonny’s package manager or mip, see libraries).

from machine import I2C, Pin
from bmp280 import BMP280       # name depends on the driver installed
from time import sleep
 
i2c = I2C(0, scl=Pin(5), sda=Pin(4))
print("Addresses:", [hex(a) for a in i2c.scan()])
 
capteur = BMP280(i2c)           # default address 0x76
 
while True:
    print("T =", capteur.temperature, "°C   P =", capteur.pressure, "Pa")
    sleep(1)

Breathe on the module: the temperature rises. (The exact API depends on the driver you choose. The scan() stays the common diagnostic step.)

Pitfalls

Wrong address. A driver set to 0x77 while the module is at 0x76: the device cannot be found. Always start with scan().

Pull-ups missing or too many. With no pull-up the bus floats. Too many pull-up modules in parallel → the equivalent resistance gets too low and the bus no longer holds. Disable the pull-ups on every module but one.

Incompatible VCC level. A 3.3 V module powered at 5 V → destruction. For mixed buses, a two-way level shifter (a PCA9306, or a BSS138 circuit).

Address clash. Two identical sensors at the same address (0x68) → the bus is corrupted. Move an address strap, or use an I2C multiplexer (TCA9548A).

SDA/SCL swapped. scan() finds nothing. Check the pin assignment.

Cables too long. I2C tolerates ~1-2 m in standard mode with correctly sized pull-ups. Beyond that, corrupted frames. Shorten, screen, or move to a differential bus.

Special case — Several buses, or the same address

The Pico has two controllers (I2C0 and I2C1 on separate pins): you spread the load, or isolate two devices sharing an address. An alternative for several identical devices on a single bus: a TCA9548A multiplexer (8 channels).

Where it fits in the project

  • Step 2 of the proof of concept phase — every I2C sensor or display is validated in two moves: scan() for the address, a library example for the reading.
  • Step 4 of the concept phase — I2C imposes itself from 2 or more devices, or when GPIO pins are scarce.

i2c.scan() is the bus’s universal diagnostic tool: in a few seconds it separates a hardware problem (nothing answers) from a software one (the device answers but the reading fails).

See also