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

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年的帆船酒店内景迪拜,一个有钱人的天堂!当
欧冠尤文图斯VS巴黎圣日尔曼提前淘汰尤文又遭伤病潮昨日欧冠小组赛最后一轮一场焦点大战利物浦主场对阵那不勒斯。上半场,两队均无建树。下半场刚开始,米尔纳受伤无法坚持比赛被换下。克瓦拉茨赫利亚任意球助攻厄斯蒂高头槌破门,但是经VAR裁外媒曝尤文2000万欧报价前恒大锋霸?冬窗或加盟替代迪马利亚塔利斯卡曾效力于广州恒大据沙特媒体利雅得体育报报道,尤文图斯2000万欧报价沙特劲旅利雅得胜利队的锋霸塔利斯卡,并试图在冬窗把巴西人带到意大利都灵。目前尤文图斯方面暂未对此消息进行最近火了一种穿法叫卫衣打底裤,遮肉增高不说,还特洋气潮流更迭不息,时尚圈的发展也是越来越让人捉摸不透了,面对时尚圈的发展,我们应该如何才能够发挥出个人的优势呢?重点就在于我们的穿搭,说实话,在秋冬季节,卫衣真的挺有存在感的。而卫衣和号称干皮救星的埃及魔法膏,你真的用对了吗?这款畅销46个国家的万用膏,喜欢护肤的小仙女应该对它都不陌生吧?成分简单,天然绿色,仅仅只有橄榄油蜂蜡蜂蜜花粉蜂王乳蜂胶几种成分,采用埃及纯天然工艺制成。用起来格外安心。既然号称万发明家刘绍贵研发牡丹精华液润肤面乳(抑皱抗衰)文刘绍贵本发明的牡丹精华液润肤面乳中,二裂酵母发酵产物溶胞物与半乳糖酵母样菌发酵产物滤液能够协同增强美白祛斑和护肤保湿作用。乳酸杆菌发酵产物具有抗菌抗炎的作用。面乳中的各个组分按各周琦队友130字感谢球迷,不舍队友,但对俱乐部和主教练只字不提CBA第一阶段收官后,联赛也进入了为期一个月的窗口期,除了国家队球员集训备战此次世界杯预选赛,其他俱乐部球员都已经进入了休息调整的时间。不过联盟在11月1号更新了最新的自由球员名单巴克利我爱克莱他把我的评论当真了并将这件事看成为私人恩怨直播吧11月2日讯今日,前NBA球员现著名篮球评论员查尔斯巴克利在节目中谈到了勇士球员克莱汤普森。巴克利此前表示,克莱曾有一段时间是NBA最棒的攻守兼备后卫,但现在他已经与那时不是贝克汉姆晒家人合影,11岁的小七依旧婴儿肥,未见长子夫妇引热议11月2日,贝克汉姆及其家人们分享了纪录片和大卫贝克汉姆一起拯救我们的小队首映式上的合影,爱妻维多利亚次子罗密欧三儿子克鲁兹以及爱女小七等家人纷纷来到现场为小贝打call。当日,贝爆冷!小将范争一力拔萝卜绝杀戴局长进四强北京时间2022年11月2日凌晨,斯诺克冠中冠结束了第二个小组的全部比赛,继第一个比赛日,前世界第一塞尔比强势晋级四强后,我国选手上赛季欧洲大师赛冠军范争一表现抢眼,连续两场绝杀,忍无可忍逼蔡老板出手,换掉纳什会是好的开始!下一个调整欧文?篮网又输了,当然对于本赛季的篮网来说,输球已经不叫什么新鲜事了。当然外界并没有太关注篮网输给公牛这场比赛,而是纳什下课了。虽然纳什和篮网双方都用友好协商来形容,但明眼人都很清楚,这欧冠尤文图斯vs大巴黎尤文无缘欧冠!巴黎全力争夺小组头名欧冠小组赛末轮的首日比赛结束,两支英超球队利物浦和热刺都取得好成绩,但昨晚光看比分利物浦两球零封那不勒斯,可是在场面上那不勒斯才是率先进球的一方,可惜越位被吹,利物浦虽说主场赢球止