Skip to content

What is I2C?

I2C (Inter-Integrated Circuit, pronounced “eye-two-see”) is a communication protocol that connects multiple devices using just two wires — one for data (SDA) and one for a clock signal (SCL). It’s how your tinyCore talks to its built-in IMU, and how you’ll connect most external sensors and displays through the QWIIC ports.

Every time you plug a sensor or display into one of the tinyCore’s QWIIC connectors, you’re using I2C. It’s the most common way to add capabilities to your board — temperature sensors, OLED screens, gas detectors, distance sensors, and hundreds more all use I2C. Understanding how it works will help you troubleshoot when a sensor doesn’t respond (and it will happen).

I2C uses two shared lines:

  • SDA (Serial Data) — carries data bits in both directions
  • SCL (Serial Clock) — carries a clock signal so all devices stay in sync

Your tinyCore is the controller. It generates the clock signal and initiates all communication. The sensors and devices connected to it are peripherals (also called targets — you may see older docs call them “slaves”). Every peripheral connects its SDA and SCL pins to the same shared SDA and SCL lines.

Addressing: How Devices Know Who’s Being Talked To

Section titled “Addressing: How Devices Know Who’s Being Talked To”

Every I2C device has a fixed 7-bit address — a unique number (between 0x00 and 0x7F) that identifies it on the bus. The tinyCore’s built-in LSM6DSOX IMU, for example, lives at address 0x6A.

When the tinyCore wants to talk to a specific device, it sends that device’s address over the bus. Every device on the bus hears the address, but only the one that matches responds. This is how one pair of wires can serve dozens of devices — they take turns.

After the address, the tinyCore sends one extra bit that says whether it wants to write data to the device or read data from it. The addressed device responds with an ACK (acknowledge) signal to confirm it’s listening, and then the actual data transfer happens.

If two devices have the same address, the controller can’t tell them apart — this is called an address conflict. Some sensors have an address pin (labeled ADDR or A0) that you can wire HIGH or LOW to change the address by one bit. If that’s not an option, you can use an I2C multiplexer like the TCA9548A to put conflicting devices on separate bus segments.

The tinyCore has two QWIIC/STEMMA QT connectors — these are standardized 4-pin plugs that make I2C wiring a single cable click instead of four individual wires.

SparkFun calls them QWIIC. Adafruit calls them STEMMA QT. They use the exact same connector and pinout — fully cross-compatible. You can plug any SparkFun, Adafruit, SEEED Studio, or DFRobot QWIIC/STEMMA sensor into your tinyCore and it’ll work.

PinSignalWire Color
1GNDBlack
23.3VRed
3SDABlue
4SCLYellow

Both connectors on the tinyCore share the same I2C bus, so you can daisy-chain sensors: plug one into each port, or use cables with pass-through connectors to chain even more.

An I2C scanner is a short program that checks every possible address (1–127) and reports which ones respond. It’s the first thing you should run when a sensor isn’t working. If the scanner doesn’t find your device, you know the problem is physical (wiring, power, bad connection) and not in your application code.

#include <Wire.h>
void setup() {
pinMode(6, OUTPUT);
digitalWrite(6, HIGH); // power on the QWIIC bus
Serial.begin(115200);
Wire.begin(3, 4); // SDA = GPIO 3, SCL = GPIO 4
}
void loop() {
Serial.println("Scanning...");
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print("Device found at 0x");
Serial.println(addr, HEX);
}
}
delay(5000);
}

The tinyCore uses non-default I2C pins. You must specify them explicitly:

Wire.begin(3, 4); // SDA = GPIO 3, SCL = GPIO 4

Calling Wire.begin() without arguments will use the ESP32-S3 defaults (GPIO 8 and GPIO 9), which aren’t connected to the QWIIC ports or the IMU. I2C will silently not work.

The tinyCore routes power to the QWIIC connectors through GPIO 6. You must set this HIGH before any I2C communication will work:

pinMode(6, OUTPUT);
digitalWrite(6, HIGH); // power on I2C bus

This power-gating design lets the firmware cut power to I2C peripherals during deep sleep, saving battery. It’s a common pattern on low-power ESP32 boards (Adafruit’s S3 Feather does the same thing with GPIO 7).

I2C supports two common speeds: standard mode (100 kHz) and fast mode (400 kHz). The default is 100 kHz. To switch to fast mode:

Wire.setClock(400000);

Most sensors work fine at either speed. If you’re getting communication errors with a specific sensor, try dropping back to 100 kHz.

Almost every tinyCore project that uses I2C sensors will start like this:

#include <Wire.h>
void setup() {
Serial.begin(115200);
// Power on and initialize I2C
pinMode(6, OUTPUT);
digitalWrite(6, HIGH);
delay(100); // give devices time to power up
Wire.begin(3, 4); // SDA = GPIO 3, SCL = GPIO 4
}

Scanner finds nothing at all

  1. Did you set GPIO 6 HIGH? (most common mistake on tinyCore)
  2. Did you call Wire.begin(3, 4) with the correct pins?
  3. Is the QWIIC cable fully seated on both ends?
  4. Is the device actually an I2C device? (some sensors are SPI-only)

Scanner finds the device, but your library won’t initialize it

  • Some libraries call Wire.begin() internally with wrong defaults. Initialize Wire.begin(3, 4) before calling the library’s begin() function.
  • Check if the library expects a different I2C address than your device uses.

Intermittent failures or garbled data

  • Cable too long. I2C is designed for short distances — keep cables under 1 meter. Beyond that, signal quality drops.
  • Too many devices loading the bus. Each device adds capacitance. If you have 5+ sensors daisy-chained, try reducing cable lengths or lowering clock speed.
  • Missing pull-up resistors. QWIIC/STEMMA breakout boards include pull-ups, so plug-and-play connections work. If you’re wiring a bare sensor directly to the GPIO pins, add 4.7kΩ resistors from SDA to 3.3V and SCL to 3.3V.
FeaturetinyCore ESP32-S3
SDA pinGPIO 3
SCL pinGPIO 4
I2C power pinGPIO 6 (must be HIGH)
Default speed100 kHz
Fast mode400 kHz
QWIIC/STEMMA ports2
Onboard I2C deviceLSM6DSOX IMU at 0x6A
Init codeWire.begin(3, 4)

HowToMechatronics — clear diagrams showing I2C bus wiring, signal waveforms, start/stop conditions, and a practical Arduino sensor example.

MickMake — covers I2C electrical specs, open-drain connections, pull-ups, and the full read/write protocol sequence.