专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

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

春节前武汉球员再次讨薪,武汉长江队是退出中超还是就地解散?武汉长江队(原武汉卓尔队)2019赛季风光无限,在李铁团队带领下平过上港胜过恒大,取得当年中超第六名。受疫情影响,2020和2021两个赛季勉强保级,艰难活在中超。2022赛季越踢vivoY35上手评测颜值性能均衡,千元机新体验一转眼又到了本年度的尾声。回顾2022年的手机市场可谓热闹不已,从中低端次旗舰到高端旗舰机,各大厂商皆使出了浑身解数疯狂内卷,推出的新品更是层出不穷。难得的是,vivo在经历了激烈sanag塞那AirRun自律运动狂甩不掉的气传导耳机大家对于骨传导耳机一定不会陌生,特别是喜欢跑步健身的朋友,基本都会有一副骨传导耳机作为跑步装备,但是就算是再昂贵的骨传导耳机,长时间通过骨头传导声音来听歌,也会导致耳部发麻,更不用哪款Mac最受欢迎?MacBookAirPro贡献了四分之三销售额IT之家1月11日消息,根据市场调查机构CIRP近日公布的报告,MacBookAir和MacBookPro贡献了苹果个人电脑业务将近四分之三的销售额,而台式机型号占比仅为26。IT微星发布MAG321QRQD量子点IPS电竞显示器IT之家1月11日消息,游戏硬件制造商MSI微星宣布推出QD系列显示器的最新成员MAG321QRQD。该显示器配备1440p分辨率IPS面板(屏幕比例为169,分辨率2560x14李宁新款休闲鞋太霸气,DVA1新配色亮相,态极大三角2。0来袭看下最近的国产品牌球鞋新品,李宁这边,underdog老师晒出了一组新鞋的设计图,造型很新颖,中底结构令人惊叹,两双鞋,一双是高帮的袜套结构,鞋头还有个贯穿鞋身的横条,鞋面相对来说泰迪熊外套小脚裤才是今冬最流行穿搭,时髦保暖,人见人夸冬天的寒冷相信大家都已经领教了,所以与其追究颜值高的服装,不如索性就拉着保暖这一个特点不放。秉承这样的穿搭要点,今年悄然流行起一种外套和裤装的组合泰迪熊外套小脚裤哦!满是暖暖毛毛的冬天穿白色,太好看了!白色,空纳万境,缤纷世界里的一股清流,给人以视觉上最浪漫的享受,还藏着淡极始如艳的高级。冬天穿白色,穿的是俗世归空的心境,穿的是纯净质朴的天真,穿的是对美好最初的渴望,穿的更是那抹天文学家发现最遥远的银河恒星,已抵达仙女座星系边界科学家发现最遥远的银河恒星。银晕的结构。NASAESAA。Feild银河系是我们的宿主星系。距离银河系最近的河外大星系是仙女座星系,两者之间相距约250万光年。银河系和仙女座星系正我国人造太阳发现新的高能量约束模式来源人民网人民日报null工作人员对全超导托卡马克核聚变实验装置(EAST)进行升级改造。新华社记者刘军喜摄记者从中国科学院合肥物质科学研究院等离子体物理研究所获悉近期,该所核聚变崔正元遭爆勾引人妻环抱紧贴骑车!见对话曝光全否认只是妹妹记者张筱涵综合报导韩记者李镇浩主要透过个人YouTube爆料演艺圈八卦,9日共8分钟的影片中,可见一名男子(下称A男)气愤抗议家庭遭到UN成员崔正元破坏,现在和妻子(下称B女)正在
大衣下面少搭配裙子,今年搭这4条裤子更好看,高级有气质深秋季节怎么能少得了大衣搭配的身影,温柔高级又极富质感的大衣是穿多久都不用担心会过时的经典单品,轻松穿出满满的秋日氛围感。但是要想让大衣穿搭的时尚度更上一个台阶,那下装的搭配也是绝你该如何屹立于天地之间?你这辈子做得最有种的事情是什么?是从别人羡慕的岗位离职出来自己创业?还是自己想当老板的野心?其实都不是,从一开始,我并没有那么大的野心,我只是想做个好职员,最多想成为一名优秀的工作人的行为是最好的风水师谣零零计划老甄絮语退后一步,行安乐法,道三个好,结欢喜缘。人生快乐不快乐看心情,心情好不好看心态,心态平不平看行为。人的行为就是最好的风水,我们都应该择善而行。海阔天空,任凭风吹麦随笔开悟开悟什么意思?就是开窍,以前不懂的,突然就理解了。我理解的开悟,有两个层面。一个是链接,一个是实事求是。开悟说得简单,就是突然明白了。但实际上是非常困难的,需要阅历经历经验等积累。亲亲,我的水稻稻子熟了,燃烧着金色的火焰。它不言不语,只是点头微笑,用饱满的喜悦,问候大地,问候大地上的人们。我欢喜着这份沉默,沉默扬起金色的喜悦,喜悦在我的肋骨间轻舞飞扬。我剥开一颗谷粒,一片懂你的人,才配得上你的余生不知你有没有过这样的经历想找人说话,翻遍了通讯录和朋友圈,却找不到可以聊天的人。翻着翻着,就失去了倾诉的欲望。原本的满腹话语,变成了睡前的一缕叹息。这样的经历总会让人莫名伤感,以为沙漠中的胡杨你从亿万年前走来,听过丝绸之路的驼铃声,领略过秦汉的羌笛风情,见证过那座荒废的千年古城。你屹立在沙漠中,和那肆虐的风沙勇敢地抗争。你忍受着太阳的炙烤,成为荒漠中最美的一道风景。你生交流人生百态3头条创作挑战赛陪伴,是两情相悦的一种习惯懂得,是两心互通的一种眷恋。为天地立心,为生民立命,为往圣继绝学,为万世开太平。宋张载武功再高,也怕菜刀智力再好,一砖撂倒走自己的路,让别人为青春留下了感动在最好的年纪遇到最好的你,是每个年轻人的梦想。就像王菲的歌词里说的,我再也无法忘记你的脸,只因为我在人群里又看了你一眼。当命运让我们相遇,你会怎么做?默默暗恋还是苦苦追求?2021裤子靴子,今年秋冬最飒又时髦的穿搭嗨,各位小仙女们,大家好呀!今年秋冬,靴子可是最不可缺少的单品之一,不管是短靴,亦或者是长靴都是一样的。在选择靴子这种鞋款来穿时,该配裙子还是裤子呢?当然,无论是选择什么下装来配,银牌相当于金牌!德乒名将盛赞马龙手感太好,把我的思路打丢了10月9日,成都世乒赛团体赛落下帷幕,国乒笑到最后,斩获双冠王。男团决赛,被打败的德国华裔名将邱党,接受采访时,表达了看法,在他看来,银牌就相当于金牌,因为输给的是中国队。邱党,是
友情链接:快好知快生活快百科快传网中准网文好找聚热点快软件