蓝桥杯单片机组第八届预赛电子钟 解题感悟与细节记录

本文详述了参与蓝桥杯单片机竞赛过程中,实现电子钟、温度显示及闹钟设置的完整过程。作者分享了在DS1302实时时钟、DS18B20温度传感器、数码管显示等方面的代码实现细节,包括延时函数调整、按键逻辑设计和闹钟提醒功能。同时,指出了编程中遇到的问题和解决方案,如数码管消影、温度显示亮度等,并提出了二次优化的目标。
摘要由CSDN通过智能技术生成

1 解题背景

笔者耗时两周大致完成对蓝桥杯单片机竞赛平台CT107D常用模块的熟悉,考虑到返校后的课程压力,笔者选择趁热打铁,立刻开始针对赛题的训练。

从什么题目入手呢?一位很厉害的学长建议我选择第八届预赛的电子钟题目开始,如下图1.1,1.2。

图1.1
图1.2

这道题目初看十分常规,但是真正着手进行实现之后发现有一些很值得注意的地方以及二刷的时候应该专注的方向。

耗时六个小时(这速度,不禁对马上到来的省赛感到担忧T_T)后,基本实现了题目要求的所有功能,还是比较有成就感滴 ^ _ ^ 哈哈哈……(虽然代码行数多到爆啊喂!!!二刷看能不能精简到500行左右!)

后文将一个一个复盘注解代码中所有的模块。

2 代码呈现与复盘

2.1 头文件引入

#include<reg52.h>
#include<onewire.h>
#include<ds1302.h>

所需要的驱动文件竞赛的时候会给出,记得将所需要的.c && .h文件复制粘贴到工程文件夹中,以及在main.c中记得引入!!!

还需要特别注意的是,官方文件中声明:

1-本文件夹中提供的驱动代码供参赛选手完成程序设计参考之用。
2-选手可以自行编写相关代码或以该代码为基础,根据试题中的时钟频率要求,调整延时间隔。
3-提供驱动代码的测试环境:IAP15F2K61S2单片机 @12MHz。

但是比赛用的板子的频率是1MHz的!!!很坑,我们要把onewire.c文件中的延时函数的延时功能增大12倍左右才能正常读取环境温度(否则温度读取速度过快,导致一直显示同一个数字),如下 :

2.1.1

另外,官方不知道为什么在onewire.h文件中声明了一个函数,我们需要手动添加在mian.c中读取温度用到的三个函数,如下:

2.1.2

2.2 变量声明

sbit S4 = P3^3;
sbit S5 = P3^2;
sbit S6 = P3^1;
sbit S7 = P3^0;

sbit L1 = P0^0;

unsigned char code SMGDM[13] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xbf,0xff,0xc6};  //0~9,-,全熄和C的数码管段码
unsigned char code WRITE_DS1302_ADDRESS[7] = {0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};  //DS1302读、写地址存储数组,0写1读,0相当于“暂停”键,只有停下来才可以写
unsigned char code READ_DS1302_ADDRESS[7] = {0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};
unsigned char TIME[7] = {0x50,0x59,0x23,0x04,0x02,0x05,0x22}; //设定实时时钟初始时间
unsigned char PROTECT_P0;  //寄存P0的值,保护亮灯的P0值不受数码管动态显示的影响
unsigned char i;  //for循环中介变量
unsigned char cnt = 0;  //计时器计时变量
unsigned int temperature;  //温度存储变量  //!!!w
unsigned char ALARM_TIME[3] = {0x00,0x00,0x00};  //存放闹钟时间的数组
unsigned char ALARM_flag = 0;  //闹钟提醒标志变量
unsigned char temp;  //时钟闹钟显示闪烁变量

首先定义用到的位变量:独立按键和L1
其次定义用到的全局变量。

将所有变量都写在前面好像是一种传统,我个人习惯定义在各自的模块里。不过这样一列颇有种点兵的霸气感哈哈哈~

2.3 底层代码

//================HC138译码器选择使能========================
void HC138(unsigned char channel)
{
	switch(channel)
	{
		case 4:P2 = (P2 & 0x1f) | 0x80;break;
		case 5:P2 = (P2 & 0x1f) | 0xa0;break;
		case 6:P2 = (P2 & 0x1f) | 0xc0;break;
		case 7:P2 = (P2 & 0x1f) | 0xe0;break;
		case 0:P2 = (P2 & 0x1f) | 0x00;break;
		default:break;
	}
}
//==============================================================
//========================系统初始化============================
void INITSYSTEM()
{
	HC138(5);P0 = 0x00;  //关闭蜂鸣器、继电器等外设
	HC138(4);P0 = 0xff;  //关闭LED灯
	HC138(0);  //养成出入随手关门的好习惯hhh
}
//==============================================================
void delay(unsigned int t)  //延时函数
{
	while(t--);
}
//=====================定时器相关函数===========================
void INITTIMER0()
{
	TMOD = 0x01;
	TH0 = (65535 - 20000) / 256;
	TL0 = (65535 - 20000) % 256; 
	
	ET0 = 1;
	EA = 1;  //此处TR0并不应该打开,后续需要精确定时时再开启定时器
}

void SERVICETIMER0() interrupt 1
{
	TH0 = (65535 - 20000) / 256;  //01模式下记得手动重装初值!
	TL0 = (65535 - 20000) % 256;
	cnt++;  //每20ms加1
}
//==============================================================

此处没什么值得提的地方。就一个词:默写!(顺便一提,stc-isp上有共阴数码管的段码,可以选择在那里粘贴了再取反。)

2.4 DS1302实时时钟模块相关函数

void INIT_DS1302()
{
	Write_Ds1302_Byte(0x8e,0x00);
	for(i = 0;i < 7;i++)
	{
		Write_Ds1302_Byte( WRITE_DS1302_ADDRESS[i],TIME[i] );
	}
	Write_Ds1302_Byte(0x8e,0x80);
}

void READ_DS1302()
{
	for(i = 0;i < 7;i++)
	{
		TIME[i] = Read_Ds1302_Byte( READ_DS1302_ADDRESS[i] ); 
	}
}
  • INIT_DS1302():首先是对实时时钟的初始化操作:将题目给定的初始化时间写入DS1302中,要注意的是记得用0x00打开写入功能,写完后一定要用0x80(即首位1)关闭!记忆方法:0是暂停,停了就可以写;1是运行,运行的时候写不了。

  • READ_DS1302() :用TIME[ ]数组来实时读取实时时钟的数据以显示时间,后续在调整闹钟时的松手检测while循环中要记得加上这句,否则显示时间的数据不会更新!

2.5 DS18B20温度模块相关函数

void READDS18B20()  //。。。。。。大坑
{
	unsigned char LSB,MSB;
	
	init_ds18b20();
	Write_DS18B20(0xcc);  //跳过ROM
	Write_DS18B20(0x44);  //转换温度
	
	delay(100);  //延时等待温度数据的更新
	
	init_ds18b20();
	Write_DS18B20(0xcc);
	Write_DS18B20(0xbe);  //读取温度
	
	LSB = Read_DS18B20();
	MSB = Read_DS18B20();
	
	//正温度
	temperature = MSB * 256 + LSB;  //?256?
	temperature = temperature >> 4;
}

这里命名的时候要注意,官方给的驱动中读取温度的函数叫“Read_DS18B20()”,但是我写的读取温度的函数惯性地也取了这个名字,编译报错的时候还找了老久……难怪之前见有人的写法是“RD_DS18B20()”。

下次留意!RD_DS18B20!!!

另外,温度的读取应该考虑正温度就够了,如果需要读取负温度,就取反后加一,符号位是11111。(不记得了就查手册) (正常老师应该也不会在负温度的环境进行测量吧哈哈哈……)

2.6 数码管显示相关函数

数码管需要显示三个部分:时钟、闹钟、温度

2.6.1 单个数码管显示
void SMGBYTE(unsigned char position,value)
{
	HC138(6);
	P0 = 0x01 << position;  //位选
	HC138(7);
	P0 = SMGDM[value];  //段选
	delay(500);  //延时消影
}
2.6.2 温度显示
void SMG_SHOW_DS18B20()
{
	PROTECT_P0 = P0;  //保护灯光(闹钟)状态
	
	SMGBYTE(0,11);
	SMGBYTE(1,11);
	SMGBYTE(2,11);
	SMGBYTE(3,11);
	SMGBYTE(4,11);
	SMGBYTE(5,temperature /10);
	SMGBYTE(6,temperature % 10);
	SMGBYTE(7,12);
	//一次循环的最后关掉所有灯以免com8特别亮
	HC138(6);P0 = 0xff;
	HC138(7);P0 = 0xff;
	
	HC138(0);  //需要关,否则com8的a段将会随L1一起闪烁
	
	P0 = PROTECT_P0;
}

这里有一个很重要的经验:凡是数码管动态显示与LED组合要求实现某些功能时,一定要加上这样的“保护现场”的变量。这是由于数码管显示与LED亮灭都是控制138译码器选择不同使能端进而都通过P0的值来进行状态呈现的!

另外,调试的过程中还发现最后一位“C”的亮度格外亮,经过上述关灯步骤后这个问题确实解决了,但是新问题出现了:所有灯的亮度都变得很低。这是二刷中应该着力解决的不足之处!

2.6.3 显示时钟与闹钟
void SMG_SHOW(unsigned char a0,a1,a2)
{
	PROTECT_P0 = P0;
	
	SMGBYTE(0,a0 / 16);
	SMGBYTE(1,a0 % 16);
	SMGBYTE(2,10);
	SMGBYTE(3,a1 / 16);
	SMGBYTE(4,a1 % 16);
	SMGBYTE(5,10);
	SMGBYTE(6,a2 / 16);
	SMGBYTE(7,a2 % 16);
	
	HC138(0);  //需要关,否则com8的a段将会随L1一起闪烁
	
	P0 = PROTECT_P0;
}

之所以能将时钟与闹钟的显示统一为这种形式是因为两者都由时、分、秒表示。

需要注意的是由于DS1302用的是BCD码格式存储和处理时间数据,故而显示十位和个位时需要除的是十六进制的16(   1 6 1 \ 16^1  161 ),而不是十进制的10(   1 0 1 \ 10^1  101)。

深感此坑之易栽,下次我决定尝试自己外部定义时分秒的变量来用十进制,后续写加减逻辑的时候也会少需要考虑很多数学关系!

结合以上代码,我们进一步可以写出:“在‘时钟显示’状态下,按下S4按键,显示温度数据,松开按键,返回’时钟显示‘界面。”这个功能。如下:

void SMG_SHOW_DS1302_DS18B20(unsigned char a0,a1,a2)
{
	PROTECT_P0 = P0;
	
	SMGBYTE(0,a0 / 16);
	SMGBYTE(1,a0 % 16);
	SMGBYTE(2,10);
	SMGBYTE(3,a1 / 16);
	SMGBYTE(4,a1 % 16);
	SMGBYTE(5,10);
	SMGBYTE(6,a2 / 16); 
	SMGBYTE(7,a2 % 16);
	
	HC138(0);  //需要关,否则com8的a段将会随L1一起闪烁
	
	P0 = PROTECT_P0;
	
	//在时钟显示模式下按S4显示温度
	if(S4 == 0)
	{
		delay(100);
		while(S4 == 0)  //松手检测
		{
			READDS18B20();
			SMG_SHOW_DS18B20();
		}
	}
}

2.7 闹钟提醒

即L1灯的定时闪烁。(没要求用蜂鸣器是因为老师们也被这个板子默认上电蜂鸣器响的设定给搞得很头痛吧hhh)

void ALARM()
{
	unsigned char k = 0;  //5秒标志变量
	if((TIME[0] == ALARM_TIME[0]) && (TIME[1] == ALARM_TIME[1]) && (TIME[2] == ALARM_TIME[2]) && (ALARM_flag == 0))  //当时间与闹钟时、分、秒完全一致且闹钟未开启时进入if语句
	{
		TR0 = 1;  //打开定时器0
		while(k < 25)
		{
			if(S4 == 0 || S5 == 0 || S6 == 0 || S7 == 0)
			{
				delay(50);  //延时按键消抖
				if(S4 == 0 || S5 == 0 || S6 == 0 || S7 == 0)  //按任意键退出循环
				{
					ALARM_flag = 1;  //防止退出循环后再进来
					break;
				}
			}
			HC138(4);  //记得开闸,否则灯亮不了!
			switch(k % 2)
			{
				case 0:L1 = 0;break;
				case 1:L1 = 1;break;
				default:break;
			}
			if(cnt == 10)  //cnt = 10 代表时间过去了0.2s,当k = 25 时,即时间过去了5s时,闹钟提醒结束
			{
				cnt = 0;
				k++;
			}
			READ_DS1302();  
			SMG_SHOW_DS1302_DS18B20(TIME[2],TIME[1],TIME[0]);
		}
		HC138(4);
		L1 = 1;  //熄灭L1
		HC138(0);
		while(ALARM_flag == 1)
		{
			if(cnt == 50)  //一秒后再释放变量flag,因为最初的一秒钟里闹钟时间与实时时钟时间相等,若在这里不拖住将重复进入闹钟状态,导致按键“失效”
			{
				ALARM_flag = 0;
			}
			READ_DS1302();
			SMG_SHOW_DS1302_ DS18B20(TIME[2],TIME[1],TIME[0]);
		}
		k = 0;
		TR0 = 0;  //关闭定时器
	}
}

需要注意的是,想要实时显示时间,就必须要将读函数:READ_DS1302() 与显示函数SMG_SHOW_DS1302_ DS18B20(a0,a1,a2) 配套使用,否则时间数据不会更新。在主函数的while(1)中如此,在这里的while(k < 25)也是如此。

开始的时候还遇到了一个问题:第一次按下任意键虽然L1会瞬间熄灭但是马上又重新点亮!我尝试了长按和按两次,发现L1会熄灭且不重新点亮。

找了一番程序的漏洞才发现原来是这样:在最初的1s内闹钟存储数组与时钟存储数组严格相等(精确度是秒),故只要在最初的1s内松手,就会立马进入下一次循环来激活闹钟(在1s内闹钟等于时钟恒成立)。

对此我的解决方案是设出如上ALARM_flag参数来表征闹钟状态:0 ,1分别代表允许与不允许进入闹钟提醒循环。当在闹钟提醒循环(while(k < 25){ })中按下任意独立按键时,flag将变为1,进入延时兜底循环(while(ALARM_flag == 1){ })中,1s后被放出,此时闹钟时间已不再等于时钟时间了,自然不会在放手后重新进入闹钟提醒循环。

2.8 按键逻辑设计

这一部分是本题最难的部分,是我耗时最多的地方,同时也是代码最长的地方。

下面将以闹钟的设置为例,记录设计思路:

void SET_ALARM()  //设置闹钟
{
	unsigned char stat = 0;  //状态存储参数
	
	if(S6 == 0)
	{
		delay(100);  //延时消抖
		if(S6 == 0)
		{
			stat = 1;  //进入按键逻辑循环
			while(S6 == 0)  //松手检测
			{
				SMG_SHOW(ALARM_TIME[2],ALARM_TIME[1],ALARM_TIME[0]);
			}
			while(stat != 0)
			{
				switch(stat)
				{
					case 1:
						TR0 = 1;  //随用随开
						if(cnt == 50)
						{
							temp = 0xbb;  //装载变量
						}
						if(cnt == 100)
						{
							temp = ALARM_TIME[2];
							cnt = 0;
						}
						SMG_SHOW(temp,ALARM_TIME[1],ALARM_TIME[0]);  //数码管动态显示闹钟
						if(S4 == 0)
						{
							delay(100);
							{
								if(S4 == 0)
								{
									if(ALARM_TIME[2] == 0x00)  //注意极限位置,小时的位置极限位置是0和23,分秒的极限位置是0和59
									{
										ALARM_TIME[2] = 0x23;					
									}
									else if((ALARM_TIME[2] % 16) == 0)  //简单的数学关系
									{
										ALARM_TIME[2] = (ALARM_TIME[2] / 16 - 1) + 9;
									}
									else
									{
										ALARM_TIME[2] -= 1;
									}
									while(S4 == 0)
									{
										SMG_SHOW(temp,ALARM_TIME[1],ALARM_TIME[0]);
									}
								}
							}
						}
						if(S5 == 0)
						{
							delay(100);
							{
								if(S5 == 0)
								{
									if(ALARM_TIME[2] == 0x23)
									{
										ALARM_TIME[2] = 0x00;								
									}
									else if((ALARM_TIME[2] % 16) == 9)
									{
										ALARM_TIME[2] = (ALARM_TIME[2] / 16 + 1) * 16;
									}
									else
									{
										ALARM_TIME[2] += 1;
									}
									while(S5 == 0)
									{
										SMG_SHOW(temp,ALARM_TIME[1],ALARM_TIME[0]);
									}								
								}
							}
						}
						if(S6 == 0)
						{
							delay(100);
							if(S6 == 0)
							{
								while(S6 == 0)
								{
									SMG_SHOW(temp,ALARM_TIME[1],ALARM_TIME[0]);
								}
								stat = 2;
								cnt = 0;  //记得清零
							}
						}												
						break;
						
					case 2:
						if(cnt == 50)
						{
							temp = 0xbb;
						}
						if(cnt == 100)
						{
							temp = ALARM_TIME[1];
							cnt =0;
						}
						SMG_SHOW(ALARM_TIME[2],temp,ALARM_TIME[0]);
						if(S4 == 0)
						{
							delay(100);
							{
								if(S4 == 0)
								{
									if(ALARM_TIME[1] == 0x00) 
									{
										ALARM_TIME[1] = 0x59;					
									}
									else if((ALARM_TIME[1] % 16) == 0)
									{
										ALARM_TIME[1] = (ALARM_TIME[1] / 16 - 1) + 9;
									}
									else
									{
										ALARM_TIME[1] -= 1;
									}
									while(S4 == 0)
									{
										SMG_SHOW(ALARM_TIME[2],temp,ALARM_TIME[0]);
									}
								}
							}
						}
						if(S5 == 0)
						{
							delay(100);
							{
								if(S5 == 0)
								{
									if(ALARM_TIME[1] == 0x59)
									{
										ALARM_TIME[1] = 0x00;								
									}
									else if((ALARM_TIME[1] % 16) == 9)
									{
										ALARM_TIME[1] = (ALARM_TIME[1] / 16 + 1) * 16;
									}
									else
									{
										ALARM_TIME[1] += 1;
									}
									while(S5 == 0)
									{
										SMG_SHOW(ALARM_TIME[2],temp,ALARM_TIME[0]);
									}								
								}
							}
						}
						if(S6 == 0)
						{
							delay(100);
							if(S6 == 0)
							{
								while(S6 == 0)
								{
									SMG_SHOW(ALARM_TIME[2],temp,ALARM_TIME[0]);
								}
								stat = 3;
								cnt = 0;
							}
						}												
					break;
						
					case 3:
						if(cnt == 50)
						{
							temp = 0xbb;
						}
						if(cnt == 100)
						{
							temp = ALARM_TIME[0];
							cnt =0;
						}
						SMG_SHOW(ALARM_TIME[2],ALARM_TIME[1],temp);
						if(S4 == 0)
						{
							delay(100);
							{
								if(S4 == 0)
								{
									if(ALARM_TIME[0] == 0x00)
									{
										ALARM_TIME[0] = 0x59;					
									}
									else if((ALARM_TIME[0] % 16) == 0)
									{
										ALARM_TIME[0] = (ALARM_TIME[0] / 16 - 1) + 9;
									}
									else
									{
										ALARM_TIME[0] -= 1;
									}
									while(S4 == 0)
									{
										SMG_SHOW(ALARM_TIME[2],ALARM_TIME[1],temp);
									}
								}
							}
						}
						if(S5 == 0)
						{
							delay(100);
							{
								if(S5 == 0)
								{
									if(ALARM_TIME[0] == 0x59)
									{
										ALARM_TIME[0] = 0x00;								
									}
									else if((ALARM_TIME[0] % 16) == 9)
									{
										ALARM_TIME[0] = (ALARM_TIME[0] / 16 + 1) * 16;
									}
									else
									{
										ALARM_TIME[0] += 1;
									}
									while(S5 == 0)
									{
										SMG_SHOW(ALARM_TIME[2],ALARM_TIME[1],temp);
									}								
								}
							}
						}
						if(S6 == 0)
						{
							delay(100);
							if(S6 == 0)
							{
								while(S6 == 0)
								{
									SMG_SHOW(ALARM_TIME[2],ALARM_TIME[1],temp);
								}
								TR0 = 0;
								stat = 0;
								cnt = 0; 
							}
						}										
					break;
					default:break;
				}
			}
		}
	}
}

/ * ========================================================== * /
250多行看似复杂,其实只要写清楚了case1,剩下的就是复制粘贴的事情了。

unsigned char stat = 0;  //状态存储参数
	
	if(S6 == 0)
	{
		delay(100);  //延时消抖
		if(S6 == 0)
		{
			stat = 1;  //进入按键逻辑循环
			while(S6 == 0)  //松手检测
			{
				SMG_SHOW(ALARM_TIME[2],ALARM_TIME[1],ALARM_TIME[0]);
			}
			while(stat != 0)
			{
				switch(stat)
				{
					case 1:
						TR0 = 1;  //随用随开
						if(cnt == 50)
						{
							temp = 0xbb;  //装载变量
						}
						if(cnt == 100)
						{
							temp = ALARM_TIME[2];
							cnt = 0;
						}
						SMG_SHOW(temp,ALARM_TIME[1],ALARM_TIME[0]);  //数码管动态显示闹钟
						if(S4 == 0)
						{
							delay(100);
							{
								if(S4 == 0)
								{
									if(ALARM_TIME[2] == 0x00)  //注意极限位置,小时的位置极限位置是0和23,分秒的极限位置是0和59
									{
										ALARM_TIME[2] = 0x23;					
									}
									else if((ALARM_TIME[2] % 16) == 0)  //简单的数学关系
									{
										ALARM_TIME[2] = (ALARM_TIME[2] / 16 - 1) + 9;
									}
									else
									{
										ALARM_TIME[2] -= 1;
									}
									while(S4 == 0)  //松手检测
									{
										SMG_SHOW(temp,ALARM_TIME[1],ALARM_TIME[0]);
									}
								}
							}
						}
						if(S5 == 0)
						{
							delay(100);
							{
								if(S5 == 0)
								{
									if(ALARM_TIME[2] == 0x23)
									{
										ALARM_TIME[2] = 0x00;								
									}
									else if((ALARM_TIME[2] % 16) == 9)
									{
										ALARM_TIME[2] = (ALARM_TIME[2] / 16 + 1) * 16;
									}
									else
									{
										ALARM_TIME[2] += 1;
									}
									while(S5 == 0)  //松手检测
									{
										SMG_SHOW(temp,ALARM_TIME[1],ALARM_TIME[0]);
									}								
								}
							}
						}
						if(S6 == 0)
						{
							delay(100);
							if(S6 == 0)
							{
								while(S6 == 0)  //松手检测
								{
									SMG_SHOW(temp,ALARM_TIME[1],ALARM_TIME[0]);
								}
								stat = 2;
								cnt = 0;  //记得清零
							}
						}												
						break;

注意:凡是有按键必须要加松手检测! 检测的同时要记得正常显示数码管。

本题的一个难点是怎么让数码管的某两位进行秒闪而其他位正常动态显示。有大佬(@ 漩涡鸣熊)提出了数码管同异步扫描算法,我个人的解决方案是最简单粗暴地在前1s内熄灭目标数码管,在后1s内正常动态显示。

怎么实现这个想法呢?我设出了装载熄灭状态的参数temp,并且改进数码管动态显示函数(设置为接收a0,a1,a2三个形式参数),以达到动态调整数码管“闪烁”位置的目的。

在按下加减按键S5、S4后,我们需要注意:由于采用的是BCD码的形式存储数据,进位时不可简单地+1,而是要将16进制转换成10进制再转换成16进制,需要一步数学处理。在二刷时计划单独设出时分秒变量进行数学处理,代码行数应该会精简很多!

剩下的事情就是复制粘贴了,小小修改一下就ok。

对于时钟设置,只需要不读取DS1302的数据即可实现“时间停止”的目的,调整好时钟数据后补一步初始化(INIT_DS1302();)即可将时钟的初值重新装载为新设定的时间。

2.9 主函数

点兵陈将,收割战场。

void main()
{
	INITSYSTEM();
	INIT_DS1302();
	INITTIMER0();
	while(1)
	{
		READ_DS1302();
		SMG_SHOW_DS1302_DS18B20(TIME[2],TIME[1],TIME[0]);
		ALARM();
		SET_CLOCK();
		SET_ALARM();
	}
}

3 回顾与展望

3.1 本题主要经验

  1. RD_DS18B20();
  2. 凡是数码管动态显示与LED组合要求实现某些功能时,一定要加上“保护现场”的变量
  3. 由于DS1302用的是BCD码格式存储和处理时间数据,故而显示十位和个位时需要除的是十六进制的16(   1 6 1 \ 16^1  161 ),而不是十进制的10(   1 0 1 \ 10^1  101)。
  4. 凡是有按键必须要加松手检测!

3.2 二刷目标

  1. 数码管显示完全消影
  2. 显示温度时亮度正常
  3. 时分秒外部定义用十进制算法计算时间

若偶然看到此文的大佬们有心情就缺点或可优化处指点一二,感激不尽!

  • 12
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值