本教程面向具有一定C语言基础和单片机基础的读者。文章重点在于代码编写与功能实现,不深入讲解原理细节(作者作为蓝桥杯备赛者,文中内容可能存在不足之处)

完整工程文件:

本文主要介绍了矩阵键盘的非阻塞扫描(采用共阴极接法),文章内给出了4位按键(以近年来常用的S4、S5、S8、S9为例)的扫描函数以及16位按键的扫描函数及其实现思路,但是本文仅实现了矩阵键盘获取哪一位按键按下并松开,并未实现按键双击,长按等功能,该功能将会在下篇文章中实现。

一、模块原理图

                                                                图一 矩阵键盘

二、代码编写思路

作者计划采用状态机方法实现矩阵键盘扫描:为S4、S5、S8、S9四个按键分别定义两个bit变量key_x_state_curkey_x_state_pre,用于记录按键当前及前一时刻的状态。按键扫描函数以10ms为周期在定时器中断中执行,通过比较前后两次扫描结果实现消抖处理。具体判定逻辑为:当key_x_state_pre为1且key_x_state_cur为0时判定为按键按下;当key_x_state_pre为0且key_x_state_cur为1时则判定为按键释放。 状态机实现非阻塞式按键具体思路可观看江协的讲解视频

具体代码实现:

key.c

#include "key.h"

sbit ROW3 = P3^2;
sbit ROW4 = P3^3;
sbit COL1 = P4^4;
sbit COL2 = P4^2;

bit key_s4_state_pre = 0;
bit key_s4_state_cur = 0;

bit key_s5_state_pre = 0;
bit key_s5_state_cur = 0;

bit key_s8_state_pre = 0;
bit key_s8_state_cur = 0;

bit key_s9_state_pre = 0;
bit key_s9_state_cur = 0;

uint8_t key_getnum(void)
{
	uint8_t key_num = 0;

    /*初始化键盘,检测低电平*/
    ROW3 = ROW4 = COL1 = COL2 = 1;
	
    /*将当前状态赋值给上一次状态,准备获取新状态*/
	key_s4_state_pre =	key_s4_state_cur;
	key_s5_state_pre =	key_s5_state_cur;
	key_s8_state_pre =	key_s8_state_cur;
	key_s9_state_pre =	key_s9_state_cur;
	
	COL1 = 0;
	
    if(ROW4 == 0)
     key_s4_state_cur = 1;//按键按下时为1
	else
     key_s4_state_cur = 0;//按键未按下时为0
      
    if(ROW3 == 0)
     key_s5_state_cur = 1;
    else
     key_s4_state_cur = 0;
	
    COL1 = 1;
	
	COL2 = 1;
	
    if(ROW4 == 0)
     key_s8_state_cur = 1;
	else
     key_s8_state_cur = 0; 
     
    if(ROW3 == 0)key_s9_state_cur = 0;
	
    COL2 = 1;
	
	if(key_s4_state_cur == 0 && key_s4_state_pre == 1)//按键松开检测
		key_num = 4;
	/*其他按键判断*/
	
	
}

代码改善:

该代码虽然适用于读取单个按键,但在处理多个按键时需要定义过多变量,导致代码冗长繁琐,不利于比赛应用。为解决这一问题,我们可将四个按键的状态信息整合到一个八位变量中:高四位存储上一次状态,低四位存储当前状态。通过左移操作即可实现状态更新,从而简化存储逻辑。同时

当需要扫描16个按键时,可以使用一个32位变量来同时存储当前状态和上一次状态。更新状态时仍采用左移操作来实现。

代码实现:

key.c

#include "key.h"

sbit ROW3 = P3^2;
sbit ROW4 = P3^3;
sbit COL1 = P4^4;
sbit COL2 = P4^2;


uint8_t key_getstate(void)
{
 uint8_t state = 0;
 	
 COL1 = COL2 = ROW3 = ROW4 = 1;
	
 COL1 = 0;
 if(ROW4 == 0)state |= 0x01;
 if(ROW3 == 0)state |= 0x01<<1;
 COL1 = 1;
	
 COL2 = 0;
 if(ROW4 == 0)state |= 0x01<<2;
 if(ROW3 == 0)state |= 0x01<<3;
 COL2 = 1;

 return state;	
}

main.c

#include <STC15F2K60S2.H>

uint8_t key_state = 0;
uint8_t key_slow = 0;


void Timer0_Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x80;			//定时器时钟1T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0x20;				//设置定时初始值
	TH0 = 0xD1;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 1;				//使能定时器0中断
	EA = 1;
}

void key_task(void)
{
  uint8_t key_num = 0;
  if(key_slow)return;
	key_slow = 1;
  key_state = key_state << 4;
  key_state |= key_getstate();
	
  if((key_state & 0x11) == 0x10)key_num = 4;
  if((key_state & 0x22) == 0x20)key_num = 5;
  if((key_state & 0x44) == 0x40)key_num = 8;
  if((key_state & 0x88) == 0x80)key_num = 9;

  switch(key_num)
  {
    case 4:
      break;
    case 5:
      break;
    case 8:
      break;
    case 9:
      break;
  }
}

void Timer0_Isr(void) interrupt 1
{
  if(++key_slow >= 10)key_slow = 0;

}

void main()
{

  while(1)
  {
     key_task();
  }

}

三、代码测试

优化后的内容如下:


将本模块与之前介绍的数码管模块进行联动测试,实现以下功能:

  1. 数码管默认显示一个5位数字
  2. 当按下按键4时,切换数码管显示内容
  3. 切换后,数码管的第7和第8位将显示"FF"

main.c

#include <STC15F2K60S2.H>
#include "Nixie.h"
#include "key.h"
//数码管
uint8_t Nixie_Slow;//数码管延迟
uint8_t Nixie_State[8] = {16,16,16,16,16,16,16,16};
uint8_t Nixie_Point = 0;//数码管显示索引

uint8_t Nixie_Show_Mode = 0;//显示模式

//按键
uint8_t key_state = 0;
uint8_t key_slow = 0;

//需要显示的数据
int32_t Fre = 12345;
int32_t Count_S = 0;



void Timer1_Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x40;			//定时器时钟1T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x16;				//设置定时初始值
	TH1 = 0xD1;				//设置定时初始值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
	EA  = 1;        //开启总中断
	ET1 = 1;				//使能定时器1中断
}

void key_task(void)
{
  uint8_t key_num = 0;
  if(key_slow)return;
	key_slow = 1;
  key_state = key_state << 4;
  key_state |= key_getstate();
	
  if((key_state & 0x11) == 0x10)key_num = 4;
  if((key_state & 0x22) == 0x20)key_num = 5;
  if((key_state & 0x44) == 0x40)key_num = 8;
  if((key_state & 0x88) == 0x80)key_num = 9;

  switch(key_num)
  {
    case 4:
			Nixie_Show_Mode++;
		  Nixie_Show_Mode %= 2;
      break;
    case 5:
      break;
    case 8:
      break;
    case 9:
      break;
  }
}


//数码管数据处理函数
void Nixie_Task(void)
{
	uint8_t i = 0;
	//数据更新频率限制
  if(Nixie_Slow) return;
	Nixie_Slow = 1;
	
	switch(Nixie_Show_Mode)
	{
	  case 0:
			//数据处理
			Nixie_State[0] = 15;
			Nixie_State[1] = 16;
			Nixie_State[2] = 16;	
			Nixie_State[3] = (Fre/10000)%10;
			Nixie_State[4] = (Fre/1000)%10;
			Nixie_State[5] = (Fre/100)%10;
			Nixie_State[6] = (Fre/10)%10;
			Nixie_State[7] = (Fre/1)%10;	
			i = 3;
			//首位0清除
			while(Nixie_State[i] == 0)
			{
				Nixie_State[i] = 16;
				i++;
				if(i >= 8)
				{
					break;
				}
			}
	    break;
			
		case 1:
			Nixie_State[0] = 16;
			Nixie_State[1] = 16;
			Nixie_State[2] = 16;	
			Nixie_State[3] = 16;
			Nixie_State[4] = 16;
			Nixie_State[5] = 16;
			Nixie_State[6] = 15;
			Nixie_State[7] = 15;	
			break;
	
	}
}

void main(void)
{
  Timer1_Init();
	
	while(1)
	{
		key_task();
	  Nixie_Task();
	}
	
}

void Timer1_Isr(void) interrupt 3
{
	static Count_ms;
	/*数码管显示*/
	Nixie_Point++;
	Nixie_Slow++;
	Count_ms++;
	if(Count_ms >= 1000)
	{
	  Count_S++;
    Count_ms = 0;		
	}

	
	if(Nixie_Slow>=100){Nixie_Slow =  0;}
	if(Nixie_Point>=8){Nixie_Point = 0;}

	Nixie_Show(Nixie_Point,Nixie_State[Nixie_Point]);
  
  /*按键*/
	key_slow++;
	if(key_slow >= 10){key_slow = 0;}
	
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐