蓝桥杯单片机类按键代码详解及考点分析

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

文章目录

  • 一、按键的底层驱动代码讲解
  • 1.按键原理图
  • 按键原理讲解
  • 2.按键代码
  • (1)按键头文件
  • (2)独立按键
  • (3)矩阵按键
  • (4)main.c中按键函数(三行消抖的理解)
  • 二、按键考点解析与代码展示
  • 1.按键控制界面,工作模式的切换
  • (1)多个模式相同等级
  • (2)多模式不同级
  • 2.按键长按。
  • (1)长按按键从A界面跳转到B界面,松开后恢复A界面
  • (2)按键长按,按键抬起超过规定时间生效
  • (3)按键长按,时间到后生效(无须按键抬起)
  • 蓝桥杯的资料,个人应该有一些,需要什么资料可以私信我哈。
  • 一、按键的底层驱动代码讲解

    1.按键原理图


    CON3中将1,2用跳线帽短接是矩阵键盘,可用范围为S4到S19。将2,3短接则是独立按键,可用范围是S4到S7.

    按键原理讲解

    单取一个按键,一端接到GND,一段接到单片机的IO口上(以下称I口,输入口)。当按键按下时,I口读取电平为低电平,按键未按下单片机的I口读取为高电平。此时只需要单片机按时扫描这个I口是否为低电平,即可判断按键按是否按下。如果有四个这样的按键,就是上图中的独立按键。

    如果将GND同时也换成单片机的另IO口(以下称O口,输出口),那么当我有需要读取按键时,将O口设置输出为低电平,再去读取I口的电平,即可判断按键是否按下。这样做有什么好处呢。

    如果我需要有16个按键,如果用独立按键,那么我将需要16个I口取读取按键电平。如果将16个按键每行放置4个,放4列,如上图CON1,2口短接时。并却我采用O口输出口的话,采用4个O口,每个O口控制一列4个按键,采用4个I口,每个I口读取每行4个按键。这样输出对应竖排,输入对应横排。两个分量,就可以在二维平面上定位到按键的位置。如此,将本来16个IO口减少到了8个IO口,节约了单片机的资源,这就是矩阵键盘。

    矩阵键盘的使用,先将一个O口置零,读取所有的I口,再依次将O口置零读取I口。那么这样去读取按键会不会有时间差。不会的,一个按键按下的过程至少需要大几十个毫秒,我只去要将O口轮换的速度变快,就可以了。

    2.按键代码

    (1)按键头文件

    #ifndef __KEY_H
    #define __KEY_H
    #include "STC15F2K60S2.H"
    
    unsigned char Key_Read();
    unsigned char Key_Read_BTN();
    
    #endif
    

    (2)独立按键

    #include "key.h"
    //P30-P34为I口,读取高低电平判断按键是否按下 
    unsigned char Key_Read_BTN()
    {
    	unsigned char key_value; //设置按键代号变量 
    	
    	if(P33 == 0) key_value = 4; //S4按键,用4代替 
    	else if(P32 == 0) key_value = 5;
    	else if(P31 == 0) key_value = 6;
    	else if(P30 == 0) key_value = 7;
    	else key_value = 0; 
    	// 如果没有按键按下返回值为零,这个很重要。 
    	return key_value;//将按键代号返回,便于接下来的处理 
    }
    

    (3)矩阵按键

    #include "key.h"
    
    unsigned char Key_Read()
    {
    	unsigned int key_new; 
    	// I口读取变量,有16个按键,16位,两个字节,所以用int型变量 
    	unsigned char key_value; //设置按键代号变量 
    	
    	P44 = 0; P42 = 1; P35 = 1; P34 = 1; //将一个O口置零 
    	key_new = P3&0x0F; //读取全部的I口数据 
    	P44 = 1; P42 = 0; P35 = 1; P34 = 1;
    	key_new =(key_new<<4)|(P3&0x0F); //将上次读取的数据左移,并存储这次I口全部数据 
    	P44 = 1; P42 = 1; P35 = 0; P34 = 1;
    	key_new =(key_new<<4)|(P3&0x0F);	
    	P44 = 1; P42 = 1; P35 = 1; P34 = 0;
    	key_new =(key_new<<4)|(P3&0x0F);	
    	
    	//4个O口对应16个I口数据全部读取。 
    	
    	switch(~key_new)    //取反,按键按下是0,不按是1 
    	{
    		case 0x8000: key_value = 4; break;
    		case 0x4000: key_value = 5; break;
    		case 0x2000: key_value = 6; break;
    		case 0x1000: key_value = 7; break;
    		
    		case 0x0800: key_value = 8; break;
    		case 0x0400: key_value = 9; break;
    		case 0x0200: key_value = 10; break;
    		case 0x0100: key_value = 11; break;
    		
    		case 0x0080: key_value = 12; break;
    		case 0x0040: key_value = 13; break;
    		case 0x0020: key_value = 14; break;
    		case 0x0010: key_value = 15; break;
    		
    		case 0x0008: key_value = 16; break;
    		case 0x0004: key_value = 17; break;
    		case 0x0002: key_value = 18; break;
    		case 0x0001: key_value = 19; break;
    		
    		default: key_value = 0;
    	}
    	
    	return key_value;
    }
    

    (4)main.c中按键函数(三行消抖的理解)

    void Key_Proc()
    {
    	if(key_slow_down) return;
    	key_slow_down = 1;
    	//降速处理每10毫秒进入一次按键处理
    	
    	key_value = Key_Read_BTN();//进入底层驱动读取键值
    	key_down = key_value&(key_value^key_old);//下降沿
    	key_up = (~key_value)&(key_value^key_old);//上升沿
    	key_old = key_value;
    	//三行代码按键消抖,文章中讲解
    	switch(key_down)//对按键的下降沿进行扫描处理
    	{
    		case 4:    
    			if(display>>4 == 0x1)
    				display = 0x20;
    			else if(display>>4 == 0x2)
    				display = 0x31;
    			else if(display>>4 == 0x3)
    				display = 0x41;
    			else if(display>>4 == 0x4)
    				display = 0x11;
    			break;	//S4按下时的处理,13届蓝桥杯国赛部分参考
    		default: break;
    	}
    }
    
    key_value = Key_Read_BTN();
    key_down = key_value&(key_value^key_old);
    key_old = key_value;
    

    这三行代码是蓝桥杯官方提供的按键消抖代码,简洁精炼,值得我们去学习。
    学习代码之前先看一幅图

    A是上升沿,B是下降沿,A-B为高电平,B-A为低电平。

    key_value = Key_Read_BTN();
    //这一行就是去读取高低电平状态,高电平即使未按下,低电平是按下。这一行只能说明是一种状态,如果是最基本的按下触发,状态值是不可以使用,因为一次按键按下至少维持几十毫秒,再这几十个毫秒里程序会不只一次获取状态值,那么我们通过按键所执行的任务就会多次执行。
    所以仅仅读取电平状态是不可行的,如何做到按键一次按下,只执行一次。我们想到去读取跳变沿A或B,官方给出的是读取下降沿(即B);为此引入第二行代码,再讲解第二行代码前,先说一下第三行代码

    key_old = key_value;
    //为了运用代码得到跳变沿,即我们需要获取上一次的电平状态与这一次的电平状态经过逻辑运算得到。这第三行代码就是为了得到上一次的电平状态

    key_down = key_value&(key_value^key_old);
    //第二行代码既要获得下降沿又要获得按键键值,即那个按键发送下降沿。
    key_value^key_old 将上次的电平状态与当前的电平状态取按位异或,可以检测出跳变沿,没有跳变沿为0,又跳变沿为当前跳变沿的键值。当然仅仅一个按位异或是不够的,并不能区分出上升沿和下降沿。所以要在按位与key_value(得到下降沿)或者按位与(~key_value)(得到上升沿)。
    //为什么上升沿检测比下降沿检测多一个取反
    key_down = key_value&(key_value^key_old);//下降沿
    key_up = (~key_value)&(key_value^key_old);//上升沿
    首先下降沿检测前一次状态是抬起,键值是0,而瞬时状态为按下,键值为x,(key_value^key_old)为x。x与上x,任然为x,得到键值。
    而检测上升沿,前一次状态是按下,键值是下,而瞬时状态为抬起,键值为0。(key_value^key_old)为x。应为瞬时键值为0,所以通过取反来获得键值。

    总结,前半段决定上升沿还是下降沿,后半段检测跳变沿。

    二、按键考点解析与代码展示

    1.按键控制界面,工作模式的切换

    (1)多个模式相同等级

    	unsigned char state_flag;
    	switch(key_down)
    	{
    		case 12: 
    			if(++state_flag == 3) state_flag = 0; 
    			//用state_flag代表界面,并且加到3清零。
    			//显示模块通过state_flag的值显示
    			break;
    		default: break;
    	}
    

    (2)多模式不同级


    简介表示
    1.数据界面
    (1)时间
    (2)温度
    (3)亮暗状态
    2.参数界面
    (1)时间参数
    (2)温度参数
    (3)指示灯参数

    	unsigned char display = 0x11;
    	//采用16进制数据,高位代表高模式,低位代表低模式
    	switch(key_down)
    	{		
    		case 4: //高模式模式切换
    			if(display>>4 == 1) 
    				display = 0x21;
    			else
    				display = 0x11;
    			break;
    		case 5://低模式模式切换
    			if(display == 0x11)
    				display = 0x12;
    			else if(display == 0x12)
    				display = 0x13;
    			else if(display == 0x13)
    				display = 0x11;
    			else if(display == 0x21)
    				display = 0x22;
    			else if(display == 0x22)
    				display = 0x23;
    			else if(display == 0x23)
    				display = 0x21;
    			break;
    	}
    

    总结:用一个对应变量决定模式状态。按键部分是对变量的处理。

    2.按键长按。

    (1)长按按键从A界面跳转到B界面,松开后恢复A界面

    	if( key_old == 4)//S4按下
    	{
    		if(state_flag != 1 && state_flag != 2)//非设置状态
    		{
    			led_class_disp = 1; //长按状态设置
    		}
    	}
    	else led_class_disp = 0;
    

    注意点在于,长按显示不需要去读取跳边沿,而是直接读取电平状态。

    (2)按键长按,按键抬起超过规定时间生效



    下降沿开始计数,上升沿停止计数。判断计数时间。

    	
    	//按键程序
    	key_value =Key_Read_BTN();
    	key_down = key_value & ( key_value ^ key_old);//下降沿
    	key_up = (key_value^0xff) & ( key_value ^ key_old);//上升沿
    	key_old = key_value;
    	
    	if(key_down == 7)
    	{
    		ms_statrt = ms;//定时器中断ms每1毫秒加1,检测S7下降沿,将ms赋值到ms_statrt。
    	}
    	if(key_up == 7)
    	{
    			if((ms - ms_statrt) >= 1000)  led_flag ^= 1 ;//抬起时检测(ms - ms_statrt)是否超过规定时间
    			else f_value = freq;
    	}
    	
    

    为什么if(key_down)和if(key_up)同级:
    答:按键只检测跳变沿并不是检测高低电平,跳变沿是瞬时性的所以在跳变沿里检测另一个跳变沿是不合理的。

    (3)按键长按,时间到后生效(无须按键抬起)


    注意点:下降沿仅开始计数,对时间进行判断。对短按识别上升沿识别。对长按进行一次进入判断(一次判断,赋值卡死而不是取反)。

    下降沿,开始计数,(是否进行一次判断)
    短时间上升沿扫描,
    长时间,电平扫描加一次判断。

    	if(key_down == 7)
    	{
    		key_time = ms; // 计数开始
    		key_long = 1;//长按一次判断,即长按达到时间后只进行一次操作
    	}
    	
    	if(ms-key_time<1000)//时间未到,短按操作
    	{
    		if(key_up == 7)
    		{
    			if(display == 0x41)
    				freq_compare = freq_compare-0.5;
    			else if(display == 0x42)
    				temp_compare = temp_compare-10;
    			else if(display == 0x43)
    				dist_compare = dist_compare-0.1;
    			else if(display == 0x11)
    				display = 0x12;
    			else if(display == 0x12)
    				display = 0x11;
    		}
    	}
    	else // 长按操作
    	{
    		if(key_value == 7&&key_long == 1) // 判断此时按键电平状态和一次操作
    		{
    			key_long = 0;
    			if(display==0x20)
    				relay_count = 0;
    		}
    	}
    

    蓝桥杯的资料,个人应该有一些,需要什么资料可以私信我哈。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 蓝桥杯单片机类按键代码详解及考点分析

    发表评论