概述
I2C(Inter-Integrated Circuit)总线是由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准。具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。
I2C 总线通过串行数据(SDA)线和串行时钟(SCL)线在连接到总线的器件间传递信息。每个器件都有一个唯一的地址识别(无论是微控制器——MCU、LCD驱动器、存储器或键盘接口),而且都可以作为一个发送器或接收器(由器件的功能决定)。
I2C有四种工作模式:
1.主机发送
2.主机接收
3.从机发送
4.从机接收
以我们的S5PC100来说,我们主要讨论的主机发送模式。
在传输数据的时候,串行数据线(SDA)必须在时钟的高电平周期保持稳定,串行数据线(SDA)的高或低电平的状态只有在串行时钟线(SCL)的时钟信号是低电平时才能改变。这是由于当处于时钟的高电平周期时,串行数据线的电平变化具有特殊的含义。
串行时钟线(SCL)是高电平时,串行数据线(SDA)从高电平向低电平切换,这种情况表示起始条件。
串行时钟线(SCL)是高电平时,串行数据线(SDA)从低电平向高电平切换,这种情况表示停止条件。
在数据传输时,发送到SDA线上的每个字节必须是8位,每次数据可以发送的字节数量不受限制。并且每个字节后必须跟一个响应位。
在Linux内核中定义了I2C驱动体系结构。我们通过一个图来说明:
在Linux系统中,I2C驱动由三部分构成,即I2C核心、I2C总线驱动和I2C设备驱动。
I2C核心用于联系I2C设备驱动与I2C控制驱动的纽带。实现了一组和硬件无关的函数。
I2C总线驱动用于在I2C总线上产生相应的时序。
I2C设备驱动调用I2C core中的函数来完成I2C设备驱动的注册以及I2C消息的封装。这也是我们主要关心的部分。
在I2C子系统中实现I2C通用设备驱动,我们可以使用这个通过驱动来实现对I2C设备的控制,但是I2C消息就必须由用户程序自己封装,这绝对是不可行的。所以我们推荐自己实现对应的I2C特定驱动,从中封装I2C消息。使用户程序不需关心内部的细节。
但是我们还是首先需要了解一下如何使用这个通用的I2C驱动。
首先我们需要配置内核来添加I2C总线驱动。修改内容为:
首先修改配置文件,添加S5PC100的支持。然后执行make menuconfig来添加总线设备驱动和通过设备驱动。
之后编译内核,运行内核后我们会发现在/dev/下多了两个设备节点i2c-0和i2c-1.这是证明我们的通用设备驱动已经加载成功。
我们直接访问这个设备文件就可以控制I2C设备。如何操作呢?
在应用程序中:
1.打开i2c设备文件。
2.构建消息
3.执行ioctl操作
打开i2c设备文件不用多说。构建什么消息呢?
我们首先需要来看两个数据结构
i2c_rdwr_ioctl_data 用于给ioctl操作传递参数。当中有两个成员。
第一个参数为msgs。即要传递的消息。它是一个指针,由于保存一个消息的首地址或者一组消息的数组首地址。
第二个参数为消息的个数。
接下来我们来看i2c_msg
该结构体用于封装一个消息。
addr:即从设备的地址
flags:标志,下面定义了很多标志。0代表写。1代表读
len: 数据的长度
buf: 用于发送或者接收数据的缓冲区。
知道构建消息相关的两个结构体了,那么接下来我们应该如何构建消息呢?这就需要查看对应要访问的i2c设备的芯片手册了。
打开lm75的芯片手册。
首先我们需要找到lm75的从机地址。
i2c设备地址有7位构成高4位是固定的,低3位由设备的三个地址引脚来决定。
通过这里我们得知lm75温度传感器的i2c地址的高4位。低三位我们通过查看原理图可以得知都为0.所以lm75的i2c地址为0x48.
我们通过一个图可以得知lm75温度传感器的内部寄存器的操作过程:
首先我们可以看到一个pointer 寄存器,它用于选择到底要访问哪个寄存器。
剩下四个寄存器分别有不同的功能。我们当前就是读取温度值,所以直接操作温度寄存器即可。
如何操作我们需要查看lm75的访问时序图。
通过查看lm75的温度寄存器可以得知温度寄存器有16位,高8位表示对应的整数的温度,第7位值为1,表示在高8位的温度的基础上加0.5摄氏度。并且如果最高位为1的话表示当前读到的数值是一个负数的补码。即零
下多少度。
所以如果想要读取温度的话我们至少需要读取两个字节的数据。
接下来我们来看时序图。
根据时序图我们就可以构建消息了。
首先我们需要设置pointer寄存器,指向lm75的温度寄存器。
之后再构建一个消息用于读取温度的值
消息构建好后就可以执行ioctl操作了:
我们测试一下。。。
那么如何是我们自己编写特定的i2c驱动怎么做呢?
首先我们需要在平台代码中加入lm75的设备信息。
type代表设备类型,addr:从设备号,platform_data平台数据。irq中断号。
添加设备信息后,在内核启动后,会将数组中的所有设备全部注册到内核中。
接下来我们来看如何编写一个特定的i2c设备驱动:
1.构建i2c设备驱动结构体(i2c_driver)
1.probe/remove 对应之前的加载函数与卸载函数
2.driver成员。通用驱动属性,我们需要设置name成员但不是用于匹配设备,那如何匹配设备呢?
3.id_table成员用于设置当前i2c_driver所支持的设备类型。
i2c_device_id结构体中有两个成员name用于配置设备,driver_data驱动的私有数据,我们基本不会用,直接传递0即可。
并且要求,在设置完支持的设备后,需要在后面添加一个空成员用于结束标志
2.实现字符设备驱动模型(之前的加载函数用于probe,卸载函数用于remove)
3.实现读操作用于读取温度。当中构建消息,发送消息:
i2c_transfer(适配器, 消息首地址, 数量);
适配器从何而来,当我们的i2c设备与驱动匹配后执行probe函数,第一个参数为client。它当中就包含了适配器,所以我们在probe函数中,将client的首地址保存起来,用于传输消息。
3.加载函数中注册i2c设备驱动i2c_add_driver
4.卸载函数中注销i2c设备驱动i2c_del_driver
文章来源:华清远见嵌入式学院,原文地址:http://www.embedu.org/Column/Column853.htm