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年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

网络初识必知会

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

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

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

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…

不断优化的素数算法

前言:素数判断是算法中重要的一环,掌握优秀的素数判断方法是算法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 时间 时间是一种用来描述物体运动变化的量…

黑马点评-01基于Redis实现短信登陆的功能

环境准备 当前模型 nginx服务器的作用 手机或者app端向nginx服务器发起请求,nginx基于七层模型走的是HTTP协议,可以实现基于Lua直接绕开tomcat访问Redis nginx也可以作为静态资源服务器,轻松扛下上万并发并负载均衡到下游的tomcat服务器,利用集群支撑起整个项目 使用nginx部…

黑马JVM总结(二十七)

(1)synchronized代码块 synchronized代码块的底层原理,它是给一个对象进行一个加锁操作,它是如何保证如果你出现了synchronized代码块中出现了问题,它需要给这个对象有一个正确的解锁操作呢,加锁解锁是成对…

【c++_containers】10分钟带你学会list

前言 链表作为一个像是用“链子”链接起来的容器,在数据的存储等方面极为便捷。虽然单链表单独在实际的应用中没用什么作用,但是当他可以结合其他结构,比如哈希桶之类的。不过今天学习的list其实是一个带头双向链表。 言归正传,让…

overleaf在线编辑工具使用教程

文章目录 1 用 orcid注册overleaf获取模板2 使用模板 1 用 orcid注册overleaf获取模板 通常来说,在期刊投稿网站information for author中找template 。下载压缩包后上传到over leaf中。 加入找不到官方模板,用overleaf中的 2 使用模板 .bib文件&…

3D孪生场景SDK:Viwer 孪生世界

NSDT 编辑器 提供三维场景构建、场景效果设计、场景服务发布全流程工具等,其场景编辑器支持资产管理、灯光设置、骨骼动画等功能;致力于协助资源不足的中小企业及个人快速开发数字孪生场景,帮助企业提高生产力、实现降本增效。 NSDT编辑器简…

adb详细教程(四)-使用adb启动应用、关闭应用、清空应用数据、获取设备已安装应用列表

adb对于安卓移动端来说,是个非常重要的调试工具。本篇介绍常用的adb指令 文章目录 一、启动应用:adb shell am start二、使用浏览器打开指定网址:adb shell am start三、杀死应用进程adb shell am force-stop/adb shell am kill四、删除应用所…

【AI视野·今日CV 计算机视觉论文速览 第262期】Fri, 6 Oct 2023

AI视野今日CS.CV 计算机视觉论文速览 Fri, 6 Oct 2023 Totally 73 papers 👉上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Improved Baselines with Visual Instruction Tuning Authors Haotian Liu, Chunyuan Li, Yuheng Li, Yong Jae Lee大型多模…

python开发幸运水果抽奖大转盘

概述 当我女朋友跟我说要吃水果,又不知道吃啥水果时候,她以为难为到我了,有啥事难为到程序员的呢! 今天用python利用第三方tkinterthreadingtime库开发一个幸运水果抽奖大转盘!抽到啥吃啥 详细 老规矩!咱…

基于A4988/DRV8825的四路步进电机驱动器

概述 简化板的CNC sheild V3.0,仅保留步进电机速度与方向的控制引脚STEP/DIR、使能端EN、芯片供电VCC\GND,共计11个引脚。PCB四周开设四个M3通孔,以便于安装固定。此外,将板载的焊死的保险丝更改为可更换的保险座保险丝&#xff…