第八章 矩阵按键实验

目录

[1. 矩阵按键介绍](#1. 矩阵按键介绍)

[2. 矩阵按键检测方式](#2. 矩阵按键检测方式)

[3. 实验硬件设计](#3. 实验硬件设计)

[3.1 LCD1602](#3.1 LCD1602)

[3.1.1 硬件图](#3.1.1 硬件图)

[3.1.2 相关控制API](#3.1.2 相关控制API)

[3.2 矩阵按键](#3.2 矩阵按键)

[3.2.1 硬件图](#3.2.1 硬件图)

[4. 实验软件设计](#4. 实验软件设计)

[4.1 项目结构图](#4.1 项目结构图)

[4.1.1 main.c](#4.1.1 main.c)

[4.1.2 Delay.c](#4.1.2 Delay.c)

[4.1.3 Delay.h](#4.1.3 Delay.h)

[4.1.4 LCD1602.c](#4.1.4 LCD1602.c)

[4.1.5 LCD1602.h](#4.1.5 LCD1602.h)

[4.1.6 MartixKey.c](#4.1.6 MartixKey.c)

[4.1.7 MartixKey.h](#4.1.7 MartixKey.h)

[5. 小结](#5. 小结)


1. 矩阵按键介绍

独立按键在于单片机连接的时候,每一个按键都需要单片机的一个I/O口,若某单片机系统需较多按键, 如果用独立按键便会占用过多的 I/O 口资源。 单片机系统中 I/O 口资源往往比较宝贵, 当用到多个按键时为了减少 I/O 口引脚, 引入了矩阵按键。

本章以 4*4 矩阵键盘为例讲解其工作原理和检测方法。开发板上将 16 个按键排成 4 行 4 列, 第一行将每个按键的一端连接在一起构成行线, 第一列将每个按键的另一端连接在一起构成列线, 这样便一共有 4 行 4 列共 8 根线, 我们将这 8 根线连接到单片机的 8 个 I/O 口上, 通过程序扫描键盘就可检测 16 个键。

无论是对立按键还是矩阵键盘,检测是否按下都是一样的(检测该端口是否为低电平)

独立键盘有一端固定为低电平, 此种方式编程比较简单。 而矩阵键盘两端都与单片机 I/O 口相连, 因此在检测时需编程通过单片机 I/O 口送出低电平。 检测方法有多种, 最常用的是行列扫描和线翻转法。

2. 矩阵按键检测方式

行列扫描法:先给一列为低电平,其余列为高电平,然后立即检测一次各行是否为低电平,若检测到某一行为低电平, 则我们便可确认当前被按下的键是哪一行哪一列的。

线翻转法:使所有的行线为低电平,检测所有列线是否有低电平,如果有, 就记录列线值; 然后再翻转, 使所有列线都为低电平, 检测所有行线的值,由于有按键按下, 行线的值也会有变化, 记录行线的值。 从而就可以检测到全部按键。

普中A7的参考资料就是线翻转法,按键检测逻辑代码如下:

cpp 复制代码
/*******************************************************************************
* 函 数 名       : key_matrix_flip_scan
* 函数功能		 : 使用线翻转扫描方法,检测矩阵按键是否按下,按下则返回对应键值
* 输    入       : 无
* 输    出    	 : key_value:1-16,对应S1-S16键,
				   0:按键未按下
//*******************************************************************************/
u8 key_matrix_flip_scan(void)
{
	static u8 key_value=0;
	KEY_MATRIX_PORT=0x0f;//给所有行赋值0,列全为1
	if(KEY_MATRIX_PORT!=0x0f)//判断按键是否按下
	{
		delay_10us(1000);//消抖
		if(KEY_MATRIX_PORT!=0x0f)
		{
			//测试列
			KEY_MATRIX_PORT=0x0f;
			switch(KEY_MATRIX_PORT)//保存行为0,按键按下后的列值	
			{
				case 0x07: key_value=1;break;
				case 0x0b: key_value=2;break;
				case 0x0d: key_value=3;break;
				case 0x0e: key_value=4;break;
			}
			//测试行
			KEY_MATRIX_PORT=0xf0;
			switch(KEY_MATRIX_PORT)//保存列为0,按键按下后的键值	
			{
				case 0x70: key_value=key_value;break;
				case 0xb0: key_value=key_value+4;break;
				case 0xd0: key_value=key_value+8;break;
				case 0xe0: key_value=key_value+12;break;
			}
			while(KEY_MATRIX_PORT!=0xf0);//等待按键松开	
		}
	}
	else
		key_value=0;		
	
	return key_value;		
}

我主要用的行列扫描法,但是和江协科技代码稍有区别,其实大差不差,甚至我觉得上面普中案例给的代码执行效率更高。

3. 实验硬件设计

本实验使用到硬件资源如下:

  • LCD1602

  • 矩阵按键

3.1 LCD1602

LCD1602 还没讲呢,就是先给出了一些实用的API,先提供调用,后面详细学习的时候再看。

3.1.1 硬件图

3.1.2 相关控制API

LCD1602.H

cpp 复制代码
#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

LCD1602.C

cpp 复制代码
// #include <REGX52.H>
#include <STC89C5xRC.H>


//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

3.2 矩阵按键

3.2.1 硬件图

我们可以看到该模块独立,4*4 矩阵按键引出的 8 根控制管脚并未直接连接到 51 单片机的 IO 上, 而是连接到 JP3 端子上。Hx(1-4)代表的是行,Lx(1-4)代表的是列。(4行4列)

我的接线方式是 P10-P17 分别接矩阵键盘的 1-8 管脚(P10-P13是行,P14-P17是列)

4. 实验软件设计

要实现的功能是:通过矩阵按键S1~S16按下后,LCD1602显示数字1-16

4.1 项目结构图

4.1.1 main.c
cpp 复制代码
#include <STC89C5xRC.H>

#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"

unsigned char KeyNumber;

/******************************************************
*	功能:按动矩阵键盘 1-16 按键,显示相应编号在 LCD1602 上
*******************************************************/
void main(){
		LCD_Init();
		LCD_ShowString(1,1,"MatrixKey");
		while(1){
			KeyNumber =  MatrixKey();
			// 0 不参与显示
			if(KeyNumber){
				LCD_ShowNum(2,1,KeyNumber,2);
			}
		}
}
4.1.2 Delay.c
cpp 复制代码
// 可调毫秒延时函数(12MHz晶振)
void delay_ms(unsigned int ms) {
    unsigned int i, j;
    for(i = ms; i > 0; i--)
        for(j = 110; j > 0; j--);
}
4.1.3 Delay.h
cpp 复制代码
#ifndef __DELAY__H__
#define __DELAY__H__

void delay_ms(unsigned int ms);

#endif
4.1.4 LCD1602.c
cpp 复制代码
// #include <REGX52.H>
#include <STC89C5xRC.H>


//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}
4.1.5 LCD1602.h
cpp 复制代码
#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif
4.1.6 MartixKey.c
cpp 复制代码
#include <STC89C5xRC.H>
#include "Delay.h"

/***************************
*	函数名		:	MatrixKey
*	函数功能	:	返回按键值
*	输入		:	无
* 输出		:	按键编号
***************************/
unsigned char MatrixKey() {
    unsigned char KeyNumber = 0;
    unsigned char i, j;
    
    // 扫描4行
    for(i = 0; i < 4; i++) {
        // 设置当前行为低电平
        P1 = ~(0x01 << i); 
        // 检查4列
        for(j = 0; j < 4; j++) {
            if(!(P1 & (0x10 << j))) { // 检查对应列是否为低电平
                delay_ms(20); // 消抖
                while(!(P1 & (0x10 << j))); // 等待按键释放
                delay_ms(20); // 再次消抖
                
                // 计算按键编号: 行号*4 + 列号 + 1
                KeyNumber = i * 4 + j + 1;
                return KeyNumber; // 直接返回,提高效率
            }
        }
    }
    return 0; // 没有按键按下
}

// 江协科技代码
//unsigned char MatrixKey(){

//	// 想直接引用就要设置初始值
//	unsigned char KeyNumber=0;
//	
//	// 初始化P1
//	P1 = 0xFF;
//	
//	// 逐行-逐列扫描,P10是第一行
//	P10 = 0;
//	// 如果说P10代表第一行,那么 P14 是第一列,
//	// 如果P14等于0,说明P10和P14是联通的,说明第一行第一列按钮被按下
//	if(P14 == 0){delay_ms(20);while(P14 == 0);delay_ms(20);KeyNumber=1;}
//	if(P15 == 0){delay_ms(20);while(P15 == 0);delay_ms(20);KeyNumber=2;}
//	if(P16 == 0){delay_ms(20);while(P16 == 0);delay_ms(20);KeyNumber=3;}
//	if(P17 == 0){delay_ms(20);while(P17 == 0);delay_ms(20);KeyNumber=4;}
//	
//	P11 = 0;
//	if(P14 == 0){delay_ms(20);while(P14 == 0);delay_ms(20);KeyNumber=5;}
//	if(P15 == 0){delay_ms(20);while(P15 == 0);delay_ms(20);KeyNumber=6;}
//	if(P16 == 0){delay_ms(20);while(P16 == 0);delay_ms(20);KeyNumber=7;}
//	if(P17 == 0){delay_ms(20);while(P17 == 0);delay_ms(20);KeyNumber=8;}
//	
//	P12 = 0;
//	if(P14 == 0){delay_ms(20);while(P14 == 0);delay_ms(20);KeyNumber=9;}
//	if(P15 == 0){delay_ms(20);while(P15 == 0);delay_ms(20);KeyNumber=10;}
//	if(P16 == 0){delay_ms(20);while(P16 == 0);delay_ms(20);KeyNumber=11;}
//	if(P17 == 0){delay_ms(20);while(P17 == 0);delay_ms(20);KeyNumber=12;}
//	
//	P13 = 0;
//	if(P14 == 0){delay_ms(20);while(P14 == 0);delay_ms(20);KeyNumber=13;}
//	if(P15 == 0){delay_ms(20);while(P15 == 0);delay_ms(20);KeyNumber=14;}
//	if(P16 == 0){delay_ms(20);while(P16 == 0);delay_ms(20);KeyNumber=15;}
//	if(P17 == 0){delay_ms(20);while(P17 == 0);delay_ms(20);KeyNumber=16;}
//	
//	return KeyNumber;
//	
//}

// 普中例程代码
/*******************************************************************************
* 函 数 名       : key_matrix_flip_scan
* 函数功能		 : 使用线翻转扫描方法,检测矩阵按键是否按下,按下则返回对应键值
* 输    入       : 无
* 输    出    	 : key_value:1-16,对应S1-S16键,
				   0:按键未按下
//*******************************************************************************/
//u8 key_matrix_flip_scan(void)
//{
//	static u8 key_value=0;
//	KEY_MATRIX_PORT=0x0f;//给所有行赋值0,列全为1
//	if(KEY_MATRIX_PORT!=0x0f)//判断按键是否按下
//	{
//		delay_10us(1000);//消抖
//		if(KEY_MATRIX_PORT!=0x0f)
//		{
//			//测试列
//			KEY_MATRIX_PORT=0x0f;
//			switch(KEY_MATRIX_PORT)//保存行为0,按键按下后的列值	
//			{
//				case 0x07: key_value=1;break;
//				case 0x0b: key_value=2;break;
//				case 0x0d: key_value=3;break;
//				case 0x0e: key_value=4;break;
//			}
//			//测试行
//			KEY_MATRIX_PORT=0xf0;
//			switch(KEY_MATRIX_PORT)//保存列为0,按键按下后的键值	
//			{
//				case 0x70: key_value=key_value;break;
//				case 0xb0: key_value=key_value+4;break;
//				case 0xd0: key_value=key_value+8;break;
//				case 0xe0: key_value=key_value+12;break;
//			}
//			while(KEY_MATRIX_PORT!=0xf0);//等待按键松开	
//		}
//	}
//	else
//		key_value=0;		
//	
//	return key_value;		
//}
4.1.7 MartixKey.h
cpp 复制代码
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__

/***************************
*	函数名		:	MatrixKey
*	函数功能	:	返回按键值
*	输入		:	无
* 输出		:	按键编号
***************************/
unsigned char MatrixKey();

#endif

5. 小结

因为其他的比较简单并且有简单注释,这里只解释 MartixKey.c 核心逻辑代码

cpp 复制代码
/***************************
*	函数名		:	MatrixKey
*	函数功能	:	返回按键值
*	输入		:	无
* 输出		:	按键编号
***************************/
unsigned char MatrixKey() {
    // 声明一个变量KeyNumber并初始化为0,用于存储检测到的按键编号。
    unsigned char KeyNumber = 0;
    // 声明两个循环变量i和j,用于控制行和列的循环。
    unsigned char i, j;
    
    // 开始一个for循环,循环4次,对应4行。i的值从0到3。
    for(i = 0; i < 4; i++) {

        // 将P1口设置为当前行为低电平,其他行为高电平。0x01 << i将1左移i位,然后取反,使得第i位
        // 为0(低电平),其他位为1(高电平)。例如,当i=0时,0x01<<0为0x01,取反后为0xFE(二进
        // 制11111110),即P1.0为低电平,其他为高电平。
        P1 = ~(0x01 << i); 

        // 内层for循环,循环4次,对应4列。j的值从0到3。
        for(j = 0; j < 4; j++) {

            // 检查P1口的第(4+j)位(即列线)是否为低电平。0x10 << j表示将0x10(即二进制00010000)左移j位,例如j=0时,为0x10(即P1.4),j=1时为0x20(P1.5)等等。然后与P1进行按位与操作,如果该列线为低电平,则表达式结果为0,取非后为真,进入if语句。
            if(!(P1 & (0x10 << j))) {

                delay_ms(20); // 消抖
                while(!(P1 & (0x10 << j))); // 等待按键释放
                delay_ms(20); // 再次消抖
                
                // 计算按键编号。按键编号的计算公式为:行号i乘以4(因为每行有4个按键)加上列号j,然后加1(因为编号从1开始)。例如,第0行第0列对应按键1,第0行第1列对应按键2,以此类推。
                KeyNumber = i * 4 + j + 1;

                return KeyNumber; // 直接返回,提高效率
            }
        }
    }
    return 0; // 没有按键按下
}
相关推荐
三佛科技-134163842122 小时前
卷发棒/卷发梳MCU方案分析
单片机·嵌入式硬件
白掰虾3 小时前
STM32N6&AI资料汇总
人工智能·stm32·嵌入式硬件·stm32n6·stm32ai
Aczone284 小时前
硬件(十)IMX6ULL 中断与时钟配置
arm开发·单片机·嵌入式硬件·fpga开发
机器视觉知识推荐、就业指导4 小时前
单片机关于中断的理解
单片机·嵌入式硬件
星空的资源小屋4 小时前
Digital Clock 4,一款免费的个性化桌面数字时钟
stm32·单片机·嵌入式硬件·电脑·excel
xiugou7987 小时前
记录一下CubeMX+Clion的STM32 CMake工程中如何修改Flash程序存储的起始地址
stm32·单片机·嵌入式硬件
XINVRY-FPGA8 小时前
XCKU15P-2FFVA1760I AMD 赛灵思 Xilinx Kintex UltraScale+ FPGA
arm开发·嵌入式硬件·阿里云·fpga开发·云计算·硬件工程·fpga
文火冰糖的硅基工坊13 小时前
[硬件电路-192]:基级与发射极两端的电压超过1.5v可能是什么原因
单片机·嵌入式硬件·系统架构·电路·跨学科融合
源远流长jerry16 小时前
STM32之RTOS移植和使用
stm32·单片机·嵌入式硬件