当前位置: 首页 > article >正文

第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 显示、按键检测)可以并行执行,实现非阻塞的系统行为。

相关文章:

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

第1期 定时器实现非阻塞式程序 按键控制LED闪烁模式 解决按键扫描&#xff0c;松手检测时阻塞的问题实现LED闪烁的非阻塞总结补充&#xff08;为什么不会阻塞&#xff09; 参考江协科技 KEY1和KEY2两者独立控制互不影响 阻塞&#xff1a;如果按下按键不松手&#xff0c;程序就…...

开源语音克隆项目 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%。如…...

CloudberryDB(六)SPI拓展功能

SPI&#xff08;Server Programming Interface&#xff09;在实现原理主要涉及以下几个方面&#xff1a; 1. **模块化设计**&#xff1a;SPI模块是内核中的一个独立模块&#xff0c;允许内核开发者在C函数中执行SQL语句&#xff0c;并管理事务。这种设计使得开发者可以在不修改…...

非谓语动词三驾马车

文章目录 1. 不定式基本结构不定式的由来1.不受主语的人称和数的限制2.没有限定时态3.可以在句子中充当不同的成分 常见句子成分1. 作主语2. 作表语3. 作宾语4. 作定语5. 作状语 不定式 vs 动名词 2. 动名词动名词做成分作主语作主语补语作定语作宾语介词宾语 3. 分词(现在、过…...

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

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

Lineageos 22.1(Android 15)更换开机动画

一、原理简介 我们直接用最简单的替换zip的方式来更换开机动画&#xff0c;首先我们要查看系统代码使用的zip包的路径&#xff0c;可能与aosp原生的代码不一定一样。 /frameworks/base/cmds/bootanimation/BootAnimation.cpp bool BootAnimation::threadLoop() {ATRACE_CALL(…...

大语言模型推理中的显存优化 有哪些

大语言模型推理中的显存优化 有哪些 目录 大语言模型推理中的显存优化 有哪些显存优化背景Offloading/Checkpoint原理举例显存优化背景 在大语言模型推理时,显存是显著瓶颈。以开源的BLOOM 176B模型为例,在8张A100计算卡上,通常对话设置下仅能进行批量为10左右的推理。为缓…...

更高效实用 vscode 的常用设置

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

【异或数列——博弈论】

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

阿里云大文件ossutil工具进行上传下载,该工具支持断点续传

ossutil工具进行上传下载&#xff0c;该工具支持断点续传、文件分片、多文件同时上传等 下载和配置 https://help.aliyun.com/zh/oss/developer-reference/install-ossutil 上传操作 https://help.aliyun.com/zh/oss/developer-reference/upload-objects-6 下载操作 https://h…...

草图绘制技巧

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;同时确保知识资产在多终端环境下的无缝适配与安全共享。这…...

QGIS热力图制作全流程详解

一、热力图的概念与应用 热力图&#xff08;Heatmap&#xff09;是一种通过颜色梯度展示空间数据密度的可视化工具&#xff0c;常用于分析点数据的聚集程度。例如&#xff0c;犯罪热点、人口分布、交通流量等场景均可通过热力图直观呈现。QGIS作为开源GIS软件&#xff0c;支持…...

切换镜像源(npm)

常见的npm镜像源 官方源 URL: https://registry.npmjs.org 淘宝镜像源&#xff08;npmmirror&#xff09; URL: https://registry.npmmirror.com 其他常用镜像源 URL: https://registry.cnpmjs.org (CNPM) 这里是引用 切换npm镜像源 切换到官方源 npm config set registry http…...

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;可以参考相关概率论与数理统计的…...

Go框架面试突击!30道高频题解析

前言 有粉丝朋友问我能不能整理Go主流框架方面的面试题&#xff0c;安排&#xff01; 这篇文章分享了gRPC、GoFrame、GoZero、GoMicro、GORM、Gin等主流框架的30道面试题和详解。 需要大厂面经的朋友们也可以直接加我好友&#xff0c;私信我。 gRPC 1.gRPC是什么&#xff…...

从VGG到Transformer:深度神经网络层级演进对模型性能的深度解析与技术实践指南

一、技术原理&#xff08;数学公式示意图&#xff09; 1. 层深与模型容量关系 数学表达&#xff1a;根据Universal Approximation Theorem&#xff0c;深度网络可表达复杂函数&#xff1a; f ( x ) f L ( f L − 1 ( ⋯ f 1 ( x ) ) ) f(x) f_L(f_{L-1}(\cdots f_1(x))) f…...

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…...

【ISO 14229-1:2023 UDS诊断全量测试用例清单系列:第十二节】

ISO 14229-1:2023 UDS诊断服务测试用例全解析&#xff08;TesterPresent_0x3E服务&#xff09; 作者&#xff1a;车端域控测试工程师 更新日期&#xff1a;2025年02月14日 关键词&#xff1a;UDS协议、0x3E服务、会话保持、ISO 14229-1:2023、ECU测试 一、服务功能概述 0x3E服…...

李宏毅机器学习笔记:【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通过引入一维卷积技术&…...

Linux内核 - 非仿生机器人之感知主控系统(协议栈)

Linux内核 - 非仿生机器人之感知主控系统&#xff08;协议栈&#xff09; 注&#xff1a;该项目为18年实习期间&#xff0c;参与非仿生六足机器人&#xff08;Linux方案&#xff09;的个人理解和积累。时至今日&#xff0c;再看其实仅为一套系统编程相关框架&#xff0c;一直为…...

Node.js 工具模块

Node.js 工具模块 引言 Node.js 是一个开源的、基于 Chrome V8 引擎的 JavaScript 运行时环境。它允许开发者使用 JavaScript 编写服务器端代码,从而构建快速、可扩展的网络应用。在 Node.js 开发过程中,工具模块扮演着至关重要的角色。本文将详细介绍 Node.js 中常用的工具…...