蓝桥杯第十四届电子类单片机组程序设计

目录

前言

蓝桥杯大赛历届真题(点击查看)

一、第十四届比赛题目

1.比赛原题

2.题目解读

1)任务要求

2)注意事项

二、任务实现

1.NE555读取时机的问题

1)缩短计数时间

2)实时读取

2.温度传感器读取时机的问题

3.由亮变暗的检查

4.按键长按/短按

5.平均数和最大值的处理

6.对于小数的处理

三、代码实现

main.c

onewire.c

onewire.h

iic.c

iic.h

ds1302.c


前言

之前也不止一次提到,随着蓝桥杯蓝桥杯知名度越来越高,它省赛题目也一年比一年难,第十四届蓝桥杯省赛题目,可以说是这些省赛题目当中最难得了,个人感觉已经赶上国赛水平了。这其中也用到了许多对问题的处理方法,本篇文章会择其进行介绍,当然,条条大路通罗马,我通向罗马的道路也仅供参考,也并非绝对。

蓝桥杯第十四届省赛题目可以在蓝桥杯官网找到

蓝桥杯大赛历届真题(点击查看)

一、第十四届比赛题目

1.比赛原题

2.题目解读

说实话,5页的题目,5个数码管菜单,真的让人看到眼花缭乱,下面是我列出来的比赛的一些要求完成的任务以及注意事项,仅供帮助大家理解,具体要求还得看赛题。

1)任务要求

  • 定义湿度,使用频率表示湿度(将频率转化为湿度),注意范围
  • PCF8591读取光敏电阻电压,当检测到由"亮"到"暗"时,读取一次温度和湿度并记录,读取一次当前的时间并记录。
  • 计算最近读取的若干次温度和湿度的平均值(保留一位小数)和最大值
  • 显示菜单0:显示当前的时间
  • 显示菜单1 2 3:回显记录的数据,共三个子菜单,分别显示温度,湿度和上一次读取数据时的时间以及总共读取数据的次数
  • 显示菜单4,显示温度参数(具体作用下文会提)
  • 每当触发一次读取(参看第二条),界面跳转到温湿度显示界面,显示读取到的温湿度值,3s后再跳回原菜单,期间所有按键失效,并且不再触发读取
  • 如果读取到的湿度数据为无效数据(参看第一条),则湿度显示"AA",该次读取的数据不进行计算,即把该次读取到的温湿度值,算到平均温湿度值以及最高温湿度值里,读取到有效次数也不+1
  • 按键s4,s4在除了温湿度界面均有效,可以在时间界面,数据回显以及参数设置界面之间跳转
  • 按键s5,s5仅在数据回显的子菜单内有效,用于在三个子菜单之间切换
  • 按键s8,s8仅在参数界面有效,按下s8,温度参数+1
  • 按键s9,s9仅在参数界面和时间数据回显界面(数据回显的子菜单)下有效。在参数界面,按下s9,温度参数-1;在数据回显界面长按2s以上s9,松开s9后所有记录的数据清空。
  • LED灯:在时间界面,led1点亮,否则熄灭;在数据回显界面,led2点亮,否则熄灭;在温湿度显示界面,led3点亮,否则熄灭
  • 如果当前采集到的温度值高于温度参数,则led4以100ms为间隔闪烁,否则熄灭;如果当前采集到的湿度数据为无效数据,则led5点亮,直到采集到有效数据;如果当前采集到的温度数据和湿度数据比上一次的数据都高(至少采集两组数据才算),则led6点亮,否则熄灭
  • led7熄灭

2)注意事项

  • 上电时处在时间界面,温度参数设置为30(初始时间呢?好像没说设置多少哎)
  • 注意湿度的有效值,如果读取到的湿度值为无效值,则该次读取到的温度湿度和时间不参与计算(或存储),但是还是得显示
  • 如果一次没有读取数据或者一次有效数据都没有读取到,则时间回显子界面的时、间隔、分显示位置熄灭;温度、湿度回显子界面除界面标识符外的其它位熄灭。
  • 其它相关的注意事项

二、任务实现

对于此项赛题,有许多需要处理的问题:

1.NE555读取时机的问题

之前的NE555都是记录1s内,电平的变化次数,这个次数就刚好是频率。但是如果我们每次都是接收到由亮到暗的信号之后,再读取NE555的话,会花费超过1s的时间,不符合题目要求的<0.5s的灵敏度。因此要读取NE555,可以进行以下两种处理方法,这里只说思路:

1)缩短计数时间

频率是1s内电平变化的次数,我们之前记录1s内电平变化的次数也正因如此。但是现在我们没有1s时间去读取NE555,我们可以缩短读取电平的时间,比如每隔200ms读取一次这一段时间内电平变化的次数,然后再乘5就是频率。这种方法可以提高读取的响应速度,但是会失去更多精度,具体的实现方法相信已经阅读过关于NE555的小伙伴们都可以轻松实现。

2)实时读取

虽然题目要求的是,接收到由亮变暗的信号之后读取一次NE555,但我们可以不那么"老实",我们可以一直都在读取NE555,当收到由亮变暗的信号之后,在用其他变量记录此时NE555读取出来的值即可,虽然这种方法会浪费更多的单片机算力,需要添加更多的变量,但是读取的NE555更加及时。后面的代码中也都用的这种方法。

2.温度传感器读取时机的问题

与NE555类似,温度传感器也要考虑读取时机的问题。咱们之前提到温度传感器的温度转化需要时间,而且需要的时间不短,如果我们在温度转化完成之前就读取温度,那我们只能读取到上一次温度转化出来的结果。而我们使用的读取温度传感器的函数,恰恰是这样的,每调用一次函数,发送一次温度转化的指令并读取一次温度,也就是说,我们需要连续调用温度传感器,或者调用一次读取温度的函数之后,等500ms甚至更久之后,在重新读取才能读取到有效的数据。这就跟NE555一样了,解决方法也类似,可以一直读取温度传感器,当接收到由亮变暗的信号之后,我们再用新的变量接受当前温度传感器的值,同样需要mcu由更多的开销,同样需要定义更多的变量,不过这些都无足轻重。具体方法跟NE555处理方法类似,就不在赘述了。

当然,这个问题是跟着我之前写的代码完成赛题的话才会遇到的问题,也并非所有人写的代码都需要考虑这个问题。

3.由亮变暗的检查

由亮变暗触发读取(通过AD转化器读取光敏电阻),或者前几年有个赛题出的,当前电压从高过阈值到低于阈值则记录一次之类的题目都类似,这里先介绍一下思路:

由亮到暗这个过程的第一步,就是当前环境是"亮",在某一时刻环境又变到了"暗",我们定义一个变量is_Liang来记录这个状态,is_Liang=1表示当前环境是"亮",is_Liang=0表示当前环境为"暗"。当is_Liang==1时,我们只需检查当前实时读取的AD值是否小于某个阈值,如果小于,则判断"由暗到亮",并更新is_Liang状态位。同理,如果当前环境为"暗",AD又高于某个值,说明进行了从暗到亮的跳变。当然,为了使亮暗分明,我们可以把由亮到暗的阈值设置的低于由暗到亮的阈值:

if(is_Liang==1)//如果当前正处在"亮"状态

{

if(ad<70)//如果检测到光敏电阻的AD小于70,就尝试判断为进入"暗"状态,从亮到暗触发一次采集。(当然这个70的阈值可以自己设置,尽量设置低一些)

{

if(++change_200>200)//连续200次进入定时器,都处在暗状态,说明真的暗了(题目要求响应时间小于500ms,这个精度没毛病)

{

change_200=0;

is_Liang=0;

is_cai_ji=1;//触发一次采集

}

}

else if(change_200>0)//不满足条件,就当什么都没发生

{

change_200=0;

}

}

else if(is_Liang==0&&mod!=5)//如果正处在"暗"状态

{

if(ad>100)//与从亮到暗同理,注意两个阈值尽量不要设置的一样,设置一样了不好处理

{

if(++change_200>200)

{

change_200=0;

is_Liang=1;

}

}

else if(change_200>0)

{

change_200=0;

}

}

我是使用定时器1来完成上述代码的,为了防止出现误判,也为了提高判断的时间,这里是连续200ms内,都满足条件才判断状态改变。

至于"每从高于阈值到低于阈值记录一次",其实与亮暗跳变一致。

4.按键长按/短按

这个也是一个最近几年经常考的问题,这个的处理方法很多,我只介绍我自己会用到一种。

第七届比赛时已经介绍了一种led灯闪烁的方法,我们稍加改进就可以实现记录按下去多长时间的功能,当按下去的时间超过某个值,我们就视为长按。题目要求的是按下按键2s以上就视为长按,所以我们定义一个标志位is_2s,跟之前一样,当is_2s等于0时,2s之后会被定时器置为1.我们再while(P32==0)之前将is_2s置为0,当跳出while循环时,判断is_2s是否为1,为1,说明按下按键这段时间超过2s了,我们就可以对其进行长按按键的处理,可以是修改其他的标志位,也可以直接把长按的功能在这里实现,为0说明没有长按,就直接跳过了。

if(P32==0)

{

Delay5ms();

is_2s=0;//长按功能,按下按键之后,is_2s被置为0,2s之后is_2s被置为1,如果2s之后还没松手(此时is_2s为1,则视为长按)

while(P32==0){run();}

if(mod==3&&is_2s==1)//is_2s标志位为1,说明从is_2s清零到现在已经过去2s了,也就是已经长按按键2s了

{

//长按

}

Delay5ms();

key_value=9;

}

is_2s为0时,2s后会在1ms的定时器内被置为1

if(is_3s==0)//数3s

{

if(++count_3s>3000)

{

is_3s=1;//数够3s的标志位

count_3s=0;

}

}

5.平均数和最大值的处理

平均数和最大值的处理其实也很简单,不过如果第一次写或者没看懂题目要求,以为需要记录每次读取的数据时,确实也会手足无措,会想着定义一个指针,怎么怎么存储这些数据,其实题目并没有要求可以读取到每次记录的数据,只要求记录每次读取数据的平均值和最大值即可。我们只需要知道已经读取了几次数据、之前记录的平均值、这次读取到的数据就可以计算出新的平均值:

新的平均值=(旧的平均值*已经读取到的有效数据数+这次读取到的数据)/(已经读取到的有效数据数+1)

最大值只需要比较当前最大值与当前读取到的数据谁大,当前读取到的数据大,新的最大值等于当前读取到的数据,当前最大值大,新的最大值就是当前最大值

这个已经读取到的有效数据数可以根据自己的代码修改,可能需要加一减一之类的

Now_temp=temp;//更新温度

arr_temp=(arr_temp*ci_shu+Now_temp*10)/(ci_shu+1);//平均温度。扩大了10倍,便于读取小数点后1位

max_temp=max_temp>Now_temp? max_temp:Now_temp;//更新最高温度

last_shi_du=Now_shi_du;//记录上一次的湿度值

Now_shi_du=shi_du;//更新湿度

arr_shi_du=(arr_shi_du*ci_shu+Now_shi_du*10)/(ci_shu+1);//平均湿度。扩大了10倍,便于读取小数点后1位

max_shi_du=max_shi_du>Now_shi_du? max_shi_du:Now_shi_du;//更新最大湿度

这里算均值的时候,由于题目要求精确到小数点后一位,于是就算了10倍的均值,后续处理时,在缩小十倍,就可以符合题目要求了。

6.对于小数的处理

一听到小数,我们就会想到float,但是,对于定一一个变量都不用int,连char都不用,还得用unsigned char的keil来说,我们更倾向于它是一个整数,而且还得是正整数。所以我们就想到把这个数字扩大10倍,显示的时候,只需要在最后一位之前加一个小数点即可,而且使用整数时也便于我们后续的处理。一般资源数据包会给我们0到9的断码表,我们再写出0.到9.的断码表,使得Seg_Table[5]是显示5,Seg_Table[5+10]显示5.(注意是5加上".")。对此次省赛的处理中,所有平均值都是要求精确到小数点后一位,所以在计算时都是把平均值扩大十倍,在显示时多显示一个小数点。

三、代码实现

这次用到了onewire iic和ds1302,其实onewire.c iic.c和ds1302.c里的内容,每次都是一样的,顶多函数名字叫的不一样,只会比之前写的模板少,不会多(所以底层驱动里的注释就不写了,不清楚的可以看对应的之前写过的文章)。

main.c

cpp 复制代码
#include <stc15.h>
#include <intrins.h>
#include "onewire.h"
#include "iic.h"
#include "ds1302.h"

code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,//0-9加上小数点
//0.	1.	2.		3.		4.	5.		6.	7.	8.		9.
0xFF,//20 熄灭
0xBF,//- 21
0xC6,//C 22
0x89,//H 23
0x8e, //F 24
0x8C,	//P 25
0x86, //E 26
0x88  //A 27
};
unsigned char Led_Num=0xFF;
#define LED_ON(x)			Led_Num&=~(0x01<<x);P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
#define LED_OFF(x)		Led_Num|=0x01<<x;		P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
#define LED_OFF_ALL()	Led_Num=0xFF;				P0=0xFF;P2|=0x80;P2&=0x9F;P2&=0x1F;

#define NIXIE_CHECK()	P2|=0xC0;P2&=0xEF;P2&=0x1F;
#define NIXIE_ON()		P2|=0xE0;P2&=0xFF;P2&=0x1F;

void Timer0_Init(void);//定时器0用于NE555
void Timer1_Init(void);		//1毫秒@12.000MHz
void Delay100ms(void);	//@12.000MHz
void get_key(void);//读取按键函数
void show_menu(void);//显示菜单函数
void run(void);//(主)运行程序函数
void led_run(void);//LED灯控制函数

unsigned char Nixie_num[8]={20,20,20,20,20,20,20,20};//数码管待显示的数据
unsigned char location=0;//当前数码管扫描到的位置,中间变量
unsigned char key_value=0;//读取到的键值,中间变量
unsigned int temp=0;//实时读取到的温度值
unsigned char ad=0;//实时读取到的AD转化值
unsigned int f=0;//频率的中间变量
unsigned int fre=0;//定义频率
unsigned char mod=0;//定义菜单模式,取值范围0到5,0:时钟,1 2 3回显数据 4:参数设置, 5:温湿度显示
unsigned int shi_du=0;//定义湿度
unsigned char ci_shu=0;//读取到有效数据的次数
unsigned int Now_temp=0;//当前记录的温度值(温度一直在读取,但不是实时记录,因此会产生许多中间的变量,下同)
unsigned int Now_shi_du=0;//当前记录的湿度值
unsigned char Now_time[3];//记录读取到有效数据的时间(秒分时)
unsigned int arr_temp=0;//平均温度,为方便保留一位小数,这里在计算时扩大了十倍
unsigned int arr_shi_du=0;//平均湿度,处理同上
unsigned int max_temp=0;//记录的最高温度
unsigned int max_shi_du=0;//记录的最高湿度
unsigned int temp_canshu=30;//温度参数,默认为30
unsigned int last_temp=0;//上一次记录的温度(主要用于当前数据与上一次数据比较,以控制led6
unsigned int last_shi_du=0;//上一次记录的湿度
unsigned int wrong_temp;//记录当读取到的湿度值无效时的温度值,只读取,不处理或储存

bit shi_du_is_you_xiao=0;//实时读取的湿度值是否有效,1:有效,0:无效
bit is_cai_ji=0;//采集标志位,为1时采集一次数据
bit is_3s=1;//记录进入菜单5之后,3s后返回原菜单的标志为
bit is_2s=1;//记录按下s9长按2s的标志位
bit shidu_wuxiao=0;//当前读取的湿度是否有效,1:无效,0:有效
bit is_up=0;//当前读取的温度和湿度均比上一次高,1:均比上一次高,0:不是均比上一次高,用于控制Led6
void main()
{
	LED_OFF_ALL();
	read_ds();
	ad=read_pcf(1);//光敏电阻在通道1
	ds1302_init();
	Timer0_Init();
	Timer1_Init();
	EA=1;
	Delay100ms();
	while(1)
	{
		get_key();
		run();
		Delay100ms();
	}
}
unsigned char last_mod=0;
void run()
{
	show_menu();//显示菜单
	led_run();//控制LED灯
	ad=read_pcf(1);//实时读取AD
	read_time();//实时更新时间
	temp=read_ds();
	temp=temp<99?temp:99;//如果读取到的温度值高于99,则令读取到的温度值为99(提上要求的,实际上应该达不到,除非仿真)
	if(is_cai_ji==1)//采集标志位为1,采集一次数据
	{
		is_cai_ji=0;
		
		if(shi_du_is_you_xiao==1)//只有当实时读取的湿度值为有效值时,才进行记录和除了
		{
			shidu_wuxiao=0;
			
			last_temp=Now_temp;//记录上一次的温度值
			Now_temp=temp;//更新温度
			arr_temp=(arr_temp*ci_shu+Now_temp*10)/(ci_shu+1);//平均温度。扩大了10倍,便于读取小数点后1位
			max_temp=max_temp>Now_temp? max_temp:Now_temp;//更新最高温度
			
			last_shi_du=Now_shi_du;//记录上一次的湿度值
			Now_shi_du=shi_du;//更新湿度
			arr_shi_du=(arr_shi_du*ci_shu+Now_shi_du*10)/(ci_shu+1);//平均湿度。扩大了10倍,便于读取小数点后1位
			max_shi_du=max_shi_du>Now_shi_du? max_shi_du:Now_shi_du;//更新最大湿度
			
			Now_time[0]=time[0];Now_time[1]=time[1];Now_time[2]=time[2];//记录读取数据时的时间
			
			ci_shu++;//读取到有效数据的次数++
			
			/*如果读取到了至少两次有效数据,并且当前读取的温度和湿度都比上一次高,则给点亮Led6的标志位is_up置1,否则置0*/
			if(Now_temp>last_temp&&Now_shi_du>last_shi_du&&ci_shu>=2)
			{
				is_up=1;
			}
			else if(Now_temp<=last_temp||Now_shi_du<=last_shi_du||ci_shu>=2)
			{
				is_up=0;
			}
		}
		else//没有读取到有效数据,只读取数据,不记录和处理
		{
			wrong_temp=temp;//无效的温度数据(只用于数码管显示)
			shidu_wuxiao=1;
		}
		last_mod=mod;//受到读取数据的指令之后,要跳转到温湿度界面,跳转完3s还要再跳回来,所以要记录是从哪个菜单跳出去的
		mod=5;//跳转到温湿度显示界面
		is_3s=0;//清零3s计时
	}
	else if(mod==5&&is_3s==1)//3s过后
	{
		mod=last_mod;//重新跳回刚才的菜单
	}
}

unsigned int count_1s=0;//数1s,中间变量
bit is_Liang=1;//明暗标志位,is_Liang==1,当前环境为亮。is_Liang==0,当前环境为暗,中间变量
unsigned int change_200=0;//连续200次进入定时器,其亮暗情况均与亮暗标志位不符合,则改变亮暗标志位(其实也就对应main读取两次AD,非必要)
unsigned int count_3s=0;//数3s,用于3s后跳转菜单,中间变量
unsigned int count_2s=0;//数2s,用于数长按2s按键,中间变量
void Timer1_Isr(void) interrupt 3
{
	/*数码管显示*/
	P0=0x01<<location;NIXIE_CHECK();
	P0=Seg_Table[Nixie_num[location]];NIXIE_ON();
	
	if(++location==8)
		location=0;
	
	/*NE555读取,每隔1s读取一次,读取到的值就是频率*/
	if(++count_1s==1000)
	{
		count_1s=0;
		fre=f;//读取频率
		f=0;
		if(fre>=200&&fre<=2000)//读取的频率有效
		{
			shi_du_is_you_xiao=1;//实时读取的频率是否有效标志位,注意与当前读取的频率是否有效标志位shidu_wuxiao进行区分
			shi_du=(unsigned int)(4*fre/90+10/9);//将频率转化为湿度(方程需要自己拟合)
		}
		else//读取的频率无效
		{
			shi_du_is_you_xiao=0;
		}
	}
	
	if(is_Liang==1&&mod!=5)//如果当前正处在"亮"状态,且没有在温湿度显示界面(题目要求处在温湿度显示界面的3s内,不重复读取)
	{
		if(ad<70)//如果检测到光敏电阻的AD小于70,就尝试判断为进入"暗"状态,从亮到暗触发一次采集。(当然这个70的阈值可以自己设置,尽量设置低一些)
		{
			if(++change_200>200)//连续200次进入定时器,都处在暗状态,说明真的暗了(题目要求响应时间小于500ms,这个精度没毛病)
			{
				change_200=0;
				is_Liang=0;
				is_cai_ji=1;//触发一次采集
			}
		}
		else if(change_200>0)//不满足条件,就当什么都没发生
		{
			change_200=0;
		}
	}
	else if(is_Liang==0&&mod!=5)//如果正处在"暗"状态
	{
		if(ad>100)//与从亮到暗同理,注意两个阈值尽量不要设置的一样,设置一样了不好处理
		{
			if(++change_200>200)
			{
				change_200=0;
				is_Liang=1;
			}
		}
		else if(change_200>0)
		{
			change_200=0;
		}
	}
	
	if(is_3s==0)//数3s
	{
		if(++count_3s>3000)
		{
			is_3s=1;//数够3s的标志位
			count_3s=0;
		}
	}
	if(is_2s==0)
	{
		if(++count_2s>2000)
		{
			is_2s=1;//数够2s的标志位
			count_2s=0;
		}
	}
}
void Timer0_Isr(void) interrupt 1
{
	f++;
}
void Timer0_Init(void)
{//可以从stc-isp的范例程序里抄
		AUXR = 0x80;                    //定时器0为1T模式
    TMOD = 0x04;                    //设置定时器0为16位自动重装载外部记数模式
    TH0 = TL0 = 0xff;               //设置定时器0初始值
    TR0 = 1;                        //定时器0开始工作
    ET0 = 1;                        //开定时器0中断
}
void Timer1_Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x40;			//定时器时钟1T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x20;				//设置定时初始值
	TH1 = 0xD1;				//设置定时初始值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
	ET1 = 1;				//使能定时器1中断
}
void Delay100ms(void)	//@12.000MHz
{
	unsigned char data i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 144;
	k = 71;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void Delay5ms(void)	//@12.000MHz
{
	unsigned char data i, j;

	i = 59;
	j = 90;
	do
	{
		while (--j);
	} while (--i);
}

void get_key()
{
	unsigned char key_P3=P3;
	unsigned char key_P4=P4;
	
	P44=0;
	if(P32==0){Delay5ms();while(P32==0){run();}Delay5ms();key_value=5;}
	else if(P33==0){Delay5ms();while(P33==0){run();}Delay5ms();key_value=4;}
	
	P42=0;
	if(P32==0)
	{
		Delay5ms();
		is_2s=0;//长按功能,按下按键之后,is_2s被置为0,2s之后is_2s被置为1,如果2s之后还没松手(此时is_2s为1,则视为长按)
		while(P32==0){run();}
		if(mod==3&&is_2s==1)//is_2s标志位为1,说明从is_2s清零到现在已经过去2s了,也就是已经长按按键2s了
		{
			/*在时间回显菜单里,长按2s按键9,则情况所有储存的数据。
			这里需要清除的数据有:当前读取的温度值和湿度值,记录的最高的温度值和湿度值,记录的平均温度值和湿度值
			上一次读取的温度值和湿度值(仅仅用于比较,为了安全也清0)
			上一次读取数据的时间
			读取的湿度是否有效标志位等
			*/
			Now_temp=0;Now_shi_du=0;
			arr_temp=0;arr_shi_du=0;
			max_temp=0;max_shi_du=0;
			Now_time[0]=0;Now_time[1]=0;Now_time[2]=0;
			last_temp=0;last_shi_du=0;
			ci_shu=0;shidu_wuxiao=0;
		}
		Delay5ms();
		key_value=9;
	}
	else if(P33==0){Delay5ms();while(P33==0){run();}Delay5ms();key_value=8;}
	
	//s4切换菜单
	if(key_value==4&&mod!=5)//仅在不是在温湿度显示菜单时有效
	{
		if(mod==0)//如果在时间显示菜单,则切换到数据回显菜单
			mod=1;
		else if(mod==1||mod==2||mod==3)//如果在数据回显菜单,则切换到参数菜单
			mod=4;//注意数据回显菜单有三个子菜单
		else if(mod==4)//如果在参数菜单,则切换到时间显示菜单
			mod=0;
	}
	
	//s5切换数据回显的子菜单
	else if(key_value==5&&(mod==1||mod==2||mod==3))//仅在数据回显菜单内有效
	{
		if(mod<3)
			mod++;
		else 
			mod=1;
	}
	
	//s8加
	else if(key_value==8&&mod==4)//在参数设置菜单中,按下s8参数+1
	{
		if(++temp_canshu>100)//注意参数范围是0到99
			temp_canshu=0;
	}
	//s9减(长按功能在if(P32==0)那呢)
	else if(key_value==9&&mod==4)//在参数显示菜单,按下s9参数-1
	{
		if(temp_canshu>0)//注意参数范围是0到99
			temp_canshu--;
		else
			temp_canshu=99;
	}
	key_value=0;
	P3=key_P3;
	P4=key_P4;
}

void show_menu(void)
{
	if(mod==0)//菜单0显示时间
	{
		Nixie_num[0]=time[2]/10%10;
		Nixie_num[1]=time[2]/1%10;
		Nixie_num[2]=21;
		Nixie_num[3]=time[1]/10%10;
		Nixie_num[4]=time[1]/1%10;
		Nixie_num[5]=21;
		Nixie_num[6]=time[0]/10%10;
		Nixie_num[7]=time[0]/1%10;
	}

	/*题目要求:当触发次数为0时,时间回显子界面的时、间隔、分显示位置熄灭;温度、湿度回显子界面除界面标识符外的其它位熄灭。
	下边mod=1 2 3时对ci_shu=0的判断就是为了完成题目的上述要求*/
	else if(mod==1)//回显子菜单1,温度回显
	{
		Nixie_num[0]=22;//C
		Nixie_num[1]=20;//熄灭
		if(ci_shu==0)//如果一次有效数据都没有读到,则时间回显子界面的时、间隔、分显示位置熄灭;
		{
			Nixie_num[2]=20;//熄灭,下同
			Nixie_num[3]=20;
			Nixie_num[4]=20;
			Nixie_num[5]=20;
			Nixie_num[6]=20;
			Nixie_num[7]=20;
		}
		else//计算平均温度时数据扩大了10倍,在显示时把小数点加上
		{
			Nixie_num[2]=max_temp/10%10;//最大温度
			Nixie_num[3]=max_temp/1%10;
			Nixie_num[4]=21;
			Nixie_num[5]=arr_temp/100%10;//平均温度
			Nixie_num[6]=arr_temp/10%10+10;//加小数点
			Nixie_num[7]=arr_temp/1%10;
		}
	}
	else if(mod==2)//回显子菜单2,湿度回显
	{
		Nixie_num[0]=23;//H
		Nixie_num[1]=20;
		if(ci_shu==0)//"温度、湿度回显子界面除界面标识符外的其它位熄灭"
		{
			Nixie_num[2]=20;//熄灭
			Nixie_num[3]=20;
			Nixie_num[4]=20;
			Nixie_num[5]=20;
			Nixie_num[6]=20;
			Nixie_num[7]=20;
		}
		else//同上,计算平均湿度时数据扩大了10倍,在显示时把小数点加上
		{
			Nixie_num[2]=max_shi_du/10%10;//最大湿度
			Nixie_num[3]=max_shi_du/1%10;
			Nixie_num[4]=21;
			Nixie_num[5]=arr_shi_du/100%10;//平均湿度
			Nixie_num[6]=arr_shi_du/10%10+10;//加上小数点
			Nixie_num[7]=arr_shi_du/1%10;
		}
	}
	else if(mod==3)//回显子菜单3,时间回显
	{
		Nixie_num[0]=24;//F
		Nixie_num[1]=ci_shu/10%10;//触发次数
		Nixie_num[2]=ci_shu/1%10;
		if(ci_shu==0)//"温度、湿度回显子界面除界面标识符外的其它位熄灭"
		{
			Nixie_num[3]=20;//熄灭
			Nixie_num[4]=20;
			Nixie_num[5]=20;
			Nixie_num[6]=20;
			Nixie_num[7]=20;
		}
		else
		{
			/*显示的是读取数据时的时间,只显示时分*/
			Nixie_num[3]=Now_time[2]/10%10;//时间
			Nixie_num[4]=Now_time[2]/1%10;
			Nixie_num[5]=21;
			Nixie_num[6]=Now_time[1]/10%10;//分钟
			Nixie_num[7]=Now_time[1]/1%10;
		}
	}
	else if(mod==4)//菜单4参数界面
	{
		Nixie_num[0]=25;//P
		Nixie_num[1]=20;//熄灭
		Nixie_num[2]=20;
		Nixie_num[3]=20;
		Nixie_num[4]=20;
		Nixie_num[5]=20;
		Nixie_num[6]=temp_canshu/10%10;//温度参数
		Nixie_num[7]=temp_canshu/1%10;
	}
	else if(mod==5)//菜单5温湿度界面
	{
		Nixie_num[0]=26;//E
		Nixie_num[1]=20;//熄灭
		Nixie_num[2]=20;

		/*如果当前的湿度无效,则温度显示读取到的温度(只读取不储存和处理那个),湿度显示AA*/
		if(shidu_wuxiao==1)
		{
			Nixie_num[3]=wrong_temp/10%10;//温度
			Nixie_num[4]=wrong_temp/1%10;
			Nixie_num[5]=21;//-
			Nixie_num[6]=27;//湿度,显示A
			Nixie_num[7]=27;//A
		}
		else//数据有效,正常显示
		{
			Nixie_num[3]=Now_temp/10%10;//温度
			Nixie_num[4]=Now_temp/1%10;
			Nixie_num[5]=21;//-
			Nixie_num[6]=Now_shi_du/10%10;//湿度
			Nixie_num[7]=Now_shi_du/1%10;
		}
	}
}
bit led1_is_on=0;//这几个变量都是某个led灯的状态标志位,用于记录led灯的亮灭状态
bit led2_is_on=0;
bit led3_is_on=0;
bit led4_is_on=0;
bit led5_is_on=0;
bit led6_is_on=0;
void led_run(void)
{
	if(mod==0&&led1_is_on==0)//在时间菜单,led1点亮
	{
		LED_ON(0);
		led1_is_on=1;
	}
	else if(mod!=0&&led1_is_on==1)//否则,led1熄灭
	{
		LED_OFF(0);
		led1_is_on=0;
	}
	
	if((mod==1||mod==2||mod==3)&&led2_is_on==0)//在数据回显菜单,led2点亮
	{
		LED_ON(1);
		led2_is_on=1;
	}
	else if(!(mod==1||mod==2||mod==3)&&led2_is_on==1)//否则,led2熄灭
	{
		LED_OFF(1);
		led2_is_on=0;
	}
	
	if(mod==5&&led3_is_on==0)//在温湿度菜单,led3点亮
	{
		LED_ON(2);
		led3_is_on=1;
	}
	else if(mod!=5&&led3_is_on==1)//否则,led3熄灭
	{
		LED_OFF(2);
		led3_is_on=0;
	}
	
	if(Now_temp>temp_canshu)//如果读取到的温度值高于温度阈值,则led4闪烁
	{
		/*主函数的while(1)循环里有100ms延时,这里只需要保证进入一次led_run函数翻转一次led4的状态,即可闪烁*/
		if(led4_is_on==0)//当前led4熄灭,则点亮
		{
			LED_ON(3);
			led4_is_on=1;
		}
		else if(led4_is_on==1)//当前led4已电亮,则熄灭
		{
			LED_OFF(3);
			led4_is_on=0;
		}
	}
	else if(led4_is_on==1)//如果读取到的温度值不高于阈值,则led4熄灭
	{
		LED_OFF(3);
		led4_is_on=0;
	}
	
	if(shidu_wuxiao==1&&led5_is_on==0)//如果当前读取到的湿度无无效,则led5点亮
	{
		LED_ON(4);
		led5_is_on=1;
	}
	else if(shidu_wuxiao==0&&led5_is_on==1)//如果当前读取到的湿度有效,则led5熄灭
	{
		LED_OFF(4);
		led5_is_on=0;
	}
	
	if(is_up==1&&led6_is_on==0)//如果温度和湿度均比上一次读取到的值高(在run函数进行判断),则led6点亮
	{
		LED_ON(5);
		led6_is_on=1;
	}
	else if(is_up==0&&led6_is_on==1)//否则熄灭
	{
		LED_OFF(5);
		led6_is_on=0;
	}
}

onewire.c

cpp 复制代码
/*	# 	单总线代码片段说明
	1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
	2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
		中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <stc15.h>
#include <intrins.h>
#include "onewire.h"
sbit DQ=P1^4;
//
void Delay_OneWire(unsigned int t)  
{
	unsigned char i;
	while(t--){
		for(i=0;i<12;i++);
	}
}

//
void Write_DS18B20(unsigned char dat)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		DQ = 0;
		DQ = dat&0x01;
		Delay_OneWire(5);
		DQ = 1;
		dat >>= 1;
	}
	Delay_OneWire(5);
}

//
unsigned char Read_DS18B20(void)
{
	unsigned char i;
	unsigned char dat;
  
	for(i=0;i<8;i++)
	{
		DQ = 0;
		dat >>= 1;
		DQ = 1;
		if(DQ)
		{
			dat |= 0x80;
		}	    
		Delay_OneWire(5);
	}
	return dat;
}

//
bit init_ds18b20(void)
{
  	bit initflag = 0;
  	
  	DQ = 1;
  	Delay_OneWire(12);
  	DQ = 0;
  	Delay_OneWire(80);
  	DQ = 1;
  	Delay_OneWire(10); 
    initflag = DQ;     
  	Delay_OneWire(5);
  
  	return initflag;
}

unsigned int read_ds(void)
{
	unsigned char low=0;
	unsigned char high=0;
	unsigned int temp=0;
	
	init_ds18b20();
	Write_DS18B20(0xCC);
	Write_DS18B20(0x44);
	Delay_OneWire(200);
	
	init_ds18b20();
	Write_DS18B20(0xCC);
	Write_DS18B20(0xBE);
	low=Read_DS18B20();
	high=Read_DS18B20();
	
	temp=high;
	temp&=0x0F;
	temp<<=8;
	temp|=low;
	temp>>=4;
	
	return temp;
}

onewire.h

cpp 复制代码
#ifndef _ONEWIRE_H_
#define _ONEWIRE_H_

unsigned int read_ds(void);

#endif

iic.c

cpp 复制代码
/*	#   I2C代码片段说明
	1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
	2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
		中对单片机时钟频率的要求,进行代码调试和修改。
*/

#define DELAY_TIME	5
#include <stc15.h>
#include <intrins.h>
#include "iic.h"
sbit scl=P2^0;
sbit sda=P2^1;
//
static void I2C_Delay(unsigned char n)
{
    do
    {
        _nop_();_nop_();_nop_();_nop_();_nop_();
        _nop_();_nop_();_nop_();_nop_();_nop_();
        _nop_();_nop_();_nop_();_nop_();_nop_();		
    }
    while(n--);      	
}

//
void I2CStart(void)
{
    sda = 1;
    scl = 1;
	I2C_Delay(DELAY_TIME);
    sda = 0;
	I2C_Delay(DELAY_TIME);
    scl = 0;    
}

//
void I2CStop(void)
{
    sda = 0;
    scl = 1;
	I2C_Delay(DELAY_TIME);
    sda = 1;
	I2C_Delay(DELAY_TIME);
}

//
void I2CSendByte(unsigned char byt)
{
    unsigned char i;
	
    for(i=0; i<8; i++){
        scl = 0;
		I2C_Delay(DELAY_TIME);
        if(byt & 0x80){
            sda = 1;
        }
        else{
            sda = 0;
        }
		I2C_Delay(DELAY_TIME);
        scl = 1;
        byt <<= 1;
		I2C_Delay(DELAY_TIME);
    }
	
    scl = 0;  
}

//
unsigned char I2CReceiveByte(void)
{
	unsigned char da;
	unsigned char i;
	for(i=0;i<8;i++){   
		scl = 1;
		I2C_Delay(DELAY_TIME);
		da <<= 1;
		if(sda) 
			da |= 0x01;
		scl = 0;
		I2C_Delay(DELAY_TIME);
	}
	return da;    
}

//
unsigned char I2CWaitAck(void)
{
	unsigned char ackbit;
	
    scl = 1;
	I2C_Delay(DELAY_TIME);
    ackbit = sda; 
    scl = 0;
	I2C_Delay(DELAY_TIME);
	
	return ackbit;
}

//
void I2CSendAck(unsigned char ackbit)
{
    scl = 0;
    sda = ackbit; 
	I2C_Delay(DELAY_TIME);
    scl = 1;
	I2C_Delay(DELAY_TIME);
    scl = 0; 
	sda = 1;
	I2C_Delay(DELAY_TIME);
}

//void wirite_pcf(unsigned dat)
//{
//	I2CStart();
//	I2CSendByte(0x90);
//	I2CWaitAck();
//	I2CSendByte(0x40);
//	I2CWaitAck();
//	I2CSendByte(dat);
//	I2CWaitAck();
//	I2CStop();
//}

unsigned char read_pcf(unsigned add)
{
	unsigned char ad=0;
	
	I2CStart();
	I2CSendByte(0x90);
	I2CWaitAck();
	I2CSendByte(add);
	I2CWaitAck();
	I2CStop();
	
	I2CStart();
	I2CSendByte(0x91);
	I2CWaitAck();
	ad=I2CReceiveByte();
	I2CSendAck(1);
	I2CStop();
	
	return ad;
}

iic.h

cpp 复制代码
#ifndef _IIC_H_
#define _IIC_H_

//void wirite_pcf(unsigned dat);
unsigned char read_pcf(unsigned add);

#endif

ds1302.c

cpp 复制代码
/*	# 	DS1302代码片段说明
	1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
	2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
		中对单片机时钟频率的要求,进行代码调试和修改。
*/								
#include <stc15.h>
#include <intrins.h>
#include "ds1302.h"
sbit SCK=P1^7;
sbit SDA=P2^3;
sbit RST=P1^3;
unsigned char time[3]={5,3,13};
//
void Write_Ds1302(unsigned  char temp) 
{
	unsigned char i;
	for (i=0;i<8;i++)     	
	{ 
		SCK = 0;
		SDA = temp&0x01;
		temp>>=1; 
		SCK=1;
	}
}   

//
void Write_Ds1302_Byte( unsigned char address,unsigned char dat )     
{
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
 	RST=1; 	_nop_();  
 	Write_Ds1302(address);	
 	Write_Ds1302(dat);		
 	RST=0; 
}

//
unsigned char Read_Ds1302_Byte ( unsigned char address )
{
 	unsigned char i,temp=0x00;
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
 	RST=1;	_nop_();
 	Write_Ds1302(address);
 	for (i=0;i<8;i++) 	
 	{		
		SCK=0;
		temp>>=1;	
 		if(SDA)
 		temp|=0x80;	
 		SCK=1;
	} 
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
	SCK=1;	_nop_();
	SDA=0;	_nop_();
	SDA=1;	_nop_();
	return (temp);			
}
void ds1302_init(void)
{
	unsigned char add=0;
	unsigned char i=0;
	add=0x80;
	Write_Ds1302_Byte(0x8E,0x00);
	for(i=0;i<3;i++)
	{
		Write_Ds1302_Byte(add,(time[i]/10)<<4|(time[i]%10));
		add+=2;
	}
	Write_Ds1302_Byte(0x8E,0x80);
}
void read_time(void)
{
	unsigned char add=0;
	unsigned char dat=0;
	unsigned char i=0;
	add=0x81;
	for(i=0;i<3;i++)
	{
		dat=Read_Ds1302_Byte(add);
		time[i]=dat/16*10+dat%16;
		add+=2;
	}
}

ds1302,h

cpp 复制代码
#ifndef _DS1302_H_
#define _DS1302_H_
extern unsigned char time[3];
void ds1302_init(void);
void read_time(void);

#endif
相关推荐
博晶网络16 小时前
MR400D工业级4G路由器:TCP/IP与UDP协议,解锁工业物联网高效传输新范式‌
网络·单片机·嵌入式硬件
求梦82018 小时前
【力扣hot100题】旋转图像(15)
算法·leetcode·职场和发展
叁散21 小时前
实验项目1 RFID 标签实验
单片机·嵌入式硬件
比奇堡派星星1 天前
MCU 裸机时间片调度系统
单片机·嵌入式硬件
深圳市方中禾科技1 天前
LED驱动芯片FZH02,应用开发相关数据技术手册
单片机·嵌入式硬件·led
踩坑记录1 天前
leetcode hot100 11.盛最多水的容器 medium 双指针
算法·leetcode·职场和发展
yuanmenghao1 天前
CAN系列 — (6) CAN FD 带宽、CPU、中断:工程上是如何一起算的?
网络·驱动开发·单片机·mcu·自动驾驶·信息与通信
码农水水1 天前
中国邮政Java面试:热点Key的探测和本地缓存方案
java·开发语言·windows·缓存·面试·职场和发展·kafka
a程序小傲1 天前
小红书Java面试被问:TCC事务的悬挂、空回滚问题解决方案
java·开发语言·人工智能·后端·python·面试·职场和发展
Geminit1 天前
无人机培训,蚂蚁智飞在线训练,AI赋能新培训/学习模式
职场和发展