刚入门Linux设备I2C通信,感觉很迷惑,不管是看书,看视频,还是网上查,都是千篇一律的资料,抄来抄去,还都是过时的,很TM 恶心。
比如说我现在在做的,在应用程序中去读取EEPROM的数据,问题到现在还没解决(2018/8/1:现在已经解决了),但是至少已经有了一点点的认识,虽然使用的是网上说的方法,但是第一,方法太多太杂,第二,说的不清不楚。
下面先说下自己的一些心得吧,后续有了再加进来。
一.
很多情况下都不需要像网上说的那样累赘,因为我们很多人都是在平台的基础上去进行开发,所以像i2c_adapter和i2c_algorithm这类的驱动我们根本不用写,我们写的最多的应该是(I2C)设备驱动,也就是把某一个设备接入到Linux系统中,然后写这个设备的驱动。
二.
(I2C)设备驱动也有很多种写法,Linux3.x的内核中使用dts去代替原先的platform_data(好像是这样),这样一来,设备驱动的编写方式就有了改动,一个网页是这样,一个网页又是那样,有的甚至网页上都没有,对于刚入门的学者,也许他们拿到的内核版本和网上的版本有什么区别,他们根本就不知道,当然也不知道应该怎么写。
三.
以读取EEPROM数据为例,有很多方法都可以。比如说接下来说的这种
第一种:
dts,(eeprom)设备驱动都不需要写,只需要在应用程序中通过I2C总线对设备地址进行传输就可以了。如下:
#include <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#define I2C_RETRIES 0x0701
#define I2C_TIMEOUT 0x0702
#define I2C_RDWR 0x0707
/*********参数定义与内核一致,路径include/linux/i2c-dev.h*******/
struct i2c_msg
{
unsigned short addr;
nsigned short flags;
#define I2C_M_TEN 0x0010
#define I2C_M_RD 0x0001
unsigned short len;
unsigned char *buf;
};
struct i2c_rdwr_ioctl_data
{
struct i2c_msg *msgs;
int nmsgs;
/* nmsgs这个数量决定了有多少开始信号,对于“单开始时序”,取1*/
};
int main()
{
int fd,ret;
struct i2c_rdwr_ioctl_data e2prom_data;
fd=open("/dev/i2c-0",O_RDWR);
/*
*/dev/i2c-0是在注册i2c-dev.c后产生的,代表一个可操作的适配器。如果不使用i2c-dev.c
*的方式,就没有,也不需要这个节点。
*/
if(fd<0)
{
perror("open error");
}
e2prom_data.nmsgs=2;
/*
*因为操作时序中,最多是用到2个开始信号(字节读操作中),所以此将
*e2prom_data.nmsgs配置为2
*/
e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));
if(!e2prom_data.msgs)
{
perror("malloc error");
exit(1);
}
ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/
ioctl(fd,I2C_RETRIES,2);/*重复次数*/
/***write data to e2prom**/
e2prom_data.nmsgs=1;
(e2prom_data.msgs[0]).len=2; //1个待写入的寄存器地址和1个数据
(e2prom_data.msgs[0]).addr=0x50;//e2prom 设备地址
(e2prom_data.msgs[0]).flags=0; //write
(e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
(e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 待写入寄存器的地址
(e2prom_data.msgs[0]).buf[1]=0x58;//the data to write
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
if(ret<0)
{
perror("ioctl error1");
}
sleep(1);
/******read data from e2prom*******/
e2prom_data.nmsgs=2;
(e2prom_data.msgs[0]).len=1; //1个待写入的寄存器地址
(e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址
(e2prom_data.msgs[0]).flags=0;//write
(e2prom_data.msgs[0]).buf[0]=0x10;//e2prom数据地址,即寄存器地址
(e2prom_data.msgs[1]).len=1;//1个待读出的数据
(e2prom_data.msgs[1]).addr=0x50;// e2prom 设备地址
(e2prom_data.msgs[1]).flags=I2C_M_RD;//read
(e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1);//存放返回值的地址。
(e2prom_data.msgs[1]).buf[0]=0;//初始化读buffer
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
if(ret<0)
{
perror("ioctl error2");
}
printf("buff[0]=%x/n",(e2prom_data.msgs[1]).buf[0]);
/***打印读出的值,没错的话,就应该是前面写的0x58了***/
close(fd);
return 0;
}
2018/8/1:继续添加另一种方法(第二种):
unsigned char p_tof_eeprom_double[128*1024];//全局变量,用于存储标定数据
int iic1_open()
{
int fd;
fd=open("/dev/i2c-1",O_RDWR);
if(fd<0)
{
perror("/dev/i2c-1 open error\n");
return -1;
}
return fd;
}
//读操作先发Slaveaddr_W+Regaddr_H+Regaddr_L 3个字节来告诉设备操作器件及两个byte参数
//然后发送Slaveaddr_R读数据
static int iic_read(int fd, unsigned char buff[], int reg_addr, int count)
{
int ret;
char sendbuffer[2];
sendbuffer[0]=reg_addr>>8;
sendbuffer[1]=reg_addr;
write(fd,sendbuffer,2);
ret=read(fd,buff,count);
// printf("read %d byte at 0x%x\n", ret, reg_addr);
return ret;
}
//在写之前,在数据前加两个byte的参数,根据需要解析
static int iic_write(int fd, unsigned char buff[], int reg_addr, int count)
{
int ret;
static char sendbuffer[100];
memcpy(sendbuffer+2, buff, count);
sendbuffer[0]=reg_addr>>8;
sendbuffer[1]=reg_addr;
ret=write(fd,sendbuffer,count+2);
// printf("write %d byte at 0x%x\n", (ret-2), reg_addr);
return ret;
}
void save_file_to_sdcard(void *p_in,unsigned int size)
{
char save_file[100];
char *p_save = (char *)p_in;
sprintf(save_file, "/mnt/sdcard/tof_calib_data.usb");
FILE *out_file = fopen(save_file,"wb");
printf("------------------------dump begin----------------------------\n");
fwrite(p_save, size, 1, out_file);
fclose(out_file);
printf("------------------------dump over----------------------------\n");
}
int Read_Cailb_Data(int slave_address,int register_address)
{
int fd,ret,i,j;
unsigned char *p1=p_tof_eeprom_double;
fd=iic1_open();
ret = ioctl(fd,I2C_TENBIT,0); //7bit slave address
for(j=0;j<2;j++)
{
ret = ioctl(fd,I2C_SLAVE_FORCE,slave_address); //设置I2C从设备地址[6:0]
for(i=0;i<8;i++)
{
ret=iic_read(fd,p1,register_address+i*8*1024,8*1024);
// printf("%d bytes read:\n",ret);
p1+=8*1024;
}
slave_address++;
}
printf("=====Read Calibration data success!======\n");
close(fd);
return 0;
}
int Control_Register(int slave_address,int register_address,int flag,int value)
{
int fd,ret;
unsigned char buf[2];
fd=iic1_open();
ret = ioctl(fd,I2C_TENBIT,0); //7bit slave address
ret = ioctl(fd,I2C_SLAVE_FORCE,slave_address); //设置I2C从设备地址[6:0]
if(flag==0)//read
{
ret=iic_read(fd,buf,register_address,2);
// printf("======buf[0] is 0x%x==========\n",buf[0]);
// printf("======buf[1] is 0x%x==========\n",buf[1]);
printf("=====Read Register data success!======\n");
}
else if(flag==1)//write
{
buf[0]=value>>8;
buf[1]=value;
ret=iic_write(fd,buf,register_address,2);
// printf("======buf[0] is 0x%x==========\n",buf[0]);
// printf("======buf[1] is 0x%x==========\n",buf[1]);
printf("=====Write Register data success!======\n");
}
else
printf("Control Register error!\n");
close(fd);
return 0;
}
int test()
{
// read calib
Read_Cailb_Data(0x56,0);
// save file to sdcard
// save_file_to_sdcard(p_tof_eeprom_double,128*1024);
// read register
// Control_Register(0x3d,0x9835,0,NULL);
// write register
// Control_Register(0x3d,0x9835,1,0x0312);
return 0;
}
首先要说明,这种方法也不需要在dts及设备驱动中做任何的添加及修改,只需要在应用层添加这些函数即可。
其次,这种方法很通用,不管是eeprom还是sensor的寄存器数值,都可以进行随机读写,比第一种方法通用的多。
当然,还有其他的方法,比如说通过sysfs的方式去读取,或者其他方式,但是这些方式可能需要编写eeprom的设备驱动。我现在也不太懂,等我弄懂了再来添加。