范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

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.实现效果

31省市前三季度GDP出炉广东江苏山东居总量前三山西福建江西居增速前三央广网北京11月2日消息(记者金龙)11月2日,陕西统计局发布数据显示,前三季度全省实现地区生产总值23501。97亿元,按可比价格计算,同比增长4。8。至此,全国已经有31个省市山东省临沂市最新一批建设工程项目汇总清单,共50个据中项网统计,2022年山东省临沂市重点项目陆续发布最新动态,工程涵盖机械电子电器环保建材教育科研等不同领域,项目清单如下表1。云南上亨商贸有限公司山东旭德环保科技新材料项目2。上美景山东丨孔繁森同志纪念馆耿耿忠心照雪山美景山东,精彩无限。眼下,随着霜降到来,中国北方正渐渐水瘦山寒,本期美景山东专栏特地推出一期人文景观孔繁森同志纪念馆。孔繁森,山东聊城人,1966年加入中国共产党。1979年,国家肺热咳嗽可用地骨皮煲汤,帮你清热下火地骨皮,是枸杞的根皮,入药有凉血除蒸清肺降火等功效。可用于治疗肺热咳喘,虚劳潮热盗汗,高血压等症。另外,用地骨皮煲汤煮粥也是常见的使用方法,下面就给大家讲一下具体用法。煲汤煮粥煲汤张仲景除湿圣方,除去一身湿气,排尽肺中顽痰巧治睡觉打呼噜大家好,我是沈医生,经常感觉喉咙里有痰,怎么吐也吐不完?睡觉时还经常打呼噜,吵得人睡不着觉?这都是因为体内有湿了!今天给大家推荐一个张仲景除湿圣方,祛除全身湿气,化尽肺中顽痰,巧治热水泡脚,究竟是养生还是慢性自杀呢?多数人可能蒙在鼓里春天泡脚,升阳固脱夏天泡脚,祛湿除暑秋天泡脚,润肺濡肠冬天泡脚,丹田温灼。这是中医养生书籍上提及过的语句,主要说明泡脚这个养生方法在四季都可以进行,每个季节泡脚养生功效是不一样的,喝茶也能喝醉人,这是真的么?清朝有诗曰茶亦醉人何须酒,书自香我何须花。多数人也这样认为茶能使我沉醉着迷的话,还需要酒干什么?事实上,喝茶真的能喝醉呢,并且醉茶并不比醉酒轻松。多数人醉茶的时候,头昏脑胀,肠胃不入冬三分虚!1补血果2护肝菜3养胃粥换着吃,把前3季亏空身体补回来转眼已到11月,冬季的序幕缓缓拉开。经过了漫长炎热的夏天,以及短暂却干燥的秋天,人体消耗大秋燥伤人,大家或多或少都有体虚的表现。正所谓虚则补之,入冬后,自然要适当进补。一是把亏损的阿的江正式下课,刘晓宇试训上海,CBA全明星赛迎来最新进展目前CBA联赛进入到第1个窗口期当中,各支球队也要进行接下来的一个调整工作,有的球队需要换帅,有的球队要补强球员,同时CBA方面也迎来了一条好消息,接下来让我们一同来关注一下。首先伊布最新表态引爆争议,C罗梅西很意外,球迷吐槽成天刷存在感北京时间11月2日,欧洲足坛传来最新消息,伊布拉希莫维奇在接受媒体采访时做出争议表态,他表示在巴萨失去了自我,没赢过欧冠但依然是最佳球员,这样的情况也是引起了不小的争议。在谈到自己诺基亚最新ESG报告已出炉近日,诺基亚发布了强化的环境社会和治理(ESG)战略,该战略已纳入诺基亚业务和技术战略,并专注于可发挥最大影响力的领域。可持续发展是诺基亚领先科技,成就世界和合共生这一宗旨的核心。
治疗关节疼痛的药酒,这里有治疗关节疼痛,肢体不遂药酒,这里全都有关节疼痛原因很多,有虚症,有寒症还有淤症,湿症等,大家用药酒治疗时需辩证,找到合适的药酒进行治疗。以下为治疗关节疼痛的药酒,仅供参考。1。当归为什么维他命不能乱吃?今天学习了素问。阴阳离合论才终于理解了倪师说的为什么维他命等纯营养的东西不能吃身边有好多朋友经常吃钙片也有的吃维生素E维生素C等营养片特别是一些更年期的妇女大量的补充女性雌激素片补早上空腹喝水,到底有益还是有害?比不吃早餐危害都大?必须警惕早上空腹喝水的人,比不吃早餐的人危害更大?现在的人呢都注重养生,都有早起空腹喝水的习惯,但是呢最近却出现了这样的言论,说啊早上空腹喝水的人,比不吃早餐的人危害更大,这是真的吗?大家每次饭后,都有一次炎症在等着你,你知道吗?你知道吗?其实吃饭的不光是给身体提供能量,包括营养素,还或多或少,或轻或重的给您来一场炎症,一吃东西就肯定会触发一部分的炎症反应。今儿咱聊一聊,每一次吃饭其实你都会发一次炎,当然大中老年人中风高发,生活中该如何预防?做好这六点让你远离疾病在日常生活中,我们常听到脑梗中风脑血栓等说法,其实都是属于脑中风这个疾病。中风不是某一个疾病,而是一类急性脑血管疾病。中风多发生在45岁以上的中老年人群中,老年人往往患有高血压高血猕猴桃是一种非常热门的水果,不仅美味可口,而且富含丰富的营养猕猴桃是一种非常热门的水果,它不仅美味可口,而且富含丰富的营养成分。在这篇文章中,我们将详细解释猕猴桃的营养成分。维生素C猕猴桃是一种富含维生素C的水果,每100克猕猴桃可提供你身吃了20年才知道,它竟是天然叶酸!中老年要多吃,抗病毒叶酸是人体非常重要的微量元素之一,在人体发挥着重要功效,既能预防胎儿畸形治疗贫血,还能够降低脑血管疾病的发生风险。但人体并不能自我产生叶酸,因此需要不断从外界获取叶酸。叶酸对中老年秘制鸡爪大全,红烧的,柠檬的,泡椒的,好吃到停不下来爱吃爪爪,不喜外售的香辛料卤味,就喜欢自己家里最平凡的那一抹滋味,健康又美味。周末闲暇卤上一锅,居家好零嘴哈哈哈!边追剧边吃,绝对过瘾!喜欢鸡爪的小伙伴们不要错过,可以翻看我的菜谱买蒜薹,要分清红头和绿头,差别很大,搞懂再买不吃亏大年三十,除夕夜的团年饭,我们家今年全员下厨房,每人做几道自己拿手的家常菜,我媳妇提前包好了饺子和猪肉大葱馅的包子我做的是胡萝卜烧羊肉排骨藕汤炸藕夹炸鱼块卤鸡爪卤牛肉我妈做的是泥蒿张红甫建议常吃的3种面食早餐,健康,还不油腻早餐在一天中占有主要地位,决不能忽视,今天蓉儿就给大家分享六款面食早餐的制作方法,既营养又好吃,学着每天不油腻地吃饭。一懒人的水煎包这个懒人水饺包不需要掐皱,也不需要二次醒发的,而13道好吃家常主食做法,详细教程钟水饺做菜搜索锦囊菜谱主料面粉猪肉末各250克调料料酒10克,盐2克,鸡蛋清15克,鸡精2克,胡椒粉1克,香油5克,复制酱油100克,蒜泥50克,辣椒油75克,姜葱水葱花各25克,