转自:http://blog.youkuaiyun.com/spfyy/article/details/6831904
编写一程序,用S3C2410的I2C接口对串行EEPROM 24LC04(I2C接口)进行读/写操作,写入一组数据,然后读出并显示出来,检验是否正确。
分析:S3C2410的I2C为主设备,EEPROM的I2C为从设备,进行的操作为主设备写、和主设备读。
(1)设置I2C控制寄存器
1)收发传输:IICCON=0b 1 0 1 0 1111 = 0xAF
含义:应答使能、时钟分频为IICCLK = PCLK /16 、中断使能、清除中断标志、预分频值取15。
2)接收结束传输:IICCON=0b 0 0 1 0 1111 = 0x2F
含义:禁止应答(非应答)、时钟分频为IICCLK = PCLK /16 、中断使能、清除中断标志、预分频值取15。
(2)I2C控制状态寄存器
1)主模式发送、启动传输
IICSTAT=0b 11 1 1 0 0 0 0 = 0xF0
2)主模式发送、结束传输
IICSTAT=0b 11 0 1 0 0 0 0 = 0xD0
3)主模式接收、启动传输
IICSTAT=0b 10 1 1 0 0 0 0 = 0xB0
4)主模式接收、结束传输
IICSTAT=0b 10 0 1 0 0 0 0 = 0x90
一、 写单字节
(1)单字节写操作:
字节写操作以来自于主器件的起始位开始, 4 位控制码紧随其后。接下来的3 位是存储块寻址位(不带地址输入引脚的器件)或片选位(带地址输入引脚的器件)。然后主发送器将R/W 位(该位为逻辑低电平)发送到总线。从器件在第九个时钟周期产生一个确认位。主器件发送的第二个字节是地址字节。24XX 器件会对每一个地址字节作出确认,并把地址位锁存进器件内部的地址计数器。对于24XX00 器件,只使用地址字节的低4 位。高4 位可为任意值。送出最后一个地址字节后, 24XX 器件发出确认信号ACK。主器件在接收到该确认信号后即发送数据字,该数据字将被写入已寻址的存储器位置。24XX 器件再次发出确认信号,之后主器件产生停止条件,启动内部写周期。写(内部写)周期期间, 24XX 不会对命令进行确认。因此需要确认查询。
(2)字节读操作:
随机读操作允许主器件以随机方式访问任意存储器。执行该指令前必须先设置地址字节。作为写操作的一部分,通过发送字节地址给24XX 来完成地址字节的设置(R/W 设置为‘0’)。字节地址发送完后,主器件一接收到确认信号即产生起始条件。内部地址计数器设置完之后写操作即被终止。主器件再次发送控制字节,而该字节中R/W 位设置为‘1’。之后24XX 会发出确认信号, 并发送8 位数据字节。主器件不会对数据传输作出确认,但会产生停止条件,24XX 即停止数据发送。在随机读取命令之后,内部地址计数器加1 指向下一条地址。
(3)读写操作:
主机发送模式(写):数据被发送后,I2C 总线接口会等待IICDS(I2C 数据移位寄存器)被写入新的数据。此时,SCL线保持低电平。
写中断TX: S3C2410X 可以利用中断来判断当前数据字节是否已经被完全移位送出。在CPU 接收到中断请求后,需在中断处理中,再次将下一个新的数据写入IICDS,如此循环。
主机接收模式:数据被接收到后,I2C 总线接口将等待直到IICDS 寄存器被程序读出。在数据被读出之前,SCL 线保持低电平。
读中断RX: S3C2410X 也利用中断来判别是否接收到了新的数据。CPU 收到中断请求之后,中断处理程序将从IICDS 读取数据。
(4)控制字节值:
所寻从设备地址(A0)+操作控制命令(R/ W):
1)主设备发送: 0xA0;主设备接收: 0xA1
2)24LC04有512字节,分成低256字节和高256, 字节地址分别是0xA0和0xA2
(5)确认查询流程:
下图是字节写的整个流程,其中的循环是确认查询流程。
在写周期期间器件不会对命令作出确认,这可用来确定写周期何时完成。如果主器件已经发出写命令的停止条件,器件将启动内部定时写周期。可以随时进行确认查询。这包括在主器件发出起始条件后,再发送用于写命令(R/W = 0)的控制字节。如果器件仍处在写周期内,则不返回确认信号。一旦没有返回确认信号,起始位和控制字节必须重新发送。如果写周期结束,器件返回确认信号,主器件就可以执行下一个读或写命令。参见流程图。图中的循环用于检测内部写结束的确认信号,实质是不断查询rIICSTAT[0]的状态。
可见,内写结束后,对确认的查询是关键。
实验源程序:
#include"def.h"
#include"2410addr.h"
#include"2410lib.h"
#include<string.h>
#include"..\INC\config.h"
void Wr24C04(U32 slvAddr,U32 addr,U8 data);
void Rd24C04(U32 slvAddr,U32 addr,U8 *data);
void Main(void)
{
unsignedint i , j;
static U8 data[256];
Target_Init( );
Uart_Printf("[I2c test using at24c04]\n");
rGPEUP |= 0xc000; //1100禁止上拉(悬浮),有外部上拉
rGPECON &= ~0xf0000000;
rGPECON |= 0xa0000000; //GPE15:IICSDA GPE14:IICSCL
rIICCON = (1<<7) | (0<<6) |(1<<5) | (0xf); // 0xaf, 允许应答和中断,清已有中断 //IICCLK=PCLK/16
rIICSTAT= 0x10; // I2C总线数据传送允许Rx/Tx
Uart_Printf("Write test data into AT24C04 \n");
for(i=0; i<256; i++)
Wr24C04(0xa0, (U8)i , i ); // 0xa0: 24LC04地址,(U8)i:待写入数据到芯片的地址, // i:要写入的数据
for(i=0; i<256; i++)
data[i] = 0;
Uart_Printf("\nRead test data fromAT24C04\n");
for(i=0; i<256; i++)
Rd24C04(0xa1, (U8)i, &data[i]);
for(i=0; i<16; i++)
{
for(j=0; j<16; j++)
Uart_Printf("%2x",data[i*16+j]);
Uart_Printf("\n");
}
Uart_Printf("OK! Write data is same toRead data!\n");
}
// 写一字节函数定义
void Wr24C04(U32 slvAddr,U32 addr,U8 data)
{
rIICDS = slvAddr; //设置控制字节
rIICSTAT = 0xf0; //启动发送
while((rIICCON & 0x10)==0); //查询中断状态
while(rIICSTAT & 1); //检查应答ACK
rIICDS = addr; //设置字地址字节
rIICCON = 0xaf; //清除中断状态
while((rIICCON & 0x10)==0); //查询中断状态
while(rIICSTAT & 1); //检查应答ACK
rIICDS = data; //发送数据
rIICCON = 0xaf; //清除中断状态.
while((rIICCON & 0x10)==0); //查询中断状态
while(rIICSTAT & 1); //检查应答ACK
rIICSTAT = 0xd0; //停止写
rIICCON = 0xaf; //
Delay(1); //等待结束生效
while(1)
{
rIICDS = slvAddr;
rIICSTAT = 0xf0;
rIICCON = 0xaf;
while((rIICCON & 0x10)==0);
if(!(rIICSTAT & 0x1)) //检查应答ACK
break;
}
rIICSTAT = 0xd0; //Stop 写
rIICCON = 0xaf;
Delay(1); //等待结束生效
}
// 读一字节函数定义
void Rd24C04(U32 slvAddr,U32 addr,U8 *data)
{
rIICDS = slvAddr; //设置从设备地址 control byte
rIICSTAT = 0xf0; //启动发送 S
while((rIICCON & 0x10)==0); //查询Tx中断状态
while(rIICSTAT & 1);
rIICDS = addr; //置单元偏移地址,发送的
rIICCON = 0xaf; //操作
while((rIICCON & 0x10)==0); //查询Tx中断状态 有中断时,IICSCL被拉低,IIC停//止传输。若要恢复操作,将rIICCON[4]清零。
while(rIICSTAT & 1);
rIICDS = slvAddr;
rIICSTAT = 0xb0; //启动读
rIICCON = 0xaf; //清除中断条件,恢复IIC操作
while((rIICCON & 0x10)==0); //查询中断状态
while(rIICSTAT & 1);
rIICCON = 0x2f; // 清除中断标志,禁止应答,因仅读1字节
while((rIICCON & 0x10)==0); //查询中断状态
*data =rIICDS; //读取数据after the cpu receives the interrupt request,it should //read the datafrom the iicds register.
rIICSTAT = 0x90; //停止读
rIICCON = 0xaf; //清除中断
Delay(1);
}
二、 写多字节
(1)页写入操作:
(2)写多字节的流程:
BEGIN
Pages = 0
整页写页数= 待写字节数 / pagesize(16)
While pages < 整页写页数
{
整页写入数据
Pages ++
}
剩余不足一页的字节数 = 待写入字节数 %pagesize(16)
写入剩余字节
END
(3)连续写时的跨页问题:
写控制字节、字地址字节和首个数据字节以和写单字节相同的方式发送给 24XX 器件。不同的是,主器件发送的是多至一整页的数据字节,而不是停止条件,这些数据字节临时存储在片内页缓冲器中。在主器件发送停止条件之后,这些数据
将被写入存储器。每接收一个字,内部地址计数器加一。如果在停止条件产生前,主器件有超出一页的数据要发送,地址计数器将会翻转,先前写入的数据将被覆
盖。对于字节写操作,一旦接收到停止条件,内部写周期开始。
(4)跨页问题的解决办法:
引入了两个参数pages和i 。pages代表已经占用的页数,i代表写入的总字节数。
以连续写51个字节为例:前48字节以整页方式写入,后3个字节再另起一页连续写入。地址计数器每次翻转,代表addr 又被赋值为该页的首地址。因此每次外写完一页时,就发送停止信号开始内部写,并且 将 addr += 16; 这就解决了跨页。再重新启动,写下一页。如此循环。
实验源程序:
#include "def.h"
#include "2410addr.h"
#include "2410lib.h"
#include <string.h>
#include "..\INC\config.h"
void Wr24C04(U32 slvAddr,U32 addr,U8 data);
void Rd24C04(U32 slvAddr,U32 addr,U8 *data);
void start(U32 slvAddr);
void internal_write(U32 slvAddr);
void Main(void)
{
unsigned int i,j;
staticU8 data[256];
Target_Init();
Uart_Printf("[I2c test using at24c04]\n");
rGPEUP|= 0xc000;
rGPECON &= ~0xf0000000;
rGPECON |= 0xa0000000;
rIICCON = (1<<7) | (0<<6) |(1<<5) | (0xf);
rIICSTAT= 0x10;
Uart_Printf("Writetest data into AT24C04\n");
Wr24C04(0xa0,0,1);
for(i=0; i<256; i++)
data[i]= 0;
Uart_Printf("\nReadtest data from AT24C04\n");
for(i=0; i<256; i++)
Rd24C04(0xa0, (U8)i, &data[i]);
for(i=0; i<16; i++)
{
for(j=0; j<16; j++)
Uart_Printf("%2x",data[i*16+j]);
Uart_Printf("\n");
}
Uart_Printf("OK! Write data is same toRead data!\n");
}
// 写函数定义 (写多字节)
void Wr24C04(U32 slvAddr,U32 addr,U8 data)
{
intsize =51; //待写入的总字节数
intm, n, i= 0; // i 为已经写入的字节数
intpage=0;
n= size % 16;
m= size - n; //以整页写方式写入的总字节数
while(page< size/16)
{
start(0xa0);
rIICDS = addr ; //置单元偏移地址,发送
rIICCON= 0xaf ; //清除中断状态
while((rIICCON& 0x10)==0); //查询中断状态
while(rIICSTAT& 1); //检查应答ACK
while(m--)
{
if ((i% 16 == 0) && (i !=0))//防止第一页被漏写
{
internal_write(0xa0);
//至此,则将单独一页字节写入eeprom阵列完毕(内写)
addr += 0x10;
page++;
//重新启动
start(0xa0);
rIICDS = addr; //置单元偏移地址,发送
rIICCON= 0xaf; //清除中断状态
while((rIICCON& 0x10)==0); //查询中断状态
while(rIICSTAT& 1); //检查应答ACK
}
rIICDS = data; //发送数据
rIICCON= 0xaf; //清除中断状态.
while((rIICCON& 0x10)==0); //查询中断状态
while(rIICSTAT& 1); //检查应答ACK
i++; //写入的字节数
}
internal_write(0xa0);
addr+= 0x10 ;
page++;
}
// 以下程序为连续写不足一页的字节数据,或者如果size<16就从这执行连续写入
start(0xa0);
rIICDS = addr; //置单元偏移地址,发送
rIICCON= 0xaf; //清除中断状态
while((rIICCON& 0x10)==0); //查询中断状态
while(rIICSTAT& 1); //检查应答ACK
while(n--)
{
rIICDS = data; //发送数据
rIICCON= 0xaf; //清除中断状态.
while((rIICCON& 0x10)==0); //查询中断状态
while(rIICSTAT& 1); //检查应答ACK
}
internal_write(0xa0);
rIICSTAT= 0xd0; //Stop 写
rIICCON = 0xaf;
Delay(1); //等待结束生效
}
// 读一字节函数定义
void Rd24C04(U32 slvAddr,U32 addr,U8 *data)
{
rIICDS= slvAddr; //设置从设备地址 control byte
rIICSTAT= 0xf0; //启动发送 S
while((rIICCON& 0x10)==0); //查询Tx中断状态
while(rIICSTAT& 1);
rIICDS= addr; //置单元偏移地址,发送的
rIICCON= 0xaf; //操作
while((rIICCON& 0x10)==0); //查询Tx中断状态 有中断时,IICSCL被拉低,IIC停止//传输。若要恢复操作,将rIICCON[4]清零。
while(rIICSTAT& 1);
rIICDS= slvAddr;
rIICSTAT= 0xb0; //启动读
rIICCON= 0xaf; //清除中断条件,恢复IIC操作
while((rIICCON& 0x10)==0); //查询中断状态
while(rIICSTAT& 1);
rIICCON= 0x2f; // 清除中断标志,禁止应答,因仅读1字节
while((rIICCON& 0x10)==0); //查询中断状态
*data=rIICDS; //读取数据 after the cpu receives theinterrupt request,it should //read the data from the iicds register.
rIICSTAT= 0x90; //停止读
rIICCON= 0xaf; //清除中断
Delay(1);
}
//***************************************
void start(U32 slvAddr)
{
rIICDS= slvAddr; //设置从设备地址
rIICSTAT= 0xf0; //启动发送
while((rIICCON& 0x10)==0); //查询Tx中断状态
while(rIICSTAT& 1); //检查应答ACK
}
//****************************************
void internal_write(U32 slvAddr)
{
rIICSTAT= 0xd0; //Stop 写
rIICCON = 0xaf;
Delay(1);
//启动一页的内写
while(1)
{
rIICDS = slvAddr;
rIICSTAT= 0xf0; //启动发送 S
while((rIICCON& 0x10)==0);
rIICCON = 0xaf; //清中断
if(!(rIICSTAT& 0x1)) //检查应答ACK
break;
}
}
//***************************************
连续写51个1: