赞
踩
背景:FreeRTOS使用CMSIS_RTOS_V2封装,工程使用HAL库并STM32CubeMX生成
首先从main函数开始分析,main函数中初始化的顺序为 HAL库初始化——系统时钟初始化——外设初始化——RTOS内核初始化——RTOS线程、互斥量、信号量等初始化——内核启动
HAL库进行了初始化HAL_Init();这里面包含了系统滴答的初始化(如下图)
- int main(void)
- {
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
-
- /* Configure the system clock */
- SystemClock_Config();
-
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- /* 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 3 */
- }
系统滴答使用的是HSI高速内部时钟,由内部RC振荡器生成(STM32H7为64MHz)。系统滴答的初始化主要包括了:
1.中断优先级、中断使能、中断频率(默认为1ms)、
2.在中断模式下启动TIM time Base时间基准生成(这里还不能进入SysTick_Handler中断)
- /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
- if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
- {
- return HAL_ERROR;
- }
系统内核初始化osKernelInitialize();这里面并没有做什么事情,只是对全局变量KernelState进行了状态赋值KernelState = osKernelReady;
- osStatus_t osKernelInitialize (void) {
- osStatus_t stat;
-
- if (IS_IRQ()) {
- stat = osErrorISR;
- }
- else {
- if (KernelState == osKernelInactive) {
- #if defined(USE_TRACE_EVENT_RECORDER)
- EvrFreeRTOSSetup(0U);
- #endif
- #if defined(USE_FreeRTOS_HEAP_5) && (HEAP_5_REGION_SETUP == 1)
- vPortDefineHeapRegions (configHEAP_5_REGIONS);
- #endif
- KernelState = osKernelReady;
- stat = osOK;
- } else {
- stat = osError;
- }
- }
-
- return (stat);
- }
RTOS初始化MX_FREERTOS_Init();里面一般是对一些用户线程、信号量、互斥量等的创建,由用户定义
重点来了:osKernelStart(); 内核启动,这里主要调用的是 vTaskStartScheduler();
- osStatus_t osKernelStart (void) {
- osStatus_t stat;
-
- if (IS_IRQ()) {
- stat = osErrorISR;
- }
- else {
- if (KernelState == osKernelReady) {
- /* Ensure SVC priority is at the reset value */
- SVC_Setup();
- /* Change state to enable IRQ masking check */
- KernelState = osKernelRunning;
- /* Start the kernel scheduler */
- vTaskStartScheduler();
- stat = osOK;
- } else {
- stat = osError;
- }
- }
-
- return (stat);
- }
在vTaskStartScheduler()中主要做的事情是:
1.创建空闲线程
2.创建软件定时器(如果使能)
3.一些全局变量的初始化
4.启动调度器xPortStartScheduler()
- void vTaskStartScheduler( void )
- {
- BaseType_t xReturn;
-
- /* Add the idle task at the lowest priority. */
- #if( configSUPPORT_STATIC_ALLOCATION == 1 )
- {
- StaticTask_t *pxIdleTaskTCBBuffer = NULL;
- StackType_t *pxIdleTaskStackBuffer = NULL;
- uint32_t ulIdleTaskStackSize;
-
- /* The Idle task is created using user provided RAM - obtain the
- address of the RAM then create the idle task. */
- vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
- xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
- configIDLE_TASK_NAME,
- ulIdleTaskStackSize,
- ( void * ) NULL, /*lint !e961. The cast is not redundant for all compilers. */
- portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
- pxIdleTaskStackBuffer,
- pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
-
- if( xIdleTaskHandle != NULL )
- {
- xReturn = pdPASS;
- }
- else
- {
- xReturn = pdFAIL;
- }
- }
- #else
- {
- /* 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. */
- }
- #endif /* configSUPPORT_STATIC_ALLOCATION */
-
- #if ( configUSE_TIMERS == 1 )
- {
- if( xReturn == pdPASS )
- {
- xReturn = xTimerCreateTimerTask();
- }
- else
- {
- mtCOVERAGE_TEST_MARKER();
- }
- }
- #endif /* configUSE_TIMERS */
-
- if( xReturn == pdPASS )
- {
- /* freertos_tasks_c_additions_init() should only be called if the user
- definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
- the only macro called by the function. */
- #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
- {
- freertos_tasks_c_additions_init();
- }
- #endif
-
- /* Interrupts are turned off here, to ensure a tick does not occur
- before or during the call to xPortStartScheduler(). The stacks of
- the created tasks contain a status word with interrupts switched on
- so interrupts will automatically get re-enabled when the first task
- starts to run. */
- portDISABLE_INTERRUPTS();
-
- #if ( configUSE_NEWLIB_REENTRANT == 1 )
- {
- /* Switch Newlib's _impure_ptr variable to point to the _reent
- structure specific to the task that will run first.
- See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
- for additional information. */
- _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
- }
- #endif /* configUSE_NEWLIB_REENTRANT */
-
- xNextTaskUnblockTime = portMAX_DELAY;
- xSchedulerRunning = pdTRUE;
- xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
-
- /* If configGENERATE_RUN_TIME_STATS is defined then the following
- macro must be defined to configure the timer/counter used to generate
- the run time counter time base. NOTE: If configGENERATE_RUN_TIME_STATS
- is set to 0 and the following line fails to build then ensure you do not
- have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your
- FreeRTOSConfig.h file. */
- portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
-
- traceTASK_SWITCHED_IN();
-
- /* 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(). */
- }
- }
- else
- {
- /* This line will only be reached if the kernel could not be started,
- because there was not enough FreeRTOS heap to create the idle task
- or the timer task. */
- configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
- }
-
- /* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
- meaning xIdleTaskHandle is not used anywhere else. */
- ( void ) xIdleTaskHandle;
- }
启动调度器xPortStartScheduler(),这里面主要做的是:
1.一些全局变量的初始化
2.启动Tick计数器vPortSetupTimerInterrupt(),即允许产生Tick ISR(PS:这里的系统滴答配置与前面HAL库初始化时的系统滴答配置没搞明白。。。)
3.启动第一个任务prvStartFirstTask()
- BaseType_t xPortStartScheduler( void )
- {
- /* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0.
- See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
- configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
-
- /* This port can be used on all revisions of the Cortex-M7 core other than
- the r0p1 parts. r0p1 parts should use the port from the
- /source/portable/GCC/ARM_CM7/r0p1 directory. */
- configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
- configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
-
- #if( configASSERT_DEFINED == 1 )
- {
- volatile uint32_t ulOriginalPriority;
- volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
- volatile uint8_t ucMaxPriorityValue;
-
- /* Determine the maximum priority from which ISR safe FreeRTOS API
- functions can be called. ISR safe functions are those that end in
- "FromISR". FreeRTOS maintains separate thread and ISR API functions to
- ensure interrupt entry is as fast and simple as possible.
- Save the interrupt priority value that is about to be clobbered. */
- ulOriginalPriority = *pucFirstUserPriorityRegister;
-
- /* Determine the number of priority bits available. First write to all
- possible bits. */
- *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
-
- /* Read the value back to see how many bits stuck. */
- ucMaxPriorityValue = *pucFirstUserPriorityRegister;
-
- /* The kernel interrupt priority should be set to the lowest
- priority. */
- configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) );
-
- /* Use the same mask on the maximum system call priority. */
- ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
-
- /* Calculate the maximum acceptable priority group value for the number
- of bits read back. */
- ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
- while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
- {
- ulMaxPRIGROUPValue--;
- ucMaxPriorityValue <<= ( uint8_t ) 0x01;
- }
-
- #ifdef __NVIC_PRIO_BITS
- {
- /* Check the CMSIS configuration that defines the number of
- priority bits matches the number of priority bits actually queried
- from the hardware. */
- configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
- }
- #endif
-
- #ifdef configPRIO_BITS
- {
- /* Check the FreeRTOS configuration that defines the number of
- priority bits matches the number of priority bits actually queried
- from the hardware. */
- configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
- }
- #endif
-
- /* Shift the priority group value back to its position within the AIRCR
- register. */
- ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
- ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
-
- /* Restore the clobbered interrupt priority register to its original
- value. */
- *pucFirstUserPriorityRegister = ulOriginalPriority;
- }
- #endif /* conifgASSERT_DEFINED */
-
- /* Make PendSV and SysTick the lowest priority interrupts. */
- portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
- portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
-
- /* Start the timer that generates the tick ISR. Interrupts are disabled
- here already. */
- vPortSetupTimerInterrupt();
-
- /* Initialise the critical nesting count ready for the first task. */
- uxCriticalNesting = 0;
-
- /* Ensure the VFP is enabled - it should be anyway. */
- prvEnableVFP();
-
- /* Lazy save always. */
- *( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;
-
- /* Start the first task. */
- prvStartFirstTask();
-
- /* Should not get here! */
- return 0;
- }
启动第一个任务prvStartFirstTask()用汇编代码编写,主要做的是:
1.复位主堆栈指针MSP的值,表示从此以后MSP指针被FreeRTOS接管
2.使能全局中断
3.使用汇编指令svc 0触发SVC中断,完成启动第一个任务的工作
- __asm void prvStartFirstTask( void )
- {
- PRESERVE8
-
- /* Cortext-M3硬件中,0xE000ED08地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址*/
- ldr r0, =0xE000ED08
- ldr r0, [r0]
- /* 取出向量表中的第一项,向量表第一项存储主堆栈指针MSP的初始值*/
- ldr r0, [r0]
-
- /* 将堆栈地址存入主堆栈指针 */
- msr msp, r0
- /* 使能全局中断*/
- cpsie i
- cpsie f
- dsb
- isb
- /* 调用SVC启动第一个任务 */
- svc 0
- nop
- nop
- }
SVC中断:
1.使用全局指针pxCurrentTCB获得第一个要启动的任务TCB,从而获得任务的当前堆栈栈顶指针pxTopOfStack
2.寄存器R4~R11出栈,将最新的堆栈栈顶指针赋值给线程堆栈指针PSP
3.r14最后四位按位或上0x0d,表示返回时从线程堆栈中做出栈操作、返回后进入线程模式、返回Thumb状态。也就是说,返回后会进入第一个就绪的线程,不会再返回到main函数。
- __asm void vPortSVCHandler( void )
- {
- PRESERVE8
-
- ldr r3, =pxCurrentTCB /* pxCurrentTCB指向处于最高优先级的就绪任务TCB */
- ldr r1, [r3] /* 获取任务TCB地址 */
- ldr r0, [r1] /* 获取任务TCB的第一个成员,即当前堆栈栈顶pxTopOfStack */
- ldmia r0!, {r4-r11} /* 出栈,将寄存器r4~r11出栈 */
- msr psp, r0 /* 最新的栈顶指针赋给线程堆栈指针PSP */
- isb
- mov r0, #0
- msr basepri, r0
- orrr14, #0xd /* 这里0x0d表示:返回后进入线程模式,从进程堆栈中做出栈操作,返回Thumb状态*/
- bx r14
- }
参考:FreeRTOS高级篇3---FreeRTOS调度器启动过程分析_vtaskstartscheduler-CSDN博客
(PS:仅作为学习笔记,如有错误欢迎指正......)
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。