目录
在一个FreeRTOS应用中,系统具有两个基础时钟:HAL和FreeRTOS基础时钟。所以要搞清楚为什么存在HAL和FreeRTOS两个基础时钟,以及它们是如何工作的。
1、使用SysTick作为HAL的基础时钟
HAL需要设置一个定时器作为基础时钟。基础时钟通过定时中断产生嘀嗒信号,嘀嗒信号的默认频率是1000Hz,也就是基础时钟的定时周期是1ms。基础时钟主要用于实现延时函数HAL_Delay(),或在一些有超时(timeout)设置的函数里确定延时。
在不使用FreeRTOS时,CubeMX默认将HAL的基础时钟源设置为SysTick定时器。SysTick是Cortex-M内核自带的一个24位的定时器,将SysTick作为HAL的基础时钟后,在NVIC中会自动启用SysTick的中断,并且优先级设置为最高。用户可以修改SysTick的中断优先级,但是不能禁用SysTick中断。
(1)基础时钟的初始化
在CubeMX生成的初始化代码中,HAL_Init()是main()函数中执行的第一个函数。HAL_Init()对SysTick定时器进行设置,使其定时中断周期为1ms。函数HAL_Init()的源代码如下:
/**
* @brief This function is used to initialize the HAL Library; it must be the first
* instruction to be executed in the main program (before to call any other
* HAL function), it performs the following:
* Configure the Flash prefetch, instruction and Data caches.
* Configures the SysTick to generate an interrupt each 1 millisecond,
* which is clocked by the HSI (at this stage, the clock is not yet
* configured and thus the system is running from the internal HSI at 16 MHz).
* Set NVIC Group Priority to 4.
* Calls the HAL_MspInit() callback function defined in user file
* "stm32f4xx_hal_msp.c" to do the global low level hardware initialization
*
* @note SysTick is used as time base for the HAL_Delay() function, the application
* need to ensure that the SysTick time base is always set to 1 millisecond
* to have correct HAL operation.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_Init(void)
{
/* Configure Flash prefetch, Instruction cache, Data cache */
#if (INSTRUCTION_CACHE_ENABLE != 0U)
__HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */
#if (DATA_CACHE_ENABLE != 0U)
__HAL_FLASH_DATA_CACHE_ENABLE();
#endif /* DATA_CACHE_ENABLE */
#if (PREFETCH_ENABLE != 0U)
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* 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(TICK_INT_PRIORITY)是对SysTick定时器进行定时周期和中断的设置。HAL_InitTick()是在文件stm32f4xx_hal.c中用__weak修饰符定义的弱函数,其源代码如下:
/**
* @brief This function configures the source of the time base.
* The time source is configured to have 1ms time base with a dedicated
* Tick interrupt priority.
* @note This function is called automatically at the beginning of program after
* reset by HAL_Init() or at any time when clock is reconfigured by HAL_RCC_ClockConfig().
* @note In the default implementation, SysTick timer is the source of time base.
* It is used to generate interrupts at regular time intervals.
* Care must be taken if HAL_Delay() is called from a peripheral ISR process,
* The SysTick interrupt must have higher priority (numerically lower)
* than the peripheral interrupt. Otherwise the caller ISR process will be blocked.
* The function is declared as __weak to be overwritten in case of other
* implementation in user file.
* @param TickPriority Tick interrupt priority.
* @retval HAL status
*/
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
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_InitTick()可以被重新实现。在使用SysTick作为基础时钟时,使用的就是文件stm32f4xx_hal.c中的HAL_InitTick()。
函数HAL_Init()中最后调用的函数HAL_MspInit()也是一个弱函数,在HAL驱动中就是个空函数。在CubeMX生成的初始代码中,在文件stm32f4xx_hal_msp.c中重新实现了这个函数,功能就是启用了RCC的时钟信号,并设置中断优先级分组策略,也就是图11-2中用户设置的就先级分组策略。文件stm32f4xx_hal_msp.c中重新实现的函数HAL_MspInit()的代码如下:
/**
* @brief Initialize the MSP.
* @retval None
*/
__weak void HAL_MspInit(void)
{
/* NOTE : This function should not be modified, when the callback is needed,
the HAL_MspInit could be implemented in the user file
*/
}
所以,main()函数执行函数HAL_Init()后,就设置了SysTick定时器的定时周期和中断优先级。默认的SysTick定时周期为1ms,产生的嘀嗒信号频率为1000Hz。所以,基础定时器也称为嘀嗒定时器。
(2)基础时钟的中断处理
在文件stm32f4xx_it.c中,自动生成了SysTick定时器中断的ISR,其代码如下:
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
在SysTick定时器的定时中断里,执行了函数HAL_IncTick(),这是在文件stm32f4xx_hal.c中实现的函数,函数的代码如下:
/**
* @brief This function is called to increment a global variable "uwTick"
* used as application time base.
* @note In the default implementation, this variable is incremented each 1ms
* in SysTick ISR.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @retval None
*/
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
它的功能就是使得全局变量uwTick递增,这个变量就是嘀嗒信号的计数值。当嘀嗒信号频率为1000Hz时,递增量uwTickFreq的值为1;当嘀嗒信号频率为100Hz时,uwTickFreq的值为10。
在文件stm32f4xx_hal.c中,还定义了操作嘀嗒定时器的两个函数,用于暂停和恢复滴答定时器,都是用__weak定义的弱函数,代码如下:
/*禁止SysTick中断*/
/**
* @brief Suspend Tick increment.
* @note In the default implementation , SysTick timer is the source of time base. It is
* used to generate interrupts at regular time intervals. Once HAL_SuspendTick()
* is called, the SysTick interrupt will be disabled and so Tick increment
* is suspended.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @retval None
*/
__weak void HAL_SuspendTick(void)
{
/* Disable SysTick Interrupt */
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
}
/*恢复SysTick中断*/
/**
* @brief Resume Tick increment.
* @note In the default implementation , SysTick timer is the source of time base. It is
* used to generate interrupts at regular time intervals. Once HAL_ResumeTick()
* is called, the SysTick interrupt will be enabled and so Tick increment
* is resumed.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @retval None
*/
__weak void HAL_ResumeTick(void)
{
/* Enable SysTick Interrupt */
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
}
常用的延时函数HAL_Delay()就是利用嘀嗒信号来实现的,其代码如下:
/**
* @brief This function provides minimum delay (in milliseconds) based
* on variable incremented.
* @note In the default implementation , SysTick timer is the source of time base.
* It is used to generate interrupts at regular time intervals where uwTick
* is incremented.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @param Delay specifies the delay time length, in milliseconds.
* @retval None
*/
__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) //最少延时1ms
{
wait += (uint32_t)(uwTickFreq); //获取嘀嗒信号当前计数值
}
while((HAL_GetTick() - tickstart) < wait)
{
}
}
程序中调用的函数HAL_GetTick()用于返回全局变量uwTick的值,也就是嘀嗒信号的当前计数值。函数HAL_Delay()的输入参数Delay是以毫秒为单位的延时时间。延时的原理是:先读取嘀嗒信号的当前计数值,保存到变量tickstart中,计算在此基础上延时所需要的计数值差量wait,然后在while循环中,不断地用函数HAL_GetTick()读取滴答信号当前计数值,计算相对于tickstart的差量,当差量超过wait时,就达到了延时时间。
使用SysTick作为HAL基础定时器时,其作用就是用于产生嘀嗒信号计数值,然后用于延时计算。如果不需要用到延时计算,停掉SysTick定时器对系统运行是没有什么影响的。例如,在低功耗设计时,为了使系统进入睡眠模式后不被SysTick的中断唤醒,应暂停SysTick定时器的中断。
2、使用其他定时器作为HAL的基础时钟
在不使用FreeRTOS时,HAL基础时钟默认是SysTick,也可以选择其他定时器作为基础时钟,例如,可以选择定时器TIM6作为HAL的基础时钟。
选择TIM6作为HAL基础时钟后,TIM6就不能再用作其他用途,在CubeMX中不能再对TIM6做任何设置。在NVIC设置中,自动启用TIM6的中断,优先级应设置为最高。可以修改TIM6的中断优先级,但是不能关闭TIM6的中断。同时,SysTick定时器的中断也是自动启用的,且不能关闭。
(1)基础时钟的初始化
在使用定时器TIM6作为HAL的基础时钟,并由CubeMX生成代码后,项目的Src目录下新增了一个文件stm32f4xx_hal_timebase_tim.c,这个文件里重新实现了文件stm32f4xx_hal.c中的3个弱函数,用定时器TIM6替代了SysTick的功能。文件stm32f4xx_hal_timebase_tim.c的完整代码如下:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file stm32f4xx_hal_timebase_tim.c
* @brief HAL time base based on the hardware TIM.
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"
#include "stm32f4xx_hal_tim.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim6;
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/**
* @brief This function configures the TIM6 as a time base source.
* The time source is configured to have 1ms time base with a dedicated
* Tick interrupt priority.
* @note This function is called automatically at the beginning of program after
* reset by HAL_Init() or at any time when clock is configured, by HAL_RCC_ClockConfig().
* @param TickPriority: Tick interrupt priority.
* @retval HAL status
*/
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;
/* 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;
htim6.Init.Prescaler = uwPrescalerValue;
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_DAC_IRQn);
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
/* Configure the TIM IRQ priority */
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
status = HAL_ERROR;
}
}
}
/* Return function status */
return status;
}
/**
* @brief Suspend Tick increment.
* @note Disable the tick increment by disabling TIM6 update interrupt.
* @param None
* @retval None
*/
void HAL_SuspendTick(void)
{
/* Disable TIM6 update Interrupt */
__HAL_TIM_DISABLE_IT(&htim6, TIM_IT_UPDATE);
}
/**
* @brief Resume Tick increment.
* @note Enable the tick increment by Enabling TIM6 update interrupt.
* @param None
* @retval None
*/
void HAL_ResumeTick(void)
{
/* Enable TIM6 Update interrupt */
__HAL_TIM_ENABLE_IT(&htim6, TIM_IT_UPDATE);
}
函数HAL_InitTick()是在HAL_Init()中被调用的,重新实现的这个函数对定时器TIM6进行了初始化配置,设置其中断优先级,配置其分频系数、计数周期等,使其定时器周期为1ms。
重新实现的函数HAL_ResumeTick()和HAL_SuspendTick()也是对TIM6的操作。
(2)基础时钟的中断处理
使用定时器TIM6作为HAL的基础时钟并用CubeMX生成代码后,在文件stm32f4xx_it.c中,SysTick的ISR代码变成了空的,TIM6的ISR代码如下:
/**
* @brief This function handles TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts.
*/
void TIM6_DAC_IRQHandler(void)
{
/* USER CODE BEGIN TIM6_DAC_IRQn 0 */
/* USER CODE END TIM6_DAC_IRQn 0 */
HAL_TIM_IRQHandler(&htim6);
/* USER CODE BEGIN TIM6_DAC_IRQn 1 */
/* USER CODE END TIM6_DAC_IRQn 1 */
}
定时器UEV中断的回调函数是HAL_TIM_PeriodElapsedCallback(),在文件main.c中自动重新实现了这个函数,其功能就是执行函数HAL_IncTick(),代码如下:
/**
* @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)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM6)
{
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
所以,在使用TIM6作为HAL的基础时钟后,TIM6完全替代了SysTick。
3、 FreeRTOS的基础时钟
FreeRTOS必须使用SysTick定时器作为其基础时钟,用于产生FreeRTOS的嘀嗒信号,而且在SysTick的定时中断里进行任务状态检查,发出任务调度申请。
在使用FreeRTOS时,如果执意使用SysTick作为HAL的基础时钟,生成的代码构建后是无法正常运行的,即使FreeRTOS只有一个非常简单的任务。FreeRTOS因没有基础时钟,无法产生嘀嗒信号,所以无法正常运行。
在使用FreeRTOS时,必须为HAL设置一个非SysTick定时器作为HAL的基础时钟,SysTick将自动作为FreeRTOS的基础时钟。这是由FreeRTOS的移植决定的,因为SysTick是CortexM内核的一个定时器,在整个STM32系列中都是存在的,使用SysTick作为FreeRTOS的基础时钟进行移植,显然适用性更强,针对不同系列的STM32处理器进行移植时,需要的改动最少。
(1)SysTick定时器的初始化
在CubeMX中,基于STM32F407ZG创建一个项目,使用TIM6作为HAL的基础时钟,启用FreeRTOS后NVIC的自动设置结果如上图。定时器TIM6的抢占优先级为0(默认为15,一般修改为0),SySTick和PendSV中断的优先级都为15,而且这2个中断都不能被关闭,不能修改优先级。
在FreeRTOS中,系统嘀嗒信号的频率由参数configTICK_RATE_HZ决定,默认是1000Hz。SysTick通过定时中断产生嘀嗒信号,SysTick默认定时周期是1ms。
在main()函数中,执行函数osKernelStart()启动内核时,对SysTick定时器进行初始化设置。跟踪函数osKernelStart()的源代码,发现最终设置SysTick定时器的是文件port.c中的函数xPortStartScheduler()和vPortSetupTimerInterrupt()。函数xPortStartScheduler()设置SysTick和PendSV中断的中断优先级,函数vPortSetupTimerInterrupt()设置SySTick的定时周期,代码如下:
/*
* Setup the systick timer to generate the tick interrupts at the required
* frequency.
*/
__attribute__(( weak )) void vPortSetupTimerInterrupt( void )
{
/* Calculate the constants required to configure the tick interrupt. */
#if( configUSE_TICKLESS_IDLE == 1 )
{
ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
}
#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 | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}
函数vPortSetupTimerInterrupt()的功能是配置SysTick定时器相关的寄存器,使其以设定的频率产生中断。如果参数configUSE_TICKLESS_IDLE的值等于1,也就是使用了Tickless低功耗模式,会计算几个常数,这几个常数会在Tickless低功耗模式的时候用于嘀嗒计数值的补偿。
函数中用到的宏portNVIC_SYSTICK_CTRL_REG、portNVIC_SYSTICK_LOAD_REG等是SysTick相关寄存器的移植定义,在文件port.c中的定义如下:
/* 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 ) )
#define portNVIC_SYSPRI2_REG ( * ( ( volatile uint32_t * ) 0xe000ed20 ) )
查阅Cortex-M4内核技术手册会发现,这3个宏对应的就是SysTick的控制和状态寄存器SYST_CSR、重载值寄存器SYST_RVR和当前值寄存器SYST_CVR。
(2)SysTick定时器的中断处理
在使用TIM6作为HAL基础时钟并启用了FreeRTOS的项目中,用户会发现在文件stm32f4xx_itc中没有SysTick中断服务例程SysTick_Handler()的代码框架。而FreeRTOS要使用SysTick的定时中断产生嘀嗒信号,必然要定义SysTick定时器中断的ISR。搜索关键字SysTick_Handler,发现在文件FreeRTOSConfig.h中有如下的宏定义:
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#if defined(SysTick)
#undef SysTick_Handler
/* CMSIS SysTick interrupt handler prototype */
extern void SysTick_Handler (void);
/* FreeRTOS tick timer interrupt handler prototype */
extern void xPortSysTickHandler (void);
/*
SysTick handler implementation that also clears overflow flag.
*/
#if (USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION == 0)
void SysTick_Handler (void) {
/* Clear overflow flag */
SysTick->CTRL;
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
/* Call tick handler */
xPortSysTickHandler();
}
}
#endif
#endif /* SysTick */
通过这2个宏定义和1个函数移植转换,FreeRTOS将自己移植的3个中断的处理函数与CMSIS的标准ISR名称关联起来。例如,SysTick中断的标准ISR名称是SysTick_Handler,FreeRTOS移植的函数名是xPortSysTickHandler。
FreeRTOS移植的SysTick中断的处理函数xPortSysTickHandler()是在文件port.c中定义的,其功能是调用函数xTaskIncrementTick()使嘀嗒信号计数值递增,并且检查是否需要进行上下文切换。如果需要进行上下文切换,就挂起PendSV中断,实际的上下文切换是在PendSV的中断处理程序里执行的。函数xPortSysTickHandler()的源代码如下:
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. */
portDISABLE_INTERRUPTS();
{
/* 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;
}
}
portENABLE_INTERRUPTS();
}
其中,调用的函数xTaskIncrementTick()是在文件task.c中实现的。文件task.c定义了一个表示嘀嗒信号当前计数值的全局变量xTickCount,函数xTaskIncrementTick()的一个功能就是每次使xTickCount的值加1,函数vTaskDelay()实现毫秒级延时就会用到全局变量xTickCount。
综上,HAL和FreeRTOS的基础时钟问题,可总结为以下几点:
- 在不使用FreeRTOS时,系统自动使用SysTick定时器作为HAL基础时钟,但也可以选择其他定时器作为HAL基础时钟。HAL基础时钟的作用是产生HAL的嘀嗒信号,默认周期是1ms,HAL的毫秒级延时函数HAL_Delay()的实现就依赖于HAL的基础时钟。
- 在使用FreeRTOS时,SysTick定时器将自动成为FreeRTOS的基础时钟,必须指定一个非SysTick定时器作为HAL基础时钟。
- 在FreeRTOS中,SysTick用于产生FreeRTOS的嘀嗒信号,默认周期是1ms。延时函数VTaskDelay()的实现就依赖于FreeRTOS的基础时钟。FreeRTOS还在SysTick的定时中断里进行任务状态检查、任务调度申请等工作。
本文不提供例程,因为很简单,读者为了印证本文的说法,可以依次建立一个不使用RTOS的工程, 分别选择SysTick定时器和其他定时器比如TIM6作为HAL基础时钟;然后再建立一个工程,选择使用FreeRTOS并选择TIM6作为HAL基础时钟。