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

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

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
在这里插入图片描述
STM32 时延函数之阻塞和非阻塞的实现讨论_第1张图片

设置TIM1参数,分频系数10,000,计数频率为8KHz
在这里插入图片描述
STM32 时延函数之阻塞和非阻塞的实现讨论_第2张图片

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

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在这里插入图片描述
STM32 时延函数之阻塞和非阻塞的实现讨论_第3张图片

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

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参数
STM32 时延函数之阻塞和非阻塞的实现讨论_第4张图片

编写延时函数
(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++;
	}
}

开始使用!

你可能感兴趣的:(STM32)