嵌入式Linux驱动开发(I2C专题)(三)

本文介绍了如何在Linux系统中通过内核提供的i2c-dev驱动程序直接访问I2C设备,包括I2C-Tools的使用方法、交叉编译设置、以及使用SMBus和一般I2C协议操作AP3216C传感器的示例。

无需编写驱动直接访问设备_I2C-Tools介绍

参考资料:

  • Linux驱动程序: drivers/i2c/i2c-dev.c
  • I2C-Tools-4.2: https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/
  • AP3216C:
    • git clone https://e.coding.net/weidongshan/01_all_series_quickstart.git

1. I2C硬件连接

在这里插入图片描述

2. 无需编写驱动程序即可访问I2C设备

APP访问硬件肯定是需要驱动程序的,
对于I2C设备,内核提供了驱动程序drivers/i2c/i2c-dev.c,通过它可以直接使用下面的I2C控制器驱动程序来访问I2C设备。
框架如下:
在这里插入图片描述
i2c-tools是一套好用的工具,也是一套示例代码。

3. 体验I2C-Tools

使用一句话概括I2C传输:APP通过I2C Controller与I2C Device传输数据。
所以使用I2C-Tools时也需要指定:

  • 哪个I2C控制器(或称为I2C BUS、I2C Adapter)
  • 哪个I2C设备(设备地址)
  • 数据:读还是写、数据本身
3.1 交叉编译
  • 在Ubuntu设置交叉编译工具链
    • STM32MP157
      export ARCH=arm
      export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
      export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
      
    • IMX6ULL
      export ARCH=arm
      export CROSS_COMPILE=arm-linux-gnueabihf-
      export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
      
  • 修改I2C-Tools的Makefile指定交叉编译工具链
  CC      ?= gcc
  AR      ?= ar
  STRIP   ?= strip
  改为(指定交叉编译工具链前缀, 去掉问号):
  CC      = $(CROSS_COMPILE)gcc
  AR      = $(CROSS_COMPILE)ar
  STRIP   = $(CROSS_COMPILE)strip

在Makefile中,“?=”在第一次设置变量时才会起效果,如果之前设置过该变量,则不会起效果。

  • 执行make即可
    • 执行make时,是动态链接,需要把libi2c.so也放到单板上
    • 想静态链接的话,执行:make USE_STATIC_LIB=1
3.2 用法
  • i2cdetect:I2C检测
  // 列出当前的I2C Adapter(或称为I2C Bus、I2C Controller)
  i2cdetect -l
  
  // 打印某个I2C Adapter的Functionalities, I2CBUS为0、1、2等整数
  i2cdetect -F I2CBUS
  
  // 看看有哪些I2C设备, I2CBUS为0、1、2等整数
  i2cdetect -y -a I2CBUS
  
  // 效果如下
  # i2cdetect -l
  i2c-1   i2c             STM32F7 I2C(0x40013000)                 I2C adapter
  i2c-2   i2c             STM32F7 I2C(0x5c002000)                 I2C adapter
  i2c-0   i2c             STM32F7 I2C(0x40012000)                 I2C adapter
  
  # i2cdetect -F 0
  Functionalities implemented by /dev/i2c-0:
  I2C                              yes
  SMBus Quick Command              yes
  SMBus Send Byte                  yes
  SMBus Receive Byte               yes
  SMBus Write Byte                 yes
  SMBus Read Byte                  yes
  SMBus Write Word                 yes
  SMBus Read Word                  yes
  SMBus Process Call               yes
  SMBus Block Write                yes
  SMBus Block Read                 yes
  SMBus Block Process Call         yes
  SMBus PEC                        yes
  I2C Block Write                  yes
  I2C Block Read                   yes
  
  // --表示没有该地址对应的设备, UU表示有该设备并且它已经有驱动程序,
  // 数值表示有该设备但是没有对应的设备驱动
  # i2cdetect -y -a 0  
       0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
  00: 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  10: -- -- -- -- -- -- -- -- -- -- UU -- -- -- 1e --
  20: -- -- UU -- -- -- -- -- -- -- -- -- -- -- -- --
  30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  • i2cget:I2C读
    使用说明如下:
  # i2cget
  Usage: i2cget [-f] [-y] [-a] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
    I2CBUS is an integer or an I2C bus name
    ADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given)
    MODE is one of:
      b (read byte data, default)
      w (read word data)
      c (write byte/read byte)
      Append p for SMBus PEC

使用示例:

  // 读一个字节: I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
  i2cget -f -y I2CBUS CHIP-ADDRESS
  
  // 读某个地址上的一个字节: 
  //    I2CBUS为0、1、2等整数, 表示I2C Bus
  //    CHIP-ADDRESS表示设备地址
  //    DATA-ADDRESS: 芯片上寄存器地址
  //    MODE:有2个取值, b-使用`SMBus Read Byte`先发出DATA-ADDRESS, 再读一个字节, 中间无P信号
  //                   c-先write byte, 在read byte,中间有P信号 
  i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE  
  
  // 读某个地址上的2个字节: 
  //    I2CBUS为0、1、2等整数, 表示I2C Bus
  //    CHIP-ADDRESS表示设备地址
  //    DATA-ADDRESS: 芯片上寄存器地址
  //    MODE:w-表示先发出DATA-ADDRESS,再读2个字节
  i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE  
  • i2cset:I2C写
    使用说明如下:
  # i2cset
  Usage: i2cset [-f] [-y] [-m MASK] [-r] [-a] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
    I2CBUS is an integer or an I2C bus name
    ADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given)
    MODE is one of:
      c (byte, no value)
      b (byte data, default)
      w (word data)
    i (I2C block data)
      s (SMBus block data)
      Append p for SMBus PEC

使用示例:

  // 写一个字节: I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
  //           DATA-ADDRESS就是要写的数据
  i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS
  
  // 给address写1个字节(address, value):
  //           I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
  //           DATA-ADDRESS: 8位芯片寄存器地址; 
  //           VALUE: 8位数值
  //           MODE: 可以省略,也可以写为b
  i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE [b]
  
  // 给address写2个字节(address, value):
  //           I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
  //           DATA-ADDRESS: 8位芯片寄存器地址; 
  //           VALUE: 16位数值
  //           MODE: w
  i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE w
  
  // SMBus Block Write:给address写N个字节的数据
  //   发送的数据有:address, N, value1, value2, ..., valueN
  //   跟`I2C Block Write`相比, 需要发送长度N
  //           I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
  //           DATA-ADDRESS: 8位芯片寄存器地址; 
  //           VALUE1~N: N个8位数值
  //           MODE: s
  i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN s
  
  // I2C Block Write:给address写N个字节的数据
  //   发送的数据有:address, value1, value2, ..., valueN
  //   跟`SMBus Block Write`相比, 不需要发送长度N
  //           I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
  //           DATA-ADDRESS: 8位芯片寄存器地址; 
  //           VALUE1~N: N个8位数值
  //           MODE: i
  i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN i
  • i2ctransfer:I2C传输(不是基于SMBus)
    使用说明如下:

    # i2ctransfer
    Usage: i2ctransfer [-f] [-y] [-v] [-V] [-a] I2CBUS DESC [DATA] [DESC [DATA]]...
      I2CBUS is an integer or an I2C bus name
      DESC describes the transfer in the form: {r|w}LENGTH[@address]
        1) read/write-flag 2) LENGTH (range 0-65535) 3) I2C address (use last one if omitted)
      DATA are LENGTH bytes for a write message. They can be shortened by a suffix:
        = (keep value constant until LENGTH)
        + (increase value by 1 until LENGTH)
        - (decrease value by 1 until LENGTH)
        p (use pseudo random generator until LENGTH with value as seed)
    
    Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):
      # i2ctransfer 0 w1@0x50 0x64 r8
    Example (same EEPROM, at offset 0x42 write 0xff 0xfe ... 0xf0):
      # i2ctransfer 0 w17@0x50 0x42 0xff-
    

    使用举例:

    // Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):
    # i2ctransfer -f -y 0 w1@0x50 0x64 r8
    
    // Example (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50):
    # i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3
    
    // Example 
    // first: (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50)
    // and then: (bus 0, read 3 byte at offset 0x64 from EEPROM at 0x50)
    # i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3@0x50  
    # i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3 //如果设备地址不变,后面的设备地址可省略
    
3.3 使用I2C-Tools操作传感器AP3216C

百问网的开发板上有光感芯片AP3216C:
在这里插入图片描述
AP3216C是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:

  • 复位:往寄存器0写入0x4
  • 使能:往寄存器0写入0x3
  • 读光强:读寄存器0xC、0xD得到2字节的光强
  • 读距离:读寄存器0xE、0xF得到2字节的距离值
    AP3216C的设备地址是0x1E,假设节在I2C BUS0上,操作命令如下:
  • 使用SMBus协议
i2cset -f -y 0 0x1e 0 0x4
i2cset -f -y 0 0x1e 0 0x3
i2cget -f -y 0 0x1e 0xc w
i2cget -f -y 0 0x1e 0xe w
  • 使用I2C协议
i2ctransfer -f -y 0 w2@0x1e 0 0x4
i2ctransfer -f -y 0 w2@0x1e 0 0x3
i2ctransfer -f -y 0 w1@0x1e 0xc r2
i2ctransfer -f -y 0 w1@0x1e 0xe r2

4. I2C-Tools的访问I2C设备的2种方式

I2C-Tools可以通过SMBus来访问I2C设备,也可以使用一般的I2C协议来访问I2C设备。
使用一句话概括I2C传输:APP通过I2C Controller与I2C Device传输数据。
在APP里,有这几个问题:

  • 怎么指定I2C控制器?
    • i2c-dev.c提供为每个I2C控制器(I2C Bus、I2C Adapter)都生成一个设备节点:/dev/i2c-0、/dev/i2c-1等待
    • open某个/dev/i2c-X节点,就是去访问该I2C控制器下的设备
  • 怎么指定I2C设备?
    • 通过ioctl指定I2C设备的地址
    • ioctl(file, I2C_SLAVE, address)
      • 如果该设备已经有了对应的设备驱动程序,则返回失败
    • ioctl(file, I2C_SLAVE_FORCE, address)
      • 如果该设备已经有了对应的设备驱动程序
      • 但是还是想通过i2c-dev驱动来访问它
      • 则使用这个ioctl来指定I2C设备地址
  • 怎么传输数据?
    • 两种方式
    • 一般的I2C方式:ioctl(file, I2C_RDWR, &rdwr)
    • SMBus方式:ioctl(file, I2C_SMBUS, &args)

5. 源码分析

5.1 使用I2C方式

示例代码:i2ctransfer.c
在这里插入图片描述

5.2 使用SMBus方式

示例代码:i2cget.c、i2cset.c

如果该设备已经有了对应的设备驱动程序
* 但是还是想通过i2c-dev驱动来访问它
* 则使用这个ioctl来指定I2C设备地址

  • 怎么传输数据?
    • 两种方式
    • 一般的I2C方式:ioctl(file, I2C_RDWR, &rdwr)
    • SMBus方式:ioctl(file, I2C_SMBUS, &args)
      在这里插入图片描述
      在这里插入图片描述
<think>嗯,用户想了解嵌入式LinuxI2C总线驱动开发的教程或示例代码。首先,我需要回忆一下Linux内核中I2C子系统的结构。LinuxI2C子系统分为控制器驱动(适配器)、设备驱动核心层。控制器驱动负责处理具体的硬件操作,比如寄存器读写,而设备驱动则是针对具体的I2C设备,比如传感器或EEPROM。核心层则提供注册通信的接口。 接下来,用户可能需要一个简单的示例,展示如何编写一个I2C设备驱动。比如,假设有一个I2C温度传感器,地址是0x48,那么设备驱动需要实现probe、read等函数。需要用到i2c_driver结构体,并注册到内核中。同时,设备树(Device Tree)的配置也是关键,用户需要在设备树中添加相应的节点,指定I2C总线设备地址。 另外,用户可能对用户空间的访问方法感兴趣,比如通过sysfs或i2c-tools进行调试。这时候可以提到使用i2cget、i2cset等命令,或者直接在应用程序中使用ioctl调用。同时,需要注意内核中I2C设备驱动的加载顺序依赖关系,比如确保控制器驱动已经正确加载。 可能还需要考虑错误处理调试技巧,比如使用dmesg查看内核日志,检查设备是否成功注册,以及数据传输是否有误。此外,引用中的platform_deviceplatform_driver的关系可能相关,因为在某些情况下,I2C设备可能作为platform设备存在,需要匹配驱动。 最后,用户的问题可能涉及到如何修改现有驱动以支持不同配置,比如引用3中提到修改音频驱动实现双通道录音。类似地,修改I2C驱动可能需要调整传输模式或时钟频率等参数。需要确保代码中的I2C消息结构正确,并使用正确的API函数,比如i2c_transfer或SMBus函数。</think>### 嵌入式 Linux I2C 总线驱动开发指南 #### 一、I2C子系统框架 Linux内核中I2C子系统分为个层级: 1. **I2C核心层**:提供总线注册/注销、设备匹配等基础功能 2. **I2C控制器驱动(适配器驱动)**:实现具体SoC的I2C控制器操作,如寄存器读写 3. **I2C设备驱动**:针对具体外设(如EEPROM、传感器)的功能实现 #### 二、设备驱动开发步骤 1. **定义设备树节点**(以STM32MP157为例): ```dts &i2c1 { status = "okay"; clock-frequency = <100000>; temp_sensor@48 { compatible = "vendor,tmp175"; reg = <0x48>; }; }; ``` 2. **实现驱动结构体**: ```c static struct i2c_driver tmp175_driver = { .driver = { .name = "tmp175", .of_match_table = tmp175_of_match, }, .probe = tmp175_probe, .remove = tmp175_remove, .id_table = tmp175_id, }; ``` 3. **实现关键函数**(以温度读取为例): ```c static int tmp175_read_temp(struct i2c_client *client) { u8 buf[2]; int ret = i2c_master_recv(client, buf, 2); if (ret < 0) return ret; int temp = (buf[0] << 8) | buf[1]; return temp >> 7; // TMP175为9位精度 } ``` #### 、用户空间访问方法 1. 使用i2c-tools调试: ```bash i2cdetect -y 1 # 扫描I2C总线设备 i2cget -y 1 0x48 0x00 # 读取寄存器 ``` 2. 通过sysfs接口操作: ```c int fd = open("/dev/i2c-1", O_RDWR); ioctl(fd, I2C_SLAVE, 0x48); i2c_smbus_write_byte(fd, 0x00); int temp = i2c_smbus_read_byte(fd); ``` #### 四、关键API参考 1. **数据传输函数**: ```c i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) i2c_smbus_read_byte(struct i2c_client *client) ``` 2. **设备注册函数**: ```c module_i2c_driver(tmp175_driver) // 简化注册过程 ``` #### 五、常见问题排查 1. 确认设备树节点时钟频率与设备匹配 2. 检查`i2c-dev`模块是否加载 3. 使用示波器验证物理信号完整性 4. 通过`dmesg | grep i2c`查看内核日志[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式学习者。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值