蓝桥杯单片机第十四届国赛注意点

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

考察部分:除了三大金刚(LED、按键、数码管)外,本套题还考察到了Ds18b20、DAC、继电器,超声波考察的不是特别多,还可以介绍。

关于新鲜点

1.在工厂模式下的校准值的设置,要注意有个符号位 -

2.在测距界面下,Led1~Led8亮灭的方式

3.考察到了S8和S9同时按下的双按键

两个注意点

一:当在测距界面按下S8时开启记录功能,连续记录6s,但是被记录的数据是最近一次的数据,也就是说,当只需要在开启记录功能后的第六秒,测量一次距离,并将值传给记录变量;

二:真实距离值等于测量的距离值加上校准的距离值


提示:以下是本篇文章正文内容,下面案例可供参考

一、关于几个外设的配置?

Ds18b20,DA输出这两个外设的配置相信大家早已烂熟于心,这里就不做展示了,有需要的小伙伴可以自行搜索。

下面来讲讲继电器、超声波,这两个外设的配置。

1.继电器

让我们先来了解一下什么是static关键字

在C语言中,`static` 关键字有多种用途,主要包括声明静态变量、静态函数以及定义静态成员变量和函数。以下是 `static` 关键字的一些常见用法:

1. **静态变量**:
   - 在函数内部,使用 `static` 声明的变量在函数调用结束后不会消失,而是保留其值,直到程序结束。
   - 这些变量的初始化只在第一次调用函数时进行,之后的函数调用会保留上一次的值。

2. **全局静态变量**:
   - 使用 `static` 声明的全局变量具有内部链接,这意味着它们只能在定义它们的文件中访问,而不能在其他文件中访问。

3. **静态函数**:
   - 使用 `static` 声明的函数只能在定义它们的文件中调用,这有助于避免命名冲突。

4. **静态成员变量和函数**:
   - 在结构体或类中,使用 `static` 声明的成员变量或函数是静态的,属于类型本身而不是类型的实例。

5. **编译器优化**:
   - `static` 关键字还可以帮助编译器进行优化,因为编译器知道 `static` 变量的生命周期,可以做出更好的优化决策。

6. **存储类说明符**:
   - `static` 是一种存储类说明符,它控制变量或函数的生命周期和可见性。

7. **初始化**:
   - 静态变量可以初始化,如果没有显式初始化,全局和静态变量会被自动初始化为0。

使用 `static` 关键字可以提高程序的可读性、可维护性,并有助于管理程序中变量和函数的生命周期。在多文件项目中,`static` 可以用来避免全局变量和函数的命名冲突。

代码部分展示

void Beep_Disp(unsigned char pos,enable)
{
	static unsigned char temp = 0x00;
	static unsigned char temp_old = 0xff;
	if(enable)
	{
		temp |= (0x01<<pos);
	}else{
		temp &= ~(0x01<<pos);
	}
	
	if(temp != temp_old)
	{
		P0 = temp;
		P2 = P2&0x1f | 0xa0;
        P2 &= 0x1f;
		temp_old = temp;
	}
}

分析

在这个 `Beep_Disp` 函数中,`static` 关键字用于声明两个局部变量 `temp` 和 `temp_old`,使它们在函数调用之间保持其值。这是 `static` 关键字在函数内部的一个常见用法,它允许这些变量在程序的整个生命周期内存在,而不仅仅是在函数调用的持续时间内。

让我们逐步分析这个函数:

1. **静态变量初始化**:
   - static unsigned char temp = 0x00;声明了一个静态变量 temp,并初始化为 0x00。

这意味着每次程序运行开始时,temp`都会被设置为 0x00。

   - static unsigned char temp_old = 0xff;声明了另一个静态变量 temp_old,并初始化为 0xff。

这同样意味着每次程序开始时,temp_old 都会被设置为 0xff。

2. **根据 enable 参数更新 temp**:
   - 如果 enable 参数为非零值(通常这意味着它应该启用),则 temp 通过位运算 `|=`(按位或赋值)与 `(0x01<<pos)` 结合,这会在 `temp` 的第 `pos` 位设置一个 `1`。

   - 如果 `enable` 参数为零,`temp` 通过位运算 `&=`(按位与赋值)与 `(~(0x01<<pos))` 结合,这会在 `temp` 的第 `pos` 位清零。

3. **检查 `temp` 是否发生变化**:
   - 如果 `temp` 的值与 `temp_old` 不同,这意味着输出状态已经改变。

4. **更新硬件状态**:
   - 如果 `temp` 发生变化,函数会通过 `P0 = temp;` 将 `temp` 的值赋给端口 `P0`,这可能连接到一些LED或其他输出设备。

   - 然后,根据:

                                P2 = P2 & 0x1f | 0xa0; 

                                P2 &= 0x1f;

这两行代码来选择HC573锁存器的特定通道,这可能是为了更新与 `P0` 端口连接的设备的状态。

5. **更新 `temp_old`**:
   - 无论 `temp` 是否变化,`temp_old` 都会被更新为当前的 `temp` 值,以便在下一次函数调用时检测变化。

通过使用 `static` 关键字,这个函数能够在每次调用时记住 `temp` 和 `temp_old` 的值,从而实现对输出设备状态的跟踪和更新。如果没有使用 `static`,`temp` 和 `temp_old` 将在每次函数调用结束后重置为其初始值,这将导致无法正确跟踪状态变化。

2.超声波

这里给还不了解的PCA的小伙伴们提供一个新思路,用PCA定时来配置。

其实在蓝桥杯单片机的这个开发板STC15f2k60s2中,有3(定时器0、定时器1、定时器2)+1个定时器(PCA)

PCA(Programmable Counter Array,可编程计数器阵列)是8051微控制器中的一个多功能硬件模块,它在蓝桥杯单片机竞赛中使用的单片机上也可能存在。PCA能够执行多种定时器和计数器相关的任务。

使用超声波测量距离的原理:

超声波相关硬件发送一个信号,当遇到障碍是就返回,相关硬件根据两次的时间差,从而计算出距离。

   公式 :                     距离 = 声速 * 发出超声波到接收返回的时间 / 2;

实现步骤:

1.产生8个40KHz的超声波,通过Tx引脚发射出去

void Ul_Wave_Init()
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		Tx =  1;
		Delay12us();
		Tx =  0;
		Delay12us();
	}
}

在这之前,先生成一个12us的延时,为什么是12us?

1s = 1000ms = 1000 000us

T = 1/f ;   f = 40Khz = 40 000;

所以T = 25us ,以25秒为一个周期他的一半是12或13,所以我们可以生成一个12us或13us的延时

void Delay12us()		//@12.000MHz
{
	unsigned char i;

	_nop_();    //注意:用到了这个函数,要添加相应的头文件 -->  #include <intrins.h>
	_nop_();
	i = 33;
	while (--i);
}

2.发送后,开启定时器,计算脉冲

3.等待超声波信号返回,如果接收到反射回来的信号,Rx引脚变为高电平

4.停止定时器,读取脉冲个数,即获得时间T

5.根据公式算出距离

unsigned char Wave_Dat()
{
	unsigned int time;
	CMOD = 0;
	CH =CL = 0;
	Wave_Init();
	CR = 1;
	while((Rx == 1)&&(CF == 0));
	CR = 0;
	if(CF == 0)
	{
		time = (CH<<8|CL)*0.017;//*0.017 等价于*340(声速)/2/10000;
		return time;
	}else{
		CF = 0;
		return 0;
	}
}

为什么上面的*0.017等价于我上面写的呢?

因为由公式L = V * T/2;其中,V的单位为m/s,而T的单位为us,然后我们计算出来的距离的单位是厘米,所以涉及了单位的转化。为什么要将这些,

因为如果是一般的超声波,他的声速我们默认是340m/s,而在本题中,声速是可以改变的,所以我们这里要用一个变量

而且我们这个变量除了在超声波文件中可以用,还要在main.c文件中可以使用,这里就需要了解另一个关键字extern

在超声波相关函数Ultrasound.c中,

#include <STC15F2K60S2.H>
#include "Ultrasound.h"
#include <intrins.h>

sbit Tx = P1^0;
sbit Rx = P1^1;

extern unsigned int Speed_Num;//传播速度

void Delay12us()		//@12.000MHz
{
	unsigned char i;

	_nop_();
	_nop_();
	i = 33;
	while (--i);
}


void Ul_Wave_Init()
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		Tx =  1;
		Delay12us();
		Tx =  0;
		Delay12us();
	}
}



unsigned char Ul_Wave_Data()
{
	unsigned int time;
	CMOD = 0;
	CH = CL = 0;
	Ul_Wave_Init();
	CR = 1;
	while((Rx == 1)&& (CF == 0));
	CR = 0;
	if(CF == 0)
	{
		time = (CH <<8| CL)*(Speed_Num/20000.0);
		return time;
	}else{
		CF = 0;
		return 0;
	}
	
}

在这里用extern关键字定义一个变量

extern unsigned int Speed_Num;//传播速度    

注意:这里只定义,不要赋值,赋值在main.c文件中声明的时候进行。

不然会报错!!!

main.c文件中

uint Speed_Num = 340;//传播速度    */定义在了Ultrasound.c文件中

extern关键之关于多次声明,但只能定义一次。

以上,我们完成了基础外设的配置,接下来我们来解决题目中的一些坑。

二、本套题中的阴谋 \(^o^)/~

1.关于校准值的相关设置

本题中校准值的设置是-90~90;因为我觉得有负数不好处理,所以我操作了一下

我将范围设置为0~180,这样我这里的90,就相当于题目中指的0;

大于等于90,就显示                   x - 90 

而小于90的部分就显示           - (90 - x)

                       uchar Aux_values = 90;/* 关于校准值既然范围是-90到90,有负数有点麻烦,                        
                                                所以我们不放将范围等价到0-180,以90为初始值*/

                        bit Aux_Flag;//当Aux_values >=90,为 0   当Aux_values<90,为 1

                        smg[0] = 17;//F
                        smg[1] = Factory_Son_mode + 1;
                        smg[2] = smg[3] = smg[4] = 10;
                        if(Aux_values >= 90)
                        {
                            smg[5] = 10;
                            smg[6] = (Aux_values - 90) > 9? ((Aux_values - 90)/10) : 10;
                            smg[7] = (Aux_values - 90)%10;
                        }else{
                           if((90 - Aux_values) > 9)
                           {
                             smg[5] = 11;//-
                             smg[6] = (90 - Aux_values) > 9? ((90 - Aux_values)/10) : 10;
                             smg[7] = (90 - Aux_values)%10;
                           }else {
                                smg[5] = 10;
                                smg[6] = 11;//-
                                smg[7] = (90 - Aux_values)%10;
                            }
                        }
                        smg_point[6] = 0;

为了防止大家看不懂我的数码管显示

这里附上数码管相关底层

#include <STC15F2K60S2.H>
#include "Smg.h"


code smg_weixuan[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
code unsigned char smg_duanxuan[] = 
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0xff, //(空)10
0xbf, //"-" 11
0x88, //A12
0x83, //b13
0xc6, //C14
0xa1, //d15
0x86, //E16
0x8e, //F17
0x8c  //P18
};


void Smg_Disp(unsigned char x,y,point)
{
	P0 = 0xff;
	P2 = P2&0x1f|0xe0;
    P2 &= 0x1f;
	
	P0 = smg_weixuan[x];
	P0 = 0xff;
	P2 = P2&0x1f|0xc0;
	P2 &= 0x1f;

	P0 = smg_duanxuan[y];
	if(point)
		P0 &= 0x7f;
	P2 = P2&0x1f|0xe0;
    P2 &= 0x1f;
}

//数码管
smg[8] = {10,10,10,10,10,10,10,10};
smg_point[8] = {0,0,0,0,0,0,0,0};

关于测距界面LED的亮灭,有点想计算机中2进制的转10进制

L1(P00)L2(P01)L3(P02)L4(P03)L5(P04)L6(P05)L7(P06)L8(P07)
二进制00001010
十进制0* 2^00* 2^10* 2^20* 2^3 1* 2^40* 2^51* 2^60*2^7

结果 = 1* 2^4 + 1* 2 ^6 = 16 + 64 = 80;

所以我们只需用二进制表示相关LED亮灭即可,

为防止小伙伴们看不懂我写的LED,下面先展示我的LED相关底层

#include <STC15F2K60S2.H>
#include "Led.h"

void Led_Disp(unsigned char pos,enable)
{
	static unsigned char temp = 0x00;
	static unsigned char temp_old = 0xff;
	if(enable)
	{
		temp |= (0x01<<pos);
	}else{
		temp &= ~(0x01<<pos);
	}
	
	if(temp != temp_old)
	{
		P0 = ~temp;
		P2 = P2&0x1f | 0x80;
        P2 &= 0x1f;
		temp_old = temp;
	}
}

//LED
led[8] = {0,0,0,0,0,0,0,0};

生成一个1ms的定时器0;将数码管,按键,LED的减速放在定时器中,我们尽量不要使用delay延时,因为这个很呆,当执行delay时我们的单片机是不能干别的事情的,这就可能导致数据执行时,数据刷新不到位,可能会有一系列Bug等着你。

void Timer0Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0 = 1;
	EA = 1;
}

//*******中断服务函数
void Timer0() interrupt 1
{
	static uchar a;
	if(++Key_Slow_Down == 10)  Key_Slow_Down = 0;
	if(++Led_Slow_Down == 200) Led_Slow_Down = 0;
	if(++Smg_Slow_Down == 500) Smg_Slow_Down = 0;
	Smg_Disp(a,smg[a],smg_point[a]);
	Led_Disp(a,led[a]);
	
	if(++a == 8) a = 0;


 .....
}

不要忘记将Timer0Init()放在程序的开始

//******主程序
void main()
{
	AllStart();
	while(Temperature_Read() == 85);
*****Timer0Init();*******
	while(1)
	{
	    SMG_Process();
		KEY_Process();
		Led_Process();
		Other_Process();
	}
}

然后就是在测距模式下Led相关的亮灭了

代码展示

void Led_Process()
{
	uchar i;
	
	
	if(Mode == 2){
		for(i=1;i<8;i++)
		 led[i] = 0;
		led[0] = L1_Flag?1:0;
	}
	
	
	if(Led_Slow_Down) return;
	Led_Slow_Down = 1;
	
	if(Mode == 0)
	{
		if((Ultrasound_old - Ultrasound < 10) || (Ultrasound - Ultrasound_old <10))
		{
			led[0] = Ultrasound%2;
			led[1] = Ultrasound/2%2;
			led[2] = Ultrasound/4%2;
			led[3] = Ultrasound/8%2;
			led[4] = Ultrasound/16%2;
			led[5] = Ultrasound/32%2;
			led[6] = Ultrasound/64%2;
			led[7] = Ultrasound/128;
		}
	}else if(Mode == 1)
	{
		for(i=0;i<7;i++)
		 led[i] = 0;
		led[7] = 1;
	}
}

上述代码中if((Ultrasound_old - Ultrasound < 10) || (Ultrasound - Ultrasound_old <10))

 这个是为了防止我们数码管上显示的距离突然跳变的,其实就是为了好看一点,也可以不加。

2.双按键

新考点,两个按键都按下。

关于按键,我采用的是三行按键法,有人可能会问,为什么不用状态机,实不相瞒,其实我以前会状态机法,但是自从学了三行按键法,因为三行按键法只有三行,比较简介易上手(其实就是我懒,不想多敲)渐渐地就忘记了。

关于三行按键法

在使用前要先定义几个变量

//按键
uchar Key_Val,Key_Down,Key_Up,Key_Old;


Key_Val = Key_Scan();
//三行按键法
Key_Down = Key_Val & (Key_Val ^ Key_Old);
Key_Up 	 = ~Key_Val & (Key_Val ^ Key_Old);
Key_Old = Key_Val;

这里的Key_Down和Key_Up都是瞬时值

而Key_Old的值比Key_Val延后10ms

为什么是10ms呢?

因为我们在定时器中断中,按键的减速变量设置的是10ms

按键之所以要减速是因为要去除抖动,相信大家都知道了。

其中,

Key_Val = Key_Scan();  //获取被按下的按键值

那么我们有要求看Key_Scan()这个函数

//矩阵按键
unsigned char Key_Scan()
{
	unsigned char temp = 0;
	
//第一行
	P44 = 0;P42 = 1;P35 = 1;P34 = 1;
	if(P33 == 0) temp = 4;
	if(P32 == 0) temp = 5;
	if(P31 == 0) temp = 6;
	if(P30 == 0) temp = 7;	
//第二行
	P44 = 1;P42 = 0;P35 = 1;P34 = 1;
	if(P33 == 0) temp = 8;
	if(P32 == 0) temp = 9;
	if(P31 == 0) temp = 10;
	if(P30 == 0) temp = 11;	
	if(P33 == 0 && P32 == 0) temp = 89;
//第三行
	P44 = 1;P42 = 1;P35 = 0;P34 = 1;
	if(P33 == 0) temp = 12;
	if(P32 == 0) temp = 13;
	if(P31 == 0) temp = 14;
	if(P30 == 0) temp = 15;	
//第四行
	P44 = 1;P42 = 1;P35 = 1;P34 = 0;
	if(P33 == 0) temp = 16;
	if(P32 == 0) temp = 17;
	if(P31 == 0) temp = 18;
	if(P30 == 0) temp = 19;		
	
	return temp;
}

有人可能发现了扫描第二行时的if(P33 == 0 && P32 == 0) temp = 89;

后面会讲解

   注意这里的 temp变量一定要赋初始值  ,不然按键会有问题。

   unsigned char temp = 0; 

下面关于三行按键法

这⾥以S4(0100)为例,来进⾏说明

键盘状态Key_ValKey_OldKey_Val^Key_OldKey_DownKey_Up
未按下000000000000^0000 = 000000000000
按下过程中(10ms)010000000100^0000 = 010001000000
按下稳定(10ms后)010001000100^0100= 000000000000
抬起过程(10ms)000001000000^0100 = 010000000100

双按键相关代码展示

void KEY_Process()
{
	if(Key_Slow_Down) return;
	Key_Slow_Down = 1;
	
	Key_Val = Key_Scan();
	//三行按键法
	Key_Down = Key_Val & (Key_Val ^ Key_Old);
	Key_Up 	 = ~Key_Val & (Key_Val ^ Key_Old);
	Key_Old = Key_Val;
	
	if(Key_Old == 89)
	{
		S8S9_Flag = 1;
		
		Key_Clock = 1;
	}
	if(Key_Clock && Key_Old) return;//还在同时按下
		Key_Clock = 0;


        .......
}

上述代码中的Key_Clock 变量其实是给双按键上来一个锁,因为本套题目中,S8与S9单独按下时均有功能,所以为了防止误触发。

而当我同时按下时我让我的一个标志位置1  --> S8S9_Flag = 1;  

在Other_Process()函数中执行相关功能:

void Other_Process()
{
   if(Reset)
	{
		Distance_Para = 40;
		T_Para_10x = 300;
		Aux_values = 90;
		Speed_Num = 340;
		DA_Voltage = 1;
		Reset = 0;
	}
}


总结

提示:这里对文章进行总结:
本套题看起来不难,但其实还是有强度,需要对外设的使用了如指掌,还有新颖的考点,我没有参加过第十四届蓝桥杯,但是我觉得要想在考场上把功能都实现,还真是不简单!!!

希望大家看完我的这篇文章能有所收获。

谢谢你的观看。

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值