(喜欢的朋友麻烦点个关注~~~ 后期还会进行持续更新)
AT24C系列为美国ATMEL公司推出的串行COMS型E2PROM,是典型的串行通信E2PROM ,AT24CXX是IIC总线串行器件,具有工作电源宽(1.8~6.0 V),抗干扰能力强(输入引脚内置施密特触发器滤波抑制噪声),功耗低(写状态时最大工作电流3 mA),高可靠性(写次数100万次,数据保存100年),支持在线编程等特点.
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送, 高速 IIC 总线一般可达 400kbps 以上。
IIC:两线式串行总线,它是由数据线SDA和时钟线SCL构成的串行总线,可发送和接收数据。
在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbs以上。
时钟线SCL:在通信过程起到控制作用。
数据线SDA:用来一位一位的传送数据。
并且IIC又分为软件模拟IIC以及硬件IIC
软件IIC:软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。 硬件IIC:一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。
补充:
1.硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。 2.IIC是半双工通信方式
IIC总线上可以挂很多设备:多个主设备,多个从设备(外围 设备)。上图中主设备是两个单片机,剩下的都是从设备
多主机会产生总线裁决问题。当多个主机同时想占用总线时,企图启动总线传输数据,就叫做总线竞争。I2C通过总线仲裁,以决定哪台主机控制总线
上拉电阻一般在4.7k~10k之间
每个接到I2C总线上的器件都有唯一的地址。主机与其它器件间的数据传输可以是由主机发送数据到其它器件,这时主机 即为发送器,总线上收数据的器件则为接收器。
I2C总线协议规定,任何将数据传送到总线的器件作为发送器。任何从总线接收数据的器件为接收器。
主器件控制串行时钟和起始、停止信号的发生。主器件任何期间都可以发送或接收数据,但是主器件控制数据传送模式(发送或者接收)
IIC 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。
结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。 CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号, CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障
这些信号中,起始信号是必需的,IIC 总线时序图如图所示
I2C总线数据传送,数据位的有效性规定:
接着IIC通信过程由开始、结束、发送、响应、接收五个部分构成
1、(在发送、接收数据的时候)当SCL为高电平时,SDA线不允许变化;当SCL线为低电平时,SDA线可以任意0、1变化,因此要传输数据的时候,SDA线要提前拉高,以保证SCL拉高之后的稳定性 2、(在任意时候)只有当SCL为高电平时,IIC电路才对SDA线上的电平(0或者1)进行记录,当SCL线为低电平时,无论SDA是高还是低,IIC电路都不对SDA进行采样。
在介绍上面五个部分前,我们首先说说空闲状态,什么是空闲状态,就是没有通信时的状态(初始状态)
I2C总线的SDA和SCL两条信号同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
开始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平。 停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。
起始信号和终止信号都是由主机发送的。在起始信号产生之后,总线就处于被占用的状态,在终止信号产生之后,总线就处于空闲状态。
连接到I2C总线上的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号。
开始信号代码:
//产生IIC起始信号
//1.设置SDA输出
//2.先拉高SDA,再拉高SCL,空闲状态
//3.拉低SDA
//4.准备接收数据
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
停止信号代码:
//产生IIC停止信号
//1.设置SDA输出
//2.先拉低SDA,再拉低SCL
//3.拉高SCL
//4.拉高SDA
//5.停止接收数据
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
发送器每发送一个字节,就在时钟脉冲9期间释放数据先,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间位稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P
每当发送器传输完一个字节的数据之后,发送端会等待一定的时间,等接收方的应答信号。接收端通过拉低SDA数据线,给发送端发送一个应答信号,以提醒发送端我这边已经接受完成,数据可以继续传输,接下来,发送端就可以继续发送数据了
每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
应答信号代码:
//产生ACK应答
//这里就很清楚了,产生应答:SCL在SDA一直为低电平期间完成低高电平转换
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
//这里就很清楚了,不产生应答:SCL在SDA一直为高电平期间完成低高电平转换
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
在I2C总线上传送的每位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,SDA逐位地串行传送每一位数据。数据位的传输是边沿触发。
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
//IIC_SCL=0;
//在SCL上升沿时准备好数据,进行传送数据时,拉高拉低SDA,因为传输一个字节,一个SCL脉冲里传输一个位。
//数据传输过程中,数据传输保持稳定(在SCL高电平期间,SDA一直保持稳定,没有跳变)
//只有当SCL被拉低后,SDA才能被改变
//总结:在SCL为高电平期间,发送数据,发送8次数据,数据为1,SDA被拉高,数据为0,SDA被拉低。
//传输期间保持传输稳定,所以数据线仅可以在时钟SCL为低电平时改变。
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7;
//获取数据的最高位,然后左移7位
//如果某位为1,则SDA为1,否则相反
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
单片机发送完一个字节后面必须跟一个等外应答函数:
思路:先让SDA=1,再判断在一定时间内SDA是否变为0,从而识别出外设有没有发送应答信号。
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
//1.设置SDA为输入
//2.拉高SDA
//3.拉高SCL
//4.等待接收器返回应答信号,如果数据线SDA一直为高,就一直等待,并返回1(无效应答),如果数据线SDA为低,返回0(有效应答)
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
发送数据是一位一位发送,接收数据也是一位一位接收进来,最后返回应答信号
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
//先拉低SCL,延时后拉高
//读取数据
//是否发送应答
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
iic.c
#include "iic.h"
#include "delay.h"
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高,空闲状态
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0; //START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0; //钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT(); //sda线输出
IIC_SCL=0;
IIC_SDA=0; //STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1; //发送I2C总线结束信号
delay_us(4);
}
//发送数据后,等待应答信号到来
//返回值:1,接收应答失败,IIC直接退出
// 0,接收应答成功,什么都不做
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0; //时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0; //拉低时钟开始数据传输
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7;
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN(); //SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck(); //发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
iic.h
#ifndef __IIC_H
#define __IIC_H
#include "sys.h"
位段定义 方便后面直接操作IO口 Reg:寄存器地址 Bit:该寄存器的第多少位
//#define BITBAND_REG(Reg,Bit) (*((uint32_t volatile*)(0x42000000u + (((uint32_t)&(Reg) - (uint32_t)0x40000000u)<<5) + (((uint32_t)(Bit))<<2))))
//IO方向设置
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//IO操作函数
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
#endif
AT :ATMEL公司出品
24: 系列号
C :商业
XX : 存储容量 ,举例 01 –> 1K à 128 字节
02 à 2K à 256 字节
…………….
16à 16K à 2K 字节
型号 | 容量bit | 容量byte | 页数 | 字节/页 | 器件寻址位 | 可寻址器件数 | WordAddress位数/字节数 | 备注 |
AT24C01 | 1k | 128 | 16 | 8 | A2A1A0 | 8 | 7/1 | |
AT24C02 | 2k | 256 | 32 | 8 | A2A1A0 | 8 | 8/1 | |
AT24C04 | 4k | 512 | 32 | 16 | A2A1 | 4 | 9/1 | WordAddress使用P0位 |
AT24C08 | 8k | 1024 | 64 | 16 | A2 | 2 | 10/1 | WordAddress使用P0、P1位 |
AT24C16 | 16k | 2048 | 128 | 16 | - | 1 | 11/1 | WordAddress使用P0、P1、P2位 |
AT24C32 | 32k | 4k | 128 | 32 | A2A1A0 | 8 | 12/2 | |
AT24C64 | 64k | 8k | 256 | 32 | A2A1A0 | 8 | 13/2 | |
AT24C128 | 128k | 16k | 256 | 64 | A1A0 | 4 | 14/2 | |
AT24C256 | 256k | 32k | 512 | 64 | A1A0 | 4 | 15/2 |
AT24C512 | 512k | 64k | 512 | 128 | A2A1A0 | 8 | 16/2 |
如上图所示,根据AT24CXX容量不同,设备地址也不同;如,
AT24C01/AT24C02:A0、A1、A2引脚作为7位设备地址的低三位,高4位固定为1010B,低三位A0、A1、A2(接GND为0,接VCC为1)确定了AT24CXX的设备地址,所以一根I2C线上最大可以接8个AT24CXX,地址为1010000B~1010111B。
AT24C04~AT24C16: A0、A1、A2只使用一部分,不用的悬空或者接地(数据手册中写的是悬空不接)。举例:AT24C04只用A2、A1引脚作为设备地址。另外一引脚P0(即原来A0的位置)没有使用,PCB中可悬空,发送地址中对应的这位(P0)用来写入页寻址的页面号,因此一根I2C线上最大可以接4个AT24C04,地址为101000xB~ 101011xB;同理,一根I2C线上最大可以接2个AT24C08,地址为10100xxB ~ 10101xxB;一根I2C线上最大可以接1个AT24C16,地址为1010xxxB ~ 1010xxxB;
AT24C32/AT24C64: 和AT24C01/AT24C02一样,区别是,发送数据地址变成16位。先发送设备地址高8位,再发送设备地址8位。
并且容量为AT24C01~AT24C16,首先发送设备地址(8位地址),再发送数据地址(8位地址),再发送或者接受数据。
AT24C32/AT24C64~AT24C512,首先发送设备地址(8位地址),再发送高位数据地址,再发送地位数据地址,再发送或者接受数据。
AT24C32/AT24C64~AT24C512,首先发送设备地址(8位地址),再发送高位数据地址,再发送地位数据地址,再发送或者接受数据。
注意事项: 对AT24C32来说,WP置高,则只有四分之一受保护,即0x0C00-0x0FFF。也就是说保护区为1KBytes。对于低地址的四分之三,则不保护。所以,如果数据较多时,可以有选择地存储。不重要的数据则放在低四分之三区域,重要的数据则放在高四分之一区域
在本篇文章,我们使用的是AT24C16
I2C总线协议规定,任何将数据传送到总线的器件作为发送器。任何从总线接收数据的器件为接收器。
主器件控制串行时钟和起始、停止信号的发生。主器件任何期间都可以发送或接收数据,但是主器件控制数据传送模式(发送或者接收)。WP写保护引脚:当该引脚连接到VCC,I2C器件内的内容被写保护(只能读)。如果允许对器件进行正常的读写,那么WP引脚需连接到地或者悬空。通过器件地址输入端A0、A1、A2可以实现讲最多8个at24c01器件和at24c02器件、4个at24c04器件、2个at24c08器件、1个at24c16器件连接到总线上。当总线上只有一个器件时,A0、A1、A2可以连接到地或者悬空
以at24c01/at24c02 和at24C16 举例:
I2C总线上所有外围器件都有唯一的地址,这个地址由器件地址和引脚地址两部分组成。共7位。
器件地址是I2C器件固有的地址编码,器件出厂时已经给定,不可更改。
引脚地址由I2C总线外围器件的地址引脚A0、A1、A2决定,根据其在电路中接电源正极、接地或者悬空的不同,形成不同的地址代码。引脚地址数也决定了同一器件可接入总线的最大数目
AT24C16与AT24C01/02/04/08 不同,它引脚的A2,A1,A0是无效的,也就是它没有自己独立的地址,总线上只能挂一个AT24C16设备。
AT24C16总共2048字节,分为128页,每页16字节,地址范围是0~2047。
128页只需要7位地址,分为高3位和低4位,高3位在设备地址中,低4位在字节地址中。
设备地址:1010+页地址高3位+读写方向(1:读 0:写)
字节地址:页地址高4位+4位页内偏移地址
例如读写地址:1864 ,首先计算该地址是多少页的多少个字节,1864/16=116(0x74)页,1864%16=8(0x08),即116页的第8个字节
其中页地址0x74=0 1 1 1 0 1 0 0,最高位忽略,分为D6、D5、D4(高3位)和D3~D0(低4位)两个部分 。
可以计算出 设备地址和字节地址:
设备地址:1010+111+0/1 (AT24C16设备地址高4位固定为1010)
字节地址:0100+1000(高4位是页地址低4位,低4位是页内偏移地址,即0x08)
举个实际的例子:
对AT24C16访问时,按照页地址和页偏移量的方式进行访问。
比如要访问第100页的第3个字节,则在发送寻址的时候,就要发送0X0643,其中页地址的高三位放在器件地址中。
第100页的第3个字节 == 0X0643
0643 = 6 * 256 + 4 * 16 + 3 = (616+4)*16 + 3 = 1603
就是 100页的第3个字节。
所以在编写程序对AT24C16第100页的第3个字节进行写数据的时候,步骤如下:
1)发送起始信号;
2)发送器件地址0XAC(1010 1100,1010是固定地址,110是页地址的高三位,0表示写操作);
3)发送操作地址0X43(0100 0010,0100是页地址的低四位,0010是页地址偏移量,即第100页内的第三个字节;
4)发送要写的数据;
5)发送终止信号。
总线寻址:
主机向从机发送8位数据,这8位数据是在起始信号之后发送的第一个字节,后面的字节都是数据,不再是寻址,除非又重新来一个起始信号。
主机给从机发送第一个字节(总线寻址那个字节),若是读命令,则从机接收到该 命令之后,主动往主机发送数据。
主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,若相同,则认为自己正在被主机寻址,根据R/T位将自己确定为发送器和接收器
从机地址的确定:第0位是读写位。(如对于24C02这块存储器,它若作为从机,那么它的地址中7~4位是固定的,更改不了,第3~1位是可以更改的,每一位根据硬件的管教连接来确定,连接高电平那就是1,低电平就是0)
在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用“0”表示主机发送数据(T),“1”表示主机接收数据(R)。
每次数据传送总是由主机产生的终止信号来结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
数据传输组合方式:
主机向从机发送数据,数据传送方向在整个传递过程中不变:
主机在第一个字节后,立即从从机读数据(传输方向不变):
在传送过程中,当需要改变传递方向时,起始信号和从机地址都被重复一次产生一次,但两次读/写方向位正好相反
注:主机做的都是编程控制,从机做的都是自主控制,也可以说是硬件控制,如主机给应答信号是编程控制,但是从机给应答信号是硬件控制,我们只需要检查在SDA为高期间,SCL保持低电平一些时间,即可判定从机给了主机应答信号
Byte Write写一个字节
上图是x24C04(实际为BR24G04)的写单个字节的时序,可看出与x24c01/x24c02的写单个字节基本相同,不同的是SlaveAddress中只有A2、A1两位表示硬件地址,另外一位为P0,用来扩展内存字节的地址。x24C08则只有一位A2表示器件的硬件地址,页选择位有P1、P0两位,x24C16没有硬件地址位,也就是说使用x24C16只能在同一条IIC总线上连接1个器件,本来表示地址的3个bit全部用作“页选择位”P2、P1、P0。我们可以通过一些设置,将这3款芯片的读单字节的驱动程序统一起来
Page Write写一页
上图是x24C04的页写时序,与x24C01、x24C02的也基本相同,仅红框中的部分有区别,和3.1.1中的写单个字节一样,器件地址只有2位,另一位为页选择位;x24C08、x24C16与此类似
读任意地址
上图是x24C04的读任意地址时序,同样,读任意地址的时序与x24C01、x24C02的也基本相同,只是第一次发送的SlaveAddress包含页选择位P0-P2。
顺序读(页读)
上图是x24C04的顺序读即页读的时序,与前述类似,顺序读在发送SlaveAddress的时候,也会包含页选择位。
AT24Cxx.c
#include "AT24Cxx.h"
#include "delay.h"
#include "stdio.h"
//初始化IIC接口
void AT24CXX_Init(void)
{
IIC_Init();
}
#if EE_TYPE<=AT24C08 //24C01/02/08
//在AT24CXX指定地址读出一个数据
//ReadAddr:开始读数的地址
//返回值 :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
u8 temp=0;
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0); //发送写命令
IIC_Wait_Ack(); //等待应答
IIC_Send_Byte(ReadAddr>>8);//发送高地址
IIC_Wait_Ack();//等待应答
} else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送器件地址0XA0,写数据
IIC_Wait_Ack();//等待应答
IIC_Send_Byte(ReadAddr%256); //发送低地址
IIC_Wait_Ack();//等待应答
IIC_Start(); //起始信号
IIC_Send_Byte(0XA1);//进入接收模式
IIC_Wait_Ack();//等待应答
temp=IIC_Read_Byte(0);
IIC_Stop();//产生一个停止条件
return temp;
}
//在AT24CXX指定地址写入一个数据
//WriteAddr :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
IIC_Start();//起始信号
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0);//发送设备地址
IIC_Wait_Ack();//等待应答
IIC_Send_Byte(WriteAddr>>8);//发送字节高地址
} else
{
IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址0XA0,写数据
}
IIC_Wait_Ack();//等待应答
IIC_Send_Byte(WriteAddr%256); //发送字节低地址
IIC_Wait_Ack();//等待应答
IIC_Send_Byte(DataToWrite); //发送要写入的数据
IIC_Wait_Ack();//等待应答
IIC_Stop();//产生一个停止条件
delay_ms(10);
}
#else //24C16
/*****************************************************************
*函数名: AT24CXX_ReadOneByte(u16 ReadAddr)
*功能:AT24CXX 读指定地址的一个字节 AT24C16使用
*调用:底层I2C读写函数
*被调用:外部调用
*形参:
ReadAddr:要读取的地址
*返回值:返回读取的数据
*其他:每次读就启动一次I2C时序
*****************************************************************/
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
unsigned char Page=0,WordAddress=0,DeviceAddress=0xA0;
u8 temp=0;
Page=ReadAddr/AT24CXX_Page_Size;
WordAddress=(ReadAddr%AT24CXX_Page_Size) & 0x0F;
DeviceAddress |= (((Page<<1) & 0xE0)>>4);//High 3 bits
WordAddress |= (Page & 0x0F)<<4;//Low 4 bits
IIC_Start(); //起始信号
IIC_Send_Byte(DeviceAddress&0xFE);//发送设备地址+写方向
IIC_Wait_Ack();//等待应答
IIC_Send_Byte(WordAddress);//发送字节地址
IIC_Wait_Ack();//等待应答
IIC_Start(); //起始信号
IIC_Send_Byte(DeviceAddress|0x01);//发送设备地址+读方向
IIC_Wait_Ack();//等待应答
temp=IIC_Read_Byte(0);
IIC_Stop();//产生一个停止条件
return temp;
}
/*****************************************************************
*函数名: AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
*功能:AT24CXX 向指定地址写入一个字节 AT24C16使用
*调用:
*被调用:外部调用
*形参:
WriteAddr:要写入的地址
DataToWrite:写入的数据
*返回值:无
*其他:每次写就启动一次I2C时序
*****************************************************************/
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
unsigned char Page=0,WordAddress=0,DeviceAddress=0xA0;
Page=WriteAddr/AT24CXX_Page_Size;
WordAddress=(WriteAddr%AT24CXX_Page_Size) & 0x0F;
DeviceAddress |= (((Page<<1) & 0xE0)>>4);//High 3 bits
WordAddress |= (Page & 0x0F)<<4;//Low 4 bits
#if DEBUG > 0
printf("Page:%x\r\n",Page);
printf("WordAddress:%x\r\n",WordAddress);
printf("DeviveAddress:%x\r\n",DeviceAddress);
#endif
IIC_Start(); //启始信号
IIC_Send_Byte(DeviceAddress);//发送设备地址
IIC_Wait_Ack();//等待应答
IIC_Send_Byte(WordAddress);//发送字节地址
IIC_Wait_Ack();//等待应答
IIC_Send_Byte(DataToWrite); //发送要写入的数据
IIC_Wait_Ack();//等待应答
IIC_Stop();//产生一个停止条件
delay_ms(10);
}
#endif
/*---------------读写方式选择-----------------*/
#if QuickWR == 0
//在AT24CXX里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
//在AT24CXX里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
while(NumToWrite--)
{
AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}
#else //快速读写方式
/*****************************************************************
*函数名: AT24CXX_Write_Bytes(u8 *pBuffer,u16 WriteAddress,u8 Len)
*功能: 页写函数 最多写入一页(16字节)
*调用: 底层I2C写函数
*被调用:外部调用
*形参:
*pBuffer:指向写入缓存区
WriteAddr:要写入的地址
Len:写入数据长度
*返回值:无
*其他:启动一次I2C时序最多写入一页(16Bytes)数据,明显快于按字节写入
*****************************************************************/
void AT24CXX_Write_Bytes(u8 *pBuffer,u16 WriteAddress,u8 Len)
{
unsigned char Page=0,WordAddress=0,DeviceAddress=0xA0;
u8 i=0;
Page=WriteAddress/AT24CXX_Page_Size;
WordAddress=(WriteAddress%AT24CXX_Page_Size) & 0x0F;
DeviceAddress |= (((Page<<1) & 0xE0)>>4);//High 3 bits
WordAddress |= (Page & 0x0F)<<4;//Low 4 bits
IIC_Start();//启始信号
IIC_Send_Byte(DeviceAddress);//发送设备地址
IIC_Wait_Ack();//等待应答
IIC_Send_Byte(WordAddress);//发送字节地址
IIC_Wait_Ack();//等待应答
for(i=0; i>4);//High 3 bits
WordAddress |= (Page & 0x0F)<<4;//Low 4 bits
while(NumToRead)
{
*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
#endif //快速读写方式
//检查AT24CXX是否正常
//这里用了24XX的最后一个地址(255)来存储标志字.
//如果用其他24C系列,这个地址要修改成该系列的最后一个字节地址
//返回1:检测失败
//返回0:检测成功
u8 AT24CXX_Check(void)
{
u8 temp;
temp=AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX
if(temp==0X55)return 0;
else//排除第一次初始化的情况
{
AT24CXX_WriteOneByte(255,0X55);
temp=AT24CXX_ReadOneByte(255);
if(temp==0X55)return 0;
}
return 1;
}
AT24Cxx.h
#ifndef __AT24CXX_H
#define __AT24CXX_H
#include "IIC.h"
//24CXX驱动函数(适合24C01~24C16,24C32~256未经过测试!有待验证!)
//V1.2
#define AT24C01 127
#define AT24C02 255
#define AT24C04 511
#define AT24C08 1023
#define AT24C16 2047
#define AT24C32 4095
#define AT24C64 8191
#define AT24C128 16383
#define AT24C256 32767
/*----------------EEPROM相关配置--------------------*/
#define EE_TYPE AT24C16 //EEPROM类型
#define AT24CXX_Page_Size 16 //AT24C16每页有16个字节
#define DEBUG 0 //串口调试开关
#define QuickWR 0 //快速读写开关
u8 AT24CXX_ReadOneByte(u16 ReadAddr); //指定地址读取一个字节
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite); //指定地址写入一个字节
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite); //从指定地址开始写入指定长度的数据
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead); //从指定地址开始读出指定长度的数据
u8 AT24CXX_Check(void); //检查器件
void AT24CXX_Init(void); //初始化IIC
#endif
main.c
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "AT24cxx.h"
#include "IIC.h"
#include "stdio.h"
//要写入到24c16的字符串数组
const u8 TEXT_Buffer[]={"C++ is the best language!"};//要写入的内容
#define SIZE sizeof(TEXT_Buffer) //写入内容的大小
#define ADDRESS 2020 //读写地址
int main(void)
{
u8 datatemp[SIZE];
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
delay_init(); //延时函数初始化
uart_init(9600); //串口初始化为9600
LED_Init(); //初始化与LED连接的硬件接口
AT24CXX_Init(); //IIC初始化
while(AT24CXX_Check())//检测不到24c16
{
delay_ms(500);
LED0=!LED0;//DS0闪烁
}
while(1)
{
AT24CXX_Write(ADDRESS,(u8*)TEXT_Buffer,SIZE);
printf("Write:%s\r\n",TEXT_Buffer); //显示写入内容
delay_ms(1000);
AT24CXX_Read(ADDRESS,datatemp,SIZE);
printf("Read:%s\r\n",datatemp);//显示读取内容
}
}
注:此代码可用于AT24C01~~AT24C512系列,但博主只测试了AT24C08/16,其余系列待验证
(5条消息) STM32快速读写AT24C16 代码 模拟I2C_Ruler.的博客-CSDN博客_at24c16读写程序
(5条消息) AT24C32、AT24C64、AT24C128、AT24C256、AT24C512系列EEPROM芯片单片机读写驱动程序_wanglong3713的博客-CSDN博客_at24c512驱动
IIC通信协议详解 - -零 - 博客园 (cnblogs.com)
代码下载:
https://download.csdn.net/download/m0_59601101/85659458