蓝桥杯-单片机组基础4——独立按键与数码管的窗口切换(附小蜜蜂课程代码)

蓝桥杯单片机组备赛指南请查看 :本专栏第1篇文章

本文章针对蓝桥杯-单片机组比赛开发板所写,代码可直接在比赛开发板上使用。

型号:国信天长4T开发板(绿板),芯片:IAP15F2K61S2

(使用国信天长蓝板也可以完美兼容,与绿板几乎无差别)


1.编程目的

        在比赛中数码管是必考的,且会涉及数码管的窗口切换操作,因此本文建立在小蜜蜂老师关于独立键盘的讲解基础上,进行窗口切换的练习。编程目的如下:

        通过设置,使开发板具备计时器与日期显示功能。按下S7为计时器功能,数码管开始从0计时(使用延时函数),S5与S4功能被屏蔽。按下S6进入日期显示功能,显示今日日期“24-02-10”,按下S5日期加一天,按下S4日期减一天。状态变化图如下所示:

2.原理介绍

 2.1 四位独立按键原理图

(截取自SCH_硬件原理图V30.pdf)

        蓝桥杯比赛开发板的独立按键与矩阵键盘共用同一个按键,只有左边的4个按键为独立按键(S7,S6,S5,S4)。历史中独立按键也考过,因此与矩阵键盘有相同的重要性。

        左边的J5为跳线帽连接处,4T的开发板没有单独做独立按键,而和矩阵键盘中的4个集成到了一起,因此在使用时我们要使用跳线帽将J5的2和3引脚连接起来,没有跳线帽可以使用杜邦线连接。

        由于J5的2和3引脚相连,则独立按键S7~S4的左端直接接地,按键右端与P3^3~P3^0相连,因此我们可以通过读取四个引脚值来判断按键是否被按下。编程时,先让四个引脚赋值为1,当S7按下时,由于按键左端接地,因此根据强下拉效应P3^0引脚变为低电平,此时如果读取到P3^0电平为低,则可知按键被按下,再进行其他操作。

跳线帽连接如下图所示:

2.2 数码管原理图

(截取自官方4T开发板SCH_硬件原理图V30.pdf)

        针对该部分的原理讲解可参考:戳此跳转到 基础3——数码管的原理讲解,在此处给出会直接使用到的编程流程。

        编程时,应先设置74HC138译码器的“CBA”输入端口为“110”,选中Y6C引脚为低电平,从而激活锁存器,再对P0端口进行赋值,选中数码管的公共端com8~com1中的某一个位。

然后设置74HC138译码器的“CBA”输入端口为“111”,选中Y7C引脚为低电平,从而激活锁存器,再对P0端口进行赋值,设置需要在数码管上选中的内容。

        共阳极数码管段码如下:

unsigned char code SMG_duanma [19]=
            { 0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 ,  0x80 , 0x90 , 0x88 , 0x80 , 0xc6 , 0xc0 , 0x86 , 0x8e , 0xbf , 0x7f , 0xbf };

2.3 按键消抖原理

只要是涉及到按键的操作,必须做消抖处理!

        单片机的开发板硬件电路已经固定,因此不能采用硬件消抖,我们采用软件消抖的方式。软件消抖的方式可以设置延时函数、设置定时/计数器查询,设置模糊反应(读取按键的时间戳,在某个区间内才响应)等方式。我们这里也采用最常见的延时函数方式。

        当我们按下一次按键并迅速松开,单片机读取到的电压信号如下图所示。可以观察到到松开以前,一瞬间的操作会产生许多尖刺,这些尖刺信号会产生伪高电平的效果,从而让单片机认为我们按了很多次。因此我们可以建立一个延时函数,使得单片机跳过这段尖刺的过程。

        编程时,当读取到引脚为低电平,则设置一个2ms的延时不进行操作。2ms后如果引脚变为高电平则表示按键成功,依旧是低电平则继续一个2ms的延时。从而达到一次按键的准确读取。

2.4 窗口切换原理

        目前比较易于理解的方式是设置标志位进行切换。我们定义一个窗口变量:window_flag。通过查询window_flag不同的值,从而让数码管选中不同的内容进行显示。这是最易操作与最多人所使用的,我们采用这一种方式。

        其他方式有结构体等,但我看不懂,没有深究,暂时不管,以后有机会研究了再和大家分享。

        编程时,我们可以设置当window_flag=1时,为计时器功能;window_flag=2时,为日期显示功能。

3.代码参考

3.1 题目代码

        按下S7为计时器功能,数码管开始从0计时,S5与S4功能被屏蔽。按下S6进入日期显示功能,显示今日日期“24-02-10”,按下S5日期加一天,按下S4日期减一天。

代码中的一些解释:

        由于通篇采用延时函数,为了计数器可以肉眼看出效果,因此设置了延时100ms计时,这会导致在100ms内单片机不工作,甚至不响应我们的切换窗口操作。因此从计时器功能转向日期功能时,会偶尔不灵,后期我们使用中断方式可以解决这个问题。

        由于采用查询全局变量的方式确定键值,因此在对day变量进行自增与自减操作后,必须将key_value的值改掉,不然会导致key_value一直判定为自增或自减。采用返回值的方式可以规避这个问题,但查询全局变量更好理解,因此本文采用这种方法。需要参考返回值方式的代码可评论或私信。

#include < reg52.h >
#include < intrins.h>

sbit HC173_A = P2^5;
sbit HC173_B = P2^6;
sbit HC173_C = P2^7;

sbit S7 = P3^0;
sbit S6 = P3^1;
sbit S5 = P3^2;
sbit S4 = P3^3;

unsigned char code SMG_duanma [19]=
			{ 0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 
				0x80 , 0x90 , 0x88 , 0x80 , 0xc6 , 0xc0 , 0x86 , 0x8e ,
				0xbf , 0x7f , 0xbf };

void select_HC173 ( unsigned char channal )
{
	switch ( channal )
	{
		case 4 :
			HC173_A = 0;
			HC173_B = 0;
			HC173_C = 1;
		break;
		case 5 :
			HC173_A = 1;
			HC173_B = 0;
			HC173_C = 1;
		break;		
		case 6 :
			HC173_A = 0;
			HC173_B = 1;
			HC173_C = 1;
		break;		
		case 7 :
			HC173_A = 1;
			HC173_B = 1;
			HC173_C = 1;
		break;	
	}
}

void Delay100ms()		//@12.000MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 144;
	k = 71;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}



void Delay3ms()		//@12.000MHz
{
	unsigned char i, j;

	i = 36;
	j = 1;
	do
	{
		while (--j);
	} while (--i);
}

void set_SMG ( unsigned char pos_SMG , unsigned char value_SMG )//封装数码管的调用方式
{
	select_HC173( 6 );
	P0 = 0x01 << pos_SMG;
	select_HC173 ( 7 );
	P0 = SMG_duanma[value_SMG];
}

unsigned char window_flag = 0;
unsigned char key_value = 0; 
//分享我的变量名,函数名命名规则
//针对变量:作用翻译为2个英文单词,主体在前,属性在后;如key_value
//针对函数名:功能翻译为两个英文单词,动词在前,名词在后;如get_key.如果是要写入主函数的子函数,则加上ing
void key_windowing () //获取按键信息,并设置窗口标志位window_flag
{	
	if ( S7 == 0 )
	{
		Delay3ms ();
		while( S7 == 0 );
		
		if ( S7 == 1 )
		{
			key_value = 7;
			window_flag = 1;
		}
	}
	else if ( S6 == 0 )
	{
		Delay3ms ();
		while( S6 == 0 );
		
		if ( S6 == 1 )
		{
			key_value = 6;
			window_flag = 2;
		}
	}
	else if ( S5 == 0 )
	{
		Delay3ms ();
		while( S5 == 0 );
		
		if ( S5 == 1 )
		{
			key_value = 5;
		}
	}
	else if ( S4 == 0 )
	{
		Delay3ms ();
		while( S4 == 0 );
		
		if ( S4 == 1 )
		{
			key_value = 4;
		}
	}
}
	
unsigned char day = 10;
void set_functioning ()//根据不同的键值设置功能
{
	if ( window_flag == 0 )
	{
		set_SMG( 0 , 18 );
		Delay3ms ();
		set_SMG( 1 , 18 );
		Delay3ms ();
		set_SMG( 2 , 18 );
		Delay3ms ();
		set_SMG( 3 , 18 );
		Delay3ms ();
		set_SMG( 4 , 18 );
		Delay3ms ();
		set_SMG( 5 , 18 );
		Delay3ms ();
		set_SMG( 6 , 18 );
		Delay3ms ();
		set_SMG( 7 , 18 );
		Delay3ms ();				
	}
	else if ( window_flag == 1 )
	{
		
		set_SMG( 0 , 2 );
		Delay3ms ();
		set_SMG( 1 , 4 );
		Delay3ms ();
		set_SMG( 2 , 18 );
		Delay3ms ();
		set_SMG( 3 , 0 );
		Delay3ms ();
		set_SMG( 4 , 2 );
		Delay3ms ();
		set_SMG( 5 , 18 );
		Delay3ms ();
		set_SMG( 6 , day/10 );
		Delay3ms ();
		set_SMG( 7 , day%10 );
		Delay3ms ();
		
		if( key_value == 5 )
		{
			day ++;
			key_value = 7;
			if ( day == 30 )
			{
				day = 0;
			}
		}
		else if ( key_value == 4 )
		{	
			if ( day > 0 )
			{
				key_value = 7;
				day --;
			}
		}
	}
	else if ( window_flag == 2 )
	{
		unsigned char i;
		for ( i=0 ; i<10 ; i++ )
		{
			set_SMG( 7 , i );
			Delay100ms();
			if ( window_flag == 1 )
			{
				break;
			}
			if ( i == 10 )
			{
				i = 0;
			}		
		}
	}
}

void main ()
{
	while ( 1 )
	{
		set_functioning ();
		key_windowing ();
	}
}

3.2 小蜜蜂老师课程代码

S7控制灯光模式1,L1常量,此时只能用S5控制灯组L5~L3,S6控制灯光模式2,L2常亮,此时只能用S4控制灯组L8~L6.

//S7和S6控制led的组别,L1和L2为分别的状态
// S5 S4是开关灯组,常按点亮,两状态互斥
#include < reg52.h >
#include < intrins.h>

sbit HC173_A = P2^5;
sbit HC173_B = P2^6;
sbit HC173_C = P2^7;

sbit L1 = P0^0;
sbit L2 = P0^1;
sbit L3 = P0^2;
sbit L4 = P0^3;
sbit L5 = P0^4;
sbit L6 = P0^5;
sbit L7 = P0^6;
sbit L8 = P0^7;

sbit S7 = P3^0;
sbit S6 = P3^1;
sbit S5 = P3^2;
sbit S4 = P3^3;


void select_HC173 ( unsigned char channal )
{
	switch ( channal )
	{
		case 4 :
			HC173_A = 0;
			HC173_B = 0;
			HC173_C = 1;
		break;
		case 5 :
			HC173_A = 1;
			HC173_B = 0;
			HC173_C = 1;
		break;		
		case 6 :
			HC173_A = 0;
			HC173_B = 1;
			HC173_C = 1;
		break;		
		case 7 :
			HC173_A = 1;
			HC173_B = 1;
			HC173_C = 1;
		break;	
	}
}

void Delay2ms ()
{
	unsigned char i,j;
	
	_nop_ ();
	_nop_ ();
	i = 22;
	j = 128;
	do 
	{
		while ( --j );
	}while ( --i );
}

void clean_shake ( unsigned char t )
{
	while ( t-- )
	{
		Delay2ms ();
	}
}


void LED_BTNrunning ()
{	
	static unsigned char state_k = 0;
	if ( S7 == 0 )
	{
		clean_shake ( 3 );
		if ( S7 == 0 )
		{
			state_k = 1;
			L1 = 0;
			L2 = 1;

		}
	}
	else if ( S6 == 0 )
	{
		clean_shake ( 3 );
		if ( S6 == 0 )
		{
			state_k = 2;
			L1 = 1;
			L2 = 0;

		}
	}

	switch ( state_k )
	{
		case 0:
			P0 = 0xff;
		break;
		
		case 1:

			if ( S5 == 0 )
			{
				clean_shake ( 2 );
				if ( S5 == 0 )
				{					
					L3 = 0;
					L4 = 0;
					L5 = 0;
					L6 = 1;
					L7 = 1;
					L8 = 1;
				}
				else 
				{
					L3 = 1;
					L4 = 1;
					L5 = 1;
					L6 = 1;	
					L7 = 1;
					L8 = 1;					
				}
			}
		break;
				
		case 2:
			if ( S4 == 0 )
			{
				clean_shake ( 2 );
				if ( S4 == 0 )
				{					
					L3 = 1;
					L4 = 1;
					L5 = 1;
					L6 = 0;
					L7 = 0;
					L8 = 0;					
				}
				else 
				{
					L3 = 1;
					L4 = 1;
					L5 = 1;
					L6 = 1;	
					L7 = 1;
					L8 = 1;					
				}
			}
		break;			
	}

}

void main ()
{
	select_HC173 ( 4 );
	while ( 1 )
	{
		LED_BTNrunning();
	}
}

4.编程思路重述

        对于窗口切换:定义一个全局变量作为窗口标志,根据标志的值转换数码管的输出

对于获取键值:常规有两种思路,一种是查询全局变量值:通过函数改变全局变量的值,然后通过查询该变量的当前值从而进行逻辑处理,方便快捷实现代码逻辑,但是需要在自增或自减操作后手动将该变量值改变,从而避免“永远生效”。

        另一种是获取返回值,即不断运行键值函数,得到其返回值,当某一时刻返回值符合条件时就响应,逻辑理解稍微复杂,占用单片机RAM较大,但是逻辑更严密,形式为:if( get_key == 1),get_key为获取键值的函数,为char类型。

//分享我的变量名,函数名命名规则
//针对变量:作用翻译为2个英文单词,主体在前,属性在后;如key_value
//针对函数名:功能翻译为两个英文单词,动词在前,名词在后;如get_key.如果是要写入主函数的子函数,则加上ing

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值