delay_delay函数 - CSDN
精华内容
参与话题
  • delay函数的用法及声明

    万次阅读 2017-01-16 14:02:30
    在VC中使用带上头文件 #include 注意: 在VC中Sleep中的第一个英文字符为大写的"S" 在标准C中是sleep, 不要大写.. 下面使用大写的来说明,, 具体用什么看你用什么编译器.... Sleep函数的一般形式: ...
     在VC中使用带上头文件
      #include <windows.h>
      注意:
      在VC中Sleep中的第一个英文字符为大写的"S"
      在标准C中是sleep, 不要大写.. 下面使用大写的来说明,, 具体用什么看你用什么编译器. 简单的说VC用Sleep, 别的一律使用sleep.
      Sleep函数的一般形式:
      Sleep(unisgned long);
      其中,Sleep()里面的单位,是以毫秒为单位,所以如果想让函数滞留1秒的话,应该是Sleep(1000);
      例:
      #include <windows.h>
      int main()
      {
      int a;
      a=1000;
      Sleep(a);/* VC 使用Sleep*/
      return 0;
      }
    
    usleep功能:
    暂停执行。 语法: void usleep(int micro_seconds); 返回值: 无 函数种类: PHP 系统功能 内容说明:本函数可暂时使程序停止执行。参数 micro_seconds 为要暂停的毫秒数(微妙还是毫秒?)。 注意:这个函数不能工作在 Windows 操作系统中。参见:usleep() 与sleep()类似,用于延迟挂起进程。进程被挂起放到reday queue。
      只是一般情况下,延迟时间数量级是秒的时候,尽可能使用sleep()函数。
      且,此函数已被废除,可使用nanosleep。
      如果延迟时间为几十毫秒,或者更小,尽可能使用usleep()函数。这样才能最佳的利用CPU时间
    
    delay:
    函数名: delay 
      功 能: 将程序的执行暂停一段时间(毫秒) 
      用 法: void delay(unsigned milliseconds); 
      程序例: 
      /* Emits a 440-Hz tone for 500 milliseconds */ 
      #include<dos.h> 
      int main(void) 
      { 
      sound(440); 
      delay(500); 
      nosound(); 
      return 0; 
      }
      (由于delay读音像地雷,在各大OI灌水区通用……) 
    
    
    delay()是循环等待,该进程还在运行,占用处理器。   
    sleep()不同,它会被挂起,把处理器让给其他的进程。
    
    sleep()参数指定暂停时间,单位是s   
    delay()参数指定暂停时间,单位是ms
    展开全文
  • Delay_ms延时函数详解

    万次阅读 2017-11-13 21:24:40
    void Delay_ms(unsigned int time) { unsigned char n; while(time>0) { for(n=0;n;++n) { asm("nop"); } time--; } } 在流程中加入延时函数的原因是:单片机执行命令的速度太快(如果选择1MHz的晶振...

    void Delay_ms(unsigned int time)
    {
        unsigned char  n;
       while(time>0)
        {
        for(n=0;n<187;++n)
      {
    asm("nop");
    }
    time--;
    }
    }

    在流程中加入延时函数的原因是:单片机执行命令的速度太快(如果选择1MHz的晶振频率,AVR执行大多数指令的时间仅仅是1),任由单片机;连续改变端口输出的话,由于人眼辨别物体的速度跟不上,我们将看不到任何闪烁的效果,所以加入延时来放慢端口电平的变化速度


    软件延时的基本原理是多次重复执行指令,比如1条指令执行需要1微妙的时间,那么执行一千条这个指令 就会消耗一毫秒的时间;  其中asm("nop");语句是插入汇编指令的写法。表示在for循环中插入一条nop指令,这是一个空操作指令;它的执行将消耗掉一个cpu周期,因为我们的目的就是延时,所以空操作就可以;

    但是C语言书写的fof循环编译器会加入很多我们看不到的底层实现代码,它们的执行也需要时间,所以循环次数远小于1000;


    注意:软件延时只能用到要求不高的场合,因为软件延时会无端的浪费掉单片机的资源,延时也容易被打断就不准确了
    展开全文
  • 头文件Delay.h(网上很难找到的,建议下载后收藏,并放到keil的INC文件夹中,以后就可以直接调用了)
  • 常用的延时函数delay()

    2020-07-29 14:21:47
    这是一个常用的延时功能模块,编程有一 定的用处。
  • 【stm32】delay详解

    千次阅读 2019-11-01 12:40:41
    什么是SysTick CM3 内核的处理器,内部包含了一个 SysTick 定时器,SysTick 是一个 24 位的倒计数定时器,当计数到 0 时,将从RELOAD 寄存器中自动重装载定时初值,开始新一轮计数。只要不把它在 SysTick 控制及状态...

    1 首先看函数:

    1.1 当使用ucos时才有以下部分:
    主要实现宏定义与基本函数定义。

    #if SYSTEM_SUPPORT_OS							//如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
    //当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
    //首先是3个宏定义:
    //    delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
    //delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
    // delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
    //然后是3个函数:
    //  delay_osschedlock:用于锁定OS任务调度,禁止调度
    //delay_osschedunlock:用于解锁OS任务调度,重新开启调度
    //    delay_ostimedly:用于OS延时,可以引起任务调度.
    
    //本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
    //支持UCOSII
    #ifdef 	OS_CRITICAL_METHOD						//OS_CRITICAL_METHOD定义了,说明要支持UCOSII				
    #define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
    #define delay_ostickspersec	OS_TICKS_PER_SEC	//OS时钟节拍,即每秒调度次数
    #define delay_osintnesting 	OSIntNesting		//中断嵌套级别,即中断嵌套次数
    #endif
    
    //支持UCOSIII
    #ifdef 	CPU_CFG_CRITICAL_METHOD					//CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII	
    #define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
    #define delay_ostickspersec	OSCfg_TickRate_Hz	//OS时钟节拍,即每秒调度次数
    #define delay_osintnesting 	OSIntNestingCtr		//中断嵌套级别,即中断嵌套次数
    #endif
    
    
    //us级延时时,关闭任务调度(防止打断us级延迟)
    void delay_osschedlock(void)
    {
    #ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
    	OS_ERR err; 
    	OSSchedLock(&err);							//UCOSIII的方式,禁止调度,防止打断us延时
    #else											//否则UCOSII
    	OSSchedLock();								//UCOSII的方式,禁止调度,防止打断us延时
    #endif
    }
    
    //us级延时时,恢复任务调度
    void delay_osschedunlock(void)
    {	
    #ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
    	OS_ERR err; 
    	OSSchedUnlock(&err);						//UCOSIII的方式,恢复调度
    #else											//否则UCOSII
    	OSSchedUnlock();							//UCOSII的方式,恢复调度
    #endif
    }
    
    //调用OS自带的延时函数延时
    //ticks:延时的节拍数
    void delay_ostimedly(u32 ticks)
    {
    #ifdef CPU_CFG_CRITICAL_METHOD
    	OS_ERR err; 
    	OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);	//UCOSIII延时采用周期模式
    #else
    	OSTimeDly(ticks);							//UCOSII延时
    #endif 
    }
     
    //systick中断服务函数,使用ucos时用到
    void SysTick_Handler(void)
    {	
    	if(delay_osrunning==1)						//OS开始跑了,才执行正常的调度处理
    	{
    		OSIntEnter();							//进入中断
    		OSTimeTick();       					//调用ucos的时钟服务程序               
    		OSIntExit();       	 					//触发任务切换软中断
    	}
    }
    #endif
    

    1.2 使用ucos的delay函数

    #if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
    //延时nus
    //nus为要延时的us数.		    								   
    void delay_us(u32 nus)
    {		
    	u32 ticks;
    	u32 told,tnow,tcnt=0;
    	u32 reload=SysTick->LOAD;					//LOAD的值	    	 
    	ticks=nus*fac_us; 							//需要的节拍数	  		 
    	tcnt=0;
    	delay_osschedlock();						//阻止OS调度,防止打断us延时
    	told=SysTick->VAL;        					//刚进入时的计数器值
    	while(1)
    	{
    		tnow=SysTick->VAL;	
    		if(tnow!=told)
    		{	    
    			if(tnow<told)tcnt+=told-tnow;		//这里注意一下SYSTICK是一个递减的计数器就可以了.
    			else tcnt+=reload-tnow+told;	    
    			told=tnow;
    			if(tcnt>=ticks)break;				//时间超过/等于要延迟的时间,则退出.
    		}  
    	};
    	delay_osschedunlock();						//恢复OS调度									    
    }
    //延时nms
    //nms:要延时的ms数
    void delay_ms(u16 nms)
    {	
    	if(delay_osrunning&&delay_osintnesting==0)	//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)	    
    	{		 
    		if(nms>=fac_ms)							//延时的时间大于OS的最少时间周期 
    		{ 
       			delay_ostimedly(nms/fac_ms);		//OS延时
    		}
    		nms%=fac_ms;							//OS已经无法提供这么小的延时了,采用普通方式延时    
    	}
    	delay_us((u32)(nms*1000));					//普通方式延时  
    }
    #else //不用OS时
    

    1.3 不使用ucos的delay函数

    //延时nus
    //nus为要延时的us数.		    								   
    void delay_us(u32 nus)
    {		
    	u32 temp;	    	 
    	SysTick->LOAD=nus*fac_us; 					//时间加载	  		 
    	SysTick->VAL=0x00;        					//清空计数器
    	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数	  
    	do
    	{
    		temp=SysTick->CTRL;
    	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
    	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
    	SysTick->VAL =0X00;      					 //清空计数器	 
    }
    //延时nms
    //注意nms的范围
    //SysTick->LOAD为24位寄存器,所以,最大延时为:
    //nms<=0xffffff*8*1000/SYSCLK
    //SYSCLK单位为Hz,nms单位为ms
    //对72M条件下,nms<=1864 
    void delay_ms(u16 nms)
    {	 		  	  
    	u32 temp;		   
    	SysTick->LOAD=(u32)nms*fac_ms;				//时间加载(SysTick->LOAD为24bit)
    	SysTick->VAL =0x00;							//清空计数器
    	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数  
    	do
    	{
    		temp=SysTick->CTRL;
    	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
    	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
    	SysTick->VAL =0X00;       					//清空计数器	  	    
    } 
    

    1.4 无论是否使用ucos的初始化函数

    //初始化延迟函数
    //当使用OS的时候,此函数会初始化OS的时钟节拍
    //SYSTICK的时钟固定为HCLK时钟的1/8
    //SYSCLK:系统时钟
    void delay_init()
    {
    #if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
    	u32 reload;
    #endif
    	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);	//选择外部时钟  HCLK/8
    	fac_us=SystemCoreClock/8000000;				//为系统时钟的1/8  
    #if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
    	reload=SystemCoreClock/8000000;				//每秒钟的计数次数 单位为M  
    	reload*=1000000/delay_ostickspersec;		//根据delay_ostickspersec设定溢出时间
    												//reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右	
    	fac_ms=1000/delay_ostickspersec;			//代表OS可以延时的最少单位	   
    
    	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;   	//开启SYSTICK中断
    	SysTick->LOAD=reload; 						//每1/delay_ostickspersec秒中断一次	
    	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;   	//开启SYSTICK    
    
    #else
    	fac_ms=(u16)fac_us*1000;					//非OS下,代表每个ms需要的systick时钟数   
    #endif
    }	
    

    2 什么是SysTick

    CM3 内核的处理器,内部包含了一个 SysTick 定时器,SysTick 是一个 24 位的倒计数定时器,当计数到 0 时,将从RELOAD 寄存器中自动重装载定时初值,开始新一轮计数。只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息。 STM32 的内部 SysTick 来实现延时既不占用中断,也不占用系统定时器。
    SysTick 是 MDK 定义了的一个结构体(在 core_m3.h 里面),里面包含 CTRL、LOAD、VAL、CALIB 等 4 个寄存器,

    2.1 SysTick->CTRL 的各位定义如图所示:
    在这里插入图片描述
    2.2 SysTick-> LOAD 的定义如图所示:
    在这里插入图片描述
    2.3 SysTick-> VAL 的定义如图 所示:
    在这里插入图片描述
    2.4 SysTick-> CALIB 不常用

    2.5 ucos下的SysTick以及如何实现delay_ms与delay_us

    ucos 运行需要一个系统时钟节拍(类似 “心跳”),而这个节拍是固定的(由 OS_TICKS_PER_SEC 宏定义设置),比如要求 5ms 一次(即可设置OS_TICKS_PER_SEC=200),在 STM32 上面,一般是由 SysTick 来提供这个节拍,也就是SysTick要设置为 5ms 中断一次,为 ucos 提供时钟节拍,而且这个时钟一般是不能被打断的(否则就不准了)。

    因为在 ucos 下 SysTick 不能再被随意更改,如果我们还想利用 SysTick 来做 delay_us 或者delay_ms 的延时,就必须想点办法了,这里我们利用的是时钟摘取法。以 delay_us 为例,比如
    delay_us(50),在刚进入 delay_us 的时候先计算好这段延时需要等待的 SysTick 计数次数,这里
    为 50 * 9(假设系统时钟为72Mhz,那么SysTick每增加1,就是1/9us)(SysTick每增加9就是1us)(SysTick的频率为div8),然后我们就一直统计SysTick 的计数变化,直到这个值变化了 50*9,一旦检测到变化达到或者超过这个值,就说明延时 50us 时间到了。这样,我们只是抓取 SysTick 计数器的变化,并不需要修改 SysTick 的任何状态,完全不影响 SysTick 作为 UCOS 时钟节拍的功能,这就是实现 delay 和操作系统共用SysTick 定时器的原理。

    2.6外部8M晶振原因

    Systick 的时钟来自系统时钟 8 分频,正因为如此,系统时钟如果不是 8 的倍数(不能被 8 整除),则会导致延时函数不准确,这也是我们推荐外部时钟选择 8M 的原因。

    3 函数详解

    3.1 delay_init

    delay_init 函数使用了条件编译,来选择不同的初始化过程,如果不使用 OS 的时候,只是设置一下 SysTick 的时钟源以及确定 fac_us 和 fac_ms 的值。而如果使用 OS 的时候,则会进行一些不同的配置,这里的条件编译是根据 SYSTEM_SUPPORT_OS 这个宏来确定的,该宏在 sys.h 里面定义。

    void delay_init()
    {
    #if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
    	u32 reload;
    #endif
    	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);	//选择外部时钟  HCLK/8
    	fac_us=SystemCoreClock/8000000;				//为系统时钟的1/8  
    #if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
    	reload=SystemCoreClock/8000000;				//每秒钟的计数次数 单位为M  
    	reload*=1000000/delay_ostickspersec;		//根据delay_ostickspersec设定溢出时间
    												//reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右	
    	fac_ms=1000/delay_ostickspersec;			//代表OS可以延时的最少单位	   
    
    	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;   	//开启SYSTICK中断
    	SysTick->LOAD=reload; 						//每1/delay_ostickspersec秒中断一次	
    	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;   	//开启SYSTICK    
    
    #else
    	fac_ms=(u16)fac_us*1000;					//非OS下,代表每个ms需要的systick时钟数   
    #endif
    }	
    
    • SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);这一句把SysTick的时钟选择外部时钟,SysTick 的时钟源自 HCLK 的 8 分频,假设我们外部晶振为 8M,然后倍频到 72M,那么 SysTick 的时钟即为 9Mhz,也就是SysTick 的计数器 VAL 每减 1,就代表时间过了 1/9us。
    • fac_us=SystemCoreClock/8000000;这句话就是计算在 SystemCoreClock 时钟频率下延时
      1us 需要多少个 SysTick 时钟周期。
    • fac_ms=(u16)fac_us*1000;就是计算延时 1ms 需要多少个 SysTick 时钟周期,它自然是 1us
      的 1000 倍。

    在不使用 OS 的时候:fac_us为 us 延时的基数,也就是延时 1us时候SysTick->LOAD 所应设置的值。fac_ms 为 ms 延时的基数,也就是延时 1ms,SysTick->LOAD 所应设置的值。fac_us为 8 位整形数据,fac_ms 为 16 位整形数据。

    当使用 OS 的时候,fac_us还是 us 延时的基数,不过这个值不会被写到 SysTick->LOAD寄存器来实现延时,而是通过时钟摘取的办法实现的。而 fac_ms 则代表 ucos自带的延时函数所能实现的最小延时时间(如 delay_ostickspersec=200,那么 fac_ms 就是 5ms)。

    3.2 不使用ucos的delay_us

    该函数用来延时指定的 us,其参数 nus 为要延时的微秒数。

    void delay_us(u32 nus)
    {		
    	u32 temp;	    	 
    	SysTick->LOAD=nus*fac_us; 					//时间加载	  		 
    	SysTick->VAL=0x00;        					//清空计数器
    	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数	  
    	do
    	{
    		temp=SysTick->CTRL;
    	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
    	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
    	SysTick->VAL =0X00;      					 //清空计数器	 
    }
    

    实现一次延时 nus的操作:

    • 先把要延时的 us 数换算成 SysTick 的时钟数,然后写入 LOAD 寄存器。
    • 然后清空当前寄存器 VAL 的内容,再开启倒数功能。
    • 等到倒数结束,即延时了 nus。
    • 最后关闭 SysTick,清空 VAL 的值。

    nus 的值,不能太大,必须保证 nus<=(2^24)/fac_us,否则将导致延时时间不准确。 temp&0x01,这一句是用来判断 systick 定时器是否还处于开启状态,可以防止 systick 被意外关闭导致的死循环

    3.3 ucos下的delay_us

    void delay_us(u32 nus)
    {		
    	u32 ticks;
    	u32 told,tnow,tcnt=0;
    	u32 reload=SysTick->LOAD;					//LOAD的值	    	 
    	ticks=nus*fac_us; 							//需要的节拍数	  		 
    	tcnt=0;
    	delay_osschedlock();						//阻止OS调度,防止打断us延时
    	told=SysTick->VAL;        					//刚进入时的计数器值
    	while(1)
    	{
    		tnow=SysTick->VAL;	
    		if(tnow!=told)
    		{	    
    			if(tnow<told)tcnt+=told-tnow;		//这里注意一下SYSTICK是一个递减的计数器就可以了.
    			else tcnt+=reload-tnow+told;	    
    			told=tnow;
    			if(tcnt>=ticks)break;				//时间超过/等于要延迟的时间,则退出.
    		}  
    	};
    	delay_osschedunlock();						//恢复OS调度									    
    }
    
    • 利用时钟摘取法,ticks 是延时 nus 需要等待的 SysTick 计数次数(也就是延时时间),told 用于记录最近一次的 SysTick->VAL 值,然后 tnow 则是当前的SysTick->VAL 值,通过他们的对比累加,实现 SysTick 计数次数的统计,统计值存放在 tcnt 里面,然后通过对比 tcnt 和 ticks,来判断延时是否到达,从而达到不修改 SysTick 实现 nus 的延时,从而可以和 OS 共用一个 SysTick。
    • delay_osschedlock 和 delay_osschedunlock 是 OS 提供的两个函数,用于调度上锁和解锁,这里为了防止 OS 在 delay_us 的时候打断延时,可能导致的延时不准,所以我们利用这两个函数来实现免打断,从而保证延时精度!同时,此时的 delay_us,可以实现最长 2^32us 的延时,大概是 4294 秒。

    3.4 不使用ucos的delay_ms

    void delay_ms(u16 nms)
    {	 		  	  
    	u32 temp;		   
    	SysTick->LOAD=(u32)nms*fac_ms;				//时间加载(SysTick->LOAD为24bit)
    	SysTick->VAL =0x00;							//清空计数器
    	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数  
    	do
    	{
    		temp=SysTick->CTRL;
    	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
    	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
    	SysTick->VAL =0X00;       					//清空计数器	  	    
    } 
    

    注意:单次延时时间不能超过1864ms,原因如下:
    同delay_us大致一样,LOAD 仅仅是一个 24bit 的寄存器,延时的 ms 数不能太长。否则超出了 LOAD 的范围,高位会被舍去,导致延时不准。最大延迟 ms 数可以通过公式:nms<=0xffffff81000/SYSCLK 计算。SYSCLK 单位为 Hz,nms 的单位为 ms。如果时钟为 72M,那么 nms 的最大值为 1864ms。超过这个值,建议通过多次调用 delay_ms 实现,否则就会导致延时不准确。

    3.5 ucos下的delay_ms

    void delay_ms(u16 nms)
    {	
    	if(delay_osrunning&&delay_osintnesting==0)	//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)	    
    	{		 
    		if(nms>=fac_ms)							//延时的时间大于OS的最少时间周期 
    		{ 
       			delay_ostimedly(nms/fac_ms);		//OS延时
    		}
    		nms%=fac_ms;							//OS已经无法提供这么小的延时了,采用普通方式延时    
    	}
    	delay_us((u32)(nms*1000));					//普通方式延时  
    }
    
    • 该函数中,delay_osrunning 是 OS 正在运行的标志,delay_osintnesting 则是 OS 中断嵌套次数,必须 delay_osrunning 为真,且 delay_osintnesting 为 0 的时候,才可以调用 OS 自带的延时函数进行延时(可以进行任务调度),delay_ostimedly 函数就是利用 OS 自带的延时函数,实现任务级延时的,其参数代表延时的时钟节拍数(假设 delay_ostickspersec=200 ,那么delay_ostimedly (1),就代表延时 5ms)。

    • 当 OS 还未运行的时候,我们的 delay_ms 就是直接由 delay_us 实现的,OS 下的 delay_us可以实现很长的延时而不溢出!,所以放心的使用 delay_us 来实现 delay_ms,不过由于 delay_us的时候,任务调度被上锁了,所以还是建议不要用 delay_us 来延时很长的时间,否则影响整个系统的性能。

    • 当 OS 运行的时候,我们的 delay_ms 函数将先判断延时时长是否大于等于 1 个 OS 时钟节拍(fac_ms),当大于这个值的时候,我们就通过调用 OS 的延时函数来实现(此时任务可以调度),不足 1 个时钟节拍的时候,直接调用 delay_us 函数实现(此时任务无法调度)。

    展开全文
  • Delay line 延迟线 今天我们将讨论 Delay 和 Vibrato 两种音频特效的技术原理和实现细节。 Delay 和 Vibrato 都是基于 Delay line 实现的。Delay line 作为音频特效中重要的基础组件,它很容易实现,并且稍作修改就...

    Delay line 延迟线

    今天我们将讨论 Delay 和 Vibrato 两种音频特效的技术原理和实现细节。

    Delay 和 Vibrato 都是基于 Delay line 实现的。Delay line 作为音频特效中重要的基础组件,它很容易实现,并且稍作修改就能够应用实现于不同的音效。

    Delay line 非常简单,它能功能是将一个信号进行延迟。通过使用多条 delay line,并加以不同的信号延迟,然后将这些信号相加在一起,我们就能够创建大量的音频特效。

    在模拟信号中,delay line 的实现相当复杂,需要引入物理扩展(例如弹簧)来延迟波的传播。

    在数字信号中,delay line 通常使用 ”循环缓冲区” 的数据结构来实现延迟。循环缓冲区本质上可以用一个数组实现,用一个索引来指向下一个存放信号的位置,当索引超过缓冲区大小时,将其重新置于开始位置。这样一来,就像往一个圈里顺时针填数据,当我们需要延迟信号时,计算逆时针回退的个数即可。

    下面是 delay line 的一种实现,更多细节大家可以参看代码

    template <typename T>
    class DelayLine
    {
    public:
        void clear() noexcept
        {
            std::fill(raw_data_.begin(), raw_data_.end(), T(0));
        }
        /**
         * return the size of delay line
         */
        size_t size() const noexcept
        {
            return raw_data_.size();
        }
    
        /**
         * resize the delay line
         *
         * @note resize will clears the data in delay line
         */
        void resize(size_t size) noexcept
        {
            raw_data_.resize(size);
            least_recent_index_ = 0;
    
            clear();
        }
    
        /**
         * push a value to delay line
         */
        void push(T value) noexcept
        {
            raw_data_[least_recent_index_] = value;
            least_recent_index_ = (least_recent_index_ == 0) ? (size() - 1):(least_recent_index_ - 1);
        }
    
        /**
         * returns the last value
         */
        T back() const noexcept
        {
            return raw_data_[(least_recent_index_ + 1) % size()];
        }
    
        /**
         * returns value with delay
         */
        T get(size_t delay_in_samples) const noexcept
        {
            return raw_data_[(least_recent_index_ + 1 + delay_in_samples) % size()];
        }
        
         /**
         * Returns interpolation value
         */
        T getInterpolation(float delay) const noexcept
        {
            int previous_sample = static_cast<int>(std::floorf(delay));
            int next_sample = static_cast<int>(std::ceilf(delay));
            float fraction = static_cast<float>(next_sample) - delay;
    
            return fraction*get(previous_sample) + (1.0f-fraction)*get(next_sample);
        }
    
        /**
         * set value in specific delay
         */
        void set(size_t delay_in_samples, T new_val) noexcept
        {
            raw_data_[(least_recent_index_ + 1 + delay_in_samples) % size()] = new_val;
        }
    
    private:
        size_t least_recent_index_{0};
        std::vector<T> raw_data_;
    };
    

    Delay 延迟

    Delay 音效非常简单,但应用非常广泛。最简单的情况下,将声音进行延迟并与原始信号相加就可以使的乐器的声音更加生动活泼,或者用更长时间的延迟,来达到二重奏的效果。很多熟悉的音效(例如 Chorus、Flanger、Vibrato 和 Reverb)也是基于 Delay 实现的。

    Basic Delay 基本延迟

    Basic delay 会在指定延迟时间后播放音频。根据应用的不同,延迟时间可能从几毫秒到几秒,甚至更长。这里是一小段 basic delay 算法结果。Basic delay 通常也被称为 Echo(回声)效果。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o4pYgOua-1583193401645)(音频特效:Delay 和 Vibrato.resources/C3053E8A-7537-47F9-89CB-45891AEC396E.png)]

    Basic delay 算法原理部分很简单,通常是将带有延迟的信号与原始信号相加。其中 y[n]y[n] 表示输出信号,x[n]x[n]表示原始信号,NN表示延迟(单位是采样个数),gg表示延迟信号的增益
    y[n]=x[n]+gx[nN] y[n] = x[n] + gx[n - N]

    利用 Z 变换,得到传递函数为:

    Y(z)=X(z)+gzNX(z)H(z)=Y(z)X(z)=1+gzN=zN+gzN \begin{aligned} Y(z) &= X(z) + gz^{-N}X(z) \\ H(z) &= \frac{Y(z)}{X(z)} = 1+gz^{-N} = \frac{z^N+g}{z^N} \end{aligned}

    因为传递函数 H(z)H(z) 的所有极点都在单位圆内,因此 basic delay 在所有情况下都是稳定的。

    Dalay with Feedback 反馈延迟

    Basic delay 使用场景比较受限,因为它仅仅产生单个回声。大多数音频延迟单元还具有反馈控制,它能够将延迟输出的信号再发送到输入,如下图。反馈使声音不断重复,如果反馈增益小于 1,那么每次回声都会变得更加安静。从理论上讲,回声将永远重复,但它们最终会变得非常安静,以至于你无法听到。
    delay with feedback
    根据上图,我们可以写出反馈延迟的差分方程:
    y[n]=x[n]+gffd[n]whered[n]=x[nN]+gfbd[nN] y[n] = x[n] + g_{ff}d[n] \quad \text{where} \quad d[n] = x[n-N] + g_{fb}d[n-N]
    然后可以转换为只与 y[n]y[n]x[n]x[n] 相关差分方程:
    y[nN]=x[nN]+gffd[nN]d[n]=gfbgffy[nN]+(1gfbgffx[nN])y[n]=gfby[nN]+x[n]+(gffgfb)x[nN] \begin{aligned} y[n-N] &= x[n-N] + g_{ff}d[n-N] \\ d[n] &= \frac{g_{fb}}{g_{ff}}y[n-N] + (1-\frac{g_{fb}}{g_{ff}}x[n-N])\\ y[n] &= g_{fb}y[n-N] + x[n] + (g_{ff} - g_{fb})x[n-N] \end{aligned}
    计算其传递方程:
    H(z)=Y(z)X(z)=zN+gffgfbzNgfb H(z) = \frac{Y(z)}{X(z)} = \frac{z^N + g_{ff} - g_{fb}}{z^N-g_{fb}}
    因此,系统的极点在 gfbN\sqrt[N]{g_{fb}},这就说明当 gfb<1\vert g_{fb}\vert < 1 时系统是稳定的。这个结果符合直觉,因为只有反馈增益小于1时,回声才会随着时间越来越小。

    这里是反馈延迟的算法输出。可以听到反馈延迟效果很像我们在大山里喊叫的效果,比起 basic delay,反馈延迟有多次回声,每次回声音量逐渐变小。

    反馈延迟的实现大致如下,通过 delay line 来获取延迟信号,并且往 delay line 中记录带有反馈的信号。如果 feedback 为0,那么反馈延迟将退化为 basic delay。

    void DelayEffect::processBlock(AudioBuffer<float> &buffer)
    {
        const int num_channels = buffer.getNumChannels();
        const int num_samples = buffer.getNumSamples();
    
        for(int c = 0; c < num_channels; ++c)
        {
            float* channel_data = buffer.getWritePointer(c);
            auto& dline = dlines_[c];
            size_t delay_samples = delay_length_in_sample_[c];
    
            for(int i = 0; i < num_samples; ++i)
            {
                const float in = channel_data[i];
                const float delay_val = dline.get(delay_samples);
                float out = 0.0f;
    
                out = (dry_mix_ * in + wet_mix_ * delay_val);
    
                dline.push( in + feedback_ * delay_val);
    
                channel_data[i] = out;
            }
        }
    }
    

    Vibrato 颤音

    颤音指的是音调周期性微小变化。传统意义上,颤音并不是音效效果,而是歌手和乐器演奏者使用的一种技术。例如在小提琴上,通过在指板上有节奏地前后摇动手指,稍微改变琴弦的长度来产生颤音。但是在音频信号中,我们可以使用调制的 delay line 来实现颤音。

    前面提到的两种延迟算法,它们的延迟长度是固定,不随时间变化的那种。颤音与它们最大的不同在于其延迟长度随着时间变化而变化,而这种变化会导致音调的变化,这里我们举个例子来说明,假设:
    M[n]=MmaxΔmn M[n] = M_{max}-\Delta m * n

    这时候 :
    y[n]=x[nMmax+Δmn]=x[(1+Δm)nMmax] y[n] = x[n-M_{max} + \Delta m * n] = x[(1+\Delta m)*n - M_{max}]

    在这里插入图片描述

    也就是说,原来信号频率提升了 1+Δm1+\Delta m 倍。如果 Δm<0\Delta m < 0 那就是降低了频率
    频率的变化和 MmaxM_{max} 无关,只和 Δm\Delta m 有关

    fration[n]=f(1+Δm)f=1+Δm=1(M[n]M[n1]) f_{ration}[n] = \frac{f*(1+\Delta m)}{f} = 1+\Delta m = 1 - (M[n] - M[n-1])

    Low-Frequency Oscillator 低频振荡器

    为了达到颤音的效果,我们需要模拟那种音调周期性变化的感觉,而延迟长度的变化会引起音调变化,因此如果我们让延迟长度发生周期性变化,那么音调也是周期性变化的。

    为了延迟的长度周期性变化,我们可以用一个低频振荡器(Low-Frequency Oscillator; LFO)来控制它,以正弦 LFO 为例,公式为:
    M[n]=Wsin(2πnf/fs) M[n] = W\sin(2\pi nf/f_s)
    其中,WW调节变换的范围;ff是 LFO 的频率,影响音调的变化周期;fsf_s表示采样率。音调的变化可以计算为:
    M[n]M[n1]=W(sin(2πnf/fs)sin(2π(n1)f/fs))2πfWcos(2πnf/fs) \begin{aligned} M[n] - M[n-1] &= W(\sin(2\pi nf/f_s) - \sin(2\pi(n-1)f/f_s)) \\ &\approx 2\pi fW\cos(2\pi nf/f_s) \\ \end{aligned}
    fration[n]=1(M[n]M[n1])12πfWcos(2πnf/fs) f_{ration}[n] = 1 - (M[n] - M[n-1]) \approx 1 - 2\pi fW\cos(2\pi nf/f_s)
    因此音调的变化是个周期函数,一会上一会下的。

    这里是颤音算法的输出结果,颤音应用到人声上会产生一种滑稽的效果,挺有趣的。

    颤音的实现大致如下,通过 lfo 来得到延迟的长度,根据长度从 delay line 中获取数据。有一点与之前不同的是,当延迟长度不是整数时,我们采用了插值的方法,这样可以让信号更加平滑。

    void VibratoEffect::processBlock(AudioBuffer<float> &buffer) {
        const int num_channels = buffer.getNumChannels();
        const int num_samples = buffer.getNumSamples();
        float phase = 0.0f;
    
        assert(num_channels <= dlines_.size());
    
        for(int c = 0; c < num_channels; ++c)
        {
            phase = phase_;
            float* channel_data = buffer.getWritePointer(c);
            auto& dline = dlines_[c];
    
            for(int i = 0; i < num_samples; ++i)
            {
                const float in = channel_data[i];
    
                // get delay from lfo
                float delay_second = sweep_width*lfo_.lfo(phase, LFO::WaveformType::kWaveformSine);
                float delay_sample = delay_second * getSampleRate();
    
                // get interpolation delay value
                channel_data[i] = dline.getInterpolation(delay_sample);
    
                // push input to delay line
                dline.push(in);
    
                // update phase
                phase += lfo_freq*invert_sample_rate_;
                if(phase >= 1.0f)
                {
                    phase -= 1.0f;
                }
            }
        }
    
        phase_ = phase;
    }
    

    总结

    以上介绍了延迟和颤音两种音效,并给出了详细实现,它们都是基于 delay line 实现的,简单却又有用。

    展开全文
  • FPGA自学:Delay延迟

    2019-12-29 22:11:26
    如何将某信号延迟输出几个...module Delay(clk,rst,datavalid,datadelay1,datadelay2,datadelay3); input clk; input rst; input datavalid; output datadelay1,datadelay2,datadelay3; reg z1,z2,z3,z4,z5,z6,z7...
  • STM32上进行Delay延时的方法

    千次阅读 2019-04-23 16:43:43
    1、使用SYStick专门的延时。...void delay_us(uint32_t us) { static uint32_t delay_flag = 0; delay_flag = 1; /* set reload register HCLK = 48M */ SysTick->LOAD = (SYSTEM_CORE_CLOCK / 1000000) *...
  • delay的几个函数说明

    2019-06-20 13:08:52
    delay下面的主要函数是delay_init,delay_us和delay_ms。 非OS的时候,可以很简单 void delay_init(u8 SYSCLK) { SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); fac_us=SYSCLK/8; //每个us需要...
  • Xilinx IODELAY动态延迟的调用

    千次阅读 2017-10-21 16:58:45
    Xilinx SELECTIO IP核调用配作可变输入延时时其信号接口如下表所示: 接口名 方向 注释 DATA_IN_FROM_PINS Input ...DELAY_BUSY Output IODELAY校准和相位变化正在进行忙信号 DELAY_CLK Input FPG
  • input delay, output delay

    千次阅读 2018-11-19 16:02:41
    input delay, output delay在vivado中使用范围  转载:通过作者使用总结情况,IO口时序约束主要使用在以下情况: 1. 数据交换频率较高  由于IO时序约束一般计算值都是在几纳秒,当FPGA和外部数据交换频率较低...
  • set_max_delay 用于覆盖默认的setup(recovery)约束。 set_min_delay 用于覆盖默认的hold(removal)约束。 语法格式: set_max_delay <delay> [-datapath_only] [-from <node_list>] [-to <...
  • Group Delay and Phase Delay

    千次阅读 2016-01-21 15:14:48
    Group Delay 群延迟 Phase Delay 相位延迟 两个不同的概念,但都是指延迟时间。延迟故名思义就是信号从进入系统到从系统出来时的延迟。好比龙头上接了一根皮管,你拧开龙头,皮管那头水不会立马出来,...
  • 什么是Pin Delay? 芯片内部,核心单元到封装引脚之间的距离成为Pin Delay。在高速电路设计中,需要考虑芯片的Pin Delay来减少走线长度误差。 Pin Delay怎么计算? 在厂商提供的资料中,Pin Delay通常取Min Delay和...
  • set_max_delay

    千次阅读 2018-11-29 19:36:39
    现在的解决办法是使用set_max_delay 和set_min_delay来约束,但是这个延迟的值具体怎么计算,  set_output_delay为负值表示留的margin更大,对于hold更悲观; 对于setup更乐观。   我觉得如果是top level的话...
  • GUI_Delay函数

    千次阅读 2019-01-11 19:30:45
    GUI_Delay()函数 使用GUI_Delay()函数时,对于其延时时间不确定,明明设置为最小值1,延时时间 仍旧太长,不能达到需求。遂决定研究明白其实现机理。 uC/OS-II使用OSTimeDly()函数实现延时,其单位是OS_TICKS...
  • 关于TCP Delay ACK的概念我就不多说了,到处复制粘贴标准文献以及别人的文章只能让本文篇幅加长而降低被阅读完的欲望,再者这也不是什么论文,附录参考文献几乎很少有人去看,所以我把这些都略过了。和风吹的干皮鞋...
  •   在前面的博客中,介绍了output_delay的基础。output_delay就是从FPGA管脚输出到下游芯片之间的时钟和数据之间的关系。   output_delay的约束,就是希望输出时钟落在数据的合适的区间:   dMin = bskew   ...
  • 今天在使用DC设置随路时钟的时候发现里两个比较容易混淆的设置:max_delay/min_delay和input_delay/output_delay。 1)max_delay/min_delay设置指定路径的最大延迟和最小延迟。  如果电路完全是有组合逻辑电路构成的...
  • 从测试结果看采用Task.Delay(1000).Wait()似乎更合理,但大家在实际应用中似乎更多使用await Task.Delay(1000),包括MSDN上的例子。 ``` CancellationTokenSource cts = new CancellationTokenSource(); ...
1 2 3 4 5 ... 20
收藏数 305,424
精华内容 122,169
关键字:

delay