IIC(Inter-Integrated Circuit,I2C)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微处理器及其外围设备,它的最主要优点是简单和有效。它只需要数据线SDA和时钟线SCL,就能够实现CPU与被控IC之间、IC与IC之间进行双向传送。
s3c2440内部有一个IIC总线接口,因此为我们连接带有IIC通信模块的外围设备提供了便利。它具有四种操作模式:主设备发送模式、主设备接收模式、从设备发送模式和从设备接收模式。在这里我们只把s3c2440当做IIC总线的主设备来使用,因此只介绍前两种操作模式。在主设备发送模式下,它的工作流程为:首先配置IIC模式,然后把从设备地址写入接收发送数据移位寄存器IICDS中,再把0xF0写入控制状态寄存器IICSTAT中,这时等待从设备发送应答信号,如果想要继续发送数据,那么在接收到应答信号后,再把待发送的数据写入寄存器IICDS中,清除中断标志后,再次等待应答信号;如果不想再发送数据了,那么把0x90写入寄存器IICSTAT中,清除中断标志并等待停止条件后,即完成了一次主设备的发送。在主设备接收模式下,它的工作流程为:首先配置IIC模式,然后把从设备地址写入接收发送数据移位寄存器IICDS中,再把0xB0写入控制状态寄存器IICSTAT中,这时等待从设备发送应答信号,如果想要接收数据,那么在应答信号后,读取寄存器IICDS,清除中断标志;如果不想接收数据了,那么就向寄存器IICSTAT写入0x90,清除中断标志并等待停止条件后,即完成了一次主设备的接收。在完成上述两个模式时,主要用到了控制寄存器IICCON、控制状态寄存器IICSTAT和发送接收数据移位寄存器IICDS。由于我们只把s3c2440当做主设备来用,并且系统的IIC总线上只有这么一个主设备,因此用来设置从设备地址的地址寄存器IICADD,和用于仲裁总线的多主设备线路控制寄存器IICLC都无需配置。寄存器IICCON的第6位和低4位用于设置IIC的时钟频率,因为IIC的时钟线SCL都是由主设备提供的。s3c2440的IIC时钟源为PCLK,当系统的PCLK为50MHz,而从设备最高需要100kHz时,可以将IICCON的第6位置1,IICCON的低4位全为0即可。寄存器IICCON的第7位用于设置是否发出应答信号,第5位用于是否使能发送和接收中断,第4位用于中断的标志,当接收或发送数据后一定要对该位进行清零,以清除中断标志。寄存器IICSTAT的高2位用于设置是哪种操作模式,当向第5位写0或写1时,则表示结束IIC或开始IIC通讯,第4位用于是否使能接收/发送数据。
由于通讯是双方的事情,在了解了主设备的操作模式后,还要清楚从设备的运行机制,两者要达到完美地结合,才能实现彼此的通讯。在这里,从设备是EEPROM——AT24C02A,要想让s3c2440能够正确地对AT24C02A读写,就必须让s3c2440的时序完全按照AT24C02A的时序。AT24C02A的写操作有两种模式:字节写和页写。字节写是先接收带有写命令的设备地址信息,如果符合就应答,再接收设备内存地址信息,发出应答后,再接收要写入的数据,这样就完成了字节写过程。页写与字节写的区别就是,页写可以一次写多个数据,而字节写只能一次写一个数据。但由于AT24C02A的一页才8个字节,所以页写也最多写8个数据,而且只能在该页内写,不会发生一次页写同时写两页的情况。AT24C02A的读操作有三种模式:当前地址读、随机读和序列读。当前地址读是只能读取当前地址内的数据,它的时序是先接收带有读命令的设备地址信息,如果符合就应答,然后发送当前地址内的数据,在没有接收从主设备发来的应答信号的情况下终止该次操作。随机读的时序是,连续接收带有写命令的设备地址信息和设备内存地址信息,然后主设备重新开启IIC通信,AT24C02A再次接收到带有读命令的设备地址信息,在发出应答信号以后,发送该内存地址的数据,在没有接收到任何应答信号的情况下结束该次通信。当前地址读和随机读一次都只能读取一个数据,而序列读一次可以读取若干个数据,它的时序就是在当前地址读或随机读发出数据后,接收到了应答信号,那么AT24C02A会把下一个内存地址中的数据送出,除非AT24C02A接收不到任何应答信号,否则它会一直把下一个内存地址中的数据送出。序列读没有一页8个字节的限制。
在介绍完了s3c2440和AT24C02A的IIC通讯方式后,我们就可以写程序了。在这里,我们用UART来实现PC机对AT24C02A的读写。UART的通讯协议是,PC机先发送命令字节:0xC0表示要向AT24C02A写数据,0xC1表示要读取AT24C02A的数据,在命令字节后,紧跟着的是设备内存地址和写入或读取的字节数。如果是要写EEPROM数据,则在这三个字节后是要写入的数据内容。在UART通讯完毕后,s3c2440会根据命令的不同,写入或读取AT24C02A,如果是读取EEPROM,则s3c2440还会利用UART把读取到的数据上传到PC机。在这个程序中,我们只开启了UART的接收中断,而没有开启发送中断,即让s3c2440主动去完成发送任务。并且在与AT24C02A操作中,我们使用的是页写和序列读的模式,这样可以最大程度的完成一次读或写操作,而且我们所编写的页写和序列读子程度也同样可以实现字节写和随机读的模式。在这里我们限制一次读或写的数据量最多为8个字节。
unsigned char iic_buffer[8]; //IIC数据通讯缓存数组
unsigned char address,length; //EEPROM内存地址和数据通信的长度
unsigned char flag; //应答标志
unsigned char comm; //命令
unsigned char devAddr=0xa0; //从设备AT24C02A的地址
…… ……
//IIC通信中断
void __irq IicISR(void)
{
rSRCPND |= 0x1<<27;
rINTPND |= 0x1<<27;
flag = 0; //清标志
}
//UART通信中断,只开启了接收中断,因此无需判断是接收还是发送
void __irq uartISR(void)
{
char ch;
static char command;
static char count;
rSUBSRCPND |= 0x1;
rSRCPND |= 0x1<<28;
rINTPND |= 0x1<<28;
ch = rURXH0; //接收字节数据
if(command==0) //判断命令
{
switch(ch)
{
case 0xc0: //写EEPROM
command = 0xc0;
count=0;
comm = 0;
break;
case 0xc1: //读EEPROM
command = 0xc1;
count=0;
comm = 0;
break;
default:
command = 0;
count =0;
rUTXH0=ch;
break;
}
}
else
{
if(command == 0xc0) //写命令
{
count++;
if (count == 1)
{
address = ch; //接收设备内存地址信息
}
else if(count == 2)
{
length = ch; //接收写入数据个数信息
}
else //接收具体要写入EEPROM的数据
{
iic_buffer[count-3] = ch;
if(count==length+2) //接收完本次所有数据
{
rUTXH0=0xc0;
count=0;
command=0;
comm=1; //标志写命令,用于主程序
}
}
}
else if(command ==0xc1) //读命令
{
count++;
if(count==1)
{
address = ch; //接收设备内存地址信息
}
else
{
length = ch; //接收读取数据个数信息
rUTXH0=0xc1;
count=0;
command=0;
comm = 2; //标志读命令,用于主程序
}
}
}
}
//AT24C02A页写,当sizeofdate为1时,是字节写
//输入参数依次为设备内存地址、IIC数据缓存数组和要写入的数据个数
void wr24c02a(unsigned char wordAddr,unsigned char *buffer,int sizeofdate )
{
int i;
flag =1; //应答标志
rIICDS = devAddr;
rIICCON &= ~0x10; //清中断标志
rIICSTAT = 0xf0; //主设备发送模式
while(flag == 1) //等待从设备应答,
delay(100); //一旦进入IIC中断,即可跳出该死循环
flag = 1;
rIICDS = wordAddr; //写入从设备内存地址
rIICCON &= ~0x10;
while(flag)
delay(100);
//连续写入数据
for(i=0;i<sizeofdate;i++)
{
flag = 1;
rIICDS = *(buffer+i);
rIICCON &= ~0x10;
while(flag)
delay(100);
}
rIICSTAT = 0xd0; //发出stop命令,结束该次通讯
rIICCON = 0xe0; //为下次IIC通讯做准备
delay(100); //等待
}
//AT24C02A的序列读,当sizeofdate为1时,是随机读
//输入参数依次为设备内存地址、IIC数据缓存数组和要读取的数据个数
void rd24c02a(unsigned char wordAddr,unsigned char *buffer,int sizeofdate )
{
int i;
unsigned char temp;
flag =1;
rIICDS = devAddr; //
rIICCON &= ~0x10; //清中断标志
rIICSTAT = 0xf0; //主设备发送模式
while(flag)
delay(100);
flag = 1;
rIICDS = wordAddr;
rIICCON &= ~0x10;
while(flag)
delay(100);
flag = 1;
rIICDS = devAddr; //
rIICCON &= ~0x10;
rIICSTAT = 0xb0; //主设备接收模式
while (flag)
delay(100);
flag = 1;
temp = rIICDS; //读取从设备地址
rIICCON &= ~0x10;
while(flag)
delay(100);
//连续读
for(i=0;i<sizeofdate;i++)
{
flag = 1;
if(i==sizeofdate-1) //如果是最后一个数据
rIICCON &= ~0x80; //不再响应
*(buffer+i) = rIICDS;
rIICCON &= ~0x10;
while(flag)
delay(100);
}
rIICSTAT = 0x90; //结束该次通讯
rIICCON = 0xe0; //
delay(100);
}
void Main(void)
{
…… ……
//初始化IIC
rIICCON = 0xe0; //设置IIC时钟频率,使能应答信号,并开启中断
rIICSTAT = 0x10;
pISR_UART0 = (U32)uartISR;
pISR_IIC = (U32)IicISR;
flag=1;
comm=0;
while(1)
{
switch(comm) //判断命令
{
case 1: //写EEPROM命令
wr24c02a(address,iic_buffer,length);
comm=0;
flag=1;
address=0;
length=0;
break;
case 2: //读EEPROM命令
rd24c02a(address,iic_buffer,length);
comm=0;
flag=1;
address=0;
for(i=0;i<length;i++) //向PC机发送数据
{
delay(500);
rUTXH0 = iic_buffer[i];
}
length=0;
break;
}
}
}
虽然这段程序不是很难,但也花费了我不少的时间,总是出现这样或那样的问题。现在我就把编写上述程序时需要注意的事项总结几点,避免大家少走弯路:
⑴清IIC中断标志语句rIICCON &= ~0x10;一定要在读写寄存器IICDS的后面,不能放到它的前面;
⑵在等待应答的死循环while内,一定要加上延时的程序;
⑶在读取AT24C02A数据时,当读到最后一个数据时,一定不能让s3c2440发送应答信号,否则以后会无法再读取AT24C02A数据,除非关电重启;
⑷在真正对AT24C02A进行读取数据时,在发送带有读命令的从设备地址后,AT24C02A会再返回一个从设备地址信息或从设备内存地址信息作为应答,所以一定要把该字节读取后抛弃,因为它不是我们所要读取的信息;
⑸按照AT24C02A的时序,在发送从设备地址字节时,它的最低位是0表示写,1表示读。但对于s3c2440来说,不用人为设置这一位,即是0是1都无所谓,因为这一位是由s3c2440根据是主设备发送模式还是主设备接收模式来自动设置。