基于STM32 4x4键盘扫描
- 小白接触ARM快两周了,开发板上4个按键已经不能满足学习需要。开发板上按键都是一个按键对应一个IO口,大量浪费IO口资源,那需要很多很多按键怎么办呢,难道没有什么方法解决吗??有,当然有了,那就是矩阵键盘扫描,在查阅许多大神博客、资料后有了点眉目便开始尝试,历经千辛万苦终于弄出来了!那喜悦!那开心!下面给大家分享尝试过程中踩到的雷。水平有限,若描述中存在错误欢迎在评论中指出。
矩阵键盘扫描原理
浏览过多篇文章后决定尝试翻转法来进行矩阵键盘扫描,丢出键盘原理图:
四行四列共八个IO口,矩阵键盘扫描难点在于确定是哪个按键按下,单片机不像人类长有眼睛,一看就知道按键在哪一行那一列。那如何确定行列值呢?没错利用键盘行列线连接的IO口。仔细观察矩阵键盘原理图,每个按键刚好在行线列线交点,也就是说按键按下时行线列线连接的IO口导通。利用求同存异原则,只需将行线置高列线置地,当有按键按下时行线被拉低,此时求同存异的异出现,再用GPIO_ReadInputData( )函数读出引脚状态,即可得知按键所在行;列线亦是如此,只需将列线置高,行线置地,类比行线。下面祭出代码:
void key_Init1(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;//键盘行连接的引脚
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOF,&GPIO_InitStructure);
GPIO_ResetBits(GPIOF,GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;//键盘列连接的引脚
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOF,&GPIO_InitStructure);
}void key_Init2(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOF,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOF,&GPIO_InitStructure);
GPIO_ResetBits(GPIOF,GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
}
这是得出按键所在行列的初始化代码。别看就几句IO口配置代码,这可是键盘扫描成功的关键,坑来了,坑来了,坑来了注意看,上面讲到只需要将行线置高列线置低就行,那为什么不用 GPIO_Mode_IPD下拉输入GPIO_Mode_IPU上拉输入来做呢?emmm,是的作者做了小白鼠,弄了上下拉来控制高低电平,发现怎么按按键都没反应,检查了半天程序开始查电路,惊人的发现当按键按下时该被拉低到0的电平只从3.2V降到了1.6V,而该升到高电平的引脚也只升到了1.6V。我裂开了,那还玩个锤子,这样GPIO_ReadInputData( )函数根本不能读到正确状态。又翻数篇博客,查数篇资料,头发掉了数根,发现将IO口配置成上下拉后,内部有电阻限位根本不可能降到0或者升到3.2V,而且后来察觉到这样配置相当于将两引脚短路对单片机相当不友好。找到原因后就简单了,那就改IO配置嘛。
行列扫描
通过GPIO_ReadInputData( )读出的数值是整16个IO 口的,而我们只要其中四位,若要低四位则将数值与上0x000F,若要4位到8位则与上0x00F0。在将两个值相或即可确定按键所在行列。祭出代码:
uint16_t key_scan ( void)
{
uint16_t value , value1 ,key_value;//value列值,value1行值,key_value=value | value1 得出具体行列用于主函数判断
key_Init1();
value=GPIO_ReadInputData(GPIOF);
value=value & 0x00f0;
if(value!=0x00f0)
{
delay_ms(10);
value=GPIO_ReadInputData(GPIOF);
value=value & 0x00f0;
if(value!=0x00f0)
{
value=GPIO_ReadInputData(GPIOF);
value=value & 0x00f0;
}
key_Init2();
value1=GPIO_ReadInputData(GPIOF);
value1=value1 & 0x000f;
if(value1!=0x000f)
{
delay_ms(10);
value1=GPIO_ReadInputData(GPIOF);
value1=value1 & 0x000f;
if(value1!=0x000f)
{
value1=GPIO_ReadInputData(GPIOF);
value1=value1 & 0x000f;
}
key_value=value | value1 ;
}
}
return key_value;
}
确定键值
得出行列值后确定键值就简单多了,只需要将得出的行列值与可能存在的行列值比对然后赋你想要的值即可。笔者用的是switch语句,具体如下:
int main(void)
{
uint16_t SB=0 , OUT_value=0;//SB存键值供OLED显示,OUT_value存行列值
SysTick_Init ();
OLED_Init();
while(1)
{
OUT_value= key_scan();
switch (OUT_value)
{
case 0x00ee : SB=0; break ;
case 0x00de : SB=1; break ;
case 0x00be : SB=2; break ;
case 0x007e : SB=3; break ;
case 0x00ed : SB=4; break ;
case 0x00dd : SB=5; break ;
case 0x00bd : SB=6; break ;
case 0x007d : SB=7; break ;
case 0x00eb : SB=8; break ;
case 0x00db : SB=9; break ;
case 0x00bb : SB=10; break ;
case 0x007b : SB=11; break ;
case 0x00e7 : SB=12; break ;
case 0x00d7 : SB=13; break ;
case 0x00b7 : SB=14; break ;
case 0x0077 : SB=15; break ;
default : break;
}
OLED_Clear();
OLED_ShowNum(80,0,56,2,16);
OLED_ShowNum(50,4,SB,2,16);
delay_ms(500);
}
}
实物图如下: