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标准协议:

  1. 设置从机地址到I2CDS
  2. 配置控制寄存器产生START信号
  3. 等待传输完成(检查I2CCON[4])
  4. 清除Pending位继续传输
  5. 发送数据后产生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 接收流程实现

接收流程需注意主机应答控制:

  1. 发送从机地址(写模式)
  2. 发送寄存器地址
  3. 重新发送从机地址(读模式)
  4. 切换为主机接收模式
  5. 读取数据后发送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

四大核心对象:

  1. i2c_adapter:物理控制器抽象
  2. i2c_client:从机设备描述
  3. i2c_driver:设备驱动逻辑
  4. 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

  1. 设备注册:

c

Copy

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, 
                                 struct i2c_board_info const *info);
  1. 驱动注册:

c

Copy

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
  1. 数据传输:

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 驱动初始化流程

  1. 配置采样率:

c

Copy

i2c_smbus_write_byte_data(client, SMPLRT_DIV, 0x07); // 125Hz
  1. 设置传感器量程:

c

Copy

// 加速度±2g
i2c_smbus_write_byte_data(client, ACCEL_CONFIG, 0x00);  
// 陀螺仪±250dps
i2c_smbus_write_byte_data(client, GYRO_CONFIG, 0x00);   
  1. 退出睡眠模式:

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 字符设备接口

  1. 设备节点:/dev/i2c-N
  2. 基本操作:

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, &reg, 1);
read(fd, buf, 6); // 读取加速度数据

5.2 ioctl高级控制

批量传输示例:

c

Copy

struct i2c_msg msgs[2] = {
    { // 写寄存器地址
        .addr = 0x68,
        .flags = 0,
        .len = 1,
        .buf = &reg
    },
    { // 读数据
        .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 传输速率优化

  1. 提升时钟频率:

c

Copy

// 配置I2C控制器为快速模式
i2c_smbus_write_byte_data(client, I2C_SPEED, 0x01); 
  1. 使用DMA传输:

c

Copy

struct i2c_msg msg = {
    .flags = I2C_M_DMA_SAFE,
    .len = 512,
    .buf = dma_buffer
};

7.2 调试技巧

  1. 逻辑分析仪抓包:
    • 检查起始/停止条件
    • 验证地址和数据波形
    • 测量实际传输速率
  2. 内核调试信息:

bash

Copy

echo 1 > /sys/module/i2c_core/parameters/debug
dmesg | grep i2c
  1. I2C工具集:

bash

Copy

# 扫描总线设备
i2cdetect -y 1

# 寄存器读取
i2cget -y 1 0x68 0x75

八、总结与展望

本文系统性地讲解了I2C总线从硬件原理到Linux驱动开发的完整知识体系,结合Exynos4412和MPU6050的实践案例,展示了嵌入式系统中外设驱动的开发流程。在物联网设备蓬勃发展的今天,掌握I2C等基础总线技术对于嵌入式开发者至关重要。

未来发展方向:

  1. 自动化设备树配置:开发可视化配置工具
  2. 实时性优化:结合RT-Preempt补丁提升实时性能
  3. 安全增强:增加I2C通信加密机制
  4. AI驱动:集成机器学习模型实现智能传感器融合

通过持续深入理解Linux内核机制,结合具体硬件平台特性,开发者能够构建出高效稳定的嵌入式系统,为智能设备的发展奠定坚实基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值