STM32 (基于HAL库)4×4矩阵按键驱动详细教程

STM32矩阵键盘:

矩阵的工作原理:
矩阵原理图如下:
在这里插入图片描述
矩阵键盘由16个按键组成,也就是4*4矩阵键盘,按照传统的按键接法,16个按键需要16个单片机io口,按照矩阵键盘接法,16个按键只需要八个io口,我选择了PA口的0~7号引脚,前四个依次接矩阵键盘的行,后四个依次接矩阵键盘的列;矩阵键盘的每一个按键都是接了两个io口,也就是一端必须是输出高电平或者低电平,另一端去检测是否是高电平或者低电平;简单来说,也就是一端配置为GPIO输出,另一端配置为GPIO输入或者外部中断检测。所以只需要配置行线或者列线为输出,则另外一端就配置为中断检测。这个只是为了方便才选择连续的引脚,也可以选择不连续的引脚。
下面是STM32CubeMax配置引脚图:
在这里插入图片描述
我把行线配置为输出,列线配置为中断;
在这里插入图片描述
PA0-PA3配置为开漏输出低电平,其实推挽输出低电平也行。大家不明白腿玩输出与开漏输出的可以自己去查一下。
在这里插入图片描述

PA4-PA7配置为外部中断,下降沿触发,上拉,之所以这样配置,是因为我们PA0-PA3配置的是输出低电平,所以我们需要先上拉,让他那个引脚默认输入高电平,只有按下按键,两引脚想通才会由高电平变为低电平,所以我们需要按下按键触发的话也就是由高到低的时候,也就是下降沿触发。
配置完了矩阵键盘,还需要配置一下串口1,完成输出显示。

在这里插入图片描述
记得勾选中断,设置一下中断优先级
以上就是相关配置,点击右上角生成代码即可。

为了方便,给大家准备好了一份驱动代码:

keyboard_exit.c文件:

#include "keybord_exit.h"
#include "stm32f1xx_hal.h"


#define  KEY_DELAY  10  //中断去抖动间隔

volatile uint8_t  key;
volatile uint32_t  key_tic;
	
static void Key_EXTI_Mode(void)// A4-7exti 0-3output
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();
  //__HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, GPIO_PIN_RESET);

  /*Configure GPIO pins : PA4 PA5 PA6 PA7 */
  GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pins : PA0 PA1 PA2 PA3 */
  GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI4_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(EXTI4_IRQn);

  HAL_NVIC_SetPriority(EXTI9_5_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
}

static void Key_Input_Mode(void)// A4-7output 0-3input
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);

 
  /*Configure GPIO pins : PA4 PA5 PA6 PA7 */
  GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pins : PA0 PA1 PA2 PA3 */
  GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

static uint8_t Get_Key(uint8_t num)//按键两引脚值相加(引脚只用到了前八个所以只有低 八位)
{
	switch(num)
	{
		case 0x11:key='0';break;
		case 0x21:key='1';break;
		case 0x41:key='2';break;
		case 0x81:key='3';break;
		case 0x12:key='4';break;
		case 0x22:key='5';break;
		case 0x42:key='6';break;
		case 0x82:key='7';break;
		case 0x14:key='8';break;
		case 0x24:key='9';break;
		case 0x44:key='A';break;
		case 0x84:key='B';break;
		case 0x18:key='C';break;
		case 0x28:key='D';break;
		case 0x48:key='E';break;		
		case 0x88:key='F';break;	
	}
	return key;
}
//非阻塞方式获取按键,返回0,没有键值,非0,获得键值
//该函数在中断中调用
uint8_t Get_KeyNum(uint16_t GPIO_Pin)
{
	uint8_t i=0,num=0;

	num = (uint8_t)GPIO_Pin;
	
	if(HAL_GetTick() - key_tic < KEY_DELAY) //中断间隔过小,放弃
	{
		key_tic = HAL_GetTick();
		return 0;
	}
	HAL_Delay(1);
	key_tic = HAL_GetTick();
	//略微延时后,读取管脚状态,去除上升沿抖动
	if( HAL_GPIO_ReadPin(GPIOA,GPIO_Pin) != GPIO_PIN_RESET ) 
		return 0;
	
	if(num)
	{
		Key_Input_Mode();
		for(i=0;i<4;i++)
		{
			if(HAL_GPIO_ReadPin(GPIOA,(1<<i)) == GPIO_PIN_RESET)
				num |= (1<<i);
		}
		Key_EXTI_Mode();	
		return Get_Key(num);
	}
	else
	{
		Key_EXTI_Mode();	
		return 0;
	}
}

void Key_Bord_Init(void)
{
	Key_EXTI_Mode();
	key = 0;
	key_tic = HAL_GetTick();
}

这份驱动文件里面可能大家不太明白,我们cubemax配置了引脚,怎么还有引脚配置的操作,是这样的,这个文件的引脚配置是为了让轮询和中断同时使用,后面完成一个相互切换的配置。Key_EXTI_Mode函数是中断配置;
Key_Input_Mode函数是轮询配置。只不过转成轮询之后输出端改变了一下,变成了列输出,行输入。

还有一个地方就是Get_Key函数里面的这些十六进制,大家不太明白,我解释一下,在Get_KeyNum函数中有下图的一段代码
在这里插入图片描述
这段代码先是轮询检测是哪个引脚按下了,就是一个移位运算,后边num|=(1<<i)简单来讲,就是把一个按键两端同时接的两个引脚的值相加起来;而这些值就是stm32f1xx_hal_gpio.h里的这些引脚的宏值,如下:
在这里插入图片描述
比如说,我上边PA0和PA4接的第一个按键,因为我们用的0——7引脚,八位全是0,只用到了低八位,那就是0x01(PA0)与0x10(PA4)相加,之后结果就是0x11,依次类推,也可以算出其他按键被按下的值;所以1-16按键按下,依次返回’0’-'F’的字符。
keyboard_exit.h文件:

#ifndef __KEY_BORD_EXTI_H
#define __KEY_BORD_EXTI_H
#include "stm32f1xx_hal.h"

extern volatile uint8_t key;

void Key_Bord_Init(void);
//非阻塞方式获取按键,返回0,没有键值,非0,获得键值
//中断中调用
uint8_t Get_KeyNum(uint16_t GPIO_Pin);

#define ReadPin_Port  GPIOA

#endif

主函数代码:

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "keybord_exit.h"
#include <stdio.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//输出重定向
int fputc(int ch,FILE *f){
 uint8_t temp[1]={ch};
 HAL_UART_Transmit(&huart1,temp,1,2);
 return ch;
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)//实现一下回调函数
{
	char c;
	
	c = Get_KeyNum(GPIO_Pin);
	if(c != 0)//判断按键是否真正有按键按下
	{
		printf("按键值:%c\n",c);
	}
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  
//Key_Bord_Init();//如果cubemax直接配置了,就不用调用这一句

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

运行效果:
在这里插入图片描述

以上就是矩阵键盘的原理和驱动代码。

  • 44
    点赞
  • 297
    收藏
    觉得还不错? 一键收藏
  • 49
    评论
STMicroelectronics的STM32F4系列微控制器是一款高性能、低功耗的MCU芯片。HAL(Hardware Abstraction Layer)库是STMicroelectronics提供的一种软件库,用于简化STM32系列微控制器的开发流程。 SPI(Serial Peripheral Interface)是一种通信协议,可以用于在微控制器和外设之间进行串行数据传输。LCD(Liquid Crystal Display)是一种常见的显示器件,用于显示图像和文本。使用STM32F4 HAL库驱动SPI LCD可以实现在STM32F4微控制器上控制LCD屏幕的功能。 首先,我们需要在STM32F4的硬件上连接SPI总线和LCD屏幕。通过阅读相关的硬件文档,我们可以了解到哪些引脚是SPI总线的主线和从线,以及如何正确连接LCD屏幕。 接下来,我们需要编写代码来初始化SPI总线和LCD屏幕。使用HAL库的相关函数,我们可以在代码中初始化SPI总线和配置相关的参数,如数据传输速率、时钟相位、数据位宽等。 在SPI总线初始化完成后,我们可以使用HAL库的函数来发送和接收数据。通过编写相应的代码,我们可以将需要显示的图像数据或文本发送到LCD屏幕上。 最后,我们需要编写代码来进行LCD屏幕的控制。这包括设置显示模式(如亮度、对比度等)、光标位置和显示字符等。HAL库提供了一系列函数,可以简化这些操作的实现。 总之,使用STM32F4 HAL库驱动SPI LCD可以方便地在STM32F4微控制器上控制LCD屏幕显示。我们只需要正确连接硬件,并编写相应的代码来初始化SPI总线和LCD屏幕,然后使用HAL库的函数进行数据传输和屏幕控制即可。这样可以大大简化LCD驱动的开发流程,节省开发时间和提高开发效率。
评论 49
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值