超詳細的FreeRTOS移植全教程——基於srm32

  • 2019 年 10 月 15 日
  • 筆記

準備

在移植之前,我們首先要獲取到FreeRTOS的官方的源碼包。這裡我們提供兩個下載鏈接:

一個是官網:http://www.freertos.org/
另外一個是代碼託管網站:https://sourceforge.net/projects/freertos/files/FreeRTOS/

這裡我們演示如何在代碼託管網站裏面下載。打開網站鏈接之後,我們選擇FreeRTOS的最新版本V9.0.0(2016年),儘管現在FreeRTOS的版本已經更新到V10.0.1了,但是我們還是選擇V9.0.0,因為內核很穩定,並且網上資料很多,因為V10.0.0版本之後是亞馬遜收購了FreeRTOS之後才出來的版本,主要添加了一些雲端組件,我們本書所講的FreeRTOS是實時內核,採用V9.0.0版本足以。

簡單介紹FreeRTOS

FreeRTOS包含Demo例程和內核源碼(比較重要,我們就需要提取該目錄下的大部分文件)。
Source文件夾裏面包含的是FreeRTOS內核的源代碼,我們移植FreeRTOS的時候就需要這部分源代碼;
Demo 文件夾裏面包含了FreeRTOS官方為各個單片機移植好的工程代碼,FreeRTOS為了推廣自己,會給各種半導體廠商的評估板寫好完整的工程程序,這些程序就放在Demo這個目錄下,這部分Demo非常有參考價值。
在這裡插入圖片描述

Source文件夾

這裡我們再重點分析下FreeRTOS/ Source文件夾下的文件,①和③包含的是FreeRTOS的通用的頭文件和C文件,這兩部分的文件試用於各種編譯器和處理器,是通用的。需要移植的頭文件和C文件放在②portblle這個文件夾。
在這裡插入圖片描述

portblle文件夾,是與編譯器相關的文件夾,在不同的編譯器中使用不同的支持文件。①中的KEIL就是我們就是我們使用的編譯器,其實KEIL裏面的內容跟RVDS裏面的內容一樣,所以我們只需要③RVDS文件夾裏面的內容即可,裏面包含了各種處理器相關的文件夾,從文件夾的名字我們就非常熟悉了,我們學習的STM32有M0、M3、M4等各種系列,FreeRTOS是一個軟件,單片機是一個硬件,FreeRTOS要想運行在一個單片機上面,它們就必須關聯在一起。MemMang文件夾下存放的是跟內存管理相關的源文件。
在這裡插入圖片描述

移植過程

提取源碼

  1. 首先在我們的STM32裸機工程模板根目錄下新建一個文件夾,命名為「FreeRTOS」,並且在FreeRTOS文件夾下新建兩個空文件夾,分別命名為「src」與「port」,src文件夾用於保存FreeRTOS中的核心源文件,也就是我們常說的『.c文件』,port文件夾用於保存內存管理以及處理器架構相關代碼,這些代碼FreeRTOS官方已經提供給我們的,直接使用即可,在前面已經說了,FreeRTOS是軟件,我們的開發版是硬件,軟硬件必須有橋樑來連接,這些與處理器架構相關的代碼,可以稱之為RTOS硬件接口層,它們位於FreeRTOS/Source/Portable文件夾下。
  2. 打開FreeRTOS V9.0.0源碼,在「FreeRTOSv9.0.0FreeRTOSSource」目錄下找到所有的『.c文件』,將它們拷貝到我們新建的src文件夾中,

在這裡插入圖片描述

  1. 打開FreeRTOS V9.0.0源碼,在「FreeRTOSv9.0.0FreeRTOSSourceportable」目錄下找到「MemMang」文件夾與「RVDS」文件夾,將它們拷貝到我們新建的port文件夾中

在這裡插入圖片描述

  1. 打開FreeRTOS V9.0.0源碼,在「FreeRTOSv9.0.0 FreeRTOSSource」目錄下找到「include」文件夾,它是我們需要用到FreeRTOS的一些頭文件,將它直接拷貝到我們新建的FreeRTOS文件夾中,完成這一步之後就可以看到我們新建的FreeRTOS文件夾已經有3個文件夾,這3個文件夾就包含FreeRTOS的核心文件,至此,FreeRTOS的源碼就提取完成。

在這裡插入圖片描述

添加到工程

添加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的路徑也要加到開發環境裏面。
在這裡插入圖片描述

修改FreeRTOSConfig.h

FreeRTOSConfig.h是直接從demo文件夾下面拷貝過來的,該頭文件對裁剪整個FreeRTOS所需的功能的宏均做了定義,有些宏定義被使能,有些宏定義被失能,一開始我們只需要配置最簡單的功能即可。要想隨心所欲的配置FreeRTOS的功能,我們必須對這些宏定義的功能有所掌握,下面我們先簡單的介紹下這些宏定義的含義,然後再對這些宏定義進行修改。

#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,%drn",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 */

修改stm32f10x_it.c

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開發」公眾號!