当前位置:   article > 正文

蓝桥杯嵌入式之按位点灯 按键长按、短按、双击操作(附源码)_嵌入式短按长按

嵌入式短按长按

蓝桥杯嵌入式之按位点灯 按键长按、短按、双击操作(附源码)

GPIO引脚配置原理

STM32 的每个 IO 端口都有 7 个寄存器来控制,分别是:CRH、CHL、ODR、IDR、BSRR、BRR、LCLK,加上APB2 外设时钟使能寄存器ACC->APB2ENR就可以实现对IO口的所有操作了。不管是库函数还是HAL库究其根源都是通过控制这几个寄存器实现的。
各个寄存器功能如下:

  1. CRH和CRL寄存器:IO 口的模式和输出速率控制寄存器。此寄存器共32位,一个IO口共需要四位进行配置,因为一组IO口共16位,所以采用CRH和CRL共同控制一组IO口,其中CRH控制高8位,CRL控制低8位。控制图如下:
    CRL和CRH位功能图
    从图中可以看出:对于一个IO口的控制,高2位(CNFy)为输出模式设置位,低2为(MODEy)为输入模式和输出速度设置位。如配置A0口为输出速度为50M的推挽输出模式:首先对CRL的最低4位清0,对CRL按位与0XFFFFFFF0,然后在低4位按位或上3。所以对CRL的最终配置为:GPIOA->CRL&=0XFFFFFFF0;GPIOA->CRL|=3<<0。这样就实现了不影响A组口中其他位配置且对A0进行配置的目的(或写为:GPIOA->CRL=GPIOA->CRL&0XFFFFFFF0|3<<0)。
    注:IO 口寄存器必须要按 32 位字被访问,即使按位操作不是32位也会在其前面进行补0至32位后再进行按位操作。
  2. ODR和IDR寄存器
    ODR为32位读写寄存器,只使用的低16位控制IO口的输出电平。
    ODR寄存器位功能表
    ODR的低16位对应一组IO口的16位,当对其写入0时输出低电平,写入为1时输出高电平;也可以对其进行读去当前IO口的输出电平操作,输出高电平为1,输出低电平为0。
    如:控制A6输出为高电平,A6为第七位,需要先清空第七位再对第七位赋值1。GPIOA->ODR&=~0X00000040;GPIOA->ODR|=0X00000040;也可以写为:GPIOA->ODR=GPIOA->ODR& ~0X00000040 |(1<<6)。
    如:读取A6输出电平,将读取的ODR数据按位与上所读位为1其他位为0的数,再右移所读位到最低位。GPIO_PIN_STATE=(GPIOA->ODR & (1<<6))>>6;
    注:ODR是对设置为输出模式的引脚进行控制。
    INR为32为只读寄存器,只使用低16为读取引脚的输入电平
    IDR寄存器位功能表
    引脚设置为输入模式后,可以通过IDR寄存器读取引脚输入状态。如:读取引脚A3的输入状态。先按位与上A3所在的第四位为1其他为0的数,再右移3位使检测位到达最低位。GPIO_PIN_STATE=(GPIOA->INR&(1<<3))>>3;
  3. BSRR和BRR寄存器
    BSRR为32位寄存器,其中高16位为IO口高电平输出控制位,低16位位IO口低电平控制位。
    BSRR寄存器位功能
    高16位写1时是对ODR进行清0操作,低16位写1时是对ODR进行写1操作,此寄存器只有写1有效,写0无效,所以对其某一位进行操作时可以不用管其他位。
    如:设置A5输出为低电平,低电平由高16位控制,所以对其高16位的第五位设置为1。 GPIOA->BSRR=GPIOA->BSRR&(1<<(5+16));
    BRR 寄存器是端口位清除寄存器,作用和BSRR的高16位相同。
    注:BSRR和BRR最终都是操作ODR,所以可以直接控制ODR,简化代码复杂度。
  4. SCKR寄存器为32位读写寄存器
    LCKR寄存器位功能
    第16位为锁键,当其值为1时可以对IO口进行解锁与锁定,0至15位为IO开关位,1为锁定0为不锁定。对16位写入如上图所示的序列进行开锁。
  5. APB2 外设时钟使能寄存器(RCC->APB2ENR)
    ACC->APB2ENR寄存器位功能
    其高16位保留,低16位分别为挂在APB2时钟线上的外设是否使能位,1为使能,0为非使能。当使用外设时使能相对应的时钟,不使用时为了降低功耗要及时关闭。
    以下代码为配置PD2为推挽输出模式且输出高电平
	RCC->APB2ENR|=1<<5;    //使能PORTD时钟	   	 
	GPIOD->CRL=GPIOD->CRL & 0XFFFFF0FF | (3<<8);  //PD.2 配置为推挽输出
    GPIOD->ODR=GPIOD->ODR & ~0X00000004 | (1<<2); //PD.2 输出高电平
  • 1
  • 2
  • 3

所以对IO口的控制并不难,记住这几个寄存器就OK了。

LED按位操作

点灯即为对IO口输出电平进行操作,正常点灯按位操作可以直接对ODR寄存器进行操作。
如:将A3置为高电平 GPIOA->ODR=GPIOA->ODR&~0X00000008 | (1<<3);
但是由于蓝桥杯开发板PC口连接了LCD和LED两种外设,所以使用了74HC573芯片对8为数据进行锁存处理。因此,单独对一个引脚直接进行操作是不方便的。所以我设置了一个LED控制函数如下所示:

void Disp_Led(uint8_t LED)
{
	GPIOC->ODR=(GPIOC->ODR&0X00FF) | 0XFF00;
	GPIOD->ODR=(GPIOD->ODR&~0X0004) | 0X0004;	
	GPIOD->ODR=(GPIOD->ODR&~0X0004) | 0X0000;	

	GPIOC->ODR=(GPIOC->ODR&0X00FF) | LED<<8;
	GPIOD->ODR=(GPIOD->ODR&~0X0004) | 0X0004;	
	GPIOD->ODR=(GPIOD->ODR&~0X0004) | 0X0000;	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

对此函数10ms执行一次,需要改变哪一位就对LED进行按位操作,如:点亮第0位,熄灭第一位。
LED=LED&~0X01 | 0X01; LED=LED& ~0X02 | 0X00;
什么时候需要改就什么时候对LED进行赋值,这样就实现了LED的按位操作。

按键长按、短按、双击操作

首先定义一个按键结构体如下:

struct 
	{
		uint8_t judge_sta;  //按键判断当前步骤
		uint16_t key_time1; //按键按下时间
		uint8_t key_time2;  //双击价格时间
		uint16_t key_sta;  //按键状态
		bool key_single;   //单击标志
		bool key_double;   //双击标志
		bool key_long;     //长按标志
	}Key_N[4];
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

打开一个10ms的定时器中断,利用中断时间进行消抖和按下时间的判断,从而避免按键影响整体代码的扫描速度。以下代码在10ms的定时器中断中

		/*************按键**********************/
		Key_N[0].key_sta=GPIOB->IDR &(1);          //读取按键状态判断是否按下
		Key_N[1].key_sta=(GPIOB->IDR &(1<<1)) >>1; //读取按键状态判断是否按下
		Key_N[2].key_sta=(GPIOB->IDR &(1<<2)) >>2; //读取按键状态判断是否按下
		Key_N[3].key_sta=GPIOA->IDR &(1);          //读取按键状态判断是否按下
		for (uint8_t i=0;i<4;i++) //循环4个按键
		{
		switch (Key_N[i].judge_sta) //判断此时按键所处的状态
			{
				case 0 :{if(Key_N[i].key_sta==0) Key_N[i].judge_sta=1;}break; //确认按下
				case 1 :{if(Key_N[i].key_sta==0) Key_N[i].judge_sta=2;}break; //进行消抖
				case 2 : //判断是否抬起
						{
							if(Key_N[i].key_sta==1) Key_N[i].judge_sta=3;	//抬起则进入下个状态					
							else Key_N[i].key_time1++; //没抬起则进行时间累加

						}break;
				case 3 : //按键抬起状态
						{
							if(Key_N[i].key_sta==0) //判断是否再次按下双击
							{
								Key_N[i].judge_sta=4;	 //	进入下一个状态
							}
							else //没有再次按下进行时间累加
							{
								Key_N[i].key_time2++; //双击间隔时间自加
								if(Key_N[i].key_time2>30)  //设置双击时间间隔最大为300ms,过300ms还是没有再次按下,表明是单击或长按
								{//判断按下时间是否大于1s,大于1s为长按
									if(Key_N[i].key_time1>100) Key_N[i].key_long=1;
									else Key_N[i].key_single=1; //否则为短按
									Key_N[i].key_time1=0;  //按键按下时间标志清0
									Key_N[i].key_time2=0;	//是否双击按键时间清0
									Key_N[i].judge_sta=0;	//按键判断状态清0							
								}
							}

						}break;
				case 4 :{if(Key_N[i].key_sta==0) Key_N[i].judge_sta=5;}break;	//双击的第二次按下消抖			
				case 5 : //判断第二次是否抬起
						{
							if(Key_N[i].key_sta==1)  //抬起了
							{
								Key_N[i].key_double=1; //双击标志之一
								Key_N[i].key_time1=0;//按键按下时间标志清0
								Key_N[i].key_time2=0;	//是否双击按键时间清0
								Key_N[i].judge_sta=0;	//按键判断状态清0
							}
						}break;
			}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

完整工程

(链接:https://pan.baidu.com/s/1DbZ6YarBLwqGaJnvFC1RPg
提取码:al08)

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号