10 - I2C Guide

### I2C Basics Guide #### What is I2C? I2C (Inter-Integrated Circuit) is a serial communication protocol for connecting low-speed peripherals (e.g., sensors) to microcontrollers using two wires: SDA (data) and SCL (clock). #### SDA and SCL Purpose - **SDA (Serial Data)**: Bidirectional line for transmitting/receiving data bits. - **SCL (Serial Clock)**: Generated by the master device to synchronize data transfer. #### ESP32-S3 Default SDA/SCL GPIOs Defaults: SDA = GPIO 8, SCL = GPIO 9 (configurable in code). We can use this code to printout the default SDA and SCL for any MCU in Arduino IDE ``` cpp void setup() { Serial.begin(115200); delay(3000); Serial.print("SDA: ");Serial.println(SDA); Serial.print("SCL: ");Serial.println(SCL); } void loop() {} ``` ![](https://cdn.shopify.com/s/files/1/0331/9994/7908/files/Pasted_image_20251014163253.png?v=1760421464) #### Routing and Using Any Pin for I2C on ESP32-S3 ESP32-S3 supports multiple I2C buses via software configuration. In Arduino/ESP-IDF: - Use `Wire.begin(SDA_PIN, SCL_PIN);` to assign any available GPIOs (e.g., GPIO 4 for SDA, GPIO 5 for SCL). - For multiple buses: Use `TwoWire I2C1 = TwoWire(1);` then `I2C1.begin(SDA_PIN, SCL_PIN);`. - Ensure pins are not multiplexed with other functions; add pull-ups if needed. ### Verifying/Retrieving Current SDA and SCL GPIO Numbers via Wire Library In the Arduino Wire library for ESP32-S3, the pins set via Wire.begin(SDA_PIN, SCL_PIN) are stored in protected class members (_sda_pin and _scl_pin). There's no public getter, but you can use a preprocessor hack to access them for verification at runtime. ``` cpp // Hack to access protected members (use only for testing) #define protected public #include <Wire.h> #undef protected #define SDA_PIN 4 #define SCL_PIN 5 void setup() { Serial.begin(115200); delay(3000); Wire.begin(SDA_PIN, SCL_PIN); Serial.print("SDA GPIO used: ");Serial.println(Wire.sda); Serial.print("SCL GPIO used: ");Serial.println(Wire.scl); } void loop() {} ``` ![](https://cdn.shopify.com/s/files/1/0331/9994/7908/files/Pasted_image_20251014164931.png?v=1760421468) #### I2C Address Each device has a unique 7-bit address (0x00–0x7F) for identification on the bus. Multiple devices share the same lines; the master selects by sending the address. Some devices allow address configuration via pins. #### I2C Scan Code (Arduino/ESP Example) Use this code to scan for connected devices: ```cpp #include <Wire.h> void setup() { Wire.begin(); Serial.begin(115200); Serial.println("I2C Scanner"); } void loop() { byte error, address; int nDevices = 0; for (address = 1; address < 127; address++) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("Device at 0x"); if (address < 16) Serial.print("0"); Serial.println(address, HEX); nDevices++; } } if (nDevices == 0) Serial.println("No devices found"); delay(5000); // Scan every 5s } ``` #### Why External Pull-Up Resistors Needed? If you are unable to get the I2C address, probably the external pull-up are required for both I2C line. I2C uses open-drain outputs, so lines are pulled low by devices but need resistors to pull high (logic 1). Many sensors have built-in pull-ups; if not, add 4.7K–10KΩ resistors from SDA/SCL to VCC (e.g., 3.3V/5V) to ensure reliable signaling. ![](https://cdn.shopify.com/s/files/1/0331/9994/7908/files/Pasted_image_20251014165101.png?v=1760421471)