在移植以前,咱們首先要獲取到FreeRTOS的官方的源碼包。這裏咱們提供兩個下載連接:segmentfault
一個是官網: http://www.freertos.org/
另一個是代碼託管網站: https://sourceforge.net/proje...
這裏咱們演示如何在代碼託管網站裏面下載。打開網站連接以後,咱們選擇FreeRTOS的最新版本V9.0.0(2016年),儘管如今FreeRTOS的版本已經更新到V10.0.1了,可是咱們仍是選擇V9.0.0,由於內核很穩定,而且網上資料不少,由於V10.0.0版本以後是亞馬遜收購了FreeRTOS以後纔出來的版本,主要添加了一些雲端組件,咱們本書所講的FreeRTOS是實時內核,採用V9.0.0版本足以。安全
FreeRTOS包含Demo例程和內核源碼(比較重要,咱們就須要提取該目錄下的大部分文件)。
Source文件夾裏面包含的是FreeRTOS內核的源代碼,咱們移植FreeRTOS的時候就須要這部分源代碼;
Demo 文件夾裏面包含了FreeRTOS官方爲各個單片機移植好的工程代碼,FreeRTOS爲了推廣本身,會給各類半導體廠商的評估板寫好完整的工程程序,這些程序就放在Demo這個目錄下,這部分Demo很是有參考價值。
架構
這裏咱們再重點分析下FreeRTOS/ Source文件夾下的文件,①和③包含的是FreeRTOS的通用的頭文件和C文件,這兩部分的文件試用於各類編譯器和處理器,是通用的。須要移植的頭文件和C文件放在②portblle這個文件夾。
less
portblle文件夾,是與編譯器相關的文件夾,在不一樣的編譯器中使用不一樣的支持文件。①中的KEIL就是咱們就是咱們使用的編譯器,其實KEIL裏面的內容跟RVDS裏面的內容同樣,因此咱們只須要③RVDS文件夾裏面的內容便可,裏面包含了各類處理器相關的文件夾,從文件夾的名字咱們就很是熟悉了,咱們學習的STM32有M0、M三、M4等各類系列,FreeRTOS是一個軟件,單片機是一個硬件,FreeRTOS要想運行在一個單片機上面,它們就必須關聯在一塊兒。MemMang文件夾下存放的是跟內存管理相關的源文件。
函數
添加FreeRTOSConfig.h文件
FreeRTOSConfig.h文件是FreeRTOS的工程配置文件,由於FreeRTOS是能夠裁剪的實時操做內核,應用於不一樣的處理器平臺,用戶能夠經過修改這個FreeRTOS內核的配置頭文件來裁剪FreeRTOS的功能,因此咱們把它拷貝一份放在user這個文件夾下面。
打開FreeRTOSv9.0.0源碼,在「FreeRTOSv9.0.0FreeRTOSDemo」文件夾下面找到「CORTEX_STM32F103_Keil」這個文件夾,雙擊打開,在其根目錄下找到這個「FreeRTOSConfig.h」文件,而後拷貝到咱們工程的user文件夾下便可,等下咱們須要對這個文件進行修改。學習
建立工程分組
接下來咱們在mdk裏面新建FreeRTOS/src和FreeRTOS/port兩個組文件夾,其中FreeRTOS/src用於存放src文件夾的內容,FreeRTOS/port用於存放portMemMang文件夾 與portRVDSARM_CM3文件夾的內容。
而後咱們將工程文件中FreeRTOS的內容添加到工程中去,按照已經新建的分組添加咱們的FreeRTOS工程源碼。
在FreeRTOS/port分組中添加MemMang文件夾中的文件只需選擇其中一個便可,咱們選擇「heap_4.c」,這是FreeRTOS的一個內存管理源碼文件。
添加完成後:網站
添加頭文件路徑
FreeRTOS的源碼已經添加到開發環境的組文件夾下面,編譯的時候須要爲這些源文件指定頭文件的路徑,否則編譯會報錯。FreeRTOS的源碼裏面只有FreeRTOSinclude和FreeRTOSportRVDSARM_CM3這兩個文件夾下面有頭文件,只須要將這兩個頭文件的路徑在開發環境裏面指定便可。同時咱們還將FreeRTOSConfig.h這個頭文件拷貝到了工程根目錄下的user文件夾下,因此user的路徑也要加到開發環境裏面。
ui
FreeRTOSConfig.h是直接從demo文件夾下面拷貝過來的,該頭文件對裁剪整個FreeRTOS所需的功能的宏均作了定義,有些宏定義被使能,有些宏定義被失能,一開始咱們只須要配置最簡單的功能便可。要想爲所欲爲的配置FreeRTOS的功能,咱們必須對這些宏定義的功能有所掌握,下面咱們先簡單的介紹下這些宏定義的含義,而後再對這些宏定義進行修改。spa
#ifndef FREERTOS_CONFIG_H #define FREERTOS_CONFIG_H #include "stm32f10x.h" #include "bsp_usart.h" //針對不一樣的編譯器調用不一樣的stdint.h文件 #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include <stdint.h> extern uint32_t SystemCoreClock; #endif //斷言 #define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int) #define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__) /************************************************************************ * FreeRTOS基礎配置配置選項 *********************************************************************/ /* 置1:RTOS使用搶佔式調度器;置0:RTOS使用協做式調度器(時間片) * * 注:在多任務管理機制上,操做系統能夠分爲搶佔式和協做式兩種。 * 協做式操做系統是任務主動釋放CPU後,切換到下一個任務。 * 任務切換的時機徹底取決於正在運行的任務。 */ #define configUSE_PREEMPTION 1 //1使能時間片調度(默認式使能的) #define configUSE_TIME_SLICING 1 /* 某些運行FreeRTOS的硬件有兩種方法選擇下一個要執行的任務: * 通用方法和特定於硬件的方法(如下簡稱「特殊方法」)。 * * 通用方法: * 1.configUSE_PORT_OPTIMISED_TASK_SELECTION 爲 0 或者硬件不支持這種特殊方法。 * 2.能夠用於全部FreeRTOS支持的硬件 * 3.徹底用C實現,效率略低於特殊方法。 * 4.不強制要求限制最大可用優先級數目 * 特殊方法: * 1.必須將configUSE_PORT_OPTIMISED_TASK_SELECTION設置爲1。 * 2.依賴一個或多個特定架構的彙編指令(通常是相似計算前導零[CLZ]指令)。 * 3.比通用方法更高效 * 4.通常強制限定最大可用優先級數目爲32 * 通常是硬件計算前導零指令,若是所使用的,MCU沒有這些硬件指令的話此宏應該設置爲0! */ #define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 /* 置1:使能低功耗tickless模式;置0:保持系統節拍(tick)中斷一直運行 * 假設開啓低功耗的話可能會致使下載出現問題,由於程序在睡眠中,可用如下辦法解決 * * 下載方法: * 1.將開發版正常鏈接好 * 2.按住復位按鍵,點擊下載瞬間鬆開復位按鍵 * * 1.經過跳線帽將 BOOT 0 接高電平(3.3V) * 2.從新上電,下載 * * 1.使用FlyMcu擦除一下芯片,而後進行下載 * STMISP -> 清除芯片(z) */ #define configUSE_TICKLESS_IDLE 0 /* * 寫入實際的CPU內核時鐘頻率,也就是CPU指令執行頻率,一般稱爲Fclk * Fclk爲供給CPU內核的時鐘信號,咱們所說的cpu主頻爲 XX MHz, * 就是指的這個時鐘信號,相應的,1/Fclk即爲cpu時鐘週期; */ #define configCPU_CLOCK_HZ (SystemCoreClock) //RTOS系統節拍中斷的頻率。即一秒中斷的次數,每次中斷RTOS都會進行任務調度 #define configTICK_RATE_HZ (( TickType_t )1000) //可以使用的最大優先級 #define configMAX_PRIORITIES (32) //空閒任務使用的堆棧大小 #define configMINIMAL_STACK_SIZE ((unsigned short)128) //任務名字字符串長度 #define configMAX_TASK_NAME_LEN (16) //系統節拍計數器變量數據類型,1表示爲16位無符號整形,0表示爲32位無符號整形 #define configUSE_16_BIT_TICKS 0 //空閒任務放棄CPU使用權給其餘同優先級的用戶任務 #define configIDLE_SHOULD_YIELD 1 //啓用隊列 #define configUSE_QUEUE_SETS 1 //開啓任務通知功能,默認開啓 #define configUSE_TASK_NOTIFICATIONS 1 //使用互斥信號量 #define configUSE_MUTEXES 1 //使用遞歸互斥信號量 #define configUSE_RECURSIVE_MUTEXES 1 //爲1時使用計數信號量 #define configUSE_COUNTING_SEMAPHORES 1 /* 設置能夠註冊的信號量和消息隊列個數 */ #define configQUEUE_REGISTRY_SIZE 10 #define configUSE_APPLICATION_TASK_TAG 0 /***************************************************************** FreeRTOS與內存申請有關配置選項 *****************************************************************/ //支持動態內存申請 #define configSUPPORT_DYNAMIC_ALLOCATION 1 //支持靜態內存 #define configSUPPORT_STATIC_ALLOCATION 0 //系統全部總的堆大小 #define configTOTAL_HEAP_SIZE ((size_t)(36*1024)) /*************************************************************** FreeRTOS與鉤子函數有關的配置選項 **************************************************************/ /* 置1:使用空閒鉤子(Idle Hook相似於回調函數);置0:忽略空閒鉤子 * * 空閒任務鉤子是一個函數,這個函數由用戶來實現, * FreeRTOS規定了函數的名字和參數:void vApplicationIdleHook(void ), * 這個函數在每一個空閒任務週期都會被調用 * 對於已經刪除的RTOS任務,空閒任務能夠釋放分配給它們的堆棧內存。 * 所以必須保證空閒任務能夠被CPU執行 * 使用空閒鉤子函數設置CPU進入省電模式是很常見的 * 不能夠調用會引發空閒任務阻塞的API函數 */ #define configUSE_IDLE_HOOK 0 /* 置1:使用時間片鉤子(Tick Hook);置0:忽略時間片鉤子 * * * 時間片鉤子是一個函數,這個函數由用戶來實現, * FreeRTOS規定了函數的名字和參數:void vApplicationTickHook(void ) * 時間片中斷能夠週期性的調用 * 函數必須很是短小,不能大量使用堆棧, * 不能調用以」FromISR" 或 "FROM_ISR」結尾的API函數 */ /*xTaskIncrementTick函數是在xPortSysTickHandler中斷函數中被調用的。所以,vApplicationTickHook()函數執行的時間必須很短才行*/ #define configUSE_TICK_HOOK 0 //使用內存申請失敗鉤子函數 #define configUSE_MALLOC_FAILED_HOOK 0 /* * 大於0時啓用堆棧溢出檢測功能,若是使用此功能 * 用戶必須提供一個棧溢出鉤子函數,若是使用的話 * 此值能夠爲1或者2,由於有兩種棧溢出檢測方法 */ #define configCHECK_FOR_STACK_OVERFLOW 0 /******************************************************************** FreeRTOS與運行時間和任務狀態收集有關的配置選項 **********************************************************************/ //啓用運行時間統計功能 #define configGENERATE_RUN_TIME_STATS 0 //啓用可視化跟蹤調試 #define configUSE_TRACE_FACILITY 0 /* 與宏configUSE_TRACE_FACILITY同時爲1時會編譯下面3個函數 * prvWriteNameToBuffer() * vTaskList(), * vTaskGetRunTimeStats() */ #define configUSE_STATS_FORMATTING_FUNCTIONS 1 /******************************************************************** FreeRTOS與協程有關的配置選項 *********************************************************************/ //啓用協程,啓用協程之後必須添加文件croutine.c #define configUSE_CO_ROUTINES 0 //協程的有效優先級數目 #define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) /*********************************************************************** FreeRTOS與軟件定時器有關的配置選項 **********************************************************************/ //啓用軟件定時器 #define configUSE_TIMERS 1 //軟件定時器優先級 #define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1) //軟件定時器隊列長度 #define configTIMER_QUEUE_LENGTH 10 //軟件定時器任務堆棧大小 #define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2) /************************************************************ FreeRTOS可選函數配置選項 ************************************************************/ #define INCLUDE_xTaskGetSchedulerState 1 #define INCLUDE_vTaskPrioritySet 1 #define INCLUDE_uxTaskPriorityGet 1 #define INCLUDE_vTaskDelete 1 #define INCLUDE_vTaskCleanUpResources 1 #define INCLUDE_vTaskSuspend 1 #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 #define INCLUDE_eTaskGetState 1 #define INCLUDE_xTimerPendFunctionCall 1 //#define INCLUDE_xTaskGetCurrentTaskHandle 1 //#define INCLUDE_uxTaskGetStackHighWaterMark 0 //#define INCLUDE_xTaskGetIdleTaskHandle 0 /****************************************************************** FreeRTOS與中斷有關的配置選項 ******************************************************************/ #ifdef __NVIC_PRIO_BITS #define configPRIO_BITS __NVIC_PRIO_BITS #else #define configPRIO_BITS 4 #endif //中斷最低優先級 #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 //系統可管理的最高中斷優先級 #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /* 240 */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /**************************************************************** FreeRTOS與中斷服務函數有關的配置選項 ****************************************************************/ #define xPortPendSVHandler PendSV_Handler #define vPortSVCHandler SVC_Handler /* 如下爲使用Percepio Tracealyzer須要的東西,不須要時將 configUSE_TRACE_FACILITY 定義爲 0 */ #if ( configUSE_TRACE_FACILITY == 1 ) #include "trcRecorder.h" #define INCLUDE_xTaskGetCurrentTaskHandle 1 // 啓用一個可選函數(該函數被 Trace源碼使用,默認該值爲0 表示不用) #endif #endif /* FREERTOS_CONFIG_H */
SysTick中斷服務函數是一個很是重要的函數,FreeRTOS全部跟時間相關的事情都在裏面處理,SysTick就是FreeRTOS的一個心跳時鐘,驅動着FreeRTOS的運行,就像人的心跳同樣,假如沒有心跳,咱們就至關於「死了」,一樣的,FreeRTOS沒有了心跳,那麼它就會卡死在某個地方,不能進行任務調度,不能運行任何的東西,所以咱們須要實現一個FreeRTOS的心跳時鐘,FreeRTOS幫咱們實現了SysTick的啓動的配置:在port.c文件中已經實現vPortSetupTimerInterrupt()函數,而且FreeRTOS通用的SysTick中斷服務函數也實現了:在port.c文件中已經實現xPortSysTickHandler()函數,因此移植的時候只須要咱們在stm32f10x_it.c文件中實現咱們對應(STM32)平臺上的SysTick_Handler()函數便可。FreeRTOS爲開發者考慮得特別多,PendSV_Handler()與SVC_Handler()這兩個很重要的函數都幫咱們實現了,在在port.c文件中已經實現xPortPendSVHandler()與vPortSVCHandler()函數,防止咱們本身實現不了,那麼在stm32f10x_it.c中就須要咱們註釋掉PendSV_Handler()與SVC_Handler()這兩個函數了。操作系統
//void SVC_Handler(void) //{ //} //void PendSV_Handler(void) //{ //} extern void xPortSysTickHandler(void); //systick中斷服務函數 void SysTick_Handler(void) { #if (INCLUDE_xTaskGetSchedulerState == 1 ) if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { #endif /* INCLUDE_xTaskGetSchedulerState */ xPortSysTickHandler(); #if (INCLUDE_xTaskGetSchedulerState == 1 ) } #endif /* INCLUDE_xTaskGetSchedulerState */ }
這裏,咱們建立一個單任務,任務使用的棧和任務控制塊是在建立任務的時候FreeRTOS動態分配的。
任務必須是一個死循環,不然任務將經過LR返回,若是LR指向了非法的內存就會產生HardFault_Handler,而FreeRTOS指向一個死循環,那麼任務返回以後就在死循環中執行,這樣子的任務是不安全的,因此避免這種狀況,任務通常都是死循環而且無返回值的。
而且每一個任務循環主體中應該有阻塞任務的函數,不然就會餓死比它優先級更低的任務!!!
/* FreeRTOS頭文件 */ #include "FreeRTOS.h" #include "task.h" /* 開發板硬件bsp頭文件 */ #include "bsp_led.h" static void AppTaskCreate(void);/* AppTask任務 */ /* 建立任務句柄 */ static TaskHandle_t AppTask_Handle = NULL; int main(void) { BaseType_t xReturn = pdPASS;/* 定義一個建立信息返回值,默認爲pdPASS */ /* 開發板硬件初始化 */ BSP_Init(); /* 建立AppTaskCreate任務 */ xReturn = xTaskCreate((TaskFunction_t )AppTask, /* 任務入口函數 */ (const char* )"AppTask",/* 任務名字 */ (uint16_t )512, /* 任務棧大小 */ (void* )NULL,/* 任務入口函數參數 */ (UBaseType_t )1, /* 任務的優先級 */ (TaskHandle_t* )&AppTask_Handle);/* 任務控制塊指針 */ /* 啓動任務調度 */ if(pdPASS == xReturn) vTaskStartScheduler(); /* 啓動任務,開啓調度 */ else return -1; while(1); /* 正常不會執行到這裏 */ } static void AppTask(void* parameter) { while (1) { LED1_ON; vTaskDelay(500); /* 延時500個tick */ LED1_OFF; vTaskDelay(500); /* 延時500個tick */ } }
歡迎關注「物聯網IoT開發」公衆號