Blog · How-to

Arduino i2c tutorial: the Wire library from scratch

How-to

Sensor libraries hide i2c so well that the first missing library leaves you stranded. This is the whole Wire API — enough to talk to any register-based chip with nothing but a datasheet.

Setup

Arduino i2c wiring and Wire.begin()

Four wires: VCC, GND, SDA, SCL. On an Uno or Nano, SDA is A4 and SCL is A5; on an ESP32 the defaults are GPIO21/22. Breakouts on Qwiic or STEMMA QT reduce this to clicking a cable in. Then:

#include <Wire.h>

void setup() {
  Wire.begin();              // controller mode
  // ESP32: Wire.begin(21, 22);   custom SDA, SCL pins
  Wire.setClock(400000);     // optional: Fast-mode. Default is 100 kHz.
  Serial.begin(115200);
}

Stick with the default 100 kHz until everything works — it's the most forgiving speed, and sensors rarely need more. First job after wiring: run a scanner to confirm the device's address.

Writing

Arduino i2c write: sending to a register

The universal pattern — address the chip, name the register, send the value:

void writeRegister(uint8_t addr, uint8_t reg, uint8_t value) {
  Wire.beginTransmission(addr);  // queues the address + write bit
  Wire.write(reg);               // register pointer
  Wire.write(value);             // payload
  uint8_t err = Wire.endTransmission();  // actually transmits
  // err: 0 = OK, 2 = address NACKed, 3 = data NACKed
  if (err) Serial.printf("I2C write failed: %u\n", err);
}

Everything between beginTransmission and endTransmission is buffered and sent in one transaction. The return code is your built-in debugger: a 2 means nobody answered the address — the same signal a scanner uses. (Every Wire call used here is covered in Arduino's official Wire reference if you want the full API.)

Reading

Arduino i2c read with requestFrom (the repeated-start dance)

uint8_t readRegister(uint8_t addr, uint8_t reg) {
  Wire.beginTransmission(addr);
  Wire.write(reg);                    // set the register pointer
  Wire.endTransmission(false);        // false = repeated start, keep the bus
  Wire.requestFrom(addr, (uint8_t)1); // read 1 byte
  return Wire.available() ? Wire.read() : 0;
}

The false in endTransmission(false) is the detail most tutorials skip: it issues a repeated start instead of a stop, welding the pointer-write and the data-read into one atomic transaction. Some devices tolerate a stop between the halves; plenty don't. Always pass false when a read follows.

Prove the whole loop works with a chip-ID check — a BME280 at 0x76 keeps its ID in register 0xD0:

uint8_t id = readRegister(0x76, 0xD0);
// 0x60 = BME280, 0x58 = BMP280 — same address, different chip
Serial.println(id, HEX);

That one byte instantly settles the classic "is my '280 the humidity one?" question — same trick works on most chips, since nearly all keep a who-am-I register.

Multi-byte reads are the same shape — request more and drain the buffer:

Wire.requestFrom(addr, (uint8_t)6);   // e.g. 6 bytes of sensor data
while (Wire.available()) buf[i++] = Wire.read();

Pitfalls

Five Wire library mistakes that cost people afternoons

1. Missing pull-ups on bare chips. Breakouts include them; a naked chip on a breadboard doesn't. 4.7 kΩ to VCC on both lines — the pull-up guide has the full sizing story.

2. 5 V board, 3.3 V sensor. An Uno will happily over-volt a 3.3 V-only device. Level-shift, or check the pairing in the compatibility checker first.

3. Wrong address form. Wire uses 7-bit addresses. If a datasheet says 0xEC, that's the 8-bit write form of 0x76 — shift right once.

4. Two devices, one address. Scanner shows one entry where you wired two? They're colliding — the conflict guide has four fixes, including Wire1.

5. Reading without the repeated start. If a device returns the same stale byte forever or 0xFF, check that pointer-write ends with endTransmission(false).

Still stuck after those? Work the 7-step checklist, or watch the actual bytes with a $10 logic analyzer.

Questions

Arduino i2c FAQ

Which pins are i2c on my Arduino?

Uno/Nano: A4 = SDA, A5 = SCL (also broken out as dedicated SDA/SCL pins on newer headers). Mega: 20/21. ESP32: GPIO21/22 by default, but Wire.begin(sda, scl) can move it to almost any pair. RP2040 boards: check the pinout — they have two i2c peripherals with several pin options.

What does 'endTransmission returned 2' mean?

Error 2 is NACK on the address byte — nothing answered at that address. Wrong address, wrong wiring, or the device isn't powered. Error 3 is NACK on data (the device exists but rejected a byte); 0 is success. Testing that return value is exactly how i2c scanners work.

Do I need external pull-up resistors with Arduino?

With breakout boards, usually not — they carry their own. With a bare chip on a breadboard, yes: 4.7 kΩ from SDA and SCL to VCC. The Wire library enables the AVR's internal pull-ups, but at 20–50 kΩ they're too weak to count on for anything but the shortest bench test.

Can a 5 V Arduino talk to a 3.3 V sensor?

Electrically the danger is real: a 5 V Uno drives SDA/SCL to 5 V and can damage a 3.3 V-only device. Use a bidirectional i2c level shifter, or pick a breakout with one built in (Adafruit STEMMA QT boards are 5 V-safe; bare Qwiic boards are not). Our compatibility checker flags exactly when a shifter is required.

How do I use two i2c buses at once?

Boards with a second peripheral expose Wire1 (ESP32, RP2040, Due and others): call Wire1.begin() and pass &Wire1 to libraries that accept a TwoWire pointer. It's the cleanest fix when two devices insist on the same address.