51单片机入门_江协科技_27~28_OB记录的自学笔记_AT24C02数据存储&秒表

27. AT24C02(I2C总线)

  • 27.1. 存储器介绍

  • 27.2. 存储器简化模型介绍,存储原理

  • 27.3. AT24C02介绍

    •AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息

    •存储介质:E2PROM

    •通讯接口:I2C总线

    •容量:256字节

  • 27.4. AT24C02引脚及应用电路,VCC接电源,A0~A2和GND接地,WP写保护接地,上拉电阻在单片机接口位置已经接好,开发板原理图中就不需要接了

  • 27.5. AT24C02内部结构框图,EEPROM为存储单元网格,DEC译码器,RECOVERY数据擦除,

  • 27.6. I2C总线介绍

    •I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线(通信协议,实现多设备通信,并标准规范化数据通信)

    •两根通信线:SCL(Serial Clock)、SDA(Serial Data)

    •同步、半双工,带数据应答

    •通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度。(标准和专利付费),下面是I2C相关的附件设备,左边第一个12864的小屏幕,中间是DS3231的时钟芯片,精度更高,右侧是陀螺仪。

  • 27.7. I2C电路规范

    •所有I2C设备的SCL连在一起,SDA连在一起

    •设备的SCL和SDA均要配置成开漏输出模式(单片机IO口为弱上拉模式,可以想象成输出口带上拉电阻,输出电流受限,开漏模式则是在上述基础上取消上拉电阻,无输出的时候IO口容易受外界干扰,电压不稳定)

    •SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右(有标准规定,什么速率接什么电阻)

    •开漏输出和上拉电阻的共同作用实现了"线与"的功能,此设计主要是为了解决多机通信互相干扰的问题(可以实现与一个设备通信的时候,其他设备处于"断开"的状态,防止通信干扰)

  • 27.8. I2C时序结构(六块拼图)

    •起始条件:SCL高电平期间,SDA从高电平切换到低电平(S蓝色开始)

    •终止条件:SCL高电平期间,SDA从低电平切换到高电平(P红色结束)

  • 27.9. I2C时序结构_发送一个字节_绿色S byte

    •发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节(下面SDA是连根线麻花的状态的意思是SDA初始情况不确定是0还是1,在SCL高电平(固定SDA的0或1状态),红色框是基本结构,SDA的最终状态看数据发送完毕后的状态,如果是0就是低电平,1就是高电平)

  • 27.10. I2C时序结构_接收一个字节_紫色byte

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

  • 27.11. I2C时序结构

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

    •接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

  • 27.12. I2C数据帧

    •发送一帧数据(S为开始)(S:SLAVE+ADDRESS+W写,1读0写,地址前4位固定,24C02为1010,24C02芯片引脚A2,A1,A0接地0)(RA接收应答,从机应该发0)(S byte数据)(结束)

    •完成任务:(上述完成)向谁发什么

  • 27.13. I2C数据帧

    •接收一帧数据:(S开始)(发送地址+R读)(RA应答0)(数据读)(主机发送应答)。。。(最后可发应答或非应答)(结束)

    •完成任务:(上述完成)向谁收什么

  • 27.14. I2C数据帧

    •先发送再接收数据帧(复合格式)上述两个拼接,取消了一个P

    •完成任务:向谁收指定的什么

  • 27.15. AT24C02数据帧

    •字节写:在WORD ADDRESS处写入数据DATA(发送一帧数据的变形)

    •随机读:读出在WORD ADDRESS处的数据DATA(复合格式)

    •AT24C02的固定地址为1010,可配置地址本开发板上为000

    所以SLAVE ADDRESS+W写为0xA0(1010 0000),SLAVE ADDRESS+R读为0xA1(1010 0001)

  • 27.16. AT24C02数据帧

    •字节写:在"字地址"处写入"数据"

    •随机读:读出在"字地址"处的"数据"

    27.17. 24C02手册介绍24C04地址8位不够(8位256)

28. AT24C02数据存储&秒表

  • 28.1. 程序设计思路

    • 分为2个模块I2C.c 和AT24C02.c
    • I2C.c模块包括6个模块,开始,结束,发送字节,接受字节,发送应答,接受应答
    • AT24C02写2个数据帧,第一个数据帧在地址下写入数据,第二个在某个地址下读出;
    • main中只需要调用AT24C02函数即可;
  • 28.2. Proteus测试环境搭建

    在Proteus中搜索 24C02找到24C02C并插入;

    • 笔者的例子中的接线方式如图
  • 28.3. 按照编程思路,先做好I2C.c模块程序如下:

    #include <REGX52.H>

    sbit I2C_SCL=P2^1;
    sbit I2C_SDA=P2^0;

    /**
    * @brief I2C开始
    * @param 无
    * @retval 无
    */

    void I2C_Start(void)
    {
    I2C_SDA=1; //不清楚SDA 的初始状态,所以都置1
    I2C_SCL=1;
    I2C_SDA=0;
    I2C_SCL=0;
    }

    /**
    * @brief I2C停止
    * @param
    * @retval
    */

    void I2C_Stop(void)
    {
    I2C_SDA=0; //不清楚SDA 的初始状态,所以都置1
    I2C_SCL=1;
    I2C_SDA=1;
    }

    /**
    * @brief I2C发送一个字节
    * @param Byte要发送的字节
    * @retval 无
    */

    void I2C_SendByte(unsigned char Byte)
    {
    unsigned char i;
    for(i=0;i<8;i++)
    {
    I2C_SDA=Byte&(0x80>>i);
    I2C_SCL=1; //复核芯片手册关于高低切换时间的限制;
    I2C_SCL=0; //所以此处无需delay;
    }
    }

    /**
    * @brief I2C接收一个字节
    * @param 无
    * @retval 接收到的一个字节数据
    */

    unsigned char I2C_ReceiveByte()
    {
    unsigned char i,Byte;
    I2C_SDA=1;
    for(i=0;i<8;i++)
    {
    I2C_SCL=1;
    if(I2C_SDA){Byte|=(0x80>>i);}
    I2C_SCL=0;
    }
    return Byte;
    }

    /**
    * @brief I2C发送应答
    * @param AckBit应答位,0为应答,1为非应答
    * @retval 无
    */

    void I2C_SendAck(unsigned char AckBit)
    {
    I2C_SDA=AckBit;
    I2C_SCL=1;
    I2C_SCL=0;
    }

    /**
    * @brief I2C接收应答位
    * @param 无
    * @retval 接收到的应答位,0为应答,1为非应答
    */

    unsigned char I2C_ReceiveAck(void)
    {
    unsigned char AckBit;
    I2C_SDA=1;
    I2C_SCL=1;
    AckBit=I2C_SDA;
    I2C_SCL=0;
    return AckBit;
    }

  • 28.4. I2C.h程序如下:

    #ifndef I2C_H
    #define I2C_H

      void I2C_Start(void);
      void I2C_Stop(void);
      void I2C_SendByte(unsigned char Byte);
      unsigned char I2C_ReceiveByte();
      void I2C_SendAck(unsigned char AckBit);
      unsigned char I2C_ReceiveAck(void);
    

    #endif

  • 28.5. AT24C02.c的程序如下:

    #include <REGX52.h>
    #include "I2C.h"

    #define AT24C02_ADDRESS 0xa0

    /**
    * @brief AT24C02 写入一个字节
    * @param WordAddress字节要写入的地址0~255,Data要写入的数据
    * @retval 无
    */

    void AT24C02_WriteByte(unsigned char WordAddress, Data)
    {

      I2C_Start();
      I2C_SendByte(AT24C02_ADDRESS);
      I2C_ReceiveAck();
      I2C_SendByte(WordAddress);
      I2C_ReceiveAck();
      I2C_SendByte(Data);
      I2C_ReceiveAck();
      I2C_Stop();
    

    }

    /**
    * @brief AT24C02读取一个字节
    * @param WordAddress 要读出的字节的地址
    * @retval 读出的数据
    */

    unsigned char AT24C02_ReadByte(unsigned char WordAddress)
    {
    unsigned char Data;
    I2C_Start();
    I2C_SendByte(AT24C02_ADDRESS);
    I2C_ReceiveAck();
    I2C_SendByte(WordAddress);
    I2C_ReceiveAck();
    I2C_Start();
    I2C_SendByte(AT24C02_ADDRESS|0x01);
    I2C_ReceiveAck();
    Data=I2C_ReceiveByte();
    I2C_SendAck(1);
    I2C_Stop();

      return Data;
    

    }

  • 28.6. AT24C02.h的程序如下:

    #ifndef AT24C02_H
    #define AT24C02_H

    void AT24C02_WriteByte(unsigned char WordAddress, Data);
    unsigned char AT24C02_ReadByte(unsigned char WordAddress);

    #endif

  • 28.7. 主程序如下,在独立按键1按下的时候增加数字(最大65535),独立按键2按下的时候减小数字(最小0),按键3按下的时候,将对应的数字写入AT24C02,按键4按下的时候读出并显示之前写入的数字;

    #include <REGX52.h>
    #include "Four_Key.h"
    #include "LCD1602.h"
    #include "AT24C02.h"
    #include "delay_xms.h"

    unsigned char KeyNum;
    unsigned int Num;

    void main()
    {
    LCD_Init(); //LCD1602初始化
    LCD_ShowString(1,1,"Hello");//不按按键显示Hello
    // AT24C02_WriteByte(0,123);
    // delay_xms(5);
    // AT24C02_WriteByte(1,234);
    // delay_xms(5);
    // AT24C02_WriteByte(2,345);
    // delay_xms(5);
    // Data=AT24C02_ReadByte(1);
    // LCD_ShowNum(2,1,Data,3);
    while(1)
    {
    KeyNum=Four_Key();
    if(KeyNum==1)
    {
    Num++;
    LCD_ShowNum(1,1,Num,5);//数字随按键1按下释放后增加
    }
    if(KeyNum==2)
    {
    Num--;
    LCD_ShowNum(1,1,Num,5);//数字随按键2按下释放后减小
    }
    if(KeyNum==3)
    {
    AT24C02_WriteByte(0,Num%256);
    delay_xms(5);
    AT24C02_WriteByte(1,Num/256);
    delay_xms(5);
    LCD_ShowString(2,1,"Write OK");
    delay_xms(1000);
    LCD_ShowString(2,1," ");//将数字拆分高低8位写入,写入后延时5ms
    }
    if(KeyNum==4)
    {
    Num=AT24C02_ReadByte(0);
    Num|=AT24C02_ReadByte(1)<<8;
    LCD_ShowNum(1,1,Num,5);
    LCD_ShowString(2,1,"Read OK");
    delay_xms(1000); //读出并显示之前写入的数字

      	}			
      		
      }
    

    }

  • 28.8. Proteus仿真和开发板测试无误

  • 28.9. 新建工程AT24C02数据存储&秒表-定时器扫描按键数码管

    • 工程的编程思路:
    • 主函数main直接引用三个模块:定时器模块,独立按键模块和数码管模块
    • 但是数码管和按键扫描需用用到定时器中断,如果独立按键模块和数码管模块直接一起调用定时器模块会出错;
    • 所以需要改变写法;
    • main中写定时器中断,按键模块中写一个函数是中断函数的调用,数码管一样的思路(称为驱动函数或调用函数,让主函数每隔一段时间调用一下)
    • 反之,如果将独立按键模块与数码管显示都放在定时器模块中,则程序耦合性较高,太混乱,不利于代码管理;
    • 之前的独立按键模块是用延时函数,为了消抖在按键按下后延时20ms,松开后依旧延时20ms,如果按键不松手,按键模块在while(1)中一直循环等待,之后考虑修改为每隔20ms对按键进行扫描(同时过滤了抖动),看按键的状态,同时设立一个标志位对比按键按下前后的状态,每隔20ms进行前后标志位对比,如果标志位没有变化说明按键没有按下,否则为按下,按键不卡程序;
  • 28.10. 新建工程,程序需要实现的功能为,按下按键1,松开后秒表开始计时,再按一下1松开,秒表计时停止,按下按键2,计数清零,按下按键3,存储数据到AT24C02,按下按键4,将之前存储的数据显示在数码管上面,新建的工程中的数码管显示的模块程序为:

  • nixietube.c

    #include <REGX52.H>
    #include "delay_xms.h"

    unsigned char Nixie_Buf[9]={0,10,10,10,10,10,10,10,10,};

    unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40};

    void Nixie_SetBuf(unsigned char Location,Number)
    {
    Nixie_Buf[Location]=Number;
    }

    void Nixie_Scan(unsigned char Location,Number)
    {
    P0=0x00;//清零;
    switch(Location)
    {
    case 1:P2_4=1;P2_3=1;P2_2=1;break;
    case 2:P2_4=1;P2_3=1;P2_2=0;break;
    case 3:P2_4=1;P2_3=0;P2_2=1;break;
    case 4:P2_4=1;P2_3=0;P2_2=0;break;
    case 5:P2_4=0;P2_3=1;P2_2=1;break;
    case 6:P2_4=0;P2_3=1;P2_2=0;break;
    case 7:P2_4=0;P2_3=0;P2_2=1;break;
    case 8:P2_4=0;P2_3=0;P2_2=0;break;
    }
    P0=NixieTable[Number];//数码管消影,因为位选-段选-位选-段选,段选数据与位选穿位导致
    }

    void Nixie_Loop(void)
    {
    static unsigned char i;//计次
    Nixie_Scan(i,Nixie_Buf[i]);
    i++;
    if(i>=9){i=1;}

    }

  • nixietube.h的程序为:

    #ifndef NIXIETUBE_H
    #define NIXIETUBE_H

    void Nixie_SetBuf(unsigned char Location,Number);
    void Nixie_Scan(unsigned char Location,Number);
    void Nixie_Loop(void);

    #endif

  • 独立按键的模块程序Key.c程序为:

    #include <REGX52.h>
    #include "delay_xms.h"

    unsigned char Key_KeyNumber;

    unsigned char Key(void)
    {
    unsigned char Temp=0; //加入中间变量,对Key_KeyNumber进行清0
    Temp=Key_KeyNumber;
    Key_KeyNumber=0;
    return Temp;
    }

    unsigned char Key_GetStatus()
    {
    unsigned char KeyNum=0;

      	if(P3_1==0){KeyNum=1;}
      	if(P3_0==0){KeyNum=2;}
      	if(P3_2==0){KeyNum=3;}
      	if(P3_3==0){KeyNum=4;}
      
      return KeyNum;
    

    }

    void Key_Loop(void)
    {
    static unsigned char Now_Status,Last_Status;
    Last_Status=Now_Status;
    Now_Status=Key_GetStatus();
    if(Last_Status==1 && Now_Status==0){Key_KeyNumber=1;}
    if(Last_Status==2 && Now_Status==0){Key_KeyNumber=2;}
    if(Last_Status==3 && Now_Status==0){Key_KeyNumber=3;}
    if(Last_Status==4 && Now_Status==0){Key_KeyNumber=4;}
    }

  • Key.h的程序为:

    #ifndef KEY_H
    #define KEY_H

       unsigned char Key();
       void Key_Loop();
    

    #endif

  • main.c主函数程序为:

    #include <REGX52.h>
    #include "TimeR0.h"
    #include "Key.h"
    #include "nixietube.h"
    #include "delay_xms.h"
    #include "AT24C02.h"

    unsigned char KeyNum;
    unsigned char Min,Sec,mSec;
    unsigned char RunFlag;

    void main()
    {
    TimeR0_Init();
    while(1)
    {
    KeyNum=Key();
    if(KeyNum==1)
    {
    RunFlag=!RunFlag;
    }
    if(KeyNum==2)
    {
    Min=0;
    Sec=0;
    mSec=0;
    }
    if(KeyNum==3)
    {
    AT24C02_WriteByte(0,Min);
    delay_xms(5);
    AT24C02_WriteByte(1,Sec);
    delay_xms(5);
    AT24C02_WriteByte(2,mSec);
    delay_xms(5);
    }
    if(KeyNum==4)
    {
    Min=AT24C02_ReadByte(0);
    Sec=AT24C02_ReadByte(1);
    mSec=AT24C02_ReadByte(2);
    }

      		Nixie_SetBuf(1,Min/10);
      		Nixie_SetBuf(2,Min%10);
      		Nixie_SetBuf(3,11);
      		Nixie_SetBuf(4,Sec/10);
      		Nixie_SetBuf(5,Sec%10);
      		Nixie_SetBuf(6,11);
      		Nixie_SetBuf(7,mSec/10);
      		Nixie_SetBuf(8,mSec%10);
      }
    

    }

    void Sec_Loop(void)
    {
    mSec++;
    if(mSec>=100)
    {
    mSec=0;
    Sec++;
    if(Sec>=60)
    {
    Sec=0;
    Min++;
    if(Min>=60)
    {
    Min=0;
    }
    }
    }
    }

    void TimeR0_Routine() interrupt 1 //中断子函数
    {
    static unsigned int T0Count1,T0Count2,T0Count3; //设置静态子函数用T0Count,防止T0Count丢失
    TL0=0x18; //设置定时初始值
    TH0=0xFC; //设置定时初始值
    T0Count1++;
    if(T0Count1>=1000)
    {
    T0Count1=0;
    Key_Loop();
    }
    T0Count2++;
    if(T0Count2>=2)
    {
    T0Count2=0;
    Nixie_Loop();
    }
    T0Count3++;
    if(T0Count3>=10)
    {
    T0Count3=0;
    Sec_Loop();
    }
    }

  • Proteus仿真

相关推荐
亦枫Leonlew14 分钟前
三维测量与建模笔记 - 3.3 张正友标定法
笔记·相机标定·三维重建·张正友标定法
考试宝17 分钟前
国家宠物美容师职业技能等级评价(高级)理论考试题
经验分享·笔记·职场和发展·学习方法·业界资讯·宠物
黑叶白树2 小时前
简单的签到程序 python笔记
笔记·python
幸运超级加倍~3 小时前
软件设计师-上午题-15 计算机网络(5分)
笔记·计算机网络
DARLING Zero two♡4 小时前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
泰迪智能科技014 小时前
和合共赢 丨 广州探迹科技有限公司与泰迪智能科技战略合作签约仪式圆满结束
科技
芊寻(嵌入式)4 小时前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
Qingniu014 小时前
【青牛科技】应用方案 | RTC实时时钟芯片D8563和D1302
科技·单片机·嵌入式硬件·实时音视频·安防·工控·储能
学术头条5 小时前
AI 的「phone use」竟是这样练成的,清华、智谱团队发布 AutoGLM 技术报告
人工智能·科技·深度学习·语言模型
准橙考典5 小时前
怎么能更好的通过驾考呢?
人工智能·笔记·自动驾驶·汽车·学习方法