I2C


概述

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

更多相关嵌入式免费资料查看华清远见讲师博文>>

相关内容