1.直接上代码

#include "stm32f10x.h"
#include "led.h"
#include "key.h"void KeyConfig(void)
{GPIO_InitTypeDef GPIO_InitStructure;   //定义结构体RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);    //打开GPIOB的外设时钟/* 配置结构体并初始化到GPIOB */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;           //选择需要使用的引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;   //配置引脚输出模式GPIO_Init(GPIOB, &GPIO_InitStructure);                //初始化结构体
}int main (void)
{LedConfig();KeyConfig();while(1){if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9) == 1)    //检测按键是否出现高电平状态{keyDelay(50);       //延时去抖if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9) == 1)    //再次检测GPIO_ResetBits(GPIOC,GPIO_Pin_13);        //点亮LED灯}else{GPIO_SetBits(GPIOC,GPIO_Pin_13);      //熄灭LED灯}keyDelay(100); //普通延时}
}

2.代码解析

代码的整体思路是,初始化LED灯以及按键检测IO,每隔100ms检测一次按键电平状态是否发生改变。当检测到按键按下时(即检测到高电平时),将LED灯点亮,否则熄灭LED灯。

在初始化代码中,相对比起LED减少了一步-配置IO口的频率(GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;),该代码是针对当IO口需要输出时的引脚输出速率,但本次工程需要使用的是IO的输入状态。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

        选择GPIOB_PIN_9引脚作为按键检测的输入引脚。

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;

        定义引脚模式,此处因为引脚需要作用的功能是输入检测,所以需要配置为输入模式,而输入模式在官方固件库代码中有三种模式,

GPIO_Mode_IN_FLOATING;GPIO_Mode_IPD;GPIO_Mode_IPU

第一种模式两种状况,其一是用于 IIC,SPI,UART...等通讯协议中,即数据状态不确定的情况下;其二是当外围硬件已经接有上拉/下拉电阻的情况下。

第二、三种模式一般是用于 引脚需要固定在某一种状态,前者是下拉输入,后者是上拉输入

本次工程中因为是按键检测,需要引脚长期处于某一种状态,以确保LED灯不会因为引脚的漂浮不定所造成误触发,所以配置为GPIO_Mode_IPD。同时在确定引脚模式之前应该查原理图,检测按键不按下时的电平状态,此时的电平状态即为配置初始状态。

此时引脚PB9硬件外围并没有接上/下拉电阻,引脚处于漂浮状态,而当按键按下时3.3V电压直达PB9,状态改变为高电平,松开后引脚又恢复到漂浮状态,所以此时需要给它一个初始电平,根据原理图理解,该初始电平状态为低电平时,后续引脚电平检测才能检测到电平的改变。

if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9) == 1)   //检测按键是否出现高电平状态
{keyDelay(50);      //延时去抖if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9) == 1)    //再次检测GPIO_ResetBits(GPIOC,GPIO_Pin_13);        //点亮LED灯
}
else
{GPIO_SetBits(GPIOC,GPIO_Pin_13);       //熄灭LED灯
}

        传入形参中,前者为IO口所在的组,后者为具体IO。此语句是用于检测按键是否出现高电平。当出现高电平时,进入下一次检测,用以去除按键抖动,如果下一次检测依然出现高电平,则代表按键正常按下,执行点亮LED灯操作。当检测不到按键按下时熄灭LED灯。实现按键按下时点亮LED灯,松开熄灭LED灯。

3.花样按键

3.1矩阵按键

一个按键一个IO进行检测这种方法只适用于按键少的情况,但当按键的数量达到10+以上时,一IO一按键的状况就不实用了,因为IO口资源对于单片机来说是尤为珍贵的,对于这种情况,就有了一种应对方法——矩阵按键。

矩阵按键就是将N个按键,进行XY平面分布,组成一个阵列,随后分别将每一行每一列的引脚串联起来,让同一列的共用同一个引脚,同一行共用同一引脚,最后大致原理图如下:

我们用到的是3*4的矩阵键盘,根据原理图分析矩阵电路图的连接方式。

  • 第一到四行分别连接单片机的PB6~PB9
  • 第一到三列分别连接单片机的PA8-PA10

我们将PB6~PB9初始化配置为上拉输入模式,将PA8~PA10配置为推挽输出模式,并将其默认置0。在这种情况下当有按键按下时,引脚会被拉低,松开按键后引脚又会被芯片重新拉高。

举个例子,当按键1被按下时,PB9就会被拉低,此时如果按照51单片机的逻辑,我们只需要再次检测列引脚查看哪个被拉低,就可以确定按键在哪个位置。但在STM32中引脚的输入与输出是分开的,输入引脚的上拉电压在按键按下被接通后,在流入输出引脚后会被芯片内的NMOS对地放掉,所以引脚状态并不会发生改变。关于 I/O端口的基本结构可以看这位博主的文

在这种情况下我们需要将推挽输出的IO进行电平反转,再检测是哪一列,具体代码如下:

引脚初始化代码

void MatrixKeyConfig(void)
{GPIO_InitTypeDef GPIO_InitStructure;   //定义结构体RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    //打开GPIOA的外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //打开GPIOB的外设时钟/* 配置结构体并初始化到GPIOA */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;            //选择需要使用的引脚GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //配置引脚输出模式GPIO_Init(GPIOA, &GPIO_InitStructure);                //初始化结构体GPIO_ResetBits(GPIOA, GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10);/* 配置结构体并初始化到GPIOB */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;         //选择需要使用的引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;   //配置引脚输出模式GPIO_Init(GPIOB, &GPIO_InitStructure);
}

引脚输入的初始化可以参考上面的按键初始化 ,输出初始化参考前面讲过的LED。

矩阵检测

char MatrixCheck(void)
{uint16_t line;uint16_t keydata = 0;if((GPIO_ReadInputData(GPIOB)>>6) != 0x3ff){keyDelay(1);if((GPIO_ReadInputData(GPIOB)>>6) != 0x3ff){keydata = GPIO_ReadInputData(GPIOB)>>6;       //记录当前按键状态line = 0x3ff-keydata;        //得到当前被按下行数,8-4  4-3  2-2 1-1  line=0x08时,对应第四行被按下。GPIO_SetBits(GPIOA,GPIO_Pin_8);       //IO反转,if((GPIO_ReadInputData(GPIOB)>>6) == keydata) //检测输入是否因为IO反转发生改变。{GPIO_ResetBits(GPIOA,GPIO_Pin_8);   GPIO_SetBits(GPIOA,GPIO_Pin_9);if((GPIO_ReadInputData(GPIOB)>>6) == keydata){GPIO_ResetBits(GPIOA,GPIO_Pin_9);GPIO_SetBits(GPIOA,GPIO_Pin_10);if((GPIO_ReadInputData(GPIOB)>>6) == keydata){GPIO_ResetBits(GPIOA,GPIO_Pin_10);}else line |= 0x10;      //代表第一列按键被按下}elseline |= 0x20;         //代表第二列按键被按下}elseline |= 0x30;             //代表第三列按键被按下GPIO_ResetBits(GPIOA, GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10);       //重新拉低输出IOswitch(line){case 0x18:return '1';case 0x14:return '4';case 0x12:return '7';case 0x11:return '*';case 0x28:return '2';case 0x24:return '5';case 0x22:return '8';case 0x21:return '0';case 0x38:return '3';case 0x34:return '6';case 0x32:return '9';case 0x31:return '#';default:return 'n';}}}return 'n';
}

第一步检测按键状态是否发生改变,>>6  是为了将第一个按键所在的IO对齐数据的第一位。对于PB端口而言当高于PB5的端口没有被配置时都处于高阻态,所以他们的电平都是高电平,因此PB组IO在>>6后的默认数据为0x03ff。当检测到不为0x03ff时,代表有按键按下。使用keydata来储存按键状态,用0x03ff减去keydata就得到按键所在行数(行数对应8421,8代表在从下往上数第四行,4代表在第三行,以此类推),随后反转输出IO的电平,再检测PB口数据,将得到的数据与IO前的数据进行对比,如果不相等则代表上拉输入IO无法通过输出IO内部NMOS对地放电(有电势差才有电导通),因此可以确定列所在的位置。在完成对列的检测后仍需要对输出IO进行一个电平拉低。

上面我们得到一个完成的行列数据,随后将line参数传入switch进行一个选择,这完成了一整个矩阵按键的代码编写。

矩阵按键的具体应用

void MatrixUse(void)
{char keyData;keyData = MatrixCheck2();if(keyData == '1')GPIO_Write(GPIOA,GPIO_Pin_7);     //点亮第一个LED灯else if(keyData == '2')                   GPIO_Write(GPIOA,GPIO_Pin_6);      //点亮第二个LED灯else if(keyData == '3')                   GPIO_Write(GPIOA,GPIO_Pin_5);      //点亮第三个LED灯else if(keyData == '4')                   GPIO_Write(GPIOA,GPIO_Pin_4);      //点亮第四个LED灯else if(keyData == '0')GPIO_Write(GPIOA,0);
}

通过矩阵按键检测函数的返回值进行对应自定义操作,这里以点灯为例子。

3.2 矩阵按键在项目应用注意事项

在我们的具体项目应用中,引脚的电平状态不可能总是0x03ff,因此就必须要考虑只需要获得对应使用那一组引脚的数据,原理图中使用的是PB6-PB9,在二进制表中对应的位就是

0000 0011 1100 0000

将其转化为16进制数就是 0x03c0,得到这个数据由什么用呢,上面说到我们需要屏蔽其他位,只留下我们需要的位,此时我们只需要将GPIO_ReadInputData(GPIOB)函数得到的IO数据和0x03c0进行一个位与操作,就可以达到我们想要的效果,'&'运算符的特性是对0敏感,即有0必0。因此我们需要将3.1的代码进行一个修改。

char MatrixCheck2(void)
{uint16_t line;uint16_t keyDefault = 0x03c0;           //需要用到引脚的所在位号uint16_t keydata = (GPIO_ReadInputData(GPIOB) & keyDefault);      //此处获取一次的目的是DEBUG模式查看数值if((GPIO_ReadInputData(GPIOB) & keyDefault)!= keyDefault){keyDelay(1);      //滤波if((GPIO_ReadInputData(GPIOB) & keyDefault)!= keyDefault){keydata = (GPIO_ReadInputData(GPIOB) & keyDefault);     //记录当前按键状态line = (keyDefault-keydata)>>6;        //得到当前被按下行数,">>6"将数据进行低位对齐 8-4  4-3  2-2 1-1  line=0x08时,对应第四行被按下。GPIO_SetBits(GPIOA,GPIO_Pin_8);        //IO反转if((GPIO_ReadInputData(GPIOB) & keyDefault) == keydata){GPIO_ResetBits(GPIOA,GPIO_Pin_8);GPIO_SetBits(GPIOA,GPIO_Pin_9);if((GPIO_ReadInputData(GPIOB) & keyDefault) == keydata){GPIO_ResetBits(GPIOA,GPIO_Pin_9);GPIO_SetBits(GPIOA,GPIO_Pin_10);if((GPIO_ReadInputData(GPIOB) & keyDefault) == keydata){GPIO_ResetBits(GPIOA,GPIO_Pin_10);}else line |= 0x10;     //代表第一列按键被按下}elseline |= 0x20;         //代表第二列按键被按下}elseline |= 0x30;             //代表第三列按键被按下GPIO_ResetBits(GPIOA, GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10);       //重新拉低输出IOswitch(line){case 0x18:return '1';case 0x14:return '4';case 0x12:return '7';case 0x11:return '*';case 0x28:return '2';case 0x24:return '5';case 0x22:return '8';case 0x21:return '0';case 0x38:return '3';case 0x34:return '6';case 0x32:return '9';case 0x31:return '#';default:return 'n';}}}return 'n';
}

代码的大体思路与3.1所述的一样,(GPIO_ReadInputData(GPIOB) & keyDefault) == keydata 这局代码是为了在去除其他IO口干扰后再进行数据的比较。

3.3密码锁

密码锁的大致原理是,通过矩阵按键将输入的数据存入数组中,随后进行密码的自动校验,校验的方法使用strcmp() 函数。代码如下

int main(void)
{/*模块初始化*///GPIOinit...uint16_t keys_scan_count = 0;        //按键输入次数char keys;                            //密码储存临时区域char keys_data[KeyWords_MAX]={0};    //密码储存数组  KeyWords_MAX为5char *openkeys = "13258";            //密码while(1){keys = MatrixCheck2();if(keys != 'n')                                       //检测不为非法输入{if(keys == '#')                                  //此处 # 用作清空输入{memset(keys_data,0,strlen(keys_data));        //清空数组GPIO_Write(GPIOA,0x00);                       //关闭数码管GPIO_SetBits(GPIOC,GPIO_Pin_13);         //关闭LEDkeys_scan_count = 0;                        //计数清0continue;}GPIO_Write(GPIOA,regLed[keys-48]);              //数码管显示当前输入数,字符串1对应的ASCII为48,所以强转HEX需要-48keys_data[keys_scan_count] = keys;              //储存输入密码if(strcmp(keys_data,openkeys) == 0) GPIO_ResetBits(GPIOC,GPIO_Pin_13);        //点亮LED灯keys_scan_count++;                                //输入次数+1if(keys_scan_count >=strlen(openkeys))keys_scan_count = 0;    //大于最大密码数清0}keyDelay(100);}
}

从代码角度来理解就是,keys获取按键输入,随后进行字符检测,因为当无输入时MatrixCheck2()的返回值是'n',当不为'n'时代表有按键按下,进入下一步对键值检测是否为特殊字符'#',若是则清空输入数组,否则储存输入键值。

按键检测 提取码:8866

往期内容

STM32F103基于固件库创建工程模板

STM32F103 GPIO之点亮一个LED灯

STM32F103 GPIO之按键检测相关推荐

  1. linux 按键检测 防抖,GPIO输入——按键检测

    当按下一个按键时,系统是如何检测到的呢? 我们通过LED灯的亮灭状态来间接完成按键检测.当按下按键时,LED灯亮,再次按下时,LED灯灭. 要完成这个实验,我们就会用到GPIO外设的基本输入功能. 查 ...

  2. 【STM32】GPIO输入—按键检测

    Author:AXYZdong 自动化专业 工科男 有一点思考,有一点想法,有一点理性 文章目录 2.1硬件设计 2.2软件设计 2.2.1编程要点 2.2.2代码分析 1.按键引脚宏定义 2.按键 ...

  3. 11、GPIO输入—按键检测

    文章目录 0.前言 1.硬件设计 2.软件设计 2.1.编程要点 2.2.代码分析 2.2.1.按键引脚宏定义 2.2.2.按键 GPIO 初始化函数 2.2.3.检测按键的状态 2.2.4.主函数 ...

  4. STM32——GPIO输入——按键检测

    硬件介绍 当按键置空时,IO接地 按键按下之后,IO口接通3.3V高电压,电流比较大,为了避免损坏IO,这里需要加装一个限流电阻.可以看到IO口是默认低电平,按键按下后产生一个上升沿,和平常的电路设计 ...

  5. stm32实现GPIO输入按键检测

    1.硬件设计 按键机械触点断开.闭合时,由于按键触点的弹性作用,按键开关不会马上稳定接通或一下就断开,使用按键时就会产生下图中的带纹波信号,需要软件消抖处理滤波 由于用软件消抖处理滤波不方便输入检测, ...

  6. GPIO 输入—按键检测

    这里要用到一定的模电知识.电容两端电压不能突变,电感两端电流不能突变.这里利用了电容的放电延时实现硬件消抖.按键按下会有抖动,波形有毛刺,使得高低电平显现不明显,而按键按下时,电容放电一下,马上又被充 ...

  7. STC8H1K08 - GPIO 按键检测

    文章目录 不使用硬件或软件消抖的按键检测 原理图 Keil 工程结构 源文件 参考 STC8H 系列单片机所有的 I/O 口均有 4 种工作模式:准双向口/弱上拉(标准 8051 输出口模式).推挽输 ...

  8. Proteus仿真STM32F103R6微控制器的GPIO(按键控制LED开关)

    Proteus仿真STM32F103R6微控制器的GPIO,检查按键,控制LED灯的反转.. 输入:按键检测:输出:高低电平,控制LED. 一.原理图: 二.源码: #include "st ...

  9. Openwrt按键检测分析-窥探Linux内核与用户空间通讯机制netlink使用

    首先看一下Openwrt系统中关于按键功能的使用和修改,以18.06版本为例 按键功能实现在脚本中, 比如18.06/package/base-files/files/etc/rc.button/re ...

最新文章

  1. 阿里达摩院已经研发出第一个可控的量子比特
  2. Terraform入门 - 3. 变更基础设施
  3. 驱动保护中的ObjectType_Callback探索
  4. C#编写简单的聊天程序
  5. mongodb 排序_技术分享 | MongoDB 一次排序超过内存限制的排查
  6. 陈艳青(为奥运冠军名字作诗)
  7. android改包名
  8. 设计模式(Design pattern—
  9. shell 数组详解
  10. MongoDB 在windows shell环境下的基本操作和命令的使用示例(一)
  11. Centos7下载linux内核源码
  12. html图片不要角,详细解说CSS折角的制作(不用图片)
  13. Cesium结合kriging.js制作降雨等值面
  14. python三行代码抠图_Python用5行代码如何实现批量抠图 Python用5行代码实现批量抠图方法...
  15. 真的是没有底线了,重新认识Java
  16. 萌宠历险记html5游戏在线玩,7724萌宠历险记
  17. 拥有WiFi 6+的华为路由 AX 3 Pro 到底香不香
  18. C语言或less或sass中,ceil floor 无法传入动态变量取整的办法
  19. 盘一盘那些开设了大数据专业的中国高校
  20. 《MySQL高级篇》三、存储引擎

热门文章

  1. 2021最新 阿里云搭建饥荒专用服务器
  2. 虚拟服务器部署网站特别慢,虚拟主机的网站为什么时快时慢?
  3. n9 android,Meego变安卓,诺基亚 N9 (2020) 5G概念手机亮相
  4. cousera of pku:魔兽世界:行军
  5. 開始EEPlat之旅
  6. 什么是 TCP 粘包/拆包
  7. 越狱后天气闪退 iPhone5天气闪退解决方法
  8. 【转】创业者最容易被忽悠的五大方面
  9. 收集到的创业者的事迹、文章、等等
  10. Trie树沉思录(1)