Chinaunix首页 | 论坛 | 博客
  • 博客访问: 99385
  • 博文数量: 35
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 147
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-05 22:10
个人简介

不经历风雨,怎能见彩虹!

文章分类

全部博文(35)

文章存档

2013年(35)

我的朋友

分类: LINUX

2013-05-17 17:33:01

 

作者:曹忠明,讲师。

I2C总线是有Philips公司开发的,它是一种比较简单的总线,接线简单:只有两根线数据线(SCL)和时钟线(SDA),控制简单。所以一些封装较小的器件多使用I2C总线,常见的使用I2C总线的设备有EEPROM、rtc及一些传感器。这里我们介绍下基于linux的I2C设备驱动的编写。

I2C设备驱动的编写有多种方式:

一种是直接操作CPU的I2C控制器,正对于某一个设备写一个字符驱动,这种驱动相对来说比较直接,不需要太依赖于内核相关配置,但是这类设备驱动依赖CPU,可移植性较差。

一种是基于linux内核I2C子系统完成设备驱动的编写,一般内核会继承相关CPU的控制器驱动即使没有也可以通过技术支持可以获得,所以我们只需要使用linux下I2C子系统提供的相关接口来构建我们的设备驱动就行了。这样我们的设备驱动并不依赖于某一个特定的CPU,可移植性较好。
        在写驱动之前我们先了解下I2C总线中几个比较重要的概念:

1、 地址

I2C总线上可以连接多个相同或不同的设备,总线怎么样才能知道数据应该发送到那个设备呢,这里需要一个地址来唯一的标识一个设备。I2C设备地址有7位地址和10位地址,那么这个地址是怎么来的呢,其实这个地址我们可以通过相关的芯片手册获得,这里通过一个EEPROM和一个温度传感器来说明。
EEPROM(AT24C02/04/08/16)芯片手册上有如下说明:

再结合原理图

在通过芯片手册我们可以知道EEPROM的地址的前四位为1010,通过原理图A0/A1/及NC的状态我们可以知道后三位为000,这样我们就知道这个EEPROM在I2C总线上的地址为7’b1010000。

同样我们可以通过如下内容知道温度传感器的地址为7’b1001000

芯片手册:

原理图:

2、 时序

不同的I2C设备有不同的时序,我们也可以说是不同的协议,我们需要了解一些时序相关的东西,我们发送数据是什么时候开始什么时候结束,怎么发送都由这个时序决定。

开始/停止                


完整时序                 


现在的CPU多数都有I2C控制器,我们不需要太关心具体时序的实现,这些都由控制器去完成,并且内核已经集成多数CPU的I2C控制器驱动,我们写设备驱动就是按照I2C子系统的要求,为它提供需要的数据即可。

I2C子系统下设备驱动有两种模式,一种是用户模式设备驱动这种驱动依赖I2C子系统中的i2c-dev这个驱动,我们需要在应用程序去封装数据,这需要应用程序的开发人员具备相当的硬件基础,另外一种是普通的设备驱动。分别看下这两种方法的具体实现过程。

用户模式驱动实现:

相关结构体:

struct i2c_msg {
                        __u16 addr;     /* slave address  */
                        __u16 flags;
                        #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
                        #define I2C_M_RD 0x0001 /* read data, from slave to master */
                        __u16 len;     /* msg length */
                        __u8 *buf;     /* pointer to msg data */
               };
                          struct i2c_rdwr_ioctl_data {
                          struct i2c_msg *msgs; /* pointers to i2c_msgs */
                          __u32 nmsgs; /* number of i2c_msgs */
                };                

上面就是我们向底层传递的结构,我们需要把我们的时序封装成这样的结构然后传递下去就行了。                

AT24c04时序                

               

转化为消息结构为:
                  e2prom_data.nmsgs=2;
                  (e2prom_data.msgs[0]).len=1; //e2prom 目标数据的地址
        (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]=0x0; //e2prom数据地址
        (e2prom_data.msgs[1]).len=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; //初始化读缓冲                

这里我们封装了两个消息,在这个时序中操作模式改变了,所以我们必须封装为两个时序,如果操作模式不变封装一个消息就可以了比如如下时序:                

        

  e2prom_data.nmsgs=1;
                  (e2prom_data.msgs[0]).len=1; //e2prom 目标数据的地址
        (e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址
        (e2prom_data.msgs[0]).flags=0; //write
                  (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(1);
                (e2prom_data.msgs[0]).buf[0]=0x0; //e2prom数据地址                

接着我们可以看看别的设备的时序大家可以发现大同小异!                


我们把刚才封装的消息通过ioctl发下去就能够完成数据的读写了。例程如下:
         #include
                   #include
                   #include
                   #include
                   #include
                   #include
                   #include
                   #include
                   #include
                   #include                         

int main()
          {
                  int fd,ret;
                  struct i2c_rdwr_ioctl_data e2prom_data;
                  fd=open("/dev/i2c-0",O_RDWR);
                          if(fd<0)
                          {
                                  perror("open error");
                          }
                  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);/*重复次数*/
                  sleep(1);

                  e2prom_data.nmsgs=2;
                  (e2prom_data.msgs[0]).len=1; //e2prom 目标数据的地址
        (e2prom_data.msgs[0]).addr=0x48; // e2prom 设备地址
        (e2prom_data.msgs[0]).flags=0; //write
                  (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
                  (e2prom_data.msgs[0]).buf[0]=0x0; //e2prom数据地址
        (e2prom_data.msgs[1]).len=2; //读出的数据
        (e2prom_data.msgs[1]).addr=0x48; // e2prom 设备地址
        (e2prom_data.msgs[1]).flags=I2C_M_RD;//read
                  (e2prom_data.msgs[1]).buf=(unsigned char*)malloc(2);//存放返回值的地址。
        (e2prom_data.msgs[1]).buf[0]=0; //初始化读缓冲
        (e2prom_data.msgs[1]).buf[1]=0; //初始化读缓冲
        ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
                  if(ret<0)
                  {
                          perror("ioctl error2");
                  }
                  printf("%x",(e2prom_data.msgs[1]).buf[0]);
                  printf("%x\n",(e2prom_data.msgs[1]).buf[1]);

                  close(fd);
                  return 0;
        }

阅读(1071) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~