之前文章
优化后文章
1.2、 STM32按键的长按和短按(一)
1.3、 STM32按键的长按和连按(二)
目录
上一节用实现了按键的长、短按键,这一节在原本的功能上加一个多击功能,可以实现双击、三击、四击等等,下面开始吧。
1、轻触按键
上一章有说过按键,不过不是每个人都看过上一章,这里稍微重复一下。
按键原理图
根据原理图可知,在按键没有按下处于断开时,引脚PA0连接电阻R1接地,属于弱下拉,此时PA0为低电平状态;在按键按下处于闭合时,引脚PA0与VCC连接,属于上拉,此时PA0为高电平状态。简单来说,按键按下时为高电平,按键松开时为低电平。
按键是高电平响应,代码可以这这样写:
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出频率
GPIO_Init(GPIOA, &GPIO_InitStructure); //结构体配置完成初始化
}
uint8_t KEY_Scan(void)
{
static uint8_t key_state = 0;//按键状态位
if(KEY_STATA && !key_state) {
printf("key_ON\r\n");//按下按键
key_state = 1;
}
if(!KEY_STATA && key_state) {
printf("KEY_OFF\r\n");//松开按键
key_state = 0;
}
return key_state;
}
2、按键长按和连按原理
判断按键长按和短按的依据就是按下时间的长短,就需要一个计时器来记录时间。这里有两种选择,一种是延时计时,利用delay延时函数通过不断轮询记录时间;另一种是利用单片机的外设——定时器计时,利用定时器的基础计时功能在溢出中断记录时间,两种方法都可以完成计时功能,选择哪一种就看个人喜好,这里用的是第二种。
有了计时器就可以计时实现按键的长短按,由下面两个图简单说明:
长按实现:按键按下时,计时器开启开始记录时间;停止计时不是通过按键松开来决定,而是记录时间达到2s(长按时间)时计时器关闭,长按完成后,按键松开无影响。
连按实现:连按由两个或两个以上短按组成,不一样的是,连按在按键松开后,只清除记录时间,不关闭计时器,然后紧接着记录松开时间,等待多次按键的到来,如果500ms内有按键按下,则开启连按,如果500ms内没有按键按下,则连按结束关闭计时器。
自定义变量类型,自定义一个变量类型里面包含了,按键计数器valcnt来记录连按了几次,按键计数结束标志位flag来表示单按或者连按结束了,按键长按vallong记录长按,最后一个定时器溢出计数器tim2_cnt用来记录时间,把这些变量整合在一起方便一些。
typedef struct
{
uint8_t valcnt; // 按键计数器
uint8_t flag; // 按键计算结束标志位
uint8_t vallong; // 按键长按
uint16_t tim2_cnt; // tim2溢出计数器
}Keyval_TypeDef;
3、定时器配置
基本配置不变,使用通用定时器TIM2中断溢出计时,定时器常规配置:
- 开启时钟
- tim2时基初始化
- tim2配置内部时钟
- tim2中断配置
- NVIC设置中断优先级
- tim2使能
/**
* @brief tim2初始化
* @param psc -- 预分频值
* @param arr -- 自动重装载值
* @retval None
*/
void TIM2_Init(uint32_t psc, uint32_t arr)
{
TIM_TimeBaseInitTypeDef tim2_init_struct;
NVIC_InitTypeDef NVIC_init_struct;
//1、开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//2、tim2时基初始化
tim2_init_struct.TIM_ClockDivision = TIM_CKD_DIV1; //不分频
tim2_init_struct.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
tim2_init_struct.TIM_Prescaler = psc - 1; //预分频值
tim2_init_struct.TIM_Period = arr - 1; //自动重装载值
tim2_init_struct.TIM_RepetitionCounter = 0; //重复计数器; 高级定时器才使用
TIM_TimeBaseInit(TIM2, &tim2_init_struct);
//3、tim2配置内部时钟
TIM_InternalClockConfig(TIM2);
//4、tim2中断配置
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//5、tim2设置中断优先级
NVIC_init_struct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_init_struct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_init_struct.NVIC_IRQChannelSubPriority = 0;
NVIC_init_struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_init_struct);
//6、tim2使能
// TIM_Cmd(TIM2, ENABLE);
}
编写TIM中断服务函数
当使能定时器后,TIM2的寄存器CNT溢出后就会触发中断,进入中断服务函数,每次进入全局变量tim2_cnt自增一次,代表记下一定时间,这个时间根据TIM2_Init()函数的两个参数psc和arr决定。
Tout(溢出时间) = (ARR+1)(PSC+1)/Tclk(时钟分割)
如果TIM2_Init(72, 1000*10),那么每自增一次代表经过10ms。
第一个if判断,按键按下时,计时器记录时间达到2000ms实现长按;第二个if判断,按键松开时,计时器记录时间达到500ms实现有无按键按下实现连按。
/**
* @brief This function handles TIM2 interrupt request.
* @param None
* @retval None
*/
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update)) {
if((g_key1.tim2_cnt++ >= 200) && KEY1_STATE()) {
TIM_Cmd(TIM2, DISABLE); // 按键按下时, 溢出计数器达到2s, 表示长按
g_key1.vallong = 1; // 按键长按
g_key1.valcnt = 0; // 清空连按次数
}
else if((g_key1.tim2_cnt >= 50) && !KEY1_STATE()) {
TIM_Cmd(TIM2, DISABLE); // 按键松开时, 溢出计数器达到500ms, 表示连按结束
g_key1.flag = 1;
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
4、按键配置
基本配置不变,使用GPIO外部中断,常规配置:
- 开启时钟;
- gpio初始化;
- 选择信号来源;
- 开始EXTI1配置;
- 开始NVIC配置;
/**
* @brief KEY初始化
* @param None
* @retval None
*/
void KEY_Init(void)
{
GPIO_InitTypeDef gpio_init_struct;
EXTI_InitTypeDef exti_init_struct;
NVIC_InitTypeDef nvic_init_struct;
//1、开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//2、gpio初始化
gpio_init_struct.GPIO_Mode = GPIO_Mode_IPU;
gpio_init_struct.GPIO_Pin = GPIO_Pin_0;
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init_struct);
//3、选择信号来源
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
//4、开始EXTI1配置
exti_init_struct.EXTI_Line = EXTI_Line0;//中断通道选择
exti_init_struct.EXTI_Mode = EXTI_Mode_Interrupt;//中断响应模式
exti_init_struct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//双边沿触发
exti_init_struct.EXTI_LineCmd = ENABLE;//EXTI使能
EXTI_Init(&exti_init_struct);
//5、开始NVIC配置
nvic_init_struct.NVIC_IRQChannel = EXTI0_IRQn;//打开中断通道
nvic_init_struct.NVIC_IRQChannelPreemptionPriority = 3;//抢占优先级
nvic_init_struct.NVIC_IRQChannelSubPriority = 0;//响应优先级
nvic_init_struct.NVIC_IRQChannelCmd = ENABLE;//NVIC使能
NVIC_Init(&nvic_init_struct);
}
编写EXTI中断服务函数:
进入中断服务函数后,读取宏定义KEY_STATA函数,判断按键是按下还是松开,由原理图可知,当宏函数为真(高电平)时,按键是按下的,当宏函数为假(低电平)时,按键是松开的。
按下按键计时器开启,开始计时,按键松开判断是否为有效按键,按键计数器自增,清空计时器记录时间,等待下一次按键的到来,按键按下30ms~2000ms为有效按键。
/**
* @brief This function handles EXTI0 interrupt request.
* @param None
* @retval None
*/
void EXTI0_IRQHandler(void)
{
static uint8_t keyval = 0; // 静态按键状态 0 -- 松开 1 -- 按下
if(EXTI_GetITStatus(EXTI_Line0) != RESET) {
if(!keyval && KEY1_STATE()) {
keyval = 1; // 按键按下
g_key1.tim2_cnt = 0; // 清空tim2计算器
TIM_Cmd(TIM2, ENABLE); // 启动定时器, 开始计时
}
else if(keyval && !KEY1_STATE()) {
keyval = 0; // 按键松开
if((g_key1.tim2_cnt >= 3) && (g_key1.tim2_cnt < 200)) {
//溢出计数器到50ms和2s之间, 表示短按, 按键计数器g_key1.valcnt自增
printf("key short, cnt:%d0ms\n", g_key1.tim2_cnt);
g_key1.valcnt++; // 按键计数器自增
}
g_key1.tim2_cnt = 0; // tim2溢出次数清零
}
EXTI_ClearITPendingBit(EXTI_Line0); // 清除中断标志
}
}
5、按键扫描
通过全局变量传递参数,按键扫描,再清除按键计数器。
/**
* @brief KEY扫描
* @param None
* @retval None
*/
uint8_t KEY_Scan(void)
{
if(g_key1.flag) {
uint16_t temp = g_key1.valcnt;
g_key1.valcnt = 0;
g_key1.flag = 0;
printf("key1 presses:%d\n", temp);
return temp;
}
else if(g_key1.vallong) {
g_key1.vallong = 0;
printf("key1 long\r\n");
return 10;
}
return 0;
}
6、主函数
int main(void)
{
uart1_init(115200);
LED_Init();
KEY_Init();
TIM2_Init(7200, 100); //10ms定时中断
while(1) {
KEY_Scan();
}
}
运行效果:
代码分享:
通过百度网盘分享的文件:按键(标准库).7z
链接:https://pan.baidu.com/s/10W7D9UnBLv-znrrVctD1Kg?pwd=ydn5
提取码:ydn5