I2C总线驱动开发:MPU6050应用
引言
I2C(Inter-Integrated Circuit)总线作为嵌入式系统中广泛使用的通信协议,在传感器、外设控制等领域扮演着重要角色。本文将深入探讨I2C总线的工作原理、Exynos4412平台裸机驱动实现、Linux内核中的I2C子系统架构,并以MPU6050六轴传感器为案例,演示完整的驱动开发流程。通过裸机到操作系统的完整视角,帮助开发者构建系统的I2C知识体系。
一、I2C总线技术解析
1.1 I2C总线基础架构
I2C总线采用两线制(SDA数据线+SCL时钟线)实现设备间的同步串行通信,具有以下核心特性:
- 多主从架构:支持多主机和多个从机设备
- 地址寻址:7位或10位设备地址机制
- 速率分级:标准模式(100kbps)、快速模式(400kbps)和高速模式(3.4Mbps)
- 传输格式:起始位->地址位->读写位->应答位->数据位->停止位
典型时序特征:
- 起始条件:SCL高电平时SDA下降沿
- 停止条件:SCL高电平时SDA上升沿
- 数据有效性:SDA在SCL高电平期间保持稳定
https://img-blog.csdnimg.cn/20210601155212377.png
1.2 Exynos4412的I2C控制器
Exynos4412芯片集成多个I2C控制器,主要寄存器包括:
I2CCON
(控制寄存器):
-
时钟分频配置
-
应答信号使能
-
中断控制位
I2CSTAT
(状态寄存器):
-
传输模式选择
-
起始/停止信号生成
-
总线忙状态指示
I2CDS
(数据寄存器):
- 存储发送/接收数据
关键位配置示例:
c
Copy
// 使能ACK,时钟分频512,开启中断
I2C5.I2CCON = 1<<7 | 1<<6 | 1<<5;
二、Exynos4412裸机I2C驱动实现
2.1 发送流程实现
数据发送流程遵循I2C标准协议:
- 设置从机地址到I2CDS
- 配置控制寄存器产生START信号
- 等待传输完成(检查I2CCON[4])
- 清除Pending位继续传输
- 发送数据后产生STOP信号
关键代码解析:
c
Copy
void iic_write(unsigned char slave_addr, unsigned char addr, unsigned char data)
{I2C5.I2CDS = slave_addr; // 从机地址I2C5.I2CCON = 1<<7 | 1<<6 | 1<<5; I2C5.I2CSTAT = 0x3 << 6 | 1<<5 | 1<<4; // 主机发送模式while(!(I2C5.I2CCON & (1<<4))); // 等待传输完成I2C5.I2CDS = addr; // 寄存器地址I2C5.I2CCON &= ~(1<<4);while(!(I2C5.I2CCON & (1<<4)));I2C5.I2CDS = data; // 数据I2C5.I2CCON &= ~(1<<4);while(!(I2C5.I2CCON & (1<<4)));I2C5.I2CSTAT = 0xD0; // 产生STOP
}
2.2 接收流程实现
接收流程需注意主机应答控制:
- 发送从机地址(写模式)
- 发送寄存器地址
- 重新发送从机地址(读模式)
- 切换为主机接收模式
- 读取数据后发送NACK
典型代码实现:
c
Copy
void iic_read(unsigned char slave_addr, unsigned char addr, unsigned char *data)
{// 地址阶段I2C5.I2CDS = slave_addr;I2C5.I2CCON = 1<<7 | 1<<6 | 1<<5;I2C5.I2CSTAT = 0x3 << 6 | 1<<5 | 1<<4;while(!(I2C5.I2CCON & (1<<4)));// 寄存器地址I2C5.I2CDS = addr;I2C5.I2CCON &= ~(1<<4);while(!(I2C5.I2CCON & (1<<4)));// 重启接收I2C5.I2CDS = slave_addr | 0x01;I2C5.I2CSTAT = 2<<6 | 1<<5 | 1<<4;while(!(I2C5.I2CCON & (1<<4)));I2C5.I2CCON &= ~((1<<7) | (1<<4)); // 关闭ACK*data = I2C5.I2CDS; // 读取数据
}
三、Linux内核I2C子系统剖析
3.1 内核I2C架构设计
Linux内核I2C子系统采用分层架构:
https://img-blog.csdnimg.cn/202106011610432.png
四大核心对象:
- i2c_adapter:物理控制器抽象
- i2c_client:从机设备描述
- i2c_driver:设备驱动逻辑
- i2c_algorithm:总线通信方法
3.2 设备树配置实例
典型I2C节点配置:
dts
Copy
i2c@138D0000 {compatible = "samsung,s3c2440-i2c";reg = <0x138D0000 0x100>;interrupts = <0 58 0>;#address-cells = <1>;#size-cells = <0>;mpu6050@68 {compatible = "invensense,mpu6050";reg = <0x68>;interrupt-parent = <&gpx3>;interrupts = <5 0>;};
};
3.3 驱动开发关键API
- 设备注册:
c
Copy
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
- 驱动注册:
c
Copy
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
- 数据传输:
c
Copy
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
典型消息结构体:
c
Copy
struct i2c_msg {__u16 addr; // 从机地址__u16 flags; // 读写标志__u16 len; // 数据长度__u8 *buf; // 数据缓冲区
};
四、MPU6050传感器驱动开发实践
4.1 硬件特性解析
MPU6050主要参数:
- 三轴陀螺仪:±250/500/1000/2000 dps
- 三轴加速度计:±2/4/8/16 g
- 16位ADC分辨率
- I2C从机地址:0x68(AD0低电平)
关键寄存器:
c
Copy
#define SMPLRT_DIV 0x19 // 采样率分频
#define PWR_MGMT_1 0x6B // 电源管理
#define ACCEL_CONFIG 0x1C // 加速度配置
#define GYRO_CONFIG 0x1B // 陀螺仪配置
4.2 驱动初始化流程
- 配置采样率:
c
Copy
i2c_smbus_write_byte_data(client, SMPLRT_DIV, 0x07); // 125Hz
- 设置传感器量程:
c
Copy
// 加速度±2g
i2c_smbus_write_byte_data(client, ACCEL_CONFIG, 0x00);
// 陀螺仪±250dps
i2c_smbus_write_byte_data(client, GYRO_CONFIG, 0x00);
- 退出睡眠模式:
c
Copy
i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x01);
4.3 数据读取实现
六轴数据读取示例:
c
Copy
static int mpu6050_read_values(struct i2c_client *client, struct mpu6050_data *data)
{u8 buffer[14];int ret;// 读取14字节数据(加速度+温度+陀螺仪)ret = i2c_smbus_read_i2c_block_data(client, ACCEL_XOUT_H, 14, buffer);data->accel_x = be16_to_cpup((__be16 *)&buffer[0]);data->accel_y = be16_to_cpup((__be16 *)&buffer[2]); data->accel_z = be16_to_cpup((__be16 *)&buffer[4]);data->temp = be16_to_cpup((__be16 *)&buffer[6]);data->gyro_x = be16_to_cpup((__be16 *)&buffer[8]);data->gyro_y = be16_to_cpup((__be16 *)&buffer[10]);data->gyro_z = be16_to_cpup((__be16 *)&buffer[12]);return 0;
}
数据转换公式:
c
Copy
// 加速度计算(±2g量程)
accel_x_g = (raw_value / 16384.0) * 9.80665;// 陀螺仪计算(±250dps量程)
gyro_x_dps = raw_value / 131.0;
五、应用层I2C设备操作
5.1 字符设备接口
- 设备节点:
/dev/i2c-N
- 基本操作:
c
Copy
int fd = open("/dev/i2c-1", O_RDWR);
ioctl(fd, I2C_SLAVE, 0x68); // 设置从机地址uint8_t reg = 0x3B;
uint8_t buf[6];
write(fd, ®, 1);
read(fd, buf, 6); // 读取加速度数据
5.2 ioctl高级控制
批量传输示例:
c
Copy
struct i2c_msg msgs[2] = {{ // 写寄存器地址.addr = 0x68,.flags = 0,.len = 1,.buf = ®},{ // 读数据.addr = 0x68,.flags = I2C_M_RD,.len = 6,.buf = data}
};struct i2c_rdwr_ioctl_data msgset = {.msgs = msgs,.nmsgs = 2
};ioctl(fd, I2C_RDWR, &msgset);
六、驱动开发进阶技巧
6.1 设备树匹配驱动
驱动定义:
c
Copy
static const struct of_device_id mpu6050_dt_ids[] = {{ .compatible = "invensense,mpu6050" },{ }
};static struct i2c_driver mpu6050_driver = {.driver = {.name = "mpu6050",.of_match_table = mpu6050_dt_ids,},.probe = mpu6050_probe,.remove = mpu6050_remove,.id_table = mpu6050_ids,
};
6.2 中断处理实现
配置中断引脚:
c
Copy
// 设备树
interrupts = <&gpx3 5 IRQ_TYPE_EDGE_RISING>;// 驱动代码
ret = devm_request_threaded_irq(&client->dev, client->irq,NULL, mpu6050_irq_handler,IRQF_TRIGGER_RISING | IRQF_ONESHOT,"mpu6050", data);
6.3 FIFO缓存使用
配置FIFO:
c
Copy
// 启用加速度和陀螺仪FIFO
i2c_smbus_write_byte_data(client, FIFO_EN, 0x78); // 读取FIFO计数
uint16_t count = i2c_smbus_read_word_swapped(client, FIFO_COUNTH);
七、性能优化与调试
7.1 传输速率优化
- 提升时钟频率:
c
Copy
// 配置I2C控制器为快速模式
i2c_smbus_write_byte_data(client, I2C_SPEED, 0x01);
- 使用DMA传输:
c
Copy
struct i2c_msg msg = {.flags = I2C_M_DMA_SAFE,.len = 512,.buf = dma_buffer
};
7.2 调试技巧
- 逻辑分析仪抓包:
- 检查起始/停止条件
- 验证地址和数据波形
- 测量实际传输速率
- 内核调试信息:
bash
Copy
echo 1 > /sys/module/i2c_core/parameters/debug
dmesg | grep i2c
- I2C工具集:
bash
Copy
# 扫描总线设备
i2cdetect -y 1# 寄存器读取
i2cget -y 1 0x68 0x75
八、总结与展望
本文系统性地讲解了I2C总线从硬件原理到Linux驱动开发的完整知识体系,结合Exynos4412和MPU6050的实践案例,展示了嵌入式系统中外设驱动的开发流程。在物联网设备蓬勃发展的今天,掌握I2C等基础总线技术对于嵌入式开发者至关重要。
未来发展方向:
- 自动化设备树配置:开发可视化配置工具
- 实时性优化:结合RT-Preempt补丁提升实时性能
- 安全增强:增加I2C通信加密机制
- AI驱动:集成机器学习模型实现智能传感器融合
通过持续深入理解Linux内核机制,结合具体硬件平台特性,开发者能够构建出高效稳定的嵌入式系统,为智能设备的发展奠定坚实基础。