Mastering I2C for Linux Driver Development
A Comprehensive Guide from Protocol Basics to Production Drivers
1. Introduction to I2C
The?Inter-Integrated Circuit (I2C)?protocol is a synchronous, two-wire serial communication standard designed for short-distance communication between integrated circuits. Developed by Philips (now NXP Semiconductors), I2C is widely used in embedded systems to connect sensors, EEPROMs, displays, and other peripherals to microcontrollers or System-on-Chips (SoCs).
Key Advantages:
2. I2C Protocol Fundamentals
2.1 Signal Lines
SignalDescriptionSDASerial Data Line (bidirectional, open-drain).SCLSerial Clock Line (generated by the master).
2.2 Speed Modes
2.3 Frame Structure
An I2C transaction consists of four phases:
3. Addressing I2C Devices
3.1 7-Bit vs. 10-Bit Addressing
3.2 Scanning the Bus
Use the?i2c-tools?package to detect devices:
i2cdetect -y 1 # Scan bus 1 (Raspberry Pi)
Output Example:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --
4. Linux I2C Subsystem
4.1 Key Components
4.2 Key Data Structures
4.3 Common API Functions
5.1 Device Tree Configuration
Define the I2C controller and devices in the Device Tree (DTS):
&i2c1 {
status = "okay";
clock-frequency = <100000>; // 100 kHz
// BMP280 temperature sensor
temp_sensor: bmp280@76 {
compatible = "bosch,bmp280";
reg = <0x76>;
};
// AT24C32 EEPROM
eeprom: at24c32@50 {
compatible = "atmel,at24";
reg = <0x50>;
pagesize = <32>;
};
};
5.2 Driver Registration
#include <linux/i2c.h>
static const struct i2c_device_id bmp280_id[] = {
{ "bmp280", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, bmp280_id);
static const struct of_device_id bmp280_of_match[] = {
{ .compatible = "bosch,bmp280" },
{ }
};
MODULE_DEVICE_TABLE(of, bmp280_of_match);
static struct i2c_driver bmp280_driver = {
.driver = {
.name = "bmp280",
.of_match_table = bmp280_of_match,
},
.probe = bmp280_probe,
.remove = bmp280_remove,
.id_table = bmp280_id,
};
module_i2c_driver(bmp280_driver);
5.3 Reading Data from a Sensor
static int bmp280_read_temp(struct i2c_client *client, int *temp) {
u8 reg = BMP280_REG_TEMP;
u8 buf[3];
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0, // Write
.len = 1,
.buf = ®,
},
{
.addr = client->addr,
.flags = I2C_M_RD, // Read
.len = 3,
.buf = buf,
},
};
// Execute transaction
if (i2c_transfer(client->adapter, msgs, 2) != 2) {
dev_err(&client->dev, "I2C read failed");
return -EIO;
}
// Convert raw data to temperature
*temp = (buf[0] << 12) | (buf[1] << 4) | (buf[2] >> 4);
return 0;
}
6. SMBus Compatibility
7. Debugging I2C Drivers
7.1 Tools
i2cdetect -y 1 # Scan bus 1
i2cget -y 1 0x76 # Read from address 0x76
i2cset -y 1 0x50 0x00 0xFF # Write 0xFF to EEPROM at 0x00
7.2 Common Issues
8. Best Practices
9. Real-World Example: EEPROM Driver
9.1 Writing to AT24C32
static int at24_write(struct i2c_client *client, u16 addr, u8 *data, u8 len) {
u8 buf[len + 2];
buf[0] = addr >> 8; // High byte
buf[1] = addr & 0xFF; // Low byte
memcpy(&buf[2], data, len);
return i2c_master_send(client, buf, len + 2);
}
9.2 Reading from AT24C32
static int at24_read(struct i2c_client *client, u16 addr, u8 *data, u8 len) {
u8 addr_buf[2] = { addr >> 8, addr & 0xFF };
struct i2c_msg msgs[] = {
{ .addr = client->addr, .flags = 0, .len = 2, .buf = addr_buf },
{ .addr = client->addr, .flags = I2C_M_RD, .len = len, .buf = data },
};
return i2c_transfer(client->adapter, msgs, 2);
}
10. Conclusion
Mastering I2C driver development in Linux empowers you to interface with a vast ecosystem of sensors, memory chips, and peripherals. By understanding the protocol’s fundamentals, leveraging the kernel’s I2C subsystem, and adhering to best practices, you can create robust, production-ready drivers.
Next Steps:
Resources:
Happy coding!???
Architect
2 天前Also sometimes i2c-detect takes long time to detect the devices on the bus.. can you suggest, what is the issue here ?
Architect
2 天前you can use i2c_transfer for MASTER write to SLAVE too, instead of using flag I2C_M_RD in MASTER read from SLAVE, make .flags= 0 during Write and use sizeof(arr) for .len instead of using hard coded lengths in bytes..
Automotive | Embedded Software Developer | AUTOSAR | Bare metal Driver Development | Embedded Linux | Linux Device Driver Developer | Embedded C |TDA4VM SOC | FREE-RTOS | UDS | YOCTO | DDS |
2 天前Very informative! I am following each post of yours.it does have in depth explanation of each Subsystem of Linux. Thanks for Sharing!