蓝桥杯嵌入式总结(KEY配置_按键扫描(三行代码)_矩阵按键_GPIO口输入和输出类型)

KEY

  • 按键输入:通过按键向芯片输入信号,一般的按键都会接上拉电阻接芯片,另一端接地,按下将芯片的引脚拉低。因此在初始化函数中芯片引脚的工作模式为上拉输入,引脚的初始电平为高电平。
  • 矩阵按键:通过芯片向矩阵按键(4*4)的行和列接口分别输入0000 1111和1111 0000检测两次,判断出按键的坐标。

1.KEY初始化

CT117E开发板KEY1-4引脚,分别为PA0、PA8、PB1、PB2。
KEY_Init()函数注意的问题:
按键输入时GPIO的工作模式为上拉输入。

void KEY_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Mode =  GPIO_Mode_IPU;	//上拉输入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;
	GPIO_Init(GPIOB,&GPIO_InitStructure);	
}

2.按键抖动产生的原因

通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。
在这里插入图片描述
由于单片机的运行速度非常快,按下一次按键,可能在A点检测到一次低电平,在B点检测到一次高电平,在C点又检测到一次低电平。同时抖动是随机,不可测的。那么按下一次按键,抖动可能对会让单片机误以为按下多次按键。
设置系统定时器,50ms进行一次按键扫描,就尽可能的避免按键抖动对按键检测的影响。

3.短按和长按

按键输入最主要的一步是按键扫描,方法有很多。
三行代码的按键扫描(不是原创)

#define KEY1 GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)//读取指定端口管脚的输入
#define KEY2 GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)
#define KEY3 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)
#define KEY4 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2)
#define KEYINPUT KEY1|(KEY2<<1)|(KEY3<<2)|(KEY4<<3)|0XF0 //将四个按键的IO口附在0xf0的后四位上,分别为KEY4/3/2/1
u8 trg = 0;
u8 cont = 0;

void KEY_Reading()
{
	u8 read_data = (KEYINPUT)^(0xff);
	trg = read_data&(read_data^cont);
	cont = read_data;
}
  • 按键配置的上拉输入,当没有按键按下时,KEYINPUT为0xff;read_data = (KEYINPUT)^(0xff) = 0x00;trg = 0x00&0x00 = 0x00;cont = 0x00;
  • 当按键4按下时,KEYINPUT为0xf7; read_data = 0x08;计算trg的cont为按键未按下时的cont:trg = 0x08&(0x08^0x00) = 0x08;cont = 0x08依次列推,trg 为0x04、0x02、0x01;
  • 当按键4长按时,第二次进入按键扫描时,按键4 trg = 0x08&(0x08^0x08) = 0x00;cont的值保持不变,按键4、3、2、1分别长按:cont = 0x08、0x04、0x02、0x01;
  • 按键短按的标志位为trg 按键长按的标志位为cont.
  • 在SYSTICK中断处理函数每50ms进行一次按键扫描,如果按键判断语句放在中断外时,必须要用(长按标志位cont & KEY_FLAG按键扫描标志位)作为判断条件,if语句中变量累加20次后在执行操作,达到地效果为按键长按1s后执行操作;如果按键判断语句放在SYSTICK中断内则不需要。

按键1短按:蜂鸣器响100ms,长按:点亮第二个LED;

按键4短按:点亮第三个LED,长按:熄灭第三个LED;

u8 time_count = 0;	//长按时间累加位
u8 time_count1 = 0;
extern u8 trg;
extern u8 cont;
	if(KEY_FLAG) //按键扫描
	{
		KEY_FLAG = 0;	
		KEY_Reading();
	}
	
	if(trg == 0x01)
	{
		Beep_Flag = 1;
	}
	
	if(cont == 0x01 && KEY_FLAG)	//按键1长按 按键判断语句放在中断外应把(长按标志位cont & KEY_FLAG按键扫描标志位)作为判断条件
	{
		time_count ++;
		if(time_count == 20)//50ms进行一次按键扫描,长按时间为1s触发
		{
			time_count = 0;
			GPIOC->ODR &= ~(1<<8);
			GPIOD->ODR |= (1<<2);//打开锁存器
			GPIOD->ODR &= ~(1<<2);//关闭锁存器	
		}
	}
	if(trg == 0x08)
	{
		GPIOC->ODR &= ~(1<<9);
		GPIOD->ODR |= (1<<2);//打开锁存器
		GPIOD->ODR &= ~(1<<2);//关闭锁存器
	}
	if(cont == 0x08 && KEY_FLAG)
	{
		time_count1 ++;
		if(time_count1 == 20)//50ms进行一次按键扫描,长按时间为1s触发
		{
			time_count1 = 0;
			GPIOC->ODR |= (1<<9);
			GPIOD->ODR |= (1<<2);//打开锁存器
			GPIOD->ODR &= ~(1<<2);//关闭锁存器	
		}
	}

4.矩阵按键

经过三行代码的按键扫描的启发,自己写了一下矩阵按键的三行代码。
与按键输入不同的点:

  1. 矩阵按键需要扫描两次,分别是行和列。
  2. 矩阵按键的行和列端口的初始值由芯片输出,要自己设定。
    矩阵按键扫描函数

在这里插入图片描述

  • 4*4的矩阵按键需要8个IO口,我把它接在了PC0-PC7;矩阵按键第一列到第四列是PC0-PC3,第一行到第四行是PC4-PC7。
  • 检测方式:行或列分别赋0和1,检测赋0的行或列,变为1的行或列为按键的坐标。
  • KEYINPUT宏定义,PC0-PC8由低位到高位赋值,与按键输入方法相同。
#define KEY0 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0)//读取指定端口管脚的输入
#define KEY1 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1)
#define KEY2 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_2)
#define KEY3 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_3)
#define KEY4 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_4)
#define KEY5 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_5)
#define KEY6 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_6)
#define KEY7 GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_7)

#define KEYINPUT 	KEY0|(KEY1<<1)|(KEY2<<2)|(KEY3<<3)|(KEY4<<4)|(KEY5<<5)|(KEY6<<6)|(KEY7<<7)|0X00//矩阵按键的八个IO口附在0x00的上,分别为KEY7/6/5/4/3/2/1/0
  • 矩阵按键的检测分为检测行和列,因此分成了两个函数,行检测函数和列检测函数。
u8 trg_line = 0;	//行检测标志位
u8 cont_line = 0;
u8 trg_column = 0;	//列检测标志位
u8 cont_column = 0;

行检测函数

初始值设置:列的IO口均为1,行的IO口均为0,因此KEYINPUT = 0x0f;与按键扫描的原理相同,readline =
 (KEYINPUT)^(0x0f);
 
因为KEYINPUT异或和它相等的值才为0x00,这样trg_line和trg_column的初始值也为0。

当第一行有按键按下时,KEYINPUT = 0x1f, readline = 0x1f^0x0f = 0x10;  trg_line = 0x10(0x10^0x00)
 = 0x10; 依次列推,trg_line = 0x20、0x40、0x80。
void MATRIX_KEY_Readline()		//读取是来自矩阵按键的哪一行
{
	u8 read_line;
	
	GPIO_SetBits(GPIOC, GPIO_Pin_0);	//0000 1111
	GPIO_SetBits(GPIOC, GPIO_Pin_1);
	GPIO_SetBits(GPIOC, GPIO_Pin_2);
	GPIO_SetBits(GPIOC, GPIO_Pin_3);
	GPIO_ResetBits(GPIOC, GPIO_Pin_4);
	GPIO_ResetBits(GPIOC, GPIO_Pin_5);
	GPIO_ResetBits(GPIOC, GPIO_Pin_6);
	GPIO_ResetBits(GPIOC, GPIO_Pin_7);

	read_line = (KEYINPUT)^(0x0f);
	trg_line = read_line&(read_line^cont_line);
	cont_line = read_line;
}

列检测函数

初始值设置:列的IO口均为0,行的IO口均为1,因此KEYINPUT = 0xf0,与按键扫描的原理相同,read_column = 
(KEYINPUT)^(0xf0);

因为KEYINPUT异或和它相等的值才为0x00,这样cont_line和cont_column的初始值也为0。

当第一列有按键按下时,KEYINPUT = 0xf1, read_column = 0xf1^0xf0 = 0x01;  trg_column = 0x01(0x01^
0x00) = 0x01; 依次列推,trg_column = 0x02、0x04、0x08。
void MATRIX_KEY_Readcolumn()	//读取是来自矩阵按键的哪一列
{
	u8 read_column;
	
	GPIO_ResetBits(GPIOC, GPIO_Pin_0);	//1111 0000
	GPIO_ResetBits(GPIOC, GPIO_Pin_1);
	GPIO_ResetBits(GPIOC, GPIO_Pin_2);
	GPIO_ResetBits(GPIOC, GPIO_Pin_3);
	GPIO_SetBits(GPIOC, GPIO_Pin_4);
	GPIO_SetBits(GPIOC, GPIO_Pin_5);
	GPIO_SetBits(GPIOC, GPIO_Pin_6);
	GPIO_SetBits(GPIOC, GPIO_Pin_7);
	
	read_column = (KEYINPUT)^(0xf0);
	trg_column = read_column&(read_column^cont_column);
	cont_column = read_column;
}

行、列检测函数内要用GPIO_SetBits();函数设置行和列端口的初始值;

5.矩阵按键初始化函数

void MATRIX_KEY_Init()	//矩阵按键初始化
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode =  GPIO_Mode_Out_PP;	//与按键输入配置的上拉输入不同,矩阵按键配置推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStructure);	
}

注意:GPIO的工作模式为推挽输出。

按键(1,1)点亮第三个LED,按键(2,2)熄灭第三个LED。

	if(KEY_FLAG) //按键扫描
	{
		KEY_FLAG = 0; 		
		MATRIX_KEY_Readline();
		MATRIX_KEY_Readcolumn();			
	}
	if(trg_line == 0x10 && trg_column == 0x01)	//第一行第一个按键按下
	{
		GPIOC->ODR &= ~(1<<9);
		GPIOD->ODR |= (1<<2);//打开锁存器
		GPIOD->ODR &= ~(1<<2);//关闭锁存器
	}
	if(trg_line == 0x20 && trg_column == 0x02)	//第二行第二个按键按下
	{
		GPIOC->ODR |= (1<<9);
		GPIOD->ODR |= (1<<2);//打开锁存器
		GPIOD->ODR &= ~(1<<2);//关闭锁存器
	}		

6.GPIO口输入和输出类型

Cortex-M3 里,GPIO 工作模式的配置有 8 种:

  1. GPIO_Mode_IPU 上拉输入
  2. GPIO_Mode_IPD 下拉输入
  3. GPIO_Mode_AIN 模拟输入
  4. GPIO_Mode_IN_FLOATING 浮空输入
  5. GPIO_Mode_Out_OD 开漏输出
  6. GPIO_Mode_AF_OD 复用开漏输出
  7. GPIO_Mode_AF_OD 复用开漏输出
  8. GPIO_Mode_AF_PP 复用推挽输出

上拉输入/下拉输入/模拟输入

字面意思理解即可。

浮空输入

浮空输入状态下,IO的电平状态是不确定的,完全由外部输入决定,如果在该引脚悬空的情况下,读取该端口的电平
是不确定的。

推挽输出

可以输出高,低电平,连接数字器件;推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通
的时候另一个截止。

开漏输出

输出端相当于三极管的集电极。要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动其吸收电流的能力相对
强(一般 20MA 以内)。
一般来说,开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电
平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以
改变传输电平。比如加上上拉电阻就可以提供 TTL/CMOS 电平输出等。(上拉电阻的阻值决定了逻辑电平转换的沿的
速度 。阻值越大,速度越低功耗越小, 所以负载电阻的选择要兼顾功耗和速度。)

在这里插入图片描述

图中,左边是推挽输出模式,其中比较器输出高电平时下面的 PNP 三极管截止,而上面 NPN 三极管导通,输出电
平VS+;当比较器输出低电平时则恰恰相反, PNP 三极管导通,输出和地相连,为低电平。右边可以理解为开漏输
出形式,需要接上拉。

复用开漏输出、复用推挽输出

可以理解为 GPIO 口被用作第二功能时的配置情况(即并非作为通用 IO口使用)。

(以上仅个人观点)

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页