蓝桥杯单片机第十届国赛 部分功能解析

@蓝桥杯第十届国赛部分功能解析TOC

蓝桥杯单片机第十届国赛 部分功能解析

备注: 这是本人第一次发表的文章,内容有不足、有问题、有改进的地方请在评论区留言 (主要供给个人复习,随手一写,对大佬无用请转走)

按键部分:

下降沿代码

	key_number = key_trigger();
	key_change = (key_number ^ Key_old) & key_number;
	Key_old = key_number;
(这三行代码是从蓝桥杯官方指导书上学到的,非常非常实用。)
	备注:
	key_trigger() 负责对按下的按键进行采集并返回。
	key_number    负责读取key_trigger()的返回值。
	key_change    负责记录下降沿触发的按键。
	key_old       负责记录按键的上一个状态

拿例子说话
当有按键’4’按下时:
key_number = 4;
key_old = 0;
key_change = (4 ^ 0) & 4;
key_old = 4;
那么key_change的值 就为:4
(key_change 就得到了一次按下的按键值)

这是单片机在有按键按下时的第一次扫描,第二次扫描的变化及以后
key_number = 4;
key_old = 4;
key_change = (4 ^ 4) & 4;
key_old = 4;
得到的key_change结果就是:0
(这时候如果我们一直按着不松手得到的key_change结果也就一直是 0。呢就会有人怀疑为什么 & key_number,这一步到底有什么用?答案就在下面。)

当我们松手时
key_number = 0;
key_old = 4;
key_change = (0 ^ 4) & 0;
key_old = 0;
我们仔细观察这个 & key_number 这步操作,发现 key_change 仍然为 0 试想一下如果没有这步操作将会是什么?

理解了以后也就解决了上面的问题。我们发现如果触发一个按键以后 key_change仅仅会得到一次按下的键值,其余都是0,也就得到了下降沿的检测;

扩展 上升沿

	key_change = (~key_number) & (key_number ^ Key_old);

这里没有使用到,所以不再赘述,可以按照如上方法进行验证

长按按键(1s)+ 按键汇总

代码如下:

void timer0() interrupt 1 //12mhz 1ms
{
	if(key_delay) key_delay--;  
}
void key_pricedure()
{
	if(key_slow_down) return; //减速
	key_slow_down = 1; //此行代码是为了提高效率 在规定时间内仅进入一次,防止多次进入 
	
	key_number = key_trigger();
	key_change = (key_number ^ Key_old) & key_number;
	Key_old = key_number; //上面对此部分进行了解析

	switch(key_change)
	{
		case 8:
			key_delay = 1000;  
			//......下面部分加入按键按下时操作的代码,且长按不会影响下面的内容
			
	}
	if((key_number == 8) && (key_delay == 0)) //按键8触发 同时保持了1s
	{	
		key_delay = 1000;  //1s进入一次长按代码 且短按代码只进入一次
		//......下面部分加入按键长按的代码
		//DAC_output_flag ^= 1; 例子
	}
}

(如果我们按下S8按键,使switch语句中的case 8执行一次。)
“按下时间” < 1s:
在一秒以内 “key_number = 0” ,此时 key_delay 还在不断的进行"-- --"操作,等减到0的时候,key_number早就变成了0 逃之夭夭。
那么程序也就不会执行到长按的代码 。
“按下时间” >= 1s:
当按下S8以后1S内不松手,也就代表着key_delay = 0 的时候此时 key_number = 8;也就符合长按要求。

但应注意根据需求加 key_delay = 1000;
例如:上面的 DAC_output_flag ^= 1 ; 如果没有加key_delay = 1000; 进入长按程序后就会使这个标志位一直在翻转,你根本不知道你松手以后会变成0\1;加了以后就是1S进入一次长按,翻转一次。
如果我们需要长按后仅仅触发一次,那么最笨的方法就是进入长按函数后加大key_delay 的值,使下一次进入增大到很长时间。( 当然也可以增加标志位来操作,效果都一样。)

当我们在松手的时候虽然key_delay 可能还在一直“减减”但也无伤大雅,因为我们按下S8按键以后仍然会对其重新赋值!

超声波部分:

代码如下:

sbit csb_TI = P1^0;
sbit csb_RI = P1^1;
void timer1_init()
{
	AUXR &= 0XBF;
	TMOD &= 0X0F;
	TCON |= 0X40;
	
	TL1 = 0xF4;		//设置定时初值
	TH1 = 0xFF;		//设置定时初值
}

unsigned int csb_distance_fun() //最大距离99
{
	unsigned char number = 10; //发送5个周期的超声波
	unsigned int temp;
	TL1 = 0xF4;		//设置定时初值 
	TH1 = 0xFF;		//设置定时初值 
	csb_TI = 0; 
	while(number--) //控制翻转次数 10次 也就是五个周期,发送5个周期的超声波
	{
		while(!TF1); //12ms会使csb_TI 进行一次翻转 也就制造了 40000hz的频率
		TF1 = 0;
		csb_TI ^= 1;
	}	
	TR1 = 0; //如果不关掉定时器,由于12ms很快 可能会使定时器1出发标志为 ‘1’ 那么下面就影响了(这段解释有瑕疵)
	TH1 = 0x00; 
	TL1 = 0x00;
	TR1 = 1;
	while(!TF1 && csb_RI); //检测是否接收到超声波信号 如果有那么csb_RI会被硬件拉低 或者就是长时间未接收到超声波信号都会打破死循环。
	if(TF1) //是否是时间长未接收到超声波信号,这个时间长度是 65536ms 因为上面使TH1和TL1 都为0了
	{
		TF1 = 0;
		temp = 99; 
	}
	else //如果接收到超声波信号
**加粗样式**	{
		temp = (TH1 << 8) + TL1;
		temp = (unsigned int)(temp * 0.017); //距离计算 = 声速 * 时间 / 2
		if(temp > 99) temp = 99;
	}
	return temp;	
}

超声波测距原理上面的备注已经比较详细了,这里不再赘述。

这边额外引出一点:
特别注意一点,如果没有用产生40Khz频率的定时器中断服务函数,就一定要关闭中断允许。否则触发中断以后没有指定的中断服务函数,程序就进入了死循环(原理这块我也不理解,后期查到了补上来)。
但是不开中断允许会影响标志位吗?答案当然不会的,所以我们就采用标志位查询法来制造频率。

在这里插入图片描述

看上图!不会影响TF1 (也就是定时器1中断标志位)制位,但ET1关闭后不会产生中断请求,也就不会进入中断服务函数,不会死循环了。

  • 但是在超声波部分我遇到了一个问题:
    temp = (TH1 << 8) + TL1;
    temp = (unsigned int)(temp * 0.017); //距离计算 = 声速 * 时间 / 2
    如果换成
    temp = (((TH1 << 8) + TL1)* 17)/ 1000;
    以后会出现问题
    大概就是如果超过65内部就会进位 比如66就成了0,67成了1…一直没有弄清楚原因,有知道的大佬请留言,万分感谢。

串口部分:

1.万能算法

这段内容主要介绍PC机发送的指令有共同点,应对这类题目我们的突破口就是这个共同点。
拿第十届国赛题目为例:

由于我们每次有效指令的结尾都是‘\n’这也就是个突破口,我们只要检测’\n’来了以后,
再进行判断指令的内容是否符合。

//*******接受指令
void uart1() interrupt 4
{
	if(RI)
	{
		RI = 0;
		uart_read_buf[uart_temp_number++] = SBUF;
	}
}

//**************检测指令
void uart_pricedure()
{
	if(uart_read_buf[uart_temp_number - 1] == '\r')
	{
		uart_temp_number = 0; //这一步是关键 当添加了这一步以后如果收到正确指令仅发送一次 由于uart_temp_number - 1 = 255;呢么就一定不符合条件了
		if((uart_read_buf[0] == 'S') && (uart_read_buf[1] == 'T') && (uart_read_buf[2] == '\n'))
		//对应正确指令待回应的内容
		else if((uart_read_buf[0] == 'P') && (uart_read_buf[1] == 'A') && (uart_read_buf[2] == 'R') && (uart_read_buf[3] == 'A') && (uart_read_buf[4] == '\n'))
		//对应正确指令待回应的内容	
	}
}

呢么新问题就诞生了,如果没有以\n结尾的指令怎么办?(下列可能存在问题,欢迎指正)

这我们就回到了一个原始的问题,波特率是什么?
答案就是:每秒传输的“bit”的大小,注意这里是‘位’而不是‘字节’。

而我们又知道一个字节 = 8位,在传输时以字符串(文本)传输,所以字符串里的一个字母占一个字节。转义字符也占一个字节。
而串口的发送又有用户规定的校验位,停止位,起始位,数据位组成一帧数据。
一般情况我们用不到校验,所以在发送数据时有:停止位占一位,数据为8位。 加一起也就是9位。假设波特率是9600 呢末发送一个字母所用的时间就是 9/9600 秒 而我们最长需要发送6个字节(PARA\n\r)也就是 54/9600秒 计算后为5.6毫秒。也就是说我们如果有字节发送给单片机了最长5.6ms发送完毕,超过时间的全是错误信息。

为了简单高效处理,我们可以做一个串口减速,减速时常为了确保准确性我们设置为200ms检测一次。(一般情况下肯定满足题目要求)所以我把有一丢丢瑕疵版的串口代码改成如下:

void timer() interrupt 1
{
	if(++uart_slow_down == 200) uart_slow_down = 0;
}


void uart_pricedure()
{
//***********************************CORE
	if(uart_slow_down) return//这个减速可是核心,200ms进入一次判断收到的指令是否正确,如果不正确直接输出error便可
	uart_slow_down = 1;
//***********************************CORE


	
	if(uart_read_buf[uart_temp_number - 1] == '\n')
	{
		if((uart_read_buf[0] == 'S') && (uart_read_buf[1] == 'T') && (uart_read_buf[2] == '\r'))
		{
			uart_temp_number = 0; 
			sprintf(uart_send_buf,"right1\r\n");
		}
		else if((uart_read_buf[0] == 'P') && (uart_read_buf[1] == 'A') && (uart_read_buf[2] == 'R') && (uart_read_buf[3] == 'A') && (uart_read_buf[4] == '\r'))
		{		
			uart_temp_number = 0; 
			sprintf(uart_send_buf,"right2\r\n");
		}
		uart_send_fun(uart_send_buf);

	}
	
	if(uart_temp_number != 0)
	{
		sprintf(uart_send_buf,"error\r\n");
		uart_send_fun(uart_send_buf);
		uart_temp_number = 0;
	}
}

呢瑕疵就暴露出来了,如果我们输入正确信息但没接收完就进入了?呢不就有BUG了!

呢咱们就掰扯掰扯,输入正确信息耗时大约6ms而我们减速是200ms,也就是3%的概率出BUG,如果你把减速加长,波特率加大,这样会减少BUG。而且这是我认为最简单的方法。
这个BUG也不严重,完全不影响下次输入正确信息。而且我假设把波特率加到19200减速加到400这样就只有0.7%的BUG 应对考试没问题鸭~
其他串口问题按照这个思路走就可以,举一反三。

最后祝大家都能拿到如愿成绩,旗开得胜,马到成功。

(认为不好的我也没要求你看,请转走,但我所说的有问题还请大家指正)

在这里插入图片描述

  • 21
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 23
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值