I2C协议和驱动框架分析(一)
一、I2C协议及硬件原理
1. I2C总线协议
1.1 概念
I2C(Inter-Integrated Circuit)总线是由Philips公司开发的两线式串行总线,这两根线分别为时钟线(SCL)和双向数据线 (SDA)。I2C在标准模式下传输速率最高100Kbits/s,在快速模式下最高可达400Kbits/s,高速模式下位速率高达3.4Mbits/s。在嵌入式系统中,I2C应用非常广泛。大多数SoC中都集成了I2C总线(I2C控制器),这是硬件基础。我们熟知的各类sensor,TP,PMU,camera,Audio codec,都是I2C接口,所以熟悉I2C的协议和驱动框架对于从事Linux底层驱动开发的人来说无疑是非常必要的。
1.2 传输时序
先上图:
3种类型信号:
(1)开始信号(S):SCL为高电平时,SDA由高电平向低电平跳变,开始数据传送;
(2)结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束数据传送;
(3)响应信号(ACK):接收器接受到8位数据以后,在第9个时钟周期,拉低SDA电平。
此外,还有一点可以get到的是:SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化。
1.3 传输格式
读写的格式如下(红色背景表示是从机的行为):
7位地址模式下的写操作格式: | ||||||
S | 从机的7位地址 | 0 | ACK | 8位数据 | ACK | P |
7位地址模式下的读操作格式: | ||||||
S | 从机的7位地址 | 1 | ACK | 8位数据 | ACK | P |
SDA线上的数据传输单位是字节,即8位的数据。首先传输的是最高位。启动一个传输时,主机先发出S信号,然后发出8位数据,高7位是从设备地址,第8位是读写位(0-W, 1-R)。一般来说,每次一个字节的数据传输完,主机或者从机必须发出ACK信号,才能进行下一个数据的传输。当然,也有例外:
(1) 当从机不能响应从机地址时(比如从机正在忙于处理其他事情而无法响应主机操作,或者是总线上并没有挂这个地址的从设备),也就是在第9个SCL周期内SDA线并没有被拉低,即没有ACK信号。这时主机发送一个P信号终止传输或者重新发出一个S信号开始新的传输。
(2) 如果从机在传输过程中不能接收更多的数据,它也不会发出ACK信号。这时主机必须终止传输,发送一个P信号或者重新发出一个S信号开始新的传输。
10位地址模式格式:
10位地址模式下的写操作格式: | ||||||||
S | 从机的第一个7位地址(11110xx) | 0 | ACK | 从机的第二个7位地址 | ACK | 8位数据 | ACK | P |
10位地址模式下的读操作格式: | ||||||||
S | 从机的第一个7位地址(11110xx) | 0 | ACK | 从机的第二个7位地址 | ACK | 8位数据 | ACK | P |
10位寻址不会影响已有的7位寻址,7位和10位地址的器件可以连接到相同的I2C总线 。10位从机地址由S信号后的头两个字节组成,采用了保留的“1111XXX”作为S信号后第一个字节的头7位。保留地址位1111XXX 有8个组合,但是只有4个组合11110XX 用于10位寻址,剩下的4个组合11111XX 保留给后续增强的I2C总线。第一个字节的头7位是11110XX 的组合,其中最后两位(XX) 是10位地址的两个最高位。
2. 硬件原理
2.1 连接模型:
主控和从设备之间的连接示意图一般如下:
2.2 I2C控制器介绍
以三星的S3C2440(ARM9)为例来介绍,其他ARM架构的SoC里的I2C控制器,原理大同小异。S3C2440的I2C控制器结构框图如下:
先介绍一下各个寄存器:
(1)IICCON寄存器
从下面的截图(截自韦东山老师所著《嵌入式Linux应用开发完全手册》一书)可以知道,IICCON寄存器用于控制是否发出ACK信号、设置IIC时钟、使能中断以及标识中断是否发生。是否发出ACK信号要根据实际情况而定,比如当主机处于接收模式时,在收到最后一个字节的数据前,需要设置bit[7]为0,即在收到最后一个字节的数据后不发出ACK信号,这样从机就能知道传输结束了。SCL线上的时钟频率由bit[6]和bit[3:0]共同决定。I2C中断在以下三种情况下发生:①当发出从机地址信息或自己作为从机接收到一个和自己相同的从机地址时;②当总线仲裁失败时;③当发送或接收完一个字节的数据时。第二种情况中,所谓总线仲裁,是指当多个主机试图去控制总线时,通过仲裁可以保证只有一个主机获取到总线控制权,并且它传输的信息不会被破坏。
(2)IICSTAT寄存器
bit[7:6]用于选择该控制器处于什么工作模式;
bit[5]控制发出S/P信号,什么时候发哪个信号,都是软件可控的;
bit[4]控制接收/发送功能;
bit[0]标识是否接收到ACK信号。
(3)IICADD寄存器
用到bit[7:1],保存从机地址。该寄存器在串行输出使能位IISTAT[4]=0时,才能写入,这么做的目的是为了在传输功能使能后避免因修改该寄存器值而导致地址混乱。
(4)IICDS寄存器
保存要发送或已经接收的8位数据。串行输出使能位IISTAT[4]=1时,才能写入。
下面以主机模式下,介绍启动或恢复I2C传输的方法:
(1)如果中断标志位IICCON[4]=0,那么让IICSTAT[5:4]=0b11,主机就会发出S信号及IICDS里的数据;
(2)如果中断标志位IICCON[4]=1,表示之前有数据传输,现在I2C传输被暂停。此时只要清掉中断标志位,即令IICCON[4]=0,就可以恢复I2C操作。在中断里(恢复I2C操作之前),我们需要将下一个发送的数据写入IICDS寄存器(发送模式),或者从IICDS里读取出接收到的数据(接收模式)。
2.3 I2C读写操作流程
下面通过简单的C代码来介绍主机模式的寄存器级别读写操作逻辑流程。首先是对I2C控制器进行初始化:
准备工作就绪,随时准备读或者写操作,读写的代码结合上面的寄存器说明来分析,整个I2C通信过程就会更清楚。
说明:这里清中断清的是中断控制器那边的寄存器,并不影响I2CCON的bit[4],此时该标记位仍然为1,所以I2C还是处于暂停传输状态。
请思考一下,读/写从设备的一个寄存器的流程应该是什么样的?
首先需要说明一下的是,上面的i2c_read和i2c_write并不知道发送的到底是地址还是数据,发送的字节是什么意思,只有我们知道。上面两个函数地址和数据的逻辑关系是我们定的,内核的I2C驱动亦是如此。
写从设备的一个寄存器:
(1)、i2c_init
(2)、i2c_write(slave_addr, buf[2], 2)
先发出从设备地址,待从设备响应发出ACK之后,触发中断,进入中断处理程序。首先把buf[0]写入I2CDS(此时g_s3c2440_i2c.len = 1),然后恢复传输。从设备收到数据后,知道这是寄存器地址,于是发出ACK信号,这时又触发中断,再次进入中断处理程序。程序继续把buf[1]写入I2CDS(此时g_s3c2440_i2c.len = 0),然后恢复传输。从设备又收到数据,知道这是要写入刚才发过来的寄存器地址里,于是再次发出ACK信号,再次触发中断进入中断处理程序。此时if ((g_s3c2440_i2c.len--) == 0)条件成立,直接恢复传输,发出P信号,同时i2c_write的循环条件也成立,跳出,i2c_write执行完毕。
读从设备的一个寄存器:
(1)、i2c_init
(2)、i2c_write(slave_addr, buf[1], 1)
先写操作,buf[0]是要读取的寄存器地址,发给从设备后,从设备在下一次读操作返回该寄存器的值。
(3)、i2c_read(slave_addr, buf[1], 1)
读操作开始,注意此时会重新发出S信号。i2c_read在发出从设备地址后,从设备发出ACK信号,触发中断进入中断处理程序。此时由于是发出的设备地址(任何时候S信号随后的第一个数据一定是设备地址),并不会接收到数据。如果只接收一个(或接收到的是最后)一个字节的数据,那么还要设置I2CCON的bit[7]为0,即接收到数据后主机不发出ACK信号,这样从设备就知道数据传输结束了。当从设备响应并发出上次写操作发出的寄存器地址里的值后,再次触发中断。在中断里取出I2CDS里接收到的数据,保存到buf[0],恢复I2C传输并发出P信号。i2c_read执行完毕。
请再思考一下,如果是一次读/写多个寄存器呢?写好说,读呢?是(2)(3)(2)(3)重复这样还是(2)(3)(一次读多个数据)这样呢?
I2C协议和驱动框架学习(二)
https://blog.****.net/weixin_43555423/article/details/90739753