蓝桥杯单片机第十届国赛 部分功能解析
Posted 时光654
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了蓝桥杯单片机第十届国赛 部分功能解析相关的知识,希望对你有一定的参考价值。
@蓝桥杯第十届国赛部分功能解析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 应对考试没问题鸭~
其他串口问题按照这个思路走就可以,举一反三。
最后祝大家都能拿到如愿成绩,旗开得胜,马到成功。
(认为不好的我也没要求你看,请转走,但我所说的有问题还请大家指正)
以上是关于蓝桥杯单片机第十届国赛 部分功能解析的主要内容,如果未能解决你的问题,请参考以下文章