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.矩阵按键
经过三行代码的按键扫描的启发,自己写了一下矩阵按键的三行代码。
与按键输入不同的点:
- 矩阵按键需要扫描两次,分别是行和列。
- 矩阵按键的行和列端口的初始值由芯片输出,要自己设定。
矩阵按键扫描函数
- 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 种:
- GPIO_Mode_IPU 上拉输入
- GPIO_Mode_IPD 下拉输入
- GPIO_Mode_AIN 模拟输入
- GPIO_Mode_IN_FLOATING 浮空输入
- GPIO_Mode_Out_OD 开漏输出
- GPIO_Mode_AF_OD 复用开漏输出
- GPIO_Mode_AF_OD 复用开漏输出
- GPIO_Mode_AF_PP 复用推挽输出
上拉输入/下拉输入/模拟输入
字面意思理解即可。
浮空输入
浮空输入状态下,IO的电平状态是不确定的,完全由外部输入决定,如果在该引脚悬空的情况下,读取该端口的电平
是不确定的。
推挽输出
可以输出高,低电平,连接数字器件;推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通
的时候另一个截止。
开漏输出
输出端相当于三极管的集电极。要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动其吸收电流的能力相对
强(一般 20MA 以内)。
一般来说,开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电
平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以
改变传输电平。比如加上上拉电阻就可以提供 TTL/CMOS 电平输出等。(上拉电阻的阻值决定了逻辑电平转换的沿的
速度 。阻值越大,速度越低功耗越小, 所以负载电阻的选择要兼顾功耗和速度。)
图中,左边是推挽输出模式,其中比较器输出高电平时下面的 PNP 三极管截止,而上面 NPN 三极管导通,输出电
平VS+;当比较器输出低电平时则恰恰相反, PNP 三极管导通,输出和地相连,为低电平。右边可以理解为开漏输
出形式,需要接上拉。
复用开漏输出、复用推挽输出
可以理解为 GPIO 口被用作第二功能时的配置情况(即并非作为通用 IO口使用)。
(以上仅个人观点)