源地址:http://nervfzb.blog.163.com/blog/static/31481399201101010572983/
嵌入式开发 2011-01-10 23:04:04 阅读97 评论0 字号:大中小 订阅
IIC总线用于很多方面,TQ2440上有一块AT24C02A芯片,可以由这个来学学IIC总线的使用。
这个实验中,2440CPU作为主机而AT24C02A作为从机。下面在来看看IIC通信的时候所用到的基本信号类型。
在传输数据过程中有3种类型的信号:开始信号、结束信号、响应信号。
1. 开始信号(S):SCL为高电平,SDA从高电平向低电平跳变。开始传送数据。
2. 结束信号(P):SCL为高电平,SDA从低电平向高电平跳变。结束传送数据。
3. 响应信号(ACK):接收器在接受到8位数据后,在第9个时钟周期,拉低SDA电平。
分析:主要要注意响应信号的时序,发送器发出第8位数据后会变成高电平,这个时候在第9个时钟周期,接收器会将SDA拉低为低电平。
接下来再来看看AT24C02A芯片对于通信数据格式要求:
要寻址AT24C02A的话,必须要明确其从机地址是如何确定的。这个芯片有A0、A1、A2三个管脚,对这三个管脚进行接地或者接VCC可以确定对其的寻址地址(一条总线上最多可以同时接8个AT24C02A所以必须要通过上述三个管脚来对其进行地址编排)。在寻址的时候还要指明芯片是作为接收器还是发送器。AT24C02A芯片的前4位必须是1010。所以综上所述可以得到如下的从机设备地址:
芯片内部是一个存储器的功能,所以功能上需要发送片内地址和收发数据。其他更多的信息,参考《at24c02a的芯片资料重点翻译》中的具体描述。
下面再看看,S3C2440对于IIC的具体操作。
S3C2440资料中,对于主机模式收/发和从机模式收/发都做了一个操作流程说明,这里S3C2440作为主机,所以就关注主机模式下的收/发。
主机模式发送流程操作
主机模式接受流程操作
后面程序就依照上述流程结合AT24C02A的数据写和读的各个流程。
参考了其他成熟程序后,确定本次试验IIC的操作函数如下。
IIC初始化:void IIC_init(void)
主要负责管脚初始化以及各类寄存器初始化。
AT24C02A的读函数:void rd24c02a(unsigned char wordAddr,unsigned char *buffer,int sizeofdate)
主要负责从AT24C02A的指定地址读取数据。封装了随机读和随机顺序读。区别在于数据长度。
AT24C02A的写函数:void wr24c02a(unsigned char wordAddr,unsigned char *buffer,int sizeofdate)
主要负责向AT24C02A的指定地址写数据。封装了字节写和页写。区别在于数据长度。
AT24C02A的中断函数:void at24_iic_interrupt(void)
主要负责控制芯片读写的控制。
分析:这个IIC的总线控制需要中断的控制。这个中断主要是表明数据已经发出或者接收。方便进行下一步操作。
下面是相关的程序:
void IIC_init(void)
{
//GPIO E'initialize
GPEUP = 0xc000;
GPECON = 0xa0000000;
//enable interrupt
//INTMSK &= ~(1 << 27);
//set IICCLK clock
IICCON = ACK_ENABLE | TIME_SRC_CHOOSE(PCLK_16) | TX_RX_INTERRUPT_EN | TX_FRE_COEFFICIENT(0xf);
IICADD = 0x10;//It's the cpu's own slave address,not use here
IICSTAT = 0x10;//enable IIC TX
}
分析:这个函数其实就是配置了IIC管脚所在的寄存器,然后配置了IIC工作的信号以及时钟源的配置。熟悉这个流程便是。
//read from at24c02a,when sizeofdate equal 1,it will become radom read
void rd24c02a(unsigned char wordAddr,unsigned char *buffer,int sizeofdate)
{
unsigned int i,j;
unsigned char temp;
FLAG = 1;
IICDS = DEVADDR;
IICCON &= ~(0x10);
IICSTAT = 0xf0;
while(FLAG)
for(i = 0;i < 100;i++);
FLAG = 1;
IICDS = wordAddr;
IICCON &= ~(0x10);
while(FLAG)
for(i = 0;i < 100;i++);
FLAG = 1;
IICDS = DEVADDR;
IICCON &= ~(0x10);
IICSTAT = 0xb0;
while(FLAG)
for(i = 0;i < 100;i++);
FLAG = 1;
temp = IICDS;
IICCON &= ~(0x10);
while(FLAG)
for(i = 0;i < 100;i++);
for(i = 0;i < sizeofdate;i++)
{
FLAG = 1;
if(i == sizeofdate - 1)
IICCON &= ~(0x80);
buffer[i] = IICDS;
IICCON &= ~(0x10);
while(FLAG)
for(j = 0;j < 100;j++);
}
IICSTAT = 0x90;
IICCON = 0xaf;
for(i = 0;i < 10000;i++);
}
分析:这个函数封装了AT24C02A的随机地址读和页地址读,不过在实践过程发现使用这个函数进行页读取的时候有如下情况(1).地址要从0开始8位对齐,否则会出现折返回读的情况(不会跳入读取下一页的数据)。(2).各个页非8位对齐地址读取的时候也有不同的情况的产生(有点无规律,反正读不出正确数据)。
看看上面函数的流程:首先是发出从设备寻找地址并指定为写入操作→写入要读取数据的首地址→发出从设备寻找地址并指定为读取操作→这时会先返回一个数据(这个是不需要的要抛弃,上面红色部分)→按照给定的数据长度读取数据(长度超过8位会折返读)→结束操作。全部按照资料上给出的操作步骤来,理解透彻就可以。
//at24c02a page write,but when sizeofdate equal 1,it will become byte write
void wr24c02a(unsigned char wordAddr,unsigned char *buffer,int sizeofdate)
{
unsigned int i,j;
FLAG = 1;//when enter the interrupt,it will become 0
IICDS = DEVADDR;//slave addr;
IICCON &= ~(0x10);
IICSTAT = 0xf0;
while(FLAG == 1)
for(i = 0;i < 100;i++);
FLAG = 1;
IICDS = wordAddr;
IICCON &= ~(0x10);
while(FLAG)
for(i = 0;i < 100;i++);
//write data to device
for(i = 0;i < sizeofdate;i++)
{
FLAG = 1;
IICDS = buffer[i];
IICCON &= ~(0x10);
while(FLAG)
for(j = 0;j < 100;j++);
}
IICSTAT = 0xd0;
IICCON = 0xaf;
for(i = 0;i < 10000;i++);
}
分析:这个函数封装了AT24C02A的随机写和页写,需要注意的时候是页写的时候超出一页的边界后会折返回这页的开头再写。函数流程是按照S3C2440芯片资料上给出的程序框架构建。发出从设备寻址地址并指定写入模式→向从设备写入要写入数据区的首地址→写入数据到设备中(最多8个字节)→结束。
void at24_iic_interrupt(void)
{
unsigned int iicSt,i ;
SRCPND |= (1 << 27);
INTPND |= (1 << 27);
iicSt = IICSTAT;
if(iicSt & 0x08)
{
//bus arbitration failed
uart0_printf("bus arbitration failed/n/r");
}
for(i = 0;i < 100;i++);
FLAG = 0;
}
分析:这个函数是用于处理IIC中断的函数,可以看出这个函数主要的作用便是将FLAG置为0,好让其上面的读写函数跳出循环进行下一步操作。这也如同串口一样,接受到一个数据后产生中断。至于函数中的某些细节,主要是为了增强程序的稳定性和正确性(程序中的绿色标注是为了防止设备在仲裁失败后依然还进行操作,红色部分是为了防止延时不够导致数据读取出错)。
void rdat24c02a(unsigned char wordAddr,unsigned char *buffer,int sizeofdate)
{
int i,j;
unsigned char temp_page[8],temp_byte[1];
if(wordAddr % 8 == 0)
{
for(i = wordAddr;((wordAddr + sizeofdate) - i) >= 8;i+=8)
{
rd24c02a(i,temp_page,8);
for(j = 0;j < 8;j++)
{
buffer[i - wordAddr + j] = temp_page[j];
}
}
for(j = 0;j < (wordAddr + sizeofdate - i);j++)
{
rd24c02a((i + j),temp_byte,1);
buffer[i - wordAddr + j] = temp_byte[0];
}
}
else
{
for(i = wordAddr;i < 8;i++)
{
rd24c02a(i,temp_byte,1);
buffer[i - wordAddr] = temp_byte[0];
}
for(i = wordAddr + 8 - wordAddr%8;wordAddr + sizeofdate - i >= 8;i+=8)
{rd24c02a(i,temp_page,8);
for(j = 0;j < 8;j++)
{
buffer[i - wordAddr + j] = temp_page[j];
}
}
for(j = 0;j < wordAddr + sizeofdate - i;j++)
{
rd24c02a((i + j),temp_byte,1);
buffer[i - wordAddr + j] = temp_byte[0];
}
}
}
分析:这个是对AT24C02A芯片读取功能的一个封装,目的是在使用函数的时候不用再注意8字节限制和首地址对齐之类的问题。只要地址的大小加上读取数据的长度限制在2K之内就可以了。为了提高效率,所以是页读和随机读结合的一种构造(虽然只用随机读程序结构会更简单)。
void wrat24c02a(unsigned char wordAddr,unsigned char *buffer,int sizeofdate)
{
int i,j;
unsigned char temp_page[8],temp_byte[1];
if(wordAddr % 8 == 0)
{
for(i = wordAddr;((sizeofdate + wordAddr) - i) >= 8;i+=8)
{
for(j = 0;j < 8;j++)
{
temp_page[j] = buffer[i - wordAddr + j];
}
wr24c02a(i,temp_page,8);
}
for(j = 0;j < (sizeofdate - (i - wordAddr));j++)
{
temp_byte[0] = buffer[i - wordAddr + j];
wr24c02a((i + j),temp_byte,1);
}
}
else
{
for(i = wordAddr;i < 8;i++)
{
temp_byte[0] = buffer[i - wordAddr];
wr24c02a(i,temp_byte,1);
}
for(i = wordAddr + 8 - wordAddr%8;wordAddr + sizeofdate - i >= 8;i+=8)
{
for(j = 0;j < 8;j++)
{
temp_page[j] = buffer[i - wordAddr + j];
}
wr24c02a(i,temp_page,8);
}
for(j = 0;j < wordAddr + sizeofdate - i;j++)
{
temp_byte[0] = buffer[i - wordAddr + j];
wr24c02a(i + j,temp_byte,1);
}
}
}
这个是对wr24c02a函数的一种封装。目的与读函数rdat24c02a一样,构造思路上几乎是一样的。
到此,关于IIC器件AT24C02A芯片的各种初始化以及操作函数展示完了。可以建立一个main函数来调用他们。当然,前提是开启中断。至于这些函数在本次试验的后期测试是基于《TQ2440的学习——S3C2440基于串口中断实现的一个简单串口控制台》串口控制台程序,里面是不是留出两个命令“read iic”和“write iic”的处理位置?现在就要将它们用起来。
注意下面的程序片段:
if(!(no_system_strcmp("read iic",cmd_buf)))
{
no_system_memset(cmd_buf,0,50);
uart0_printf("you input the command read iic/n/r");
uart0_printf("the data are:");
no_system_memset(temp_data_page,0,31);
rdat24c02a(6,temp_data_page,30);
uart0_printf(temp_data_page);
uart0_printf("/n/r");
}
else if(!(no_system_strcmp("write iic",cmd_buf)))
{
no_system_memset(cmd_buf,0,50);
unsigned int j;
char word = 'a';
for(j = 0;j < 30;j++,word++)
{
if(word > 'z')
word = 'a';
temp_data_page[j] = word;
}
temp_data_page[30] = '/0';
uart0_printf("you input the command write iic/n/r");
wrat24c02a(6,temp_data_page,30);
uart0_printf("data write complatly/n/r");
}
总结:
这次程序需要总结的主要是AT24C02A的相关特性,在这个器件的基础上应该要对IIC协议和S3C2440对IIC的各种操作熟悉才是。在读写AT24C02A方面,除了注意中断要延时以保证正确的读写数据意外,在读或写操作发出停止信号后要延时较长时间以使停止信号正确的发出。在程序过程中,由于堆栈分配的区域问题,会出现乱码的情况(如果在main函数定义数组过大,在操作的时候会出现打印不全或者某个功能失效)。全局变量在程序也没用起来(所以后来使用宏定义来装载读写的一个全局变量值(指FLAG))。总之,让我更加深刻的理解了操作系统存在的必要性。而且无操作系统状态的内存分配是个相当严重的问题。对于malloc的构建在后面应该要涉及到,方便后面在无操作系统状态写更加复杂的程序。代码不紧凑,堆栈分配没有策略,内存无法自由分配,全局变量无法再最终程序得到有效组织,都是今后需要解决的问题。
参考书籍:
赵春江《s3c2440的IIC应用——读写AT24C02A》http://blog.csdn.net/zhaocj/archive/2010/04/12/5477152.aspx
韦东山《嵌入式Linux应用开发完全手册》
S3C2440芯片手册、AT24C02A芯片手册