# key4x4_scan **Repository Path**: lushidegreen/key4x4_scank ## Basic Information - **Project Name**: key4x4_scan - **Description**: 该仓库使用“按键驱动框架”,实现了4x4矩阵键盘的驱动。支持短按、长按、双击、持续按4种键值。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 1 - **Created**: 2023-09-08 - **Last Updated**: 2025-07-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 4x4矩阵键盘驱动(Lu) #### 硬件需求 1.STM32最小系统 ![输入图片说明](image/31096016_1733933.png) 2.一块4x4的键盘模块 ![1694146955654](image/1694146955654.png) #### 软件需求 1.STM32CubeMX 2.Keil-ARM(V5) #### 4x4矩阵键盘扫描方法 每次扫描,分2步。 第一步:行(PA0~3)设为上拉输入,列(PA4~7)设为开漏输出LOW,然后读取行(PA0~3)的输入值。 ​ 如果有按键输入,则相应的行为0,其他行为1。 ![1694158481636](image/1694158481636.png) 第二步:列(PA4~7)设为上拉输入,行(PA0~3)设为开漏输出LOW,然后读取列(PA4~7)的输入值。 ​ 如果有按键输入,则相应的列为0,其他列为1。 ![1694158799806](image/1694158799806.png) 如果不考虑多个按键按下的情况,则有16种输入值,对应16个按键。(详见后面的Step3中在key.h定义的输入值。) #### Step1.构建工程。 可以用任何方式构建工程。只要完成8个连接键盘的GPIO,一个Timer,一个UART的初始化即可。 为了简单,在此用CubeMX v6.0.1进行构建。版本不重要,构建完成之后,后面不再使用CubeMX。 [构建工程不是本文的重点,所以过程从略,仅对CubeMX配置过程截图。] ![1693993592043](image/1693993592043.png) ![1693994143557](image/1693994143557.png) 按键引脚KEY_L1~4,KEY_R1~4,先配置为输出。 ![1693994203558](image/1693994203558.png) ![1693993877667](image/1693993877667.png) ![1694011327674](image/1694011327674.png) ![1694144414227](image/1694006989404.png) #### Step2.将printf()重定向到UART。 ``` /* USER CODE BEGIN Includes */ #include "stdio.h" /* USER CODE END Includes */ /* USER CODE BEGIN PV */ /** * @brief: 标准接口驱动函数fputc的实现.映射到UART1. * @author: lusd * @param [in] ch,要输出的字符. * @param [in] f,文件指针.此函数中未使用. * @return 直接返回传入的参数ch. */ int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 10000); return ch; } /* USER CODE END PV */ ``` #### Step3.移植按键驱动框架。 从[按键驱动框架](https://gitee.com/lushidegreen/key-driver-framework)复制key.c/.h到工程。 并包含头文件key.h,然后添加启动定时和定时器中断代码: ``` /* USER CODE BEGIN 2 */ HAL_TIM_Base_Start_IT(&htim1); /* USER CODE END 2 */ ``` ``` /* USER CODE BEGIN 4 */ /** * @brief: TIM周期中断回调函数.每10ms将scan_flag置1. * @author: lusd * @param [in] htim,产生中断的定时器句柄. * @return none. */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (TIM1 == htim->Instance) { key.scan_flag = 1; } } /* USER CODE END 4 */ ``` ``` /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ /* 扫描按键,并输出键值 */ if (key.scan_flag) { key_val = key_scan(); if (KEY_NONE != key_val) { printf("KEY VALUE = %d.\r\n", key_val); } } } /* USER CODE END 3 */ ``` #### Step4.适配矩阵键盘硬件。 根据硬件,修改get_keys_input()函数。 ``` static uint16_t get_key_input(void) { uint16_t key_val = NOKEY_INPUT_VALUE; uint16_t input; uint16_t row = 0; // 行 uint16_t column = 0; // 列 KEY_GPIO_input_pullup(KEY_R1_Pin|KEY_R2_Pin|KEY_R3_Pin|KEY_R4_Pin); // 行设为输入上拉 KEY_GPIO_output_od_Low(KEY_L1_Pin|KEY_L2_Pin|KEY_L3_Pin|KEY_L4_Pin); // 列设为开漏输出Low input = GPIOA->IDR; // 读取原始输入 if(!(input & KEY_R1_Pin)) { // PA4输入低电平有效 row |= KEY_R1_Pin; } if(!(input & KEY_R2_Pin)) { // PA5输入低电平有效 row |= KEY_R2_Pin; } if(!(input & KEY_R3_Pin)) { // PA6输入低电平有效 row |= KEY_R3_Pin; } if(!(input & KEY_R4_Pin)) { // PA7输入低电平有效 row |= KEY_R4_Pin; } KEY_GPIO_input_pullup(KEY_L1_Pin|KEY_L2_Pin|KEY_L3_Pin|KEY_L4_Pin); // 列设为输入上拉 KEY_GPIO_output_od_Low(KEY_R1_Pin|KEY_R2_Pin|KEY_R3_Pin|KEY_R4_Pin); // 行设为开漏输出Low input = GPIOA->IDR; // 读取原始输入 if(!(input & KEY_L1_Pin)) { // PA0输入低电平有效 column |= KEY_L1_Pin; } if(!(input & KEY_L2_Pin)) { // PA1输入低电平有效 column |= KEY_L2_Pin; } if(!(input & KEY_L3_Pin)) { // PA2输入低电平有效 column |= KEY_L3_Pin; } if(!(input & KEY_L4_Pin)) { // PA3输入低电平有效 column |= KEY_L4_Pin; } KEY_GPIO_input_pullup(KEY_R1_Pin|KEY_R2_Pin|KEY_R3_Pin|KEY_R4_Pin); // 行设为输入上拉 key_val = row | column; return key_val; } ``` 设置按键引脚状态的两个函数: ``` /** * @brief: 配置按键引脚为输入上拉. * @author: lusd * @param [in] Pin,引脚掩码. * @return none. */ void KEY_GPIO_input_pullup(uint32_t Pin) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } ``` ``` /** * @brief: 配置按键引脚为开漏输出Low. * @author: lusd * @param [in] Pin,引脚掩码. * @return none. */ void KEY_GPIO_output_od_Low(uint32_t Pin) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOA, Pin, GPIO_PIN_RESET); GPIO_InitStruct.Pin = Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } ``` 在key.h定义按键按下时的输入值: ![1694145112410](image/1694145112410.png) 定义各个按键返回的键值,每个按键有4种: (短按)KEYn_SHORT、(长按)KEYn_LONG、(持续按)KEYn_CNTINUS、(双击)KEYn_DOUBLE。 ![1694145207236](image/1694145207236.png) 修改keys_info[]按键信息表 : ``` key_info_t keys_info[HW_KEYS_NUM] = { {KEY1_INPUT_VALUE, KEY1_SHORT, KEY1_LONG, KEY_NONE, KEY1_DOUBLE, 200}, {KEY2_INPUT_VALUE, KEY2_SHORT, KEY2_LONG, KEY_NONE, KEY2_DOUBLE, 200}, {KEY3_INPUT_VALUE, KEY3_SHORT, KEY3_LONG, KEY_NONE, KEY3_DOUBLE, 200}, {KEY4_INPUT_VALUE, KEY4_SHORT, KEY4_LONG, KEY_NONE, KEY4_DOUBLE, 200}, {KEY5_INPUT_VALUE, KEY5_SHORT, KEY5_LONG, KEY_NONE, KEY5_DOUBLE, 200}, {KEY6_INPUT_VALUE, KEY6_SHORT, KEY6_LONG, KEY_NONE, KEY6_DOUBLE, 200}, {KEY7_INPUT_VALUE, KEY7_SHORT, KEY7_LONG, KEY_NONE, KEY7_DOUBLE, 200}, {KEY8_INPUT_VALUE, KEY8_SHORT, KEY8_LONG, KEY_NONE, KEY8_DOUBLE, 200}, {KEY9_INPUT_VALUE, KEY9_SHORT, KEY9_LONG, KEY_NONE, KEY9_DOUBLE, 200}, {KEY10_INPUT_VALUE, KEY10_SHORT, KEY10_LONG, KEY_NONE, KEY10_DOUBLE, 200}, {KEY11_INPUT_VALUE, KEY11_SHORT, KEY11_LONG, KEY_NONE, KEY11_DOUBLE, 200}, {KEY12_INPUT_VALUE, KEY12_SHORT, KEY12_LONG, KEY_NONE, KEY12_DOUBLE, 200}, {KEY13_INPUT_VALUE, KEY13_SHORT, KEY13_LONG, KEY_NONE, KEY13_DOUBLE, 200}, {KEY14_INPUT_VALUE, KEY14_SHORT, KEY14_LONG, KEY_NONE, KEY14_DOUBLE, 200}, {KEY15_INPUT_VALUE, KEY15_SHORT, KEY15_LONG, KEY_NONE, KEY15_DOUBLE, 200}, {KEY16_INPUT_VALUE, KEY16_SHORT, KEY16_LONG, KEY_NONE, KEY16_DOUBLE, 200}, }; ``` 注:当前每个按键都是支持3种键值:短按、长按、双击。长按键值生效时间均为2秒。 #### step5.编译、运行测试。 下图是KEY1和KEY2的分别 短按、长按、双击后的串口信息。 ![1694146531691](image/1694146531691.png) The End. 2023-09-08.