其实这篇文章不侧重移植,因为我们会使用CubeMX配置,那样会自动移植FreeRTOS。
关于FreeRTOS,可以参考官网:FreeRTOS - Quick start guide
当我们在CubeMX中配置了CMSIS_V2后尝试编译的时候会有一个弹窗。
第一个问题就是强烈建议不要使用Systick作为HAL的时基。
默认情况下,我们会选用Systick(滴答定时器)作为HAL的时基,这个时基有什么用呢?我们看HAL库源码就会发现,在配置时钟树函数中会调用滴答计时器的初始化函数,初始化函数中配置的是1ms一个中断,并且在中断服务函数中在累加一个全局变量,这个变量就是在进行中断计数。
__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;
}
/**
* @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 */
}
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
HAL_Delay()函数就是在此基础上实现的,他是一种轮询计数的方式实现delay。
__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)
{
}
}
或者在封装的外设驱动函数中的超时判断,也是使用的Systick。那么为什么加入RTOS后,就不建议我们使用了呢?
RTOS会强制将Systick的中断优先级设置为最低,RTOS还会使用Systick中断服务函数进行任务调度,如果这个时候有一个中断优先级较高(高于Systick中断)的中断服务函数中调用了HAL_Delay(),那么意味着会卡死,因为HAL_Delay()函数中也会等待Systick中断进行计算,但是现在还在执行更高优先级的中断。所以这样是有风险的。。。
但是其实我们是不建议在中断服务函数中进行延迟操作的。但是出于软件健壮的考虑,我们的HAL时基可以使用其他定时器来实现。
我们现在使用TIM4来作为时基。同样的,在时钟树配置后就会初始化TIM4作为时基。
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM4 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 == TIM4) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
然后,我们也会发现SysTick_Handler函数在cmsis_os2.c文件中定义了。
/*
SysTick handler implementation that also clears overflow flag.
*/
void SysTick_Handler (void) {
/* Clear overflow flag */
SysTick->CTRL;
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
/* Call tick handler */
xPortSysTickHandler();
}
}
会在最开始的HAL_Init函数中初始化Systick。最后,还有三点需要关注一下。
一个就是在main函数中,osKernelStart函数下面的注释。
/* Init scheduler */
osKernelInitialize();
/* Call init function for freertos objects (in freertos.c) */
MX_FREERTOS_Init();
/* Start scheduler */
osKernelStart();
/* We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
HAL_Delay(1000);
}
/* USER CODE END 3 */
表示不会执行osKernelStart函数后面的代码,因为控制器被调度器拿走了。
二是在vTaskStartScheduler函数中,默认会创建一个任务,叫空闲任务,他的优先级是配置为最低的0。这个空闲任务是干嘛用的呢?我们看源码注释:
/** THIS IS THE RTOS IDLE TASK - WHICH IS CREATED AUTOMATICALLY WHEN THE
SCHEDULER IS STARTED. **/
/* In case a task that has a secure context deletes itself, in which case
the idle task is responsible for deleting the task's secure context, if
any. */
在某些任务自己删除自己后,空闲任务可以帮忙释放堆栈空间和TCB。
三是在CubeMX中添加FreeRTOS后,会有一个文件夹CMSIS_RTOS_V2,这是MX对FreeRTOS接口的再一次封装,一般是os开头的函数名或者变量名。当然,我们也可以直接使用FreeRTOS的原生接口。