书接上回
示例工程代码库地址如下:
1. 错误理解
博主以 GD32F30x 固件库中的 Template 工程作为示例基础工程,
进行 FreeRTOS 移植,其 main 函数部分实现如下:
int main(void)
{
/* configure systick */
systick_config();
/* print out the clock frequency of system, AHB, APB1 and APB2 */
printf("\r\nCK_SYS is %d", rcu_clock_freq_get(CK_SYS));
printf("\r\nCK_AHB is %d", rcu_clock_freq_get(CK_AHB));
printf("\r\nCK_APB1 is %d", rcu_clock_freq_get(CK_APB1));
printf("\r\nCK_APB2 is %d", rcu_clock_freq_get(CK_APB2));
}
移植过程中,没有去深究 FreeRTOS 内核源码,移植后的 main 函数实现如下:
int main(void)
{
/* configure systick */
systick_config();
delay_init();
uart_printf_init();
OS_Init();
system_init();
OS_StartScheduler();
while(1) { OLOGI("!"); }//OS_StartScheduler 调用失败将返回,添加死循环更容易排查问题
}
想当然的以为 FreeRTOS 的 SysTick 系统滴答时钟配置是调用 systick_config 函数实现的,
由于其内部配置的滴答频率和我配置的 FreeRTOS 系统滴答频率一致,暂时没有出问题。
其定义实现分别如下:
void systick_config(void)
{
/* setup systick timer for 1000Hz interrupts */
if (SysTick_Config(SystemCoreClock / 1000U)){
/* capture error */
while (1){
}
}
/* configure the systick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
void SysTick_Handler(void)
{
xPortSysTickHandler();
delay_decrement();
}
FreeRTOSConfig.h
#define configCPU_CLOCK_HZ ( ( unsigned long ) SystemCoreClock )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
当博主想使用 Systick 来做 us 级延时时,问题便暴露了。
systick_config 修改为 1000000 Hz 中断频率:
void systick_config(void)
{
/* setup systick timer for 1000Hz interrupts */
if (SysTick_Config(SystemCoreClock / 1000000U)){
/* capture error */
while (1){
}
}
/* configure the systick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
gd32f30x_it.c 中 SysTick_Handler 实现修改如下:
void SysTick_Handler(void)
{
static uint32_t s_ticks = 0;
if(1000 == (++s_ticks)){
s_ticks = 0;
xPortSysTickHandler();
}
delay_decrement();
}
这样就天真的认为 xPortSysTickHandler 的调用频率仍旧是 configTICK_RATE_HZ = 1000Hz
根据官网说明,configTICK_RATE_HZ 不能定义太大,否则系统调度将占用大量时间,降低效率。
可现实往往事与愿违,运行发现 FreeRTOS 延时函数的延时时间不正常了,系统调度也出现了问题
其原因调试了很久,才发现原来 SysTick 中断频率仍旧是 1000Hz,
导致 xPortSysTickHandler 的调用频率经上面处理变成了 1Hz
经过这么一折腾,就有了下面的正确理解。
2. 正确理解
FreeRTOS 的调度函数 vTaskStartScheduler 部分实现如下:
void vTaskStartScheduler( void )
{
/* 省略部分代码 */
//创建空闲任务
/* The Idle task is being created using dynamically allocated RAM. */
xReturn = xTaskCreate( prvIdleTask,
configIDLE_TASK_NAME,
configMINIMAL_STACK_SIZE,
( void * ) NULL,
portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
}
/* 省略部分代码 */
//跟 MCU 相关的任务调度启动函数
/* Setting up the timer tick is hardware specific and thus in the
* portable interface. */
if( xPortStartScheduler() != pdFALSE )
{
/* Should not reach here as if the scheduler is running the
* function will not return. */
}
else
{
/* Should only reach here if a task calls xTaskEndScheduler(). */
}
/* 省略部分代码 */
}
xPortStartScheduler 函数,其部分实现如下:
BaseType_t xPortStartScheduler( void )
{
/* 省略部分代码 */
/* 此处设置了 SysTick 中断的优先级为 portNVIC_SYSTICK_PRI
* 而 portNVIC_SYSTICK_PRI = (((uint32_t)configKERNEL_INTERRUPT_PRIORITY) << 24UL)
*/
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
/* Start the timer that generates the tick ISR. Interrupts are disabled
* here already. */
vPortSetupTimerInterrupt();//里面再次对 SysTick 进行了相关配置
/* 省略部分代码 */
/* Start the first task. */
prvStartFirstTask();
/* Should not get here! */
return 0;
}
vPortSetupTimerInterrupt 函数实现如下:
__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. */
//设置 SysTick 的计数值,根据 FreeRTOSConfig.h 中定义的 configTICK_RATE_HZ
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
//启用 SysTick IRQ 和 SysTick 定时器
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}
至此,SysTick 的计数值,中断优先级,中断的使能以及定时器的启用,都进行了配置。
因为 vTaskStartScheduler 函数内部对 SysTick 进行了初始化的配置,
所以一开始的 main 函数中调用 systick_config 其实是不需要的,
就算调用最终也会被 vTaskStartScheduler 中的配置覆盖,
而这也是导致前面错误理解中遇到的问题的根本原因了
3. 总结
FreeRTOS 内核源码还是要多读一读,才能知其根本。
若有解说不当之处,欢迎指正,以免误己误人。