STM32-按键输入

1  硬件连接

1.1 mini

STM32-按键输入

1.2 战舰

STM32-按键输入

1.3 探索者

STM32-按键输入

        KEY0->PE4  上拉输入        KEY1->PE3  上拉输入        KEY2->PE2  上拉输入        WK_UP->PA0  下拉输入

        按键输入最关键的是对按键的初始化和输入判断。这里按键的初始化与LED的初始化不同的是GPIO的模式不一样了,LED是推挽输出,按键则相反,是输入,这个时候要考虑是哪一种输入方式,这里当我的板子上按键是共阴极的,当按键按下的时候IO口输入的低电平,所以我需要在IO口接上拉电阻,使用上拉输入模式;这里当我的板子上按键是共阳极的,当按键按下的时候IO口输入的高电平,所以我需要在IO口接下拉电阻,使用下拉输入模式。

2  GPIO输操作说明

        读取IO口输入电平调用库函数:uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

        读取IO口输入电平操作寄存器:GPIOx_IDR:端口输入寄存器

        使用位带操作读取IO口输入电平:PEin(4) ------------读取GPIOE.4口电平       PEin(n)------------读取GPIOE.n口电平

3  按键输入实验

        按键消抖:我们采用延时消抖的思想,因为按键是机械开关,所以在按下的时候会产生电平的抖动,这在51单片机中经常使用。我个人认为延时消抖应该是一种思想。

STM32-按键输入

        第一步先检查按键是否按下,如果按下了,那么就延时20毫秒,再次判断按键是否按下,确保其不是因为机械抖动造成的误判断。两次判断以后,其结果基本确定,如果按键按下了,就点亮LED灯,然后再加入一个死循环,等待按键被松开。就是通过一个等待按键松开的函数来跳出循环,达到按键锁存的目的。

        对于按键输入来说,怎么判断是连续按,还是不连续按,这是一个重点。以前是将两种情况分开考虑,写成了两个函数,然后判断,其实,这个可以写成一个函数,只需添加一个选择形参,然后对形参进行判断操作即可。

        按键扫描(支持连续按)的一般思路:如果我要实现:按键按下,没有松开,只能算按下一次,这个函数无法实现。

u8 KEY_Scan(void)                              u8 key_Scan(u8 mode)
{                                              {
   if(KEY按下)                                   if(KEY0 == 0 || KEY1 == 0|| WK_UP == 1)   //key按键按下
  {                                               {
    delay_ms(10);/*延时10-20ms,防抖。*/           delay_ms(10);
    if(KEY确实按下)                                 if(KEY0 == 0)             //    if(key 确实按下)
     {                                                 return KEY0_PRES;     //   返回按键值
      return KEY_Value;                            else if(KEY1 == 0)
     }                                                 return KEY1_PRES;
    return 无效值;                                 else if(WK_UP == 1)
  }                                                    return WKUP_PRES;
}                                                 }
                                                  else if(KEY1 == 1 && KEY0 == 1 && WK_UP == 0)
                                                       return 0;                       //没有按键按下 返回无效值
                                               }

        按键扫描(不支持连续按)的一般思路:不支持连续按:就是说,按键按下了,没有松开,只能算一次。

u8 KEY_Scan(void)                             u8 key_Scan(u8 mode)
{                                             {
  static u8 key_up=1;                            static u8 key_up = 1; //按键松开标志
  if(key_up &&  KEY按下)                       if(key_up && (KEY0 == 0 || KEY1 == 0|| WK_UP == 1))
  {                                              {
    delay_ms(10);//延时,防抖                       delay_ms(10);
    key_up=0;//标记这次key已经按下                  key_up = 0;
    if(KEY确实按下)                                 if(KEY0 == 0)              //if(key确实按下)
     {                                                  return KEY0_PRES;      // return 键值
      return KEY_VALUE;                             else if(KEY1 == 0)
     }                                                  return KEY1_PRES;
  }else if(KEY没有按下)  key_up=1;                  else if(WK_UP == 1)
  return 没有按下                                       return WKUP_PRES;
}                                                }
                                                 else if(KEY1 == 1 && KEY0 == 1 && WK_UP == 0)
                                                    key_up = 1;
                                                 return 0;
                                              }

        按键扫描(两种模式合二为一)的一般思路:

u8 KEY_Scan(u8 mode)                            u8 key_Scan(u8 mode)
{                                               {
  static u8 key_up=1;                             static u8 key_up = 1; //按键松开标志
  if(mode==1) key_up=1;//支持连续按               if(mode)
  if(key_up &&  KEY按下)                            key_up = 1;
  {                                               if(key_up && (KEY0 == 0 || KEY1 == 0|| WK_UP == 1))
    delay_ms(10);//延时,防抖                     {
    key_up=0;//标记这次key已经按下                    delay_ms(10);
    if(KEY确实按下)                                   key_up = 0;
    {                                                 if(KEY0 == 0)
      return KEY_VALUE;                                   return KEY0_PRES;
    }                                                 else if(KEY1 == 0)
   }else if(KEY没有按下)  key_up=1;                       return KEY1_PRES;
   return 没有按下                                    else if(WK_UP == 1)
}                                                         return WKUP_PRES;
                                                  }
                                                  else if(KEY1 == 1 && KEY0 == 1 && WK_UP == 0)
                                                      key_up = 1;
                                                  return 0;
                                                }

          在主函数中 执行到这条 key=KEY_Scan(0)命令 ,也就是mode=0,那么开始执行该函数。定义一个关键字static 变量 key_up,并给其赋值为1。if(mode)key_up=1; 不执行。执行if(key_up&&(KEY0==0||KEY1==0||WK_UP==1)) ,假设此时按下KEY1且不松开,则执行该if后面的语句。delay_ms(10);目的是去抖动。将key_up赋值为0,然后函数返回值为1(KEY0_PRES在宏定义中表示为1),跳出函数,继续执行主函数,使得LED1灯亮。程序继续走,再一次走到key=KEY_Scan(0),调用KEY_Scan函数,这时候由于key_up在上一次被赋值为0了,故此时if(key_up&&(KEY0==0||KEY1==0||WK_UP==1)) 也不执行,直接执行else if(KEY0==1&&KEY1==1&&KEY2==1&&KEY3==0)key_up=1;此时key_up才重新变为1,然后return 0;那么主函数中key=0,则不执行if(key);故此时LED1灯状态不变,依然亮。这样就实现了第一种模式,即按键连续按的时候,灯/蜂鸣器的状态不改变。(其他的形式类似)

        下面贴一个正点原子论坛中一位老哥的解释:key_up只是作为一个标志,if(key_up&&(KEY0==0||KEY1==0||KEY2==0||KEY3==1))这里边判断按键按下是要跟key_up相与的,也就是当你有键按下时还需要key_up为1才能让程序读取到按键值。key_up作为静态变量只会被初始化一次(也就是static u8 key_up=1只会执行一次),每一次读取到一个按键值后key_up就会被置0,让它重新变为1只有两种情况:
        (1)else if(KEY0==1&&KEY1==1&&KEY2==1&&KEY3==0)key_up=1;这里就并不是连续按了,只要你按键松开了,下次继续读取没问题。
        (2)你如果是想连续按着不动又希望程序能多次读取到的话,必须通过if(mode)key_up=1; 这一句就能让key_up恢复1,这样就实现了不需要松开按键就让它恢复1,即可实现连续按键并且程序连续读取。

        (1)使能按键对应IO口时钟。调用函数:RCC_APB2PeriphClockCmd();
        (2)初始化IO模式:上拉/下拉输入。调用函数:GPIO_Init();
        (3)扫描IO口电平(库函数/寄存器/位操作)。

/************mian.c************/
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "key.h"
 
int main(void)
 {
 	vu8 key=0;	
	delay_init();	    	 //延时函数初始化	  
 	LED_Init();			     //LED端口初始化
	KEY_Init();          //初始化与按键连接的硬件接口
	BEEP_Init();         	//初始化蜂鸣器端口
	LED0=0;					//先点亮红灯
	while(1)
	{
 		key=KEY_Scan(0);	//得到键值
	   	if(key)
		{						   
			switch(key)
			{				 
				case WKUP_PRES:	//控制蜂鸣器
					BEEP=!BEEP;
					break;
				case KEY2_PRES:	//控制LED0翻转
					LED0=!LED0;
					break;
				case KEY1_PRES:	//控制LED1翻转	 
					LED1=!LED1;
					break;
				case KEY0_PRES:	//同时控制LED0,LED1翻转 
					LED0=!LED0;
					LED1=!LED1;
					break;
			}
		}else delay_ms(10); 
	}	 
}
 
/************key.h************/
#ifndef __KEY_H
#define __KEY_H	 
#include "sys.h"	 

//#define KEY0 PEin(4)    //PE4
//#define KEY1 PEin(3)	  //PE3 
//#define KEY2 PEin(2)	  //PE2
//#define WK_UP PAin(0)	  //PA0  WK_UP

#define KEY0  GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)    //读取按键0
#define KEY1  GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)    //读取按键1
#define KEY2  GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)    //读取按键2 
#define WK_UP   GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)  //读取按键3(WK_UP) 

#define KEY0_PRES 	1	//KEY0按下
#define KEY1_PRES	2	//KEY1按下
#define KEY2_PRES	3	//KEY2按下
#define WKUP_PRES   4	//KEY_UP按下(即WK_UP/KEY_UP)

void KEY_Init(void);    //IO初始化
u8 KEY_Scan(u8);  	    //按键扫描函数					    
#endif
 
/************key.c************/
#include "stm32f10x.h"
#include "key.h"
#include "sys.h" 
#include "delay.h" 
								    
//按键初始化函数
void KEY_Init(void) //IO初始化
{ 
 	GPIO_InitTypeDef GPIO_InitStructure;
 
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);//使能PORTA,PORTE时钟

	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;//KEY0-KEY2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
 	GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE2,3,4

	//初始化 WK_UP-->GPIOA.0	  下拉输入
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0

}
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,KEY0按下
//2,KEY1按下
//3,KEY2按下 
//4,KEY3按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY2>KEY3!!
u8 KEY_Scan(u8 mode)
{	 
	static u8 key_up=1;//按键按松开标志
	if(mode)key_up=1;  //支持连按		  
	if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1))
	{
		delay_ms(10);//去抖动 
		key_up=0;
		if(KEY0==0)return KEY0_PRES;
		else if(KEY1==0)return KEY1_PRES;
		else if(KEY2==0)return KEY2_PRES;
		else if(WK_UP==1)return WKUP_PRES;
	}else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1; 	    
 	return 0;// 无按键按下
}

        注意此函数有响应优先级:KEY0>KEY1>KEY2>KEY_UP !!

4  C语言关键字 :static

4.1 局部变量

        普通局部变量是再熟悉不过的变量了,在任何一个函数内部定义的变量(不加static修饰符)都属于这个范畴。编译器一般不对普通局部变量进行初始化,也就是说它的值在初始时是不确定的,除非对其显式赋值。普通局部变量存储于进程栈空间,使用完毕会立即释放。静态局部变量使用static修饰符定义,即使在声明时未赋初值,编译器也会把它初始化为0。且静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变。变量在全局数据区分配内存空间;编译器自动对其初始化;其作用域为局部作用域,当定义它的函数结束时,其作用域随之结束。静态局部变量的效果跟全局变量有一拼,但是位于函数体内部,就极有利于程序的模块化了。

4.2 全局变量

        全局变量定义在函数体外部,在全局数据区分配存储空间,且编译器会自动对其初始化。普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。

4.3 Static变量

        Static的用途主要有两个:
                一是用于修饰存储类型使之成为静态存储类型
                二是用于修饰链接属性使之成为内部链接属性。
        1)静态存储类型:
                在函数内定义的静态局部变量,该变量存在内存的静态区,所以即使该函数运行结束,静态变量的值不会被销毁,函数下次运行时能仍用到这个值。
                在函数外定义的静态变量——静态全局变量,该变量的作用域只能在定义该变量的文件中,不能被其他文件通过extern引用。
        2) 内部链接属性
                静态函数只能在声明它的源文件中使用。

        下例中,每次调用getValue函数之后,返回值是多少?

int getValue(void)                      int getValue(void)
{                                       {
  int  flag=0;                             static int  flag=0;
  flag++;                                  flag++;
  return flag;                             return flag;
}                                       }

上一篇:C#枚举(一)使用总结以及扩展类分享


下一篇:js中的进制类型转换