STM32入门教程-2023版【3-4】按键控制制LED

news2024/11/24 14:52:37

关注 + 点赞    不错过精彩内容

图片

大家好,我是硬核王同学,最近在做免费的嵌入式知识分享,帮助对嵌入式感兴趣的同学学习嵌入式、做项目、找工作! 

这篇文章以项目代码的形式实现GPIO输入

一、按键控制LED

(1)搭建面包板电路

根据接线图接线,两个按键分别接B1、B11,两个LED接A1、A2,按键一端接GPIO口,一端接GND,就是上一章第一种的接按键的方法,LED一接GPIO口,一端接VCC,就是低电平点亮的接法。这些按键和按键和GPIO端口连接都是随意的,具体接多少个,哪个端口,哪个外设都看自己的需求。

图片

(2)新建工程

打开工程文件夹,复制一下蜂鸣器工程的代码,改个名字叫 3-4 按键控制LED。

打开工程后,此时我们需要完成LED和按键的驱动代码,但把两个混在主函数里,就会太乱,也不好移植,所以这次选择将驱动代码封装起来,单独放在.c和.h文件里,这就是模块化编程的方式。

图片

想封装代码,可以打开工程文件夹,再新建一个文件夹,叫Hardware,用来存放硬件驱动

图片

回到keil,也添加一个Hardware的组

图片

再添加一个Hardware的头文件路径

图片

图片

(3)封装LED代码

在左边的Project中右键Hardware组,添加一个LED的C文件,这个文件就是封装LED的程序

图片

再右键Hardware组,添加一个LED的.H头文件,这个文件就是封装LED的程序

图片

这样在Hardware中我们就有了LED.c和LED.h两个文件用来封装LED的驱动程序。LED.c用来存放驱动程序的主体代码,LED.h用来存放这个驱动程序可以对外提供的函数或变量的声明

这两个文件建好以后还需要添加一些必要的代码使其可以正常使用,首先在LED.c文件中右键,include一个stm32f10x的头文件

图片

在在LED.h文件中添加防止头文件重复包含的代码

图片

在LED.c文件中,我们首先需要一个LED的初始化函数,则可以写成如下形式

图片

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

开启GPIOA端口的时钟,使能对该端口的访问。

GPIO_InitTypeDef GPIO_InitStructure;

定义一个名为GPIO_InitStructure的结构体变量,用于配置GPIO引脚的属性。

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

将GPIO引脚的模式设为推挽输出,表示该引脚可以输出电平。

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;

将GPIO引脚的第1和第2引脚设置为待初始化。

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

设置GPIO引脚的速度为50MHz,表示引脚的最大切换速率为50MHz。

GPIO_Init(GPIOA, &GPIO_InitStructure);

根据上述配置的属性初始化GPIOA端口的引脚。第二个参数是结构体变量名,前面加上取地址的符号,这里使用的是地址传递。

void LED_Init(void){        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启时钟        //配置端口模式        GPIO_InitTypeDef GPIO_InitStructure;//定义一个结构体变量        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_Init(GPIOA, &GPIO_InitStructure);}//这个函数是用来初始化LED的

因为这个函数是需要被外部引用的,所以我们需要将这个函数名复制到LED.h的文件中,后面不要忘了加分号。这样就是对模块外部声明,这个函数是可以被外部调用的函数

图片

此时,我们可以回到main.c,把上一个实验蜂鸣器的这些代码删掉,再包含LED模块的头文件,之后在主函数里,直接调用LED_Init,这样就完成了LED的初始化。

在这条代码前,有提示一个警告,这是因为我们新写的代码还没有更新,软件还不知道有这个函数,编译一下就会显示0错误0警告,有时候这个软件也会时不时报个警告或错误,这个有可能是语法检查更新较慢,直接编译一下,没有问题就行了

图片

将此代码编译下载后,可以看到LED灯已经点亮了,这说明我们的代码里写的端口配置和模块化编程是没有问题的

图片

因为GPIO配置好了之后默认就是低电平,所以我们还没操作LED,LED就亮起来了,那我们可以在LED_Init函数的最后加上GPIO_ResetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);这样初始化之后,如果不操作LED,LED就是熄灭的了

图片

 
void LED_Init(void){        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启时钟        //下面配置端口模式        GPIO_InitTypeDef GPIO_InitStructure;//定义一个结构体变量        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_Init(GPIOA, &GPIO_InitStructure);    //初始化        GPIO_ResetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);//GPIO配置好后默认是低电平}//这个函数是用来初始化LED的

将此代码编译下载后,可以看到LED灯已经熄灭了,这样初始化之后LED默认就是关闭的状态了,这样只需调用LED_Init();两个LED的两个GPIO口就初始化完成了

除了初始化,我们还需要点亮和熄灭LED的函数,在LED.c文件中可以加上

void LED1_ON(void){         GPIO_ResetBits(GPIOA, GPIO_Pin_1);} void LED1_OFF(void){         GPIO_SetBits(GPIOA, GPIO_Pin_1);}void LED2_ON(void){         GPIO_ResetBits(GPIOA, GPIO_Pin_2);} void LED2_OFF(void){         GPIO_SetBits(GPIOA, GPIO_Pin_2);}

图片

这里用了4个函数来实现两个灯的打开和关闭,如果你觉得函数太多了,那你也可以定义一个LED_Set函数,包含两个参数,一个参数选择操作哪个灯,另一个参数选择开还是关,这样写复用性更高,推荐使用这种写法。

这里同时记得去LED.h文件里面声明这四个函数

void LED1_ON(void); void LED1_OFF(void);void LED2_ON(void); void LED2_OFF(void);

图片

下面接着实现LED闪烁,直接在主函数调用,编译后下载这样LED就在交替闪烁了

图片

模块化编程的好处:将驱动代码封装起来,使得主函数中代码变得更加简洁,初始化就是初始化,开灯就是开灯,关灯就是关灯,不需要再管那些底层的各种参数了

(4)封装按键代码

和LED一样,我们可以把按键也封装到驱动函数模块中,右键Hardware组,添加一个Key的C文件,记得Key.c文件中右键添加stm32f10x的头文件

图片

再右键Hardware组,添加一个Key的.H头文件,记得添加防止头文件重复包含的代码

图片

图片

首先在Key.c文件中写一个Key_Init初始化函数,可以写成如下形式

图片

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

函数使能GPIOB的时钟。

GPIO_InitTypeDef GPIO_InitStructure;

定义一个名为GPIO_InitStructure的结构体变量,用于配置GPIO引脚的属性。

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

将GPIO引脚的模式设为上拉输入模式,在上拉输入模式下,当按钮未按下时,GPIO引脚上的电压会被拉高到VDD电压。

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;

将GPIO引脚的第1和第11引脚,也就是按键所连的引脚,设置为待初始化。

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

设置GPIO引脚的速度为50MHz,表示引脚的最大切换速率为50MHz。

GPIO_Init(GPIOB, &GPIO_InitStructure);

根据上述配置的属性初始化GPIOB端口的引脚。第二个参数是结构体变量名,前面加上取地址的符号,这里使用的是地址传递。

 
void KEY_Init(void){        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);                GPIO_InitTypeDef GPIO_InitStructure;        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//需要读取按键,所以选择上拉输入        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //这里是输出速度,在输入模式下其实没有用        GPIO_Init(GPIOB, &GPIO_InitStructure);}

接着再写一个读取按键值的函数,调用这个函数,就可以返回按下按键的键码,它的返回值是uint8 t,就是unsigned char的意思,按键键码默认给0,如果没有按键按下,就返回0。

图片

在这个函数中我们需要用到特殊的GPIO库函数,可以从gpio.h中找一下GPIO的库函数文件,选中的这几个函数是GPIO的读取函数,第一个函数GPIO_ReadInputDataBit是用来读取输入数据奇存器某一个端口的输入值的,它的参数是GPIOx和GPIO_Pin,用来指定某一个端口,返回值是uint8_t,代表这个端口的高低电平,读取按键我们就需要用到这个函数

图片

再看看其他的几个GPIO的读取函数,第一个函数上面刚刚用过,用来读取数据输入寄存器某一个端口的输入值,读取按键要用到这个函数

 
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

第二个函数用来读取整个数据输入寄存器的,参数只有一个GOIOx,用来指定外设,返回值是uint16_t,是一个16位的数据,每一位代表一个端口值

 
*uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);

第三个函数是用来读取输出数据寄存器的某一个位,所以原则上来说,它并不是用来读取端口的输入数据的,这个函数一般用于输出模式下,用来看自己输出什么,具体有什么用,下面可以给大家演示一下

 
*uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

第四个函数用来读取整个数据输出寄存器的

 
*uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);

上面的这四个函数就是用来读取下面这个图中的输出和输入寄存器,GPIO_ReadInputDataBit读取输入数据寄存器的某一位,GPIO_ReadInputData读取整个输入数据,GPIO_ReadOutputDataBit读取输出数据寄存器的某一位,GPIO_ReadOutputData读取整个输出数据。所以如果想读取GPIO口的数据,就需要用Readlnput的这两个函数,如果在输出模式下,想要看一下现在输出了什么,才需要用到ReadOutput的这两个函数

图片

这里我们需要读取外部输入的端口值,所以可以这样写GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1);用于读取PB1的端口值

图片

这个函数的返回值就是输入寄存器某一位的值,0代表低电平,1代表高电平,这时我们可以加上if判断,如果读取PB1端口值等于0,就代表按键按下,我们进入if里操作

图片

此时按键刚按下,电平不稳定会有个抖动,所以需要Delay一段时间,消一下抖,在这里加上Delay_ms(20); 也不要忘了在Key.c文件中添加Delay.h的头文件

图片

接着我们还需要检测一下接键松手的情况,因为我们的按键一般是松手之后才有动作的,所以在这里加上一个while循环,判断条件还是读取PB1是否等于0

图片

如果按键一直按下,就卡在这里,直到松手,此时电平又会发生抖动,再Delay_ms(20); 消一下按键松手的抖动

图片

接着我们赋值KeyNum=1;用这个变量将键码1,传递出去 ,这就是PB1按键的检测

图片

PB11的按键,也是一样,可以直接复制粘贴

图片

uint8_t KEY_GETNUM(void)  //uint8_t相当于unsinged char{           uint8_t KEY_NUM = 0 ;  //如果按键没有按下,默认给0       if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0 ){           Delay_ms(20);  //需要用到delay函数,头文件还需加上#include "Delay.h"           while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0 );//按键一般是松手才有动作,所以加上判断           Delay_ms(20); //同样是消抖           KEY_NUM = 1;           }       if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0 ){           Delay_ms(20);           while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0 );           Delay_ms(20);           KEY_NUM = 2;           }        //这段实现的功能就是,按键1按下LED1点亮,按键2按下LED2熄灭        return KEY_NUM;}

记得在Key.h中声明一下这两个函数

图片

下面验证一下写的按键,到main.c中,添加"Key.h"头文件,然后主循环之前,初始化一下按键

图片

接着我们定义一个全局变量KeyNum,用来存一下键码的返回值,这里注意一下,我们这个也叫KeyNum和Key.c中的不是同一个!main.c中的是全局变量,Key.c中的是局部变量,两者作用域不一样。即使在main函数中,再定义一个同名的KeyNum,这三个都是不一样的,main函数中的也是一个局部变量。在函数外面的是全局变量,每个函数都可以使用,在函数里,优先使用自己的局部变量,我们就用这个全局变量来获取返回值

图片

下面我们在while中实现当按下按键1,LED1点亮,当按下按键2,LED1熄灭

图片

下载编译后,可以正常实现,证明这两部分的代码模块实现成功了

图片

那如果要实现单独一个按键按一下熄灭,再按一下点亮,该如何实现呢?这就需要用到GPIO_ReadOutputDataBit();函数,在LED.c里加上下面这段函数,使LED的状态取反,此时当按键1按下,LED1就会取反

图片

这个函数逻辑就是,调用GPIO ReadOutputDataBit函数,读取当前的端口输出状态,如果当前输出0,就给LED置1,否则,就置0,这样就实现了端口的电平翻转

void LED1_Turn(void){        if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)//读取当前端口输出状态        {         GPIO_SetBits(GPIOA, GPIO_Pin_1);        }        else GPIO_ResetBits(GPIOA, GPIO_Pin_1);}//实现了端口的电平反转

那我们可以复制一下这个函数,给按键2和LED2也加上翻转的功能

图片

void LED2_Turn(void){        if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0)//读取当前端口输出状态        {         GPIO_SetBits(GPIOA, GPIO_Pin_2);        }        else GPIO_ResetBits(GPIOA, GPIO_Pin_2);}//实现了端口的电平反转

最后记得把这两个函数放到头文件里声明一下

图片

此时可以在main中实现按下按键1按一下LED1点亮,再按一下LED1熄灭,按键2按一下LED2点亮,再按一下LED2熄灭

图片

图片

最后注意一下,这个驱动函数模块写好之后,尽量在这些函数的上面加上一些注释,尽量在这些函数的上面加上一些注释,这样,别人在使用你的函数驱动时,才知道如何使用,就像STM32的库函数一样,在每个函数上面,写一下这样的注释,自己写代码时也尽量打打注释,这样方便自己和别人理解

作 者 :硬核王同学

------- END -------

关注公众号回复“加群”按规则加入技术交流群  回复“1024”查看更多内容

图片

觉得有用请点个免费的赞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1372885.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java中输入和输出处理(三)二进制篇

叮咚!加油!马上学完 读写二进制文件Data DataInputStream类 FilFeInputStream的子类 与FileInputStream类结合使用读取二进制文件 DataOutputStream类 FileOutputStream的子类 与FileOutputStream类结合使用写二进制文件 读写二进制代码 package 面…

国产AI工具钉钉AI助理:开启个性化助手服务的新篇章

钉钉AI助理是钉钉平台的一项功能,它可以根据用户的需求提供个性化的AI助手服务。用户可以在AI助理页面一键创建个性化的AI助理,如个人的工作AI助理、旅游AI助理、资讯AI助理等。企业也可以充分使用企业所沉淀的知识库和业务数据,在获得授权后…

文字转语音在线合成系统源码 附带完整的安装部署教程

现如今,文字转语音(TTS)技术逐渐成为人们获取信息的重要手段之一。然而,市面上的TTS工具大多需要下载安装,且功能较为单一,无法满足用户多样化的需求。因此,开发一款功能强大、易于部署的文字转…

共享wifi项目如何加盟?

共享wifi贴项目如何加盟呢?具体的途径在哪里,费用是多少呢?今天小编就来一次性同你讲清楚。 我们先来讲一下共享wifi贴的加盟方法。 首先,找到共享wifi的官方渠道在点击右上角,根据页面上的信息填写资料。 然后&…

长期使用外接键盘,外物压着自带键盘,容易导致华硕飞行堡垒FX53VD键盘全部失灵【除电源键】

华硕飞行堡垒FX53VD键盘全部失灵【除电源键】 前言一、故障排查二、发现问题三、使用方法总结 前言 版本型号: 型号 ASUS FX53VD(华硕-飞行堡垒) 板号:GL553VD 故障情况描述: 键盘无法使用,键盘除开机键外…

每日学习更新(LQR+iLQR)

一直想更新一下根据cost to go来推导LQR,之前的话可能会直接套问题,但是对于理论有些困惑,正好最近在学习ilqr轨迹生成/优化,因此来推一下公式,以下参考B站Dr_CAN,链接如下: 【最优控制】5_线性…

基于arcgis的遥感深度学习数据集制作

由于很多时候,我们在研究过程中往往需要根据实际情况使用自己的影像数据来提取目标物,如果没有合适的公开数据集的话,为了满足实际需要,我们就需要制作符合自己要求的数据集。 今天我们就根据实际情况来详细讲解如何利用arcgis&am…

视频分割软件,视频批量分割,轻松搞定

有时候,为了特定的需求,我们需要对视频进行分割,传统的方法是使用视频编辑软件,逐个进行操作,费时又费力。那么,有没有一种方法可以批量分割视频,轻松高效地完成任务呢?这个确实是有…

虚拟机安装intel架构的银河麒麟V10(SP1)

一 背景 银河麒麟是国产操作系统之一,是基于Linux内核的桌面操作系统,有自己的应用中心,具有一定的生态系统。今从官网下载了V10(SP1)镜像文件,在Windowns的VMware虚拟机上安装试用。 官网:http…

JMeter之Windows安装

JMeter之Windows安装 一、安装JDK二、安装JMeter1、下载JMeter2、配置环境变量3、验证JMeter 三、扩展知识1、汉化 一、安装JDK 略 二、安装JMeter 1、下载JMeter 官网地址:https://jmeter.apache.org/download_jmeter.cgi 放到本地目录下 2、配置环境变量 变量…

RTX20系开启超分辨率

我的显卡是2070s现在也支持了超分辨率,根据网上的教程一通折腾后发现了不少的坑,记一下.希望有缘人可以少走点弯路. 机器配置如下: [CPU] CPU #1: 3600MHz, AMD Ryzen 7 3700X 8-Core Processor , AMD64 Family 23 Model 113 Stepping Instruction set: MMX MMX…

Camunda外部任务

外部任务&#xff1a;即任务可以在外部系统进行审批。 一&#xff1a;pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instan…

大模型LLM在 Text2SQL 上的应用实践

一、前言 目前&#xff0c;大模型的一个热门应用方向Text2SQL&#xff0c;它可以帮助用户快速生成想要查询的SQL语句&#xff0c;再结合可视化技术可以降低使用数据的门槛&#xff0c;更便捷的支持决策。本文将从以下四个方面介绍LLM在Text2SQL应用上的基础实践。 Text2SQL概…

22、Kubernetes核心技术 - 整合Rancher通过界面管理k8s集群

目录 一、概述 二、Rancher API Server 的功能 2.1、授权和角色权限控制 2.2、使用 Kubernetes 的功能 2.3、配置云端基础信息 2.4、查看集群信息 三、Rancher 安装 3.1、前置环境 3.2、通过 Docker 来进行安装Rancher 3.3、在 Rancher 的界面上绑定k8s集群 3.4、在 …

redis 主从同步和故障切换的几个坑

数据不一致 当我们从节点读取一个数据时&#xff0c;和主节点读取的数据不一致&#xff0c;这是因为主从同步的命令是异步进行的&#xff0c;一般情况下是主从同步延迟导致的&#xff0c;为什么会延迟&#xff0c; 主要二个原因 1、网络状态不好 2、网络没问题&#xff0c;从节…

java 项目使用 activi 设计流程,流程线上设置条件表达式时出现以下错误

项目场景&#xff1a; 背景&#xff1a; java 项目使用 activi 设计流程&#xff0c;流程线上设置条件表达式后&#xff0c;保存时出现错误 问题描述 问题&#xff1a; java 项目使用 activi 设计流程&#xff0c;流程线上设置条件表达式后&#xff0c;保存时出现以下错误&…

Matlab 之数据分布拟合

文章目录 Part.I IntroductionPart.II Distribution Fitter APP 的使用Chap.I APP 简介Chap.II 简单使用 Part.III 通过代码实现分布拟合Chap.I 基于 fitdist 函数Chap.II 获取数据的频率分布后进行曲线拟合 Reference Part.I Introduction 本文主要介绍了如何使用 Matlab 对数…

Linux学习记录——삼십팔 网络层IP协议

文章目录 1、了解IP协议2、IP协议报文1、8位服务类型2、16位总长度&#xff08;字节数&#xff09;3、8位生存时间&#xff08;TTL&#xff09; 3、网段划分1、网段划分和CIDR方案2、子网划分简单方法3、IP地址问题的解决方案 4、公网内网1、内网分配2、运营商管理方法 5、路由…

2024年3月10日PMI认证考试的报名时间确定!

⏰中国大陆地区2024年第1期PMI认证考试于3月10日举办 ⏰报名时间&#xff1a; 为减少同一时间集中报名造成的网络拥堵&#xff0c;本次报名将采取以下形式分地区、分批次开放报名。&#x1f447; 1️⃣第1批报名城市&#xff1a;⏰2024年1月11日10:00至1月18日16:00&#xff0c…

【昕宝爸爸定制】如何将集合变成线程安全的?

如何将集合变成线程安全的? ✅典型解析&#x1f7e2;拓展知识仓☑️Java中都有哪些线程安全的集合&#xff1f;&#x1f7e0;线程安全集合类的优缺点是什么&#x1f7e1;如何选择合适的线程安全集合类☑️如何解决线程安全集合类并发冲突问题✔️乐观锁实现方式 (具体步骤)。✅…