1. STM32 SysTick
STM32的SysTick(系统滴答定时器)是一个为ARM Cortex-M内核提供的系统定时器,主要用于实现定时中断、延时等功能。以下是对SysTick的详细介绍:
1.1 SysTick介绍
SysTick 是个24位的递减定时器,它使用系统时钟作为时基,通过设置寄存器的值来确定定时周期。当计数器从装载值开始递减到0时,会触发一个中断(如果使能了中断),将从 RELOAD 寄存器中自动重装载定时初值,从而继续下一个周期的计数。只要不把它在 SysTick 控制及状态寄存器中的使能位清除, 就永不停息
每经过1个系统时钟周期,计数值就减1
1.2 SysTick寄存器介绍
SysTick包含四个主要寄存器,都是24位的:
- SysTick->CTRL: SysTick控制及状态寄存器,用于配置SysTick的工作模式和状态。
- SysTick->LOAD: SysTick重装载寄存器,存储了SysTick的初始计数值(预期值减1)。
- SysTick->VAL: SysTick当前值寄存器,存储了SysTick的当前计数值。
- SysTick->CALIB: SysTick校准值寄存器,提供了校准SysTick所需的信息。
1.3 SysTick相关函数
若Timebase Source改为其他Timer需重新定义HAL_InitTick函数
HAL_Init(void)
---HAL_InitTick(TICK_INT_PRIORITY);//
//主要是调用HAL_SYSTICK_Config()函数,函数每隔1ms中断一次
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
//HAL库的SYSTICK配置函数,在文件:stm32f1xx_hal_context.c 中
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{
//内核的 Systick 配置函数,配置每隔 ticks 个 systick 周期中断一次
//文件 core_cm3.h 中
return SysTick_Config(TicksNumb);
}
SysTick_Handler函数处理SysTick中断。
void SysTick_Handler(void)
{
HAL_IncTick();
}
HAL_IncTick函数被调用都会给uwTick加1
__IO uint32_t uwTick;
HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT; /* 1KHz */
HAL_TICK_FREQ_DEFAULT = HAL_TICK_FREQ_1KHZ
HAL_TICK_FREQ_1KHZ = 1U,
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
1.4 HAL_Delay的实现
HAL_GetTick函数获取SysTick中断加1的uwTick值
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
不停的获取uwTick值,和刚被调用时的uwTick值相减,差值达到设定的延时Delay值,即推出这个函数,该延时函数是阻塞,不适用在OS系统中
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while ((HAL_GetTick() - tickstart) < wait)
{
}
}
2. FreeRTOS SysTick
在FreeRTOS中SysTick定时器尤为重要,因为它是给FreeRTOS系统提供时钟的。在FreeRTOS中任务的切换即每个任务运行的时间是由SysTick定时器提供的。
2.1 TIM作为Timebase
在移植RTOS系统时,原STM32的Timebase Source需要改为其他Timer,并且重新定义HAL_InitTick函数;这里以改为TIM6为示例
Timer的中断回调函数中,要调用HAL_IncTick(),即可实现系统tick按节奏增加;
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
RCC_ClkInitTypeDef clkconfig;
uint32_t uwTimclock, uwAPB1Prescaler = 0U;
uint32_t uwPrescalerValue = 0U;
uint32_t pFLatency;
HAL_StatusTypeDef status = HAL_OK;
/* Enable TIM6 clock */
__HAL_RCC_TIM6_CLK_ENABLE();
/* Get clock configuration */
HAL_RCC_GetClockConfig(&clkconfig, &pFLatency);
/* Get APB1 prescaler */
uwAPB1Prescaler = clkconfig.APB1CLKDivider;
/* Compute TIM6 clock */
if (uwAPB1Prescaler == RCC_HCLK_DIV1)
{
uwTimclock = HAL_RCC_GetPCLK1Freq();
}
else
{
uwTimclock = 2UL * HAL_RCC_GetPCLK1Freq();
}
/* Compute the prescaler value to have TIM6 counter clock equal to 1MHz */
uwPrescalerValue = (uint32_t) ((uwTimclock / 1000000U) - 1U);
/* Initialize TIM6 */
htim6.Instance = TIM6;
/* Initialize TIMx peripheral as follow:
+ Period = [(TIM6CLK/1000) - 1]. to have a (1/1000) s time base.
+ Prescaler = (uwTimclock/1000000 - 1) to have a 1MHz counter clock.
+ ClockDivision = 0
+ Counter direction = Up
*/
htim6.Init.Period = (1000000U / 1000U) - 1U;//999
htim6.Init.Prescaler = uwPrescalerValue;//71
htim6.Init.ClockDivision = 0;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
status = HAL_TIM_Base_Init(&htim6);
if (status == HAL_OK)
{
/* Start the TIM time Base generation in interrupt mode */
status = HAL_TIM_Base_Start_IT(&htim6);
if (status == HAL_OK)
{
/* Enable the TIM6 global Interrupt */
HAL_NVIC_EnableIRQ(TIM6_IRQn);
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
/* Configure the TIM IRQ priority */
HAL_NVIC_SetPriority(TIM6_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
status = HAL_ERROR;
}
}
}
/* Return function status */
return status;
}
/**
* @brief This function handles TIM6 global interrupt.
*/
void TIM6_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim6);
}
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM6 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
HAL_IncTick();
}
}
2.2 FreeRTOS SysTick函数
FreeRTOS使用SysTick作为系统时基,寄存器与之前一样,本质还是操作硬件寄存器状态
在FreeRTOS中已经提供了SysTick配置的函数vPortSetupTimerInterrupt(),函数在port.c文件中。当调用了开启任务调度函数vTaskStartScheduler()后里面就会调用该函数完成SysTick的配置
vTaskStartScheduler();
---xPortStartScheduler();
------vPortSetupTimerInterrupt();//启动定时器,产生tick ISR。这里已经禁用了中断
__weak void vPortSetupTimerInterrupt( void )
{
/* Calculate the constants required to configure the tick interrupt. */
#if ( configUSE_TICKLESS_IDLE == 1 )//条件编译,这段不编译
{
}
#endif /* configUSE_TICKLESS_IDLE */
/* Stop and clear the SysTick. */
portNVIC_SYSTICK_CTRL_REG = 0UL;//清空控制及状态寄存器
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;//清空当前值寄存器
/* Configure SysTick to interrupt at the requested rate. */
/*设置重装载寄存器的值*/
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
/*设置控制及状态寄存器的值*/
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
/*( 1UL << 2UL ) | ( 1UL << 1UL ) | ( 1UL << 0UL ) */
/*选择处理器时钟、开定时器中断、使能定时器*/
}
FreeRTOS 中systick寄存器相关宏,寄存器地址与1.2章节一样
/* Constants required to manipulate the core. Registers first... */
#define portNVIC_SYSTICK_CTRL_REG ( *( ( volatile uint32_t * ) 0xe000e010 ) )
#define portNVIC_SYSTICK_LOAD_REG ( *( ( volatile uint32_t * ) 0xe000e014 ) )
#define portNVIC_SYSTICK_CURRENT_VALUE_REG ( *( ( volatile uint32_t * ) 0xe000e018 ) )
FreeRTOS 中systick对应的中断处理函数xPortSysTickHandler(),功能类似与SysTick_Handler()
void xPortSysTickHandler( void )
{
/* The SysTick runs at the lowest interrupt priority, so when this interrupt
* executes all interrupts must be unmasked. There is therefore no need to
* save and then restore the interrupt mask value as its value is already
* known - therefore the slightly faster vPortRaiseBASEPRI() function is used
* in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
vPortRaiseBASEPRI();
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
/* A context switch is required. Context switching is performed in
* the PendSV interrupt. Pend the PendSV interrupt. */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}