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