hrefspace

 找回密码
 立即注册
搜索
热搜: PHP PS 程序设计
查看: 312|回复: 0

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

[复制链接]

926

主题

1137

帖子

3586

积分

超级版主

Rank: 8Rank: 8

积分
3586

论坛头条论坛元老谋士数据帝优秀版主超级版主见习版主论坛版主

发表于 2024-2-22 15:51:18 | 显示全部楼层 |阅读模式
一、电路实现
       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接口知识:
            AVRTWI模块由总线接口单元、比特率发生器、地址匹配单元和控制单元等模块构成。
SDASCL引脚
SDASCLAVR单片机TWI接口的引脚。引脚的输出驱动器包含一个波形斜率限制器,以满足TWI规范;引脚的输入部分包含尖峰抑制但愿,以去除小于50ns的毛刺。
波特率发生器
TWI工作在主控器模式时,又该控制单元产生TWI时钟信号,并驱动时钟线SCL
总线接口单元
这个单元包括:数据和地址移位寄存器、起始、停止信号控制和总线仲裁判定的硬件电路。
● 地址匹配单元
地址匹配单元将检测总线上接收到的地址是否与TWAR寄存器中的7位地址相匹配。如果匹配成功,将通知控制单元转入适当的操作状态。TWI可以响应,也可以不响应主控器对其的寻址访问。
● 控制单元
控制单元监听TWI总线,并根据TWI控制寄存器的设置作出相应的响应。



        使用AVR单片机的TWI接口最主要和最复杂的部分是设置和判断相关寄存器的内容,关于寄存器的配置请仔细阅读ATmega64的数据手册。

         在本实例中我们采用查询法来实现对AT24C02的读写操作,具体控制流程如下:写操作的具体步骤是:

1TWI寄存器配置;

2)发送START信号;

3)轮询等待TWINT置位,TWINT置位表示START信号已发出;

4)发送写器件地址,到TWDR寄存器,清零TWINT标志位,等待TWINT再次置位,再次置位表示数据已发送完成;

5)读取总线状态是否是器件地址发送完成并收到ACK

6)发送数据地址,并清零TWINT标志位,然后等待TWINT再次置位,再次置位表示数据已发送完成;

7)读取总线状态是否是器件地址发送完成并收到ACK

8)发送数据字节,并清零TWINT标志位,然后等待TWINT再次置位,再次置位表示数据已发送完成;

9)读取总线状态是否是器件地址发送完成并收到ACK

10)数据写操作结束,发送终止信号。


读操作的具体步骤是:

1TWI寄存器配置;

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的值,并根据是否读取完最后一个字节,发送ACKNACK

11)数据读取完毕,发送终止信号。


二、程序设计
     本实例采用查询法实现对AT24C02的读写操作,程序中先进行单字节的读写,并利用LED灯指示读出的数据是否正确,然后实现一个多字节的读写,同样利用LED灯指示读出的数据是否正确。完整代码如下:
  1. #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);  //向固定地址写一字节,写入数据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[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}}
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
世界上最遥远的距离,不是生与死的距离,而是我站在你面前,你却不知道我爱你
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|hrefspace

GMT+8, 2024-5-3 04:01 , Processed in 0.069234 second(s), 23 queries .

Powered by hrefspace X3.4 Licensed

Copyright © 2022, hrefspace.

快速回复 返回顶部 返回列表