IIC
《嵌入式Linux应用完全开发手册》第2篇第12章总结归纳
本章目标
- 了解IIC总线协议
- 掌握S3C2410/S3C2440中IIC的使用方法
IIC总线协议及硬件介绍
IIC总线协议
IIC总线的概念
IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的串行总线,用于连接微控制器及其外围设备。
- 只有两条总线线路:一条串行数据线(SDA),一条串行时钟线(SCL)。
- 每个连接到总线的器件都可以使用软件根据它的唯一地址来识别。
- 传输数据的设备间是简单的主从关系。
- 主机可以用作主机发送器或主机接收器。
- 它是一个真正的多主机总线,两个或多个主机同时发起数据传输时,可以通过冲突检测和仲裁来防止数据被破坏。
- 串行的8位双向数据传输,位速率在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,在高速模式下可达3.4Mbit/s。
- 片上的滤波器可以增加抗干扰功能,保证数据的完整。
- 连接到同一总线上的IC数量只受到总线最大电容400pF的限制。
下图是一条IIC总线多个设备相连的例子:
一些术语如下表:
术语 | 描述 |
---|---|
发送器 | 发送数据到总线的器件 |
接收器 | 从总线接收数据的器件 |
主机 | 发起/停止数据传输、提供时钟信号的器件 |
从机 | 被主机寻址的器件 |
多主机 | 可以有多个主机试图去控制总线,但是不会破坏数据 |
仲裁 | 当多个主机试图去控制总线时,通过仲裁可以使得只有一个主机获得总线控制权,并且它传输的信息不会被破坏 |
同步 | 多个器件同步时钟信号的过程 |
IIC总线的信号类型
IIC总线在传送数据的过程中共有3种类型信号:开始信号、结束信号和响应信号。
- 开始信号(S):SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
- 结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
- 响应信号(ACk):接收器在接收到8位数据之后,在第9个时钟周期,拉低SDA电平。
它们的波形如下图:
SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化,如下图所示:
IIC总线的数据传输格式
发送到SDA线上的每个字节必须是8位的,每次传输可以发送的字节数量不受限制。每个字节后必须跟一个响应位。首先传输的是数据的最高位。如果从机要完成一些其他功能后(例如一个内部中断程序)才能继续接收或发送下一个字节,从机可以拉低SCL迫使主机进入等待状态。当从机准备好接收下一个数据并释放SCL后,数据继续传输。如果主机在传输数据期间也需要完成一些其他功能(例如一个内部中断程序)也可以拉低SCL以占住总线。
启动一个传输时,主机发出S信号,然后发出8位数据。这8位数据中前7位为从机的地址,第8位表示传输的方向(0表示写操作,1表示读操作)。被选中的从机发出响应信号。紧接着传输一系列字节及其响应位。最后,主机发出P信号结束本次传输。
下图是IIC数据传输的3种类型读、写、读写转换:
并非每传输8位之后就会由ACK信号,以下3种例外:
- 当主机不能响应从机地址时(例如它正忙着其他事情而无法响应IIC总线的操作,或者这个地址没有对应的主机),在第9个SCL周期内SDA线没有被拉低,即没有ACK信号。这时,主机发出一个P信号终止传输或者重新发出一个S信号开始新的传输。
- 如果从机接收器在传输过程中不能接收更多数据时,它也不会发出ACK信号。这样,主机就可以意识到这点,从而发出一个P信号终止传输或者重新发出一个S信号开始新的传输。
- 主机接收器在接收到最后一个字节后,也不会发出ACK信号,于是,从机发送器释放SDA线,以允许主机发出P信号结束传输。
S3C2410/S3C2440 IIC 总线控制器
S3C2410/S3C2440 IIC 总线控制器介绍
S3C2410/S3C2440 IIC 接口有4种工作模式:主机发送器、主机接收器、从机发送器、从机接收器。其内部结构如下图所示:
S3C2410/S3C2440提供4个寄存器来完成所有的IIC操作,SDA线上的数据从IICDS寄存器发出,或传入IICDS寄存器中;IICADD寄存器中保存S3C2410/S3C2440当作从机的地址:IICCON、IICSTAT两个寄存器用来控制或标识各种各种状态,比如选择工作模式,发出S信号,P信号,决定是否发出ACK信号,检测是否收到ACK信号。寄存器的用法如下:
- IICCON寄存器(Multi-master IIC-bus control)
IICCON寄存器用于控制是否发出ACK信号、设置发送器的时钟、开启IIC中断,并标识中断是否发生。功能 位 说明 ACK信号使能 [7] 0 = 禁止 1 = 使能
在发送模式,此位无意义
在接收模式,此位使能时,SDA线在响应周期内将被拉低,即发出ACK信号发送模式时钟源选择 [6] 0 = IICCLK 为PCLK/6,1 = IICCLK为PCLK/512 发送/接收中断使能 [5] 0 = IIC总线 Tx/Rx中断使能
1 = IIC总线 Tx/Rx中断使能中断标记 [4] 此位用来标识是否有IIC中断发生,读出为0时表示没有中断发生,读出为1时表示有中断发生。当此位为1时,SCL线被拉低,此时所有IIC传输停止:如果需要继续传输,需写入0清除它 发生模式时钟分频系数 [3:0] 发送器时钟=IICCLK/(IICCON[3:0] + 1)
使用IICCON寄存器时,有如下注意事项:
①发送模式的时钟频率由位[6]、位[3:0]联合决定。另外,当IICCON[6]=0时,IICCON[3:0]不能取0或1。
②IIC中断在以下3种情况下发生:当发出地址信息或者接收到一个从机地址并且吻合时,当总线仲裁失败时,当发送/接收完一个字节的数据(包括ACK)时。
③基于SDA、SCL线上时间特性的考虑,要发送数据时,先将数据写入IICDS寄存器,然后再清除中断。
④如果IICCON[5] = 0,IICCON[4]将不能正常工作。所以,即使不使用IIC中断,也要将IICCON[5]设为1。
IICSTAT(Multi-master IIC-bus control/status)
IICSTAT寄存器用于选择IIC接口的工作模式,发出S信号、P信号,使能接收/发送功能,并标识各种状态,比如总线仲裁是否成功、作为从机是否被寻址、是否接受到0地址、是否接收到ACK信号等。
IICSTAT寄存器的各位表示如下表:功能 位 说明 工作模式 [7:6] 0b00:从机接收器
0b01:从机发送器
0b10:从机接收器
0b11:从机发送器忙状态位/S信号、P信号 [5] 读此位时0:总线空闲,1:总线忙
写此位时0:发出P信号,1:发出S信号。当发出S信号后,IICDS寄存器中的数据将被自动发送。串行输出使能位 [4] 0:禁止接收/发送功能,1:使能接收/发送功能 仲裁状态 [3] 0:总线仲裁成功 1:总线仲裁失败 从机地址状态 [2] 作为从机时,在检测到S/P信号时此位被自动清0
接收到的地址与IICADD寄存器中的值相等时,此位被置10地址状态 [1] 在检测到S/P信号时此位被自动清0
接收到的地址为0b0000000时,此位被置1最后一位的状态 [0] 0:接收到的最后一位为0(接收到ACK信号)
1:接收到的最后一位为1 (没有接受到ACK信号)IICADD寄存器(Multi-master IIC-bus address)
用到IICADD寄存器的位[7:1],表示从机地址。IICADD寄存器在串行输出使能位IICSTAT[4]为0时,才可以写入;在任何时间都可以读出。IICDS寄存器(Multi-master IIC-bus Tx/Rx data shift)
用到IICDS寄存器的位[7:0],其中保存的是要发送或已经接受的数据。IICDS寄存器在串行输出使能位IICSTAT[4]为1时,才可以写入;在任何时候都可以读出。
S3C2410/S3C2440 IIC 总线操作方法
启动或恢复S3C2410/S3C2440 的IIC传输有以下两种方法:
- 当IICCON[4]即中断状态位为0时,通过写IICSTAT寄存器启动IIC操作,有以下两种情况:
①在主机模式,令IICSTAT[5:4]等于0b11,将发出S信号和IICDS寄存器的数据(寻址),令IICSTAT[5:4]等于0b01,将发出P信号。
②在从机模式,令IICSTAT[4]等于1将等待其他主机发出S信号及地址信息。 - 当IICCON[4]即中断位为1时,表示IIC操作被暂停。在这期间设置好其他寄存器之后,向IICCON[4]中写入0即可恢复IIC操作。所谓“设置其他寄存器”,有以下3种情况。
①对于主机模式,可以按照上面①的方法写IICSTAT寄存器,恢复IIC操作后即可发出S信号和IICDS寄存器的值(寻址),或者发出P信号。
②对于发送器,可以将下一个要发送的数据写入IICDS寄存器中,恢复IIC操作后即可发出这个数据。
③对于接收器,可以从IICDS寄存器中读出接收到的数据。最后向IICCON[4]写入0的同时,设置IICCON[7]以决定在接收到下一个数据后是否发出ACK信号。
通过中断服务程序来驱动IIC传输。
- 当仲裁失败时发生中断—本次传输没有抢到总线,可以稍后继续。
- 对于主机模式,当发出S信号、地址信息并经过一个SCL周期(对于ACK信号)后,发生中断—主机可在此时判断是否成功寻址到从机。
- 对于从机模式,当接收到的地址与IICADD寄存器吻合时,先发出ACK信号,然后发生中断—从机可在此时准备后续的传输。
- 对于发送器,当发送完一个数据并经过一个SCL周期(对于ACK信号)后,发生中断。这是可以准备下一个要发送的数据,或者发送P信号以停止传输。
- 对于接收器,当接收到一个数据时,先根据IICCON[7]决定是否发出ACK信号后,然后发生中断。这时可以读取IICDS寄存器得到数据,并设置IICCON[7]以决定接收到下一个数据后是否发出ACK信号。
对于4种工作模式,S3C2410/S3C2440数据手册中都有它们的操作流程图。现在以主机发送器作为例子说明,它的工作流程如图所示:
- 配置主机发送器的各类参数。
设置GPE15、GPE14引脚用于SDA、SCL,设置IICCON寄存器选择IIC发送时钟,最后设置IICSTAT[4]为1。这样,后面才能写IICDS寄存器。1
Tips:初始时IICCON[4]为0,不能将IICSTAT设为主机模式,否则就会立刻发出S信号,发送IICDS寄存器的值。
- 将要寻址的从机地址写入IICDS寄存器。
- 将0xF0写入IICSTAT寄存器,即设为主机发送器、使能串行输出功能、发出S信号。
- 发出S信号后,步骤2中设置的IICDS寄存器值也将被发出,它用来寻址从机。
- 在响应周期之后,发生中断,此时IICCON[4]为1,IIC传输暂停。
- 如果没有数据要发送,则跳到步骤10,否则跳到步骤7。
- 将下一个要发送的数据写入IICDS寄存器中。
- 往IICCON[4]中写入0,恢复IIC传输。
- 这时IICDS寄存器中的值将被一位一位地发送出去。当8位数据发送完毕,在经过另一个SCL周期(对应ACK信号)后,中断再次发生,跳到步骤5。
- 将0xF0写入IICSTAT寄存器,即:设为主机发送器、使能串行输出功能、发出P信号。
1
Tips:这时的P信号并没有实际发出,只有清除了IICCON[4]后才会发出P信号。
- 清除IICCON[4],P信号得以发出。
- 等待一段时间,使得P信号完全发出。
IIC总线操作实例
IIC接口RTC芯片M41t11的操作方法
本书所用的开发板中,通过IIC总线连接RTC(实时时钟)芯片的M41t11,它使用电池供电,系统断电时也可以维持日期和时间。S3C2410/S3C2440作为IIC主机向M41t11发送数据以设置日期和时间、读取M41t11以获得日期和时间。连接图如下图所示:
M41t11中有8个寄存器,分别对应秒、分、时、天、日、月、年、控制寄存器,其中的数据都是以BCD格式保存(0x15表示数值15),如下表所示:
除上表的8个寄存器(地址为0-7)之外,M41t11内部还有56字节的RAM(地址为8-63)。访问M41t11前,先设置寄存器地址,以后每次读写操作完成后,M41t11内部会自动将寄存器地址加1。
所以读写M41t11分为以下两个步骤:
- 主机向M41t11发出要操作的寄存器地址(0-7)。
- 要设置M41t11时,主机连续发出数据;要读取M41t11时,主机连续读取数据。M41t11的IIC从机地址为0xD0。
程序设计
本实例将在串口上输出一个菜单,可以选择设置时间和日期,或者将它们读出来。将通过本实例验证IIC主机的发送、接收操作。
设置/读取M41t11的源码详解
文件i2c.c封装了S3C2410/S3C2440作为主机发送器、主机接收器的4个操作函数:i2c_init用于初始化,i2c_write用于发起发送数据,i2c_read用于发起读取数据,I2CHandle是IIC中断服务程序,它用于完成后续的数据传输。
S3C2410/S3C2440 IIC控制器初始化
i2c_init函数对应于图12.7的步骤1,它用来初始化IIC,代码如下:
1 | /* |
第6、7行代码将GPE15、GPE14的功能选择用于IIC:IICSDA、IICSCL。
第9行在INTMSK寄存器中开启IIC中断,这样,以后调用i2c_read、i2c_write启动传输时,即可以触发中断,进而可以在中断服务程序中进一步完成后续传输。
第19行用于选择发送时钟,并进行一些设置:使能ACK、使能中断。
第21行用于设置S3C2410/S3C2440作为IIC从机时的地址。
第22行使能IIC串行输出(设置IICSTAT[4]为1),这样,在i2c_write、i2c_read函数中就可以写IICDS寄存器了。
S3C2410/S3C2440 IIC 主机发送函数
初始化完成后,就可以调用i2c_read、i2c_write函数读写IIC从机了。它们的使用方法从参数名称中可以看出。这两个函数仅仅是启动IIC传输,然后等待,直到数据在中断服务程序中传输完毕后再返回。
1 | /* |
第7-10行用于设置全局变量g_tS3C24xx_IIC,它表明当前是写操作,并保存缓冲区地址、要传送数据的长度,将缓冲区索引值初始化为0。
第12行将从机地址写入IICDS寄存器,这样,再第13行启动传输并发出S信号后,紧接着就自动发出从机地址。
第13行设置IICSTAT寄存器,将S3C2410/S3C2440设为主机发送器,并发出S信号。后续的传输工作将在中断服务程序中完成。
第16行等待g_tS3C24xx_IIC.DataCount在中断服务程序中被设为-1,这表明传输完成,于是返回。
S3C2410/S3C2440 IIC 主机接收函数
1 | /* |
需要注意的是第8行将索引值设为-1,在中断处理函数中根据这个值决定是否从IICDS寄存器中读取数据。读操作时,第一次中断发生时表示发出了地址,这时候还不能读取数据。
S3C2410/S3C2440 IIC 中断服务程序
IIC操作的主体在中断服务程序,它分为3部分:首先是在SRCPND、INTPND中清除中断,后面两部分分别对应于写操作、读操作。先看清除中断的代码:
1 | /* |
第10、11行用来清除IIC中断的代码。需要注意的是,即使清除中断之后,IICCON寄存器中的位[4](中断标志位)仍为1,这导致IIC传输暂停。
第13行读取状态寄存器IICSTAT,发生中断时有可能因为仲裁失败,在第15行对它进行处理。
接下来一个swicth语句,分别处理读、写操作。
1 | switch(g_tS3C24xx_IIC.Mode) |
g_tS3C24xx_IIC.DataCount表示剩余等待传输的数据个数,第4行判断数据是否已经全部发送完毕:若是,则通过第7、8行发出P信号,停止传输。
第7行设置IICSTAT寄存器以便发出P信号,但是由于这时IICCON[4]仍为1,P信号还没有实际发出。当8行清除IICCON[4]后,P信号才真正发出。第9行等待一段时间,确保P信号已经发送完毕。
如果数据还没有发送完毕,第12行从缓冲区得到下一个要发送的数据,将它写入IICDS寄存器中。稍加等待之后,即可在第17行清除IICCON[4]以恢复IIC传输,这时,IICDS寄存器中的数据就会发送出去,这将触发下一个中断。
IIC的读操作代码如下:
1 | switch(g_tS3C24xx_IIC.Mode) |
读操作比写操作多了一个步骤:第一次中断发生时表示发出了地址,这时候还不能读取数据,在代码中要分辨这点。对应第5-15行:如果g_tS3C24xx_IIC.P等于-1,表示这是第一次中断,然后修改g_tS3C24xx_IIC.Pt为0,并设置IICCON寄存器恢复IIC传输(第10-14行)。
当数据传输开始后,每接收到一个数据就会触发一次中断。后面的代码读取数据,判断所有的数据是否已经完成:如果完成就发出P信号,否则就继续下一次传输。
第17行判断数据是否已经全部接收完毕:若是,先通过第19行将当前数据从IICDS寄存器中取出存入缓冲区,然后通过22、23行发出P信号停止传输。
第22行设置IICSTAT寄存器以便发出P信号,但是由于这时IICCON[4]仍为1,P信号没有实际发出。第23行清除IICCON[4]后,P信号才真正发出。第24行等待一段时间,确保P信号已经发送完毕。
第28-25用来启动下一个数据的接收。
第28行将当前数据从IICDS寄存器中取出存入缓存区中。
第31-35行判断是否只剩下最后一个数据了:若是,就通过第32行中清除IICCON[4]、IICCON[7],这样即可恢复IIC传输,并使得接收到数据后,S3C2410/S3C2440不发出ACK信号(这样从机即可知道数据传输完毕);否则,在第34行中只要清除IICCON[4]以恢复IIC传输。
中断服务程序中,当数据传输完毕时,g_tS3C24xx_IIC.DataCount将自减为-1,这样,i2c_read或i2c_write函数即可跳出等待,直接返回。
RTC芯片M41t11特性相关的操作
M41t11.c文件中提供两个函数M41t11_set_datetime、M41t11_get_datetime,前者用来设置日期和时间,后者用来读取日期与时间。它们都通过调用i2c_read或i2c_write函数来完成与M41t11的交互。
前面说过,操作M41t11只需要两个步骤:发出寄存器地址,发出数据或读出数据。M41t11_set_datetime函数把这两个步骤合为一个IIC写操作。M41t11_get_datetime函数先发起一个IIC写传输,再发起一个IIC读传输。
1 | /* |
省略号表示的代码用来设置addr_and_regs结构。这个结构分为两部分:addr_and_regs.addr表示M41t11寄存器地址(它被设为0),addr_and_regs.rtc表示M41t11的8个寄存器。
根据传入的参数填充好addr_and_regs结构之后,就可以启动IIC写操作了。“attribute((packed))”设置这个结构体为紧凑格式,使得它的大小为9Byte(否则按照内存对齐的规则为12Byte):1个字节用来保存寄存器地址,8个字节用来保存8个寄存器的值。
第12行发起一次IIC写操作,将addr_and_regs结构中的数据发送给M41t11:M41t11会把接收到的第一个数据当作寄存器的起始地址,随后是要写入寄存器的数据。
M41t11_get_datetime函数的代码与M41t11_set_datetime函数类似,如下所示:
1 | /* |
第12行发起一次IIC写传输,设置要操作的M41t11寄存器地址为0。
第13行发起一次IIC读传输,读出M41t11各寄存器的值。
省略号对应的代码根据读出的各寄存器的值,设置dr结构。M41t11下中以BCD码表示日期与时间,需要转换为程序使用的一般二进制格式。
IIC实例的连接脚本
本实例要用到第8章NAND Flash控制器的函数将代码从NAND Flash复制到SDRAM中。由于nand代码中用到了全局变量,而全局变量要运行于可读写的内存中,为了方便,使用连接脚本将这些初始化代码放在Steppingstone中。
连接脚本为i2c.lds,内容如下:
1 | SECTIONS{ |
第2-3行将head.S、init.c和nand.c对应的代码的运行地址设为0,加载地址(存在NAND Flash上的地址)设为0。从NAND Flash启动时,这些代码被复制到Steppingstone后就可以直接运行。
第4行设置其余代码的运行地址为0x3000000;第5行将代码段的加载地址设为4096,表示代码段将存在NAND Flash地址4096处。
第6-7行的“AT(…)”设置rodata段,data段的加载地址依次位于代码段之后。“LOADADDR(…)”表示某段的加载地址,“SIZEOF(…)”表示它的大小。这两行的前面使用“ALIGN(4)”使得它们的运行地址为4字节对齐,为了使各段之间加载地址的相对偏移值等于运行地址的相对偏移值,需要将“AT(…)”中的值也设为4字节对齐:先加上3,然后与~(0x03)进行与操作(将低2位设为0)。