JackCin's blog JackCin's blog
首页
  • 页面

    • Html
    • CSS
  • 核心

    • JavaScript基础
    • JavaScript高级
  • 框架

    • Vue
  • jQuery
  • Node
  • Ajax
Linux
  • 操作系统
  • 数据结构与算法
  • 51单片机
  • CC2530
  • 网站
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

JackCin

前端小菜鸡(✪ω✪)
首页
  • 页面

    • Html
    • CSS
  • 核心

    • JavaScript基础
    • JavaScript高级
  • 框架

    • Vue
  • jQuery
  • Node
  • Ajax
Linux
  • 操作系统
  • 数据结构与算法
  • 51单片机
  • CC2530
  • 网站
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 51单片机

    • 51单片机及补充知识
    • LED
    • 独立按键
    • 数码管
    • LCD1602液晶显示器
    • 矩阵按键
    • 定时器
    • 串口
    • LED点阵屏
    • 蜂鸣器
    • AT24C02存储器
      • 一、存储器介绍
        • 1.1 RAM
        • 1.2 ROM
        • 1.3 存储器简化模型
      • 二、AT24C02
        • 2.1 引脚及应用电路
        • 2.2 内部结构框图
      • 三、I2C总线
        • 3.1 I2C电路规范
        • 3.1.1 弱上拉:
        • 3.1.2 I2C
        • 3.1.3 IC的内部结构
        • 3.1.4 抽象理解I2C的通信方式
        • 3.2 I2C的时序结构
        • 3.2.1 起始条件
        • 3.2.2 终止条件
        • 3.2.3 发送一个字节
        • 3.2.4 接收一个字节
        • 3.2.5 发送应答和接收应答
        • 3.3 I2C 数据帧
        • 3.3.1 发送一帧数据
        • 3.3.2 接收一帧数据
        • 3.3.3 复合格式
        • 3.4 AT24C02数据帧
        • 3.4.1 字节写
        • 3.4.2 随机读
        • 3.5 AT24C02数据存储主函数
        • 3.6 秒表(定时器扫描按键数码管)
    • AD与DA
    • DS18B20温度传感器
    • DS1302时钟
    • LCD1602液晶显示屏
    • 红外遥控
    • 呼吸灯与直流电机调速(PWM)
  • 单片机
  • 51单片机
JackCin
2023-09-13
目录

AT24C02存储器

# AT24C02(I2C总线)

# 一、存储器介绍

  • 今天介绍一下AT24C02 (opens new window),首先呢,它是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息,在介绍AT24C02之前,我们先来介绍一下存储器!

    img

先来简单介绍一下RAM(随机存储器)以及ROM(只读存储器)的优缺点吧!

优点 缺点
RAM 储存速度快 掉电丢失
ROM 存储速度慢 掉电不丢失

# 1.1 RAM

  • RAM主要分为SRAM(静态RAM)和DRAM(动态RAM),SRAM主要用于电脑CPU以及我们的单片机CPU;而DRAM主要用在电脑内存条以及手机的运行内存,因为电容器会掉电,所以需要不断进行扫描。
优点 缺点
RAM 储存速度快 掉电丢失
ROM 存储速度慢 掉电不丢失

# 1.2 ROM

  • ROM主要分为Mask ROM(掩膜ROM),PROM(可编程ROM),EPROM(可擦除可编程ROM ),E2PROM (电可擦除可编程ROM ),这四个是一家的,还有Flash(闪存),硬盘、软盘、光盘等,其中Flash目前使用十分广泛,基本上打败了ROM一家。
特点
Mask ROM 只能读
PROM 可以写,但只能一次
EPROM 可以写多次,但要紫外线照射30分钟
E2PROM 可以写多次,并且只要几毫秒即可
Flash 与E2PROM类似,但集成度更高
硬盘、软盘、光盘等 软盘和光盘目前见的比较少了

# 1.3 存储器简化模型

img

img

  • 这里的左边是地址总线,下面是数据总线,两种线之间互相不连接,可看做是数据总线放在了地址总线上面(立体的看!)
  • 使用的原理:首先我们选择地址总线,比如像赋值10000000,相当于打开了第一行,之后选择连接的结点(之前都没有连接上),将其连上,(像上图,我们给地址总线1000 0000 然后连接(3个红点),那么数据总线就为1011 0000)
    • Mask ROM使用的方法是一个二极管(这么做的原因是防止电流经过上面的节点导致数据混乱),
    • PROM使用了两个二极管(一个二极管和保险丝),但是其中一个二极管(保险丝)比较容易击穿,当给高电压的时候,蓝色电容(保险丝熔断)击穿,实现数据写入。

# 二、AT24C02

  • AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
  • 存储介质:E2PROM
  • 通讯接口:I2C总线
  • 容量:256字节
  • img

# 2.1 引脚及应用电路

img

# 2.2 内部结构框图

img

  • 接下来我们来简单的介绍一下内部结构,我们从每个部分进行讲解!
    • 第一个就是我们刚刚介绍的存储器简化模型那样,网状结构
    • 第二个是一个译码器,用于输入地址
    • 第三个是输入输出端,通过Y DEC将数据输出
    • 第四个也是译码器,用来帮助MUX输出数据,然后就直接输出数据
    • 第五个是用来擦除数据用的
    • 第六个是用来设置地址的,里面有个寄存器是用来存储地址的,每写入和读出寄存器自动加一,读出不指定地址,默认拿出寄存器的地址
    • 第七个是开始结束逻辑
    • 第八个是一个地址比较器
    • 第九个是一个控制串行逻辑

# 三、I2C总线

  • I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
  • 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
  • 同步、半双工,带数据应答
  • 通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度

4分钟看懂!I2C通讯协议 最简单的总线通讯!_哔哩哔哩_bilibili (opens new window)

# 3.1 I2C电路规范

  • 所有I2C设备的SCL连在一起,SDA连在一起
  • 设备的SCL和SDA均要配置成开漏输出模式
  • SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
  • 开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题
    • 上拉电阻是将总线给拉成高电平,当连接在总线上的任意一个设备输出低电平时,总线被拉低就是输出了低电平
    • “线与”的意思就是连接在总线上的设备只要有一个输出低电平(0),总线就为低电平(0),只有全部设备都为高阻态时总线才是高电平

(这里因为讲到了开漏输出,所以也说一下单片机的弱上拉模式)

# 3.1.1 弱上拉:

弱上拉

  • 单片机的IO口是一种弱上拉模式,默认是1
  • 如果输入了0,那么开关口闭合,输出口拉到GND上,就输出了0
  • 如果输入了1,那么开关口断开,就输出了1,所以高电平驱动能力是弱的,低电平驱动能力比较强(单片机本来能接受的电压就不高,遇到GND就被拉到0了,所以高电平驱动能力弱)

# 3.1.2 I2C

img

  • 因为SCL和SDA都是开漏输出模式,所以如果我们想要CPU和其中一个IC通信,那只要让它为0,其他为1(为一开漏输出开关断开,出现浮空,所以就不会有影响)
  • 相当于CPU想发0的时候,就拉到0,想发1时就松手,然后上拉电阻拉到1

# 3.1.3 IC的内部结构

img

# 3.1.4 抽象理解I2C的通信方式

img

  • 通信规则:
    • 1、杠子在上方代表1,下方代表0
    • 2、每个人只能拉杆子或者松开手
    • 3、每个人需要地址进行通信
  • 先发送地址,确定要通信的从机,再发送信息

# 3.2 I2C的时序结构

  • ​ 接下来我们来介绍一下六个时序结构,只要集齐了这六个时序结构,就可以召唤数据帧了!😍😍😍

# 3.2.1 起始条件

  • ​ 起始条件:SCL高电平期间,SDA从高电平切换到低电平(相当于告诉大家我要发送信息了)
  • img
void I2C_Start(void)
{
	// 可以理解为初始化,确保SDA一定为高电平
	I2C_SDA=1;
	I2C_SCL=1;
	// 按照时序图可得,先SDA为0,再SCL为0
	I2C_SDA=0;
	I2C_SCL=0;//防止产生多余时序
	
}
1
2
3
4
5
6
7
8
9
10

# 3.2.2 终止条件

  • 终止条件:SCL高电平期间,SDA从低电平切换到高电平(相当于告诉大家我要停止了)

img

void I2C_Stop(void)
{
	I2C_SDA=0;//先写这个,初始化,确保是低电平
	I2C_SCL=1;
	I2C_SDA=1;
}
1
2
3
4
5
6
  • 终止条件是唯一一个最后不用把SCL置为低电平的时序结构

# 3.2.3 发送一个字节

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

img

  • 这里SDA画两条线是因为我们不确定SDA具体的电平
void I2C_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		//SCL低电平期间,主机将数据位依次放到SDA线上
		//之所以不用先将SCL置0,是因为,除了结束条件,其他时序结构最后都会把SCL置0
        
		I2C_SDA=(Byte&0x80>>i);   //老师想说的话一个一个往外蹦,话都传到教室(SDA)里
        //拉高SCL,从机读取数据位
		I2C_SCL=1;  //同学开始听教室里老师的话
        //拉低,以备进入下次循环,重新拉高SCL,让从机读取下一个数据位
		I2C_SCL=0; //同学听完一个
	}
	
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 像上面这种立马置高又置低的情况,是要考虑是否加延迟的,但因为我们单片机比较慢,而转换的速度又很快,所以我们可以不加延迟,但是在后面字节写的时候,因为字节写需要时间,所以执行字节写后需要给延时,让AT24C02内部执行一些操作(视频12-2,12:09)

# 3.2.4 接收一个字节

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

img

unsigned char I2C_ReceiveByte(void)
{
	unsigned char i,Byte = 0x00;
	//主机将SDA释放,从机使用SDA,将数据依次放到SDA线上
	I2C_SDA=1;  //空气里没有老师的话了,同学抓住机会发言,话都传在教室里😭
	for(i=0;i<8;i++)
	{
		//主机要读取数据需要先将SCL置为高电平
		I2C_SCL=1;   //老师开始听教室里同学的发言
		//主机读取数据位
		if(I2C_SDA){Byte|=(0x80>>i)}  //老师听到一个字就存进脑子里一个字😕
		I2C_SCL=0;  //老师听完了一个字
	}
    //读完了8个位,得到数据
	return Byte;  //老师接收到了同学完整的发言
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3.2.5 发送应答和接收应答

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

img

  • 发送应答:如果给应答,SDA就给0,不给应答就给1

    void I2C_SendAck(unsigned char AckBit)
    {
    	I2C_SDA=AckBit;  //老师给出回应,声音在教室里(也有可能不回应🤐)
    	I2C_SCL=1;  //同学开始听
    	I2C_SCL=0;	//同学结束听
    }
    unsigned char I2C_ReceiveAck(void)
    {
    	unsigned char AckBit;
    	I2C_SDA=1;  //老师不说了,教室里安静了,同学可以开始回应
    	
    	I2C_SCL=1;//老师开始听到同学回应
    	AckBit=I2C_SDA;  //老师听到了同学的回应
    	I2C_SCL=0;//老师听完了同学的回应
    	return AckBit; //0就是有听到回应,1就是没有回应
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

# 3.3 I2C 数据帧

  • I2C数据帧其实就是上面六个部分拼合在一起,把数据帧拆分开来看,就比较好理解了。

# 3.3.1 发送一帧数据

img

  • 相当于老师(主机)再讲课,学生(从机)给老师回复

# 3.3.2 接收一帧数据

img

  • 相当于老师(主机)叫学生(从机)回答问题

# 3.3.3 复合格式

  • 先发送再接收数据帧img

  • 像是一个完整的回答过程,老师(主机)提出问题,学生(从机)回答

# 3.4 AT24C02数据帧

  • AT24C02数据帧,其实不止这几个,但我们在这里就简单介绍一下这两种!
  • AT24C02的固定地址为1010,可配置地址本开发板上为000,所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1

# 3.4.1 字节写

  • 字节写:在WORD ADDRESS处写入数据DATA

img

# 3.4.2 随机读

  • 随机读:读出在WORD ADDRESS处的数据DATA
  • img

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

#define AT24C02_ADDRESS_READ 0xA1
#define AT24C02_ADDRESS_WRITE 0xA0

/**
  * @brief  AT24C02写入一个字节
  * @param  WordAddress 要写入字节的地址
  * @param  Data 要写入的数据
  * @retval 无
  */
void AT24C02_WriteByte(unsigned char WordAddress,Date)
{
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS_WRITE);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_SendByte(Date);
	I2C_ReceiveAck();
	I2C_Stop();
	
}

/**
  * @brief  AT24C02读取一个字节
  * @param  WordAddress 要读出字节的地址
  * @retval 读出的数据
  */
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
	unsigned char Date;
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS_WRITE);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS_READ);
	I2C_ReceiveAck();
	Date=I2C_ReceiveByte();
	I2C_SendAck(1);
	I2C_Stop();
	
	return Date;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 3.5 AT24C02数据存储主函数

#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "AT24C02.h"
#include "LCD1602.h"
#include "I2C.h"

unsigned char KeyNum;
unsigned int Num;

void main()
{
	LCD_Init();
	LCD_ShowNum(1,1,Num,5);
	while(1)
	{
		KeyNum=Key();
			if(KeyNum==1) //K1按键,Num自增
			{
				Num++;
				LCD_ShowNum(1,1,Num,5);
			}
			if(KeyNum==2) //K2按键,Num自减
			{
				Num--;
				LCD_ShowNum(1,1,Num,5);
			}
			if(KeyNum==3)  //K3按键,向AT24C02写入数据
			{
				//Num被定义为int,而我们C51的int 是16位,即两个字节
				//所以这里我们需要把Num拆分成高低两位,去存入
				//写入的地址可以是0~255中的任意一个(因为我们定义的是unsigned char)
				AT24C02_WriteByte(0,Num%256);  
				//每次写入后必须给5ms的延时,让AT24C02内部执行一些操作
				Delay(5);
				AT24C02_WriteByte(1,Num/256);
				Delay(5);
				LCD_ShowString(2,1,"Write OK");
				Delay(1000);
				LCD_ShowString(2,1,"        ");
				
			}
			if(KeyNum==4) //K4按键,从AT24C02读取数据
			{
				//读取高低位,整合到一起
				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,"        ");
			
			}
			
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 3.6 秒表(定时器扫描按键数码管)

  • ​ 接下来,我们将会改进之前动态数码管的实现,使用定时器来扫描,然后实现一个具有记忆功能的秒表。

  • 我们使用定时器来扫描按键以及数码管,所以按键以及数码管都需要用到定时器的功能,具体内容如下所示:

img

  • 但我们只有一个中断函数,这样很容易出错,而且不能达到目的,并且代码耦合性过高,所以我们采用另一种方式,如下所示。就是将定时函数放到主函数里面去,再每隔一段时间调用各个部分的函数以达到目的,好了,接下来我们看看代码是如何实现的吧!

img

  • 总体的思路就是,通过定时器和定时中断函数来判断独立按键是否按下,同时不断扫描数码管显示数据,

  • key.c和key.h

#include <REGX52.H>

unsigned char Key_KeyNumber;

/**
  * @brief  获取按键键码
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  */
unsigned char Key(void)
{
	unsigned char Temp=0;
	Temp = Key_KeyNumber;
	// 将Key_KeyNumber置0,因为Key_KeyNumber不会刷新
	// 不清零可能导致后面使用这key的时候没有按键按下但是返回上一次记录的KeyNum,覆盖之前不会变,导致误判按下(实则没有)
	Key_KeyNumber=0;
	return Temp;
}



/**
  * @brief  获取当前按键的状态,无消抖及松手检测
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  */
unsigned char Key_GetState()
{
	unsigned char KeyNumber=0;
	
	if(P3_1==0){KeyNumber=1;}
	if(P3_0==0){KeyNumber=2;}
	if(P3_2==0){KeyNumber=3;}
	if(P3_3==0){KeyNumber=4;}
	
	return KeyNumber;
}


/**
  * @brief  按键驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */

void Key_Loop(void)
{
	static unsigned char NowState,LastState;
	//抖动的延时为20ms
	
	LastState=NowState;
	NowState=Key_GetState();
    //上一次定时中断获取到了keyNumber,新一次为0,说明现在正处于按键松开的抖动阶段
	if(LastState==1&&NowState==0)
	{
		Key_KeyNumber=1;
	}
	if(LastState==2&&NowState==0)
	{
		Key_KeyNumber=2;
	}
	if(LastState==3&&NowState==0)
	{
		Key_KeyNumber=3;
	}
	if(LastState==4&&NowState==0)
	{
		Key_KeyNumber=4;
	}
}

------------------------------------------
Key.h
------------------------------------------
#ifndef __KEY_H__
#define __KEY_H__

unsigned char Key(void);
void Key_Loop(void);

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
  • Nixie.c和Nixie.h
#include <REGX52.H>
#include "Delay.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};
//0,1,2,3,4,5,6,7,8,9, ,-

/**
  * @brief  设置显示缓存区
  * @param  Location 要设置的位置,范围:1~8
  * @param  Number 要设置的数字,范围:段码表索引范围
  * @retval 无
  */
void Nixie_SetBuf(unsigned char Location,Number)
{
	Nixie_Buf[Location]=Number;
}


/**
  * @brief  数码管扫描显示
  * @param  Location 要显示的位置,范围:1~8
  * @param  Number 要显示的数字,范围:段码表索引范围
  * @retval 无
  */
void Nixie_Scan(unsigned char Location,Number)
{
	P0=0x00;				//段码清0,消影
	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];	//段码输出
	
}



/**
  * @brief  数码管驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */


void Nixie_Loop(void)
{	//在数码管显示
	static unsigned char i=1;
	Nixie_Scan(i,Nixie_Buf[i]);
	i++;
	if(i>=9){i=1;}

}

______________________________________________
    Nixie.h
______________________________________________
#ifndef __NIXIE_H__
#define __NIXIE_H__

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

#endif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
  • main.c
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "AT24C02.h"
#include "Timer0.h"
#include "Nixie.h"

unsigned char KeyNum;  //获取独立按键键码
unsigned char Min,Sec,MiniSec;   //秒表时间
unsigned char RunFlag;    //秒表开关运动暂停标识位


void main()
{
	Timer0_Init();  //初始化定时器
	while(1)
	{
		KeyNum=Key();    
		if(KeyNum==1)  //K1按键按下
		{
			RunFlag=!RunFlag;	//启动标志位翻转
		}
		if(KeyNum==2)   //K2按键按下
		{
			Min=0;		//时间清0
			Sec=0;
			MiniSec=0;
		}
		if(KeyNum==3)			//K3按键按下
		{
			AT24C02_WriteByte(0,Min);	//将分秒写入AT24C02
			Delay(5);
			AT24C02_WriteByte(1,Sec);
			Delay(5);
			AT24C02_WriteByte(2,MiniSec);
			Delay(5);
		}
			if(KeyNum==4)			//K4按键按下
		{
			Min=AT24C02_ReadByte(0);	//读出AT24C02数据
			Sec=AT24C02_ReadByte(1);
			MiniSec=AT24C02_ReadByte(2);
 //因为如果我们秒表没有暂停按下独立按键4,会读出我们存储的数据,但是会在这个数据的基础上继续运行,所以下面这个语句,是为了在出现这种情况时,停止
			if(RunFlag){RunFlag=0;}
		}
		
		//数码管是一位一位显示的,所以要将数字拆成高低两位
		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,MiniSec/10);
		Nixie_SetBuf(8,MiniSec%10);
		
	}
}



/**
  * @brief  秒表驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Sec_Loop(void)
{
	if(RunFlag)
	{
		MiniSec++;
		if(MiniSec>=100)
		{
			MiniSec=0;
			Sec++;
			if(Sec>=60)
			{
				Sec=0;
				Min++;
				if(Min>=60)
				{
					Min=0;
				}
			}
		}
	}
}



void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count1,T0Count2,T0Count3;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count1++;
	if(T0Count1>=20)   //20是因为抖动大概20ms
	{
		T0Count1=0;
		Key_Loop();	//20ms调用一次按键驱动函数
	}
	T0Count2++;
	if(T0Count2>2)  //给的越大,闪的越明显
	{
		T0Count2=0;
		Nixie_Loop();//2ms调用一次数码管驱动函数
	}
	T0Count3++;
	if(T0Count3>=10)    //1s等于1000ms
	{
		T0Count3=0;
		Sec_Loop();	//10ms调用一次数秒表驱动函数
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
编辑 (opens new window)
上次更新: 2023/09/13, 12:29:52
蜂鸣器
AD与DA

← 蜂鸣器 AD与DA→

最近更新
01
51单片机及补充知识
09-13
02
独立按键
09-13
03
LCD1602液晶显示器
09-13
更多文章>
Theme by Vdoing | Copyright © 2019-2023 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式