The Inter-Integrated Circuit (I2C) protocol is one of the most widely used communication standards in embedded systems. This guide will help you understand I2C fundamentals and implement it successfully in your projects.
## What is I2C?
I2C is a synchronous, multi-master, multi-slave serial communication protocol developed by Philips (now NXP). It uses only two wires to communicate with multiple devices, making it ideal for connecting sensors, displays, and other peripherals to microcontrollers.
### Key Features
- **Two-wire interface**: SDA (data) and SCL (clock)
- **Multi-device support**: Up to 127 devices on one bus
- **Multiple speeds**: Standard (100 kbps), Fast (400 kbps), Fast Plus (1 Mbps)
- **Bidirectional communication**: Half-duplex data transfer
- **Built-in addressing**: Each device has a unique address
## How I2C Works
### Basic Communication
1. **Start condition**: Master pulls SDA low while SCL is high
2. **Address frame**: 7-bit address + read/write bit
3. **Acknowledgment**: Slave responds with ACK bit
4. **Data transfer**: 8-bit data frames with ACK after each
5. **Stop condition**: Master releases SDA while SCL is high
### Pull-up Resistors
I2C uses open-drain outputs, requiring pull-up resistors:
- **Typical values**: 4.7kΩ for standard mode, 2.2kΩ for fast mode
- **Calculation**: Based on bus capacitance and speed
- **One set per bus**: Not per device
## Implementing I2C
### Hardware Setup
```cpp
// Arduino example - Wire library
#include
void setup() {
Wire.begin(); // Join I2C bus as master
Serial.begin(9600); // For debugging
}
```
### Reading from a Sensor
```cpp
// Reading temperature from sensor at address 0x48
void readTemperature() {
Wire.beginTransmission(0x48);
Wire.write(0x00); // Temperature register
Wire.endTransmission();
Wire.requestFrom(0x48, 2); // Request 2 bytes
if (Wire.available() == 2) {
int temp = Wire.read() << 8 | Wire.read();
float celsius = temp * 0.0625;
Serial.print("Temperature: ");
Serial.println(celsius);
}
}
```
### Writing to a Device
```cpp
// Writing to an I2C LCD display
void writeToLCD(byte address, byte data) {
Wire.beginTransmission(0x27); // LCD address
Wire.write(address); // Register address
Wire.write(data); // Data to write
Wire.endTransmission();
}
```
## Common I2C Devices
### Sensors
- **Temperature/Humidity**: DHT22, BME280, SHT31
- **Accelerometer/Gyro**: MPU6050, ADXL345
- **Pressure**: BMP280, MS5611
- **Light**: TSL2561, BH1750
### Displays
- **OLED**: SSD1306, SH1106
- **LCD**: PCF8574 backpack
- **LED drivers**: PCA9685
### Memory
- **EEPROM**: AT24C256
- **FRAM**: FM24C64
## Troubleshooting I2C
### Common Issues
1. **No response from device**
- Check wiring and connections
- Verify device address with I2C scanner
- Ensure pull-up resistors are present
2. **Intermittent communication**
- Check pull-up resistor values
- Reduce bus speed
- Shorten wire length
3. **Bus stuck**
- Implement bus recovery
- Check for shorts
- Verify voltage levels
### I2C Scanner Code
```cpp
void scanI2C() {
Serial.println("Scanning I2C bus...");
for (byte address = 1; address < 127; address++) {
Wire.beginTransmission(address);
byte error = Wire.endTransmission();
if (error == 0) {
Serial.print("Device found at 0x");
Serial.println(address, HEX);
}
}
Serial.println("Scan complete");
}
```
## Advanced Topics
### Multi-Master Configuration
- Bus arbitration
- Clock synchronization
- Collision detection
### Speed Optimization
- Use Fast Mode when possible
- Minimize bus capacitance
- Optimize pull-up resistors
### Level Shifting
For mixed voltage systems:
- Use bidirectional level shifters
- Consider I2C-specific shifters like PCA9306
## Best Practices
1. **Keep wires short**: Minimize bus capacitance
2. **Use proper pull-ups**: Calculate based on bus characteristics
3. **Handle errors**: Implement timeout and retry logic
4. **Document addresses**: Keep track of device addresses
5. **Test incrementally**: Add one device at a time
## Example Project: Multi-Sensor Weather Station
```cpp
#include
#include
#include
Adafruit_BME280 bme; // Temperature, humidity, pressure
BH1750 lightMeter; // Light intensity
void setup() {
Serial.begin(9600);
Wire.begin();
if (!bme.begin(0x76)) {
Serial.println("BME280 not found!");
}
if (!lightMeter.begin()) {
Serial.println("BH1750 not found!");
}
}
void loop() {
// Read all sensors
float temp = bme.readTemperature();
float humidity = bme.readHumidity();
float pressure = bme.readPressure() / 100.0F;
float lux = lightMeter.readLightLevel();
// Display results
Serial.print("Temp: "); Serial.print(temp); Serial.println("°C");
Serial.print("Humidity: "); Serial.print(humidity); Serial.println("%");
Serial.print("Pressure: "); Serial.print(pressure); Serial.println("hPa");
Serial.print("Light: "); Serial.print(lux); Serial.println("lx");
delay(2000);
}
```
## Conclusion
I2C is a versatile and reliable protocol that simplifies connecting multiple devices to your microcontroller. With proper understanding of its principles and careful attention to hardware setup, you can successfully implement I2C in your projects.
Source Parts stocks a wide variety of I2C-compatible components, from sensors to displays to interface chips. Browse our selection or contact our technical team for help selecting the right I2C devices for your project.