嵌入式学习笔记——使用寄存器编程实现按键输入功能

张开发
2026/4/12 9:56:04 15 分钟阅读

分享文章

嵌入式学习笔记——使用寄存器编程实现按键输入功能
文章目录前言模块介绍原理图编程思路检测IO口的电平Debug调试按键扫描函数延时函数功能实现总结M4系列目录前言昨天通过配置通用输出模式实现了LED灯的点亮、熄灭以及流水等操作解决了通用输出的问题今天我们再借用最常见的输入模块按键来实现一个按键控制LED的功能重点是配置GPIO为输入模式以及如何检测GPIO的输入电平。模块介绍原理图笔者用的这款最小系统有三个独立按键可以操作首先第一步还是看原理图来确定我们需要使用的端口和管脚可以看出K_UP使用的是PA0、K0使用的是PE4、KEY1使用的是PE3。注意观察这三个按键的电路其中KEY0和KEY1是没有上拉电阻的只有按下按键直接接地这一个电平模式这个我们在前面讲解GPIO模式的时候提到过如果没有外部上拉的电路想要实现高低电平的检测需要在内部编程实现上下拉这两个按键就是需要使用到内部上拉使得默认PE4、PE3端口默认是高电平也就是1只有按键按下才会被拉到低电平也就是0。而K_UP刚好相反只有上拉电路按下按键是高电平不按下的时候应该要其默认状态是低也就是说需要我们为其配置下拉。编程思路在看清楚检测原理后就需要理清编程思路根据昨天的按键技巧来首先需要新建文件命名保存key.c存在src文件夹下key.h存在inc文件夹下然后将Key.c添加到工程再然后是定义头文件编写初始化函数。编写初始化代码伪代码①编写注释/****************************************************************************函数名 :Key_Init*函数功能 :按键所用的管脚的初始化配置*函数参数 :无*函数返回值:无*函数描述 :KEY_UP------PA0------通用输入模式默认状态采取内部下拉按下按键为高电平K0----------PE4------通用输入模式默认状态采用内部上拉按下按键为低电平K1----------PE3------通用输入模式默认状态采用内部上拉按下按键为低电平***************************************************************************/②初始化函数void Key_Init(void){③使能对应端口的时钟有两个一个是GPIOA昨天用过一个是GPIOEGPIOA对应第0位GPIOE对应第4位。先在数据手册查其挂接的时钟总线然后再再第六章RCC找到对应使能进行配置④设置对应管脚的模式为通用输入模式分两组分别配置A0应该配置GPIOA的MODER 0 1两位写入00E3E4对应GPIOE的MODER的9 8 7 6 位也都应该写入0000⑤设置上下拉其中PA0设置为下拉模式应该对GPIOA的PUPDR 的1 0两位写入10PE4,PE3则应该将GPIOE的PUPDR 寄存器的9 8 7 6 位写入0101。}好了可以发现整个配置过程比昨天的输入配置稍微简单一点而且昨天输出使用的寄存器在按键输入上都是没有用上的。接下来来看看代码吧。//注释voidKey_Init(void){//打开AHB1上GPIOA端口RCC-AHB1ENR|(10);//打开GPIOE端口对应的AHB1时钟RCC-AHB1ENR|(14);//配置GPIOA0为通用输入模式GPIOA-MODER~(30);//清0 GPIOA_MODER寄存器为00通用输入模式GPIOA-PUPDR~(30);//清0 GPIOA_PUPDR寄存器为00 浮空GPIOA-PUPDR|(11);//清0 GPIOA_PUPDR寄存器为10 下拉GPIOE-MODER~(0XF6);//通用输入GPIOE-PUPDR~(0XF6);//清零GPIOE-PUPDR|(0X56);//写入0101配置为上拉模式}检测IO口的电平在GPIO做输出的时候是通过对对应端口的ODR寄存器的对应位进行写0与写1来实现输出低电平和高电平的很明显如果需要获取GPIO的输入状态肯定只能采取读的方式那么该怎么读取呢参照输出肯定会有一个对应的输入数据寄存器用来获取GPIO的状态前面介绍GPIO寄存器的时候提到过一个叫做IDR的寄存器它的作用就是存储GPIO的高低电平的。也就是说在获取输入信号时需要直接操作的就是这个IDR寄存器的对应位。其中Key_Up使用的是GPIOA-IDR的第0位K0使用的是GPIOE-IDR的第四位K1使用的是GPIOE-IDR的第三位可以发现这个寄存器本身就是只读的所以在编程的时候就不能对其使用‘‘赋值语句。由于需要获取IDR对应位的高和低所以考虑使用 操作在对应位 上1当寄存器内部对弈位是1则输出1当寄存器内部对应位是0则输出0具体的实现方式如下#defineKEY_UP(GPIOA-IDR(10))//Key_Up的状态#defineKEY0(GPIOE-IDR(13))//KEY0的状态#defineKEY1(GPIOE-IDR(14))//KEY1的状态采用宏定义的方式这样通过条件判断KEY1、KEY0、KEY_UP是否为真即可知道按键是否按下其中由于KEY1、KEY0是默认上拉未按下时是高电平此时KEY1与KEY0是非0只有按下按键才是低电平此时KEY1和KEY0是0而KEY_UP默认是下拉状态默认是低电平也就是KEY_UP是0只有当按键按下GPIO才变成高电平KEY_UP是非0。宏定义记得放回到Key.h的头文件中。然后在主函数进行调用(记得添加Key.h进入main.h)。1.检测按键状态肯定是需要实时刷新才能检测到因此有关判断必须放在While(1)主循环中而不是上方的单次运行区2.三个按键的检测原理是不同的KEY0与KEY1是需要检测低电平所以判断按下的语句是if(!KEY0)和if(!KEY1)而KEY_UP需要检测的是高电平所以语句是if(KEY_UP);3.为了看见效果需要定义三个变量Key_Up、k0、k1三个变量使用ST-LINK仿真用iWatch查看数据当然也可以使用LED灯4.使用一个模块之前一定要在初始化区调用对应模块的初始化函数。main.c的代码#includemain.hu8 Led_Speed5;u8 Key_Up0;u8 k00;u8 k10;intmain(void){/*------------------变量定义区--------------------------*//*------------------初始化外设区------------------------*/Led_Init();Key_Init();/*------------------单次运行区--------------------------*/while(1)//防止程序跑飞{/*------------------主循环区--------------------------*/if(KEY_UP)Key_Up;if(!KEY0)k0;if(!KEY1)k1;}}按照上面的步骤和位置添加好代码后编译通过即可接下来开始使用iWatch调试。Debug调试编译通过后点击1的位置等待软件加载进入如下界面这是调试与仿真的界面为了直观地看见效果现将上面定义的Key_Up、k0、k1添加到Watch中这个框的作用就是可以方便查看仿真过程中变量的值添加步骤如下图所示根据上面代码的逻辑应该是按下对应按键这些值会对应自增1按照想象来说应该是按下一次数值自增1实际效果会是这样吗。如下图所示可以发现实际效果并不是按下一次按键变量自增一而是按下一次自增了几十甚至几百很明显这是有问题的。那么产生这个现象的原因是什么呢其实就是前面提到过的主频问题按照我们的代码逻辑只要检测到对应IO是高或者是低就自增由于STM32F4的主频达到了168MHZ也就是说它1秒钟可以跑完168M条机器指令当我们按下按键的时候它就开始增加一直到我们松开按键对于肉眼和感觉来说是很短暂但是这个时间单片机已经重复运算很多次了这就会出现上面图片里的现象那么要怎么解决这个问题呢。这就需要使用到按键扫描函数了。按键扫描函数出现上面的问题是因为在按下按键后if()的条件在一段时间内一直是真导致变量一直自增那么怎么解决一直自增的问题呢这时候前辈们想到了利用检测松手以及标志位锁住来解决这个问题先看看代码/******************************************* *函数名 :Key_Scanf *函数功能 :按键扫描 *函数参数 :void *函数返回值:key_value键值 *函数描述 : 告诉主函数按下的是哪个按键。 判断案件是否按下并返回对应键值。 *********************************************/u8Key_Scanf(void){u8 key_value0;//初始键值staticu8 Key_Flag0;if(KEY_UPKey_Flag0)//判断按键是否按下{key_value1;//对按下的按键进行赋值Key_Flag1;}if(!KEY_UP)//判断按键是否松开{Key_Flag0;}returnkey_value;//返回键值}通过加加入了一个Key_Flag实现了对按键的判断锁定检测到按键按下后将标志位置位1这样即使整个按下期间KEY_UP一直是真由于后面的flag的限制也无法造成影响一直到检测到按键松开才重新释放Key_Flag这样也保证了下一次判断按键是否按下不受影响。在主函数调用然后重复上面的Debug仿真。![在这里插入图片描述](https://img-blog.csdnimg.cn/452ea8456e5f4f6f9277902350add3bd.png实现效果如下这次可以明显看出了是每次自增1但是还是有点问题就是有时候明明只按下了一次缺连着加了两三个数这是由于按键的物理抖动造成的。为了解决这个问题也有两种方式一种是通过在按键两端并联电容的方式硬件消除但是实际使用中很少有这么干的毕竟能省则省主要是因为另外一种方式是使用代码延时解决不需要物料成本。延时函数这里介绍一下STM32简单的延时函数。前面提到过STM32F407一秒钟可以运行168M条机器指令,1ms运行168 000条1us运行168条机器指令需要注意的是一条机器指令不等于一条C语句按照传言一条C语句可以近似等价于4条机器指令但是那毕竟是传言不一定准确。按照上面的逻辑我们让STM32F407单片机运行168条机器指令就可以得到一个近似1us的函数可是上哪找这个机器指令呢其实在工程中有一个机器指令它就是__nop于是有了如下的us延时与毫秒延时函数/******************************************* *函数名 :Delay_us *函数功能 :机器指令微妙延时函数 *函数参数 :微秒数u32 us *函数返回值:无 *函数描述 : 利用机器指令nop实现延时 1s---------168M条机器指令 1ms---------168 000 条机器指令 1us---------168条机器指令 *********************************************/voidDelay_us(u32 utime){while(utime--){//1us的延时时长__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();}}/******************************* 函数名Delay_ms 函数功能机器指令实现ms延时 函数形参u32 mtime 函数返回值void 备注 1s 168M 1MS 168k 1us 168 ********************************/voidDelay_ms(u32 mtime){while(mtime--){//1ms的延时Delay_us(1000);}}同样的为了方便后面管理也需要新建Delay.c与Delay.h然后将.c加入工程将.h包含进main.h的头文件。然后在Key_Scanf()函数中添加延时消抖并将剩下两个按键添加进来,最后的Key_Scanf函数如下所示/******************************************* *函数名 :Key_Scanf *函数功能 :按键扫描 *函数参数 :void *函数返回值:key_value键值 *函数描述 : 告诉主函数按下的是哪个按键。 判断案件是否按下并返回对应键值。 *********************************************/u8Key_Scanf(void){u8 key_value0;//初始键值staticu8 Key_Flag0;if(KEY_UPKey_Flag0)//判断按键KEY_UP是否按下{Delay_ms(10);//消抖if(KEY_UP){key_value1;//对按下的按键进行赋值Key_Flag1;}}elseif(!KEY0Key_Flag0)//判断按键KEY0是否按下{Delay_ms(10);//消抖if(!KEY0){key_value2;Key_Flag1;}}elseif(!KEY1Key_Flag0)//判断按键KEY1是否按下{Delay_ms(10);//消抖if(!KEY1){key_value3;Key_Flag1;}}if(!KEY_UPKEY0KEY1)//判断按键是否松开{Key_Flag0;}returnkey_value;//返回键值}功能实现通过上面的流程基本搞定了按键的输入检测接下里实现一个小的需求按下按键KEY_UP小灯1亮按下KEY0小灯2亮按下KEY1两个小灯一起灭。具体实现的main.c如下intmain(void){/*------------------变量定义区--------------------------*/u8 K_Value0;/*------------------初始化外设区------------------------*/Led_Init();Key_Init();/*------------------单次运行区--------------------------*/while(1)//防止程序跑飞{/*------------------主循环区--------------------------*/K_ValueKey_Scanf();switch(K_Value){case1:Key_Up;LED_1_ON;break;case2:k0;LED_2(1);break;case3:k1;LED_1_OFF;LED_2(0);break;default:break;}}}最终效果总结本文主要是记录将GPIO配置为输入模式检测按键输入的功能如有疑问欢迎提出另外文中如有不足也欢迎批评指正。M4系列目录1.嵌入式学习笔记——概述2.嵌入式学习笔记——基于Cortex-M的单片机介绍3.嵌入式学习笔记——STM32单片机开发前的准备4.嵌入式学习笔记——STM32硬件基础知识5.嵌入式学习笔记——认识STM32的 GPIO口6.嵌入式学习笔记——使用寄存器编程操作GPIO7.嵌入式学习笔记——寄存器实现控制LED小灯8.嵌入式学习笔记——使用寄存器编程实现按键输入功能9.嵌入式学习笔记——STM32的USART通信概述10.嵌入式学习笔记——STM32的USART相关寄存器介绍及其配置11.嵌入式学习笔记——STM32的USART收发字符串及串口中断12.嵌入式学习笔记——STM32的中断控制体系13.嵌入式学习笔记——STM32寄存器编程实现外部中断14.嵌入式学习笔记——STM32的时钟树15.嵌入式学习笔记——SysTick(系统滴答)16.嵌入式学习笔记——M4的基本定时器17.嵌入式学习笔记——通用定时器18.嵌入式学习笔记——PWM与输入捕获上19.嵌入式学习笔记——PWM与输入捕获下20.嵌入式学习笔记——ADC模数转换器21.嵌入式学习笔记——DMA22.嵌入式学习笔记——SPI通信23.嵌入式学习笔记——SPI通信的应用24嵌入式学习笔记——IIC通信

更多文章