51单片机矩阵键盘工作原理与C语言驱动实现
矩阵键盘的工作原理与硬件设计
在微控制器系统设计中,独立按键虽然逻辑简单,但每个按键都需要独占一个I/O引脚。当系统需要大量按键输入时,独立按键会迅速耗尽宝贵的I/O资源。为了解决这一问题,矩阵键盘应运而生。
矩阵键盘通过将按键排列成行和列的矩阵结构来大幅减少引脚占用。以4x4矩阵键盘为例,16个按键仅需8根控制线(4根行线和4根列线)。硬件连接上,通常将行线连接到单片机端口的高四位(如P1.4-P1.7),列线连接到低四位(如P1.0-P1.3)。
检测按键按下的核心在于识别具体是哪一行和哪一列的导线被短接。常用的扫描算法包括行列扫描法和线翻转法。
行列扫描法
行列扫描法通过逐列(或逐行)输出低电平,同时读取行(或列)的状态来判断按键位置。例如,先将第一列拉低,其余列保持高电平,然后读取行线的状态。如果某一行读取到低电平,则说明该行与第一列交叉处的按键被按下。通过循环遍历所有列,即可覆盖整个键盘矩阵。
线翻转法
线翻转法分为两步:首先将所有行线输出低电平,列线设为输入,读取列线状态以获取列坐标;随后将电平翻转,所有列线输出低电平,行线设为输入,读取行线状态以获取行坐标。结合两次读取的结果,即可精确计算出按键的矩阵坐标。这种方法执行效率较高,适合对响应速度要求严格的场景。
C语言驱动代码实现
以下代码示例展示了如何通过重构传统的冗余判断逻辑,使用位运算和循环结构来实现更优雅的矩阵键盘驱动。
1. 基础按键检测与数码管显示
本实例实现了行列扫描与线翻转两种算法,并将检测到的键值(1-16)映射为0-F的十六进制字符,通过共阴极数码管进行显示。
#include <REGX52.H>
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
#define KEY_PORT P1
#define DIGIT_PORT P0
// 共阴极数码管 0-F 字模映射表
uint8_t code DIGIT_TABLE[16] = {
0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71
};
void delay_ms(uint16_t ms) {
uint16_t i, j;
for(i = ms; i > 0; i--)
for(j = 110; j > 0; j--);
}
// 行列扫描法实现
uint8_t scan_matrix_keyboard() {
uint8_t col, row;
uint8_t read_val;
for(col = 0; col < 4; col++) {
KEY_PORT = 0xFF;
KEY_PORT &= ~(0x08 >> col); // 依次拉低 P1.3 至 P1.0
read_val = KEY_PORT & 0xF0;
if(read_val != 0xF0) {
delay_ms(10); // 硬件消抖
read_val = KEY_PORT & 0xF0;
if(read_val != 0xF0) {
for(row = 0; row < 4; row++) {
if(!(read_val & (0x80 >> row))) {
while((KEY_PORT & 0xF0) != 0xF0); // 等待按键释放
return row * 4 + col + 1;
}
}
}
}
}
return 0;
}
// 线翻转法实现
uint8_t flip_scan_keyboard() {
uint8_t row_val, col_val;
uint8_t r = 0, c = 0;
KEY_PORT = 0x0F; // 行线输出0,列线输入
row_val = KEY_PORT & 0x0F;
if(row_val != 0x0F) {
delay_ms(10);
row_val = KEY_PORT & 0x0F;
if(row_val != 0x0F) {
KEY_PORT = 0xF0; // 翻转:列线输出0,行线输入
col_val = KEY_PORT & 0xF0;
while((KEY_PORT & 0xF0) != 0xF0); // 等待按键释放
// 解析列坐标
if(row_val == 0x07) c = 0;
else if(row_val == 0x0B) c = 1;
else if(row_val == 0x0D) c = 2;
else if(row_val == 0x0E) c = 3;
// 解析行坐标
if(col_val == 0x70) r = 0;
else if(col_val == 0xB0) r = 1;
else if(col_val == 0xD0) r = 2;
else if(col_val == 0xE0) r = 3;
return r * 4 + c + 1;
}
}
return 0;
}
void main() {
uint8_t key = 0;
while(1) {
key = scan_matrix_keyboard();
if(key > 0) {
DIGIT_PORT = DIGIT_TABLE[key - 1];
}
}
}
2. 矩阵键盘密码锁应用
在实际应用中,矩阵键盘常用于密码输入设备。以下代码将键盘驱动封装为独立模块,并结合LCD1602实现了一个四位数字密码锁系统。按键1-10对应数字输入,按键11为确认,按键12为取消。
MatrixKey.h
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__
unsigned char Read_Matrix_Key(void);
#endif
MatrixKey.c
#include <REGX52.H>
#include "Delay.h"
unsigned char Read_Matrix_Key() {
unsigned char col, row;
unsigned char temp;
for(col = 0; col < 4; col++) {
P1 = 0xFF;
P1 &= ~(0x08 >> col); // 动态拉低列线
temp = P1 & 0xF0;
if(temp != 0xF0) {
Delay(20);
temp = P1 & 0xF0;
if(temp != 0xF0) {
for(row = 0; row < 4; row++) {
if(!(temp & (0x80 >> row))) {
while((P1 & 0xF0) != 0xF0);
return row * 4 + col + 1;
}
}
}
}
}
return 0;
}
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
#include "MatrixKey.h"
unsigned char current_key;
unsigned int input_password;
unsigned char input_count;
void main() {
LCD_Init();
LCD_ShowString(1, 1, "Enter Pass:");
input_password = 0;
input_count = 0;
LCD_ShowNum(2, 1, input_password, 4);
while(1) {
current_key = Read_Matrix_Key();
if(current_key > 0) {
// 数字键输入逻辑
if(current_key <= 10 && input_count < 4) {
input_password = input_password * 10 + (current_key % 10);
input_count++;
LCD_ShowNum(2, 1, input_password, 4);
}
// 确认键逻辑
else if(current_key == 11) {
if(input_password == 1234) {
LCD_ShowString(1, 13, "Success");
} else {
LCD_ShowString(1, 13, "Failed ");
}
input_password = 0;
input_count = 0;
LCD_ShowNum(2, 1, input_password, 4);
}
// 取消键逻辑
else if(current_key == 12) {
input_password = 0;
input_count = 0;
LCD_ShowString(1, 13, " ");
LCD_ShowNum(2, 1, input_password, 4);
}
}
}
}