在蓝桥杯嵌入式赛题中,按键检测几乎每年都考,考到的概率大概就是 单击 > 长按 > 双击。
按键检测有很多方法,比如阻塞法(Delay),外部中断法(EXTI),状态机法等等。
下面先来一张图,介绍一下我的思路。(写字很丑,介意勿喷),如果各位有更好的方法或者我的思路或者代码有问题,欢迎大家指正!
我这次用的是定时中断法(状态机)。
图中的 LONG 和 DOUBLE 是宏定义
#define LONG 100 //长按最低时间要求 100表示100*10ms=1000ms=1.0s
#define DOUBLE 30 //双击时间间隔,30表示30*10ms=300ms=0.3s
time1 和 time2是程序中的用来定时的变量。
下面是一些宏定义和定义的变量等。
#define LONG 100
#define DOUBLE 30
enum
{
KEY_UP,
KEY_DE,
KEY_WA
} keys[4];
bool key_state[4];
bool key_singleflag[4];
bool key_longflag[4];
bool key_doubleflag[4];
bool key_period[4] = {0};
int time1 = 0;
int time2 = 0;
下面是key_scan()函数 按键扫描,key_scan()函数到时候要放到定时器回调函数中。
void key_scan(void)
{
key_state[0] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
key_state[1] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
key_state[2] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
key_state[3] = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
for (int i = 0; i < 4; i++)
{
if (!key_period[i]) // 第一状态
{
switch (keys[i])
{
case KEY_UP:
if (key_state[i] == GPIO_PIN_RESET)
{
keys[i] = KEY_DE;
}
break;
case KEY_DE:
{
if (key_state[i] == GPIO_PIN_RESET)
{
keys[i] = KEY_WA;
}
else
{
keys[i] = KEY_UP;
}
}
break;
case KEY_WA:
{
time1++;
if (key_state[i] == GPIO_PIN_SET)
{
if (time1 <= DOUBLE)
{
key_period[i] = 1;
keys[i] = KEY_UP;
time1 = 0;
}
else if (time1 > DOUBLE && time1 < LONG)
{
key_singleflag[i] = 1;
keys[i] = KEY_UP;
time1 = 0;
}
else if (time1 >= LONG)
{
key_longflag[i] = 1;
keys[i] = KEY_UP;
time1 = 0;
}
}
}
break;
default:
break;
}
}
if (key_period[i])//第二状态
{
if (++time2 <= DOUBLE)
{
switch (keys[i])
{
case KEY_UP:
{
if (key_state[i] == GPIO_PIN_RESET)
{
keys[i] = KEY_DE;
}
}
break;
case KEY_DE:
{
if (key_state[i] == GPIO_PIN_RESET)
{
keys[i] = KEY_WA;
}
else
{
keys[i] = KEY_UP;
}
}
break;
case KEY_WA:
{
if (key_state[i] == GPIO_PIN_SET)
{
keys[i] = KEY_UP;
if (time2 <= DOUBLE)
{
key_doubleflag[i] = 1;
time2 = 0;
key_period[i] = 0;
}
}
}
break;
default:
break;
}
}
else
{
keys[i]=KEY_UP;
if(key_state[i]==GPIO_PIN_SET)
{
key_singleflag[i] = 1;
key_period[i] = 0;
time2 = 0;
}
}
}
}
}
下面是中断回调函数,我用的是TIM4,PSC=80-1 ARR=10000-1
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM4)
{
key_scan();
}
}
为了方便大家copy,下面是main.c,为了简便一点,我把所有程序都写在main.c中了
#include "main.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "lcd.h"
#include "stdbool.h"
#define LONG 100
#define DOUBLE 30
/* 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 */
enum
{
KEY_UP,
KEY_DE,
KEY_WA
} keys[4];
bool key_state[4];
bool key_singleflag[4];
bool key_longflag[4];
bool key_doubleflag[4];
bool key_period[4] = {0};
int time1 = 0;
int time2 = 0;
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void key_scan(void)
{
key_state[0] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
key_state[1] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
key_state[2] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
key_state[3] = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
for (int i = 0; i < 4; i++)
{
if (!key_period[i]) // 第一阶段
{
switch (keys[i])
{
case KEY_UP:
if (key_state[i] == GPIO_PIN_RESET)
{
keys[i] = KEY_DE;
}
break;
case KEY_DE:
{
if (key_state[i] == GPIO_PIN_RESET)
{
keys[i] = KEY_WA;
}
else
{
keys[i] = KEY_UP;
}
}
break;
case KEY_WA:
{
time1++;
if (key_state[i] == GPIO_PIN_SET)
{
if (time1 <= DOUBLE)
{
key_period[i] = 1;
keys[i] = KEY_UP;
time1 = 0;
}
else if (time1 > DOUBLE && time1 < LONG)
{
key_singleflag[i] = 1;
keys[i] = KEY_UP;
time1 = 0;
}
else if (time1 >= LONG)
{
key_longflag[i] = 1;
keys[i] = KEY_UP;
time1 = 0;
}
}
}
break;
default:
break;
}
}
if (key_period[i])
{
if (++time2 <= DOUBLE)
{
switch (keys[i])
{
case KEY_UP:
{
if (key_state[i] == GPIO_PIN_RESET)
{
keys[i] = KEY_DE;
}
}
break;
case KEY_DE:
{
if (key_state[i] == GPIO_PIN_RESET)
{
keys[i] = KEY_WA;
}
else
{
keys[i] = KEY_UP;
}
}
break;
case KEY_WA:
{
if (key_state[i] == GPIO_PIN_SET)
{
keys[i] = KEY_UP;
if (time2 <= DOUBLE)
{
key_doubleflag[i] = 1;
time2 = 0;
key_period[i] = 0;
}
}
}
break;
default:
break;
}
}
else
{
keys[i]=KEY_UP;
if(key_state[i]==GPIO_PIN_SET)
{
key_singleflag[i] = 1;
key_period[i] = 0;
time2 = 0;
}
}
}
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM4)
{
key_scan();
}
}
char text[20];
/* 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_TIM4_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim4);
LCD_Init();
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
LCD_Clear(Black);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(key_doubleflag[0]==1)
{
key_doubleflag[0]=0;
sprintf(text,"KEY_DOUBLE ");
LCD_DisplayStringLine(Line0,(uint8_t *)text);
}
else if(key_longflag[0]==1)
{
key_longflag[0]=0;
sprintf(text,"KEY_LONG ");
LCD_DisplayStringLine(Line0,(uint8_t *)text);
}
else if(key_singleflag[0]==1)
{
key_singleflag[0]=0;
sprintf(text,"KEY_SINGLE ");
LCD_DisplayStringLine(Line0,(uint8_t *)text);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV3;
RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
实验现象