蓝桥杯单片机组模块代码编写之矩阵键盘 (一)
本教程面向具有一定和单片机基础的读者。文章重点在于代码编写与功能实现,不深入讲解原理细节(作者作为备赛者,文中内容可能存在不足之处)完整工程文件:本文主要介绍了矩阵键盘的非阻塞扫描(采用共阴极接法),文章内给出了4位按键(以近年来常用的S4、S5、S8、S9为例)的扫描函数以及16位按键的扫描函数及其实现思路,但是本文仅实现了矩阵键盘获取哪一位按键按下并松开,并未实现按键双击,长按等功能,该功能将
本教程面向具有一定C语言基础和单片机基础的读者。文章重点在于代码编写与功能实现,不深入讲解原理细节(作者作为蓝桥杯备赛者,文中内容可能存在不足之处)
完整工程文件:
本文主要介绍了矩阵键盘的非阻塞扫描(采用共阴极接法),文章内给出了4位按键(以近年来常用的S4、S5、S8、S9为例)的扫描函数以及16位按键的扫描函数及其实现思路,但是本文仅实现了矩阵键盘获取哪一位按键按下并松开,并未实现按键双击,长按等功能,该功能将会在下篇文章中实现。
一、模块原理图

图一 矩阵键盘
二、代码编写思路
作者计划采用状态机方法实现矩阵键盘扫描:为S4、S5、S8、S9四个按键分别定义两个bit变量key_x_state_cur和key_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();
}
}
三、代码测试
优化后的内容如下:
将本模块与之前介绍的数码管模块进行联动测试,实现以下功能:
- 数码管默认显示一个5位数字
- 当按下按键4时,切换数码管显示内容
- 切换后,数码管的第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;}
}
更多推荐


所有评论(0)