• #循循渐进学51单片机#IIC总线与EEPROM#not.13


    1、彻底理解I2C的通信时序,不仅仅是记住。

    前几章我们学了一种通信协议叫做 UART 异步串行通信,这节课我们要来学习第二种常用的通信协议 I 2 C I 2 C 总线是由 PHILIPS 公司开发的两线式串行总线,多用于连接微处理器及其外围芯片。I 2 C 总线的主要特点是接口方式简单,两条线可以挂多个参与通信的器件,即多机模式,而且任何一个器件都可以作为主机,当然同一时刻只能有一个主机。从原理上来讲,UART 属于异步通信,比如电脑发送给单片机,电脑只负责把数据通过TXD 发送出来即可,接收数据是单片机自己的事情。而 I 2 C 属于同步通信, SCL 时钟线负责收发双方的时钟节拍,SDA 数据线负责传输数据。 I 2 C 的发送方和接收方都以 SCL 这个时钟 节拍为基准进行数据的发送和接收。从应用上来讲,UART 通信多用于板间通信,比如单片机和电脑,这个设备和另外一个设备之间的通信。而 I 2 C 多用于板内通信,比如单片机和我们本章要学的 EEPROM 之间的通信.
    在硬件上,
    I 2 C 总线是由时钟总线 SCL 和数据总线 SDA 两条线构成,连接到总线上的所有器件的 SCL 都连到一起,所有 SDA 都连到一起。 I 2 C 总线是开漏引脚并联的结构,因此我们外部要添加上拉电阻。对于开漏电路外部加上拉电阻,就组成了线“与”的关系。总线上线“与”的关系就是说,所有接入的器件保持高电平,这条线才是高电平,而任何一个器件输出一个低电平,那这条线就会保持低电平,因此可以做到任何一个器件都可以拉低电平,也就是任何一个器件都可以作为主机,如图 14-1 所示,我们添加了 R63 R64 两个上拉电阻。
    虽然说任何一个设备都可以作为主机,但绝大多数情况下我们都是用单片机来做主机, 而总线上挂的多个器件,每一个都像电话机一样有自己唯一的地址,在信息传输的过程中, 通过这唯一的地址就可以正常识别到属于自己的信息,在 KST-51 开发板上,就挂接了 2 个 I 2 C 设备,一个是 24C02 ,一个是 PCF8591
    我们在学习 UART 串行通信的时候,知道了通信流程分为起始位、数据位、停止位这三 部分,同理在 I 2 C 中也有起始信号、数据传输和停止信号,如图 14-2 所示,
    从图上可以看出来, I 2 C UART 时序流程有相似性,也有一定的区别。 UART 每个字 节中,都有一个起始位、8 个数据位、 1 位停止位。而 I 2 C 分为起始信号、数据传输部分、停 止信号。其中数据传输部分,可以一次通信过程传输很多个字节,字节数是不受限制的,而 每个字节的数据最后也跟了一位,这一位叫做应答位,通常用 ACK 表示,有点类似于 UART 的停止位。
    下面我们一部分一部分的把 I 2 C 通信时序进行剖析。之前我们已经学过了 UART ,所以学习 I 2 C 的过程我尽量拿 UART 来作为对比,这样有助于更好的理解。但是有一点大家要理解清楚,就是 UART 通信虽然用了 TXD RXD 两根线,但是实际一次通信中, 1 条线就可 以完成,2 条线是把发送和接收分开而已,而 I 2 C 每次通信,不管是发送还是接收,必须 2条线都参与工作才能完成,为了更方便的看出来每一位的传输流程,我们把图 14-2 改进成图14-3。
    起始信号: UART 通信是从一直持续的高电平出现一个低电平标志起始位;而 I 2 C 通信的起始信号的定义是 SCL 为高电平期间, SDA 由高电平向低电平变化产生一个下降沿,表示起始信号,如图 14-3 中的 Start 部分所示。
    数据传输:首先, UART 是低位在前,高位在后;而 I 2 C 通信是高位在前,低位在后。其次,UART 通信数据位是固定长度,波特率分之一,一位一位固定时间发送完毕就可以了。而 I 2 C 没有固定波特率,但是有时序的要求,要求当 SCL 在低电平的时候, SDA 允许变化,也就是说,发送方必须先保持 SCL 是低电平,才可以改变数据线 SDA ,输出要发送的当前数据的一位;而当 SCL 在高电平的时候, SDA 绝对不可以变化,因为这个时候,接收方要来读取当前 SDA 的电平信号是 0 还是 1 ,因此要保证 SDA 的稳定,如图 14-3 中的每一位数据的变化,都是在 SCL 的低电平位置。 8 位数据位后边跟着的是一位应答位,应答位我们后 边还要具体介绍。
    停止信号: UART 通信的停止位是一位固定的高电平信号;而 I 2 C 通信停止信号的定义 是 SCL 为高电平期间, SDA 由低电平向高电平变化产生一个上升沿,表示结束信号,如图14-3 中的 Stop 部分所示。

    2、能够独立完成EEPROM任意地址的单字节读写、多字节跨页连续写入读出。

    存储器件,掉电后数据不丢失

    单字节读写

    1. #include <REGX52.H>
    2. extern void InitLcd1602();
    3. extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
    4. extern void I2CStart();
    5. extern void I2CStop();
    6. extern unsigned char I2CReadNAK();
    7. extern bit I2CWrite(unsigned char dat);
    8. unsigned char E2ReadByte(unsigned char addr);
    9. void E2WriteByte(unsigned char addr, unsigned char dat);
    10. void main()
    11. {
    12. unsigned char dat;
    13. unsigned char str[10];
    14. InitLcd1602();
    15. dat = E2ReadByte(0x02);
    16. str[0] = (dat/100) + '0';
    17. str[1] = (dat/10%10) + '0';
    18. str[2] = (dat%10) + '0';
    19. str[3] = '\0';
    20. LcdShowStr(0, 0, str);
    21. dat++;
    22. E2WriteByte(0x02, dat);
    23. while (1);
    24. }
    25. unsigned char E2ReadByte(unsigned char addr)
    26. {
    27. unsigned char dat;
    28. I2CStart();
    29. I2CWrite(0x50<<1);
    30. I2CWrite(addr);
    31. I2CWrite((0x50<<1)|0x01);
    32. dat = I2CReadNAK();
    33. I2CStop();
    34. return dat;
    35. }
    36. void E2WriteByte(unsigned char addr, unsigned char dat)
    37. {
    38. I2CStart();
    39. I2CWrite(0x50<<1);
    40. I2CWrite(addr);
    41. I2CWrite(dat);
    42. I2CStop();
    43. }

     1602液晶模块

    1. #include <REGX52.H>
    2. #define LCD1602_DB P0
    3. sbit LCD1602_RS = P1^0;
    4. sbit LCD1602_RW = P1^1;
    5. sbit LCD1602_E = P1^5;
    6. void LcdWaitReady()
    7. {
    8. unsigned char sta;
    9. LCD1602_DB = 0xff;
    10. LCD1602_RS = 0;
    11. LCD1602_RW = 1;
    12. do{
    13. LCD1602_E = 1;
    14. sta = LCD1602_DB;
    15. LCD1602_E = 0;
    16. }while(sta & 0x80);
    17. }
    18. void LcdWriteCmd(unsigned char cmd)
    19. {
    20. LcdWaitReady();
    21. LCD1602_RS = 0;
    22. LCD1602_RW = 0;
    23. cmd = LCD1602_DB;
    24. LCD1602_E = 1;
    25. LCD1602_E = 0;
    26. }
    27. void LcdWriteDat(unsigned char dat)
    28. {
    29. LcdWaitReady();
    30. LCD1602_RS = 1;
    31. LCD1602_RW = 0;
    32. dat = LCD1602_DB;
    33. LCD1602_E = 1;
    34. LCD1602_E = 0;
    35. }
    36. void LcdSetCursor(unsigned char x, unsigned char y)
    37. {
    38. unsigned char addr;
    39. if(y == 0)
    40. addr = 0x00 + x;
    41. else
    42. addr = 0x40 + x;
    43. LcdWriteCmd(addr | 0x80);
    44. }
    45. void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str)
    46. {
    47. LcdSetCursor(x, y);
    48. while(*str != '\0')
    49. {
    50. LcdWriteDat(*str++);
    51. }
    52. }
    53. void InitLcd1602()
    54. {
    55. LcdWriteCmd(0x38);
    56. LcdWriteCmd(0x0C);
    57. LcdWriteCmd(0x06);
    58. LcdWriteCmd(0x01);
    59. }

     I2C模块

    1. #include <REGX52.H>
    2. #include <intrins.h>
    3. #define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
    4. sbit I2C_SCL = P3^7;
    5. sbit I2C_SDA = P3^6;
    6. void I2CStart()
    7. {
    8. I2C_SDA = 1;
    9. I2C_SCL = 1;
    10. I2CDelay();
    11. I2C_SDA = 0;
    12. I2CDelay();
    13. I2C_SCL = 0;
    14. }
    15. void I2CStop()
    16. {
    17. I2C_SDA = 0;
    18. I2C_SCL = 0;
    19. I2CDelay();
    20. I2C_SCL = 1;
    21. I2CDelay();
    22. I2C_SDA = 1;
    23. I2CDelay();
    24. }
    25. bit I2CWrite(unsigned char dat)
    26. {
    27. bit ack;
    28. unsigned char mask;
    29. for(mask = 0x80;mask!=0;mask>>=1)
    30. {
    31. if((mask&dat)==0)
    32. I2C_SDA = 0;
    33. else
    34. I2C_SDA = 1;
    35. I2CDelay();
    36. I2C_SCL = 1;
    37. I2CDelay();
    38. I2C_SCL = 0;
    39. }
    40. I2C_SDA = 1;
    41. I2CDelay();
    42. ack = I2C_SDA;
    43. I2CDelay();
    44. I2C_SCL = 0;
    45. return (~ack);
    46. }
    47. unsigned char I2CReadNAK()
    48. {
    49. unsigned char mask;
    50. unsigned char dat;
    51. I2C_SDA = 1;
    52. for(mask=0x80; mask!=0; mask>>=1)
    53. {
    54. I2CDelay();
    55. I2C_SCL = 1;
    56. if(I2C_SDA == 0)
    57. dat &= ~mask;
    58. else
    59. dat |= mask;
    60. I2CDelay();
    61. I2C_SCL = 0;
    62. }
    63. I2C_SDA = 1;
    64. I2CDelay();
    65. I2C_SCL = 1;
    66. I2CDelay();
    67. I2C_SCL = 0;
    68. return dat;
    69. }
    70. unsigned char I2CReadACK()
    71. {
    72. unsigned char mask;
    73. unsigned char dat;
    74. I2C_SDA = 1;
    75. for(mask=0x80; mask!=0; mask>>=1)
    76. {
    77. I2CDelay();
    78. I2C_SCL = 1;
    79. if(I2C_SDA == 0)
    80. dat &= ~mask;
    81. else
    82. dat |= mask;
    83. I2CDelay();
    84. I2C_SCL = 0;
    85. return dat;
    86. }
    87. }

     多字节读写

    1. #include <REGX52.H>
    2. extern void InitLcd1602();
    3. extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
    4. extern void I2CStart();
    5. extern void I2CStop();
    6. extern unsigned char I2CReadACK();
    7. extern unsigned char I2CReadNAK();
    8. extern bit I2CWrite(unsigned char dat);
    9. void E2Read(unsigned char *buf, unsigned char addr, unsigned char len);
    10. void E2Write(unsigned char *buf, unsigned char addr, unsigned char len);
    11. void MemToStr(unsigned char *str, unsigned char *src, unsigned char len);
    12. void main()
    13. {
    14. unsigned char i;
    15. unsigned char buf[5];
    16. unsigned char str[20];
    17. InitLcd1602();
    18. E2Read(buf, 0x90, sizeof(buf));
    19. MemToStr(str, buf, sizeof(buf));
    20. LcdShowStr(0, 0, str);
    21. for(i = 0;i < sizeof(buf);i++)
    22. {
    23. buf[i] = buf[i] + 1 + i;
    24. }
    25. E2Write(buf, 0x90, sizeof(buf));
    26. while (1);
    27. }
    28. void MemToStr(unsigned char *str, unsigned char *src, unsigned char len)
    29. {
    30. unsigned char tmp;
    31. while(len--)
    32. {
    33. tmp = *src>>4;
    34. if(tmp <= 9)
    35. *str++ = tmp + '0';
    36. else
    37. *str++ = tmp - 10 +'A';
    38. tmp = *str & 0x0f;
    39. if(tmp <= 9)
    40. *str++ = tmp - 10 - 'A';
    41. *str++ =' ';
    42. src++;
    43. }
    44. *str = '\0';
    45. }
    46. void E2Read(unsigned char *buf, unsigned char addr, unsigned char len)
    47. {
    48. do{
    49. I2CStart();
    50. if (I2CWrite(0x50<<1))
    51. {
    52. break;
    53. }
    54. }while(1);
    55. I2CWrite(addr);
    56. I2CStart();
    57. I2CWrite((0x50<<1)|0x01);
    58. while(len > 1)
    59. {
    60. *buf++ = I2CReadACK();
    61. len--;
    62. }
    63. *buf = I2CReadACK();
    64. I2CStop();
    65. }
    66. void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
    67. {
    68. while(len--)
    69. {
    70. do{
    71. I2CStart();
    72. if (I2CWrite(0x50<<1))
    73. {
    74. break;
    75. }
    76. I2CStop();
    77. }while(1);
    78. I2CWrite(addr++);
    79. I2CWrite(*buf++);
    80. I2CStop();
    81. }
    82. }
    1. //按页连续写入
    2. I2CWrite(addr);
    3. while (len > 0)
    4. {
    5. I2CWrite(*buf++);
    6. len--;
    7. addr++;
    8. if ((addr&0x07) == 0)
    9. {
    10. break;
    11. }
    12. }
    13. I2CStop();
    14. }


    3、将前边学的交通灯进行改进,用EEPROM保存红灯和绿灯倒计时时间,并且可以通过UART改变红灯和绿灯倒计时时间。


    4、使用按键、1602液晶、EEPROM做一个简单的密码锁程序。

  • 相关阅读:
    调整Windows键盘上只能看到拼音而无法看到实际的文本以及关闭输入法悬浮窗方法
    3、CSS动态时钟
    Java进阶(十一)缓冲流
    MindSpore官方RandomChoiceWithMask算子用例报错
    TCR宝藏级隐藏项目National Writing Board
    图像上传功能实现
    金仓数据库KingbaseES客户端编程开发框架-MyBatis-Plus(2. 概述 3. MyBatis-Plus配置说明 )
    CTFhub-RCE-远程包含
    Kafka服务端参数配置
    双十一好物分享,最值得入手的双十一好物推荐
  • 原文地址:https://blog.csdn.net/2301_77479336/article/details/133199371