Linux I2C应用编程

本文介绍了Linux下I2C编程,包括常用的I2C工具i2cdetect、i2cget、i2cset和i2cdump的使用方法,并详细阐述了在Linux应用程序中进行I2C外设读写操作的步骤和注意事项,以及I2C的寻址方式和数据传送方式。

I2C的协议内容在这里就不大费周章的描述,主要记录一些自己在实际应用过程中觉得有助于调试一些方法。

一、介绍I2C 的几个比较有用的tool

(1)i2cdetect

i2cdetect的主要功能就是I2C设备查询,它用于扫描I2C总线上的设备。它输出一个表,其中包含指定总线上检测到的设备的列表。

该命令的常用格式为:i2cdetect [-y] [-a] [-q|-r] i2cbus [first last]。具体参数的含义如下:

-y

取消交互模式。默认情况下,i2cdetect将等待用户的确认, 当使用此标志时,它将直接执行操作。

-a

强制扫描非规则地址。一般不推荐。

-q

使用SMBus“快速写入”命令进行探测。一般不推荐。

-r

使用SMBus“接收字节”命令进行探测。一般不推荐。

-F

显示适配器实现的功能列表并退出。

-V

显示I2C工具的版本并推出。

-l

显示已经在系统中使用的I2C总线。

i2cbus

表示要扫描的I2C总线的编号或名称。

first last

表示要扫描的从设备地址范围。

该功能的常用方式:

​ 第一,先通过i2cdetect -l查看当前系统中的I2C的总线情况:

 第二,若总线上挂载I2C从设备,可通过i2cdetect扫描某个I2C总线上的所有设备。可通过控制台输入i2cdetect -y 1:(其中"–"表示地址被探测到了,但没有芯片应答; "UU"因为这个地址目前正在被一个驱动程序使用,探测被省略;而16进制的地址号60,1e和50则表示发现了一个外部片选从地址为0x60,0x1e(AP3216)和0x50(eeprom)的外设芯片。

​ 第三,查询I2C总线1 (I2C -1)的功能,命令为i2cdetect -F 1:

(2)i2cget

i2cget的主要功能是获取I2C外设某一寄存器的内容。该命令的常用格式为:

​ i2cget [-f] [-y] [-a] i2cbus chip-address [data-address [mode]]。具体参数的含义如下:

-f

强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问 已经在内核驱动程序控制下的设备。

-y

取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此 标志时,它将直接执行操作。

-a

允许在0x00 - 0x07和0x78 - 0x7f之间使用地址。一般不推荐。

i2cbus

表示要扫描的I2C总线的编号或名称。这个数字应该与i2cdetect -l列出 的总线之一相对应。

chip-address

要操作的外设从地址。

data-address

被查看外设的寄存器地址。

mode

显示数据的方式: b (read byte data, default) w (read word data) c (write byte/read byte)

下面是完成读取0总线上从地址为0x50的外设的0x10寄存器的数据,命令为:

​ i2cget -y -f 1 0x50 0x10 

(3)i2cset

i2cset的主要功能是通过I2C总线设置设备中某寄存器的值。该命令的常用格式为:

​ i2cset [-f] [-y] [-m mask] [-r] i2cbus chip-address data-address [value] …[mode]

具体参数的含义如下:

-f

强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。

-r

在写入值之后立即读取它,并将结果与写入的值进行比较。

-y

取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。

-V

显示I2C工具的版本并推出。

i2cbus

表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。

-m mask

如果指定mask参数,那么描述哪些value位将是实际写入data-addres的。掩码中设置为1的位将从值中取出,而设置为0的位将从数据地址中读取,从而由操作保存。

mode

b: 单个字节 w:16位字 s:SMBus模块 i:I2C模块的读取大小 c: 连续读取所有字节,对于具有地址自动递增功能的芯片(如EEPROM)非常有用。 W与 w类似,只是读命令只能在偶数寄存器地址上发出;这也是主要用于EEPROM的。

下面是完成向0总线上从地址为0x50的eeprom的0x10寄存器写入0x55,命令为:

​ i2cset -y -f 1 0x50 0x10 0x55

​ 然后用i2cget读取1总线上从地址为0x50的eeprom的0x10寄存器的数据,

命令为:i2cget -y -f 1 0x50 0x10

遇到有写保护的外设,i2cget会提示 i2cset: write failed: Permission denied

(4)i2cdump

​ i2cdump的主要功能查看I2C从设备器件所有寄存器的值。 该命令的常用格式为:i2cdump [-f] [-r first-last] [-y] [-a] i2cbus address [mode [bank [bankreg]]]。具体参数的含义如下:

-f

强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。

-r

限制正在访问的寄存器范围。 此选项仅在模式b,w,c和W中可用。对于模式W,first必须是偶数,last必须是奇数。

-y

取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。

-V

显示I2C工具的版本并推出。

i2cbus

表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。

first last

表示要扫描的从设备地址范围。

mode

b: 单个字节 w:16位字 s:SMBus模块 i:I2C模块的读取大小 c: 连续读取所有字节,对于具有地址自动递增功能的芯片(如EEPROM)非常有用。W与 w类似,只是读命令只能在偶数寄存器地址上发出;这也是主要用于EEPROM的。

​ 下面是完成读取0总线上从地址为0x50的eeprom的数据,命令为:

​ i2cdump -f -y 1 0x50

二、在linux应用程序中操作I2C外设(读写)

        首先通过前面的介绍,我们已经知道站在cpu的角度来看,操作I2C外设实际上就是通过控制cpu中挂载该I2C外设的I2C控制器,而这个I2C控制器在linux系统中被称为“I2C适配器”,这个已经在驱动简介中介绍过了。而且众所周知,在linux系统中,每一个设备都是以文件的形式存在的,所以在linux中操作I2C外设就变成了操作I2C适配器设备文件。Linux系统(也就是内核)为每个I2C适配器生成了一个主设备号为89的设备节点(次设备号为0-255),它并没有针对特定的I2C外设而设计,只是提供了通用的read(),write(),和ioctl()等文件操作接口,在用户空间的应用层就可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。

 (1)首先要确定所要操作的I2C  adapter的设备文件节点

        i2c适配器的设备节点是/dev/i2c-x,其中x是数字。由于适配器编号是动态分配的(和注册次序有关),所以想了解哪一个适配器对应什么编号,可以查看/sys/class/i2c-dev/目录下的文件内容(在这里笔者强烈建议读者好好利用好sys文件系统)

(2)ioctl命令

这个可以参考内核源码中的include/linux/i2c-dev.h文件。下面举例说明主要的IOCTL命令:

I2C_SLAVE_FORCE

设置I2C从设备地址(只有在该地址空闲的情况下成功)

I2C_SLAVE_FORCE

强制设置I2C从设备地址(无论内核中是否已有驱动在使用这个地址都会成功)

I2C_TENBIT

选择地址位长: 0 表示是7bit地址 ; 不等于0 就是10 bit的地址。只有适配器支持I2C_FUNC_10BIT_ADDR,这个请求才是有效的。

I2C_FUNCS

获取适配器支持的功能,详细的可以参考文件include/linux/i2c.h

I2C_RDWR

设置为可读写

I2C_RETRIES

设置收不到ACK时的重试次数

I2C_TIMEOUT

设置超时的时限

(3)实例

第一,打开设备节点

第二,设置从设备的器件地址,通过ioctl(fd,I2C_SLAVE, 0x50),也可用I2C_SLAVE_FORCE

第三,通过write往从设备写数据,第一个字节需要写从设备的寄存器地址,比如0x30,后面再跟着实际数据;

通过read读取从设备的数据,先通过write发要读的寄存器地址给从设备,然后再read数据。

注意事项:

1、在代码段里有 ioctl(fd,I2C_TIMEOUT,100);,这是设置timeout时间,在做项目的时候有遇到如       果timeout时间不够长,会一直等待不到对方的ACK。(比如APM32F3959是 iPhone 配件模块       上的认证芯片,这是在实际项目中遇到的情况);

2、可以在代码中多次尝试write/read,因为有可能会失败,为了提高成功率和可靠性,这一点也         不能忽略。这一点也是在实际中测试出来的。 ioctl(fd,I2C_RETRIES,3);
 

3、在有写保护的从设备,write会提示Permission denied。

以下是写了一个简单的测试程序:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include "main_opt.h"

#define I2C_DEVICE    	"/dev/i2c-1"	
#define EEPROM_ADDR    0x50
#define DEVICE_ADDR     0x30
#define NUM 3

int I2C_write(int fd,char inRegister,char *inWriteBuf,int inWriteLen,int inRetryCount)
{
        int trynum,size,len,err;
        char buf[inWriteLen + 1] = {0};

        buf[0] = inRegister;
        memcpy(&buf[1],inWriteBuf,inWriteLen);
        len = inWriteLen + 1;
        
        for(trynum = 1;trynum <= inRetryCount;trynum++)
        {
                size = write(fd,buf,len);
                if (size == len)
                {
                        err = 0;
                        break;
                }
                
                err = -1;
                printf("write register 0x%02X, %d bytes failed (try %d): %s\n", inRegister, inWriteLen, trynum, strerror(errno) );
                if (trynum == inRetryCount)
                {
                        printf("Write Try more than %d times\n",inRetryCount);
                        break;;
                }
                usleep( 5000 );
        }

        return err;
}

int I2C_read(int fd,char inRegister,char *inReadBuf,int inReadLen,int inRetryCount)
{
        int trynum,size,len,err;
        char  buf[1] = {0};
        if (!inReadBuf)
        {
                printf("inReadBuf is NULL\n");
                return -1;
        }
        
        buf[0] = inRegister;
        
        for(trynum = 1;trynum <= inRetryCount;trynum++)
        {
                size = write(fd,buf,1);
                if (size == 1)
                {
                        err = 0;
                        break;
                }
                
                err = -1;
                printf("write register 0x%02X, %d bytes failed (try %d): %s\n", inRegister, inReadLen, trynum, strerror(errno) );

                if (trynum == inRetryCount)
                {
                        printf("Write Try more than %d times\n",inRetryCount);
                        
                        goto __EXIT;
                }
                usleep( 5000 );
        }
            
        usleep( 5000 );

        for(trynum = 1;trynum <= inRetryCount;trynum++)
        {
                size = read(fd,inReadBuf,inReadLen);
                if (size == inReadLen)
                {
                        err = 0;
                        break;
                }

                err = -1;
                printf("write register 0x%02X, %d bytes failed (try %d): %s\n", inRegister, inReadBuf, trynum, strerror(errno) );

                if (trynum == inRetryCount)
                {
                        printf("Read Try more than %d times\n",inRetryCount);
                        
                        goto __EXIT;
                }
                usleep( 5000 );
        }
        
__EXIT:
        return err;
}

int I2C_test()
{
 	int fd,i,ret=0;
       int size = 0;
       
 	unsigned char w_add=DEVICE_ADDR;
 	unsigned char rd_buf[NUM] = {0};  
 	unsigned char wr_buf[NUM] = {0};     
 
 	printf("hello,this is read_write i2c test \n");
 	
 	
 	fd =open(I2C_DEVICE, O_RDWR);
 	if (fd< 0) 
 		printf("open"I2C_DEVICE"failed \n");
       else
              printf("open %s OK\n",I2C_DEVICE);


 	if (ioctl(fd,I2C_SLAVE, EEPROM_ADDR) < 0) 
 	{            
 		printf("set slave address failed \n");
              return -1;
 	}

       ioctl(fd,I2C_TIMEOUT,100);
       ioctl(fd,I2C_RETRIES,3);

       I2C_read(fd,DEVICE_ADDR,rd_buf,NUM,5);
       for(i = 0; i < NUM;i++)
            printf("rd_buf[%d]===%x\n",i,rd_buf[i]);
     
       if (fd > 0)
       {
            close(fd);
            fd = -1;
       }
 
 	return 0;
}

三、寻址方式和数据传送方式

I2C总线上传送的数据时广义的,既包括地址,又包括真正的数据。

主机在发送起始信号后必须先发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向,‘0’表示主机发送数据给->从机,‘1’表示从机发送数据给->主机。

总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定为发送器或接收器。
 

I2C总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位,发送器发送完一个字节数据后接收器必须发送1位应答位来回应发送器,即一帧共有9位。

I2C每次发送数据必须是8位。

MSB固定,先发高位,再发低位。

### Linux I2C 应用编程教程与示例 #### 一、简介 在嵌入式系统开发中,I2C(Inter-Integrated Circuit)总线是一种广泛使用的串行通信协议。对于基于Linux系统的设备而言,在用户空间通过特定API操作I2C硬件成为了一种常见需求。 #### 二、准备工作 为了能够在Linux环境中编写针对I2C外设的应用程序,通常需要安装`i2c-tools`工具包以及确保内核已经加载了相应的驱动模块。可以通过命令`sudo modprobe i2c-dev`来动态加载该模块[^1]。 #### 三、访问方式概述 Linux提供了两种主要的方法用于从用户态控制I2C设备: - **字符设备文件接口**:这是最常用的方式之一,即把每一个连接到系统上的I2C适配器都映射成一个特殊的字符型设备节点(通常是/dev/i2c-N)。应用程序可以像读写普通文件那样打开这些特殊文件并执行ioctl调用来发送指令给底层硬件。 - **sysfs虚拟文件系统**:另一种方法是利用/sys/class/i2c_dev目录下的条目来进行简单的交互测试或诊断目的。不过这种方式不适合构建高性能的数据传输路径。 #### 四、简单例子——读取温度传感器数据 下面给出一段Python代码片段作为示范,假设有一个DS1620数字温度计接到了主板上编号为1号的I2C控制器,并且其7位地址固定为0x9A (十进制154)。 ```python import smbus def read_temperature(): bus = smbus.SMBus(1) # 创建SMBus对象,参数指定要使用的I2C通道 address = 0x9a # DS1620默认地址 config_reg = 0xAC # 配置寄存器指针值 temp_reg = 0xAA # 温度寄存器指针值 try: # 设置配置模式 bus.write_byte_data(address, config_reg, 0b00000001) # 启动转换过程... time.sleep(0.5) # 等待一段时间让芯片完成测量 raw_temp = bus.read_word_data(address, temp_reg) high_byte = ((raw_temp & 0xFF00)>>8)*2 low_byte = (raw_temp & 0x00FF)/128 temperature_celsius = high_byte + low_byte - 0.5 return round(temperature_celsius, 1) except Exception as e: print(f"Error occurred while reading from sensor: {e}") raise if __name__ == "__main__": import time t = read_temperature() print(f"The current ambient air temperature is approximately {t}°C.") ``` 这段脚本展示了如何借助`smbus`库轻松地向目标器件发出请求并获取响应信息。需要注意的是实际产品级代码应当更加健壮,比如加入重试机制处理偶尔可能出现的通讯错误等问题。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值