写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!
本文写于:2025.03.20
51单片机学习——第28节: [12-2] AT24C02数据存储&秒表(定时器扫描按键数码管)
前言
本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始51单片机的学习之路。
欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习51单片机了,就跟着B站上的江协科技开始学习了.
在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!
开发板说明
本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。不再另外购买视频中的普中开发板了。
原理图如下
视频中的都用这个开发板来实现,如果有资源就利用起来。
仔细看了看:开发板的晶振为:11.0592Mhz;12Mhz晶振是用来给CH340G芯片外置晶振;
下图是实物图
引用
51单片机入门教程-2020版 程序全程纯手打 从零开始入门
还参考了下图中的书籍:
手把手教你学51单片机(C语言版)
STC89C52手册
解答和科普
一、编写AT24C02的读写函数
1.I2C的拼图
在main函数中调出来,AT.C就不用调出来I2C了。
位声明:
sbit I2C_SCL =P1^7;
sbit I2C_SDA=P1^2;
1.1起始拼图
void I2C_Start(void)
{
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
1.2终止拼图
void I2C_Stop(void)
{
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
1.3发送一个字节
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; //看时序是否满足能够承受的时间是多少
}
}
1.4接受一个字节
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,Byte=0x00;
I2C_SDA=1; //释放总线
for(i=0;i<8;i++)
{
I2C_SCL=1;
if(I2C_SDA){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
1.5主机的发送应答
这里相当于主机写:
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
1.6 从机的接收应答
这里是从机写,反过来就是主机读
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit=0;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA; //高电平读取
I2C_SCL=0;
return AckBit;
}
2.AT24C02 的数据帧
先宏定义一个地址:W
#define AT24C02_ADDRESS 0xA0
地址:R
AT24C02_ADDRESS|0x01
2.1字节写
#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();
}
2.2 随机读
/**
* @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;
}
3、写入数据测试
#include <REGX52.H>
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"
#include "Delay.H"
unsigned char Data;
void main(void)
{
LCD_Init();
LCD_ShowString(1,1,"HELLO");
AT24C02_WriteByte(1,66);
Delay(5);
Data=AT24C02_ReadByte(1);
LCD_ShowNum(2,1,Data,2);
while(1)
{
}
}
测试现象
先写后读,掉电不丢失。
AT24C02按键控制
#include <REGX52.H>
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"
#include "Delay.H"
#include "Init.h"
unsigned char KeyNum;
unsigned int Num;
void main(void)
{
LCD_Init();
LCD_ShowNum(1,1,Num,5);
Nixietube_OFF();
DianZhengGuan();
while(1)
{
KeyNum=Key();
if(KeyNum==1)
{
Num++;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==2)
{
Num--;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==3)
{
AT24C02_WriteByte(0,Num%256);
Delay(5);
AT24C02_WriteByte(1,Num/256);
Delay(5);
LCD_ShowString(2,1,"Write OK");
Delay(1000);
LCD_ShowString(2,1," ");
}
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(1000);
LCD_ShowString(2,1," ");
}
}
}
实现现象
AT24C02存储数据(按键控制)
二、定时器扫描
之前扫描按钮和数码管都是需要通过CPU主循环进行的,使用这种方式有着很大的弊端,(1)首先是会占用CPU的资源,在扫描按钮和数码管时会浪费一定的时间,(2)其次是我们的按钮检测是通过松手检测进行的,当我们按下按钮还没有松开时,程序即会进入长时间的while循环中,无法完成其他的操作,必须要松手后才能释放CPU资源完成其他的功能。因此使用定时器代替CPU进行扫描和检测是非常必要的。
在定时器中断函数中,调用Key和Nixie函数中的函数,这样就实现了定时器用来扫描多个部分。
1、定时器扫描按键
用定时器扫描的好处是:不用在主函数while等待按键,而是用定时器扫描获取按键值,主函数仍然可以做事情。
1.1 获取按键键码值(第几个按键按下)
unsigned char Key_GetState() //按键按下获取键码值
{
unsigned char KeyNumber=0;
if(P3_0==0){KeyNumber=1;}
if(P3_1==0){KeyNumber=2;}
if(P3_2==0){KeyNumber=3;}
if(P3_3==0){KeyNumber=4;}
if(P3_4==0){KeyNumber=5;}
if(P3_5==0){KeyNumber=6;}
if(P3_6==0){KeyNumber=7;}
if(P3_7==0){KeyNumber=8;}
return KeyNumber;
}
1.2 检测松手(在上一状态为按下的同时,现状态等于1,说明在上个函数值,没有按键按下,就是松手了也就是对应的按键为高电平了)
void Key_Loop(void)
{
static unsigned char NowState,LastState; //记录上一刻的数据
LastState=NowState; //未更新的NowState就是上次的状态值
NowState= Key_GetState(); //更新现在获取按键按下的按键值
if(LastState==1&&NowState==0) //上个状态不断获取1,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=1;
}
if(LastState==2&&NowState==0) //上个状态不断获取2,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=2;
}
if(LastState==3&&NowState==0) //上个状态不断获取3,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=3;
}
if(LastState==4&&NowState==0) //上个状态不断获取4,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=4;
}
if(LastState==5&&NowState==0) //上个状态不断获取5,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=5;
}
if(LastState==6&&NowState==0) //上个状态不断获取6,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=6;
}
if(LastState==7&&NowState==0) //上个状态不断获取7,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=7;
}
if(LastState==8&&NowState==0) //上个状态不断获取8,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=8;
} //KeyNumber 不会清零
}
1.3 返回获取的按键值
unsigned char Key(void) //先把这个值清零再返回来
{
unsigned char Temp;
Temp=Key_KeyNumber;
Key_KeyNumber=0;
return Temp;
}
1.4 调用函数测试
实现定时器扫描按键,不用在主函数使用了。
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
unsigned char KeyNum,Temp;
void main()
{
Timer0_Init();
while(1)
{
KeyNum=Key(); //这个函数不会被卡主
if(KeyNum) Temp=KeyNum;
Nixie(1,Temp);
}
}
void Timer0_Routine() interrupt 1 //跳转到这里,触发中断
{
static unsigned int T0Count1; //Tcount为计数值 static只有本函数使用
TL0 = 0x66; //重新设置定时初值
TH0 = 0xFC; //重新设置定时初值
T0Count1++;
if(T0Count1>=20)
{
T0Count1=0;
Key_Loop();
}
}
while(1)
{
KeyNum=Key(); //这个函数不会被卡主
if(KeyNum) Temp=KeyNum;
Nixie(1,Temp);
}
这里说明是中断函数已经对按键扫描出来了,只是显示出来。主程序并没有对按键进行扫描。
2.定时器对数码管扫描
2.1直接由定时器中断调用扫描
主程序根本没有调用,定时器每2ms扫描一下数码管。
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
unsigned char KeyNum,Temp;
void main()
{
Timer0_Init();
while(1)
{
KeyNum=Key(); //这个函数不会被卡主
}
}
void Timer0_Routine() interrupt 1 //跳转到这里,触发中断
{
static unsigned int T0Count1,T0Count2; //Tcount为计数值 static只有本函数使用
TL0 = 0x66; //重新设置定时初值
TH0 = 0xFC; //重新设置定时初值
T0Count1++;
if(T0Count1>=20)
{
T0Count1=0;
Key_Loop();
}
T0Count2++;
if(T0Count2>=2)
{
T0Count2=0;
Nixie_Loop();
}
}
2.2 数码管封装
Nixie_Buf[]缓冲区存放要显示的8为数据。
unsigned char Nixie_Buf[9];
void Nixie_Loop(void)
{
static unsigned char i;
Nixie_Scan(i,Nixie_Buf[i]);
i++;
if(i>=9){i=1;}
}
2.3数码管函数
#include <REGX52.H>
#include "Delay.h"
sbit wei= P2^1;
sbit duan=P2^0;
unsigned char NixieDuanTable[]= {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};
unsigned char NixieWeiTable[]={0xFF,0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F};
unsigned char Nixie_Buf[9]={0,17,17,17,17,17,17,17,17}; //存放要显示的数字数据
/**
* @brief 设置数码管缓存区的数据,如设置第一位显示6,只需调用输入参数1,6
* @param Location数码管的位置,范围1-8;Number:要显示的数字
* @retval 无
*/
void Nixie_SetBuf(unsigned char Location,Number)
{
Nixie_Buf[Location]=Number;
}
/**
* @brief 数码管扫描函数
* @param WeiNum:位置范围1-8;DuanNum段码:范围1-F
* @retval 无
*/
void Nixie_Scan(unsigned char WeiNum, DuanNum)
{
duan=1;
P0=NixieDuanTable[DuanNum];
duan=0;
wei=1;
P0=NixieWeiTable[WeiNum];
wei=0;
Delay(1);
}
/**
* @brief 数码管循环扫描函数
* @param 无
* @retval 无
*/
void Nixie_Loop(void)
{
static unsigned char i=1;
Nixie_Scan(i,Nixie_Buf[i]);
i++;
if(i>=9){i=1;}
}
2.4实验代码
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "Init.h"
unsigned char KeyNum,Temp;
void main()
{
Timer0_Init();
DianZhengGuan();
while(1)
{
KeyNum=Key(); //这个函数不会被卡主
if(KeyNum)
{
Nixie_SetBuf(1,KeyNum);
Nixie_SetBuf(2,KeyNum);
Nixie_SetBuf(3,KeyNum);
}
}
}
void Timer0_Routine() interrupt 1 //跳转到这里,触发中断
{
static unsigned int T0Count1,T0Count2; //Tcount为计数值 static只有本函数使用
TL0 = 0x66; //重新设置定时初值
TH0 = 0xFC; //重新设置定时初值
T0Count1++;
if(T0Count1>=20)
{
T0Count1=0;
Key_Loop();
}
T0Count2++;
if(T0Count2>=2)
{
T0Count2=0;
Nixie_Loop();
}
}
实验现象
主循环再Delay,不会再影响按键和数码管的扫描。
思路就是在模块中定义一个函数,并且在主函数里面每隔一段时间调用一下,同时做到了定时器复用的功能。可以完成多个任务,在模块中的不要写耗时间的函数,尤其是Delay,因为每间隔1ms进入一次,你一旦延迟,整个定时器都会乱套。每隔1ms,延迟2ms。
三、秒表
在二的基础上,我们完成秒表的功能,同样利用定时器,定时10ms对应的MinSec+1,就是计时单位为10ms,也是定时器完成。
1、定义变量,分,秒,Min秒(100进位)
unsigned char Min,Sec,MinSec;
2、定时器扫描
T0Count3++;
if(T0Count3>=10)
{
T0Count3=0;
Sec_Loop();
}
3、功能实现
3.1开始设置计数逻辑,并显示在主函数
void Sec_Loop(void)
{
MinSec++;
if(MinSec>=100)
{
MinSec=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
}
}
}
}
while(1)
{
KeyNum=Key(); //这个函数不会被卡主
Nixie_SetBuf(1,Min/10);
Nixie_SetBuf(2,Min%10);
Nixie_SetBuf(3,17);
Nixie_SetBuf(4,Sec/10);
Nixie_SetBuf(5,Sec%10);
Nixie_SetBuf(6,17);
Nixie_SetBuf(7, MinSec/10);
Nixie_SetBuf(8,MinSec%10);
}
4、设置启动还是暂停位
4.1设置启动标志位
unsigned char RunFlaght; //控制启动暂停标志位
4.1 主函数通过按键控制启动标志位
KeyNum=Key(); //这个函数不会被卡主
if(KeyNum==1)
{
RunFlaght=!RunFlaght;
}
4.3启动暂停的功能
在编制为为1时启动,执行计数功能
oid Sec_Loop(void)
{
if(RunFlaght) //如果非0,就执行
{
MinSec++;
if(MinSec>=100)
{
MinSec=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
}
}
}
}
}
4.4 清零位设置
if(KeyNum==2)
{
Min=0;
Sec=0;
MinSec=0;
}
5、总体代码
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "Init.h"
unsigned char KeyNum;
unsigned char Min,Sec,MinSec;
unsigned char RunFlaght; //控制启动暂停
void main()
{
Timer0_Init();
DianZhengGuan();
while(1)
{
KeyNum=Key(); //这个函数不会被卡主
if(KeyNum==1)
{
RunFlaght=!RunFlaght;
}
if(KeyNum==2)
{
Min=0;
Sec=0;
MinSec=0;
}
Nixie_SetBuf(1,Min/10);
Nixie_SetBuf(2,Min%10);
Nixie_SetBuf(3,17);
Nixie_SetBuf(4,Sec/10);
Nixie_SetBuf(5,Sec%10);
Nixie_SetBuf(6,17);
Nixie_SetBuf(7, MinSec/10);
Nixie_SetBuf(8,MinSec%10);
}
}
void Sec_Loop(void)
{
if(RunFlaght) //如果非0,就执行
{
MinSec++;
if(MinSec>=100)
{
MinSec=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
}
}
}
}
}
void Timer0_Routine() interrupt 1 //跳转到这里,触发中断
{
static unsigned int T0Count1,T0Count2,T0Count3; //Tcount为计数值 static只有本函数使用
TL0 = 0x66; //重新设置定时初值
TH0 = 0xFC; //重新设置定时初值
T0Count1++;
if(T0Count1>=20)
{
T0Count1=0;
Key_Loop();
}
T0Count2++;
if(T0Count2>=1)
{
T0Count2=0;
Nixie_Loop();
}
T0Count3++;
if(T0Count3>=10)
{
T0Count3=0;
Sec_Loop();
}
}
实验现象:
时钟秒表暂停清零
四、运用AT24C02
1、main`
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "Init.h"
#include "AT24C02.h"
unsigned char KeyNum;
unsigned char Min,Sec,MinSec;
unsigned char RunFlaght; //控制启动暂停
void main()
{
Timer0_Init();
while(1)
{
KeyNum=Key(); //这个函数不会被卡主
if(KeyNum==1)
{
RunFlaght=!RunFlaght;
}
if(KeyNum==2)
{
Min=0;
Sec=0;
MinSec=0;
}
if(KeyNum==3) //K3按键按下
{
AT24C02_WriteByte(0,Min); //将分秒写入AT24C02
Delay(5);
AT24C02_WriteByte(1,Sec);
Delay(5);
AT24C02_WriteByte(2,MinSec);
Delay(5);
}
if(KeyNum==4)
{
Min=AT24C02_ReadByte(0);
Sec=AT24C02_ReadByte(1);
MinSec=AT24C02_ReadByte(2);
}
Nixie_SetBuf(1,Min/10);
Nixie_SetBuf(2,Min%10);
Nixie_SetBuf(3,17);
Nixie_SetBuf(4,Sec/10);
Nixie_SetBuf(5,Sec%10);
Nixie_SetBuf(6,17);
Nixie_SetBuf(7, MinSec/10);
Nixie_SetBuf(8,MinSec%10);
}
}
void Sec_Loop(void)
{
if(RunFlaght) //如果非0,就执行
{
MinSec++;
if(MinSec>=100)
{
MinSec=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
}
}
}
}
}
void Timer0_Routine() interrupt 1 //跳转到这里,触发中断
{
static unsigned int T0Count1,T0Count2,T0Count3; //Tcount为计数值 static只有本函数使用
TL0 = 0x66; //重新设置定时初值
TH0 = 0xFC; //重新设置定时初值
T0Count1++;
if(T0Count1>=20)
{
T0Count1=0;
Key_Loop();
}
T0Count2++;
if(T0Count2>=1)
{
T0Count2=0;
Nixie_Loop();
}
T0Count3++;
if(T0Count3>=10)
{
T0Count3=0;
Sec_Loop();
}
}
2、Delay
#include <REGX52.H>
void Delay(unsigned int xms) //@11.0592MHz
{
while(xms--)
{
unsigned char i, j;
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
#ifndef __DELAY_H
#define __DELAY_H
void Delay(unsigned int xms);
#endif
3、Nixie
#include <REGX52.H>
#include "Delay.h"
sbit wei= P2^1;
sbit duan=P2^0;
unsigned char code NixieDuanTable[]= {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00,0x40};
unsigned char code NixieWeiTable[]={0xFF,0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F};
unsigned char Nixie_Buf[9]={0,17,17,17,17,17,17,17,17}; //存放要显示的数字数据
/**
* @brief 设置数码管缓存区的数据,如设置第一位显示6,只需调用输入参数1,6
* @param Location数码管的位置,范围1-8;Number:要显示的数字
* @retval 无
*/
void Nixie_SetBuf(unsigned char Location,Number)
{
Nixie_Buf[Location]=Number;
}
/**
* @brief 数码管扫描函数
* @param WeiNum:位置范围1-8;DuanNum段码:范围1-F
* @retval 无
*/
void Nixie_Scan(unsigned char WeiNum, DuanNum)
{
duan=1;
P0=NixieDuanTable[DuanNum];
duan=0;
wei=1;
P0=NixieWeiTable[WeiNum];
wei=0;
}
/**
* @brief 数码管循环扫描函数
* @param 无
* @retval 无
*/
void Nixie_Loop(void)
{
static unsigned char i=1;
Nixie_Scan(i,Nixie_Buf[i]);
i++;
if(i>=9){i=1;}
}
#ifndef __NIXIE_H
#define __NIXIE_H
void Nixie_Scan(unsigned char WeiNum, DuanNum);
void Nixie_Loop(void);
void Nixie_SetBuf(unsigned char Location,Number);
#endif
4、Timer0
#include <REGX52.H>
void Timer0_Init(void) //1毫秒@11.0592MHz
{
// TMOD不能复制,如果同时使用定时器1和0,它会覆盖淹没,
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
#ifndef __TIMER0_H
#define __TIMER0_H
void Timer0_Init(void) ;
#endif
5、Key
#include <REGX52.H>
#include "Delay.h"
unsigned char Key_KeyNumber;
unsigned char Key(void) //先把这个值清零再返回来
{
unsigned char Temp;
Temp=Key_KeyNumber;
Key_KeyNumber=0;
return Temp;
}
unsigned char Key_GetState() //按键按下获取键码值
{
unsigned char KeyNumber=0;
if(P3_0==0){KeyNumber=1;}
if(P3_1==0){KeyNumber=2;}
if(P3_2==0){KeyNumber=3;}
if(P3_3==0){KeyNumber=4;}
if(P3_4==0){KeyNumber=5;}
if(P3_5==0){KeyNumber=6;}
if(P3_6==0){KeyNumber=7;}
if(P3_7==0){KeyNumber=8;}
return KeyNumber;
}
void Key_Loop(void)
{
static unsigned char NowState,LastState;
LastState=NowState; //未更新的NowState就是上次的状态值
NowState= Key_GetState(); //更新现在获取按键按下的按键值
if(LastState==1&&NowState==0) //上个状态不断获取1,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=1;
}
if(LastState==2&&NowState==0) //上个状态不断获取2,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=2;
}
if(LastState==3&&NowState==0) //上个状态不断获取3,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=3;
}
if(LastState==4&&NowState==0) //上个状态不断获取4,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=4;
}
if(LastState==5&&NowState==0) //上个状态不断获取5,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=5;
}
if(LastState==6&&NowState==0) //上个状态不断获取6,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=6;
}
if(LastState==7&&NowState==0) //上个状态不断获取7,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=7;
}
if(LastState==8&&NowState==0) //上个状态不断获取8,说明按键1按下,现在状态是0,说明松手了,设置标志位
{
Key_KeyNumber=8;
} //KeyNumber 不会清零
}
#ifndef __KEY_H
#define __KEY_H
unsigned char Key();
void Key_Loop(void);
#endif
6、I2C
#include <REGX52.H>
sbit I2C_SCL =P1^7;
sbit I2C_SDA=P1^2;
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void I2C_Start(void)
{
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void I2C_Stop(void)
{
I2C_SDA=0; //确实为低电平
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; //看时序是否满足能够承受的时间是多少
}
}
/**
* @brief I2C接收一个字节
* @param 无
* @retval 接收到的一个字节
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,Byte=0x00;
I2C_SDA=1; //释放SDA
for(i=0;i<8;i++)
{
I2C_SCL=1;
if(I2C_SDA){Byte|=(0x80>>i);} //置为为1,只有选中位置1,因为任何数与0相与不变,所以这个是置为选中的位,其他位置不变
I2C_SCL=0;
}
return Byte;
}
/**
* @brief I2C发送应答位
* @param AckBit 应答位 0为应答;1为非应答
* @retval 无
*/
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit; //主机发送应答放在SDA线上
I2C_SCL=1; //高电平写入
I2C_SCL=0;
}
/**
* @brief I2C接收应答位
* @param 无
* @retval 接收到的应答位
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit=0;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA; //高电平读取SDA(从机的应答位)
I2C_SCL=0;
return AckBit;
}
#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) ;
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck(void);
#endif
7、AT24C02
#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;
}
#ifndef __AT24C02_H__
#define __AT24C02_H__
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);
#endif
0、Init
#include <REGX52.H>
sbit dianzheng=P2^2;
sbit wei=P2^1;
void DianZhengGuan()
{
dianzheng=0;
}
void DianZhengKai()
{
dianzheng=1;
}
//数码管位关断
void Nixie_OFF(void)
{
wei=1;
P0=0xFF;
wei=0;
}
#ifndef __INIT_H
#define __INIT_H
void DianZhengGuan();
void DianZhengKai();
void Nixie_OFF();
#endif
实验现象:
定时器扫描秒表 掉电不丢失
问题
1、今天我花了好几个小时用来解决问题,因为出错了,我急的不行,最后发现了问题是我在扫描数码管的时候没有把本身的Delay(1)删除,就相当于没1ms,延迟1ms,这种情况一定要避免,整个定时器都乱套了,我说怎么不对呀。
void Nixie_Scan(unsigned char WeiNum, DuanNum)
{
duan=1;
P0=NixieDuanTable[DuanNum];
duan=0;
wei=1;
P0=NixieWeiTable[WeiNum];
wei=0;
Delay(1);
}
2、为了搞定这个问题,我想着用示波器处理一下波形,看看I2C哪里有问题,结果我发现我根本不会,怎么触发都不会,整个流程都没搞懂!!
真的感觉我好笨呀,这几天全在看着这I2C.
总结
通过本次课学会了I2C的协议,然后又编写了AT24C02的数据帧格式。
1、先介绍了六个拼图的时序软件实现
2、拼起来完成整个时序的完成还有AT24C02的运用
3、学会了定时器扫描按键和数码管
4、同时完成了定时器扫描和I2C通信和AT24C02的掉点不丢失
示波器完全不会用呀,我一定得学会!