蓝桥杯单片机第七届国赛笔记

蓝桥杯单片机第七届国赛笔记:

做完这届题目之后有很多感触和一些好的思想,想分享一下顺便巩固巩固

1.摆脱标志位的依赖:
好的编程我认为是用最简洁,清晰的程序“最近的路径” 去实现某个功能。如果遇到问题就做标志位这种方法我认为有点low了,对思维和编程能力的提高效果微乎其微,以后的编程中我也一定要避免这个问题。
2.再写完程序后,发现现象不对,一定要进行总结,为什么不对,效果没出来的原因是什么?
3.制造寄存器,(这是我起的名字)这个套路非常,非常,非常实用本次题目。

下面就开始正文了:
别被题目吓到,这次题目方法用对,它就是 “Paper Tiger”
这届虽然简单,但我感触颇深,所以写篇总结记录下。

一、个人错误篇:

首先我仅仅我在实验中错误地地方列出来

1. ‘&’和‘&&’:
(对应上面,一个是‘位与’和‘逻辑与’ )当时也不知道咋想的,感觉逻辑与好看,就吧矩阵按键部分应该的操作都换成了逻辑与 看程序吧;

#include "bsp_key.h"
unsigned char key_trigger()
{
	unsigned char number = 7;
	unsigned char temp;
	P44 = 0; P42 = 1;
	temp = P3;
	P42 = 0; P44 = 1;
	temp = (temp << 4) | (P3 & 0X0F);  //这里的(P3 & 0X0F) 我写成了(P3 && 0X0F)
	switch(~temp)
	{
		case 0x80: number = 4; break;
		case 0x40: number = 5; break;
		case 0x20: number = 6; break;
		case 0x10: number = 7; break;

		case 0x08: number = 8; break;
		case 0x04: number = 9; break;
		case 0x02: number = 10; break;
		case 0x01: number = 11; break;	
		case 0x00: number = 0; break;
		default: number = 0; break;
	}
	return number;
}

上面错误点我已经备注了,错误原因很简单:(但就是这个我程序按键部分全部无效)

原因:(针对第二列按键)
无按键按下时: P3第四位默认I/O都是高电平,逻辑与以后也就为 ‘真’。也就会导致temp低四位部分一直为 ‘0001’,高四位不会影响。然而进入switch进行匹配的也就xxxx0001 取反以后就成了 xxxx1110也就是0X‘X’E 显然肯定不会有匹配的按键,所以无论按下呢个按键都只对应进入default指令。

有按键按下时:
按下小于4个按键时,都会造P3低四位至少有一个I/O口为高,同样保证了逻辑‘真’
第二列全部按下时,也就造成P3低四个I/O口都为‘0’也就造成了逻辑‘假’,但取反以后为1111同样没有对应的case。

2. 强制转化:
这部分有歧义。

sprintf(seg_buf,"-2-%05u",(unsigned int)(100000 / read_555_number));

这里是用sprintf将字符串打印到seg_buf中去。
有的说占位符是整型,如果表示浮点型的数,呢这个浮点型的数将会被强制转换
但我通过实验现象证明,需要加上强制转换,毕竟加上强制转换也更加牢靠,就算自身不带,也不会出现问题。
(如果不加现象就是显示数据变成乱码)
3. 马虎大意
(1)

	Write_Ds1302_Byte(0x8e,0x00);
	temp = (((timer_init[0] / 10) << 4) | (timer_init[0] % 10));
	Write_Ds1302_Byte(0x84,temp);
	
	temp = (((timer_init[1] / 10) << 4) | (timer_init[1] % 10));
	Write_Ds1302_Byte(0x82,temp);
	
	temp = (((timer_init[2] / 10) << 4) | (timer_init[2] % 10));
	Write_Ds1302_Byte(0x80,temp);

在DS1302中我竟然把 % 写成了 * 导致写入初始时间错误。
(2)
定时器0读取频率,我竟然用定时器1来读取,我也是醉了。

二、优点篇:

1 .制造寄存器
(这一优点在本次例程中体现的淋漓尽致,不仅方便审查错误,而且思路也很清晰。)

unsigned char running_mode = 0x00;
0x100x11
频率频率周期
0x200x210x22
电压参数电压上限阈值电压下限阈值
0x400x410x420x43
时间小时设置分钟设置秒设置
0x800x81
最近一次电压波动的类型最近一次电压波动时间

(像上面这样定义一个寄存器,不同情况存储对应的值,当我们需要对不同情况进行处理的时候只需要判断这个一个标志位就可以了。省了很多标志位,思路也很清晰。)
下面用例子来说明其好处:

void key_pricedure()
{
	case 7: //时间
		if(running_mode & 0x40)  
		{
			running_mode = 0x40;
			write_timer_fun(user_timer);
		}
		else running_mode = 0x40; //进入else语句使
		break;
			//.
			//.
			//.
	case 4: //功能
		switch(running_mode & 0xf0)
		{
			case 0x40:  //时间
				if(++running_mode == 0x44) running_mode = 0x41;	
				break;
		}
}
void seg_pricedure()
{
	switch(running_mode)
	{
		case 0x40:
			read_timer_fun(user_timer);
			sprintf(seg_buf,"%2d-%2d-%2d",(unsigned int)user_timer[0],(unsigned int)user_timer[1],(unsigned int)user_timer[2]);
			break;
		case 0x41:
		case 0x42:
		case 0x43:
			sprintf(seg_buf,"%2d-%2d-%2d",(unsigned int)user_timer[0],(unsigned int)user_timer[1],(unsigned int)user_timer[2]);			
			break;
			//.
			//.
			//.
	}
}

解释:
(第一次按下S7进入时间显示界面)
第一次按下S7时 由于寄存器初始化时0x00,所以if括号内为0,(0 & 任何数 = 0) 也就不进入if语句,进入else后使寄存器制位成 ox40对应我们提前的配置,可以看出程序进入了时间显示界面,在显示处理函数中判断我们的寄存器,发现寄存器变成0x40 对应开始读取DS1302时间到user_timer[ ] 数组内,并开始显示数组的内容,也就是当前时间。

(按下S4进入时间设置界面)
我们按下S4 以后switch语句判断我们寄存器高四位,(细心的同学就发现了,我们寄存器高四位存放的是模式,低四位存放的是该模式下的状态。)由于running_mode & 0xf0 就代表我们只判断高四位,低四位不考虑其值,根据我们的模式,进行状态改变,根据我们对寄存器的配置可以看出,如果不断按下S4 呢我们就处于在时间设置的 时,分,秒的状态不段切换,在通过加减按键对其更改。
说到这里,此处有个问题不知道大家考虑进去没?
如果时间还在一直走,呢设置起来不是很难受,所以我们在设置的时候不能让时间走了。
实现的方法就是只有在时间显示界面下(也就是0x40)让其读取时间到数组内,其他设置状态(0x41,0x42…)不进行读取时间,也就时间了时间暂停的效果。

(题目要求再次按下S7时,表示时间设置成功。)
当我们再次按下S7 时,判断 running_mode & 0x40 是否为真,如果我们没有按下其余模式按键我们寄存器的高四位依然是0x40 ,所以if括号内为1 (任何数 & 本身 = 任何数) ,也就说明我们是第二次按下S7了,根据题目要求我们需要对DS1302重新赋值成我们改边后的参数,同时进入了时间显示界面。
这里也有一点应考虑的!
如果在设置时间按下其他模式按键,效果是怎样的?
我这里只说结论,原理大家想一下便知其所以然。
答案是:
进入其他状态,但下次按下时间显示界面的时候依然是按照设置之前的时间一直在进行加加。

呢这是一个,两个,三个怎么办?
如果你理解了这个技巧,你也就不用担心这个问题了。

2 .减少标志位
我以后也一定减少使用标志位,这样对个人思维的提高真的很有帮助。
(相对于标志位而言,前两天还听到有人说“遇到问题怎么办,一个标志位不够就来十个”真的是说到痛点上了,所以得改)
这里看完我写的,1s闪烁以后,我就对我以前写的说 拜拜!

void timer1() interrupt 3
{
	dida_number++;
	//*
	//*
	//*
}
if((dida_number - seg_delay) > 1000)
{
	switch(running_mode)
	{
		case 0x41:
			seg_buf[0] = ' '; seg_buf[1] = ' ';
			break;
		case 0x42:
			seg_buf[3] = ' '; seg_buf[4] = ' ';
			break;
		case 0x43:
			seg_buf[6] = ' '; seg_buf[7] = ' ';
			break;
		case 0x21:
			seg_buf[0] = ' '; seg_buf[1] = ' '; seg_buf[2] = ' '; seg_buf[3] = ' ';
			break;
		case 0x22:
			seg_buf[4] = ' '; seg_buf[5] = ' '; seg_buf[6] = ' '; seg_buf[7] = ' ';
			break;
	}
	if((dida_number - seg_delay) > 2000) 
	seg_delay = dida_number;
}	

这个原理太简单,我也就懒得介绍了。
大家琢磨琢磨吧,如果能用上你就知道这个的强大了!

在这里插入图片描述

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数码管是一种常见的数字显示器件,可以用于显示各种数字、字母等字符。在单片机应用中,数码管通常用于显示计数器、计时器、温度、湿度等实时数据。 数码管的种类有很多,包括共阳数码管、共阴数码管、共阳共阴混合数码管等。其中,共阳数码管是最常见的一种,也是本文所涉及的数码管类型。 单片机控制数码管的原理是通过对数码管的各个管脚进行控制,使其显示相应的数字或字符。数码管的控制方式有两种,即静态显示和动态显示。 静态显示是指将要显示的数字或字符的每一位分别输出到数码管的每个管脚上,然后使其保持不变,从而实现显示效果。静态显示的缺点是需要使用大量的I/O口,且不能灵活地改变显示内容。 动态显示是指将要显示的数字或字符的每一位依次输出到数码管的每个管脚上,并在短时间内快速切换下一个数字或字符,从而形成连续的显示效果。动态显示的优点是可以使用较少的I/O口,且可以灵活地改变显示内容。 以下是一个简单的动态显示数码管的实现示例: 1. 定义数码管的引脚 ```c #define DIG_PORT P2 // 数码管位选端口 #define DIG_COM 0x00 // 数码管位选端口初始值 #define LED_PORT P0 // 数码管段选端口 ``` 2. 定义数码管显示的数字或字符 ```c unsigned char code ledChar[] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f }; ``` 3. 实现数码管动态显示函数 ```c void display(unsigned char i) { unsigned char j, k; for (j = 0; j < 8; j++) { DIG_PORT = DIG_COM | (1 << j); // 选择数码管位(从左到右) for (k = 0; k < 100; k++); // 延时,视情况可调整 LED_PORT = ledChar[i]; // 显示数码管上的数字或字符 } } ``` 4. 调用数码管动态显示函数 ```c int main() { unsigned char i = 0; while (1) { display(i % 10); // 显示数字 i 的个位数 i++; } return 0; } ``` 以上就是一个简单的数码管动态显示的实现示例。需要注意的是,数码管的控制方式和具体实现方法可能因不同的硬件平台和编程语言而有所不同。因此,在具体应用中需要根据实际情况进行适当的调整和修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值