STM32 时延函数之阻塞和非阻塞的实现讨论

本文详细介绍STM32上的各种延时方法,包括HAL库函数、中断延时、非阻塞式延时及定时器配置。同时,文章指出HAL_Delay函数在中断中使用可能引发的问题,并提供基于定时器的非阻塞式延时解决方案。

一 、常使用的几种延时方式

1 自带的hal_delay 函数 毫秒级延迟

void HAL_Delay(__IO uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick(); //获取tick值(毫秒)
  uint32_t wait = Delay;
 
  /* Add a period to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
     wait++;//传参,延时的时间
  }
 
  while((HAL_GetTick() - tickstart) < wait)
  {
  }
}
也可以配置为us延时,改变函数参数

配置方法:也可以配置为1us

// HAL_RCC_GetHCLKFreq()/1000 1ms中断一次,即HAL_Delay函数延时基准为1ms
// HAL_RCC_GetHCLKFreq()/100000  10us中断一次,即HAL_Delay函数延时基准为10us
// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次,即HAL_Delay函数延时基准为1us
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000000);  // 配置并启动系统滴答定时器

2 中断延时----利用定时器计时

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  LED_GPIO_Init();
  /* 基本定时器初始化:1ms中断一次 */
  BASIC_TIMx_Init();
  /* 在中断模式下启动定时器 */
  HAL_TIM_Base_Start_IT(&htimx);
 
  while (1)
  {
    if(timer_count==1000)
    {
      timer_count=0;
      LED1_TOGGLE;
    }
  }
}
 
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  timer_count++;  //回调函数
}

3 优选方式------获取系统时钟计时,非阻塞式延时

 void delay_ms(int32_t nms) 
 {
  int32_t temp; 
  SysTick->LOAD = 8000*nms; 
  SysTick->VAL=0X00;//清空计数器 
  SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源 
  do 
  { 
       temp=SysTick->CTRL;//读取当前倒计数值 
  }
     while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达 
     
     SysTick->CTRL=0x00; //关闭计数器 
     SysTick->VAL =0X00; //清空计数器 
 } 

二 、存在的问题

1. HAL库延时函数HAL_Delay()遇坑,

初学HAL库感受着它强大的封装库可以拿来直接用,对于初学者很容易上手,看来以后小学生都能写单片机了
问题:延时函数一直用的HAL_Delay()毫秒级延时,但是在中断中调用延时函数却卡死在这里,从封装函数中可以发现函数中有中断获取系统时钟HAL_IncTick(void),由于优先级系统给的低,所以在高优先级的中断中无法产生这个低级的中断,导致程序卡死在HAL_Delay()中。

void delay_ms(int32_t nms) 

 {

  int32_t temp; 

  SysTick->LOAD = 8000*nms; 

  SysTick->VAL=0X00;//清空计数器 

  SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源 

  do 

  { 

       temp=SysTick->CTRL;//读取当前倒计数值 

  }

     while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达 

     

     SysTick->CTRL=0x00; //关闭计数器 

     SysTick->VAL =0X00; //清空计数器 

 }

注意:该函数好像和HAL_Delay()有冲突?不能混用在一起

2. HAL_Delay()函数锁死

HAL_Delay函数用的是中断延时,当程序中存在多个中断时,容易锁死,需要慎用。
如果在中断服务程序里面调用延迟函数 HAL_Delay 要特别注意,因为这个函数的时间基准是基于滴答定时器或者其他通用定时器实现,实现方式是滴答定时器或者其他通用定时器里面做了个变量计数。如此一来,结果是显而易见的,如果其他中断服务程序调用了此函数,且中断优先级高于滴答定时器,会导致滴答定时器中断服务程序一直得不到执行(即变量计数值无法递减0,导致HAL_Delay函数无法执行完),从而卡死在里面。所以滴答定时器的中断优先级一定要比它们高。
一句话总结就是,调用HAL_Delay函数的中断服务函数的中断优先级必须低于滴答定时器的优先级或者低于HAL_Delay函数使用的定时器的优先级。

3. CUBE生成的程序中, SysTick是中断型延时(利用中断来查询时间到了没)。

  /* Use systick as time base source and configure 1ms tick (default clock after Reset is MSI) */
  HAL_InitTick(TICK_INT_PRIORITY);



#define  TICK_INT_PRIORITY            ((uint32_t)0x000F)    /*!< tick interrupt priority */    

SysTick是内核中断,优先级别默认最低。

(可以用内核函数来修改~ 当然,这就要看内核M3的书了,而不是看STM32的参考手册那么简单。暂时就不深入研究,日后更新。)

总结起来就是,就是传说中优先级别默认最低,虽然SysTick一直在跑,但是没进入到中断来读取它的值~

(不知是哪里让我潜意识地认为SysTick级别比外设都高,导致这问题)

如果中断里调用HAL_Delay就会停在那里,因为根本不会进入那个级别更低的中断。

资料补充:

网上还有一种写 法是时间摘取法,是一直读取SysTick产生延时函数~(原子的例程就是用这种方法)
其次,有人提到,中断里面不应该使用延时,中断所占的时间越短越好有道理

附上原子的时间摘取法的程序,很有学习价值~

//////////////////////////////////////////////////////////////////////////////////	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//Mini STM32开发板
//使用SysTick的普通计数模式对延迟进行管理
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2010/5/27
//版本:V1.2
//版权所有,盗版必究。
//Copyright(C) 正点原子 2009-2019
//All rights reserved
//********************************************************************************
//V1.2修改说明
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!
//////////////////////////////////////////////////////////////////////////////////	 
static u8  fac_us=0;//us延时倍乘数
static u16 fac_ms=0;//ms延时倍乘数
//初始化延迟函数
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init(u8 SYSCLK)
{
	SysTick->CTRL&=0xfffffffb;//bit2清空,选择外部时钟  HCLK/8
	fac_us=SYSCLK/8;		    
	fac_ms=(u16)fac_us*1000;
}								    
//延时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=0x01 ;          //开始倒数  
	do
	{
		temp=SysTick->CTRL;
	}
	while(temp&0x01&&!(temp&(1<<16)));//等待时间到达   
	SysTick->CTRL=0x00;       //关闭计数器
	SysTick->VAL =0X00;       //清空计数器	  	    
}   
//延时nus
//nus为要延时的us数.		    								   
void delay_us(u32 nus)
{		
	u32 temp;	    	 
	SysTick->LOAD=nus*fac_us; //时间加载	  		 
	SysTick->VAL=0x00;        //清空计数器
	SysTick->CTRL=0x01 ;      //开始倒数 	 
	do
	{
		temp=SysTick->CTRL;
	}
	while(temp&0x01&&!(temp&(1<<16)));//等待时间到达   
	SysTick->CTRL=0x00;       //关闭计数器
	SysTick->VAL =0X00;       //清空计数器	 
}

三、基于HAL实现非阻塞式定时器

1. 配置定时器1为 ms 毫秒延时定时器

假如我们要看TIM1在哪个时钟下,进入TIM1时钟开启函数即可
假如我们要看TIM1在哪个时钟下,进入TIM1时钟开启函数即可在这里插入图片描述

配置定时器1为 ms 毫秒延时定时器
根据文章开头的方法,定时器1使用APB2时钟,主频80MHz
在这里插入图片描述
在这里插入图片描述

设置TIM1参数,分频系数10,000,计数频率为8KHz
在这里插入图片描述
在这里插入图片描述

生成工程,添加延时函数代码

void tx_delay_ms(uint16_t nms)	//量程0-8191ms
{
		__HAL_TIM_SetCounter(&htim1, 0);//htim1

		__HAL_TIM_ENABLE(&htim1);

		while(__HAL_TIM_GetCounter(&htim1) < (8 * nms));//计数频率8KHz,8次即为1ms
		/* Disable the Peripheral */
		__HAL_TIM_DISABLE(&htim1);
}

然后再在头文件添加声明即可。

2. 配置定时器8为 us 微秒延时定时器

根据文章开头的方法,定时器8使用APB2时钟,主频80MHz
在这里插入图片描述
在这里插入图片描述

设置TIM8参数,分频系数10,计数频率为8MHz在这里插入图片描述
在这里插入图片描述

生成工程,添加延时函数代码

void tx_delay_us(uint16_t nus)	//量程0-8191us
{
		__HAL_TIM_SetCounter(&htim8, 0);//htim8

		__HAL_TIM_ENABLE(&htim8);

		while(__HAL_TIM_GetCounter(&htim8) < (8 * nus));//计数频率8MHz,8次即为1us
		/* Disable the Peripheral */
		__HAL_TIM_DISABLE(&htim8);
}

然后再在头文件添加声明即可

3 中断方式延时函数

我们使用定时器3来产生中断,定时器3在APB1下,80MHz
在这里插入图片描述

在这里插入图片描述
设置定时器3参数
在这里插入图片描述

编写延时函数
(1) 首先声明中断累加变量

static volatile uint16_t tim3_counter;

(2)编写延时函数延时时长(0-65535ms)

void tim3_delay_ms(uint16_t nms)
{
	tim3_counter = 0;
	while(tim3_counter!=nms);
}

(3)设置定时器中断回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if (htim->Instance == htim3.Instance)	//定时器3中断 1ms/次
	{
		tim3_counter++;
	}
}

开始使用!

<think>好的,我现在需要帮助用户找到关于STM32实现非阻塞延时函数的方法代码示例。用户之前提供的引用资料主要涉及阻塞延时函数,比如使用循环或SysTick定时器。但用户现在明确要求非阻塞的方法,所以我得回忆一下相关知识,并结合引用内容思考如何整合信息。 首先,非阻塞延时的核心是不占用CPU资源,允许程序在等待期间执行其他任务。通常,这可以通过状态机、定时器中断或系统滴答定时器(SysTick)来实现。在用户提供的引用中,引用[1]提到了SysTick的初始化中断,这可能与非阻塞延时有关。引用[3][4]中的代码都是阻塞式的,比如循环延时,所以需要排除这些方法。 接下来,我需要考虑如何利用SysTick或硬件定时器来记录时间流逝,从而判断延时是否完成。例如,使用全局变量来存储时间戳,然后在主循环中检查当前时间是否超过预设的延时时间。这种方法需要定时器提供精确的时间基准,比如每毫秒递增一个计数器。 用户提供的引用[1]中的Delay_Init函数初始化了SysTick,可能用于产生定时中断。这里可以设想,在中断服务例程中更新一个全局时间变量,比如uwTick,类似于HAL库中的做法。然后,非阻塞延时函数可以通过比较当前uwTick开始时间来判断是否完成延时。 另外,引用[3][4]中提到了循环延时,但这些都是阻塞的,不适合非阻塞需求。所以,正确的方向应该是基于定时器中断的方法。需要确保代码示例中使用的是状态检查而非等待循环。 可能还需要考虑多任务情况下的时间管理,例如多个延时任务同时进行。因此,每个延时实例需要独立的开始时间延时长度,可能通过结构体或数组来管理。 最后,我需要确保代码示例正确使用了SysTick,并且有初始化更新时间戳的部分。同时,需要说明如何调用这些函数,比如在main循环中检查时间是否到达,从而执行相应的操作而不阻塞其他代码。</think>### STM32非阻塞延时函数实现方法 非阻塞延时的核心思想是通过**时间戳比对**实现延时判断,无需占用CPU资源。以下是三种常用实现方式及代码示例: --- ### 方法一:SysTick滴答定时器方案 #### 实现原理 利用SysTick定时器每1ms产生中断更新全局时间戳,通过比对时间差实现非阻塞判断[^1][^3] ```c // delay.h volatile uint32_t uwTick = 0; // 全局时间戳 void Delay_Init(void) { SysTick_Config(SystemCoreClock / 1000); // 1ms中断 } uint32_t HAL_GetTick(void) { return uwTick; } // SysTick中断服务函数 void SysTick_Handler(void) { uwTick++; } // 非阻塞延时判断函数 uint8_t Delay_Elapsed(uint32_t *prev_tick, uint32_t delay) { if(HAL_GetTick() - *prev_tick >= delay) { *prev_tick = HAL_GetTick(); return 1; } return 0; } ``` #### 使用示例 ```c uint32_t led_timestamp = 0; while(1) { if(Delay_Elapsed(&led_timestamp, 500)) { // 500ms间隔 LED_Toggle(); // 非阻塞执行LED翻转 } // 此处可执行其他任务 } ``` --- ### 方法二:硬件定时器方案 #### 实现原理 使用通用定时器(如TIM2)实现高精度计时,通过捕获比较寄存器自动更新时间戳[^1] ```c // 启用TIM2定时器(1MHz时钟) void TIM2_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitTypeDef timer; timer.TIM_Prescaler = 72-1; // 72MHz/72=1MHz timer.TIM_CounterMode = TIM_CounterMode_Up; timer.TIM_Period = 0xFFFFFFFF; TIM_TimeBaseInit(TIM2, &timer); TIM_Cmd(TIM2, ENABLE); } // 获取当前计时值 uint32_t Get_Micros(void) { return TIM2->CNT; } // 微秒级非阻塞判断 uint8_t Delay_NonBlocking(uint32_t *prev_time, uint32_t us) { if(Get_Micros() - *prev_time >= us) { *prev_time = Get_Micros(); return 1; } return 0; } ``` --- ### 方法三:状态机方案 #### 实现原理 通过结构体封装延时状态,支持多任务并行处理[^2] ```c typedef struct { uint32_t start_time; uint32_t duration; uint8_t is_running; } NonBlockDelay_t; void Delay_Start(NonBlockDelay_t *delay, uint32_t ms) { delay->start_time = HAL_GetTick(); delay->duration = ms; delay->is_running = 1; } uint8_t Delay_Check(NonBlockDelay_t *delay) { if(delay->is_running && (HAL_GetTick() - delay->start_time >= delay->duration)) { delay->is_running = 0; return 1; } return 0; } ``` #### 使用示例 ```c NonBlockDelay_t led_delay, sensor_delay; Delay_Start(&led_delay, 500); // LED延时500ms Delay_Start(&sensor_delay, 2000); // 传感器延时2s while(1) { if(Delay_Check(&led_delay)) { LED_Toggle(); Delay_Start(&led_delay, 500); // 重启延时 } if(Delay_Check(&sensor_delay)) { Read_Sensor(); Delay_Start(&sensor_delay, 2000); } } ``` --- ### 关键点对比 | 方法 | 精度 | 资源占用 | 适用场景 | |-------------|-------|----------|------------------------| | SysTick | 1ms | 低 | 常规任务调度 | | 硬件定时器 | 1μs | 中 | 高精度控制 | | 状态机 | 1ms | 低 | 多任务并行管理 | ---
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值