一、IIC模块介绍
目前市场上很多单片机都已经具有硬件IIC总线控制单元,这类单片机在工作时,IIC总线状态由硬件监测,无需用户介入,操作方便。
IIC总线是双线、双向的串行总线,是与其它芯片交换数据的有效手段。XEP100单片机的IIC模块的功能框图如下图所示。
IIC总线由一个双向的时钟线SCL和一个双向的数据线SDA组成。该实验通过IIC总线对片外EEPROM进行通信。可以向EEPROM写入和读取数据。单片机通信的对象是AT24C02,它是一个IIC接口的存储器,电路图如下图所示。AT24C02的存储空间的大小为2kbit。即为250字节。可以用于存储一些重要数据。在AT24C02中存储的数据,当系统掉电之后,依然可以存在。而不像程序中的变量,在掉电之后,数值就没了。
IIC总线系统为数据传输使用串行数据线(SDA)和串行时钟线(SCL)。与其连接的所有器件必须具有开漏或开极输出。逻辑与功能通过外部上拉电阻在两条线上执行。这些电阻的值与系统相关。
一般地,标准通信由以下四部分组成:
· 启动信号
· 从机地址发送
· 数据传输
· 停止信号
停止信号不应与CPU停止指令相混淆。相应的信号见下图:
(1)启动信号
当总线空闲时,没有主机占用总线(SCL和SDA线处于逻辑高电平),主机可以通过发送启动信号发起通信。启动信号定义为,当SCL在高电平时,SDA从高到低的跳变。该信号表示开始新的数据传输(每次数据传输可能包含几个字节的数据),并使所有从机退出空闲状态。
(2)从机地址发送
启动信号发出后传输的第一个字节数据是主机发送的从机地址。这是R/W位之前的7位主叫地址。R/W位告知从机数据传输的方向。
1 = 读取传输,从机向主机传输数据
0 = 写入传输,主机向从机传输数据
当主机发送的地址与某从机地址匹配时,此从机发回应答位进行响应。通过将SDA第9个时钟拉低实现。系统中不能有两个地址相同的从机。如果IIC模块是主机,它就不能发送与其自己的从机地址相同的地址。IIC不能同时既是主机又是从机。但是,如果仲裁在寻址周期中丢失,IIC就重新返回到从机模式并正确运行,即便它正被另一个主机寻址。
(3)数据传输
成功实现从机寻址之前,就可以按照主叫主机发送的R/W位指定的方向逐字节地进行数据传输。地址周期后的所有传输都被称为数据传输,即使它们包含从机的子地址报文。每个数据字节的长度均为8位。只有当SCL处于低时数据才可以更改,如果SCL处于高位,那么它必须保持稳定。SCL的一个时钟脉冲传输一个数据位,最高位被首先传输。每个数据字节后面都有一个第9(确认)位,该位由从机发出信号,通过把SDA拉低到第9个时钟实现。总之,一个完整数据传输需要9个时钟脉冲。如果从机在第9个位时间时未应答主机,从机必须保留SDA线在高电平。主机将未接收应答信号解释为不成功的数据传输。如果主机接收器在一个数据字节传输后未应答从机发送器,从机把这种情况理解为数据传输结束,并释放SDA 线。对于这两种情况,数据传输都被中止,主机会进行以下两种操作之一:
• 发送停止信号,放弃总线
• 发送重复启动信号,开始新呼叫
(4)停止信号
主机可以通过发送停止信号终止通信,以释放总线。然而,主机可以直接发送启动信号和呼叫命令,而无需首先发送停止信号。这被称为重复启动。停止信号的定义是SCL在逻辑1 位置时的从低到高的SDA 跳变。即便从机发出一个应答,主机也可以发送停止信号,此时从机必须释放总线。
(5)重复启动信号
如上图所示,重复启动信号是在无需首先生成停止信号以终止通信的情况下发送的信号。该信号由主机用来与另外一个从机进行通信,或者在不同模式(传输/接收模式)中与同一从机进行通信,而不需要释放总线。
(6)仲裁程序
IIC总线是真正的多主控总线,允许一个以上的主机连接到IIC上。如果两个甚至多个主机试图同时控制总线,那么就由时钟同步过程来决定总线时钟,总线低周期等于最长的时钟低段,总线高周期等于最短的时钟高段。竞争主机的相对优先级由数据仲裁过程确定,如果一个总线主机发出逻辑1,而另一个发出逻辑0,那么前者就丢失仲裁。失败的一方立即切换到从机接收模式,停止驱动SDA输出。在这种情况下,从主模式到从模式的转换不会生成停止条件。与此同时,会通过硬件设置一个状态位,表示仲裁丢失。
(7)时钟同步
因为线与逻辑在SCL线上执行,所以SCL上的从高到低的跳变会影响连接到总线上的所有器件。节点开始计数它们的低态周期,当节点的时钟进入低态后,它将一直保持SCL线的低态,直到达到时钟高态。然而,如果另一个节点时钟仍处于低态时段,这时此节点时钟从低到高的变化就不会改变SCL线的状态。因此,经过同步的时钟SCL由具有最长低态周期的节点保持。低态周期较短的节点在该时段进入高态等待。当所有有关节点均已完成它们的低态周期时,同步时钟SCL线被释放和拉高。这样,节点时钟和SCL 线的状态之间就没有任何差异,所有节点开始计数它们的高态周期。第一个完成其高态周期的节点再次拉低SCL线。
单片机向AT24C02写入一个字节的数据的操作如下图所示。
单片机首先第一个字节向从器件发送访问的从器件的地址,和读写方式。这里为写入模式,则单片机接下来发送的字节为需要访问的存储器的地址,即数据需要写入的地址。第三个字节是需要向存储器写入的数据。
单片机由AT24C02读取一个字节的数据的操作如下图所示。
单片机首先第一个字节向从器件发送访问的从器件的地址,和读写方式。这里为写入数据模式,单片机接下来发送的字节为需要访问的存储器的地址,即单片机需要读取的地址。之后单片机再次产生一个开始信号,第三个字节单片机再次写入从器件地址,这次访问模式为读模式,第四个字节是存储器向单片机发送的数据。
二、例程测试
在这个实验中,我们向AT24C02芯片中写入一个字节的数据,并读取,验证。代码主要部分是IIC模块的初始化,IIC发送和IIC读取的函数,初始化函数如下所示。
void INIT_IIC(void)
{
IIC0_IBFD = 0x94; //总线时钟32MHz,设置SCL主频为100KHz
IIC0_IBCR = 0x80; //使能IIC模块,禁止中断
IIC0_IBSR_IBAL = 1; //清除IBAL标志位
}
这段代码主要是设置了IIC通信的频率,以及禁止了中断。
单片机通过IIC向AT24C02写入数据的函数如下所示。
void IIC_send(unsigned char addr,unsigned char data)
{
IIC0_IBCR_TXAK = 0; //接收到数据后有应答
IIC0_IBCR_TX_RX= 1; //设置单片机为发送模式
IIC0_IBCR_MS_SL = 1; //设置单片机为主机模式,产生开始信号
IIC0_IBSR_IBIF = 1; //清除标志位
IIC0_IBDR = 0b10100000; //发送数据
while(IIC0_IBSR_IBIF == 0) //等待发送完毕
{
}
IIC0_IBSR_IBIF = 1;
while(IIC0_IBSR_RXAK) //等待应答
{
}
IIC0_IBDR = addr;
while(IIC0_IBSR_IBIF == 0)
{
}
IIC0_IBSR_IBIF = 1;
while(IIC0_IBSR_RXAK)
{
}
IIC0_IBDR = data;
while(IIC0_IBSR_IBIF == 0)
{
}
IIC0_IBSR_IBIF = 1;
while(IIC0_IBSR_RXAK)
{
}
IIC0_IBSR_IBIF = 1;
IIC0_IBCR_MS_SL = 0; //设置单片机为从机模式,产生停止信号
}
在这个函数中,addr为需要写入的AT24C02内部的存储器地址,data为需要写入的数据。对照着上一节的AT24C02的通讯协议,可以看到通讯过程分为3个阶段,分别是发送从机地址、发送存储器地址、发送待写入数据。每一步都需要等待从机的应答。
单片机通过IIC从AT24C02读取数据的函数如下所示。
unsigned char IIC_receive(unsigned char addr)
{
unsigned char data;
IIC0_IBCR_TXAK = 0; //接收到数据后有应答
IIC0_IBCR_TX_RX = 1; //设置单片机为发送模式
IIC0_IBCR_MS_SL = 1; //设置单片机为主机模式,产生开始信号
IIC0_IBDR = 0b10100000;
while(IIC0_IBSR_IBIF == 0)
{
}
IIC0_IBSR_IBIF = 1;
while(IIC0_IBSR_RXAK)
{
}
IIC0_IBDR = addr;
while(IIC0_IBSR_IBIF == 0)
{
}
IIC0_IBSR_IBIF = 1;
while(IIC0_IBSR_RXAK)
{
}
IIC0_IBCR_RSTA = 1; //产生重复开始信号
IIC0_IBDR = 0b10100001;
while(IIC0_IBSR_IBIF == 0)
{
}
IIC0_IBSR_IBIF = 1;
while(IIC0_IBSR_RXAK)
{
}
IIC0_IBCR_TX_RX = 0; //设置单片机为发送模式
IIC0_IBCR_TXAK = 1; //接收到数据后无应答
data = IIC0_IBDR; //清空IICD寄存器,准备接收
while(IIC0_IBSR_IBIF == 0)
{
}
IIC0_IBSR_IBIF = 1;
IIC0_IBCR_MS_SL = 0;
data = IIC0_IBDR; //读取接收到的数据
return(data);
}
大家也可以对照着上面的AT24C02的通讯协议对代码进行分析,其中,addr是读取的地址,函数的返回值为读取到的数据。
这个程序的主函数,如下所示。
void main(void) {
DisableInterrupts;
INIT_PLL();
INIT_IIC();
LEDCPU_dir=1;
LEDCPU=0;
EnableInterrupts;
LEDCPU = 1; //熄灭指示灯
delay();
IIC_send(0x00,'A');
delay();
receivedata = IIC_receive(0x00);
if(receivedata == 'A');
{
LEDCPU = 0; //点亮指示灯
}
for(;;)
{
}
}
在主函数中,单片机向AT24C02的0地址写入一个字节‘A’,然后读取0地址,判断读出来的数据是否为‘A’,如果正确,则点亮LED灯。
将程序下载到单片机中,观看现象,与描述一致。