_only 发表于 2024-2-22 15:51:18

自学AVR单片机二十三(EEPROM存储器AT24C02)

一、电路实现
       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 = {0};unsigned char Write_Buff = {0xa1,0xa2,0xa3,0xa4,0xa5,0xa6}; PORTB = 0xff;   DDRB = 0xFF;       //端口PortB设为输出口,通过相应位LED的变化指示程序运行结果I2C_Init();      //I2C端口初始化//PORTB |= 0xfe;EEPROM_WriteByte(0x04,0x5a);//向固定地址写一字节,写入数据0x5ai = 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 == 0xa1){PORTB = 0x7f;   //读出的数据正确,则LED7点亮}Delayms(500);if(Read_Buff == 0xa2){PORTB = 0xbf;   //读出的数据正确,则LED6点亮}Delayms(500);if(Read_Buff == 0xa3){PORTB = 0xdf;   //读出的数据正确,则LED5点亮}Delayms(500);if(Read_Buff == 0xa4){PORTB = 0xef;   //读出的数据正确,则LED4点亮}Delayms(500);if(Read_Buff == 0xa5){PORTB = 0xf7;   //读出的数据正确,则LED3点亮}Delayms(500);if(Read_Buff == 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);         //读 数据,并发送ACKdata++;}*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}}
页: [1]
查看完整版本: 自学AVR单片机二十三(EEPROM存储器AT24C02)