STM32复习笔记(二):GPIO

news2024/11/20 18:36:35

目录

(一)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)
#endif


KEYS 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,就可以检测KEY0
        keyState = 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,就可以检测KEY1
        keyState = 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,就可以检测KEY2
        keyState = 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_UP
        keyState = 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;
        }
#endif

        if (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.coloradmin.cn/o/1058976.html

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

相关文章

网络技术在学校是学习网络协议吗

大家好,我是网络工程师成长日记实验室的郑老师,您现在正在查看的是网络工程师成长日记专栏,记录网络工程师日常生活的点点滴滴 一个同学说他现在也在学这个计算机专业,他以后他现在目前规划是把软考和华为的HIP1套。我自己本身也做…

AI时代给我们公司模式带来的改变

最近和几个朋友聊天,都说公司卷的更厉害了,老板趁着经济形势不好压榨的更厉害了!但确实没办法,我们部分老板还是喜欢以人为本(成本!),更喜欢雇更多的人,看着他们低效而痛…

【Java】权限修饰符

目录 权限修饰符 权限修饰符-示例代码 权限修饰符 Java有四种访问权限,其中三种有访问权限修饰符,分别为 private,public 和 protected,还有一种不带任何修饰符: private:Java语言中对访问权限限制的最窄…

【C语言经典100例题-68】有n个整数,使其前面各数顺序向后移m个位置,最后m个数变成最前面的m个数

方法一 将原数组拆成两部分&#xff0c;前面n-m个数和后面m个数。首先将前面n-m个数逆序&#xff0c;然后将后面的m个数逆序。最后将整个数组逆序即可。 #include <stdio.h>void reverse(int arr[], int start, int end) {for (int i start, j end; i < (start en…

快排三种递归及其优化,非递归和三路划分

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 快排简介&#xff1a; 快排的三种递归实现&#xff1a; Hoare&#xff1a; 挖坑&#xff1a; 双指针&#xff1a; 小区间优化&#xff1a; 三数取中优化&#xff1a; 快排非递归实现&#xff1a; 快排的三路划…

嵌入式Linux应用开发-驱动大全-第一章同步与互斥④

嵌入式Linux应用开发-驱动大全-第一章同步与互斥④ 第一章 同步与互斥④1.5 自旋锁spinlock的实现1.5.1 自旋锁的内核结构体1.5.2 spinlock在UP系统中的实现1.5.3 spinlock在SMP系统中的实现 1.6 信号量semaphore的实现1.6.1 semaphore的内核结构体1.6.2 down函数的实现1.6.3 u…

Android etc1tool之png图片转换pkm 和 zipalign简介

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、etc1tool2.1、用法 三、zipalign3.1 使用 四…

day49 ARM

.text .globl _start _start:mov r1,#1mov r2,#0mov r3,#100 fun2:cmp r2,r3bcc fun1 stop:b stop fun1: ADD r2,r2,r1add r4,r4,r2b fun2 .end

华为云云耀云服务器L实例评测|部署个人在线电子书库 calibre

华为云云耀云服务器L实例评测&#xff5c;部署个人在线电子书库 calibre 一、云耀云服务器L实例介绍1.1 云服务器介绍1.2 应用场景1.3 支持镜像 二、云耀云服务器L实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置 三、部署 calibre3.1 calibre 介绍3.2 Docker 环境搭建3.3 c…

javaScript-事件循环-微任务-宏任务

为什么引入事件循环?如何理解&#xff1f; js是单线程的语言&#xff0c;需要把异步任务交给宿主浏览器执行&#xff0c;仿制js引擎堵塞 以下面的代码为例 异步的代码交给浏览器之后 进入队列中等待被调用&#xff1a; <!DOCTYPE html> <html lang"en"…

嵌入式Linux应用开发-驱动大全-第一章同步与互斥③

嵌入式Linux应用开发-驱动大全-第一章同步与互斥③ 第一章 同步与互斥③1.4 Linux锁的介绍与使用1.4.1 锁的类型1.4.1.1 自旋锁1.4.1.2 睡眠锁 1.4.2 锁的内核函数1.4.2.1 自旋锁1.4.2.2 信号量1.4.2.3 互斥量1.4.2.4 semaphore和 mutex的区别 1.4.3 何时用何种锁1.4.4 内核抢占…

CharacterEncodingFilter的用法

CharacterEncoding是SpringMVC提供的一个一个过滤器,用于设置请求和响应的字符编码,解决乱码问题,他本身是一个过滤器 那么在SpringBoot中,CharacterEncoding就有一个很好的秒用 setEncoding("UTF-8")设置编码 setForceEncoding(true) 设置请求和响应编码 还需要在配…

树的存储结构以及树,二叉树,森林之间的转换

目录 1.双亲表示法 2.孩子链表 3.孩子兄弟表示法 4.树与二叉树的转换 &#xff08;1&#xff09;树转换为二叉树 &#xff08;2&#xff09;二叉树转换成树 5.二叉树与森林的转化 &#xff08;1&#xff09;森林转换为二叉树 以下树为例 1.双亲表示法 双亲表示法定义了…

javaee之通用mapper

通用mapper可以帮我们写sql语句 我们需要引入依赖是 通用mapper的核心依赖 它本身就依赖一个jpa的依赖&#xff0c;通用mapper的整体依赖就包含了通用mapper的核心依赖 下面说一下通用mapper里面的常见注解 KeySql的用法 tk.mybatis.mapper.common.Mapper 这个是通用Mapper的一…

c#设计模式-行为型模式 之 模板方法模式

&#x1f680;简介 模板方法模式定义了一个操作中的算法的骨架&#xff0c;而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下&#xff0c;重新定义算法中的某些步骤。通常用于应对在开发中设计一个系统时知道了算法所需的关键步骤&#xff0c;而且确定…

用 Pycharm 远程连接 Linux 服务器——超详细

用 Pycharm 远程连接 Linux 服务器——超详细 一、介绍二、要求三、服务器配置四、Pycharm远程连接Linux服务器 实战 一、介绍 本人是做NLP的&#xff0c;pycharm写的项目&#xff0c;数据集很大&#xff0c;在自己电脑上运行很慢&#xff0c;但是放到服务器上跑就很快。下面详…

FileZila 实现wind10与Linux系统文件互传

【FileZila】实现windows与Linux系统文件互传

哨兵(Sentinel-1、2)数据下载

哨兵&#xff08;Sentinel-1、2&#xff09;数据下载 一、登陆欧空局网站 二、检索 先下载2号为光学数据 分为S2A和S2B&#xff0c;产品种类有1C和2A&#xff0c;区别就是2A是做好大气校正的影像&#xff0c;当然数量也会少一些&#xff0c;云量检索条件中记得要按格式&#x…

Covert Communication隐蔽通信论文复现

文章目录 前言Covert Communications: A Comprehensive Surveyabstract简介隐蔽通信的概念和机制隐蔽通信的简要历史经典的Alice-Bob-Willie Model与其他安全技术的区别 一、Limits of Reliable Communication with Low Probability of Detection on AWGN Channels摘要introduc…

STM32复习笔记(三):串口

目录 Preface&#xff1a; &#xff08;一&#xff09;CUBEMX配置串口 &#xff08;二&#xff09;轮询方式 &#xff08;三&#xff09;中断 DMA Preface&#xff1a; 串口通信协议简单&#xff0c;因此被广泛应用&#xff1b;串口有UART&#xff08;Universal Asynchron…