找回密码
 注册会员
更新自动建库工具PCB Footprint Expert 2024.04 Pro / Library Expert 破解版

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

[复制链接]
慧龙 发表于 2010-5-7 11:19:13 | 显示全部楼层 |阅读模式

本文包含原理图、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的操作。
      下图为本实例中用到的电路图:
8160_1243303239JD25.jpg
其中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端口寄存器配置文件,必须包含
  2. #include <util/delay.h>
  3. //变量声明
  4. #define EEPROM_BUS_ADDRESS 0xa0         //器件地址
  5. //主机发送模式时各状态字的后续动作
  6. #define TW_START            0x08        // 开始信号已发出
  7. #define TW_REP_START        0x10        //重复开始信号已发出
  8. #define TW_MT_SLA_ACK       0x18        // 写字节已发出并受到ACK信号  
  9. #define TW_MT_SLA_NACK      0x20        //写字节已发出并受到NACK信号  
  10. #define TW_MT_DATA_ACK      0x28        //数据已发出并受到ACK 信号
  11. #define TW_MT_DATA_NACK     0x30        //数据已发出并受到NACK 信号
  12. #define TW_MT_ARB_LOST      0x38        //丢失总线控制权
  13. //主机接收模式时各状态字的后续动作
  14. #define TW_MR_ARB_LOST      0x38        // 丢失总线控制权,未收到应答信号
  15. #define TW_MR_SLA_ACK       0x40        //读命令已发出并受到ACK
  16. #define TW_MR_SLA_NACK      0x48        //读命令已发出并受到NACK
  17. #define TW_MR_DATA_ACK      0x50        //数据已收到,ACK已发出
  18. #define TW_MR_DATA_NACK     0x58        //数据已收到,NACK已发出
  19. //函数声明
  20. void Delayus(unsigned int lus);         //us延时函数
  21. void Delayms(unsigned int lms);        //ms延时函数
  22. void I2C_Init(void);                    //I2C端口初始化
  23. unsigned char I2C_Start(void);         //发送起始信号
  24. void I2C_Stop(void);                    //发送结束信号
  25. unsigned char I2C_WriteByte(unsigned char dat);  //写一个字节
  26. unsigned char I2C_ReadByte(unsigned char ack);   //读一个字节
  27. unsigned char EEPROM_ReadByte(unsigned int add); //从固定地址读一字节
  28. void EEPROM_WriteByte(unsigned int add,unsigned char data);  //向固定地址写一字节
  29. void EEPROM_ReadPage(unsigned int add,unsigned char n,unsigned char *data);
  30.                                                     //多字节读操作
  31. void EEPROM_WritePage(unsigned int add,unsigned char n,unsigned char *data);  
  32.                                                       //多字节写操作
  33. int main(void)            //GCC中main文件必须为返回整形值的函数,没有参数
  34. {
  35. unsigned char i;
  36. unsigned char Read_Buff[6] = {0};
  37. unsigned char Write_Buff[6] = {0xa1,0xa2,0xa3,0xa4,0xa5,0xa6};

  38. PORTB = 0xff;     
  39. DDRB = 0xFF;       //端口PortB设为输出口,通过相应位LED的变化指示程序运行结果
  40. I2C_Init();        //I2C端口初始化
  41. //PORTB |= 0xfe;
  42. EEPROM_WriteByte(0x04,0x5a);  //向固定地址写一字节,写入数据0x5a
  43. i = EEPROM_ReadByte(0x04); //从固定地址读一字节

  44. if(i == 0x5a)
  45. {
  46.   PORTB = 0xfe;     //读出的数据正确,则LED0点亮
  47. }
  48. else
  49. {
  50.   PORTB = 0xfd;       //读出的数据不正确,则LED1点亮
  51. }

  52. for(i = 0;i < 100;i++)
  53. {
  54.   Delayms(20);
  55. }

  56. //PORTB |= 0x02;
  57. EEPROM_WritePage(0x00,6,Write_Buff);  //多字节写操作
  58.                                                       
  59. EEPROM_ReadPage(0x00,6,Read_Buff);    //多字节读操作
  60.                                                    
  61. if(Read_Buff[0] == 0xa1)
  62. {
  63.   PORTB = 0x7f;     //读出的数据正确,则LED7点亮
  64. }
  65. Delayms(500);
  66. if(Read_Buff[1] == 0xa2)
  67. {
  68.   PORTB = 0xbf;     //读出的数据正确,则LED6点亮
  69. }
  70. Delayms(500);
  71. if(Read_Buff[2] == 0xa3)
  72. {
  73.   PORTB = 0xdf;     //读出的数据正确,则LED5点亮
  74. }
  75. Delayms(500);
  76. if(Read_Buff[3] == 0xa4)
  77. {
  78.   PORTB = 0xef;     //读出的数据正确,则LED4点亮
  79. }
  80. Delayms(500);
  81. if(Read_Buff[4] == 0xa5)
  82. {
  83.   PORTB = 0xf7;     //读出的数据正确,则LED3点亮
  84. }
  85. Delayms(500);
  86. if(Read_Buff[5] == 0xa6)
  87. {
  88.   PORTB = 0xfb;     //读出的数据正确,则LED2点亮
  89. }
  90. Delayms(500);

  91. while(1)
  92. {
  93.   
  94.   
  95. }
  96. }
  97. //I2C初始化函数
  98. void I2C_Init(void)
  99. {
  100. TWSR |= (1 << TWPS1);    //16分频
  101. TWBR = 20;    //波特率
  102. TWAR = 0x00;    //被控器地址寄存器
  103. TWCR = (1 << TWEA) | (1 << TWEN);  //允许ACK,使能I2C,PC0、PC1、转换成SCL、SDA引脚
  104. }
  105. //I2C起始条件
  106. unsigned char I2C_Start(void)
  107. {
  108. TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);   //清零中断标志位,发送START信号
  109. while(!(TWCR & (1 << TWINT)));         //检测中断标志,为1表明完成发送开始信号

  110. return 1;
  111. }
  112. //I2C结束条件
  113. void I2C_Stop(void)
  114. {
  115. TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);   //清零中断标志位,发送START信号
  116. }
  117. //向I2C写一个字节
  118. unsigned char I2C_WriteByte(unsigned char dat)
  119. {
  120. unsigned char ack;

  121. TWDR = dat;                  //发送数据写入I2C数据寄存器
  122. TWCR = (1 << TWINT) | (1 << TWEN);        //清零中断标志位,开始发送
  123. while(!(TWCR & (1 << TWINT)));         //等待发送完成

  124. if((TWSR &0xf8) != TW_MT_SLA_ACK)     //读取ACK信号
  125. {
  126.   ack = 0;            //没有返回ACK
  127. }
  128. else
  129. {
  130.   ack = 1;        //返回ACK  
  131. }
  132. return ack;
  133. }
  134. //从I2C读一个字节 ACK时,应答,NACK时,不应答
  135. unsigned char I2C_ReadByte(unsigned char ack)
  136. {
  137. if (ack)
  138.        TWCR = (1<<TWINT) | (1<<TWEA) | (1 << TWEN);   // 读数据,并回送ACK
  139.     else
  140.        TWCR = (1<<TWINT) | (1 << TWEN);             //读数据,并回送NACK
  141.     while (!(TWCR & (1 << TWINT)));    //等待操作完成

  142. return (TWDR);     //返回读到的数据
  143. }
  144. //从固定地址读一字节
  145. unsigned char EEPROM_ReadByte(unsigned int add)
  146. {
  147. unsigned char data;

  148. I2C_Start();      // 发起始信号
  149.     I2C_WriteByte(EEPROM_BUS_ADDRESS);     
  150.                         //发器件地址和页地址高三位
  151. I2C_WriteByte(add);     // 发页地址低4位和页地址偏移量

  152.     I2C_Start();         // 发起始信号
  153.     I2C_WriteByte(EEPROM_BUS_ADDRESS | 0x01); // 发从机读寻址字节   
  154.     data = I2C_ReadByte(0);         //读数据,并发送NACK
  155.     I2C_Stop();      // 发停止信号

  156. return data;
  157. }
  158. //向固定地址写一字节
  159. void EEPROM_WriteByte(unsigned int add,unsigned char data)
  160. {PORTB |= 0xfe;
  161. I2C_Start();      // 发起始信号
  162. PORTB |= 0xfe;
  163.     I2C_WriteByte(EEPROM_BUS_ADDRESS);     
  164.                                      // 发器件地址和页地址高三位
  165. I2C_WriteByte(add);     // 发页地址低4位和页地址偏移量
  166.     I2C_WriteByte(data);     // 写一个字节数据到24C16
  167.     I2C_Stop();      // 发停止信号
  168.     Delayms(10);   // 等待10ms,保证24C16内部写操作完成再进行新操作
  169. }
  170.   
  171. //多字节读操作
  172. void EEPROM_ReadPage(unsigned int add,unsigned char n,unsigned char *data)
  173. {
  174. unsigned char i;

  175. I2C_Start();      // 发起始信号
  176.     I2C_WriteByte(EEPROM_BUS_ADDRESS);     
  177.                         //发器件地址和页地址高三位
  178. I2C_WriteByte(add);     // 发页地址低4位和页地址偏移量

  179.     I2C_Start();         // 发起始信号
  180.     I2C_WriteByte(EEPROM_BUS_ADDRESS | 0x01); // 发从机读寻址字节   
  181.     for(i = 0;i <= n-1;i++)              //读你n-1个数据
  182. {
  183.   *data = I2C_ReadByte(1);         //读 数据,并发送ACK
  184.   data++;
  185. }
  186. *data = I2C_ReadByte(0);         //读 最后一个数据,并发送NACK
  187.     I2C_Stop();      // 发停止信号

  188. }
  189. //多字节写操作
  190. void EEPROM_WritePage(unsigned int add,unsigned char n,unsigned char *data)  
  191. {
  192. unsigned char i;

  193. I2C_Start();      // 发起始信号
  194.     I2C_WriteByte(EEPROM_BUS_ADDRESS);     
  195.                                      // 发器件地址和页地址高三位
  196. I2C_WriteByte(add);     // 发页地址低4位和页地址偏移量
  197.     for(i = 0;i <= n-1;i++)
  198. {
  199.   I2C_WriteByte(*data++);     // 写一个字节数据到24C16  
  200.     }
  201. I2C_Stop();      // 发停止信号
  202.     Delayms(10);   // 等待10ms,保证24C16内部写操作完成再进行新操作
  203. }
  204.   
  205. //us级别的延时函数
  206. void Delayus(unsigned int lus)
  207. {
  208. while(lus--)
  209. {
  210.   _delay_loop_2(4);      //_delay_loop_2(1)是延时4个时钟周期,参数为3则延时12
  211.              //个时钟周期,本实验用12M晶体,则12个时钟周期为12/12=1us
  212.     }
  213. }
  214. //ms级别的延时函数
  215. void Delayms(unsigned int lms)
  216. {
  217. while(lms--)
  218. {
  219.   Delayus(1000);        //延时1ms
  220. }
  221. }

复制代码

*滑块验证:
您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

QQ|手机版|MCU资讯论坛 ( 京ICP备18035221号-2 )|网站地图

GMT+8, 2024-11-23 06:10 , Processed in 0.061394 second(s), 9 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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