1. 開啟調度器
vTaskStartScheduler | + vPortSetupTimerInterrupt 設置systick,初始化低功耗運行系統補償時間
+----xPortStartScheduler -- + prvEnableVFP 開啟浮點運算單元
+ prvStartFirstTask 開啟第一個任務,SVC異常處理函數
2. 關閉調度器,啥都沒用。
3. 調度器掛起,掛起層數計數變量。
4. 調度器恢復
5. 低功耗模式系統補償函數
開啟調度器
1 void vTaskStartScheduler( void ) 2 { 3 BaseType_t xReturn; 4 5 6 /* Add the idle task at the lowest priority. */ 7 #if( configSUPPORT_STATIC_ALLOCATION == 1 ) 靜態添加空閑任務 8 { 9 StaticTask_t *pxIdleTaskTCBBuffer = NULL; 10 StackType_t *pxIdleTaskStackBuffer = NULL; 11 uint32_t ulIdleTaskStackSize; 12 13 /* The Idle task is created using user provided RAM - obtain the 14 address of the RAM then create the idle task. */ 添加Idle任務 15 vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize ); 16 xIdleTaskHandle = xTaskCreateStatic( prvIdleTask, 17 "IDLE", 18 ulIdleTaskStackSize, 19 ( void * ) NULL, 20 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), 21 pxIdleTaskStackBuffer, 22 pxIdleTaskTCBBuffer ); /*lint !e961 */ 23 24 if( xIdleTaskHandle != NULL ) 25 { 26 xReturn = pdPASS; 27 } 28 else 29 { 30 xReturn = pdFAIL; 31 } 32 } 33 #else 34 { 35 /* The Idle task is being created using dynamically allocated RAM. */ 36 xReturn = xTaskCreate( prvIdleTask, 37 "IDLE", configMINIMAL_STACK_SIZE, 38 ( void * ) NULL, 39 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), 40 &xIdleTaskHandle ); /*lint !e961 */ 41 } 42 #endif /* configSUPPORT_STATIC_ALLOCATION */ 43 44 #if ( configUSE_TIMERS == 1 ) 45 { 46 if( xReturn == pdPASS ) 47 { 48 xReturn = xTimerCreateTimerTask(); 創建定時器服務!后面分析 49 } 50 else 51 { 52 mtCOVERAGE_TEST_MARKER(); 53 } 54 } 55 #endif /* configUSE_TIMERS */ 56 57 if( xReturn == pdPASS ) 58 { 59 /* Interrupts are turned off here, to ensure a tick does not occur 60 before or during the call to xPortStartScheduler(). The stacks of 61 the created tasks contain a status word with interrupts switched on 62 so interrupts will automatically get re-enabled when the first task 63 starts to run. */ 64 portDISABLE_INTERRUPTS(); 65 66 #if ( configUSE_NEWLIB_REENTRANT == 1 ) 【略】 67 { 68 /* Switch Newlib's _impure_ptr variable to point to the _reent 69 structure specific to the task that will run first. */ 70 _impure_ptr = &( pxCurrentTCB->xNewLib_reent ); 71 } 72 #endif /* configUSE_NEWLIB_REENTRANT */ 73 74 xNextTaskUnblockTime = portMAX_DELAY; 三個值的初始化 75 xSchedulerRunning = pdTRUE; 76 xTickCount = ( TickType_t ) 0U; 77 78 /* If configGENERATE_RUN_TIME_STATS is defined then the following 79 macro must be defined to configure the timer/counter used to generate 80 the run time counter time base. */ 81 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); 時間統計功能若打開,用戶實現此宏。 82 83 /* Setting up the timer tick is hardware specific and thus in the 84 portable interface. */ 85 if( xPortStartScheduler() != pdFALSE ) 86 { 87 /* Should not reach here as if the scheduler is running the 88 function will not return. */ as(因為) 89 } 90 else 91 { 92 /* Should only reach here if a task calls xTaskEndScheduler(). */ 除非(if) 93 } 94 } 95 else 96 { 97 /* This line will only be reached if the kernel could not be started, 98 because there was not enough FreeRTOS heap to create the idle task 99 or the timer task. */ 100 configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY ); idle task 或 timer task 沒創建成功 101 } 102 103 /* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0, 104 meaning xIdleTaskHandle is not used anywhere else. */ 105 ( void ) xIdleTaskHandle; 防止編譯器報錯,xTaskGetIdleTaskHandle為0時,編譯器提示idle task not use. 106 } 107 /*-----------------------------------------------------------*/
硬件初始化:systick、FPU單元、PendSV中斷。
1 BaseType_t xPortStartScheduler( void ) 2 { 3 /* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0. 4 See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */ 5 configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY ); 6 7 /* This port can be used on all revisions of the Cortex-M7 core other than 8 the r0p1 parts. r0p1 parts should use the port from the 9 /source/portable/GCC/ARM_CM7/r0p1 directory. */ 10 configASSERT( portCPUID != portCORTEX_M7_r0p1_ID ); 11 configASSERT( portCPUID != portCORTEX_M7_r0p0_ID ); 12 13 #if( configASSERT_DEFINED == 1 ) 【略】 14 { 15 volatile uint32_t ulOriginalPriority; 16 volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER ); 17 volatile uint8_t ucMaxPriorityValue; 18 19 /* Determine the maximum priority from which ISR safe FreeRTOS API 20 functions can be called. ISR safe functions are those that end in 21 "FromISR". FreeRTOS maintains separate thread and ISR API functions to 22 ensure interrupt entry is as fast and simple as possible. 23 24 Save the interrupt priority value that is about to be clobbered. */ 25 ulOriginalPriority = *pucFirstUserPriorityRegister; 26 27 /* Determine the number of priority bits available. First write to all 28 possible bits. */ 29 *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE; 30 31 /* Read the value back to see how many bits stuck. */ 32 ucMaxPriorityValue = *pucFirstUserPriorityRegister; 33 34 /* The kernel interrupt priority should be set to the lowest 35 priority. */ 36 configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) ); 37 38 /* Use the same mask on the maximum system call priority. */ 39 ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue; 40 41 /* Calculate the maximum acceptable priority group value for the number 42 of bits read back. */ 43 ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS; 44 while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE ) 45 { 46 ulMaxPRIGROUPValue--; 47 ucMaxPriorityValue <<= ( uint8_t ) 0x01; 48 } 49 50 /* Shift the priority group value back to its position within the AIRCR 51 register. */ 52 ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT; 53 ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK; 54 55 /* Restore the clobbered interrupt priority register to its original 56 value. */ 57 *pucFirstUserPriorityRegister = ulOriginalPriority; 58 } 59 #endif /* conifgASSERT_DEFINED */ 60 61 /* Make PendSV and SysTick the lowest priority interrupts. */ 62 portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; 63 portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; 64 65 /* Start the timer that generates the tick ISR. Interrupts are disabled 66 here already. */ 67 vPortSetupTimerInterrupt(); 68 69 /* Initialise the critical nesting count ready for the first task. */ 70 uxCriticalNesting = 0; 臨界區嵌套計數器,防止多個臨界區時,其中一個臨界區退出,導致多個臨界區都退出。 71 72 /* Ensure the VFP is enabled - it should be anyway. */ 73 prvEnableVFP(); 使能VFP 74 75 /* Lazy save always. */ 76 *( portFPCCR ) |= portASPEN_AND_LSPEN_BITS; 使S0~S15和FPSCR寄存器,中斷時自動保存和恢復。FPCCR寄存器、惰性壓棧,參見《權威指南》12章 浮點運算 77 78 /* Start the first task. */ 79 prvStartFirstTask(); 80 81 /* Should not get here! */ 82 return 0; 83 }
一、開啟systick
void vPortSetupTimerInterrupt( void ) { /* Calculate the constants required to configure the tick interrupt. */ #if configUSE_TICKLESS_IDLE == 1 { ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ); 系統時鍾頻率/sysTick頻率 xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick; //0xFFF_FFF ul ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ ); } #endif /* configUSE_TICKLESS_IDLE */ /* 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 ); 開啟xx }
/* A fiddle欺騙的 factor to estimate the number of SysTick counts that would have occurred while the SysTick counter is stopped during tickless idle calculations. */ #define portMISSED_COUNTS_FACTOR ( 45UL ) 詳見低功耗運行補償時間
portNVIC_SYSTICK_CLK_BIT 1<<2 portNVIC_SYSTICK_INT_BIT 1<<1 portNVIC_SYSTICK_ENABLE_BIT 1<<0 #define portNVIC_SYSTICK_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000e010 ) )

二、開啟FPU,浮點處理單元
__asm void prvEnableVFP( void ) { PRESERVE8 ldr.w r0, =0xE000ED88 ;R0=0XE000ED88 SCB->CPACR寄存器 Coprocessor Access Contrl ldr r1, [r0] ;從 R0 的地址讀取數據賦給 R1 orr r1, r1, #( 0xf << 20 ) ;R1=R1|(0xf<<20) bit20~bit23,開啟FPU str r1, [r0] ;R1 中的值寫入 R0 保存的地址中 bx r14 nop }
CPACR Register to enable floating point unit feature; available on Cortex -M4 with floating point unit only

三、開啟第一個任務
__asm void prvStartFirstTask( void ) { PRESERVE8 /* Use the NVIC offset register to locate the stack. */ ldr r0, =0xE000ED08 ldr r0, [r0] R0地址處的內容,賦值給R0 ldr r0, [r0] 獲取MSP初始值 /* Set the msp back to the start of the stack. */ msr msp, r0 復位MSP /* Globally enable interrupts. */ cpsie i 使能中斷,清除PRI MASK cpsie f 使能中斷,清除FAULT MASK dsb 數據同步屏障? isb 指令同步屏障? /* Call SVC to start the first task. */ svc 0 呼叫SVC異常 nop nop }
向量表重定位,向量表偏移寄存器(VTOR)。 地址就是 0XE000ED08,通過這個寄存器可以重新定義向量表。
向量表的起始地址保存的就是主棧指針MSP 的初始值。
在 FreeRTOS 中僅僅使用 SVC 異常來啟動第一個任務,后面的程序中就再也用不到 SVC 了。
#define xPortPendSVHandler PendSV_Handler
__asm void vPortSVCHandler( void ) { PRESERVE8 /* Get the location of the current TCB. */ ldr r3, =pxCurrentTCB 獲取pxCurrentTCB的存儲地址 ldr r1, [r3] 獲取當前任務的TCB存儲地址 ldr r0, [r1] TCB第一個字段就是任務堆棧的棧頂指針pxTopOfStack,獲取它指向的地址 /* Pop the core registers. */ ldmia r0!, {r4-r11, r14} 多加載/存儲指令(R0~R3,R12,PC,xPSR是自動壓棧出棧的) msr psp, r0 更新psp任務堆棧指針 isb mov r0, #0 msr basepri, r0 BASEPRI設為0,開中斷 bx r14 中斷返回,執行PC指向的任務函數. }
1. pxCurrentTCB 是一個指向 TCB_t 的指針,這個指針永遠指向正在運行的任務。
2. 前三步的目的就是獲取要切換到的這個任務的任務棧頂指針,
因為任務所對應的寄存器值,也就是現場都保存在任務的任務堆棧中,
所以需要獲取棧頂指針來恢復這些寄存器值!
3. R14 = LR寄存器,恢復時,它的內容為EXC_RETURN = 0xffff_fffd,是初始化任務堆棧時的值。

========================================================
有vTaskStartScheduler 就有 vTaskEndScheduler
/** * task. h * void vTaskEndScheduler( void ); * * NOTE: At the time of writing only the x86 real mode port, which runs on a PC * in place of DOS, implements this function. * * Stops the real time kernel tick. All created tasks will be automatically * deleted and multitasking (either preemptive or cooperative) will * stop. Execution then resumes from the point where vTaskStartScheduler () * was called, as if vTaskStartScheduler () had just returned. * * vTaskEndScheduler () requires an exit function to be defined within the * portable layer (see vPortEndScheduler () in port. c for the PC port). This * performs hardware specific operations such as stopping the kernel tick. * * vTaskEndScheduler () will cause all of the resources allocated by the * kernel to be freed - but will not free resources allocated by application * tasks. * */
void vTaskEndScheduler( void )
{
/* Stop the scheduler interrupts and call the portable scheduler end
routine so the original ISRs can be restored if necessary. The port
layer must ensure interrupts enable bit is left in the correct state. */
portDISABLE_INTERRUPTS(); 關中斷
xSchedulerRunning = pdFALSE;
vPortEndScheduler(); 關調度器
}
void vPortEndScheduler( void )
{
/* Not implemented in ports where there is nothing to return to.
Artificially force an assert. */
configASSERT( uxCriticalNesting == 1000UL ); 啥都沒做~只有x86內核里才使用這個東東。
}
========================================================
========================================================
vTaskSuspendAll() 掛起任務調度器, 調用此函數不需要關閉可屏蔽中斷。
void vTaskSuspendAll( void ) { /* A critical section is not required as the variable is of type BaseType_t. Please read Richard Barry's reply in the following link to a post in the FreeRTOS support forum before reporting this as a bug! - http://goo.gl/wu4acr */ ++uxSchedulerSuspended; }
uxSchedulerSuspended 是掛起嵌套計數器, 調度器掛起是支持嵌套的。
使用函數 xTaskResumeAll()可以恢復任務調度器,調用了幾次 vTaskSuspendAll()掛起調度器,同樣的也得調用幾次 xTaskResumeAll()才會最終恢復任務調度器。
假設現在有這樣一種情況, 任務 1 的優先級為 10,此時任務 1 由於等待隊列TestQueue 而處於阻塞態 。
但是有段其他的 代碼調用函數vTaskSuspendAll()掛起了任務調度器,
在還沒有調用 xTaskResumeAll()恢復任務調度器之前,有個在外部中斷發生了,在中斷服務程序里面調用函數 xQueueSendFromISR()向任務 1 發送了隊列 TestQueue。
如果任務調度器沒有阻塞的話函數 xQueueSendFromISR()會使任務 1 進入就緒態,也就是將任務 1 添加到優先級 10 對應的就緒列表 pxReadyTasksLists[10]中,這樣當任務切換的時候任務 1 就會運行。
但是現在任務調度器由於函數 vTaskSuspendAll()而掛起,這個時候任務 1 就不是添加到任務就緒列表 pxReadyTasksLists[10]中了,而是添加到另一個叫做xPendingReadyList 的列表中,
xPendingReadyList 是個全局變量,在文件 tasks.c 中有定義。
當調用函數 xTaskResumeAll()恢復調度器的時候就會將掛到列表 xPendingReadyList 中的任務重新移動到它們所對應的就緒列表 pxReadyTasksLists 中。
任務調度器恢復
1 BaseType_t xTaskResumeAll( void ) 2 { 3 TCB_t *pxTCB = NULL; 4 BaseType_t xAlreadyYielded = pdFALSE; 5 6 /* If uxSchedulerSuspended is zero then this function does not match a 7 previous call to vTaskSuspendAll(). */ 8 configASSERT( uxSchedulerSuspended ); 9 10 /* It is possible that an ISR caused a task to be removed from an event 11 list while the scheduler was suspended. If this was the case then the 12 removed task will have been added to the xPendingReadyList. Once the 13 scheduler has been resumed it is safe to move all the pending ready 14 tasks from this list into their appropriate ready list. */ 15 taskENTER_CRITICAL(); 16 { 17 --uxSchedulerSuspended; 嵌套層數變量自減一 18 19 if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) 等於0,要恢復調度器了 20 { 21 if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U ) 22 { 23 /* Move any readied tasks from the pending list into the 24 appropriate ready list. */ 從PendingReadList里面,移動到ReadyList. 25 while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE ) 26 { 27 pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); 28 ( void ) uxListRemove( &( pxTCB->xEventListItem ) ); 29 ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); 30 prvAddTaskToReadyList( pxTCB ); 31 32 /* If the moved task has a priority higher than the current 33 task then a yield must be performed. */ 34 if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) 新出來的優先級高 35 { 36 xYieldPending = pdTRUE; 切換任務 37 } 38 else 39 { 40 mtCOVERAGE_TEST_MARKER(); 41 } 42 } 43 44 if( pxTCB != NULL ) 45 { 46 /* A task was unblocked while the scheduler was suspended, 47 which may have prevented the next unblock time from being 48 re-calculated, in which case re-calculate it now. Mainly 49 important for low power tickless implementations, where 50 this can prevent an unnecessary exit from low power 51 state. */ 52 prvResetNextTaskUnblockTime(); 重新計算變量xNextTaskUnblockTime,詳見 “刪除任務 ##5” 53 } 54 55 /* If any ticks occurred while the scheduler was suspended then 56 they should be processed now. This ensures the tick count does 57 not slip逃, and that any delayed tasks are resumed at the correct 58 time. */ 59 { 任務調度器掛起時,tick中斷不會更新TickCnt,而是更新PendTick【見時間管理章節的TaskIncrementTick函數】
在恢復任務調度時,調用PendTick次TaskIncrementTick()函數,來恢復TickCnt!!
同時,如果有延時任務取消阻塞的動作,也會在TaskIncrementTick函數中進行。 60 UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */ 61 62 if( uxPendedCounts > ( UBaseType_t ) 0U ) 63 { 64 do 65 { 66 if( xTaskIncrementTick() != pdFALSE ) 67 { 68 xYieldPending = pdTRUE; 置位xYieldPending 69 } 70 else 71 { 72 mtCOVERAGE_TEST_MARKER(); 73 } 74 --uxPendedCounts; 75 } while( uxPendedCounts > ( UBaseType_t ) 0U ); 76 77 uxPendedTicks = 0; 78 } 79 else 80 { 81 mtCOVERAGE_TEST_MARKER(); 82 } 83 } 84 85 if( xYieldPending != pdFALSE ) 如果置位了 86 { 87 #if( configUSE_PREEMPTION != 0 ) 88 { 89 xAlreadyYielded = pdTRUE; 90 } 91 #endif 92 taskYIELD_IF_USING_PREEMPTION(); 置位PendSV 93 } 94 else 95 { 96 mtCOVERAGE_TEST_MARKER(); 97 } 98 } 99 } 100 else 101 { 102 mtCOVERAGE_TEST_MARKER(); 103 } 104 } 105 taskEXIT_CRITICAL(); 106 107 return xAlreadyYielded; 108 }
>1 把PendingReadyList里面的任務,恢復到ReadyList當中。(可能引起任務調度)
>2 重新計算NextTaskUnblockTime
>3 彌補在掛起調度器時,漏了的系統滴答時鍾節拍。(可能引起任務調度)
========================================================
========================================================
vTaskStepTick() 此函數在使用 FreeRTOS 的低功耗 tickless 模式的時候會用到,即宏 configUSE_TICKLESS_IDLE 為 1。
當使能低功耗 tickless 模式以后在執行空閑任務的時候系統時鍾節拍中斷就會停止運行,系統時鍾中斷停止運行的這段時間必須得補上,
這個工作就是由函數 vTaskStepTick()來完成的。 void vTaskStepTick( const TickType_t xTicksToJump ) { configASSERT( ( xTickCount + xTicksToJump ) <= xNextTaskUnblockTime ); xTickCount += xTicksToJump; traceINCREASE_TICK_COUNT( xTicksToJump ); } 函數參數 xTicksToJump 是要加上的時間值,系統節拍計數器 xTickCount 加上這個時間值得到新的系統時間。
關於 xTicksToJump 這個時間值的確定。詳見“系統低功耗睡眠模式”
啊
