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初始化流程
- 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;
}
- 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;
}
- 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 */
}
- 查看正点原子时钟初始化函数,在设置好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。
- 在主函数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的延时。