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

news2025/2/21 18:11:37

第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.coloradmin.cn/o/2299413.html

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

相关文章

开源语音克隆项目 OpenVoice V2 本地部署

#本机环境 WIN11 I5 GPU 4060ti 16G 内存 32G #开始 git clone https://github.com/myshell-ai/OpenVoice.git conda create -n opvenv python3.9 -y conda activate opvenv pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/…

DeepSeek大模型一键部署解决方案:全平台多机分布式推理与国产硬件优化异构计算私有部署

DeepSeek R1 走红后&#xff0c;私有部署需求也随之增长&#xff0c;各种私有部署教程层出不穷。大部分教程只是简单地使用 Ollama、LM Studio 单机运行量化蒸馏模型&#xff0c;无法满足复杂场景需求。一些操作配置也过于繁琐&#xff0c;有的需要手动下载并合并分片模型文件&…

如何利用PLM软件有效地推进制造企业标准化工作?

在智能制造浪潮的推动下&#xff0c;中国制造业正面临从“规模扩张”向“质量提升”的关键转型。工信部数据显示&#xff0c;85%的制造企业在产品研发、生产过程中因标准化程度不足导致效率损失超20%&#xff0c;而标准化水平每提升10%&#xff0c;企业综合成本可降低5%-8%。如…

环境影响评价(EIA)中,土地利用、植被类型及生态系统图件的制作

在环境影响评价&#xff08;EIA&#xff09;中&#xff0c;土地利用、植被类型及生态系统图件的制作需依据科学、法规和技术规范&#xff0c;以确保数据的准确性和图件的规范性。以下是主要的制作依据&#xff1a; 1. 法律法规与政策依据 《中华人民共和国环境影响评价法》 明确…

更高效实用 vscode 的常用设置

VSCode 可以说是文本编辑神器, 不止程序员使用, 普通人用其作为文本编辑工具, 更是效率翻倍. 这里分享博主对于 VSCode 的好用设置, 让 VSCode 如虎添翼 进入设置 首先进入设置界面, 后续都在这里进行配置修改 具体设置 每项配置通过搜索关键字, 来快速定位配置项 自动保存…

【异或数列——博弈论】

题目 思路 异或和为0&#xff08;即每一位都有偶数个1&#xff09;&#xff1a;平局最高有效位只有唯一的1&#xff1a;先手必胜最高有效位有奇数个1&#xff0c;偶数个0&#xff1a;先手必胜 若先选1产生优势&#xff0c;则剩下偶数个1&#xff0c;偶数个0&#xff1a;对手选…

草图绘制技巧

1、点击菜单栏文件–》新建–》左下角高级新手切换–》零件&#xff1b; 2、槽口&#xff1a;直槽口&#xff0c;中心点槽口&#xff0c;三点源槽口&#xff0c;中心点圆弧槽口&#xff1b; 3、草图的约束&#xff1a;需要按住ctrl键&#xff0c;选中两个草图&#xff0c;然后…

Spring Boot中如何自定义Starter

文章目录 Spring Boot中如何自定义Starter概念和作用1. 概念介绍2. 作用和优势2.1 简化依赖管理2.2 提供开箱即用的自动配置2.3 标准化和模块化开发2.4 提高开发效率2.5 提供灵活的配置覆盖3. 应用场景创建核心依赖1. 确定核心依赖的作用2. 创建 starter-core 模块2.1 依赖管理…

内容中台构建高效数字化内容管理新范式

内容概要 在数字化转型浪潮中&#xff0c;高效的内容管理能力已成为企业构建核心竞争力的关键要素。通过动态发布引擎、元数据智能分类与跨平台协作机制&#xff0c;企业能够实现内容的实时触达与精准分发&#xff0c;同时确保知识资产在多终端环境下的无缝适配与安全共享。这…

PyQt组态软件 拖拽设计界面测试

PyQt组态软件测试 最近在研究PyQt,尝试写个拖拽设计界面的组态软件&#xff0c;目前实现的功能如下&#xff1a; 支持拖入控件&#xff0c;鼠标拖动控件位置 拖动控件边缘修改控件大小支持属性编辑器&#xff0c;修改当前选中控件的属性 拖动框选控件&#xff0c;点选控件 控…

深度学习R4周:LSTM-火灾温度预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 任务&#xff1a; 数据集中提供了火灾温度&#xff08;Tem1&#xff09;、一氧化碳浓度&#xff08;CO 1&#xff09;烟雾浓度&#xff08;Soot 1&#xff09;…

Datawhale 数学建模导论二 笔记1

第6章 数据处理与拟合模型 本章主要涉及到的知识点有&#xff1a; 数据与大数据Python数据预处理常见的统计分析模型随机过程与随机模拟数据可视化 本章内容涉及到基础的概率论与数理统计理论&#xff0c;如果对这部分内容不熟悉&#xff0c;可以参考相关概率论与数理统计的…

UIView 与 CALayer 的联系和区别

今天说一下UIView 与 CALayer 一、UIView 和 CALayer 的关系 在 iOS 开发中&#xff0c;UIView 是用户界面的基础&#xff0c;它负责处理用户交互和绘制内容&#xff0c;而 CALayer 是 UIView 内部用于显示内容的核心图层&#xff08;Layer&#xff09;。每个 UIView 内部都有…

一键安装教程

Maven 安装 右键 以管理员身份运行点击 下一步安装完成后会同步配置环境变量打开 cmd, 输入 mvn 查看mvn版本修改 maven 本地仓库地址 见图三, 本地新建文件夹&#xff0c;修改为你本地文件夹地址 Redis 安装 右键 以管理员身份运行点击 下一步会安装到选择的文件夹下 JAVA\R…

李宏毅机器学习笔记:【6.Optimization、Adaptive Learning Rate】

Optimization 1.Adaptive Learning Rate2.不同的参数需要不同的学习率3.Root Mean Square4.RMSProp5.Adam6.learning rate scheduling7.warm up总结 critical point不一定是你在训练一个network时候遇到的最大的障碍。 1.Adaptive Learning Rate 也就是我们要给每个参数不同的…

vscode使用常见问题处理合集

目录 一、使用vite创建的vue3项目&#xff0c;script和style首行代码不会缩进,且格式化属性字段等会换行问题 首行缩进情况如下&#xff1a; 属性、参数格式化换行情况如下&#xff1a; 解决方式&#xff1a; 一、使用vite创建的vue3项目&#xff0c;script和style首行代码不…

【技术解析】MultiPatchFormer:多尺度时间序列预测的全新突破

今天给我大家带来一篇最新的时间序列预测论文——MultiPatchFormer。这篇论文提出了一种基于Transformer的创新模型&#xff0c;旨在解决时间序列预测中的关键挑战&#xff0c;特别是在处理多尺度时间依赖性和复杂通道间相关性时的难题。MultiPatchFormer通过引入一维卷积技术&…

【网络安全 | 漏洞挖掘】价值3133美元的Google IDOR

未经许可,不得转载。 文章目录 正文正文 目标URL:REDACTED.google.com。 为了深入了解其功能,我查阅了 developer.google.com 上的相关文档,并开始进行测试。 在测试过程中,我发现了一个 XSS 漏洞,但它触发的域名是经过正确沙盒化的 *.googleusercontent.com,这符合 …

大脑网络与智力:基于图神经网络的静息态fMRI数据分析方法|文献速递-医学影像人工智能进展

Title 题目 Brain networks and intelligence: A graph neural network based approach toresting state fMRI data 大脑网络与智力&#xff1a;基于图神经网络的静息态fMRI数据分析方法 01 文献速递介绍 智力是一个复杂的构念&#xff0c;包含了多种认知过程。研究人员通…

Python使用OpenCV图片去水印多种方案实现

1. 前言 本文为作者学习记录&#xff0c;使用Python结合OpenCV&#xff0c;总结了几种常见的水印去除方式&#xff0c;简单图片去水印效果良好&#xff0c;但是复杂图片有点一言难尽&#xff0c;本文部分代码仅供参考&#xff0c;并不能针对所有水印通用&#xff0c;需要根据具…