非阻塞实现高效键盘扫描功能(STM32F4XX)

目录

概述

1 原理分析

1.1 技术背景

1.2 系统硬件

1.3 STM32 IO(输入模式)寄存器分析

1.3.1 输入IO的功能描述

1.3.2 输入配置

1.3.3 GPIO 寄存器(输入模式相关)

1.3.3.1 GPIO 端口模式寄存器

1.3.3.2 GPIO 端口上拉/下拉寄存器

1.3.3.3 GPIO 端口输入数据寄存器

1.4 外设时钟使能寄存器

2 软件实现

2.1 使用STM32CubeMX创建工程

2.2 认识Hal库中和IO相关的函数

2.3 实现代码

2.3.1 定义一个Key相关的数据结构

2.3.2 初始化函数

2.3.3 按键扫描函数

2.3.4 使用键值

3 测试

3.1 编写测试代码

3.2 测试


源代码下载地址:

使用SM32-F4实现非阻塞方式,读取按键值资源-CSDN文库

概述

        本文主要介绍如何使用非阻塞方式,实现多个按键扫描功能,能准确判断按键的状态。还详细介绍STM32 F4系列芯片IO相关的寄存器,已经Hal库中和IO相关的接口函数。重点讲解非阻塞高效键盘扫描功能的代码实现逻辑。

1 原理分析

1.1 技术背景

        在一个系统程序中,一般希望程序运行尽可能的快,这样MCU才可能经可能多的执行逻辑,或者处理数据。Windows /Linux系统中引入线程和进程来解决这个问题。在单线程系统(单片机程序:主程序main中一个while循环)中,也可以模拟多线程的方式,把阻塞执行的代码,使用时间片来轮询来执行。以提高代码运行的效率。

扫描键盘功能就明显有这类任务的特征,本文就是采用系统定时器产生时间片,实现一个非阻塞任务方式,扫描键盘中的按键,并判断键值是否有效。

1.2 系统硬件

电路分析:

系统有8个独立按键,每个独立按键一个端口与一个MCU的一个IO相连,且与MCU IO相连的这个端口,接了一个上拉电阻,其目的,保持IO输入口电平的稳定性。按键的另一个端口与GND连接,当按键按下之后,MCU IO会检测到低电平信号。

1.3 STM32 IO(输入模式)寄存器分析

要使用MCU IO控制外围设备,就需要对IO模块有一个清晰的认识,这样才能正确的使用它,下面来分析STM32 IO模块的特性。笔者使用的芯片型号是STM32F407IGT6,所以,本文STM32F4xx用户手册为例来介绍其IO的使用方法。由于,STM32 IO的功能比较复杂,这里只介绍将其配置为输入IO时,该如何使用。

1.3.1 输入IO的功能描述

根据数据手册中列出的每个 I/O 端口的特性,可通过软件将通用 I/O (GPIO) 端口的各个端口位分别配置输入模式时,有如下3种方式配置:

● 输入浮空

● 输入上拉

● 输入下拉

1.3.2 输入配置

对 I/O 端口进行编程作为输入时:

● 输出缓冲器被关闭

● 施密特触发器输入被打开

● 根据 GPIOx_PUPDR 寄存器中的值决定是否打开上拉和下拉电阻

● 输入数据寄存器每隔 1 个 AHB1 时钟周期对 I/O 引脚上的数据进行一次采样

● 对输入数据寄存器的读访问可获取 I/O 状

1.3.3 GPIO 寄存器(输入模式相关)

1.3.3.1 GPIO 端口模式寄存器

每一组GPIO有个 GPIOx_MODER 端口模式寄存器, 该寄存器一共有32个bit, 每两个bit控制一组IO下的一个pin引脚的模式状态。

MODERy[1:0]: 端口 x 配置位 (Port x configuration bits) (y = 0..15)。这些位通过软件写入,用于配置 I/O 方向模式。

00:输入(复位状态)

01:通用输出模式

10:复用功能模式

11:模拟模式

举个例子:配置GPIOI_PIN7为输入引脚,需要写MODER7[1:0] = 00

1.3.3.2 GPIO 端口上拉/下拉寄存器

每一组GPIO有个 (GPIOx_PUPDR) 上拉/下拉寄存器, 该寄存器一共有32个bit, 每2个bit控制一组IO下的一个pin引脚的上拉/下拉状态。

PUPDRy[1:0]: 端口 x 配置位 (Port x configuration bits) (y = 0..15),这些位通过软件写入,用于配置 I/O 上拉或下拉。

00:无上拉或下拉

01:上拉

10:下拉

11:保留

1.3.3.3 GPIO 端口输入数据寄存器

每一组GPIO有个 (GPIOx_IDR) 输入数据寄存器 , 该寄存器一共有32个bit, 每1个bit表示对应端口输入的值。因为stm32每一组IO有16个端口,所以,使用bit0~bit15,存储输入的bit值,bit-16~bit-31保留

IDRy[15:0]: 端口输入数据 (Port input data) (y = 0..15) 这些位为只读形式,只能在字模式下访问。它们包含相应 I/O 端口的输入值。

1.4 外设时钟使能寄存器

这个寄存器主要用来打开或者关闭所使用的外设功能,STM32F407有3个外设时钟使能寄存器 ,本文仅介绍和IO相关的 RCC_AHB1ENR ,其定义如下:

在上图中可以看见,和IO相关的时钟使能bit位分布在bit0~bit8,要使用那个端口,只需将对应的位置1,就可以使能该对应位的时钟。

2 软件实现

2.1 使用STM32CubeMX创建工程

1)打开STM32CubeMX创建工程,然后在GPIO选项卡中,配置和KEY-IO相关的参数。

2)完成参数配置后,点击GENERATE CODE,生成工程文件。

2.2 认识Hal库中和IO相关的函数

1) HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)

功能: 用于初始化GPIO的属性,在STM32CubeMX中配置IO属性后,该函数会在IO初始代码中调用。这部分代码会由STM32CubeMX自动生成。

2) PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

功能:读取IO_PIN的值

函数参数:

GPIOx: GPIO组(A,B,C...)

GPIO_Pin: GPIO组下的那个引脚(0~15)

返回值: 读到IO pin 的值

3) HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)

功能:写IO_PIN的值

函数参数:

GPIOx: GPIO组(A,B,C...)

GPIO_Pin: GPIO组下的那个引脚(0~15)

PinState: 要写的状态(0 or 1)

4) HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

功能: 触发IO_Pin电平变化

函数参数:

GPIOx: GPIO组(A,B,C...)

GPIO_Pin: GPIO组下的那个引脚(0~15)

5)HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

功能: 锁存当前IO_Pin的值,reset时,该IO的电平不会发生变化

函数参数:

GPIOx: GPIO组(A,B,C...)

GPIO_Pin: GPIO组下的那个引脚(0~15)

6)HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)

功能: 当前IO_Pin的中断函数

函数参数:

GPIO_Pin: GPIO组下的那个引脚(0~15)

7)HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)

功能: 中断函数的回调函数,HAL_GPIO_EXTI_IRQHandler中会调用该函数,且这个函数函数为weak类型,用户可重新写它。

2.3 实现代码

源代码下载地址: 使用SM32-F4实现非阻塞方式,读取按键值资源-CSDN文库

2.3.1 定义一个Key相关的数据结构

定义一个数据结构,由于操作和key相关的状态控制。

typedef struct
{uint8 (*KeyActFunc)(void);   
​uint8  Count;uint8  State;uint8  DownState;uint8  ReleaseState;
}KeyAct_Stru;

2.3.2 初始化函数

1)在该段函数中,21~29 行,实现IO状态读取函数,当按键被按下后,返回值为1, 否则返回值为0

2)44~52行, 注册按键触发函数

源代码

static KeyAct_Stru s_tBtn[KEY_TOTAL];static unsigned char IsKey_1_Down(void)      {if ((KEY_1_GPIO_Port->IDR & KEY_1_Pin) == 0)         return 1;else return 0;}
static unsigned char IsKey_2_Down(void)      {if ((KEY_2_GPIO_Port->IDR & KEY_2_Pin) == 0)         return 1;else return 0;}
static unsigned char IsKey_3_Down(void)      {if ((KEY_3_GPIO_Port->IDR & KEY_3_Pin) == 0)         return 1;else return 0;}static unsigned char IsKey_up_Down(void)     {if ((KEY_UP_GPIO_Port->IDR & KEY_UP_Pin) == 0)       return 1;else return 0;}
static unsigned char IsKey_down_Down(void)   {if ((KEY_DOWN_GPIO_Port->IDR & KEY_DOWN_Pin) == 0)   return 1;else return 0;}
static unsigned char IsKey_left_Down(void)   {if ((KEY_LEFT_GPIO_Port->IDR & KEY_LEFT_Pin) == 0)   return 1;else return 0;}
static unsigned char IsKey_right_Down(void)  {if ((KEY_RIGHT_GPIO_Port->IDR & KEY_RIGHT_Pin) == 0) return 1;else return 0;}
static unsigned char IsKey_ok_Down(void)     {if ((KEY_OK_GPIO_Port->IDR & KEY_OK_Pin) == 0)       return 1;else return 0;}void bsp_KeyInit( void )
{int i = 0;KeyAct_Stru *pBtn;for( i =0; i < KEY_TOTAL; i++ ){pBtn = &s_tBtn[i];memset( pBtn, sizeof(KeyAct_Stru), 0 );}s_tBtn[0].KeyActFunc = IsKey_1_Down;s_tBtn[1].KeyActFunc = IsKey_2_Down;s_tBtn[2].KeyActFunc = IsKey_3_Down;s_tBtn[3].KeyActFunc = IsKey_up_Down;s_tBtn[4].KeyActFunc = IsKey_down_Down;s_tBtn[5].KeyActFunc = IsKey_left_Down;s_tBtn[6].KeyActFunc = IsKey_right_Down;s_tBtn[7].KeyActFunc = IsKey_ok_Down;
}

2.3.3 按键扫描函数

实现逻辑如下: 1) 当按键被按下后,检测计数器开始工作(63~75行),bsp_KeyScan()的运行周期是1ms,当count值累加到门限值时,按键按下的状态没有改变,说明按键值有效。

2)检测按键弹起:

step-1: 检测按下标记为是否有效,如果该位有效,说明按键被按下过。

step-2:计数器开始工作,当计数器的值到达门限值后,弹起状态有效,置位弹起标记。

3)键值位(102~108行)

检测按下确认状态位和弹起确认状态位。当二者都有效时,存储的键值有效。

源代码

void bsp_KeyScan( unsigned char index )
{KeyAct_Stru *pBtn;pBtn = &s_tBtn[index];	if( pBtn->KeyActFunc() ) {// key first pressed if( pBtn->Count < KEY_FILTER_MAX_TIME ){pBtn->Count  = KEY_FILTER_MAX_TIME;}else if( pBtn->Count < KEY_FILTER_NEXT_TIME ) {pBtn->Count++;}else{// confirm:  key is presssed if( !pBtn->DownState ) {pBtn->DownState = 1;}pBtn->Count = 0;}}else{if( pBtn->DownState ){if( pBtn->Count > KEY_FILTER_MAX_TIME ) {pBtn->Count = KEY_FILTER_MAX_TIME;}else if(  pBtn->Count > 0){pBtn->Count--;}else{//confirm: key is released  if( !pBtn->ReleaseState ) {pBtn->ReleaseState = 1;}}}}// confirm key press action if( pBtn->ReleaseState && pBtn->DownState ){printf(" KEY-%d is pressed!\r\n", index);pBtn->State = 1;pBtn->ReleaseState = 0;pBtn->DownState = 0;}	
}

2.3.4 使用键值

1)bsp_KeyMonitor

按键扫描函数,该函数必须放在一个以1ms为间隙扫描的任务里,其会周期性的扫描所有注册的按键状态

2)bsp_KeyGetValue

获取键值函数,通过传入key所对应ID的值,就能得到该键值

3 测试

3.1 编写测试代码

1) 第104行, 调用系统Tick函数,实现1ms Tick功能,

2)第117行,实现键盘扫描功能,其执行周期为1ms

3) 第127行,读取键值

源代码

void bsp_KeyMonitor( void )
{unsigned char index ;for( index = 0; index < KEY_TOTAL; index ++ ){bsp_KeyScan( index );}
}unsigned char bsp_KeyGetValue(KEY_ID keyID)
{unsigned char value;value = s_tBtn[keyID].State;s_tBtn[keyID].State = 0;    // clear key statusreturn value;
}

3.2 测试

编译程序,下载到板卡中,按下不同的按键,会打印不同的键值

源代码

void tick_action( void )
{static bool flag_1s = 0;static unsigned int tick_cnt = 0;static unsigned int beforTick = 0;unsigned int currentTick;unsigned char val;currentTick = HAL_GetTick();if(beforTick != currentTick ){beforTick = currentTick;tick_cnt++;// 1s actionif( (tick_cnt % 1000) == 0) {flag_1s = true;;}//1ms actionbsp_KeyMonitor();}if( flag_1s ){flag_1s = false;HAL_GPIO_TogglePin(SYS_RUN_LED_GPIO_Port, SYS_RUN_LED_Pin);}val = bsp_KeyGetValue( KEY_1 );if( val  ){test_can1_send();}val = bsp_KeyGetValue( KEY_2 );if( val  ){test_can2_send();}
}

运行代码后,可以看见:

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

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

相关文章

springboot,druid动态数据源切换

关键字&#xff1a;springboot&#xff0c;druid数据库连接池&#xff0c;两个数据源&#xff08;可以切换成多个&#xff09;&#xff0c;事务管理 关于druid简介传送门&#xff1a;https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98 具体分为四…

Doris【数据模型】

一、数据模型简介 在 Doris 中&#xff0c;数据以表&#xff08;Table&#xff09;的形式进行逻辑上的描述。 一张表包括行&#xff08;Row&#xff09;和列&#xff08;Column&#xff09;。Row 即用户的一行数据。Column 用于描述一行数据中不同的字段。 Column 可以分为两…

autoware.universe中跟踪模块详解,一看就懂!

目录 问题:阅读关键点:总结问题: 根据对预测模块代码的分析,发现预测框出现在点云前方的原因在于跟踪框出现在点云前方 对rviz上的目标进行观察后发现 车辆的检测框先出来一段时间后,跟踪框和预测框同步一块出来 跟踪框总是超出点云一部分 阅读关键点: 每个跟踪器最少要统计…

7.1.2 Selenium的用法1

目录 1. 初始化浏览器对象和访问页面 2. 查找节点及节点交互 2.1 查找单个节点 &#xff08;1&#xff09;获取方法1——特定方法 &#xff08;2&#xff09;通用方法 2.2 查找多个节点 2.3 节点交互 3. 动作链 4. 执行 JavaScript 之下拉进度条 5. 获取节点信息 5.…

谷歌seo推广秒收录怎么做?

谷歌SEO推广秒收录想要做到&#xff0c;可以利用我们光算科技独家技术&#xff0c;GSI快速收录&#xff0c;通过技术手段和操作&#xff0c;帮你的网站快速被谷歌发现和记录 这项技术具体核心就是GPC爬虫池系统&#xff0c;这个系统是专门研究谷歌搜索引擎优化的规律和算法创造…

66-ES6:var,let,const,函数的声明方式,函数参数,剩余函数,延展操作符,严格模式

1.JavaScript语言的执行流程 编译阶段&#xff1a;构建执行函数&#xff1b;执行阶段&#xff1a;代码依次执行 2.代码块&#xff1a;{ } 3.变量声明方式var 有声明提升&#xff0c;允许重复声明&#xff0c;声明函数级作用域 访问&#xff1a;声明后访问都是正常的&…

QT C++实现点击按键弹出窗口并显示图片/视频|多窗口应用程序的设计和开发

一、介绍 首先&#xff0c;QT界面开发中主要大体分为2种多窗口的形式&#xff1a; 嵌入式&#xff1a; 新生成的窗口嵌入在主窗口内部独立窗口&#xff1a; 以弹窗形式的新窗口生成和展示 这里就讲解最简单的&#xff1a;点击案件后&#xff0c;跳出一个新窗口 二、代码实…

利用FFMPEG 将RTSP流的音频G711 转码为AAC 并 推流到RTMP

之前我们的视频转码项目中 是没有加入音频的 现在 需要加入音频 &#xff0c;由于RTMP只支持AAC的 音频流 而有的RTSP流的音频编码并不是AAC 大多数都是G711编码 还分为G711A 和G711U 之前用ffmpeg命令行可以直接 完成转码 并推送到RTMP 但是考虑到无法获取更详细的状…

Qt篇——QTableWidget保存表格数据到Excel文件中,读Excel内容到QTableWidget

表格和excel例子如下图所示&#xff1a; 一、QTableWidget保存表格数据到Excel文件中 代码如下&#xff1a; &#xff08;pro文件中添加QT axcontainer&#xff09; #include <QAxObject>void MainWindow::saveTableToExcel() {QDateTime current_date_time QDateTi…

Vue3速成

文章目录 day 11. 创建vue3工程3. 响应式数据4. 计算属性 day 25. watch 监视6. watchEffect7. 标签的ref属性8. 回顾TS中的接口_泛型_自定义类型 day 1 1. 创建vue3工程 相关代码如下&#xff1a; ## 创建vue工程 npm create vuelastest## 安装node_modules npm install //…

JSON 文件里的 “$schema” 是干什么用的?

最近我在做一些前端项目&#xff0c;我发现有的配置文件&#xff0c;比如 .prettierrc.json 或者 tsconfig.json 里面都会看到一个 $schema 字段&#xff0c;有点好奇&#xff0c;就查了一下。 什么是 JSON Schema JSON Schema是一种基于JSON (JavaScript Object Notation) 的…

【Leetcode】2369. 检查数组是否存在有效划分

文章目录 题目思路代码结果 题目 题目链接 给你一个下标从 0 开始的整数数组 nums &#xff0c;你必须将数组划分为一个或多个 连续 子数组。 如果获得的这些子数组中每个都能满足下述条件 之一 &#xff0c;则可以称其为数组的一种 有效 划分&#xff1a; 子数组 恰 由 2 个…

大数据智能化-长视频领域

随着数字化时代的到来&#xff0c;长视频领域的发展迎来了新的机遇和挑战。在这一背景下&#xff0c;大数据智能化技术的应用成为长视频行业提升用户体验、优化运营管理的重要手段之一。本文将从优爱腾3大长视频背景需求出发&#xff0c;分析静态资源CDN、视频文件存储与分发、…

网络安全、信息安全、计算机安全,有何区别?

这三个概念都存在&#xff0c;一般人可能会混为一谈。 究竟它们之间是什么关系&#xff1f;并列&#xff1f;交叉&#xff1f; 可能从广义上来说它们都可以用来表示安全security这样一个笼统的概念。 但如果从狭义上理解&#xff0c;它们应该是有区别的&#xff0c;区别在哪呢&…

力扣hot100题解(python版36-40题)

36、二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 *中序 遍历* 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&am…

【机器学习基础】层次聚类-BIRCH聚类

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;相对完整的机器学习基础教学&#xff01; ⭐特别提醒&#xff1a;针对机器学习&#xff0c;特别开始专栏&#xff1a;机器学习python实战…

matplotlib.animation 3d姿态动画

目录 演示效果&#xff1a; 演示代码&#xff1a; 保存为gif 演示效果&#xff1a; 演示代码&#xff1a; import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from matplotlib.animation import FuncAnimation# 定义人体关键点…

网络防御第6次作业

防病毒网关 按照传播方式分类 病毒 病毒是一种基于硬件和操作系统的程序&#xff0c;具有感染和破坏能力&#xff0c;这与病毒程序的结构有关。病毒攻击的宿主程序是病毒的栖身地&#xff0c;它是病毒传播的目的地&#xff0c;又是下一次感染的出发点。计算机病毒感染的一般过…

动态规划(算法竞赛、蓝桥杯)--分组背包DP

1、B站视频链接&#xff1a;E16 背包DP 分组背包_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N110; int v[N][N],w[N][N],s[N]; // v[i,j]:第i组第j个物品的体积 s[i]:第i组物品的个数 int f[N][N]; // f[i,j]:前i组物品&#xff0c;能放…

基带信号处理设计原理图:2-基于6U VPX的双TMS320C6678+Xilinx FPGA K7 XC7K420T的图像信号处理板

基于6U VPX的双TMS320C6678Xilinx FPGA K7 XC7K420T的图像信号处理板 综合图像处理硬件平台包括图像信号处理板2块&#xff0c;视频处理板1块&#xff0c;主控板1块&#xff0c;电源板1块&#xff0c;VPX背板1块。 一、板卡概述 图像信号处理板包括2片TI 多核DSP处理…