蓝桥杯——单片机学习(5(2)——按键(矩阵按键))

注:此单片机型号为 STC15F2K60S2.

矩阵按键

原理

来源:我自己:)
首先,把跳线帽连接1、2,使其进入矩阵按键模式。

来源:我自己:)
此时,即为矩阵按键模式。
来源:我自己:)
按键原理
由图可看出,行接口有:P30、P31、P32、P33.
——————列接口有:P44、P42、P35、P34.
以S7举例,假设要检测S7被按下,则首先要设置其左端为低电平,即设置P44为低电平并且其余端口保持高电平,再检测其右端即P30是否为低电平即可。
(原理与独立按键相似,只是要自己设置一段为低电平。)

代码

来源:蓝桥杯官方。

unsigned char Key_Read(void)
{
  unsigned int  Key_New;
  unsigned char Key_Val;

  P44 = 0; P42 = 1; P35 = 1; P34 = 1;	// 第1列
  Key_New = P3;
  P44 = 1; P42 = 0;	// 第2列
  Key_New = (Key_New<<4) | (P3&0x0f);/*这里需要注意,先将P3与一个0x0f,将前面的清零,
  然后再将结果与前值左移四位的数相或,或是有1则为1,其他都不变,
  所以前四项保持不变,后四项看是否按下而改变。*/
  P42 = 1; P35 = 0;	// 第3列
  Key_New = (Key_New<<4) | (P3&0x0f);
	P35 = 1; P34 = 0;	// 第4列
  Key_New = (Key_New<<4) | (P3&0x0f);

  switch(~Key_New) //因为每次只按下一个按键,就算你以为同时按下,实际上仍为依次按下。
  {
    case 0x8000: Key_Val = 4; break;	// S4     0111 1111 1111 1111
    case 0x4000: Key_Val = 5; break;	// S5     1011 1111 1111 1111
    case 0x2000: Key_Val = 6; break;	// S6     1101 1111 1111 1111
    case 0x1000: Key_Val = 7; break;	// S7     1110 1111 1111 1111
    case 0x0800: Key_Val = 8; break;	// S8     1111 0111 1111 1111
    case 0x0400: Key_Val = 9; break;	// S9     1111 1011 1111 1111
    case 0x0200: Key_Val = 10; break;	// S10
    case 0x0100: Key_Val = 11; break;	// S11
    case 0x0080: Key_Val = 12; break;	// S12
    case 0x0040: Key_Val = 13; break;	// S13
    case 0x0020: Key_Val = 14; break;	// S14
    case 0x0010: Key_Val = 15; break;	// S15
    case 0x0008: Key_Val = 16; break;	// S16
    case 0x0004: Key_Val = 17; break;	// S17
    case 0x0002: Key_Val = 18; break;	// S18
    case 0x0001: Key_Val = 19; break;	// S19
    default: Key_Val = 0;
  }
  return Key_Val;
}
自己写的代码

key.h

#ifndef __KEY_H
#define __KEY_H
#include "STC15F2K60S2.H"

void key_resd(int* temp);

#endif

key.c

#include "key.h"

void key_resd(int* temp){                      //按键选择函数
	unsigned int val = 0;                      //定义一个无符号整型变量val
	
	                                           //按列扫描               被检测管脚为 P30 P31 P32 P33
	P44=0,P42=1,P35=1,P34=1;                   //第一列  S7-S4  从右往左
	val = P3 & 0x0f;                           //P3 & 上一个0x0F,若后四位有低电平,即后四位有0,则会清零
	
	P44 = 1,P42 = 0,P35 = 1,P34 = 1;           //第二列  S11-S8
	val = (val<<4)|(P3 & 0x0f);                //val向左移四位,再承接第二位的检测值
	
	
	P44 = 1,P42 = 1,P35 = 0,P34 = 1;           //第三列  S15-S12
	val = (val<<4)|(P3 & 0x0f);                //val向左移四位,再承接第三位的检测值
	
	P44 = 1,P42 = 1,P35 = 1,P34 = 0;           //第四列  S19-S16
	val = (val<<4)|(P3 & 0x0f);                //val向左移四位,再承接第四位的检测值,最左边的四位数(P37 P36 P35 P34)出了范围,自动消除,只留下第一列至第四列的检测值
	
	switch(~val){
		case 0x0001:(*temp) = 19;break;        //即为 1111 1111 1111 1110      第四列,从右往左,第一个为 S19
		case 0x0002:(*temp) = 18;break;
		case 0x0004:(*temp) = 17;break;
		case 0x0008:(*temp) = 16;break;
		
		case 0x0010:(*temp) = 15;break;
		case 0x0020:(*temp) = 14;break;
		case 0x0040:(*temp) = 13;break;
		case 0x0080:(*temp) = 12;break;
		
		case 0x0100:(*temp) = 11;break;
		case 0x0200:(*temp) = 10;break;
		case 0x0400:(*temp) = 9;break;
		case 0x0800:(*temp) = 8;break;
		
		case 0x1000:(*temp) = 7;break;
		case 0x2000:(*temp) = 6;break;
		case 0x4000:(*temp) = 5;break;
		case 0x8000:(*temp) = 4;break;
		
	}
}

seg.h

#ifndef __SEG_H
#define __SEG_H
#include "STC15F2K60S2.H"

void pos_FF();                           //位选消隐
void value_FF();                         //段选消隐
void seg_pos(unsigned int pos);          //位选
void seg_value();                        //段选
void seg_disp(int* value);               //数码管显示

#endif

seg.c

#include <stdio.h>
#include "seg.h"
#include "tim.h"

/*段选消隐*/
void value_FF(){
	P0 = 0xFF;                                          
  P2 = P2&0x1F|0xE0;                                     
	P2 &= 0x1F;
}
/*位选*/
void seg_pos(unsigned int pos){
  P0 = 1<<(pos - 1);                                      //选择com接口
	P2 = P2 & 0x1F | 0xC0;                                      //选择Y6
	P2 &= 0x1F;                                             //锁存
}

/*段选*/
void seg_value(void){
	P2 = P2 & 0x1F | 0xE0;                                      //选择Y7
	P2 &= 0x1F;                                             //锁存
}

/*显示函数*/
void seg_disp(int* value){
	
	unsigned char smgduan[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};//显示0~9的值
	unsigned int pos;                                      //定义一个位选变量pos

		if((*value) >= 0 && (*value) < 10){                    //当value为个位数时
			pos = 8;                                             //只显示第一个数码管
			
			value_FF();                                            //段选消隐
			
			seg_pos(pos);                                        //调用位选函数
			
			P0=smgduan[(*value)];                                //选择段选显示
			seg_value();  
			}
			else if((*value) < 100){                             //当value为两位数时
				
			  pos = 8;                                           //第一个数码管显示
			
			  value_FF();                                          //段选消隐
			
			  seg_pos(pos);                                      //调用位选函数			
				
				P0=smgduan[(*value)%10];                          //个位显示
				
				seg_value();                                       //调用段选显示函数
				
			  value_FF();                                          //段选消隐
				
				pos = 7;                                           //第二个数码管显示
				
				seg_pos(pos);                                      //调用位选函数				
				
				P0=smgduan[(*value)/10];                           //十位显示
				
				seg_value();                                       //调用段选函数
			}
		
}

tim.h

#ifndef __TIM_H
#define __TIM_H
#include "STC15F2K60S2.H"

void Cls_Peripheral(void);                          //关闭外设
void time(void);                                    //延时函数

#endif

tim.c

#include "tim.h"

void Cls_Peripheral(void)           //关闭外设。
{
  P0 = 0xFF;
  P2 = P2 & 0x1F | 0x80;			// P27~P25清零,再定位Y4C
  P2 &= 0x1F;						// P27~P25清零
  P0 = 0;
  P2 = P2 & 0x1F | 0xA0;			// P27~P25清零,再定位Y5C
  P2 &= 0x1F;						// P27~P25清零
}

void time(void)
{
	int i,j;
	for(i=0;i<5;i++){
	for(j=0;j<5;j++);
	}
}

main.c

#include <stdio.h>
#include <STC15F2K60S2.h>
#include "tim.h"
#include "seg.h"
#include "key.h"


int temp = 0;
int* p = &temp;
int main()
{
	Cls_Peripheral();
	
	while(1){
		key_resd(p);
	    seg_disp(p);
	}
	
}

问题

之前一直显示不出来,知道是消隐的问题,但就是改不好。然后最近看了别人的代码,发现只要段选消隐就够了,位选可以不用消隐,现在改了下,可以实现功能了。但是当数字为两位数时,个位的亮度只有一半,这应该是消隐时长的问题,但我还解决不了 orz,知道是用定时器去写,但定时器前两天才学,还没完全搞懂,先这样吧,等我搞懂了再回过头来改。 2021.10.20.

问题解决了,详见蓝桥杯 —— 单片机学习(4—— 数码管显示)
原理就是用定时器中断来显示数码管,因为定时器中断的频率是不变的,所以每位数码管的显示时间都一致。

推荐写法

按键不推荐加延时函数,最好放定时器里扫描;

sbit KEY_IN_1 = P4^4;
sbit KEY_IN_2 = P4^2;
sbit KEY_IN_3 = P3^5;
sbit KEY_IN_4 = P3^4;
sbit KEY_OUT_1 = P3^0;
sbit KEY_OUT_2 = P3^1;
sbit KEY_OUT_3 = P3^2;
sbit KEY_OUT_4 = P3^3;

u8 KeySta[4][4] = {{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}};			//当前按键状态

//按键扫描函数,在定时器中断程序里调用,定时器间隔1ms,通过全局变量来获取键值;  
void Key_Scan(void)
{
	u8 i;
	static u8 keyout = 0;		//矩阵按键扫描输出索引
	static u8 keybuff[4][4] = {{0xff, 0xff, 0xff, 0xff},\
							   {0xff, 0xff, 0xff, 0xff},\
							   {0xff, 0xff, 0xff, 0xff},\
							   {0xff, 0xff, 0xff, 0xff}};	//矩阵按键扫描缓存区
	//    \  符号表示下一行继续,记得这个符号后面不能加任何东西,包括空格   
	keybuff[keyout][0] = (keybuff[keyout][0] << 1) | KEY_IN_1;		//将每一行的4个按键值移入缓存区
	keybuff[keyout][1] = (keybuff[keyout][1] << 1) | KEY_IN_2;
	keybuff[keyout][2] = (keybuff[keyout][2] << 1) | KEY_IN_3;
	keybuff[keyout][3] = (keybuff[keyout][3] << 1) | KEY_IN_4;
		
	//消抖后更新按键状态
	for (i = 0;i < 4;i ++)
	{
		if ((keybuff[keyout][i] & 0x0f) == 0x00)
			KeySta[keyout][i] = 0;		//连续4次扫描值都是0,即4×4us内都是按下状态,认为按键已平稳按下
		else if ((keybuff[keyout][i] & 0x0f) == 0x0f)
			KeySta[keyout][i] = 1;		//连续4次扫描值都是1,即4×4us内都是松开状态,认为按键已稳定弹起
	}
	
	//执行下一次的扫描输出
	keyout ++;
	keyout = keyout & 0x03;		//索引加到4就归零
	switch (keyout)						//根据索引,释放当前输出引脚,拉低下次的输出引脚
	{
		case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
		case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
		case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
		case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
		default:break;
	}
}

参考博客:
@小默 haa—— 蓝桥杯之单片机设计与开发(7)—— 矩阵键盘输入数字

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Moqim Flourite.

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值