STM32F103C8T6标准库函数实现多按键检测状态机短按长按识别
前言
制作航模遥控器需要用到多按键检测,实现过程中主要参考了以下两篇文章,尤其是第一篇收获最大,作者的代码思想很好,但文中部分代码有误,实际运行时检测到的IO电平是错误的,花费了一天时间才调通,简单记录一下。 1.电路连接
使用STM32F103C8T6蓝色板,按键采用共阴极连接。
6个按键:
CH1Left 接PB5
CH1Right 接PB4
CH2Up 接PA15
CH2Down 接PB3
CH4Left 接PA12
CH4Right 接PA11
串口USB-TTL接法:
GND 电源地
3V3 接3.3V
TXD 接PB7
RXD 接PB6
ST-LINK V2接法:
GND 电源地
3V3 接3.3V
SWCLK 接DCLK
SWDIO 接DIO
2.程序实现
key.h - 主要定义结构体和函数预定义 #ifndef __KEY_H #define __KEY_H #include "stm32f10x.h" #include "stm32f10x_gpio.h" typedef struct // 构造按键初始化类 { GPIOMode_TypeDef GPIO_Mode; // 初始化按键模式 GPIO_TypeDef* GPIOx; // 初始化按键口 uint16_t GPIO_Pin_x; // 初始化按键引脚好 uint32_t RCC_APB2Periph_GPIOx; // 初始化时钟 }Key_Init; typedef enum _KEY_STATUS_LIST // 按键状态 { KEY_NULL = 0x00, // 无动作 KEY_SURE = 0x01, // 确认状态 KEY_UP = 0x02, // 按键抬起 KEY_DOWN = 0x04, // 按键按下 KEY_LONG = 0x08, // 长按 }KEY_STATUS_LIST; typedef struct _KEY_COMPONENTS // 状态机类 { FunctionalState KEY_SHIELD; //按键屏蔽,DISABLE(0):屏蔽,ENABLE(1):不屏蔽 uint8_t KEY_COUNT; //按键长按计数 BitAction KEY_LEVEL; //最终按键状态,按下Bit_SET(1),抬起Bit_RESET(0) BitAction KEY_DOWN_LEVEL; //按下时,按键IO实际的电平 KEY_STATUS_LIST KEY_STATUS; //按键状态 KEY_STATUS_LIST KEY_EVENT; //按键事件 BitAction (*READ_PIN)(Key_Init Key);//读IO电平函数 }KEY_COMPONENTS; typedef struct // 按键类 { Key_Init Key; // 继承初始化父类 KEY_COMPONENTS Status; // 继承状态机父类 }Key_Config; typedef enum // 按键注册表 { CH1Left, CH1Right, CH2Up, CH2Down, CH4Left, CH4Right,// 用户添加的按钮名称 KEY_NUM, // 必须要有的记录按钮数量,必须在最后 }KEY_LIST; void KEY_Init(void);//IO初始化 void Creat_Key(Key_Init* Init); // 初始化按钮函数 void ReadKeyStatus(void); // 状态机函数 void TIM3_Init(u16 arr,u16 psc); #endif
原文中Key用的是指针,结果导致读电平函数GPIO_ReadInputDataBit()寻址错误,才使得读出的电平有误。 typedef struct // 按键类 { Key_Init *Key; // 继承初始化父类 KEY_COMPONENTS Status; // 继承状态机父类 }Key_Config;
key.c - TIM3定时器初始化,定时检测按键状态;有限状态机实现 #include "stm32f10x.h" #include "key.h" #include "sys.h" #include "delay.h" #include "usart.h" //参考链接https://blog.csdn.net/qq_42679566/article/details/105892105,原文错误已修正 Key_Config Key_Buf[KEY_NUM]; // 创建按键数组 #define KEY_LONG_DOWN_DELAY 30 // 设置30个TIM3定时器中断=600ms算长按 #define DBGMCU_CR (*((volatile u32 *)0xE0042004)) /*通用定时器3中断初始化,使用TIM3控制按键定时检测 时钟选择为APB1的2倍,而APB1为36M * 参数:arr:自动重装值。 psc:时钟预分频数 */ void TIM3_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能 TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载寄存器周期的值 TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //预分频值 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分割为0,仍然使用72MHz TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);//允许更新中断 TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级0 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器 TIM_Cmd(TIM3,ENABLE); } void TIM3_IRQHandler(void) //TIM3中断 { if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否 { // 中断处理代码 ReadKeyStatus(); //调用状态机 u8 i,status; for(i = 0;i < KEY_NUM;i++) { status = Key_Buf[i].Status.KEY_EVENT; //if(status!=KEY_NULL) printf("%d,%d ",i,status);//事件处理 if(status==KEY_DOWN) printf("%d短按 ",i); if(status==KEY_LONG) printf("%d长按 ",i); } TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx更新中断标志 } } //按键初始化函数 void KEY_Init(void) //IO初始化 { Key_Init KeyInit[KEY_NUM]= { {GPIO_Mode_IPU, GPIOB, GPIO_Pin_5, RCC_APB2Periph_GPIOB}, // 初始化按键CH1Left {GPIO_Mode_IPU, GPIOB, GPIO_Pin_4, RCC_APB2Periph_GPIOB}, // 初始化按键CH1Right {GPIO_Mode_IPU, GPIOA, GPIO_Pin_15, RCC_APB2Periph_GPIOA}, // 初始化按键CH2Up {GPIO_Mode_IPU, GPIOB, GPIO_Pin_3, RCC_APB2Periph_GPIOB}, // 初始化按键CH2Down {GPIO_Mode_IPU, GPIOA, GPIO_Pin_12, RCC_APB2Periph_GPIOA}, // 初始化按键CH4Left {GPIO_Mode_IPU, GPIOA, GPIO_Pin_11, RCC_APB2Periph_GPIOA}, // 初始化按键CH4Right }; Creat_Key(KeyInit); // 调用按键初始化函数 //STM32没有彻底释放PB3作为普通IO口使用,切换到SW调试可释放PB3、PB4、PA15 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); DBGMCU_CR &=0xFFFFFFDF; //如果没有这段代码,PB3就会一直是低电平 } static BitAction KEY_ReadPin(Key_Init Key) //按键读取函数 { return (BitAction)GPIO_ReadInputDataBit(Key.GPIOx,Key.GPIO_Pin_x); } void Creat_Key(Key_Init* Init) { uint8_t i; GPIO_InitTypeDef GPIO_InitStructure[KEY_NUM]; for(i = 0;i < KEY_NUM;i++) { Key_Buf[i].Key = Init[i]; // 按钮对象的初始化属性赋值 RCC_APB2PeriphClockCmd(Key_Buf[i].Key.RCC_APB2Periph_GPIOx, ENABLE);//使能相应时钟 GPIO_InitStructure[i].GPIO_Pin = Key_Buf[i].Key.GPIO_Pin_x; //设定引脚 GPIO_InitStructure[i].GPIO_Mode = Key_Buf[i].Key.GPIO_Mode; //设定模式 GPIO_Init(Key_Buf[i].Key.GPIOx, &GPIO_InitStructure[i]); //初始化引脚 // 初始化按钮对象的状态机属性 Key_Buf[i].Status.KEY_SHIELD = ENABLE; Key_Buf[i].Status.KEY_COUNT = 0; Key_Buf[i].Status.KEY_LEVEL = Bit_RESET; if(Key_Buf[i].Key.GPIO_Mode == GPIO_Mode_IPU) // 根据模式进行赋值 Key_Buf[i].Status.KEY_DOWN_LEVEL = Bit_RESET; else Key_Buf[i].Status.KEY_DOWN_LEVEL = Bit_SET; Key_Buf[i].Status.KEY_STATUS = KEY_NULL; Key_Buf[i].Status.KEY_EVENT = KEY_NULL; Key_Buf[i].Status.READ_PIN = KEY_ReadPin; //赋值按键读取函数 } } static void Get_Key_Level(void) // 根据实际按下按钮的电平去把它换算成虚拟的结果 { uint8_t i; for(i = 0;i < KEY_NUM;i++) { if(Key_Buf[i].Status.KEY_SHIELD == DISABLE) continue; if(Key_Buf[i].Status.READ_PIN(Key_Buf[i].Key) == Key_Buf[i].Status.KEY_DOWN_LEVEL) Key_Buf[i].Status.KEY_LEVEL = Bit_SET; else Key_Buf[i].Status.KEY_LEVEL = Bit_RESET; } } void ReadKeyStatus(void) { uint8_t i; Get_Key_Level(); for(i = 0;i < KEY_NUM;i++) { switch(Key_Buf[i].Status.KEY_STATUS) { //状态0:没有按键按下 case KEY_NULL: if(Key_Buf[i].Status.KEY_LEVEL == Bit_SET)//有按键按下 { Key_Buf[i].Status.KEY_STATUS = KEY_SURE;//转入状态1 Key_Buf[i].Status.KEY_EVENT = KEY_NULL;//空事件 } else { Key_Buf[i].Status.KEY_EVENT = KEY_NULL;//空事件 } break; //状态1:按键按下确认 case KEY_SURE: if(Key_Buf[i].Status.KEY_LEVEL == Bit_SET)//确认和上次相同 { Key_Buf[i].Status.KEY_STATUS = KEY_DOWN;//转入状态2 Key_Buf[i].Status.KEY_EVENT = KEY_DOWN;//按下事件 Key_Buf[i].Status.KEY_COUNT = 0;//计数器清零 } else { Key_Buf[i].Status.KEY_STATUS = KEY_NULL;//转入状态0 Key_Buf[i].Status.KEY_EVENT = KEY_NULL;//空事件 } break; //状态2:按键按下 case KEY_DOWN: if(Key_Buf[i].Status.KEY_LEVEL != Bit_SET)//按键释放,端口高电平 { Key_Buf[i].Status.KEY_STATUS = KEY_NULL;//转入状态0 Key_Buf[i].Status.KEY_EVENT = KEY_UP;//松开事件 } else if((Key_Buf[i].Status.KEY_LEVEL == Bit_SET) && (++Key_Buf[i].Status.KEY_COUNT >= KEY_LONG_DOWN_DELAY)) //超过KEY_LONG_DOWN_DELAY没有释放 { Key_Buf[i].Status.KEY_STATUS = KEY_LONG;//转入状态3 Key_Buf[i].Status.KEY_EVENT = KEY_LONG;//长按事件 Key_Buf[i].Status.KEY_COUNT = 0;//计数器清零 } else { Key_Buf[i].Status.KEY_EVENT = KEY_NULL;//空事件 } break; //状态3:按键连续按下 case KEY_LONG: if(Key_Buf[i].Status.KEY_LEVEL != Bit_SET)//按键释放,端口高电平 { Key_Buf[i].Status.KEY_STATUS = KEY_NULL;//转入状态0 Key_Buf[i].Status.KEY_EVENT = KEY_UP;//松开事件 } else if((Key_Buf[i].Status.KEY_LEVEL == Bit_SET) && (++Key_Buf[i].Status.KEY_COUNT >= KEY_LONG_DOWN_DELAY)) //超过KEY_LONG_DOWN_DELAY没有释放 { Key_Buf[i].Status.KEY_EVENT = KEY_LONG;//长按事件 Key_Buf[i].Status.KEY_COUNT = 0;//计数器清零 } else { Key_Buf[i].Status.KEY_EVENT = KEY_NULL;//空事件 } break; default: break; } } }
main.c - 主函数调用TIM3初始化 #include "delay.h" #include "usart.h" #include "stm32f10x.h" #include "key.h" int main() { delay_init();//初始化延时函数 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2,2位抢占优先级和2位子优先级 usart_init(115200);//初始化串口1,波特率为115200 TIM3_Init(19999,71);//1MHz,每20ms检测按键一次; KEY_Init(); //KEY初始化 while(1){ delay_ms(1); } }3.实现效果
上海迪士尼2天游玩攻略,拍好烟花秀发朋友圈秀上海迪士尼和香港迪士尼有以下几个区别规模上海迪士尼乐园是全球最大的迪士尼主题乐园之一,而香港迪士尼乐园则相对较小。地理位置上海迪士尼乐园位于上海市郊区,距离市中心较远,而香港迪士尼
老照片120年前的上海,外滩苏州河南京路,和现在完全不一样人们经常说,一座城市因为有了历史,才显得厚重。历史,是一座城市的记忆,文化是一座城市的命脉。被誉为东方巴黎的上海,钟灵毓秀历史悠久,一百多年前就令世界瞩目。清朝末年,英国摄影师查尔
河小青来助力!珠海香湾街道大力宣传河长制为进一步增进群众对河长制的了解,提升爱河护河责任意识,近日,珠海市香湾街道在海天公园开展河长制宣传活动。活动通过发放河长制宣传品宣传册等方式,大力宣传河长制的目标任务河小青志愿者申
三大运营商的三阴灌肠昨日的移动电信联通纷纷杀出惊天大阴线,中国移动跌9。03,中国电信9。93,中国联通9。59,电信盘中一度封死跌停,移动破百的时候,一度认为已经到头了,后面又反复了几天,甚至冲到了
姚安花红柳绿春意浓桃花盛开产业丰惊蛰节令后,随着天气逐渐回暖,楚雄彝族自治州姚安县太平镇老街村的500多亩桃花进入了盛花期。充足的光照,适宜的气候,加上精心管理,这里的桃林树形婀娜多姿,花开锦簇艳丽。姚安县太平镇
情侣穷游庆祝婚礼,当事人准备在99个地方搭建囍字场景合影近日,甘肃一对新婚夫妻旅游结婚的视频火了。视频中,这对新婚夫妻正在西部城市流浪旅行,每到一地便搭建囍字场景并合影留念。不少网友纷纷留言,表示羡慕,在山河日月见证下的爱情真浪漫。当事
身家千万旅游公司老板被骗缅甸,几经转卖瘦40斤斗智斗勇死里逃生幸卫林是贵州人,离异单身,有自己的旅游公司,并在全国拥有142家门店,身价上千万。能拥有这么多门店这么多财富的人,靠的一定不是运气而是实力,可就是这么一个有勇有谋的人竟然也被骗到了
大唐第一名将李靖,晚年受到李世民的猜忌,战战兢兢唐朝名将如云,能征善战者有很多,比如李靖苏定方薛仁贵王忠嗣郭子仪李光弼等等,在这么多的名将当中,论军事才能和功绩谁可以排在第一位呢?那必然是李靖。李靖的名气不如卫青和霍去病,但是他
导游提醒游客理性购物,如何让这股清流淌得更远正观评论员王航如果不能建立合理的导游薪资制度,无法依法铲除不合理低价团的生存空间,劝游客理性购物的行业基本操作就依然会是稀缺品旅游,以游为主,尽量多走一些地方,少买一点东西。记住,
延庆白河堡水库迎来数十只苍鹭筑巢孵卵新京报讯(记者曹晶瑞)早春三月,候鸟北归,3月21日,新京报记者从延庆区了解到,连日来,延庆区白河堡水库内的悬崖峭壁上,迎来了数十只苍鹭筑巢孵卵栖息觅食,为初春的白河增添了无限生机
记录2迪拜,有钱人的天堂(1)2006年2008年5。12地震前,我在阿联酋迪拜工作和生活了两年,给我的感觉就是,自己的荷包太小,现金太少。2007年的帆船酒店2007年的帆船酒店内景迪拜,一个有钱人的天堂!当