本文包含原理图、PCB、源代码、封装库、中英文PDF等资源
您需要 登录 才可以下载或查看,没有账号?注册会员
×
一、电路实现
AT24C02是一种电可擦除(EEPROM)的数据存储器件,它存储的数据掉电后可保存10年,是目前应用非常广泛的一种数据存储器。它采用I2C总线通信协议。下面我们先来简单了解一下标准的I2C总线协议。
I2C总线是Philips公司推出的一种用于IC器件之间连接的2线制串行扩展总线,它通过2根信号线(串行数据线SDA、串行时钟线SCL)在连接到总线上的器件之间传送数据,所有连接在总线上的I2C器件都可以工作于发送方式或接收方式。
I2C总线的SDA和SCL是双向I/O线,必须通过上拉电阻接到正电源,当总线空闲时,2线都是“高”电平。所有连接在I2C总线上的器件引脚必须是开漏或集电极开路输出,即具有“线与”功能。所有挂在总线上器件的I2C引脚接口也应该是双向的。
I2C总线上允许连接多个器件,支持多主机通信。但为了保证数据可靠的传输,任一时刻总线只能由一台主机控制,其他设备此时均表现为从机。
AVR系列单片机内部都集成了TWI串行总线接口,该接口是对I2C总线的继承和发展,它不但全面兼容I2C总线的特点,而且在操作和使用上比I2C总线更为灵活,功能更加强大。
AVR的TWI是一个面向字节和基于中断的硬件接口,它不仅弥补了许多型号单片机只能依靠时序模拟完成I2C总线工作的缺陷,同时也有这更好的实施性和代码效率,给系统设计人员提供了极大方便。
关于I2C总线和AVR单片机的TWI总线的更详细内容请参阅相关资料。
下面我们采用AVR单片机的TWI接口实现基于I2C总线的EEPROM存储器AT24C02的操作。
下图为本实例中用到的电路图:
其中PD0、PD1分别是ATmega64的TWI接口的SCL和SDA引脚。
现在我们简单了解一下TWI接口知识:
AVR的TWI模块由总线接口单元、比特率发生器、地址匹配单元和控制单元等模块构成。
● SDA和SCL引脚
SDA和SCL是AVR单片机TWI接口的引脚。引脚的输出驱动器包含一个波形斜率限制器,以满足TWI规范;引脚的输入部分包含尖峰抑制但愿,以去除小于50ns的毛刺。
● 波特率发生器
TWI工作在主控器模式时,又该控制单元产生TWI时钟信号,并驱动时钟线SCL,
● 总线接口单元
这个单元包括:数据和地址移位寄存器、起始、停止信号控制和总线仲裁判定的硬件电路。
● 地址匹配单元
地址匹配单元将检测总线上接收到的地址是否与TWAR寄存器中的7位地址相匹配。如果匹配成功,将通知控制单元转入适当的操作状态。TWI可以响应,也可以不响应主控器对其的寻址访问。
● 控制单元
控制单元监听TWI总线,并根据TWI控制寄存器的设置作出相应的响应。
使用AVR单片机的TWI接口最主要和最复杂的部分是设置和判断相关寄存器的内容,关于寄存器的配置请仔细阅读ATmega64的数据手册。
在本实例中我们采用查询法来实现对AT24C02的读写操作,具体控制流程如下:写操作的具体步骤是:
1)TWI寄存器配置;
2)发送START信号;
3)轮询等待TWINT置位,TWINT置位表示START信号已发出;
4)发送写器件地址,到TWDR寄存器,清零TWINT标志位,等待TWINT再次置位,再次置位表示数据已发送完成;
5)读取总线状态是否是器件地址发送完成并收到ACK;
6)发送数据地址,并清零TWINT标志位,然后等待TWINT再次置位,再次置位表示数据已发送完成;
7)读取总线状态是否是器件地址发送完成并收到ACK;
8)发送数据字节,并清零TWINT标志位,然后等待TWINT再次置位,再次置位表示数据已发送完成;
9)读取总线状态是否是器件地址发送完成并收到ACK;
10)数据写操作结束,发送终止信号。
读操作的具体步骤是:
1)TWI寄存器配置;
2)发送START信号;
3)轮询等待TWINT置位,TWINT置位表示START信号已发出;
4)发送写器件地址,到TWDR寄存器,清零TWINT标志位,等待TWINT再次置位,再次置位表示数据已发送完成;
5)读取总线状态是否是器件地址发送完成并收到ACK;
6)发送数据地址,并清零TWINT标志位,然后等待TWINT再次置位,再次置位表示数据已发送完成;
7)读取总线状态是否是器件地址发送完成并收到ACK;
8)发送重复开始(RESTART)信号,并清零TWINT标志位,然后等待TWINT再次置位,再次置位表示发送重复开始信号完成;
9)发送读数据信号,并等待发送完成ACK,判断总线状态是否正确;
10)读取TWDR的值,并根据是否读取完最后一个字节,发送ACK或NACK;
11)数据读取完毕,发送终止信号。
二、程序设计 本实例采用查询法实现对AT24C02的读写操作,程序中先进行单字节的读写,并利用LED灯指示读出的数据是否正确,然后实现一个多字节的读写,同样利用LED灯指示读出的数据是否正确。完整代码如下: -
- #include <avr/io.h> //io端口寄存器配置文件,必须包含
- #include <util/delay.h>
- //变量声明
- #define EEPROM_BUS_ADDRESS 0xa0 //器件地址
- //主机发送模式时各状态字的后续动作
- #define TW_START 0x08 // 开始信号已发出
- #define TW_REP_START 0x10 //重复开始信号已发出
- #define TW_MT_SLA_ACK 0x18 // 写字节已发出并受到ACK信号
- #define TW_MT_SLA_NACK 0x20 //写字节已发出并受到NACK信号
- #define TW_MT_DATA_ACK 0x28 //数据已发出并受到ACK 信号
- #define TW_MT_DATA_NACK 0x30 //数据已发出并受到NACK 信号
- #define TW_MT_ARB_LOST 0x38 //丢失总线控制权
- //主机接收模式时各状态字的后续动作
- #define TW_MR_ARB_LOST 0x38 // 丢失总线控制权,未收到应答信号
- #define TW_MR_SLA_ACK 0x40 //读命令已发出并受到ACK
- #define TW_MR_SLA_NACK 0x48 //读命令已发出并受到NACK
- #define TW_MR_DATA_ACK 0x50 //数据已收到,ACK已发出
- #define TW_MR_DATA_NACK 0x58 //数据已收到,NACK已发出
- //函数声明
- void Delayus(unsigned int lus); //us延时函数
- void Delayms(unsigned int lms); //ms延时函数
- void I2C_Init(void); //I2C端口初始化
- unsigned char I2C_Start(void); //发送起始信号
- void I2C_Stop(void); //发送结束信号
- unsigned char I2C_WriteByte(unsigned char dat); //写一个字节
- unsigned char I2C_ReadByte(unsigned char ack); //读一个字节
- unsigned char EEPROM_ReadByte(unsigned int add); //从固定地址读一字节
- void EEPROM_WriteByte(unsigned int add,unsigned char data); //向固定地址写一字节
- void EEPROM_ReadPage(unsigned int add,unsigned char n,unsigned char *data);
- //多字节读操作
- void EEPROM_WritePage(unsigned int add,unsigned char n,unsigned char *data);
- //多字节写操作
- int main(void) //GCC中main文件必须为返回整形值的函数,没有参数
- {
- unsigned char i;
- unsigned char Read_Buff[6] = {0};
- unsigned char Write_Buff[6] = {0xa1,0xa2,0xa3,0xa4,0xa5,0xa6};
- PORTB = 0xff;
- DDRB = 0xFF; //端口PortB设为输出口,通过相应位LED的变化指示程序运行结果
- I2C_Init(); //I2C端口初始化
- //PORTB |= 0xfe;
- EEPROM_WriteByte(0x04,0x5a); //向固定地址写一字节,写入数据0x5a
- i = EEPROM_ReadByte(0x04); //从固定地址读一字节
- if(i == 0x5a)
- {
- PORTB = 0xfe; //读出的数据正确,则LED0点亮
- }
- else
- {
- PORTB = 0xfd; //读出的数据不正确,则LED1点亮
- }
- for(i = 0;i < 100;i++)
- {
- Delayms(20);
- }
- //PORTB |= 0x02;
- EEPROM_WritePage(0x00,6,Write_Buff); //多字节写操作
-
- EEPROM_ReadPage(0x00,6,Read_Buff); //多字节读操作
-
- if(Read_Buff[0] == 0xa1)
- {
- PORTB = 0x7f; //读出的数据正确,则LED7点亮
- }
- Delayms(500);
- if(Read_Buff[1] == 0xa2)
- {
- PORTB = 0xbf; //读出的数据正确,则LED6点亮
- }
- Delayms(500);
- if(Read_Buff[2] == 0xa3)
- {
- PORTB = 0xdf; //读出的数据正确,则LED5点亮
- }
- Delayms(500);
- if(Read_Buff[3] == 0xa4)
- {
- PORTB = 0xef; //读出的数据正确,则LED4点亮
- }
- Delayms(500);
- if(Read_Buff[4] == 0xa5)
- {
- PORTB = 0xf7; //读出的数据正确,则LED3点亮
- }
- Delayms(500);
- if(Read_Buff[5] == 0xa6)
- {
- PORTB = 0xfb; //读出的数据正确,则LED2点亮
- }
- Delayms(500);
- while(1)
- {
-
-
- }
- }
- //I2C初始化函数
- void I2C_Init(void)
- {
- TWSR |= (1 << TWPS1); //16分频
- TWBR = 20; //波特率
- TWAR = 0x00; //被控器地址寄存器
- TWCR = (1 << TWEA) | (1 << TWEN); //允许ACK,使能I2C,PC0、PC1、转换成SCL、SDA引脚
- }
- //I2C起始条件
- unsigned char I2C_Start(void)
- {
- TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); //清零中断标志位,发送START信号
- while(!(TWCR & (1 << TWINT))); //检测中断标志,为1表明完成发送开始信号
- return 1;
- }
- //I2C结束条件
- void I2C_Stop(void)
- {
- TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); //清零中断标志位,发送START信号
- }
- //向I2C写一个字节
- unsigned char I2C_WriteByte(unsigned char dat)
- {
- unsigned char ack;
- TWDR = dat; //发送数据写入I2C数据寄存器
- TWCR = (1 << TWINT) | (1 << TWEN); //清零中断标志位,开始发送
- while(!(TWCR & (1 << TWINT))); //等待发送完成
- if((TWSR &0xf8) != TW_MT_SLA_ACK) //读取ACK信号
- {
- ack = 0; //没有返回ACK
- }
- else
- {
- ack = 1; //返回ACK
- }
- return ack;
- }
- //从I2C读一个字节 ACK时,应答,NACK时,不应答
- unsigned char I2C_ReadByte(unsigned char ack)
- {
- if (ack)
- TWCR = (1<<TWINT) | (1<<TWEA) | (1 << TWEN); // 读数据,并回送ACK
- else
- TWCR = (1<<TWINT) | (1 << TWEN); //读数据,并回送NACK
- while (!(TWCR & (1 << TWINT))); //等待操作完成
- return (TWDR); //返回读到的数据
- }
- //从固定地址读一字节
- unsigned char EEPROM_ReadByte(unsigned int add)
- {
- unsigned char data;
- I2C_Start(); // 发起始信号
- I2C_WriteByte(EEPROM_BUS_ADDRESS);
- //发器件地址和页地址高三位
- I2C_WriteByte(add); // 发页地址低4位和页地址偏移量
- I2C_Start(); // 发起始信号
- I2C_WriteByte(EEPROM_BUS_ADDRESS | 0x01); // 发从机读寻址字节
- data = I2C_ReadByte(0); //读数据,并发送NACK
- I2C_Stop(); // 发停止信号
- return data;
- }
- //向固定地址写一字节
- void EEPROM_WriteByte(unsigned int add,unsigned char data)
- {PORTB |= 0xfe;
- I2C_Start(); // 发起始信号
- PORTB |= 0xfe;
- I2C_WriteByte(EEPROM_BUS_ADDRESS);
- // 发器件地址和页地址高三位
- I2C_WriteByte(add); // 发页地址低4位和页地址偏移量
- I2C_WriteByte(data); // 写一个字节数据到24C16
- I2C_Stop(); // 发停止信号
- Delayms(10); // 等待10ms,保证24C16内部写操作完成再进行新操作
- }
-
- //多字节读操作
- void EEPROM_ReadPage(unsigned int add,unsigned char n,unsigned char *data)
- {
- unsigned char i;
- I2C_Start(); // 发起始信号
- I2C_WriteByte(EEPROM_BUS_ADDRESS);
- //发器件地址和页地址高三位
- I2C_WriteByte(add); // 发页地址低4位和页地址偏移量
- I2C_Start(); // 发起始信号
- I2C_WriteByte(EEPROM_BUS_ADDRESS | 0x01); // 发从机读寻址字节
- for(i = 0;i <= n-1;i++) //读你n-1个数据
- {
- *data = I2C_ReadByte(1); //读 数据,并发送ACK
- data++;
- }
- *data = I2C_ReadByte(0); //读 最后一个数据,并发送NACK
- I2C_Stop(); // 发停止信号
- }
- //多字节写操作
- void EEPROM_WritePage(unsigned int add,unsigned char n,unsigned char *data)
- {
- unsigned char i;
- I2C_Start(); // 发起始信号
- I2C_WriteByte(EEPROM_BUS_ADDRESS);
- // 发器件地址和页地址高三位
- I2C_WriteByte(add); // 发页地址低4位和页地址偏移量
- for(i = 0;i <= n-1;i++)
- {
- I2C_WriteByte(*data++); // 写一个字节数据到24C16
- }
- I2C_Stop(); // 发停止信号
- Delayms(10); // 等待10ms,保证24C16内部写操作完成再进行新操作
- }
-
- //us级别的延时函数
- void Delayus(unsigned int lus)
- {
- while(lus--)
- {
- _delay_loop_2(4); //_delay_loop_2(1)是延时4个时钟周期,参数为3则延时12
- //个时钟周期,本实验用12M晶体,则12个时钟周期为12/12=1us
- }
- }
- //ms级别的延时函数
- void Delayms(unsigned int lms)
- {
- while(lms--)
- {
- Delayus(1000); //延时1ms
- }
- }
复制代码 |