要实现单片机的单击、双击、长按操作则需要理解一下他们的区别是什么
可从图中观察到单击在一定周期里有一个低电平,双击则是两个低电平,长按则是整个周期都是低电平。
本次进行的软件 STM32CubeMX + Keil5
采用的方法是中断(按键)+ 定时器 来实现单击、双击、长按操作
设计思路是这样的
- 初始化一个全局标记
- 按键中断事件发生后置位标记
- while死循环中一直检测这个标记,如果被置位那么进行消抖,然后再次检测连接KEY的IO是否处于按下状态,如是则认为本次按键有效
- 第一次按键事件有效后,启动定时器定时300ms,在此定时期间内如果有二次按下那么就是双击,如果没有按下,等到300ms定时时间到后读取IO电平,如果处于松开状态那么本次就是单击事件,如果还是按下状态那么就再次启动700ms定时器,700ms过后再次读取IO电平是否处于按下状态,如是那么就是长按。
最先开始配置时钟,因为需要通过频率来计算定时时间,配置时钟树如下:
我采用的是内部时钟,将HCLK直接输入64M(最大值),这时定时器从内部获取的频率是64M.
配置定时器,输出
查看时钟树我们知道APB2当前频率为64MHz,把预分频系数设置为640-1,自动重载值为2-1,得到的计时器更新中断频率即64,000,000/640/2=50KHz。通过调节这个频率可以控制时间,如果发现自己按下之后延时严重,就可以调节频率找到刚好的位置。
然后进行中断的配置,这里选择向下模式,并且选择上拉
配置完中断和定时器后,开启中断,设置中断优先级
这里面用到了不定长串口接收,如需了解前往:
STM32关于UART的接收方式_啵啵520520的博客-CSDN博客
到这里就配置完STM32CubeMX,下一步进行代码编写
/* USER CODE BEGIN 0 */
__IO uint16_t key_state = 0;
/*
* key_state位说明:
*
* bit[15]:按键事件完成标志位
* bit[14]:按键中断触发标志位
* bit[13]:长按事件触发需要启动第二次定时器标志位
* bit[12~0]: 按键计数,=1:单击 =2:双击 =3:长按
*/
void TIM4_Start(uint16_t timeout)
{
__HAL_TIM_SET_COUNTER(&htim4, timeout);
HAL_TIM_Base_Start_IT(&htim4);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == htim4.Instance)
{
HAL_TIM_Base_Stop_IT(&htim4);
if(key_state & 0x2000){
if(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin) == GPIO_PIN_RESET){
key_state = 0x8003;
}
else{
key_state = 0x8000;
}
}
else{
if(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin) == GPIO_PIN_RESET) // 按键处于按下状态
{
key_state |= 0X2000; // 标记启动了700ms的超时定时
TIM4_Start(700); // 启动700ms超时
}
else{
if((key_state & 0X1FFF) == 1) // 按键按下计数=1,是单击事件
{
key_state = 0X8001;
}
else if((key_state & 0X1FFF) >= 2) // 按键按下计数>=2,是双击事件
{
key_state = 0X8002;
}
else // 按键按下计数为其他值,本次按键事件无效
{
key_state = 0;
}
}
}
}
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
case KEY1_Pin:
if((key_state & 0X8000) == 0){
key_state |= 0X4000; // 标记按键中断触发,在while死循环中进行消抖和的启动定时器
}
break;
case KEY2_Pin:
HAL_GPIO_WritePin(GPIOB,LED1_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB,LED2_Pin,GPIO_PIN_SET);
break;
default:break;
}
}
/* USER CODE END 0 */
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();
MX_TIM4_Init();
/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //开启空闲中断
HAL_UART_Receive_IT(&huart1,(uint8_t *)buf,256);
__HAL_TIM_CLEAR_IT(&htim4, TIM_IT_UPDATE); // STM32的定时器初始化完毕之后如果不清理中断标记为会有直接进入中断的问题
HAL_UART_Transmit(&huart1,(uint8_t *)"开始\n",6,50);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(key_state & 0X4000) // 按键中断触发
{
key_state &= ~0x4000; // 清除事件
HAL_Delay(60); // 消抖延时
// 消抖完毕之后再次检测按键是否为按下状态,如是则本次按键有效,否则视为无效并清除标识。
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET)
{
if((key_state & 0X1FFF) == 0) // 首次按下时启动250ms超时定时器
{
TIM4_Start(2500);
}
key_state++; // 按键按下计数增加
}
}
if(key_state & 0X8000) // 按键事件发成了
{
switch(key_state)
{
case 0X8001:
HAL_GPIO_TogglePin(GPIOB,LED1_Pin);
HAL_UART_Transmit(&huart1,(uint8_t *)"单击\n",6,50);
break;
case 0X8002:
HAL_GPIO_TogglePin(GPIOB,LED2_Pin);
HAL_UART_Transmit(&huart1,(uint8_t *)"双击\n",6,50);
break;
case 0X8003:
HAL_GPIO_WritePin(GPIOB,LED1_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,LED2_Pin,GPIO_PIN_RESET);
HAL_UART_Transmit(&huart1,(uint8_t *)"长按\n",6,50);
break;
default:break;
}
key_state = 0; // 清除事件
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在其中用到了
__IO 将该变量变成一个程序每次运行都必须去检测这个变量,不能做优化
还有两个中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) //中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //定时器回调函数
你自己使用,则需要修改主循环中的处理函数(switch那里)
以后有问题在加载这后面