【STM32F4系列】【自制库】6×6矩阵键盘(软件部分,扫描法和行反转法)

请先看硬件和扫描方法的内容,本文章基于此,传送门https://blog.csdn.net/m0_57585228/article/details/125228656

需求

可以分别识别每个按键的按下与松开

扫描法

思路

  1. 初始化
  2. 定时器定时扫描一次
  3. 读取扫描到的按键
  4. 如果这次读取的和上次不同,则标志置位
  5. 将数据暂存起来,利用状态机的思想,当出现按下信号时进入按下状态,当回到松开信号时回到原状态

初始化

需要初始化的外设有,GPIO,定时器中断

GPIO

有2组,输入组设置为上拉输入,输出组设置为开漏上拉输出,详情之前介绍过
GPIOhttps://blog.csdn.net/m0_57585228/article/details/124498831

头文件(KeyBoard.h)

#define Key_L0 GPIO_Pin_0
#define Key_L1 GPIO_Pin_1
#define Key_L2 GPIO_Pin_2
#define Key_L3 GPIO_Pin_3
#define Key_L4 GPIO_Pin_4
#define Key_L5 GPIO_Pin_5
#define Key_L_GPIOx GPIOA
#define Key_L_GPIO_RCC RCC_AHB1Periph_GPIOA

#define Key_R0 GPIO_Pin_0
#define Key_R1 GPIO_Pin_1
#define Key_R2 GPIO_Pin_10
#define Key_R3 GPIO_Pin_3
#define Key_R4 GPIO_Pin_4
#define Key_R5 GPIO_Pin_5
#define Key_R_GPIOx GPIOB
#define Key_R_GPIO_RCC RCC_AHB1Periph_GPIOB

C文件(KeyBoard.c)

void Key_Scan_GPIO_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;												//声明GPIO初始化结构体
	RCC_AHB1PeriphClockCmd(Key_L_GPIO_RCC, ENABLE);									//打开GPIO时钟
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;										//设置为输出模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;										//设置为开漏输出
	GPIO_InitStruct.GPIO_Pin = Key_L0 | Key_L1 | Key_L2 | Key_L3 | Key_L4 | Key_L5; //设置引脚
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;										//设置为上拉
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;									//设置为快速模式
	GPIO_Init(Key_L_GPIOx, &GPIO_InitStruct);
	Set();
	RCC_AHB1PeriphClockCmd(Key_R_GPIO_RCC, ENABLE);									//打开GPIO时钟
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;										//设置为输入模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;										//
	GPIO_InitStruct.GPIO_Pin = Key_R0 | Key_R1 | Key_R2 | Key_R3 | Key_R4 | Key_R5; //设置引脚
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;										//设置为上拉
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;									//设置为快速模式
	GPIO_Init(Key_R_GPIOx, &GPIO_InitStruct);										//初始化
}
void Set(void)
{
	GPIO_SetBits(Key_L_GPIOx, Key_L0 | Key_L1 | Key_L2 | Key_L3 | Key_L4 | Key_L5);
}

定时器

需要开启定时器中断,中断时间设置为几到几十毫秒即可,因为在定时器中使用了1us延迟,我们给定时器中断的个较低的优先级

定时器https://blog.csdn.net/m0_57585228/article/details/124520929

C文件(KeyBoard.c)

void Key_Scan_Tim_init(void)
{
	TIM_TimeBaseInitTypeDef TIM_Init_Struct;			  //声明定时器初始化结构体
	NVIC_InitTypeDef NVIC_Init_Struct;					  //声明NVIC初始化结构体
	RCC_APB1PeriphClockCmd(Key_Tim_RCC, ENABLE);		  //打开时钟
	TIM_Init_Struct.TIM_ClockDivision = TIM_CKD_DIV1;	  //滤波器不分频
	TIM_Init_Struct.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式

	//每次中断触发时间=[(TIM_Period+1)*(TIM_Prescaler+1)/(SystemCoreClock)] (s)
	//这里是10ms
	TIM_Init_Struct.TIM_Period = 840 - 1;
	TIM_Init_Struct.TIM_Prescaler = 1000 - 1;
	TIM_Init_Struct.TIM_RepetitionCounter = 0;	   //高级定时器特有,这里写0就行
	TIM_TimeBaseInit(Key_Timx, &TIM_Init_Struct);  //调用函数初始
	TIM_ITConfig(Key_Timx, TIM_IT_Update, ENABLE); //启用溢出中断

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//设置NVIC中断分组2
	NVIC_Init_Struct.NVIC_IRQChannel = Tim_IRQn;			//中断名称
	NVIC_Init_Struct.NVIC_IRQChannelCmd = ENABLE;			//使能
	NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority = 2; //主优先级1
	NVIC_Init_Struct.NVIC_IRQChannelSubPriority = 3;		//副优先级1
	NVIC_Init(&NVIC_Init_Struct);							//初始化NVIC
	TIM_Cmd(Key_Timx, ENABLE);								//打开定时器
}

读取和置位函数

C文件(KeyBoard.c)

u8 Key_Scan_Read_IN(void)
{
	u8 zj = 0;
	if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R0))
		zj |= 1 << 0;
	if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R1))
		zj |= 1 << 1;
	if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R2))
		zj |= 1 << 2;
	if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R3))
		zj |= 1 << 3;
	if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R4))
		zj |= 1 << 4;
	if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R5))
		zj |= 1 << 5;
	return (~zj) & 0x3f;
}
void Key_Scan_ReSet(u8 i)
{
	if (i == 0)
		GPIO_ResetBits(Key_L_GPIOx, Key_L0);
	else if (i == 1)
		GPIO_ResetBits(Key_L_GPIOx, Key_L1);
	else if (i == 2)
		GPIO_ResetBits(Key_L_GPIOx, Key_L2);
	else if (i == 3)
		GPIO_ResetBits(Key_L_GPIOx, Key_L3);
	else if (i == 4)
		GPIO_ResetBits(Key_L_GPIOx, Key_L4);
	else if (i == 5)
		GPIO_ResetBits(Key_L_GPIOx, Key_L5);
}

定时器扫描

每一次触发中断扫描全部输出线,需要间隔一段时间避免频率过高导致误触

然后将其暂存起来、

C文件(KeyBoard.c)

u8 Key_Scan_Dat[6] = {0};
void Key_Scan_Code(void)
{
	static u8 Dat1[6] = {0};
	static u8 Dat2[6] = {0};
	static u8 ins = 0;
	if (ins == 0)
	{
		for (int i = 0; i < 6; i++)
		{
			Set();
			Key_Scan_ReSet(i);
			Delay_us(1);
			Dat1[i] = Key_Scan_Read_IN();
			ins = 1;
		}
		Set();
	}
	else if (ins == 1)
	{
		for (int i = 0; i < 6; i++)
		{
			Set();
			Key_Scan_ReSet(i);
			Delay_us(1);
			Dat2[i] = Key_Scan_Read_IN();
		}
		Set();
		for (int i = 0; i < 6; i++)
		{
			if ((Dat2[i] == Dat1[i]))
			{
				if (Key_Scan_Dat[i] != Dat1[i])
					Key_OK = 1;
				Key_Scan_Dat[i] = Dat1[i];
			}
		}
		ins = 0;
	}
}

动作函数

为了增加通用性,这个函数可以在主函数调用,也可以在中断调用

使用了函数指针,使用时将两个参数传入即可,就j和i分别是行和列的序号

C文件(KeyBoard.c)

void Key_Scan_Action(void (*Down)(u8 R, u8 L), void (*Up)(u8 R, u8 L))
{

	if (Key_OK)
	{
		for (int i = 0; i < 6; i++)
		{
			for (int j = 0; j < 6; j++)
			{
				if (Key_Scan_Dat[i] & (1 << j))
				{
					Key_Key[j][i] = 1;
					Key_OK = 0;
					Down(j, i);
				}
				if ((Key_Key[j][i] == 1) && ((Key_Scan_Dat[i] & (1 << j)) == 0))
				{
					Key_Key[j][i] = 0;
					Up(j, i);
				}
			}
		}
	}
}

行反转法

GPIO

输入和输出均设置为开漏上拉输出

C文件(KeyBoard.c)

void Key_RLreverse_GPIO_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;												//声明GPIO初始化结构体
	RCC_AHB1PeriphClockCmd(Key_R_GPIO_RCC, ENABLE);									//打开GPIO时钟
	RCC_AHB1PeriphClockCmd(Key_L_GPIO_RCC, ENABLE);									//打开GPIO时钟
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;										//设置为输出模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;										//设置为开漏输出
	GPIO_InitStruct.GPIO_Pin = Key_R0 | Key_R1 | Key_R2 | Key_R3 | Key_R4 | Key_R5; //设置引脚
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;										//设置为上拉
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;									//设置为快速模式
	GPIO_Init(Key_R_GPIOx, &GPIO_InitStruct);
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;										//设置为输出模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;										//设置为开漏输出
	GPIO_InitStruct.GPIO_Pin = Key_L0 | Key_L1 | Key_L2 | Key_L3 | Key_L4 | Key_L5; //设置引脚
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;										//设置为上拉
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;									//设置为快速模式
	GPIO_Init(Key_L_GPIOx, &GPIO_InitStruct);										//初始化
}

定时器

定时器设置和扫描法基本相同

C文件(KeyBoard.c)

void Key_RLreverse_Tim_init(void)
{
	TIM_TimeBaseInitTypeDef TIM_Init_Struct;			  //声明定时器初始化结构体
	NVIC_InitTypeDef NVIC_Init_Struct;					  //声明NVIC初始化结构体
	RCC_APB1PeriphClockCmd(Key_Tim_RCC, ENABLE);		  //打开时钟
	TIM_Init_Struct.TIM_ClockDivision = TIM_CKD_DIV1;	  //滤波器不分频
	TIM_Init_Struct.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式

	//每次中断触发时间=[(TIM_Period+1)*(TIM_Prescaler+1)/(SystemCoreClock)] (s)
	//这里是5ms
	TIM_Init_Struct.TIM_Period = 840 - 1;
	TIM_Init_Struct.TIM_Prescaler = 1000 - 1;
	TIM_Init_Struct.TIM_RepetitionCounter = 0;	   //高级定时器特有,这里写0就行
	TIM_TimeBaseInit(Key_Timx, &TIM_Init_Struct);  //调用函数初始
	TIM_ITConfig(Key_Timx, TIM_IT_Update, ENABLE); //启用溢出中断

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//设置NVIC中断分组2
	NVIC_Init_Struct.NVIC_IRQChannel = Tim_IRQn;			//中断名称
	NVIC_Init_Struct.NVIC_IRQChannelCmd = ENABLE;			//使能
	NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority = 3; //主优先级1
	NVIC_Init_Struct.NVIC_IRQChannelSubPriority = 3;		//副优先级1
	NVIC_Init(&NVIC_Init_Struct);							//初始化NVIC
	TIM_Cmd(Key_Timx, ENABLE);								//打开定时器
}

读取和置位函数

C文件(KeyBoard.c)

void Key_RLreverse_Set(u8 R_L, u8 bool)
{
	if (R_L == 1)
	{
		if (bool == 1)
			GPIO_SetBits(Key_L_GPIOx, (Key_L0 | Key_L1 | Key_L2 | Key_L3 | Key_L4 | Key_L5));
		else if (bool == 0)
			GPIO_ResetBits(Key_L_GPIOx, (Key_L0 | Key_L1 | Key_L2 | Key_L3 | Key_L4 | Key_L5));
	}
	else if (R_L == 0)
	{
		if (bool == 1)
			GPIO_SetBits(Key_R_GPIOx, (Key_R0 | Key_R1 | Key_R2 | Key_R3 | Key_R4 | Key_R5));
		else if (bool == 0)
			GPIO_ResetBits(Key_R_GPIOx, (Key_R0 | Key_R1 | Key_R2 | Key_R3 | Key_R4 | Key_R5));
	}
}
u8 Key_RLreverse_Read(u8 R_L)
{
	u8 zj = 0;
	if (R_L == 0)
	{
		zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R5);
		zj <<= 1;
		zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R4);
		zj <<= 1;
		zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R3);
		zj <<= 1;
		zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R2);
		zj <<= 1;
		zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R1);
		zj <<= 1;
		zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R0);
	}
	else if (R_L == 1)
	{
		zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L5);
		zj <<= 1;
		zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L4);
		zj <<= 1;
		zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L3);
		zj <<= 1;
		zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L2);
		zj <<= 1;
		zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L1);
		zj <<= 1;
		zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L0);
	}
	return zj;
}

行反转函数

这个函数是用来扫描的,需要在定时器中调用

也可以稍微更改一下放入外部中断中(可能需要额外硬件或者占用多个中断源,不推荐)

C文件(KeyBoard.c)

void Key_RLreverse_Code()
{

	static u8 zj_R;
	static u8 zj_L;
	u16 zj = 0;
	static u8 Key_Code_ins;
	static u16 Bef_Dat;
	if (Key_Code_ins == 1) //去抖
	{
		zj_R = Key_RLreverse_Read(Key_R);
		Key_RLreverse_Set(Key_R, 0); //行设为
		Key_RLreverse_Set(Key_L, 1); //列设成
		Key_Code_ins = 2;
	}
	else if (Key_Code_ins == 2)
	{
		zj_L = Key_RLreverse_Read(Key_L);
		zj = zj_L | (zj_R << 8);
		if (zj != Bef_Dat)
		{
			Key_OK = 1;
			Key_Data[Key_L] = zj_L;
			Key_Data[Key_R] = zj_R;
			Key_RLreverse_Decode();
		}
		Bef_Dat = zj;
		Key_Code_ins = 0;
	}
	else
	{

		Key_RLreverse_Set(Key_L, 0); //行设为1
		Key_RLreverse_Set(Key_R, 1); //列设为0
		if (Key_RLreverse_Read(Key_R) != 0x3F)
			Key_Code_ins = 1;
		else
			Key_Code_ins = 0;
	}
}

动作函数

和扫描法类似

C文件(KeyBoard.c)

void Key_RLreverse_Action(void (*Down)(u8 R, u8 L), void (*Up)(u8 R, u8 L))
{
	if (Key_OK)
	{
		Key_OK = 0;
		for (int i = 0; i < 6; i++)
		{
			for (int j = 0; j < 6; j++)
			{
				if (Key_Key[i][j] == Key_Down)
				{

					Down(i, j);
				}
				else if (Key_Key[i][j] == Key_Up)
				{
					Key_Key[i][j] = Key_Keep;

					Up(i, j);
				}
			}
		}
	}
}

成品

矩阵键盘演示,可以识别按键按下与松开

GitHubicon-default.png?t=M4ADhttps://github.com/HZ1213825/STM32F4_Matrix_KeyBoard百度网盘icon-default.png?t=M4ADhttps://pan.baidu.com/s/1U1B8jIfiXDHqmPbITeSRmw?pwd=mtlu 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的 STM32 矩阵键盘扫描程序示例: ```c #include "stm32f10x.h" // 矩阵键盘的行和列 #define ROW_NUM 4 #define COL_NUM 4 // 矩阵键盘的行和列对应的 GPIO 端口和引脚 #define ROW_GPIO GPIOA #define COL_GPIO GPIOB #define ROW_PIN(x) (GPIO_Pin_0 << (x)) #define COL_PIN(x) (GPIO_Pin_0 << (x)) // 矩阵键盘的按键值 const uint8_t KEY_VALUE[ROW_NUM][COL_NUM] = { {'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', 'C'}, {'*', '0', '#', 'D'} }; // 矩阵键盘扫描函数 void scan_key(void) { uint8_t row, col; char key_value; // 初始化矩阵键盘的行为输出,列为输入 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; for (row = 0; row < ROW_NUM; row++) { GPIO_InitStructure.GPIO_Pin = ROW_PIN(row); GPIO_Init(ROW_GPIO, &GPIO_InitStructure); } GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; for (col = 0; col < COL_NUM; col++) { GPIO_InitStructure.GPIO_Pin = COL_PIN(col); GPIO_Init(COL_GPIO, &GPIO_InitStructure); } // 扫描行 for (row = 0; row < ROW_NUM; row++) { GPIO_ResetBits(ROW_GPIO, ROW_PIN(row)); // 检测列 for (col = 0; col < COL_NUM; col++) { if (GPIO_ReadInputDataBit(COL_GPIO, COL_PIN(col)) == RESET) { key_value = KEY_VALUE[row][col]; // 处理按键值 } } GPIO_SetBits(ROW_GPIO, ROW_PIN(row)); } } int main(void) { while (1) { scan_key(); } } ``` 该程序的原理是通过依次将矩阵键盘的每一行拉低,并检测每一列的输入来确定按下的按键。在实际应用中,可以将该程序集成到其他应用程序中,以实现对矩阵键盘的输入响应。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值