梁山派入门指南2——滴答定时器位带操作按键输入(包括GPIO中断)

ops/2025/1/17 19:52:54/

梁山派入门指南2——滴答定时器&位带操作&按键输入

  • 1. 滴答定时器
    • 1.1 滴答定时器简介
    • 1.2 相关寄存器
    • 1.3 固件库函数
  • 2. 位带操作
    • 2.1 位带操作介绍
    • 2.2 位带操作的优势
    • 2.3 支持位带操作的内存地址
    • 2.4 位带别名区地址的计算方式
    • 2.5 位带操作使用示例
  • 3 按键输入
    • 3.1 独立按键原理图
    • 3.2 独立按键配置流程
    • 3.3 独立按键初始化
    • 3.4 按键扫描&bsp_key文件
    • 3.5 按键输入demo

1. 滴答定时器

1.1 滴答定时器简介

SysTick 定时器可用作标准的下行计数器,是一个 24 位向下计数器,有自动重新装载能力,可屏蔽系统中断发生器。Cortex-M4 处理器内部包含了一个简单的定时器,所有基于 M4 内核的控制器都带有 SysTick 定时器,这样就方便了程序在不同的器件之间的移植。SysTick 定时器可用于操作系统,提供系统必要的时钟节拍,为操作系统的任务调度提供一个有节奏的“心跳”。正因为所有的 M4内核的芯片都有 Systick 定时器,这在移植的时候不像普通定时器那样难以移植。

GD32中的RCU 通过 AHB 时钟(HCLK)8 分频后作为 Cortex 系统定时器(SysTick)的外部时钟。通过对
SysTick 控制和状态寄存器的设置,可选择上述时钟或 AHB(HCLK)时钟作为 SysTick 时钟。

GD32F4系列的时钟概览:
在这里插入图片描述
我们来看一下滴答定时器相关的部分:
在这里插入图片描述
SysTick 定时器设定初值并使能之后,每经过 1 个系统时钟周期,计数值就减 1,减到 0 时,SysTick计数器自动重新装载初值并继续计数,同时内部的 COUNTFLAG 标志位被置位,触发中断(前提开启中断)。

  • 滴答定时器的时钟频率为30Mhz

1.2 相关寄存器

在这里插入图片描述

1.3 固件库函数

SysTick 定 时 器 的 使 用 主 要 有 SysTick_Config() 函 数 和 systick_clksource_set(uint32_t systick_clksource)函数,SysTick_Config()函数主要用来设置定时时间,systick_clksource_set()函数用来选择 SysTick 时钟源。

  1. systick_config()
/*!\brief    配置sysTick的时钟和中断周期\param[in]  none\param[out] none\retval     none
*/
void systick_config(void)
{/* 设置sysTick中断的频率为1000Hz,即1ms触发1次 */if(SysTick_Config(SystemCoreClock / 1000U)) {/* capture error */while(1) {}}/* 设置sysTick的优先级为0 */NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
  1. SysTick_Handler()
/*!\brief    滴答定时器中断服务函数\param[in]  none\param[out] none\retval     none
*/
void SysTick_Handler(void)
{/* delay--,delay是需要延时的时间 */delay_decrement();
}/*!\brief    delay decrement\param[in]  none\param[out] none\retval     none
*/
void delay_decrement(void)
{if(0U != delay) {delay--;}
}
  1. delay_1ms()
/*!\brief    延时一段时间,\param[in]  count: count in milliseconds\param[out] none\retval     none
*/
void delay_1ms(uint32_t count)
{delay = count;while(0U != delay) {}
}
  1. systick_clksource_set()

在这里插入图片描述

以上就是滴答定时器涉及到的几个函数,最主要使用的应该就是,delay_1ms()这个函数,其他的只需要了解即可。。。。

2. 位带操作

2.1 位带操作介绍

为了减少“读-改-写”操作的次数,Cortex ® -M4 处理器提供了一个可以执行单原子比特操作的位带功能。存储器映射包含了两个支持位带操作的区域。其中一个是 SRAM 区的最低 1MB 范围,第二个是片内外设区的最低 1MB 范围。这两个区域中的地址除了普通应用外,还有自己的“位带别名区”。位带别名区把每个比特扩展成一个 32 位的字。当用户访问位带别名区时,就可以达到访问原始比特的目的。总结就是 CPU 不能直接对位带区中的单个数据位位寻址,只能通过对位带别名区的访问(或读/写)实现对位带区单个数据位的访问(或读/写),这种操作被称为位带操作

2.2 位带操作的优势

  • 更高效:避免了 ” 读-改-写 “ 的繁琐步骤
  • 读取更简单:只需要直接读取位带别名区的内容,就可以直接对某个bit位寻址
  • 访问速度更快:位带操作由于是属于硬件完成的,只需要CPU直接对位带别名区进行读写即可。
  • 相对安全:在带有操作系统的开发中,多任务并发运行的时候就有可能在任务切换的过程中发生不可预料的问题,而位带操作由于是属于硬件完成的不可被异常打断的操作(原子操作),相对于读-写-改的操作模式会更安全。
  • 提高运行效率和节省代码空间。简单的程序直接使用库函数或者寄存器操作,对于比较复杂的程序建议尽量使用位带操作来实现

2.3 支持位带操作的内存地址

并不是所有的区域都支持位带操作,下面是两个支持位带操作的区域:

  1. SRAM 区的最低 1MB: 0x2000_0000-0x200F_FFFF(对应位带别名区的地址:0x2200 0000)
    在这里插入图片描述
  2. 片上外设区的最低 1MB:0x4000_0000-0x400F_FFFF(对应位带别名区的地址:0x4200 0000)
    在这里插入图片描述
    在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c58d512bdca64756a4b3efce6c274e7f.png)
![在这里插入图片描述

2.4 位带别名区地址的计算方式

上面介绍了位带操作的地址和优势,那怎么去查找目标比特对应的位带别名区的地址呢?

下面的公式表明了位带别名区中的每个字如何对应位带区的相应比特或目标比特:

				     	 bit_word_addr =bit_band_base +(byte_offset×32)+(bit_number×4)

其中:

  • bit_word_addr指的是位带区目标比特对应在位带别名区的地址
  • bit_band_base指的是位带别名区的起始地址
  • byte_offset指的是位带区目标比特所在的字节的字节地址偏移量
  • bit_number指的是目标比特在对应字节中的位置(0-7)

在这里插入图片描述

2.5 位带操作使用示例

#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "bsp_led.h"
#include "sys.h"#define BIT_ADDR(byte_offset,bit_number) (volatile unsigned long*)(0x42000000 +(byte_offset << 5)+(bit_number << 2))   // 位带别名区的地址#define GPIOD_OCTL_OFFSET ((GPIOD + 0x14) - 0x40000000) 	// 偏移量#define PDout(n)  *(BIT_ADDR(GPIOD_OCTL_OFFSET,n))   		  // PD口输出/************************************************
函数名称 : main
功    能 : 主函数
参    数 : 无
返 回 值 : 无
作    者 : LC
*************************************************/
int main(void)
{systick_config();    // 滴答定时器初始化led_gpio_config();   // led初始化// gpio_bit_set(GPIOD,GPIO_PIN_7);     // PD7输出高电平// gpio_bit_reset(GPIOD,GPIO_PIN_7);   // PD7输出低电平while(1) {//gpio_bit_write(PORT_LED2,PIN_LED2,SET);     // LED2输出高电平PDout(7) = 1;                                 // PD7输出高电平delay_1ms(1000);                           		// 延时1s//gpio_bit_write(PORT_LED2,PIN_LED2,RESET);   // LED2输出低电平PDout(7) = 0;                                 // PD7输出低电平delay_1ms(1000);                              // 延时1s}
}

3 按键输入

3.1 独立按键原理图

我们打开梁山派提供的原理图,可以看到有三个按键,但是我们能使用的只有唤醒按键:
在这里插入图片描述

在这里插入图片描述

3.2 独立按键配置流程

打开梁山派开发指南,查看GPIO的输入配置流程如下:
在这里插入图片描述

3.3 独立按键初始化

根据配置流程,结合固件库手册中提供的库函数使用说明,开始编写初始化代码:
【注】:GPIO相关的库函数上一章有提到,可以查看:梁山派入门指南1——初窥门径梁山派

#define BSP_KEYUP_RCU   RCU_GPIOA   // 按键端口时钟
#define BSP_KEYUP_PORT  GPIOA       // 按键端口
#define BSP_KEYUP_PIN   GPIO_PIN_0  // 按键引脚
/************************************************
函数名称 : key_gpio_config
功    能 : 按键引脚初始化,默认配置为输入模式,下拉
参    数 : 需要初始化的引脚
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void key_gpio_config(void)
{/* 开启时钟 */rcu_periph_clock_enable(BSP_KEYUP_RCU);/* 配置为输入模式,下拉 */gpio_mode_set(BSP_KEYUP_PORT,GPIO_MODE_INPUT,GPIO_PUPD_PULLDOWN,BSP_KEYUP_PIN);
}

当然,我们也可以引入GPIO的中断,那么随之按键的初始化也发生改变:

#define BSP_KEYUP_RCU   RCU_GPIOA   // 按键端口时钟
#define BSP_KEYUP_PORT  GPIOA       // 按键端口
#define BSP_KEYUP_PIN   GPIO_PIN_0  // 按键引脚/* 中断类型 */
#define BSP_KEY_EXTI_IRQn EXTI0_IRQn /* EXTI中断源 */
#define BSP_KEY_EXTI_PORT_SOURCE EXTI_SOURCE_GPIOA
#define BSP_KEY_EXTI_PIN_SOURCE EXTI_SOURCE_PIN0/* 中断的EXTI线 */
#define BSP_KEY_EXTI_LINE EXTI_0 /* 中断服务函数名 */
#define BSP_KEY_EXTI_IRQHandler EXTI0_IRQHandler 
/************************************************
函数名称 : key_gpio_config
功    能 : 按键引脚初始化,默认配置为输入模式,下拉
参    数 : 需要初始化的引脚
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void key_gpio_config(void)
{/* 开启时钟 */rcu_periph_clock_enable(BSP_KEYUP_RCU);/* 配置为输入模式,下拉 */gpio_mode_set(BSP_KEYUP_PORT,GPIO_MODE_INPUT,GPIO_PUPD_PULLDOWN,BSP_KEYUP_PIN);/* 使能GPIO中断,需要配置SYSCFG中的EXTI寄存器,要先开启时钟 */rcu_periph_clock_enable(RCU_SYSCFG); /* 配置中断优先级 */nvic_irq_enable(BSP_KEY_EXTI_IRQn,3,0); /* 连接中断到EXTI线 */syscfg_exti_line_config(BSP_KEY_EXTI_PORT_SOURCE,BSP_KEY_EXTI_PIN_SOURCE);/* 初始化中断线 */exti_init(BSP_KEY_EXTI_LINE,EXTI_INTERRUPT,EXTI_TRIG_BOTH);/* 使能中断 */exti_interrupt_enable(BSP_KEY_EXTI_LINE);}

相应的,需要编写中断服务函数:

/************************************************
函数名称 : BSP_KEY_EXTI_IRQHandler
功    能 : 按键中断处理函数
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void BSP_KEY_EXTI_IRQHandler(void)
{/* 查询中断标志位 */if(exti_interrupt_flag_get(BSP_KEY_EXTI_LINE) == SET) {/* 获取按键的状态 */if(gpio_input_bit_get(BSP_KEYUP_PORT,BSP_KEYUP_PIN) == SET) // 按键按下{/* 需要执行的操作 */gpio_bit_toggle(PORT_LED2,PIN_LED2);}/* 清除中断标志位 */exti_interrupt_flag_clear(BSP_KEY_EXTI_LINE); // 清中断标志位}
}

3.4 按键扫描&bsp_key文件

为了节省篇幅,在这里我直接展示我写好的bsp_key文件,不介绍具体原理

  1. bsp_key.c
#include "bsp_key.h"/************************************************
函数名称 : key_gpio_config
功    能 : 按键引脚初始化,默认配置为输入模式,下拉
参    数 : 需要初始化的引脚
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void key_gpio_config(void)
{/* 开启时钟 */rcu_periph_clock_enable(BSP_KEYUP_RCU);/* 配置为输入模式,下拉 */gpio_mode_set(BSP_KEYUP_PORT,GPIO_MODE_INPUT,GPIO_PUPD_PULLDOWN,BSP_KEYUP_PIN);/* 判断是否使能中断GPIO中断 */#if KEY_INTERRUPT/* 使能GPIO中断,需要配置SYSCFG中的EXTI寄存器,要先开启时钟 */rcu_periph_clock_enable(RCU_SYSCFG); /* 配置中断优先级 */nvic_irq_enable(BSP_KEY_EXTI_IRQn,3,0); /* 连接中断到EXTI线 */syscfg_exti_line_config(BSP_KEY_EXTI_PORT_SOURCE,BSP_KEY_EXTI_PIN_SOURCE);/* 初始化中断线 */exti_init(BSP_KEY_EXTI_LINE,EXTI_INTERRUPT,EXTI_TRIG_BOTH);/* 使能中断 */exti_interrupt_enable(BSP_KEY_EXTI_LINE);#endif
}/************************************************
函数名称 : WKUP_Down_Task
功    能 : KEY_UP按键按下时调用的函数
参    数 : 需要初始化的引脚
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void WKUP_Down_Task(void)
{}/************************************************
函数名称 : WWKUP_Up_Task
功    能 : KEY_UP按键弹起时调用的函数
参    数 : 需要初始化的引脚
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void WWKUP_Up_Task(void)
{}/************************************************
函数名称 : Key_One_Scan
功    能 : 按键扫描函数,。这里只能扫描KEY_UP按键
参    数 : 需要初始化的引脚
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
{static uint8_t Key_Val[Key_Name_Max];    //按键值的存放位置static uint8_t Key_Flag[Key_Name_Max];   //KEY0~2为0时表示按下,为1表示松开,WKUP反之Key_Val[KeyName] = Key_Val[KeyName] <<1;  //每次扫描完,将上一次扫描的结果左移保存switch(KeyName){case Key_Name_WKUP:  Key_Val[KeyName] = Key_Val[KeyName] | (gpio_input_bit_get(BSP_KEYUP_PORT,BSP_KEYUP_PIN));   //读取WKUP按键值break; default:break;}if(KeyName == Key_Name_WKUP)     //WKUP的电路图与其他按键不同,所以需要特殊处理{//WKUP特殊情况//当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xffif(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1){(*OnKeyOneDown)();Key_Flag[KeyName] = 0;}//当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0){(*OnKeyOneUp)();Key_Flag[KeyName] = 1;} }}#if KEY_INTERRUPT/************************************************
函数名称 : BSP_KEY_EXTI_IRQHandler
功    能 : 按键中断处理函数
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void BSP_KEY_EXTI_IRQHandler(void)
{/* 查询中断标志位 */if(exti_interrupt_flag_get(BSP_KEY_EXTI_LINE) == SET) {/* 获取按键的状态 */if(gpio_input_bit_get(BSP_KEYUP_PORT,BSP_KEYUP_PIN) == SET) // 按键按下{/* 需要执行的操作 */gpio_bit_toggle(PORT_LED2,PIN_LED2);}/* 清除中断标志位 */exti_interrupt_flag_clear(BSP_KEY_EXTI_LINE); // 清中断标志位}
}#endif
  1. bsp_key.h
#ifndef _BSP_KEY_H
#define _BSP_KEY_H#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_led.h"/* 配置为1开启中断,为0关闭中断 */
#define KEY_INTERRUPT  0#define BSP_KEYUP_RCU   RCU_GPIOA   // 按键端口时钟
#define BSP_KEYUP_PORT  GPIOA       // 按键端口
#define BSP_KEYUP_PIN   GPIO_PIN_0  // 按键引脚#if KEY_INTERRUPT/* 中断类型 */
#define BSP_KEY_EXTI_IRQn EXTI0_IRQn /* EXTI中断源 */
#define BSP_KEY_EXTI_PORT_SOURCE EXTI_SOURCE_GPIOA
#define BSP_KEY_EXTI_PIN_SOURCE EXTI_SOURCE_PIN0/* 中断的EXTI线 */
#define BSP_KEY_EXTI_LINE EXTI_0 /* 中断服务函数名 */
#define BSP_KEY_EXTI_IRQHandler EXTI0_IRQHandler #endif	/* KEY_INTERRUPT *//* 按键引脚名字,目前可用的只有 Key_Name_WKUP */
typedef enum
{Key_Name_WKUP = 0,Key_Name_Max
}EnumKeyOneName;/* 按键引脚初始化,默认配置为输入模式,下拉 */
void key_gpio_config(void); 	  /*  KEY_UP按键按下按下和弹起时调用的函数*/
void WKUP_Down_Task(void);void WWKUP_Up_Task(void);/* 按键扫描函数 */
void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void)); #endif /* _BSP_KEY_H */

3.5 按键输入demo

结合上面的bsp_key文件,就可以编写出一个按键扫描的demo,代码如下:

#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"#include "bsp_led.h"
#include "bsp_key.h"/*!\brief    main function\param[in]  none\param[out] none\retval     none
*/
int main(void)
{uint16_t i=0;/* 配置sysTick的时钟和中断周期*/systick_config();/* led初始化 */led_gpio_config(Led_Name_Led1);led_gpio_config(Led_Name_Led2);key_gpio_config();while(1) {if(++i >= 50){/* 500ms翻转一次led的状态 */gpio_bit_toggle(PORT_LED1,PIN_LED1);i = 0;}Key_One_Scan(Key_Name_WKUP,WWKUP_Up_Task,WKUP_Down_Task);delay_1ms(10);}
}

【注】:这里的按键输入是采用GPIO输入的方式实现的,后续中断中还会对bsp_key文件进行进一步封装。
关于按键扫描的原理,可以参照:夜深人静学32系列9——GPIO驱动数码管/蜂鸣器/按键/LED 和 STM32框架之按键扫描新思路

以上就是本期的所有内容,创造不易,点个关注再走呗。

在这里插入图片描述


http://www.ppmy.cn/ops/150902.html

相关文章

目标检测新视野 | YOLO、SSD与Faster R-CNN三大目标检测模型深度对比分析

目录 引言 YOLO系列 网络结构 多尺度检测 损失函数 关键特性 SSD 锚框设计 损失函数 关键特性 Faster R-CNN 区域建议网络&#xff08;RPN&#xff09; 两阶段检测器 损失函数 差异分析 共同特点 基于深度学习 目标框预测 损失函数优化 支持多类别检测 应…

idea上git log面板的使用

文章目录 各种颜色含义具体的文件的颜色标签颜色&#x1f3f7;️ 节点和路线 各种颜色含义 具体的文件的颜色 红色&#xff1a;表示还没有 git add 提交到暂存区绿色&#xff1a;表示已经 git add 过&#xff0c;但是从来没有 commit 过蓝色&#xff1a;表示文件有过改动 标…

C#轻松实现条形码二维码生成及识别

一、前言 大家好&#xff01;我是付工。 今天给大家分享一下&#xff0c;如何基于C#来生成并识别条形码或者二维码。 二、ZXing.Net 实现二维码生成的库有很多&#xff0c;我们这里采用的是http://ZXing.Net。 ZXing是一个开放源码的&#xff0c;用Java实现的多种格式的一…

pytorch小记(六):pytorch中的clone和detach操作:克隆/复制数据 vs 共享相同数据但 与计算图断开联系

pytorch小记&#xff08;六&#xff09;&#xff1a;pytorch中的clone和detach操作&#xff1a;克隆/复制数据 vs 共享相同数据但 与计算图断开联系 1. x.clone()示例&#xff1a; 2. x.detach()示例&#xff1a;使用场景&#xff1a; 3. torch.tensor(x).float()示例&#xff…

MYSQL的第一次作业

目录 前情提要 题目解析 连接并使用数据库 创建employees表 创建orders表 创建invoices表 ​查看建立的表 前情提要 需要下载mysql并进行配置&#xff0c;建议下载8.0.37&#xff0c;详情可见MySQL超详细安装配置教程(亲测有效)_mysql安装教程-CSDN博客 题目解析 …

java进行pdf文件压缩

文章目录 pdf文件压缩 pdf文件压缩 添加依赖 <dependency><groupId>com.luhuiguo</groupId><artifactId>aspose-pdf</artifactId><version>23.1</version> </dependency>public class OptimizePdf {public static void opti…

[Unity] 【图形渲染】Unity Shader光照基础2-标准光照模型

在早期的游戏开发中,游戏引擎大多使用标准光照模型来模拟光线如何与物体表面交互。虽然现代引擎通常会采用更复杂的光照技术,但标准光照模型依然是一个关键概念,特别是在实时渲染中。在本文中,我们将深入探讨Unity标准光照模型的各个组成部分,了解光线如何与物体表面相互作…

【Uniapp-Vue3】响应式单位rpx及搭配使用UI产品工具

我们在编写CSS的时候&#xff0c;如果单位使用px&#xff0c;就会导致大小固定&#xff0c;但是如果我们想要根据不同的设备改变大小就要使用rpx作为单位。 rpx是以宽度为750的设计图得出的数据&#xff0c;以即时设计的设计稿为例&#xff1a; 该设计稿的宽度是375px 选中这…