第1期 定时器实现非阻塞式程序 按键控制LED闪烁模式

第1期 定时器实现非阻塞式程序 按键控制LED闪烁模式

  • 解决按键扫描,松手检测时阻塞的问题
  • 实现LED闪烁的非阻塞
  • 总结
  • 补充(为什么不会阻塞)

参考江协科技

在这里插入图片描述

KEY1和KEY2两者独立控制互不影响

阻塞:如果按下按键不松手,程序就会卡死在while循环里,主程序的其他程序无法执行,直到松手,函数才能结束。CPU花很长时间等大地。
非阻塞:程序执行很快且很快结束。

任务:按下K1慢闪,再按下K1熄灭
常规方法:
为什么开灯灵敏,关灯就不灵敏呢?因为开灯之后,程序会执行delay等待以及while等待,阻塞按键扫描程序,只有长按按键才能熄灭LED。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "LED.h"uint8_t KeyNum = 0;
uint8_t FlashFlag = 0;int main(void)
{OLED_Init();Key_Init();LED_Init();while (1){KeyNum = Key_GetNum();if(KeyNum == 1){FlashFlag = !FlashFlag;}if(FlashFlag){LED1_ON();Delay_ms(500);LED1_OFF();Delay_ms(500);}else{LED1_OFF();}}
}

阻塞测试
按下按键之前,屏幕快速刷新,按下按键后,数字停止刷新,表明程序阻塞在等待按键松手的地方。放手后,LED会闪烁同事数字继续自增。
在这里插入图片描述
主循环始终保持快速刷新状态,定时器定时中断可达到类似多线程的效果。
解决按键扫描松手检测时阻塞的问题。办法是用定时器扫描按键。
定时器扫描按键-单按键思路
上次采样电平 本次采样电平 结论
1 1 按键没按下
1 0 按键按下
0 0 按键按下没松开
0 1 按键按下并松开

根据以上情况置相应的标志位来执行操作。
在这里插入图片描述

定时器扫描按键-多按键
在这里插入图片描述

解决按键扫描,松手检测时阻塞的问题

如果把按键代码直接写在定时中断里面,不利于按键模块的独立封装,如果把该定时中断直接放到Key里面,那么Key就会独占这个定时器。综合考虑定义Key_Tick(void)函数。再把该函数放到定时器中断函数中,每隔1ms调用Key_Tick(void), 相当于Key模块多了个中断函数。它每隔1ms就会自动执行一次。实现多模块共用一个定时器来实现定时。
在这里插入图片描述
按键关灯变得灵敏的原因是按键扫描位于定时器中断里,即使主程序卡在Delay里面,定时器中断仍然能够执行,按键检测仍然能够执行。按键只要检测到了,就会置相应的标志位记录按键按下。

实现LED闪烁的非阻塞

LED以1s为周期,亮500ms,灭500ms
在这里插入图片描述
加入SetMode函数,用按键控制LED闪烁,如果不用的话,LED通过定时器无脑闪烁。

	while (1){KeyNum = Key_GetNum();if(KeyNum == 1){FlashFlag = !FlashFlag;}if(FlashFlag){LED1_SetMode(1);}else{LED1_SetMode(0);}OLED_ShowNum(1,1,i++,5);}
void LED1_SetMode(uint8_t Mode)
{LED1_Mode = Mode;
}void LED_Tick(void)
{if(LED1_Mode == 0){LED1_OFF();}else{LED1_Count++;//if(LED1_Count > 999) LED1_Count = 0;LED1_Count %= 1000;  //Count < 1000, 取余等于本身,等于1000,取余等于0,大于1000时,取余后会得到1000以内的余数,防止自增越界if(LED1_Count < 500){LED1_ON();}else{LED1_OFF();}}}

实验现象:
刚开始LED熄灭,主循环快速刷新
按下按键,LED闪烁
再按下按键,LED熄灭
根据OLED显示可知道主循环始终没有阻塞

继续完善代码,执行熄灭-常亮-慢闪-快闪-点闪,设置相应的状态机。

	if(LED1_Mode == 0){LED1_OFF();}else if(LED1_Mode == 1){LED1_ON();}else if(LED1_Mode == 2){LED1_Count++;//if(LED1_Count > 999) LED1_Count = 0;LED1_Count %= 1000;  //Count < 1000, 取余等于本身,等于1000,取余等于0,大于1000时,取余后会得到1000以内的余数,防止自增越界if(LED1_Count < 500){LED1_ON();}else{LED1_OFF();}}else if(LED1_Mode == 3){LED1_Count++;//if(LED1_Count > 999) LED1_Count = 0;LED1_Count %= 100;  //Count < 1000, 取余等于本身,等于1000,取余等于0,大于1000时,取余后会得到1000以内的余数,防止自增越界if(LED1_Count < 50){LED1_ON();}else{LED1_OFF();}}else{LED1_Count++;//if(LED1_Count > 999) LED1_Count = 0;LED1_Count %= 1000;  //Count < 1000, 取余等于本身,等于1000,取余等于0,大于1000时,取余后会得到1000以内的余数,防止自增越界if(LED1_Count < 100){LED1_ON();}else{LED1_OFF();}}

实现状态机轮转

		if(KeyNum == 1){LED1_MODE++;LED1_MODE %= 5;LED1_SetMode(LED1_MODE);}

如果想要每次模式切换后,闪烁都要从一个周期的最开始进行。需要额外添加代码。
在这里插入图片描述
非阻塞的代码可以保证主循环的快速执行,让每部分功能都能够得到及时响应。
注意:定时中断被多个模块复用,要确保这些模块的中断代码执行时间不要过久。
可能会出现中断重叠,如果要判断中断是否重叠,可以再进入中断的最开始就清除中断标志位。等结束之后再查看这个标志位,如果这时还没有被置1,说明中断没有重叠。

实验现象:
两个按键分别独立控制LED的亮灭以及闪烁,led始终刷新数字,主程序没有被阻塞。
在这里插入图片描述
全局变量,在主程序和中断中加入全局变量在多线程中加入互斥锁。

总结

  1. 定时器配置与中断机制
    定时器初始化:
    Timer_Init 函数配置 TIM2 定时器:

时钟源:内部时钟 72MHz。

预分频:72-1,使定时器时钟为 1MHz(72MHz / 72)。

周期:1000-1,定时器每 1ms 触发一次中断(1MHz 计数 1000 次)。

中断配置:使能更新中断,设置 NVIC 优先级。

中断服务函数:
TIM2_IRQHandler 每 1ms 执行一次:

调用 Key_Tick 和 LED_Tick 处理按键和 LED 状态。

清除中断标志,避免重复触发。

  1. 按键的非阻塞检测
    Key_Tick 函数:

20ms 消抖:通过静态变量 Count 累计中断次数,每 20ms 检测一次按键状态。

状态机逻辑:

CurrState 记录当前按键状态,PrevState 记录上一次状态。

检测按键释放瞬间(CurrState == 0 且 PrevState != 0),记录键值到 Key_Num。

非阻塞读取:主循环通过 Key_GetNum 获取键值后立即清零,避免重复触发。

  1. LED 的非阻塞控制
    LED_Tick 函数:

模式驱动:根据 LED1_Mode 和 LED2_Mode 控制 LED 行为:

模式 0:关闭。

模式 1:常亮。

模式 2:500ms 亮,500ms 灭(周期 1s)。

模式 3:50ms 亮,50ms 灭(周期 100ms)。

计数器机制:静态变量 LEDx_Count 在每次中断自增,通过取余运算实现周期性切换状态。

  1. 主循环的非阻塞特性
    主循环逻辑:

不断读取按键值 KeyNum,更新 LED 模式。

显示信息到 OLED,无需等待定时任务。

中断与主循环分工:

中断处理耗时短的任务(按键消抖、LED 状态切换)。

主循环处理非实时任务(如显示更新),避免被阻塞。

  1. 关键设计点
    时间片划分:定时器中断以 1ms 为基准,任务按需分频(如按键 20ms 检测一次)。

状态保持:使用静态变量(如 Count, LEDx_Count)保存任务状态,在中断间维持数据。

资源隔离:中断仅更新标志位或状态,主循环处理业务逻辑,降低耦合。

总结
通过定时器中断周期性触发任务,结合状态机和计数器机制,程序将耗时短且需周期性执行的操作(按键检测、LED 控制)放在中断中处理,主循环仅负责非实时任务(如显示更新)。这种设计确保了系统的高响应性和非阻塞特性。

补充(为什么不会阻塞)

LED_Tick() 函数在定时器中断(TIM2_IPQHandler)中被调用,而 LED1_Count++ 是中断服务程序(ISR)中的一个操作。LED1_Count++ 不会阻塞程序运行的原因与中断的机制和代码设计密切相关,以下是详细解释:

  1. 中断的抢占特性
    中断优先级:
    定时器中断(如 TIM2_IRQHandler)具有高于主循环的优先级。当定时器中断触发时,CPU 会立即暂停主循环的执行,跳转到中断服务函数中运行 LED_Tick()。

中断执行时间短:
LED_Tick() 中的操作(如 LED1_Count++、条件判断、LED 状态切换)均为简单操作,执行时间极短(通常在微秒级)。中断服务函数会快速完成并退出,释放 CPU 控制权,主循环随即恢复执行。

  1. 非阻塞设计的关键
    计数器自增的原子性:
    LED1_Count++ 是一个原子操作(在大多数嵌入式架构中,uint16_t 自增是单指令操作),不会被主循环打断。即使主循环正在修改 LED1_Mode,中断服务函数也能安全地更新 LED1_Count。

状态机与模式分离:

主循环:仅负责更新 LED1_Mode(通过按键触发),不直接操作硬件或耗时逻辑。

中断:根据 LED1_Mode 的值,通过 LED_Tick() 周期性更新 LED1_Count 和 LED 状态。
两者的职责分离,确保主循环不被阻塞。

  1. 代码中的具体实现
    LED_Tick() 的分支逻辑:

c
复制
else if(LED1_Mode == 2) {
LED1_Count++;
LED1_Count %= 1000;
if(LED1_Count < 500) LED1_ON();
else LED1_OFF();
}
快速执行:每个分支仅包含简单的算术运算(%)、比较和 GPIO 操作,无耗时操作(如延时、循环等待)。

计数器自增可控:LED1_Count 的范围通过 %= 1000 或 %= 100 限制,避免溢出问题。

  1. 主循环与中断的协作
    主循环无等待:
    主循环中的代码(如 OLED_ShowNum())无需等待 LED_Tick() 完成。即使中断频繁触发,主循环也能在中断间隙继续执行。

中断频率合理:
定时器中断周期为 1ms(由 TIM_Period 和 TIM_Prescaler 决定),中断处理时间远小于中断间隔,不会导致中断堆积或主循环饥饿。

  1. 数据一致性问题(额外注意事项)
    虽然 LED1_Count++ 本身不会阻塞,但需要注意 主循环和中断共享变量 的潜在风险:

LED1_Mode 的并发修改:
如果主循环正在修改 LED1_Mode(如 LED1_MODE++),而中断同时读取 LED1_Mode,可能导致数据不一致(如读到中间状态)。
解决方案:

使用原子操作或禁用中断保护共享变量:

c
复制
// 主循环中修改 LED1_Mode 时,临时禁用中断
__disable_irq();
LED1_MODE++;
__enable_irq();
将 LED1_Mode 声明为 volatile,防止编译器优化导致意外行为:

c
复制
volatile uint8_t LED1_MODE = 0;
总结
LED1_Count++ 不会阻塞程序,是因为:

中断服务函数执行时间极短(微秒级)。

主循环和中断职责分离,无耗时操作。

定时器中断频率合理,避免抢占主循环。

共享变量(如 LED1_Mode)需注意并发访问问题,但代码中未显式处理,可能存在潜在风险。

通过这种设计,LED 状态更新和主循环任务(如 OLED 显示、按键检测)可以并行执行,实现非阻塞的系统行为。

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

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

相关文章

Mybatisplus——Mybatisplus3.5.2版本使用Page分页插件查询,records有数据但是total显示0

目录 一、问题背景 debug 执行Mybatisplus使用Page分页插件查询时&#xff0c;发现 Page 里面的records有数据但是total显示0。 二、问题产生的原因 未配置MybatisPlus的分页插件拦截器导致的或者因mybatis-plus版本3.4或3.5版本导致原先的分页插件paginationInterceptor无法…

Windows安装 WSL2、Ubuntu 、docker(详细步骤 , 弃用 docker desktop )

前言 在现代软件开发领域&#xff0c;容器化技术已经成为提升应用部署效率和环境一致性的关键手段。Docker 作为一款卓越且被广泛应用的容器化平台&#xff0c;凭借其独特的技术架构&#xff0c;允许开发者将应用程序及其所需的全部依赖项&#xff0c;完整地打包进一个高度可移…

移动端测试的挑战与解决方案:兼容性、网络问题及实战策略

引言 移动应用已成为用户触达服务的核心入口,但移动端测试面临设备多样性、网络波动、用户场景复杂等多重挑战。据Statista统计,2023年全球活跃移动设备超180亿台,操作系统(Android/iOS)版本碎片化率超30%,这对测试工程师提出了极高要求。本文深度解析移动端测试的核心痛…

kron积计算mask类别矩阵

文章目录 1. 生成类别矩阵如下2. pytorch 代码3. 循环移动矩阵 1. 生成类别矩阵如下 2. pytorch 代码 import torch import torch.nn as nn import torch.nn.functional as Ftorch.set_printoptions(precision3, sci_modeFalse)if __name__ "__main__":run_code 0…

DeepSeek 概述与本地化部署【详细流程】

目录 一、引言 1.1 背景介绍 1.2 本地化部署的优势 二、deepseek概述 2.1 功能特点 2.2 核心优势 三、本地部署流程 3.1 版本选择 3.2 部署过程 3.2.1 下载Ollama 3.2.2 安装Ollama 3.2.3 选择 r1 模型 3.2.4 选择版本 3.2.5 本地运行deepseek模型 3.3.6 查看…

foobar2000设置DSP使用教程及软件推荐

foobar2000安卓中文版&#xff1a;一款高品质手机音频播放器 foobar2000安卓中文版是一款备受好评的高品质手机音频播放器。 几乎支持所有的音频格式&#xff0c;包括 MP3、MP4、AAC、CD 音频等。不论是经典老歌还是最新的流行音乐&#xff0c;foobar2000都能完美播放。除此之…

制作一个项目用于研究elementUI的源码

需求&#xff1a;修改el-tooltip的颜色&#xff0c;发现传递参数等方法都不太好用&#xff0c;也可以使用打断点的方式&#xff0c;但也有点麻烦&#xff0c;因此打算直接修改源码&#xff0c;把组件逻辑给修改了 第一步下载源码 源码地址 GitHub - ElemeFE/element: A Vue.j…

DDoS技术解析

这里是Themberfue 今天我们不聊别的&#xff0c;我们聊聊著名的网络攻击手段之一的 DDoS&#xff0c;看看其背后的技术细节。 DoS 了解 DDoS 前&#xff0c;先来讲讲 DoS 是什么&#xff0c;此 DoS 而不是 DOS 操作系统啊。1996年9月6日&#xff0c;世界第三古老的网络服务提供…

【学习资源】时间序列数据分析方法(1)

时间序列数据分析是一个有趣的话题&#xff0c;让我们多花一些时间来研究。此篇为第一篇文章。主要介绍特征提取方法、深度学习时序数据分析模型、参考资源。期望能帮助大家解决工业领域的相关问题。 1 特征提取方法&#xff1a;信号处理 (来源:INTELLIGENT FAULT DIAGNOSIS A…

0基础学LabVIEW

对于零基础的朋友来说&#xff0c;学习LabVIEW需要一个科学的学习路径和方法。通过观看优质的B站教程打好基础&#xff0c;再结合实际项目进行实践操作&#xff0c;能够快速提升LabVIEW的应用能力。以下是从入门到进阶的学习建议。 ​ 一、利用B站入门教程打基础 筛选优质教程…

微软AutoGen高级功能——Selector Group Chat

介绍 大家好&#xff0c;这次给大家分享的内容是微软AutoGen框架的高级功能Selector Group Chat(选择器群聊)&#xff0c;"选择器群聊"我在给大家分享的这篇博文的代码中有所体现微软AutoGen介绍——Custom Agents创建自己的Agents-CSDN博客&#xff0c;但是并没有详…

高通推出骁龙游戏超级分辨率™:充分释放移动游戏性能,带来更持久的续航

Snapdragon Elite Gaming 一直致力于为每位用户打造卓越游戏体验。骁龙支持众多端游级特性&#xff0c;包括144FPS游戏体验、True 10-bit HDR支持的最高视觉质量的超流畅图形&#xff0c;让玩家可以畅享超10亿色的游戏体验。骁龙将许多移动端首创特性引入备受玩家喜爱的游戏中&…

HCIA项目实践--RIP的拓展配置

9.4.7 RIP的拓展配置 &#xff08;1&#xff09;RIPV2的手工认证 RIPv2 的手工认证是增强网络安全性的手段。管理员手动配置密钥&#xff0c;路由器在收发 RIPv2 路由更新消息时&#xff0c;会对消息中的认证信息进行检查。发送方添加密钥&#xff0c;接收方用预设密钥验证。若…

Jenkins 配置 Git Repository 五

Jenkins 配置 Git Repository 五 这里包含了 Freestyle project 任务类型 和 Pipeline 任务类型 关于 Git 仓库的配置&#xff0c;如下 不同的任务类型&#xff0c;只是在不同的模块找到 配置 Git 仓库 找到 Git 仓库配置位置之后&#xff0c;所有的任务类型配置都是一样的 …

Python + WhisperX:解锁语音识别的高效新姿势

大家好&#xff0c;我是烤鸭&#xff1a; 最近在尝试做视频的质量分析&#xff0c;打算利用asr针对声音判断是否有人声&#xff0c;以及识别出来的文本进行进一步操作。asr看了几个开源的&#xff0c;最终选择了openai的whisper&#xff0c;后来发现性能不行&#xff0c;又换了…

红队视角出发的k8s敏感信息收集——持久化存储与数据泄露

在Kubernetes集群中&#xff0c;持久化存储卷如同数据的保险箱&#xff0c;承载着应用运行所必需的各类敏感信息。然而&#xff0c;从红队视角出发&#xff0c;这些存储卷也可能成为攻击者觊觎的目标。通过巧妙地利用配置不当或已知漏洞&#xff0c;攻击者能够从中收集到包括密…

微信服务号推送消息

这里如果 没有 就需要点新的功能去申请一下 申请成功之后就可以设置模版消息 推送到用户接受的页面是 需要后端调用接口 传递token 发送给客户

[Spring] Spring常见面试题

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

Edge浏览器清理主页

我们都知道&#xff0c;Microsoft Edge浏览器是微软创造的搜索浏览器&#xff0c;Windows10、11自带。但是你可以看到&#xff0c;每次你打开Edge浏览器的时候都可以看到许多的广告&#xff0c;如图&#xff1a; 导致打开Edge浏览器的时候会遭受卡顿&#xff0c;广告骚扰&#…

【编写UI自动化测试集】Appium+Python+Unittest+HTMLRunner​

简介 获取AppPackage和AppActivity 定位UI控件的工具 脚本结构 PageObject分层管理 HTMLTestRunner生成测试报告 启动appium server服务 以python文件模式执行脚本生成测试报告 下载与安装 下载需要自动化测试的App并安装到手机 获取AppPackage和AppActivity 方法一 …