STM32复习笔记(二):GPIO

目录

(一)Demo流程

(二)工程配置

(三)代码部分

(四)外部中断(EXTI)


(一)Demo流程

首先,板子上有4个按键,两颗灯,一个beep,所以设计一个demo如下:

1、按下KEY0,LED0输出翻转;

2、按下KEY1,LED1输出翻转;

3、按下KEY2,LED0和LED1输出翻转;

4、按下WK_UP,蜂鸣器输出翻转;

相关部分电路schematic如下:

 

此外,WK_UP接到PA0;

简单分析一下电路:两个LED为0点亮;按键三个为0有效,一个为1有效;蜂鸣器处有一个BJT放大电路,BEEP给1就导通,给0就截止。


(二)工程配置

按老样子配置好SYS,RCC以及时钟频率之后,开始配置引脚;首先找到PF9和PF10,配置为输出,并修改label为LED0和LED1;然后找到PE4、PE3、PE2、PA0,配置为输入,并修改label为KEY0、KEY1、KEY2、WK_UP;最后找到PF8,配置为输出,并修改label为BEEP:

接下来,给每个引脚配置初始化如下:

首先对于两个灯为0点亮,所以首先给1且上拉,先不点亮,待需要时再点亮;然后对于beep,因为给1为响,所以先给0,而电路图中已经默认帮我下拉了,所以我直接不需要下拉;最后对于按键,因为WK_UP是1有效,所以默认下拉,检测到1则表明按下,而KEY0~2是0有效,所以默认上拉,检测到0则表明按下;然后设置好相关路径等配置直接generate code即可。


(三)代码部分

首先进入到main.h,就会发现刚刚给引脚起的label名都被define在里面了:

接下来新建文件,勾选下图第一个选项,直接帮你创建相关头文件,无需勾选第二个,后面再在cmake中手动加入:

接下来,需要在cmake中使用include_directories()包含头文件路径,以及使用file()包含源文件路径(此外,如果再用cubemx生成代码的话,CmakeLists.txt文件是会被重新覆盖掉的,所以需要写入CmakeLists_template.txt中,就不会被覆盖):

再点击右键,选择重新加载cmake即可:

经过漫长的代码编写之后......终于写完了:

keyled.h:

#ifndef DEMO_GPIO_KEYLED_H
#define DEMO_GPIO_KEYLED_H
#ifdef __cplusplus
extern "C" {
#endif#include "main.h"//表示4个按键的枚举类型
typedef enum {KEY_NONE = 0,   //没有按键按下KEY0,KEY1,KEY2,WK_UP
}KEYS;#define KEY_WAIT_ALWAYS 0   //作为函数ScanPressedKey()的一种参数,表示一直等待按键输入#ifdef  LED0_Pin     //LED0
#define LED0_ON()       HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET)
#define LED0_OFF()      HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET)
#define LED0_TOGGLE()   HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin)
#endif#ifdef  LED1_Pin     //LED1
#define LED1_ON()       HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET)
#define LED1_OFF()      HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET)
#define LED1_TOGGLE()   HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin)
#endif#ifdef  BEEP_Pin     //Beep
#define BEEP_ON()       HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET)
#define BEEP_OFF()      HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET)
#define BEEP_TOGGLE()   HAL_GPIO_TogglePin(BEEP_GPIO_Port, BEEP_Pin)
#endifKEYS ScanPressedKey(uint32_t timeout);#ifdef __cplusplus
}
#endif
#endif //DEMO_GPIO_KEYLED_H

keyled.cpp:

#include "keyled.h"//轮询方式扫米奥4个按键,并返回按键值
//轮询方式扫描4个按键,返回按键值
//timeout单位ms,若timeout=0表示一直扫描,直到有键按下
KEYS ScanPressedKey(uint32_t timeout)
{KEYS  key = KEY_NONE;uint32_t  tickstart = HAL_GetTick();  //当前计数值const uint32_t btnDelay = 20;	//按键按下阶段的抖动,延时再采样时间GPIO_PinState keyState;while(true){
#ifdef	KEY0_Pin		 //如果定义了KEY0,就可以检测KEY0keyState = HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin); //低输入有效if (keyState == GPIO_PIN_RESET){HAL_Delay(btnDelay);  //前抖动期keyState = HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin); //再采样if (keyState == GPIO_PIN_RESET)return	KEY0;}
#endif#ifdef	KEY1_Pin		 //如果定义了KEY1,就可以检测KEY1keyState = HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin); //低输入有效if (keyState == GPIO_PIN_RESET){HAL_Delay(btnDelay);  //前抖动期keyState = HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin); //再采样if (keyState == GPIO_PIN_RESET)return	KEY1;}
#endif#ifdef	KEY2_Pin		 //如果定义了KEY2,就可以检测KEY2keyState = HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin); //低输入有效if (keyState == GPIO_PIN_RESET){HAL_Delay(btnDelay);  //前抖动期keyState = HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin); //再采样if (keyState == GPIO_PIN_RESET)return	KEY2;}
#endif#ifdef	WK_UP_Pin		 //如果定义了WK_UP,就可以检测WK_UPkeyState = HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin); //PE4=KeyLeft,低输入有效if (keyState == GPIO_PIN_SET)//注意这里默认是下拉{HAL_Delay(btnDelay);  //前抖动期keyState = HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin); //再采样if (keyState == GPIO_PIN_SET)return	WK_UP;}
#endifif (timeout != KEY_WAIT_ALWAYS)  //没有按键按下时,会计算超时,timeout时退出{if ((HAL_GetTick() - tickstart) > timeout)break;}}return	key;
}

主函数:

int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();while (1){KEYS curkey = ScanPressedKey(KEY_WAIT_ALWAYS);    //一直等待按键输入switch (curkey) {case KEY0:LED0_TOGGLE();break;case KEY1:LED1_TOGGLE();break;case KEY2:LED0_TOGGLE();LED1_TOGGLE();break;case WK_UP:BEEP_TOGGLE();break;}HAL_Delay(200);   //跳过后抖动}
}

以上,实现了轮询检测按键,从而控制LED及Beep。

工程链接:https://pan.baidu.com/s/11xCxqty3KRJD6cx0S65jWQ 
提取码:0xFF


(四)外部中断(EXTI)

但是,总所周知,轮询查询GPIO口是非常非常浪费cpu资源的,所以可以利用外部中断(EXTI,External Interrupt)来检测按键输入;设计一个demo如下:

1、按下KEY0,触发EXTI4,LED0输出翻转;

2、按下KEY1,触发EXTI3,LED1输出翻转;

3、按下WK_UP,触发EXTI0,LED0和LED1输出翻转;

4、按下KEY2,产生EXTI0软中断(SWIT),模拟按下WK_UP;

配置如下(因为KEY0~2为0有效,所以设置为falling edge触发且上拉;而WK_UP为1有效,所以设置为raising edge触发且下拉):

接下来配置NVIC。设置为2bit抢占优先级 & 2bit次优先级;抢占优先级:谁大可以立即抢占小的中断;次优先级:当抢占优先级一样时,优先执行次优先级大的,但是不能抢占同抢占级的,只能排队;当抢占优先级和次优先级都一样时,则FCFS(First Come First Serve);设置EXTI0,EXTI2,EXTI3,EXTI4的抢占优先级为1,2,1,1,次优先级为0,0,2,1(注意0为最高优先级,3为最低优先级),主要是为了观察同时发生中断时,高抢占优先级的中断能否如理论般正常抢占低抢占优先级的中断,还有就是抢占优先级相同时,次优先级高的是否先执行;如下图所示:

还有一点,设置外部中断的抢占优先级时不能设置为0,因为外部中断的回调函数中会用到HAL_Delay()函数来延时消抖,该函数实际上用的是SysTick嘀嗒计时器的中断,其抢占优先级为0,如果外部中断的抢占优先级也设置为0,那么SysTick嘀嗒计时器的中断就无法抢占外部中断(相同抢占优先级),这将会导致HAL_Delay()函数死循环,系统卡死。

接下来生成代码,可以观察到在stm32f4xx_it.c中,cubemx已经生成了外部中断的函数,切记函数名不能改,因为在启动的汇编文件.s中已经将它们定义好了,要保持二者一致(除非去改汇编,但是没必要):

观察每个EXTI_IRQHandler()函数,发现它们都调用了HAL_GPIO_EXTI_IRQHandler()函数,跳到定义会发现,该函数首先判断是否为中断触发,然后传入中断触发源,再调用HAL_GPIO_EXTI_Callback()外部中断回调函数(callback means 回调):

继续跟进代码可以看到,回调函数是一个__weak修饰的函数,而__weak是一个宏定义,表示为属性:__attribute__((weak)),也就是弱函数;弱函数需要用户自己重新实现,编译时编译器就会自动编译重新实现的函数而忽略弱函数,如果没有重新实现,则自动编译原来的弱函数;其中的UNUSER()是为了避免gcc编译警告:

重新实现的回调函数如下(随便写在哪个文件都行,反正编译器会找得到,不过最好写在相关文件中):

编译下载到板子中会发现结果有一点点不如预期,比如说按下WK_UP时,两个LED会翻转两次,这很明显就是触发了两次中断,但是代码里不是用了1s这么长的延时来消抖么?为什么还会有问题?问题就出在cubemx生成的代码中;观察下图可以发现,HAL_GPIO_EXTI_IRQHandler()函数先判断是否为中断,然后清除标志位,再调用中断回调函数,一般的中断流程这样处理没有问题,主要是为了硬件能及时响应下一次中断;但是对于检测按键输入EXTI就有问题了,因为按键的抖动会导致产生不止一次的外部中断,而先清除了第一次的中断标志位,再执行回调时,后面还有几个抖动的相同外部中断又来了,同样会产生中断标志位,而此时系统正在中断的回调中延时消抖,执行完第一次回调函数之后,cpu出来又发现还有一个中断标志位,将会再进行一次同样的外部中断。

当然理解了原理修改起来就不难,只需要将两行函数互换,当检测到外部中断时,立马执行中断回调,不在管外界还有多少个相同的外部中断均不理会,只有当回调函数执行完毕后,再清除中断标志,这样就避免了多次中断。如下图:

值得注意的是,当重新用cubemx生成代码后,这两行又默认变回原来的位置了,还要手动修改,,,这也是不算bug的bug吧。。。

完~


工程链接:https://pan.baidu.com/s/1Svj7bh_sRzLUYvGjNr4Q3A 
提取码:0xFF

以上均为个人学习心得,如有错误,请不吝赐教~

THE END

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

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

相关文章

外包做了3个月,技术退步明显。。。。。

先说一下自己的情况,大专生,17年通过校招进入广州某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

科普文章|一文了解平行链及其优势

平行链是一种可以连接到更规模的区块链网络(波卡)的独立区块链。不同于传统区块链(如比特币和以太坊)是孤立的并且无法在本地相互通信,平行链与其他平行链并行运行,并且相互可以无缝通信。平行链还使用波卡…

JS进阶-原型对象prototype

原型 原型就是一个对象,也称为原型对象 构造函数通过原型分配的函数是所有对象所共享的。 JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以我们也称为原型对象 这个对象可以挂载函数,对象…

Golang编译生成可执行程序的三种方法

目录 前言 正文 方法一、 方法二、 方法三、 结尾 前言 Golang是一种强类型、编译型、跨平台的编程语言,相同代码在不同平台上都可以编译出对应的可执行程序。今天就来简单介绍一下如何使用命令编译出可执行程序,本文以windows平台为例进行介绍。 …

关于在 Notion 中使用 Markdown 语法

关于在 Notion 中使用 Markdown 语法 习惯使用的 Markdown 的伙伴们应该知道,当需要加粗字体时,会首先输入 ** **,然后在里面填内容。 但是在 Notion 中,这个就不太行了。它所定义的规则是从前往后,也就是先键入**&…

1.软件测试基础

一、软件测试概念 1.什么是软件 软件是计算机程序,是由计算机代码编写的一系列指令和数据,可以实现各种功能。它指的是计算机系统中的应用程序,包括操作系统、应用软件、驱动程序等。软件可以通过编程语言编写和开发,并可以安装…

网络初识必知会

局域网:把一些设备通过交换机/路由器连接起来 广域网:把更多的局域网也相互连接,当网络规模足够大的 交换机:组网过程中的重要设备! 路由器:组网过程中的重要设备! IP地址:描述一…

QT、c/c++通过宏自动判断平台

QT、c/c通过宏自动判断平台 Chapter1 QT、c/c通过宏自动判断平台 Chapter1 QT、c/c通过宏自动判断平台 原文链接:https://blog.csdn.net/qq_32348883/article/details/123063830 背景 为了更好的进行跨平台移植、编译、调试。 具体操作 宏操作 #ifdef _WIN32//d…

什么,这年头还有人不知道404

写在前面 哥,来帮我看看,这个请求怎么404了,明明接口路径是对的啊!一个下午,组里的小哥突然让我帮忙看这个问题,我不禁一惊,啥,这年头了还有人搞不定404,如有还有&#…

C语言 实现 链 显示 效果 查找 修改 删除

显示所有信息 2023年10月1日的描述:今天放假 2023年10月2日的描述:今天有体育 2023年10月3日的描述:今天有数学 2023年10月4日的描述:今天有语文 2023年10月5日的描述:今天有政治 2023年10月6日的描述:今天交学费 2023年10月7日的描述:今天周末 2023年10月8日的描述:今天给家里…

公司安防工程简要介绍及系统需求分析

多年来 从事安保监控领域的经验,在系统的功能要求、设备选型、施 工控制、 后期维护、人员配备等各方面反复论证,最终形成了本方案。在系统 的硬件选择上,把系统的稳定性、安全性、可靠性放在第一位。根据 招标文件的要求选用当今安防行业具…

FFmpeg:打印音/视频信息(Meta信息)

多媒体文件基本概念 多媒体文件其实是个容器在容器里面有很多流(Stream/Track)每种流是由不同的编码器编码的从流中读出的数据称为包在一个包中包含着一个或多个帧 几个重要的结构体 AVFormatContextAVStreamAVPacket FFmpeg操作流数据的基本步骤 打印音/视频信息(Meta信息…

LVGL_基础控件滚轮roller

LVGL_基础控件滚轮roller 1、创建滚轮roller控件 /* 创建一个 lv_roller 部件(对象) */ lv_obj_t * roller lv_roller_create(lv_scr_act()); // 创建一个 lv_roller 部件(对象),他的父对象是活动屏幕对象// 将部件(对象)添加到组,如果设置了默认组&#xff0c…

vim教程,看完这篇绝对够用啦

简介 vi 是一种命令行的文本编辑器,vim 是进阶版的 vi,gvim是vim的图形化版本。 Vim有几种基本的工作模式: 一般模式(Normal Mode, 命令模式,普通模式)一般模式是vim默认的模式,当打开一个文件时会自动进入此模式。 在此模式下,可以执行移动光标、滚动屏幕、删除字符…

不断优化的素数算法

前言:素数判断是算法中重要的一环,掌握优秀的素数判断方法是算法player的必修课。本文介绍的是由简到繁的素数算法,便于初学者从入门到精通。 素数(质数):只能被 1 和它本身整除的数称作素数,如…

总结二:linux面经

文章目录 1、 Linux中查看进程运行状态的指令、查看内存使用情况的指令、tar解压文件的参数。2、文件权限怎么修改?3、说说常用的Linux命令?4、说说如何以root权限运行某个程序?5、 说说软链接和硬链接的区别?6、说说静态库和动态…

(四)正点原子STM32MP135移植——u-boot移植

一、概述 u-boot概述就不概述了,u-boot、kernel、dtb三件套,dddd 经过国庆艰苦奋战,已经成功把所有功能移植好了 二、编译官方代码 进入u-boot的目录 2.1 解压源码、打补丁 /* 解压源码 */ tar xf u-boot-stm32mp-v2022.10-stm32mp-r1-r0.…

充分理清限制与条件+构造二分图+最小割:ARC142E

https://www.luogu.com.cn/problem/AT_arc142_e 他的充要条件是是什么: a i , a j ≥ m i n ( b i , b j ) a_i,a_j\ge min(b_i,b_j) ai​,aj​≥min(bi​,bj​)存在 a i ≥ m a x ( b i , b j ) a_i\ge max(b_i,b_j) ai​≥max(bi​,bj​) 第一个条件直接预处理一…

Springcloud支付模块

客户端消费者80 order 微服务提供者8001 payment 订单模块可以调动支付模块 步骤: 1、建moudle 2、改写pom 3、写yml 4、主启类 5、业务类

【LinuxC】时间、时区,相关命令、函数

文章目录 一、序1.1 时间和时区1.11 时间1.12 时区 1.2 查看时间时区的命令1.21 Windows1.22 Linux 二、C语言函数2.1 通用2.11 函数简介2.12 数据类型简介 2.2 windows 和 Linux特有函数2.3 C语言示例 一、序 1.1 时间和时区 1.11 时间 时间是一种用来描述物体运动变化的量…