• 51单片机存储篇:EEPROM(I2C)


    先认识I2C通信

    I2C总线是由Philips公司开发的一种同步、半双工,带数据应答的二线制串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。

    主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。

    物理接口:SCL + SDA
    SCL(serial clock):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道。
    SDA(serial data):数据线,通信数据都通过SDA线传输。

    通信特征:串行、同步、非差分、低速率

    • I2C属于串行通信,所有的数据以位为单位在SDA线上串行传输。
    • 同步通信就是通信双方工作在同一个时钟下,一般是通信的A方通过一根CLK信号线传输A自己的时钟给B,B工作在A传输的时钟下。所以同步通信的显著特征就是:通信线中有CLK
    • 非差分。因为I2C通信速率不高,而且通信双方距离很近,所以使用电平信号通信。
    • 低速率。I2C一般是用在同一个板子上的2个IC之间的通信,而且用来传输的数据量不大,所以本身通信速率很低(一般几百KHz,不同的I2C芯片的通信速率可能不同,具体在编程的时候要看自己所使用的设备允许的I2C通信最高速率,不能超过这个速率)

    突出特征1:主设备+从设备
    I2C通信的时候,通信双方地位是不对等的,而是分主设备和从设备。通信由主设备发起,由主设备主导,从设备只是按照I2C协议被动的接受主设备的通信,并及时响应。
    谁是主设备、谁是从设备是由通信双方来定的(I2C协议并无规定),一般来说一个芯片可以只能做主设备、也可以只能做从设备、也可以既能当主设备又能当从设备(软件配置)。

    突出特征2:可以多个设备挂在一条总线上(从设备地址)
    I2C通信可以一对一(1个主设备对1个从设备),也可以一对多(1个主设备对多个从设备)。

    在这里插入图片描述
    主设备来负责调度总线,决定某一时间和哪个从设备通信。注意:同一时间内,I2C的总线上只能传输一对设备的通信信息,所以同一时间只能有一个从设备和主设备通信,其他从设备处于“冬眠”状态,不能出来捣乱,否则通信就乱套了(广播然后匹配的思路)。
    每一个I2C从设备在通信中都有一个I2C从设备地址,这个设备地址是从设备本身固有的属性,然后通信时主设备需要知道自己将要通信的那个从设备的地址,然后在通信中通过地址来甄别是不是自己要找的那个从设备。(这个地址是一个电路板上唯一的,不是全球唯一的)

    这里要注意IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI。

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

    主要用途:SoC和周边外设之间的通信(典型的如EEPROM、电容触摸IC、各种sensor等)

    I2C的时序:

    I2C的总线空闲状态、起始位、结束位
    I2C总线上有1个主设备,n(n>=1)个从设备。I2C总线上有2种状态;空闲态(所有从设备都未和主设备通信,此时总线空闲)和忙态(其中一个从设备在和主设备通信,此时总线被这一对占用,其他从设备必须歇着)。
    整个通信分为一个周期一个周期的,两个相邻的通信周期是空闲态。每一个通信周期由一个起始位开始,一个结束位结束,中间是本周期的通信数据。
    起始位并不是一个时间点,起始位是一个时间段,在这段时间内总线状态变化情况是:SCL线维持高电平,同时SDA线发生一个从高到低的下降沿。
    与起始位相似,结束位也是一个时间段。在这段时间内总线状态变化情况是:SCL线维持高电平,同时SDA线发生一个从低到高的上升沿。

    I2C数据传输格式(数据位&ACK,Acknowledge character,确认字符)
    每一个通信周期的发起和结束都是由主设备来做的,从设备只有被动的响应主设备,没法自己自发的去做任何事情。
    主设备在每个通信周期会先发8位的从设备地址(其实8位中只有7位是从设备地址,还有1位表示主设备下面要写入还是读出)到总线(主设备是以广播的形式发送的,只要是总线上的所有从设备其实都能收到这个信息)。然后总线上的每个从设备都能收到这个地址,并且收到地址后和自己的设备地址比较看是否相等。如果相等说明主设备本次通信就是给我说话,如果不想等说明这次通信与我无关,不用听了不管了。
    发送方发送一段数据后,接收方需要回应一个ACK。这个响应本身只有1个bit位,不能携带有效信息,只能表示2个意思(要么表示收到数据,即有效响应;要么表示未收到数据,无效响应)
    在某一个通信时刻,主设备和从设备只能有一个在发(占用总线,也就是向总线写),另一个在收(从总线读)。如果在某个时间主设备和从设备都试图向总线写那就完蛋了,通信就乱套了。

    数据在总线上的传输协议
    I2C通信时的基本数据单位也是以字节为单位的,每次传输的有效数据都是1个字节(8位)。
    起始位及其后的8个clk中都是主设备在发送(主设备掌控总线),此时从设备只能读取总线,通过读总线来得知主设备发给从设备的信息;然后到了第9周期,按照协议规定从设备需要发送ACK给主设备,所以此时主设备必须释放总线(主设备把SDA总线置为高电平然后不要动,释放总线,其实就类似于总线空闲状态),同时从设备试图拉低总线发出ACK。如果从设备拉低总线失败,或者从设备根本就没有拉低总线,则主设备看到的现象就是总线在第9周期仍然一直保持高,这对主设备来说,意味着我没收到ACK,主设备就认为刚才给从设备发送的8字节不对(接收失败)

    发送一个字节:

    SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。

    接收一个字节:

    SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节。

    发送应答:

    在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

    接收应答:

    在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答。

    补充:

    问题:在高电平期间读写数据,那么如果高电平持续了多个时钟周期,会不会一直读写数据呢?

    怎么体现高电平传输呢?一开始,SCL是低电平,然后拉高,然后再拉低,构造了一个高电平,这样,才能将数据发送出去或者接收到。

    SPI是通过片选来一对多的(需要多根片选线),I2C是通过地址识别来一对多的(只需要一根地址线)。

    应答信号是可以被配置有或者没有的。

    SPI可以选择从最低位或者最高位开始发送,之前学DS1302的时候是从最低位开始发的,可是在学习SPI的时候又说是从最高位开始发送的。不知道哪个是对的。后面查资料,又说其实是可以配置的,具体情况具体对待的,看手册就知道了,不必纠结。

    I2C一般是从最高位开始传输的。

    有个问题补充:

    某个引脚如果接了地,那么无法将其拉高;如果引脚是高电平,那么可以控制其拉高或者拉低。因为接地的控制力比高电平高。

    EEPROM

    ROM:Read-Only Memory,只读存储器,断电后也能保存数据。

    EEPROM:Electrically Erasable Programmable Read-Only Memory,电可擦除可编程只读存储器,最小读写单位为字节,读写速度很慢。

    EEPROM存在系统中的2种形式:内置在单片机内部,外部扩展。


    EEPROM如何编程?

    • I2C接口底层时序
    • 器件定义的寄存器读写时序

    24C02

    相关内容查看数据手册,这里提供一篇参考文章:

    24C02是一个2Kbit的串行EEPROM存储芯片,可存储256个字节数据。工作电压范围为1.8V到6.0V,具有低功耗CMOS技术,自定时擦写周期,1000000次编程/擦除周期,可保存数据100年。24C02有一个16字节的页写缓冲器和一个写保护功能。通过I2C总线通讯读写芯片数据,通讯时钟频率可达400KHz。

    可以通过存储IC的型号来计算芯片的存储容量是多大,比如24C02后面的02表示的是可存储2Kbit的数据,转换为字节的存储量为2*1024/8 = 256Byte;又比如24C04后面的04表示的是可存储4Kbit的数据,转换为字节的储存量为4*1024/8 = 512Byte;以此来类推其它型号的存储空间。
    24C02的管脚图如下:

    在这里插入图片描述

    VCC和VSS是芯片的电源和地,电压的工作范围为:+1.8V~+6.0V。
    A0、A1、A2是IC的地址选择脚。
    WP是写保护使能脚。
    SCL是I2C通讯时钟引脚。
    SDA是I2C通讯数据引脚。


    下图为芯片从地址:

    在这里插入图片描述
    以看出对于不同大小的24Cxx,具有不同的从器件地址。由于24C02为2k容量,也就是说只需要参考图中第一行的内容。

    芯片的寻址:
    AT24C设备地址为如下:前四位固定为1010,A2~A0为由管脚电平。AT24CXX EEPROM Board模块中默认为接地。A2-A0=000,最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。

    也就是说:
    写24C02的时候,从器件地址为10100000(0xA0);
    读24C02的时候,从器件地址为10100001(0xA1)。

    片内地址寻址(注意从设备地址和内部寻址的区别):

    芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。
    具体解释:
    由于24C02只有256个字节的存储空间,所以只需要1个字节就可以寻址完24C02的存储空间,但是无法寻址完更大容量的存储IC,比如24C04的存储容量是512字节,需要9个bit的地址位才能寻址完。那怎么办呢?该芯片的解决方法是将A0作为其中一个地址位,由上图可以看到,24C04的设备地址内是没有A0参数的,也就是说24C04的A0引脚是不起作用的,这样也就造成了在I2C总线上只能同时挂载4个24C04芯片。其它存储器如24C08、24C16也可以这么类推。

    24C02的WP引脚是写保护引脚,当WP引脚接高电平时,24C02只能进行读取操作,不能进行写操作。只有当WP引脚悬空或接低电平时,24C02才能进行写操作。

    读写EEPROM

    在写EEPROM的时候,要连续写入三个字节,第一个字节为从设备地址,第二个字节为要寻址的内存地址,第三个字节为要写入的数据;

    同理,读的时候也要先写入从设备地址,然后就是寻址地址,然后开始读数据。

    至于是读还是写,上面说了,用从设备地址的最后一位来表示,1表示读,0表示写。

    页写:只发一次从设备地址和寻址首地址,之后直接写入多个数据,就会从首地址开始,按连续地址依次写入。

    读写代码实现

    注意,截至至2022年7月28日凌晨0点46分,此代码是有问题的,没法跑通,编译没问题,就是从设备怎么都没法应答成功,相对应的应该是数据没有写入到ROM中,不知道哪里出了问题,先放这,等查查资料再看看。

    eeprommain.c

    1. /************************************************************
    2. *日期:2022年7月27日
    3. *作者:星辰
    4. *文件内容:eeprom功能程序入口
    5. **************************************************************/
    6. #include "uart.h"
    7. #include "eeprom.h"
    8. /*************************************************************
    9. *
    10. *函数入口
    11. *
    12. **************************************************************/
    13. void main(void)
    14. {
    15. uchar flag1 = 0, flag2 = 0;
    16. uchar uartArr[1] = {0};
    17. I2cStart();
    18. flag1 = I2cWrite(0xA0) && I2cWrite(0x22) && I2cWrite('A'); //写入数据
    19. I2cStop();
    20. if(flag1)
    21. {
    22. I2cStart();
    23. flag2 = I2cWrite(0xA0) && I2cWrite(0x22);
    24. if(flag2)
    25. {
    26. I2cStart();
    27. I2cWrite(0xA1);
    28. uartArr[0] = I2cRead(); //读出的数据
    29. }
    30. I2cStop();
    31. }
    32. UartInit();
    33. SendSomeChar(uartArr, 1);
    34. }

    eeprom.c

    1. /************************************************************
    2. *日期:2022年7月27日
    3. *作者:星辰
    4. *文件内容:eeprom读写
    5. **************************************************************/
    6. #include "eeprom.h"
    7. #include "somedelay.h"
    8. sbit SDA = P2^0;
    9. sbit SCL = P2^1;
    10. /************************************************************
    11. *
    12. *开始标志
    13. *
    14. **************************************************************/
    15. void I2cStart()
    16. {
    17. SDA = 1;
    18. Delay10us();
    19. SCL = 1;
    20. Delay10us();
    21. SDA = 0;
    22. Delay10us();
    23. SCL = 0;
    24. Delay10us();
    25. }
    26. /************************************************************
    27. *
    28. *结束标志
    29. *
    30. **************************************************************/
    31. void I2cStop()
    32. {
    33. SDA = 0;
    34. Delay10us();
    35. SCL = 1;
    36. Delay10us();
    37. SDA = 1;
    38. Delay10us();
    39. }
    40. /************************************************************
    41. *
    42. *按字节写入
    43. *
    44. **************************************************************/
    45. uchar I2cWrite(uchar dataToWrite)
    46. {
    47. uchar i = 0;
    48. SCL = 0;
    49. for(i; i < 8; i++)
    50. {
    51. SDA = dataToWrite >> 7; //从最高位开始传输
    52. dataToWrite <<= 1;
    53. Delay10us();
    54. SCL = 1;
    55. Delay10us();
    56. SCL = 0;
    57. Delay10us();
    58. }
    59. SDA = 1; //释放总线
    60. Delay10us();
    61. SCL = 1;
    62. Delay10us();
    63. if(SDA == 0)
    64. {
    65. SCL = 0;
    66. Delay10us();
    67. return 1; //返回1表示写入成功
    68. }
    69. SCL = 0;
    70. Delay10us();
    71. return 0; //返回0表示失败
    72. }
    73. /************************************************************
    74. *
    75. *按字节读取
    76. *读之前也要先写入从设备地址和寻址地址
    77. **************************************************************/
    78. uchar I2cRead()
    79. {
    80. uchar charGeted = 0;
    81. uchar i = 0;
    82. SCL = 0;
    83. for(i; i < 8; i++)
    84. {
    85. SCL = 1;
    86. Delay10us();
    87. charGeted |= SDA;
    88. if(i != 7)
    89. {
    90. charGeted <<= 1;
    91. }
    92. SCL = 0;
    93. Delay10us();
    94. }
    95. //读的时候不用发送ACK吧?我只要看有没有读出数据就可以了呀。
    96. return charGeted;
    97. }

    uart.c

    1. /************************************************************
    2. *日期:2022年7月27日
    3. *作者:星辰
    4. *文件内容:串口调试
    5. **************************************************************/
    6. #include "uart.h"
    7. #include "somedelay.h"
    8. /*************************************************************
    9. *
    10. *初始化串口
    11. *
    12. **************************************************************/
    13. void UartInit()
    14. {
    15. SCON = 0x50; //设置使用模式1,波特率可变的8位UART,接收模式可用
    16. TMOD = 0x20; //配置定时器1处于模式3,8位自动重载,用作波特率发生器
    17. PCON = 0x80; //使用波特率加倍
    18. TH1 = TL1 = 243; //设置波特率为4800Hz
    19. TR1 = 1; //打开定时器1
    20. }
    21. /*************************************************************
    22. *
    23. *串口发送字符串
    24. *
    25. **************************************************************/
    26. void SendSomeChar(uchar charArr[], int len)
    27. {
    28. while(1)
    29. {
    30. int i = 0;
    31. for(i; i < len; i++)
    32. {
    33. SBUF = charArr[i]; //直接把数据扔给硬件即可,之后的由硬件完成
    34. while(!TI); //等待上一个数据发完再发下一轮
    35. TI = 0; //软件复位
    36. Delay100us(); //必须要延时,速度太快,会出错
    37. }
    38. SBUF = '\r'; //直接把数据扔给硬件即可,之后的由硬件完成
    39. while(!TI); //等待上一个数据发完再发下一轮
    40. TI = 0; //软件复位标志位
    41. Delay1s(); //目标对象之间的延时
    42. }
    43. }

    其他的一些延时代码就不放了~~~~~~~~~~~

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  • 相关阅读:
    【算法】String Compression from Character Array
    线下商家卖货难、拓客难、引流难,不如学习一下怎么结合O2O电商
    web前端期末大作业实例 (1500套) 集合
    树状数组应用(AcWing 242,243,244)
    Spark实现TopN
    window文件夹下python脚本实现批量删除无法预览的图片
    获取热门电影算法
    详解ConCurrentHashMap源码(jdk1.8)
    心法利器[77] | 文本分类日常提点技巧
    VS生成exe软件和软件内部页面带图标
  • 原文地址:https://blog.csdn.net/qq_28576837/article/details/125987250