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.
Why This Matters for Your Projects
Section titled “Why This Matters for Your Projects”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).
How I2C Actually Works
Section titled “How I2C Actually Works”Two Wires, Many Devices
Section titled “Two Wires, Many Devices”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.
What About Conflicts?
Section titled “What About Conflicts?”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.
QWIIC / STEMMA QT Connectors
Section titled “QWIIC / STEMMA QT Connectors”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.
| Pin | Signal | Wire Color |
|---|---|---|
| 1 | GND | Black |
| 2 | 3.3V | Red |
| 3 | SDA | Blue |
| 4 | SCL | Yellow |
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.
The I2C Scanner: Your Best Debugging Tool
Section titled “The I2C Scanner: Your Best Debugging Tool”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);}On the tinyCore
Section titled “On the tinyCore”Pin Configuration
Section titled “Pin Configuration”The tinyCore uses non-default I2C pins. You must specify them explicitly:
Wire.begin(3, 4); // SDA = GPIO 3, SCL = GPIO 4Calling 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 I2C Power Pin
Section titled “The I2C Power Pin”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 busThis 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).
Clock Speed
Section titled “Clock Speed”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.
tinyCore I2C Setup Boilerplate
Section titled “tinyCore I2C Setup Boilerplate”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}Common Problems and Fixes
Section titled “Common Problems and Fixes”Scanner finds nothing at all
- Did you set GPIO 6 HIGH? (most common mistake on tinyCore)
- Did you call
Wire.begin(3, 4)with the correct pins? - Is the QWIIC cable fully seated on both ends?
- 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. InitializeWire.begin(3, 4)before calling the library’sbegin()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.
Quick Reference
Section titled “Quick Reference”| Feature | tinyCore ESP32-S3 |
|---|---|
| SDA pin | GPIO 3 |
| SCL pin | GPIO 4 |
| I2C power pin | GPIO 6 (must be HIGH) |
| Default speed | 100 kHz |
| Fast mode | 400 kHz |
| QWIIC/STEMMA ports | 2 |
| Onboard I2C device | LSM6DSOX IMU at 0x6A |
| Init code | Wire.begin(3, 4) |
Learn More
Section titled “Learn More”- Connect a Display via I2C — your first I2C project
- Detect Motion with the IMU — using the onboard I2C sensor
- What is an IMU? — understanding the LSM6DSOX on your tinyCore
- What is GPIO? — the pins that I2C runs on
Video: How I2C Works
Section titled “Video: How I2C Works”HowToMechatronics — clear diagrams showing I2C bus wiring, signal waveforms, start/stop conditions, and a practical Arduino sensor example.
Video: I2C Protocol Deep Dive
Section titled “Video: I2C Protocol Deep Dive”MickMake — covers I2C electrical specs, open-drain connections, pull-ups, and the full read/write protocol sequence.