• 【单片机】14-I2C通信之EEPROM


    1.EEPROM概念

    1.EEPROM

    1.1 一些概念

    (1)一些概念:ROM【只读存储器---硬盘】,RAM【随机访问存储器--内存】,PROM【可编程的ROM】,EPROM【可擦除ROM】,EEPROM【电可擦除ROM】

    1.2 为什么需要EEPROM

    单片机内部的ROM只能在程序下载时进行擦除和改写,但是程序运行本身是不能改写的。

    单片机内部的RAM中的数据程序运行时可以改,但是掉电就丢失了。

    有时候我们需要有一些数据存在系统中,要求掉电不丢失,而且程序还要能改。所以内部ROM和RAM都不行。【这时候系统需要一块EEPROM】

    1.3 EEPROM和flash的区别与联系

    单片机解密中Flash和EEPROM的区别-电子工程世界

    1.4 EEPROM存在系统中的2种形式

    1:内置在单片机内部

    2:外部扩展

    2.EEPROM如何编程

    1.I2C接口底层时序

    底层:CPU和I2C的接口

    2.器件定义的寄存器读写时序

    上层:器件时序

    2.AT24C02原理图和数据手册

    1.接线确定

    查看SCL和SDA无其他接线影响

    SCL对应P2.1 SDA对应P2.0

    2.数据手册理论

    立创商城_一站式电子元器件采购自营商城_现货元器件交易网-嘉立创电子商城

    24c02中文官方资料手册pdf - 百度文库

    1.芯片的基本信息

    类似于一个主持人叫A说话,其他人就不可以说话,但是其他人可以听到主持人和A说话,但是不可以回应。 --广播式

    主设备:51单片机---发送器

    从设备:24Cxxx---接收器

    2.I2C从地址确定

    每一个I2C都有从地址

    3.I2C底层时序

    起始信号:

    发送字节:一般第一个是从设备的地址【因为我们在通话之前,要先发送要进行通话的地址,设备都与自己的地址是否相同,如果相同则响应;如果不同,则丢弃】

    读取字节:

    停止信号:

    3.I2C总结

    (1)主CPU和其附属芯片之间最常用的接口,尤其是各种传感器,因此在物联网时代非常重要

    (2)三根线:SCL,SDA,GND,串行,电平式

    (3)总线式结构:可以一对多,总线上可以挂上百个器件【一个主设备,多个从设备】,用【从地址】来区分--主设备不需要地址

    (4)主从式,由主设备来发起通信及总线仲裁,从设备被动响应

    (5)通信速率一般(kbps级别),不合适语音,视频等信息类型

    4.I2C总线协议定义

    起始信号

    终止信号

    应答信号

    从设备回复主设备,判断从设备是否得到数据。

    可以设置是否要进行”应答信号“【可有可无】

    3.I2C低层时序图和程序

    1.起始信号和结束信号

    SCL和SDA交互进行判断

    (1)起始信号:SCL保持高时,SDA有一个从高到低(下降沿)
    (2)结束信号:SCL保持高时,SDA有一个从低到高(上升沿)

    起始信号

    1. /*******************************************************************************
    2. * 函 数 名 : iic_start
    3. * 函数功能 : 产生IIC起始信号
    4. * 输 入 : 无
    5. * 输 出 : 无
    6. *******************************************************************************/
    7. void iic_start(void)
    8. {
    9. IIC_SDA=1;//如果把该条语句放在SCL后面,第二次读写会出现问题
    10. delay_10us(1);
    11. IIC_SCL=1;
    12. delay_10us(1);
    13. IIC_SDA=0; //当SCL为高电平时,SDA由高变为低,表示起始信号
    14. delay_10us(1);
    15. IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
    16. delay_10us(1);
    17. }

    终止信号

    1. /*******************************************************************************
    2. * 函 数 名 : iic_stop
    3. * 函数功能 : 产生IIC停止信号
    4. * 输 入 : 无
    5. * 输 出 : 无
    6. *******************************************************************************/
    7. void iic_stop(void)
    8. {
    9. IIC_SDA=0;//如果把该条语句放在SCL后面,第二次读写会出现问题
    10. delay_10us(1);
    11. IIC_SCL=1;
    12. delay_10us(1);
    13. IIC_SDA=1; //当SCL为高电平时,SDA由低变为高,表示结束信号
    14. delay_10us(1);
    15. }

    2.I2C发送一个字节

    传输”0“或应答位(A)

    传输”1“或应答位(/A)

    1. /*******************************************************************************
    2. * 函 数 名 : iic_write_byte
    3. * 函数功能 : IIC发送一个字节
    4. * 输 入 : dat:发送一个字节
    5. * 输 出 : 无
    6. *******************************************************************************/
    7. void iic_write_byte(u8 dat)
    8. {
    9. u8 i=0;
    10. //为了保证时序正确,这里要加上
    11. //当SDA将数据放好才可以将SCL置为高电平
    12. IIC_SCL=0;
    13. for(i=0;i<8;i++) //循环8次将一个字节传出,先传高再传低位
    14. {
    15. if((dat&0x80)>0)
    16. IIC_SDA=1;
    17. else
    18. IIC_SDA=0;
    19. dat<<=1; //将次高位移动到最高位
    20. delay_10us(1);
    21. IIC_SCL=1; //产生一个上升沿
    22. delay_10us(1);
    23. IIC_SCL=0; //产生一个下降沿
    24. delay_10us(1);
    25. }
    26. }

     

    (1)I2C发送和接收字节时,都是从高位开始的

    3.应答位处理

    在接收完8位bit后,在第9个时间周期

    应答处理:SDA变低【AT2402拉低】。

    如果我们去检测,如果此时SDA为低电平,则表示已经被拉低,则表示已经响应到;如果SDA为高电平,则表示未能响应到。

     产生ACK应答  

    1. /*******************************************************************************
    2. * 函 数 名 : iic_ack
    3. * 函数功能 : 产生ACK应答
    4. * 输 入 : 无
    5. * 输 出 : 无
    6. *******************************************************************************/
    7. void iic_ack(void)
    8. {
    9. IIC_SCL=0;
    10. IIC_SDA=0; //SDA为低电平
    11. delay_10us(1);
    12. IIC_SCL=1; //将SCL拉高
    13. delay_10us(1);
    14. IIC_SCL=0; //在将SCL拉低
    15. }

     产生NACK非应答 

    1. /*******************************************************************************
    2. * 函 数 名 : iic_nack
    3. * 函数功能 : 产生NACK非应答
    4. * 输 入 : 无
    5. * 输 出 : 无
    6. *******************************************************************************/
    7. void iic_nack(void)
    8. {
    9. IIC_SCL=0;
    10. IIC_SDA=1; //SDA为高电平
    11. delay_10us(1);
    12. IIC_SCL=1;
    13. delay_10us(1);
    14. IIC_SCL=0;
    15. }

    等待应答信号到来 

    1. /*******************************************************************************
    2. * 函 数 名 : iic_wait_ack
    3. * 函数功能 : 等待应答信号到来
    4. * 输 入 : 无
    5. * 输 出 : 1,接收应答失败
    6. 0,接收应答成功
    7. *******************************************************************************/
    8. u8 iic_wait_ack(void)
    9. {
    10. u8 time_temp=0;
    11. IIC_SCL=1;
    12. delay_10us(1);
    13. while(IIC_SDA) //等待SDA为低电平
    14. {
    15. time_temp++;
    16. if(time_temp>100)//超时则强制结束IIC通信
    17. {
    18. iic_stop();
    19. return 1;
    20. }
    21. }
    22. IIC_SCL=0;
    23. return 0;
    24. }

    4.I2C接收一个字节

    释放总线

    在51单片机中,SDA=1就是释放总线【相当于主持人把话筒给嘉宾】;在其他更高级的单片机(比如STM32)这里的处理还会不一样。【因为拉高,则可以拉低(接地);但是拉低了,但是无法拉高】

    为什么SDA=1就是释放总线??是因为当51单片机把引脚拉高时,从设备可以选择再把引脚拉高或者拉低;但是当51单片机把这个引脚拉低(接地)后,从设备也没有办法把这个引脚拉高了。

    1. unsigned char IIC_ReadByte()
    2. {
    3. unsigned char a=0,dat=0;
    4. //释放总线
    5. IIC_SDA=1; //起始和发送一个字节之后IIC_SCL都是0
    6. IIC_delay();
    7. //按道理来说这里应该有一个SCL=0的
    8. for(a=0;a<8;a++){
    9. IIC_SCL=1;//通知从设备我要开始读了,可以放bit数据到SDA了
    10. IIC_delay();
    11. dat<<=1; //读取的时候高位再前
    12. dat|=IIC_SDA;
    13. IIC_delay();
    14. IIC_SCL=0;// 拉低为下一个bit周期做准备
    15. ICC_delay();
    16. }
    17. return dat;
    18. }

    4.EEPROM读写测试

    1.  器件寻址

    (1)从器件的地址是由器件自身定义的,不同的从器件的地址的定义方式是不同的,要查具体的芯片数据手册来确定

    (2)同一个I2C网络中只有一个主设备,但是从设备可以有多个。这多个从设备的地址不能相同。【硬件工程师必须保证这一点。因为从地址是不能通过软件设定的】

    (3)A0,A1,A2----2的三次方=8【表示最多只能接8个EEPROM】

    从CPU的角度来分析24C02的地址定义【如果不是从CPU角度看则得出结果不一样】

            从设备地址是:读地址:0xa1

                                     写地址:0xa0

    2.24C02写高层时序

    写操作时序

    start-send_byte(从地址)--send_byte(字节地址)---send_byte(写入数据)

     字节写

    1. /*******************************************************************************
    2. * 函 数 名 : at24c02_write_one_byte
    3. * 函数功能 : 在AT24CXX指定地址写入一个数据
    4. * 输 入 : addr:写入数据的目的地址
    5. dat:要写入的数据
    6. * 输 出 : 无
    7. *******************************************************************************/
    8. void at24c02_write_one_byte(u8 addr,u8 dat)
    9. {
    10. iic_start();
    11. iic_write_byte(0XA0); //发送写命令,发送写器件地址
    12. iic_wait_ack(); //表示要接收应答
    13. iic_write_byte(addr); //发送写地址
    14. iic_wait_ack(); //表示要接收应答
    15. iic_write_byte(dat); //发送字节
    16. iic_wait_ack();
    17. iic_stop(); //产生一个停止条件
    18. delay_ms(10);
    19. }

    页写

    1. /*******************************************************************************
    2. * 函 数 名 : at24c02_write_one_byte
    3. * 函数功能 : 在AT24CXX指定地址写入一个数据
    4. * 输 入 : addr:写入数据的目的地址
    5. dat:要写入的数据
    6. * 输 出 : 无
    7. *******************************************************************************/
    8. void at24c02_write_one_byte(u8 addr,u8 dat[],u8 i)
    9. {
    10. u8 j;
    11. iic_start();
    12. iic_write_byte(0XA0); //发送写命令,发送写器件地址
    13. iic_wait_ack(); //表示要接收应答
    14. iic_write_byte(addr); //发送写地址
    15. iic_wait_ack(); //表示要接收应答
    16. for(j=0;j
    17. iic_write_byte(dat[i]); //发送字节
    18. iic_wait_ack();
    19. }
    20. iic_stop(); //产生一个停止条件
    21. delay_ms(10);
    22. }

    3. 24C02读高层时序

    1. /*******************************************************************************
    2. * 函 数 名 : at24c02_read_one_byte
    3. * 函数功能 : 在AT24CXX指定地址读出一个数据
    4. * 输 入 : addr:开始读数的地址
    5. * 输 出 : 读到的数据
    6. *******************************************************************************/
    7. u8 at24c02_read_one_byte(u8 addr)
    8. {
    9. u8 temp=0;
    10. iic_start();
    11. iic_write_byte(0XA0); //发送写命令
    12. iic_wait_ack();
    13. iic_write_byte(addr); //发送写地址
    14. iic_wait_ack();
    15. iic_start();
    16. iic_write_byte(0XA1); //进入接收模式
    17. iic_wait_ack();
    18. temp=iic_read_byte(0); //读取字节
    19. iic_stop(); //产生一个停止条件
    20. return temp; //返回读取的数据
    21. }

    4.复合格式

    1.先发送在接收

    2.字节写+随机读

    4.加入串口输出代码

    1. /*******************************************************************************
    2. * 实验名 : EEPROM实验
    3. * 使用的IO :
    4. * 实验效果 : 按K1保存显示的数据,按K2读取上次保存的数据,按K3显示数据加一,
    5. *按K4显示数据清零。
    6. * 注意 :由于P3.2口跟红外线共用,所以做按键实验时为了不让红外线影响实验效果,最好把红外线先
    7. *取下来。
    8. *
    9. *********************************************************************************/
    10. #include
    11. #include "at24c02.h"
    12. #include "uart.h"
    13. void delay20ms(void) //误差 -0.000000000005us
    14. {
    15. unsigned char a,b,c;
    16. for(c=1;c>0;c--)
    17. for(b=222;b>0;b--)
    18. for(a=40;a>0;a--);
    19. }
    20. /*******************************************************************************
    21. * 函 数 名 : main
    22. * 函数功能 : 主函数
    23. * 输 入 : 无
    24. * 输 出 : 无
    25. *******************************************************************************/
    26. void main()
    27. {
    28. unsigned char i;
    29. unsigned char addr;
    30. unsigned char src_data[] = "()ab#cde!fg1234567";
    31. unsigned char buf[8] = "ABCDEFGH";
    32. uart_init();
    33. /*
    34. for (i=0; i<128; i++)
    35. {
    36. uart_send_byte(i);
    37. }
    38. while (1);
    39. */
    40. // 先随便找一堆数据,譬如"abcdefg1234567-_-*&%@/\"
    41. // 把这些写入EEPROM的特定地址中
    42. // 然后读EEROM的这些地址,读出后通过串口打印出来看是不是我们写入的
    43. uart_send_byte('%');
    44. addr = 0;
    45. for (i=0; i<8; i++)
    46. {
    47. At24c02Write(addr, src_data[i]);
    48. delay20ms();
    49. addr++;
    50. }
    51. //先打印出buf
    52. //如果这里没有给buf初始化,则打印会出现问题
    53. for (i=0; i<8; i++)
    54. {
    55. uart_send_byte(buf[i]);
    56. }
    57. //分割
    58. for (i=0; i<20; i++)
    59. {
    60. uart_send_byte('-');
    61. }
    62. // 读出测试
    63. addr = 0;
    64. for (i=0; i<8; i++)
    65. {
    66. buf[i] = At24c02Read(addr);
    67. delay20ms();
    68. addr++;
    69. }
    70. //将数据打印出来
    71. for (i=0; i<8; i++)
    72. {
    73. uart_send_byte(buf[i]);
    74. }
    75. while (1);
    76. // 进一步测试
    77. // 先写入一些特定内容,然后关机断电;然后改代码为读出并打印显示看内容
    78. }

    问题分析

    (1)通过调试发现程序跑飞了,经检测发现uart中没有关中断

    (2)读出内容不对,怀疑是EEPROM经不起快速的连续读写,所以在读和写之间加入20ms的delay,测试后发现读写正确了

    (3)定义了局部变量没有初始化,程序中直接去通过串口输出,结果导致程序

  • 相关阅读:
    springboot+nodejs+vue+Elementui网上商城购物系统
    寻找小红书达人技巧有哪些,小红书行业黑话汇总!
    【驯服野生verilog-mode全记录】day1 —— 常用链接与基本命令模板
    AndroidStudio使用superSUAPK ROOT(失败)
    Redis高频面试题
    postgresql数据库|wal日志的开启以及如何管理
    微信小程序签名
    CS224W Colab_2 笔记
    深入详解Mybatis的架构原理与6大核心流程
    HTML <title> 标签
  • 原文地址:https://blog.csdn.net/m0_63077733/article/details/133465151