5.Systick滴答定时器初始化(HAL库)&无OS延时函数

1.Systick滴答定时器简介

SysTick是属于CM4内核中的外设,其寄存器的定义和部分库函数都在core_cm4.h中实现。Systick是一个24bit的向下递减的计数器,一般设置其时钟等于SYSCLK的180M(另一个选择是HCLK/8)。当重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,以此循环往复。基于CM4内核的MCU都有这个系统定时器,使得软件在CM4 单片机中可以很容易的移植。因此它一般用于RTOS,用于产生时基,维持操作系统的心跳。Systick用于RTOS的总结我打算以后写UCOS博客的时候总结,这篇博客主要介绍无OS时,Systick初始化过程和延时函数使用方法。

2.Systick主要寄存器

在这里插入图片描述
控制寄存器: CLKSOURCE用于选择时钟源,一般会选择处理器时钟;ENABLE位用于使能Systick;TICKINT位置1会开启Systick中断。
在这里插入图片描述
重装载值寄存器:
在这里插入图片描述

3.Systick中断优先级

SysTick属于内核外设,和普通外设的中断优先级不同,没有抢占优先级和响应优先级的说法。在F429 中,内核外设的中断优先级由内核SCB的外设的寄存器SHPRx(x=1.2.3)配置。SPRH1-SPRH3是32位的寄存器,只能通过字节访问,每 8个字段控制着一个内核外设的中断优先级的配置。在F429 中只有位 7:3 这高四位有效,所以内核外设的中断优先级可编程为:0~15,数值越小优先级越高。
在这里插入图片描述
在这里插入图片描述

4.Systick初始化流程

  1. HAL_Init()是主函数main中执行的第一个函数,会设置中断优先级,将时钟源选为16M的HSI。我们可以注意到它还调用了HAL_InitTick()函数,它的输入参数是中断优先级,函数作用是初始化Systick并配置1ms中断一次。
HAL_StatusTypeDef HAL_Init(void)
{
  /* Configure Flash prefetch, Instruction cache, Data cache */ 
#if (INSTRUCTION_CACHE_ENABLE != 0)
   __HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */

#if (DATA_CACHE_ENABLE != 0)
   __HAL_FLASH_DATA_CACHE_ENABLE();
#endif /* DATA_CACHE_ENABLE */

#if (PREFETCH_ENABLE != 0)
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */

  /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);//中断优先级分组2

  /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
  HAL_InitTick(TICK_INT_PRIORITY);
  
  /* Init the low level hardware */
  HAL_MspInit();
  
  /* Return function status */
  return HAL_OK;
}
  1. HAL_InitTick()函数对Systick进行初始化,它设置Systick每1ms中断一次,并且设置了中断优先级。在HAL_Init()调用这个函数时的输入参数是0X0F,是内核外设中中断优先级最低的。
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  /*Configure the SysTick to have interrupt in 1ms time basis*/
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

  /*Configure the SysTick IRQ priority */
  HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0);

  /* Return function status */
  return HAL_OK;
}
  1. HAL_SYSTICK_Config()函数在stm32f4xx_hal_cortex.c中,里面调用的SysTick_Config()函数在内核文件core_cm4.h中。SysTick_Config()中完成了设置LOAD寄存器中的重装载值,设置优先级,选择内部时钟源,使能中断和使能Systick。该函数在调用时的输入参数是“HAL_RCC_GetHCLKFreq()/1000”,可以将重装载值设为主频/1000,另外值得注意的是,此函数目前在HAL_Init()中调用,因此时钟源是16M的HSI。
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{
   return SysTick_Config(TicksNumb);
}
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
  {
    return (1UL);                                                   /* Reload value impossible */
  }

  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
  SysTick->VAL   = 0UL;                                             /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk;                         /* Enable SysTick IRQ and SysTick Timer */
  return (0UL);                                                     /* Function successful */
}
  1. 查看正点原子时钟初始化函数,在设置好SYSCLK,HCLK,PCLK1/2参数后,会调用函数HAL_RCC_ClockConfig()函数设置时钟。我们查看HAL_RCC_ClockConfig()函数的定义,会发现在最后两行调用了Systick初始化函数HAL_InitTick()。在第三步调用该函数的时候时钟源还是16M的HSI,因此当时LOAD寄存器的输入参数“HAL_RCC_GetHCLKFreq()/1000”,是不能在主频调整到180M后实现1ms一次的中断的。而在HAL_RCC_ClockConfig()函数中再次调用HAL_InitTick()后,HAL_SYSTICK_Config()也会被重新调用,就可以保证在主频180M的情况下实现1ms一次的Systick中断,此时重装载值应该是180000000/1000=180000。
  2. 在主函数main中调用延时初始化函数delay_init(u8 SYSCLK),SYSCLK为180。HAL_SYSTICK_CLKSourceConfig()函数的输入参数为0x04,设置SysTick->CTRL将Systick频率设置与SYSCLK一致。
void delay_init(u8 SYSCLK)
{
    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
	fac_us=SYSCLK;						//不论是否使用OS,fac_us都需要使用
}
void HAL_SYSTICK_CLKSourceConfig(uint32_t CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(CLKSource));
  if (CLKSource == SYSTICK_CLKSOURCE_HCLK)
  {
    SysTick->CTRL |= SYSTICK_CLKSOURCE_HCLK;
  }
  else
  {
    SysTick->CTRL &= ~SYSTICK_CLKSOURCE_HCLK;
  }
}

5.无OS延时函数

时钟摘取法:比如delay_us(50),在刚进入delay_us的时候先计算好这段延时需要等待的 systick 计数次数,这里为 50180(假设系统时钟为 180Mhz,因为我们设置 systick 的频率为系统时钟频率,那么 systick每增加 1,就是 1/180us),然后就一直统计systick的计数变化,直到这个值变化了 50180,一旦检测到变化达到或者超过这个值,就说明延时 50us 时间到了。

//延时nus
//nus为要延时的us数.	
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)	 
void delay_us(u32 nus)
{		
	u32 ticks;
	u32 told,tnow,tcnt=0;
	u32 reload=SysTick->LOAD;				//LOAD的值	    	 
	ticks=nus*fac_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;			//时间超过/等于要延迟的时间,则退出.
		}  
	};
}

fac_us=180,ticks是延时nus需要等待的SysTick计数次数,told 用于记录最近一次的SysTick->VAL 值,tnow是当前的SysTick->VAL值,通过他们的对比累加,实现SysTick计数次数的统计,统计值存放在 tcnt 里面,最后通过对比tcnt和ticks来判断延时是否到达,从而达到不修改SysTick实现nus的延时。

发布了8 篇原创文章 · 获赞 23 · 访问量 2217

猜你喜欢

转载自blog.csdn.net/weixin_41898804/article/details/105258983
今日推荐