- 传送门:博客汇总帖
笔记内容参考(正点原子的FreeRTOS开发手册、cortex-m3权威指南、Cortex-M3和Cortex-M4权威指南等)以及网络笔记
代码环境:CubeMX生成的FreeRTOS
文章目录
大纲
FreeRTOS时间管理部分,主要是涉及系统节拍以及任务延时管理。
- 系统节拍:嵌入式实时操作系统运行,必须要有时钟节拍,就像人的心脏一样。FreeRTOS的时钟节拍通常由SysTick提供,
- 任务延时管理:主要是两个延时函数(相对延时函数vTaskDelay,绝对延时函数vTaskDelayUntil)
1、SysTick
SysTick直属与 Coretx-M 内核,它不是 STM32 专属的,只要是 Cortex-M 内核的 MCU 就都有 SysTick。SysTick 定时器是个 24 位的向下计数器,这个计数器的使命就是为系统提供服务的。操作系统都需要一个系统时钟,每个系统时钟周期都会触发 OS 内核执行一些系统调用,比如进行任务管理、任务切换等。SysTick 就可以完成此功能,使能 SysTick 中断,设置好定时周期,SysTikc 就会周期性的触发中断,跟系统有关的操作就可以在 SysTick 的中断服务函数中完成。如果不使用系统的话 SysTick 也可以当作普通的定时器使用,完成简单的计时功能。SysTick 定时器被捆绑在 NVIC 中,用于产生 SysTick 异常(异常号:15)。
- 在CubeMX中与一个选项就是Timebase source,这里原本默认就是SysTick,那为什么要改为其他的定时器呢?
因为操作系统需要一个时基,而在我是用的Cortex-M中专门有一个系统时钟SysTick为OS准备。由于FreeRTOS已经使用了SysTick,所以HAL库需要重新弄一个定时器(HAL_Delay())
在文件 core_cm4.h 中用结构体 SysTick_Type 来描述这四个寄存器,结构体 SysTick_Type的定义如下:
#define SCS_BASE (0xE000E000UL)
#define SysTick_BASE (SCS_BASE + 0x0010UL)
typedef struct
{
__IOM uint32_t CTRL; //控制与状态寄存器
__IOM uint32_t LOAD; //自动重装载值寄存器
__IOM uint32_t VAL; //当前计数值寄存器
__IM uint32_t CALIB; //校准值寄存器
} SysTick_Type;
#define SysTick ((SysTick_Type *) SysTick_BASE)
实际使用中操作最多的是 CTRL、LOAD 和 VAL 这三个寄存器,CALIB 寄存器基本不会使用了。CTRL、LOAD 和 VAL 这三个寄存器各 bit 的描述如表 所示:
- CTRL寄存器的第二位设置SysTick时钟源,1为内核时钟,一般使用1,也就是系统的主频(如下图)
关于SysTick的设置[main.c --> osKernelStart() --> vTaskStartScheduler() --> xPortStartScheduler() --> vPortSetupTimerInterrupt() ]
/*
* Setup the SysTick timer to generate the tick interrupts at the required
* frequency.
*/
#if( configOVERRIDE_DEFAULT_TICK_CONFIGURATION == 0 )
__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 );
}
#define portNVIC_SYSTICK_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000e010 ) )
#define portNVIC_SYSTICK_LOAD_REG ( * ( ( volatile uint32_t * ) 0xe000e014 ) )
- portNVIC_SYSTICK_CTRL_REG设置SysTick参数:
- portNVIC_SYSTICK_CLK_BIT:使用内核时钟 168MHz
- portNVIC_SYSTICK_INT_BIT:打开SysTick中断
- portNVIC_SYSTICK_ENABLE_BIT:使能SysTick
- portNVIC_SYSTICK_LOAD_REG设置SysTick重装载值:
- configSYSTICK_CLOCK_HZ:这个值最终的定义是uint32_t SystemCoreClock = 16000000;这里的16000000是初始值,并不是最终值,因为我们已经选择了内核时钟作为RTC时钟源(168M),在系统初始化运行会进行修改。如下面的代码所示
- configTICK_RATE_HZ为100,则系统节拍时钟周期为10ms,设置宏configTICK_RATE_HZ为1000,则系统节拍时钟周期为1ms,太频繁的 Tick 中断会导致过频繁的上下文切换,增加系统负担,过于长的上下文切换,会导致任务响应不及时;
在main函数里面引用了 SystemClock_Config(); 然后通过HAL_RCC_ClockConfig,更新了SystemCoreClock值
//代码其他都是删过得
/* Configure the system clock */
SystemClock_Config();
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
}
HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency)
{
/* Update the SystemCoreClock global variable */
SystemCoreClock = HAL_RCC_GetSysClockFreq() >> AHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE)>> RCC_CFGR_HPRE_Pos];
}
2、时间相关函数
FreeRTOS 中的时间延迟函数主要有以下两个作用:
- 为周期性执行的任务提供延迟。
- 对于抢占式调度器,让高优先级任务可以通过时间延迟函数释放 CPU 使用权,从而让低优先级任务可以得到执行。
函数 | 描述 |
---|---|
相对延时函数 | vTaskDelay() |
绝对延时函数 | vTaskDelayUntil() |
GET系统节拍 | xTaskGetTickCount() |
GET系统节拍(中断里面使用) | xTaskGetTickCountFromISR() |
2.1、xTaskGetTickCount
函数原型 | 描述 |
---|---|
TickType_t xTaskGetTickCount( void ) | 获取系统当前运行的时钟节拍数。 |
TickType_t xTaskGetTickCount( void )
{
TickType_t xTicks;
/* Critical section required if running on a 16 bit processor. */
portTICK_TYPE_ENTER_CRITICAL();
{
xTicks = xTickCount;
}
portTICK_TYPE_EXIT_CRITICAL();
return xTicks;
}
2.2、xTaskGetTickCountFromISR
函数原型 | 描述 |
---|---|
TickType_t xTaskGetTickCountFromISR( void ) | 获取系统当前运行的时钟节拍数。(用于在中断服务程序) |
TickType_t xTaskGetTickCountFromISR( void )
{
TickType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
/* RTOS ports that support interrupt nesting have the concept of a maximum
system call (or maximum API call) interrupt priority. Interrupts that are
above the maximum system call priority are kept permanently enabled, even
when the RTOS kernel is in a critical section, but cannot make any calls to
FreeRTOS API functions. If configASSERT() is defined in FreeRTOSConfig.h
then portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
failure if a FreeRTOS API function is called from an interrupt that has been
assigned a priority above the configured maximum system call priority.
Only FreeRTOS functions that end in FromISR can be called from interrupts
that have been assigned a priority at or (logically) below the maximum
system call interrupt priority. FreeRTOS maintains a separate interrupt
safe API to ensure interrupt entry is as fast and as simple as possible.
More information (albeit Cortex-M specific) is provided on the following
link: https://www.freertos.org/RTOS-Cortex-M3-M4.html */
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
uxSavedInterruptStatus = portTICK_TYPE_SET_INTERRUPT_MASK_FROM_ISR();
{
xReturn = xTickCount;
}
portTICK_TYPE_CLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
2.3、vTaskDelay与vTaskDelayUntil
- 相对延时是指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束
- 绝对延时是指调用vTaskDelayUntil()的任务每隔x时间运行一次。也就是任务周期运行。
vTaskDelayUntil使用:
static void vTaskLED(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 200;
/* 获取当前的系统时间 */
xLastWakeTime = xTaskGetTickCount();
while(1)
{
bsp_LedToggle(2);
/* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}