延时算法进阶

51单片机 利用定时中断做“非阻塞式”点灯 前言 很多程序员的第一句代码,估计都是“hello world”。 在电子行业里,软件工程师的第一个成(入)功(坑)代码,估计都是从点亮第一颗LED灯讲起。 今天的自学笔记记录的例程是如何利用定时中断,来做一个“非阻塞式”的闪灯程序。 这节内容,看似简单,实际项目中却有很多地方可以应用,比如模拟PWM波形,还有接下来要写的 按键消抖清零 应用 一、 “阻塞”和“非阻塞”是什么? 首先名词解释一下: [ “阻塞” 阅读详情
  1. 简介

  2. 阻塞延时

  3. 非阻塞延迟

  4. 非阻塞延迟拓展

  5. 心得体会

  • 1、简介

今天利用上课的摸鱼时间,学习了一下延时发现其中有挺多学问,同样也有些规律,以此记录下自己的心得。

  • 2、阻塞延时

void DelayXms(unsigned int xms)	
{
	unsigned char i, j;
	
	while (xms--)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
	}
}

上面是我们一般用到的阻塞延迟,它是很简单,很方便甚至在stc-isp可以直接生成(我绝对不会告诉你这个就是在stc-isp上生成的),但是它也有缺点,就是它需要等待for循环的完成,这样就很不优雅,作为时间管理大师的我肯定是无法忍受的,那么怎么才能够一“芯”多用,让多个任务“同时”运行呢?那就要使用接下来说的非阻塞延迟。

  • 3、非阻塞延迟

#include "reg52.h"

#define _DELAY_TIME 1000        //宏定义延迟时间
unsigned int u16_cnt;           //时间积累变量
unsigned char u8_delay_flag;    //延迟标志

/*延迟函数*/
void _DELAY()
{
	u16_cnt = _DELAY_TIME;
	if(u16_cnt)
	{
		u16_cnt--;
		if(!u16_cnt)
		{
			u8_delay_flag = 1;
		}
	}
	if(u8_delay_flag == 1)
	{
		u8_delay_flag = 0;
		u16_cnt = _DELAY_TIME;
		//执行语句
	}
}

以上就是最简单的非阻塞函数,其实并不难理解。

首先要判断什么时候所需时间到了,先定义一个变量,赋给它我们要延迟的时间,进入if判断语句,当时间积累变量不为0时,让它减少,当它为0时,将标志位置1。

那么要如何实现我们所需的功能?这就需要另一个if判断语句,当标志位为1时,将标志位置0,防止多次触发,将时间积累变量重置,然后实现我们所需的功能。

那它又是如何实现非阻塞的呢?我们将这个函数放入while循环里,每次循环都会对时间积累变量进行一次判断,如果成立往下走程序,如果不成立,跳出if判断,回到while循环,我们将之前for循环需要一次执行完的程序,分解为多个判断语句,使得我们不需要去等待,以实现非阻塞。

但这不是完美的,当我们的任务变多,if判断语句就会增加,我们需要在DELAY函数里花费的时间就会变多,导致我们的延时变得不精准,那有没有什么办法既能实现非阻塞延迟又能获得一个相对精准的时间呢?这时大家一定想到了定时器,没错!当我们加上定时器,就能解决以上问题。

  • 4、非阻塞延迟扩展

  • 定时器

#include "reg52.h"

/*外部变量声明*/
extern unsigned int g_u16_time_cnt;      //时间计数值
extern unsigned char g_u8_time_flag;	 //时间标志位

unsigned int g_u16_time_cnt;             //时间计数值
unsigned char g_u8_time_flag;	         //时间标志位

#define  time_delay 1000

void Timer0_Init(void)		             //1毫秒@12.000MHz
{
	TMOD &= 0xF0;			            //设置定时器模式
	TMOD |= 0x01;			            //设置定时器模式
	TL0 = 0x18;				            //设置定时初始值 (65536 - 1000)%256 1000 = 延迟时间/机械周期 机械周期 = 12*(1/晶振频率)
	TH0 = 0xFC;				            //设置定时初始值	(65536 - 1000)/256
	TF0 = 0;				            //清除TF0标志
	ET0 = 1;				            //使能定时器0中断
	EA = 1;					            //使能中断总开关
	TR0 = 1;				            //定时器0开始计时

}

void Timer0_Isr(void) interrupt 1
{
	TR0 = 0;					        //关闭定时计数器
	if(g_u16_time_cnt)
	{
		g_u16_time_cnt--;
		if(!g_u16_time_cnt)
		{
			g_u8_time_flag = 1;
			TL0 = 0x18;				    //设置定时初始值
			TH0 = 0xFC;				    //设置定时初始值
		}
	}
	TL0 = 0x18;				            //设置定时初始值
	TH0 = 0xFC;				            //设置定时初始值
	TR0 = 1;					        //打开定时计数器
}

void main()
{
	g_u16_time_cnt = time_delay;
	g_u8_time_flag = 0;
	Timer0_Init();
	while(1)
	{
		if(1 == g_u8_time_flag)
		{
			g_u8_time_flag = 0;
			//执行程序
			g_u16_time_cnt = time_delay;
		}
	}
}

这段代码其实和上面那部分代码并没有什么区别,只是加入了定时器,这里就不过多赘述,不过这里需要注意,中断的时机不能过短,否则会频繁进入中断影响主程序。

  • 多任务并行

当然我们实现功能时,肯定不可能只有一个点灯(或者点亮一颗CPU),以51单片机为例,它只内带了两个定时器,那我们需要多个定时器时,两个肯定是不够用的,那么我们就可以多写几个软件定时器,以满足我们的需求。

/*外部变量声明*/
extern unsigned int g_u16_time1_cnt;  //时间计数值
extern bit g_u8_time1_flag;	//时间标志位
extern unsigned int g_u16_time2_cnt;  //时间计数值
extern bit g_u8_time2_flag;	//时间标志位

void Timer0_Init(void)		//1毫秒@12.000MHz
{
	TMOD &= 0xF0;			//设置定时器模式
	TMOD |= 0x01;			//设置定时器模式
	TL0 = 0x18;				//设置定时初始值 (65536 - 1000)%256 1000 = 延迟时间/机械周期 机械周期 = 12*(1/晶振频率)
	TH0 = 0xFC;				//设置定时初始值	(65536 - 1000)/256
	TF0 = 0;				//清除TF0标志
	ET0 = 1;				//使能定时器0中断
	EA = 1;					//使能中断总开关
	TR0 = 1;				//定时器0开始计时

}

void Timer0_Isr(void) interrupt 1
{
	TR0 = 0;					//关闭定时计数器
	if(g_u16_time1_cnt)
	{
		g_u16_time1_cnt--;
		if(!g_u16_time1_cnt)
		{
			g_u8_time1_flag = 1;
			TL0 = 0x18;				//设置定时初始值
			TH0 = 0xFC;				//设置定时初始值
		}
	}
		if(g_u16_time2_cnt)
	{
		g_u16_time2_cnt--;
		if(!g_u16_time2_cnt)
		{
			g_u8_time2_flag = 1;
			TL0 = 0x18;				//设置定时初始值
			TH0 = 0xFC;				//设置定时初始值
		}
	}
	TL0 = 0x18;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TR0 = 1;					//打开定时计数器
}

接下来是任务的实现

#include "reg52.h"
#include "intrins.h"

/*管脚定义*/
sbit P10 = P1^0;
sbit P11 = P1^1;
sbit P12 = P1^2;
sbit P13 = P1^3;
sbit P14 = P1^4;
sbit P15 = P1^5;
sbit P16 = P1^6;
sbit P17 = P1^7;

sbit P20 = P2^0;
sbit P21 = P2^1;
sbit P22 = P2^2;
sbit P23 = P2^3;
sbit P24 = P2^4;
sbit P25 = P2^5;
sbit P26 = P2^6;
sbit P27 = P2^7;

/*延迟时间定义*/
#define _TIME1 1000
#define _TIME2 1000

void _init(void)
{
	g_u16_time1_cnt = _TIME1;//设置时间积累量
	g_u8_time1_flag = 0;//初始化时间标志位
	
	g_u16_time2_cnt = _TIME2;
	g_u8_time2_flag = 0;
}

void _task1(void)
{
	static unsigned char u8_step = 0;
	switch(u8_step)
	{
		case 0:
			if(_testbit_(g_u8_time1_flag))
			{
				g_u16_time1_cnt = _TIME1;
				P10 = 1;
				P11 = 1;
				P12 = 1;
				P13 = 1;
				P14 = 1;
				P15 = 1;
				P16 = 1;
				P17 = 1;
				u8_step = 1;
			}
			break;
		case 1:
			if(_testbit_(g_u8_time1_flag))
			{
				g_u16_time1_cnt = _TIME1;
				P10 = 1;
				P11 = 1;
				P12 = 1;
				P13 = 1;
				P14 = 1;
				P15 = 1;
				P16 = 1;
				P17 = 1;
				u8_step = 0;
			}
			break;
		default:
			break;
	}
}

void _task2(void)
{
	static unsigned char u8_step = 0;
	switch(u8_step)
	{
		case 0:
			if(_testbit_(g_u8_time2_flag))
			{
				g_u16_time2_cnt = _TIME2;
				P20 = 1;
				P21 = 1;
				P22 = 1;
				P23 = 1;
				P24 = 1;
				P25 = 1;
				P26 = 1;
				P27 = 1;
				u8_step = 1;
			}
			break;
		case 1:
			if(_testbit_(g_u8_time2_flag))
			{
				g_u16_time2_cnt = _TIME2;
				P20 = 1;
				P21 = 1;
				P22 = 1;
				P23 = 1;
				P24 = 1;
				P25 = 1;
				P26 = 1;
				P27 = 1;
				u8_step = 0;
			}
			break;
		default:
			break;
	}
}

这个可以当成一个模板,当需要多任务并行时,可以使用,同样与定时器那部分的代码差别不大,但是有一点不同,就是它没有使用传统的

if(1 == g_u8_time_flag){   
            g_u8_time_flag = 0;}

而是使用了库函数_testbit_(),这个函数可以检测数据是否为1,同时将数据清零,这样我们定义标志位时就可以用 bit 这个数据类型,既可以节省存储空间,又可以优化代码,一举两得,不过要注意,这个库函数需要包含#include "intrins.h"这个头文件。

当我们需要多个软件定时器时,可以在定时器初始化时进行补偿,使得定时器更加精确。当我们加任务的时候要注意,一个任务不能把CPU的所有时间占满,要雨露均沾。

  • 心得体会

这些需要延迟的函数我觉得都是一个套路,设定一个积累变量,一个标志位,当积累变量到达规定值,将标志位反转,然后执行程序,初始化积累变量,标志位,。

非阻塞延迟其实就是将一个循环,分成多个步骤,每次只运行一个,因为我们与机器所在的时间维度不同,所以我们会感觉任务是同时实现的,我觉得这种思想可以学习,就是无法执行多个任务是,将任务拆分为多个部分,然后运行,以达到同时执行的效果。

本文参考B站UP金善愚的视频

阻塞延时与非阻塞延时之非阻塞延时实现LED闪烁(累计主循环次数)

非阻塞延时实现LED闪烁功能( 累计定时中断次数)——多路软件定时器的实现

软件定时器非阻塞延时并行处理实现两路跑马灯——基于状态切换

单片机阻塞延时与非阻塞延时(1) 阻塞延时函数与非阻塞延时函数 阅读详情

相关推荐

利用proteus示波器验证AT89C51延时精度的实操教程

通过proteus示波器对AT89C51的延时程序进行实测,直观观察时序波形,精准评估延时效果。结合proteus示波器功能,提升单片机定时逻辑的调试效率与可靠性。

weixin_42502089的博客 687

延时函数(阻塞式,非阻塞式)

非阻塞式延时思路

Luojw_98的博客 8255

别再让HC-SR501乱动了!手把手教你调好感应距离和延时(附Arduino代码)

本文详细介绍了HC-SR501人体红外感应模块的精准调节与安装技巧,帮助解决误触和灵敏度问题。从热释电传感器原理到实战参数调节,再到安装定位和环境干扰排除,提供全面的进阶指南。附带的Arduino代码示例,助你实现商业级稳定性的智能感应系统。

weixin_42566424的博客 407

单片机实现非阻塞延时

单片机实现非阻塞延时。

h17878761977的博客 1246

FreeRTOS延时函数实战:vTaskDelay与vTaskDelayUntil的精准调度对比

本文深入对比了FreeRTOS中vTaskDelay与vTaskDelayUntil两种延时函数的精准调度差异。通过实际案例和代码分析,揭示了vTaskDelayUntil在周期稳定性上的显著优势,特别是在实时控制系统中如何实现毫秒级精度。文章还提供了调试技巧和硬件定时器结合方案,帮助开发者优化任务调度性能。

weixin_29228781的博客 340

从“锁匠”到“保安”:聊聊UDS 27服务里的延时机制与安全策略设计(附避坑指南)

本文深入探讨了UDS 27服务(SecurityAccess)在汽车电子系统中的安全机制设计,重点分析了Seed-Key实现细节、延时机制与错误计数器的工程实践。通过解析ISO14229-1标准中的安全策略,提供典型工程陷阱的避坑指南,帮助开发者构建更健壮的车载诊断安全系统。

weixin_30336061的博客 357

RocketMQ进阶-延时消息,java程序员面试算法宝典pdf

2021年的金三银四一眨眼就到了,对于很多人来说是跳槽的好机会,大厂面试远没有我们想的那么困难,摆好心态,做好准备,你也可以的。另外,面试中遇到不会的问题不妨尝试讲讲自己的思路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。BAT面试经验实战系列:Spring全家桶+Redis等其他相关的电子书:源码+调优面试真题:一个人可以走的很快,但一群人才能走的更远。

m0_60452134的博客 1164

超声延时聚焦原理及算法仿真

本系列主要记录对超声原理的一些理解,如有错误或者不到位之处,请留言指出,谢谢。本文对超声前端中的聚焦成像理论进行了讨论,文末附有相关仿真代码。

weixin_41336841的博客 6851

单片机延时C语言程序

(晶振12MHz,一个机器周期1us.) 一. 500ms延时子程序 程序: void delay500ms(void){ unsigned char i,j,k; for(i=15;i>0;i--) for(j=202;j>0;j--) for(k=81;k>0;k--); } 产生的汇编: C:0x0800 7F0F MOV R7,#0x0F C:0x08...

edu1180的博客 4262

单片机硬件和软件延时、RTOS相对延时和绝对延时_freertos延时对串口发送的影响

当然,这个延时的原理(延时函数代码)相对比较复杂,对于普通用户只需要知道如何调用以及简单原理即可,感兴趣的老铁可以自行研究一下。对计时精度要求比较高的地方适合定时器,像本章节说的周期性采集传感器数据,要求不适合很高,那么就引入本文说的绝对延时。关于RTOS的延时,这里额外拓展一下关于RTOS中的相对延时和绝对延时的内容,请移步至文章《像上面那个软件延时(Delay)就是一个典型的阻塞延时,一直消耗CPU,直到延时结束。2.软件延时为阻塞延时,硬件延时可阻塞,也可非阻赛延时;3.硬件延时应用更灵活、更广泛;

2401_85014013的博客 902

最基本的麦克风阵列延时累加和算法,DelayAndSum beamformer

最近从 phased.TimeDelayBeamformer 扒了它的代码,简单改造作为最基本的麦克风阵列算法学习 refer to https://www.mathworks.com/help/phased/ref/phased.timedelaybeamformer-system-object.html 一、延时累加和beamformer 可以说是最基本的麦克风阵列算法, 方法很简单,就是通过平移每个通道数据到合适的位置,然后累计调整系数即可。 但问题是什么? 一般采集到的麦克风数据..

book_bbyuan的博客 7416

Spring Boot中使用Redis和Lua脚本实现延时队列

Scheduled。

2401_85015451的博客 825

QT:非阻塞延迟

QT:非阻塞延迟

AI.PLAY 1017

别再用笨方法写流水灯了!51单片机P2寄存器与移位运算的巧妙玩法(STC-ISP延时函数配置)

本文介绍了51单片机P2寄存器与移位运算的巧妙玩法,帮助开发者优化流水灯代码。通过位操作和STC-ISP延时函数的灵活配置,实现从8行到3行代码的进化,提升编程效率和可读性。适合51单片机初学者和LED控制爱好者学习。

428

高德地图+Three.js实战:手把手教你打造数字农场3D可视化大屏(附完整源码)

本文详细介绍了如何利用高德地图JS API与Three.js技术栈,构建一个功能强大的数字农场3D可视化大屏。文章从技术选型、架构设计出发,深入讲解了坐标对齐、图层管理、性能优化等核心工程实践,并提供了完整的模块化代码示例,旨在为智慧农业及类似场景的3D地理可视化项目提供一套可复用的解决方案。

k8s6orchestrator的博客 890

Boost变换电路在新能源车充电系统中的创新应用与挑战

本文探讨了Boost变换电路在新能源车充电系统中的创新应用与挑战,重点分析了其在高压快充、能量回收等场景中的技术突破。通过电力电子仿真和新型器件应用,如SiC MOSFET,显著提升了功率密度和转换效率。同时,文章还深入讨论了电磁兼容性(EMC)挑战及智能控制算法的实际应用,为新能源车充电系统的优化提供了实用解决方案。

7z8x2c4v6b9的博客 217

延时相关算法与延时相关 + 长度保持算法的区别

下面先回顾 802.11a 分组检测时常用的“延时相关(Delay Correlation, DC)”度量,然后说明“延时相关 + 长度保持(Delay Correlation with Length Maintenance, DC-LM)”在公式和工程实现上的改动,并从等维度逐项对比。

weixin_41230430的博客 659

DCM4CHEE Archive Light 应用配置-存储配置

本文介绍了在DICOM归档系统中配置和管理存储的方法,包括通过用户界面和LDAP导入两种方式。主要内容包括: 存储配置概述:用户可根据需求配置单个或多个存储设备,支持在磁盘空间不足或多任务并行时使用。存储分为Archive设备级(序列元数据存储)和Application Entity级(对象存储/元数据存储)。 用户界面操作: 设备级存储配置:通过菜单导航添加/编辑存储描述符,设置Storage ID、URI等关键参数 应用级存储引用:在设备扩展和网络AE中分别配置元数据和对象存储引用 LDAP配置方法:

洪城叮当的博客 1416

RocketMQ5.0延时消息时间轮算法

RocketMQ 相较于其它消息队列产品的一个特性是支持延时消息,也就是说消息发送到 Broker 不会立马投递给消费者,要等待一个指定的延迟时间再投递,适用场景例如:下单后多长时间没付款系统自动关闭订单。RocketMQ 4.x 版本的延时消息存在一定的局限性,实现原理是:Broker 内置了名称为的Topic,包含 18 个对应延时级别的队列,每个延时级别的时间是固定的。

程序员小潘 5706

【汽车ECU标定技术解析】DCM文件与A2L、HEX的协同工作机制

本文深入解析了汽车ECU标定中DCM、A2L与HEX三大核心文件的协同工作机制。A2L文件作为定义标定变量地址与规则的“图纸”,DCM文件承载工程师调试后的具体标定数据,最终通过A2L的地址映射,将DCM数据固化生成可刷入ECU的HEX文件,形成完整的标定数据流转闭环。

weixin_29214559的博客 437

随机森林文本分类

随机森林原理及代码实现 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 机器学习入门之pandas的使用 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录随机森林原理及代码实现前言一、集成算法是什么?二、随机森林是什么三、 数据预处理3.1 先来看一下我们的文本数据3.2 预处理3.3 统计评论情感值积极与消极的占比二、使用步骤参数介绍(4个)属性1.引入库2.参数调参方法总结 前言 提示:这里可以添加本文要记录的大概内容: 例如

m0_46579176的博客 5808

c51单片机汇编语言1秒延时,51单片机汇编延时程序算法详解

将以12MHZ晶振为例,详细讲解MCS-51单片机中汇编程序延时的精确算法。指令周期、机器周期与时钟周期指令周期:CPU执行一条指令所需要的时间称为指令周期,它是以机器周期为单位的,指令不同,所需的机器周期也不同。时钟周期:也称为振荡周期,一个时钟周期 =晶振的倒数。MCS-51单片机的一个机器周期=6个状态周期=12个时钟周期。MCS-51单片机的指令有单字节、双字节和三字节的,它们的指令周期不...

weixin_36000501的博客 1万+

ECC椭圆曲线加解密原理详解(配图)

椭圆曲线加解密原理详解 本文主要参照:ECC椭圆曲线详解(有具体实例)[1] 前言: 椭圆曲线(ECC)加密原理跟RSA加解密原理比起来,可真是晦涩难懂。拜读了大神Kalafinaian文章“ECC椭圆曲线详解(有具体实例)”,终于看到了参透的曙光。 由于原文中有微小的瑕疵,也为了便于阅读起来更加流畅,我重新整理了一下这篇文章。 为什么使用椭圆曲线加密算法? RSA的解决分解整数问题需要亚指数...

Mark的博客 7万+

延时相关加长度保持分组检测算法

利用STF的周期结构,通过延时相关和滑动窗口能量计算实现快速可靠的实时帧检测。标准化自相关计算与合理门限设置保证了稳健的检测性能,适用于绝大多数无线通信环境。

weixin_41230430的博客 885

2026年佰维存储数字IC笔试试卷带答案

建立时间是有效时钟沿到来前,输入数据必须稳定保持的最短时间。保持时间是有效时钟沿到来后,输入数据必须继续稳定保持的最短时间。

Open 103

SAP 业务技术平台(BTP) Workflow(工作流)功能介绍

这背后其实发生了很多事情:Workflow 应用里 Determine Equipment Service Task 得到执行,通过 HTTP POST,消费部署在 SAP BTP 上的 Business Rules Service,后者根据我们在 Start New Instance 里传入的 Sample Data,返回自动决定出的设备明细。关于这个 SAP BTP 开发工具的更多介绍,参考我的文章:SAP 新一代全栈开发工具:SAP Business Application Studio.

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 8753
上一篇: 数码管定时器
下一篇: STC无刷电调技术分享
三无凡人
博客等级 码龄7年 67粉丝 4原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值