• I2C总线与E²PROM


    目录

    概述

    特点

    类别

    用途

    I2C时序

    I2C寻址模式

    24C01(E²PROM器件)

    E²PROM写数据流程:

    E²PROM读数据流程:

    E²PROM的页写入

    按时序写的程序

    多字节读写程序

    按页写程序


    概述

    特点

    I2C总线的主要特点是:两条线可以挂多个参与通信的器件,即多机模式,而且任何一个器件都可以作为主机,当然同一时刻只能有一个主机。

    类别

    I2C属于同步通信,SCL时钟线负责收发双方的时钟节拍,SDA数据线负责传输数据。

    I2C的发送方和接收方都以SCL这个时钟节拍为基准进行数据的发送和接收

    半双工通信

    用途

    I2C多用于板内通信

    I2C:通信协议

    E²PROM:器件

    I2C时序

    I2C总线是由始终总线SCL和数据总线SDA两条线构成。连接到总线上的所有器件的SCL都连到一起,所有SDA都连到一起。

    I2C总线是开漏引脚并联的结构,因此外部需要添加上拉电阻。添加上拉电阻后,所有线就组成了线“与”的关系,即所有接入的器件均保持高电平,这条线才是高电平;任意一个器件都可以拉低电平,也就是任何一个器件都可以作为主机。

     I2C中有起始信号、数据传输和停止信号。其中数据传输部分可以一次通信过程传输很多个字节,字节数是不受限制的,而每个字节后都跟一位,答应位,通常用ACK表示。

    I2C通信,必须两条线都参与工作才能完成

    起始信号:

    SCL为高电平期间,SDA由高电平向低电平变化产生一个下降沿,表示起始信号

    数据传输:

    I2C通信是高位在前、低位在后。

    I2C没有固定波特率,但是有时序要求。要求当SCL在低电平的时候,SDA允许变化。当SCL是高电平的时候,SDA绝对不可以变化,因为此时接收方要读取当前SDA的电平信号是0还是1,必须保证SDA的稳定。

    停止信号:

    SCL为高电平期间,SDA由低电平向高电平变化产生一个上升沿,表示结束信号。

    I2C寻址模式

    I2C通信在字节级的传输中也有固定的时序要求。

    I2C在起始信号(Start)后,首先要发送一个从机地址,这个地址一共有7位;紧跟着第8位,数据方向位(R/W),“0”表示接下来要发送数据(写),“1”表示接下来是请求数据(读)。

    若是发送的地址存在,从机SDA输出“0”;如果不存在,从机SDA输出“1”,即应答信号ACK

    主机向从机发送数据,数据传输方向在整个过程中不变:

    S

    从机地址

    0

    A

    数据

    A

    数据

    A/A非

    P

    主机在第一个字节后,立即从从机读取数据:

    S

    从机地址

    1

    A

    数据

    A

    数据

    A/A非

    P

    在传输过程中,当需要改变方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好相反:

    S

    从机地址

    0

    A

    数据

    A/A非

    S

    从机地址

    1

    A

    数据

    A非

    P

    24C01(E²PROM器件)

    24C02和24C01原理图完全一样

    24C02一共256个字节的存储空间,地址从0x00~0xff

    24C02的七位地址中,其中高4位是固定的0b1010,

    低3位的地址取决于电路设计,由A0、A1、A2的实际电平决定

    E²PROM写数据流程:

    第一步:首先是I2C的起始信号

    紧接着跟上I2C的器件地址

    在读写方向上选择“写”操作

    第二步:发送数据的存储地址

    第三步:发送要存储的数据,每个字节都会回应一个“应答位0”,来告诉我们写E²PROM数据成功,若是没有应答位,说明写入不成功。

    注:每成功写入一个字节,E²PROM存储空间的地址都会自动加1,当加到0xff,再加则会溢出,变为0x00。

    E²PROM读数据流程:

    第一步:首先是I2C的起始信号

    紧接着跟上I2C的器件地址

    在读写方向上选择“写”操作

    第二步:发送要读取的数据的地址

    第三步:再次发送I2C的起始信号

    紧接着跟上I2C的器件地址

    在读写方向上选择“读”操作

    第四步:读取完一个字节,如果想要接着读取,就发送一个“应答位ACK(0)”,若是不想读了,就发送一个“非应答位NAK(1)”

    注:无论是读还是写,SCL始终都是由主机控制的

    写的时候,应答信号由从机发出,表示从机是否正确的接收了数据

    读的时候,应答信号由主机发出,表示是否继续读下去

    E²PROM的页写入

    由于一个字节一个字节的写效率过低,所以厂商就对E²PROM进行了分页管理

    24C01、24C02这两个型号是8个字节一个页

    24C04、24C08、24C16是16个字节一个页

    分配好页之后,就可以一次性的把一页的数据写入非易失区域,然后再发送一个停止位,再进行空闲检测,发送下一页。

    按时序写的程序

    1. #include
    2. #include
    3. #define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
    4. sbit I2C_SCL = P3^7;
    5. sbit I2C_SDA = P3^6;
    6. /* 产生总线起始信号 */
    7. void I2CStart()
    8. {
    9. I2C_SDA = 1; //首先确保SDA、SCL都是高电平
    10. I2C_SCL = 1;
    11. I2CDelay();
    12. I2C_SDA = 0; //先拉低SDA
    13. I2CDelay();
    14. I2C_SCL = 0; //再拉低SCL
    15. }
    16. /* 产生总线停止信号 */
    17. void I2CStop()
    18. {
    19. I2C_SCL = 0; //首先确保SDA、SCL都是低电平
    20. I2C_SDA = 0;
    21. I2CDelay();
    22. I2C_SCL = 1; //先拉高SCL
    23. I2CDelay();
    24. I2C_SDA = 1; //再拉高SDA
    25. I2CDelay();
    26. }
    27. /* I2C总线写操作,dat-待写入字节,返回值-从机应答位的值 */
    28. bit I2CWrite(unsigned char dat)
    29. {
    30. bit ack; //用于暂存应答位的值
    31. unsigned char mask; //用于探测字节内某一位值的掩码变量
    32. for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    33. {
    34. if ((mask&dat) == 0) //该位的值输出到SDA上
    35. I2C_SDA = 0;
    36. else
    37. I2C_SDA = 1;
    38. I2CDelay();
    39. I2C_SCL = 1; //拉高SCL
    40. I2CDelay();
    41. I2C_SCL = 0; //再拉低SCL,完成一个位周期
    42. }
    43. I2C_SDA = 1; //8位数据发送完后,主机释放SDA,以检测从机应答
    44. I2CDelay();
    45. I2C_SCL = 1; //拉高SCL
    46. ack = I2C_SDA; //读取此时的SDA值,即为从机的应答值
    47. I2CDelay();
    48. I2C_SCL = 0; //再拉低SCL完成应答位,并保持住总线
    49. return (~ack); //应答值取反以符合通常的逻辑:
    50. //0=不存在或忙或写入失败,1=存在且空闲或写入成功
    51. }
    52. /* I2C总线读操作,并发送非应答信号,返回值-读到的字节 */
    53. unsigned char I2CReadNAK()
    54. {
    55. unsigned char mask;
    56. unsigned char dat;
    57. I2C_SDA = 1; //首先确保主机释放SDA
    58. for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    59. {
    60. I2CDelay();
    61. I2C_SCL = 1; //拉高SCL
    62. if(I2C_SDA == 0) //读取SDA的值
    63. dat &= ~mask; //为0时,dat中对应位清零
    64. else
    65. dat |= mask; //为1时,dat中对应位置1
    66. I2CDelay();
    67. I2C_SCL = 0; //再拉低SCL,以使从机发送出下一位
    68. }
    69. I2C_SDA = 1; //8位数据发送完后,拉高SDA,发送非应答信号
    70. I2CDelay();
    71. I2C_SCL = 1; //拉高SCL
    72. I2CDelay();
    73. I2C_SCL = 0; //再拉低SCL完成非应答位,并保持住总线
    74. return dat;
    75. }
    76. /* I2C总线读操作,并发送应答信号,返回值-读到的字节 */
    77. unsigned char I2CReadACK()
    78. {
    79. unsigned char mask;
    80. unsigned char dat;
    81. I2C_SDA = 1; //首先确保主机释放SDA
    82. for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    83. {
    84. I2CDelay();
    85. I2C_SCL = 1; //拉高SCL
    86. if(I2C_SDA == 0) //读取SDA的值
    87. dat &= ~mask; //为0时,dat中对应位清零
    88. else
    89. dat |= mask; //为1时,dat中对应位置1
    90. I2CDelay();
    91. I2C_SCL = 0; //再拉低SCL,以使从机发送出下一位
    92. }
    93. I2C_SDA = 0; //8位数据发送完后,拉低SDA,发送应答信号
    94. I2CDelay();
    95. I2C_SCL = 1; //拉高SCL
    96. I2CDelay();
    97. I2C_SCL = 0; //再拉低SCL完成应答位,并保持住总线
    98. return dat;
    99. }

    按照E²PROM读写流程写的程序

    1. /* 读取EEPROM中的一个字节,addr-字节地址 */
    2. unsigned char E2ReadByte(unsigned char addr)
    3. {
    4. unsigned char dat;
    5. I2CStart();
    6. I2CWrite(0x50<<1); //寻址器件,后续为写操作
    7. I2CWrite(addr); //写入存储地址
    8. I2CStart(); //发送重复启动信号
    9. I2CWrite((0x50<<1)|0x01); //寻址器件,后续为读操作
    10. dat = I2CReadNAK(); //读取一个字节数据
    11. I2CStop();
    12. return dat;
    13. }
    14. /* 向EEPROM中写入一个字节,addr-字节地址 */
    15. void E2WriteByte(unsigned char addr, unsigned char dat)
    16. {
    17. I2CStart();
    18. I2CWrite(0x50<<1); //寻址器件,后续为写操作
    19. I2CWrite(addr); //写入存储地址
    20. I2CWrite(dat); //写入一个字节数据
    21. I2CStop();
    22. }

    多字节读写程序

    1. /* E2读取函数,buf-数据接收指针,addr-E2中的起始地址,len-读取长度 */
    2. void E2Read(unsigned char *buf, unsigned char addr, unsigned char len)
    3. {
    4. do { //用寻址操作查询当前是否可进行读写操作
    5. I2CStart();
    6. if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
    7. {
    8. break;
    9. }
    10. I2CStop();
    11. } while(1);
    12. I2CWrite(addr); //写入起始地址
    13. I2CStart(); //发送重复启动信号
    14. I2CWrite((0x50<<1)|0x01); //寻址器件,后续为读操作
    15. while (len > 1) //连续读取len-1个字节
    16. {
    17. *buf++ = I2CReadACK(); //最后字节之前为读取操作+应答
    18. len--;
    19. }
    20. *buf = I2CReadNAK(); //最后一个字节为读取操作+非应答
    21. I2CStop();
    22. }
    23. /* E2写入函数,buf-源数据指针,addr-E2中的起始地址,len-写入长度 */
    24. void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
    25. {
    26. while (len--)
    27. {
    28. do { //用寻址操作查询当前是否可进行读写操作
    29. I2CStart();
    30. if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
    31. {
    32. break;
    33. }
    34. I2CStop();
    35. } while(1);
    36. I2CWrite(addr++); //写入起始地址
    37. I2CWrite(*buf++); //写入一个字节数据
    38. I2CStop(); //结束写操作,以等待写入完成
    39. }
    40. }

    按页写程序

    读的程序没有改变,依旧是一个字节一个字节的读取

    1. /* E2写入函数,buf-源数据指针,addr-E2中的起始地址,len-写入长度 */
    2. void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
    3. {
    4. while (len > 0)
    5. {
    6. //等待上次写入操作完成
    7. do { //用寻址操作查询当前是否可进行读写操作
    8. I2CStart();
    9. if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
    10. {
    11. break;
    12. }
    13. I2CStop();
    14. } while(1);
    15. //按页写模式连续写入字节
    16. I2CWrite(addr); //写入起始地址
    17. while (len > 0)
    18. {
    19. I2CWrite(*buf++); //写入一个字节数据
    20. len--; //待写入长度计数递减
    21. addr++; //E2地址递增
    22. if ((addr&0x07) == 0) //检查地址是否到达页边界,24C02每页8字节,
    23. { //所以检测低3位是否为零即可
    24. break; //到达页边界时,跳出循环,结束本次写操作
    25. }
    26. }
    27. I2CStop();
    28. }
    29. }

  • 相关阅读:
    2.4 Struc2vec(图神经网络笔记)
    【.Net8教程】(二)原始字符串字面量
    Ajax系列之文件上传图片即时预览
    蔚小理处境越发尴尬了?
    【AI视野·今日Robot 机器人论文速览 第五十四期】Fri, 13 Oct 2023
    传输加解密 RuoYi-Vue-PLus 4.x
    智云通CRM:报完价后客户没音讯了,该怎么办?
    使用python进行数据合并
    uni-app路由
    React报错之Functions are not valid as a React child
  • 原文地址:https://blog.csdn.net/An_muyan/article/details/124820175