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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Arxiv 大模型最新进展】PEAR: 零额外推理开销,提升RAG性能!(★AI最前线★)

【Arxiv 大模型最新进展】PEAR: 零额外推理开销&#xff0c;提升RAG性能&#xff01;&#xff08;★AI最前线★&#xff09; &#x1f31f; 嗨&#xff0c;你好&#xff0c;我是 青松 &#xff01; &#x1f308; 自小刺头深草里&#xff0c;而今渐觉出蓬蒿。 NLP Github 项目…

vscode的一些实用操作

1. 焦点切换(比如主要用到使用快捷键在编辑区和终端区进行切换操作) 2. 跳转行号 使用ctrl g,然后输入指定的文件内容&#xff0c;即可跳转到相应位置。 使用ctrl p,然后输入指定的行号&#xff0c;回车即可跳转到相应行号位置。

OAI 平台 4G(LTE)基站 、终端、核心网 端到端部署实践(一)

本系列文章,基于OAI LTE代码搭建端到端运行环境,包含 eNB,EPC,UE三个网元。本小节先介绍系统总体架构,硬件平台及驱动安装方法。 1. Overview 系统总体架构如下图所示。 2 Machine setup 2.1 Machine specs Distributor ID: Ubuntu Description: Ubuntu 18.04.5 LTS…

Linux环境Docker使用代理推拉镜像

闲扯几句 不知不觉已经2月中了&#xff0c;1个半月忙得没写博客&#xff0c;这篇其实很早就想写了&#xff08;可追溯到Docker刚刚无法拉镜像的时候&#xff09;&#xff0c;由于工作和生活上的事比较多又在备考软考架构&#xff0c;拖了好久…… 简单记录下怎么做的&#xf…

基于TI的TDA4高速信号仿真条件的理解 4.6

Application Note 《Jacinto7 AM6x, TDA4x, and DRA8x High-Speed Interface Design Guidelines》 4.6 Reviewing Simulation Results检查仿真结果 The results generated by the channel simulations outlined in the preceding sections are compared against an eye mask s…

unity学习46:反向动力学IK

目录 1 正向动力学和反向动力学 1.1 正向动力学 1.2 反向动力学 1.3 实现目标 2 实现反向动力 2.1 先定义一个目标 2.2 动画层layer&#xff0c;需要加 IK pass 2.3 增加头部朝向代码 2.3.1 专门的IK方法 OnAnimatorIK(int layerIndex){} 2.3.2 增加朝向代码 2.4 …

DeepSeek 和 ChatGPT 在特定任务中的表现:逻辑推理与创意生成

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux网络编程 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 ​ Linux网络编程笔记&#xff1a; https://blog.cs…

DAY07 Collection、Iterator、泛型、数据结构

学习目标 能够说出集合与数组的区别数组:1.是引用数据类型的一种2.可以存储多个元素3.数组的长度是固定的 int[] arr1 new int[10]; int[] arr2 {1,2,3};4.数组即可以存储基本类型的数据,又可以存储引用数据类型的数据int[],double[],String[],Student[]集合:1.是引用数据类…

ls命令的全面参数解析与详尽使用指南

目录 ls 命令的所有参数及含义 ls -a 命令详解 ls -A 命令详解 ls -b 命令详解 ls -C 命令详解 ls -d 命令详解 ls -f 命令详解 ls -F 命令详解 ls -G 命令详解 ls -h 命令详解 ls -H 命令详解 ls -i 命令详解 ls -I 命令详解 ls -l 命令详解 ls -L 命令详解 l…

【Spring+MyBatis】_图书管理系统(中篇)

【SpringMyBatis】_图书管理系统&#xff08;上篇&#xff09;-CSDN博客文章浏览阅读654次&#xff0c;点赞4次&#xff0c;收藏7次。&#xff08;1&#xff09;当前页的内容records&#xff08;类型为List&#xff09;&#xff1b;参数&#xff1a;userNameadmin&&pas…

动态规划算法篇:枚举的艺术

那么本篇文章就正式进入了动态规划的算法的学习&#xff0c;那么动态规划算法也可谓是算法内容中的一座大山&#xff0c;那么在大厂算法笔试乃至算法比赛中出现的频率也逐渐变高&#xff0c;那么可见学习好动态规划算法的一个重要性&#xff0c;那么对于动态规划最难理解的&…

算法——舞蹈链算法

一&#xff0c;基本概念 算法简介 舞蹈链算法&#xff08;Dancing Links&#xff0c;简称 DLX&#xff09;是一种高效解决精确覆盖问题的算法&#xff0c;实际上是一种数据结构&#xff0c;可以用来实现 X算法&#xff0c;以解决精确覆盖问题。由高德纳&#xff08;Donald E.…

翻转硬币(思维题,巧用bitset)

0翻转硬币 - 蓝桥云课 #include <bits/stdc.h> using namespace std; bitset<200000001> t; int main() {int n;cin>>n;int ans1;t[1]1;int totn-1;for(int i2;i<n;i){if(t[i]) continue;int ji;ans;while(j<n){t[j]!t[j];if(t[j]) tot--;else tot;ji;…

网络安全等级保护测评(等保测评):全面指南与准备要点

等保测评&#xff0c;全称为“网络安全等级保护测评”&#xff0c;是根据《网络安全法》及《网络安全等级保护条例》等法律法规&#xff0c;对信息系统进行安全等级划分&#xff0c;并依据不同等级的安全保护要求&#xff0c;采用科学方法和技术手段&#xff0c;全面评估信息系…

blackbox.ai 一站式AI代理 畅享顶级模型

最近Deepseek火遍大江南北&#xff0c;一夜之间到处都能看到有人在体验AI技术。然而这也带来了一些困难&#xff1a;由于服务器压力过大&#xff0c;ds开始使用了一些限流的措施。 实际上这只是针对免费用户的限制手段&#xff0c;通过API付费方式的用户并没有这样的限制。所以…

ERP对制造业务有何价值?

ERP 的定义 在定义 ERP 之前&#xff0c;我们先从其首字母缩写说起&#xff0c;ERP 代表企业资源规划。我们可以将 ERP 定义为一种企业软件&#xff0c;它帮助组织管理日常业务。从根本上讲&#xff0c;ERP 将客户管理、人力资源、商业智能、财务管理、库存以及供应链功能整合…

(新版本onenet)stm32+esp8266/01s mqtt连接onenet上报温湿度和远程控制(含小程序)

物联网实践教程&#xff1a;微信小程序结合OneNET平台MQTT实现STM32单片机远程智能控制 远程上报和接收数据——汇总 前言 之前在学校获得了一个新玩意&#xff1a;ESP-01sWIFI模块&#xff0c;去搜了一下这个小东西很有玩点&#xff0c;远程控制LED啥的&#xff0c;然后我就想…

详解 本机安装多个MySQL服务【为后续大数据量分库分表奠定基础,以mysql8.0为例,附有图文】

本机安装多个mysql 在电脑上新建mysql8文件夹&#xff0c;然后在mysql8文件下新建mysql3391文件夹。然后找到自己原本mysql的安装目录&#xff0c;我的是E:\software\mysql\one&#xff0c;如图所示&#xff1a; 将次目录下的所有文件全选复制粘贴在mysql3391文件夹下。 然后…

组学数据分析实操系列 |(四) 富集气泡图的绘制

前言:在上一篇中&#xff0c;我们介绍了利用Metascape零代码实现富集分析&#xff0c;但是Metascape的富集分析结果是以柱状图的形式展示的。文章中更常使用的富集结果可视化方式是气泡图。气泡图可以通过气泡的坐标、形状、颜色、大小等来展示更加丰富的富集分析结果&#xff…

浏览器开发者工具(F12)查看请求的响应体内容显示”无法加载响应数据: No resource with given identifier found“

背景 复习在 SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架中&#xff0c;点击登录请求后返回 JSON 格式的数据&#xff0c;出现只有登录失败的请求才有响应值&#xff0c;比如&#xff1a; {success: false, message: “没有此用户”, code: 400} 而成功的请求…