日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

FreeRTOS学习记录

發布時間:2024/1/1 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 FreeRTOS学习记录 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

FreeRTOS學習記錄

  • 前言
  • FreeRTOS學習記錄
  • 在STM32CubeMX中配置FreeRTOS

前言

本人小白,最近學習了FreeRTOS操作系統,打算做一點記錄。
學習的過程中雖然做了點練習,不過都是跟著例程來的,大部分沒什么記錄必要,本文更多的是零零散散地記錄我在學習過程中的一些疑惑與個人見解,可能會有錯誤還請指出。
這只是一個初學者的簡單學習記錄,并沒有全面系統的知識介紹,學習的話還是要看對應的教程啦。

使用到的工具及版本:
FreeRTOS:V9.0.0

STM32CubeMX版本:6.3.0
HAL庫:STM32CubeF4 Firmware Package V1.26.2
MDK-ARM:V5.32.0.0
開發板:野火的霸天虎開發板V2(主控芯片是STM32F407ZGT6)

參考的資料:
野火的《FreeRTOS內核實現與應用開發實戰》
FreeRTOS入門手冊_中文
FreeRTOS官網提供的介紹。

FreeRTOS學習記錄

本文對port部分(移植層面)源碼的探究查看的是FreeRTOSv9.0.0\FreeRTOS\Source\portable\RVDS\ARM_CM4_MPU,這是我所使用的開發工具和硬件決定的。其他工具或者硬件上的實現可能不一樣。

任務會在什么情況下切換?
1、 我們自己編寫的程序所引起的任務切換。比如我們自己調用taskYIELD(),或者我們調用的API函數里面包含了任務切換(這種時候往往是當前任務進入阻塞/掛起,又或者是有更高優先級的任務加入了就緒列表,API函數里面會在需要的時候去調用任務切換)。
2、 tick中斷里面會觸發任務切換。tick由內核的Systick產生,屬于內核的定時器/計數器(其設計的本意可能就是供操作系統使用的,為操作系統提供時間度量,我猜的)。
注意: 搶占式調度上面兩種情況都存在。協作式調度不存在第2種情況的切換(tick中斷的切換),協作式調度中所有的切換都是“顯式的”。
顯而易見協作式調度比搶占式調度有著更高的CPU效率,畢竟不需要每個tick中斷就跑去看看是不是要切換任務。但是它存在著高優先級中斷可能得不到快速響應的風險,這取決于我們開發者的程序設計,得看我們是不是在合適的時機調用了任務切換,如果程序結構復雜的話我們可能得時時刻刻考慮是不是該在什么地方進行一次任務切換,這可太麻煩了,有的時候甚至不現實。
通過FreeRTOSConfig.h里面的configUSE_PREEMPTION(0是協作式調度,非0是搶占式調度)決定調度類型。
tick中斷源碼與任務切換源碼
系統tick依賴于Cotex-M4的SysTick硬件中斷資源,任務切換依賴于PendSV硬件中斷資源(注:下圖的SysTick及之前的中斷是屬于內核的,后面的是芯片廠商的)

來看看SysTick_Handler(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 */ }

如果調度器此時壓根還沒開啟,或者調用了vTaskEndScheduler( void ),那么這里的xPortSysTickHandler()并不會執行,系統節拍則不會增加(當然前提是宏INCLUDE_xTaskGetSchedulerState定義為1)。調度器如果掛起,系統時間依然會增加。
接下來看看xPortSysTickHandler()里面是啥。

void xPortSysTickHandler( void ) {/* The SysTick runs at the lowest interrupt priority, so when this interruptexecutes all interrupts must be unmasked. There is therefore no need tosave and then restore the interrupt mask value as its value is alreadyknown - therefore the slightly faster vPortRaiseBASEPRI() function is usedin place of portSET_INTERRUPT_MASK_FROM_ISR(). */vPortRaiseBASEPRI();{/* Increment the RTOS tick. */if( xTaskIncrementTick() != pdFALSE ){/* A context switch is required. Context switching is performed inthe PendSV interrupt. Pend the PendSV interrupt. */portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;}}vPortClearBASEPRIFromISR(); }

首先調用了vPortRaiseBASEPRI()把中斷屏蔽,它的實現利用了Cotex-M4內核的BASEPRI register(這張圖是從《STM32F4xx-Cortex_-M4內核參考手冊》截的)。

然后調用的xTaskIncrementTick() 則是實現了:
1、系統tick加1(無論調度器是否掛起)
2、如果tick溢出,把延時列表和延時溢出列表交換
3、處理延時列表的事宜
4、如果是搶占式調度,會返回pdTRUE,這樣接下來就會把PendSV中斷標志位置位,進入PendSV中斷服務完成任務切換。如果是協作式調度則返回pdFALSE,不會置位PendSV中斷標志。

接下來看看PendSV_Handler(PendSV中斷函數,用于任務切換)。


有關R14與R15(PC)寄存器的描述(我在網上找的)

R14稱為子程序鏈接寄存器LR(Link Register),當執行子程序調用指令(BL)時,R14可得到R15(程序計數器PC)的備份。在每一種運行模式下,都可用R14保存子程序的返回地址,當用BL或BLX指令調用子程序時,將PC的當前值復制給R14,執行完子程序后,又將R14的值復制回PC,即可完成子程序的調用返回。

寄存器R15用作程序計數器(PC),在ARM狀態下,位[1:0]為0,位[31:2]用于保存PC,在Thumb狀態下,位[0]為0,位[31:1]用于保存PC。由于ARM體系結構采用了多級流水線技術,對于ARM指令集而言,PC總是指向當前指令的下兩條指令的地址,即PC的值為當前指令的地址值加8個字節程序狀態寄存器。

一開始我只找了R14的描述,然后覺得很奇怪,上圖中的程序怎么會跳到R14(PC的內容)所指的地址呢,不應該是PC+1嗎(我只了解一點51內核,cortex-M內核還沒怎么了解,以后有時間得看看它的內容),所以才查了PC寄存器的內容。
閱讀這些匯編代碼需要對照ARM與Thumb指令集說明(除非特殊需要,不然應該沒什么人會去背這個吧)。可以在keil提供的“幫助”窗口查閱。


任務優先級與中斷優先級
中斷優先級用于硬件中斷管理,屬于微處理器內核的內容。cortex-M4的中斷優先級數字越大優先級越低。
任務優先級用于調度器的任務管理,屬于操作系統的內容。FreeRTOS中任務優先級數字越大優先級越高。
vTaskDelay相對延時與vTaskDelayUntil絕對延時
我試著寫了一段代碼來進行說明

void Task_1( void* parameter ) {/* 用于保存上次時間。調用后自動更新 */static portTickType PreviousWakeTime;...PreviousWakeTime = xTaskGetTickCount(); for( ;; ){... /*假設此處可能切換成了更高優先級的任務并去執行,并假設執行該任務要花費5個tick才會把CPU釋放給Task_1*/vTaskDelay(50) or vTaskDelayUntil(&PreviousWakeTime,50) } }

我畫了個非常潦草的圖來說明Task_1的運行情況(這里假設Task_1處于運行狀態的時間非常短,沒有畫出來)

可以看到用相對延時的話任務每次被喚醒的時間間隔可能是55個tick,也可能是50個tick,絕對延時則會讓任務每次進入就緒的時間是確定的。
關于消息隊列、信號量與互斥量
用于進程間通信或同步的手段。信號量與互斥量的創建利用了消息隊列的數據結構。它們可以實現任務間的通信,本質上就是函數利用全局變量來對其他函數產生影響(進行通信)。它們可以讓等待它們的任務進入阻塞狀態。
事件位
相比起消息隊列,它的結構更加簡單。不能傳遞值,只傳遞事件標志。
任務通知
相比起上述的時間和消息隊列,它更更簡單,不需要創建該對象,也就不需要額外花費RAM空間,因為它是在任務控制塊(TCB)里定義了uint32_t類型的變量(用來傳遞值)和uint8_t類型的變量(用來傳遞狀態)來實現的,它的功能函數的實現也更加簡單。(FreeRTOS V8.2.0 才開始支持的)
內存管理方案
heap_1.c:最簡單的,只能申請內存,不能進行內存釋放,申請內存的時間是一個常量。
heap_2.c:可刪除內存。采用了一種最佳匹配算法,但是會產生內存碎片。
heap_3.c:簡單地封裝了標準 C 庫中的 malloc()和 free()函數。此時FreeRTOSConfig.h中的configTOTAL_HEAP_SIZE 宏定義不起作用,使用啟動文件里劃分的堆空間。
heap_4.c:在heap_2.c的基礎上多了合并相鄰內存空間的功能。
heap_5.c:在heap_4.c的基礎上把外擴RAM也納入分配空間。
關于“stdint.readme”文件
在C語言的發展過程中會添加新的標準,所以會有C89、C99、C11標準。不同的編譯器對標準的支持程度不一樣,有的會支持新出的標準而有的則不會。FreeRTOS希望在不同的編譯器下都可以編譯,所以它沒有用C99及C99之后的內容,除了C99引進的stdint.h。這個文件定義了一些整數類型和宏。
如果編譯器不提供stdint.h的話,就把FreeRTOS/Source/include路徑下的stdint.readme改成stdint.h并放到工程指定的頭文件路徑下吧。
關于協程
和任務(task)相似的東西,但是對RAM的要求很低,是為存儲非常小的情況設計的,但是使用起來有更多的限制,現在用的很少,FreeRTOS官方雖然沒有刪除協程,但并不再打算更新這部分內容。
關于互斥量的優先級繼承機制
一個用來減輕優先級反轉危害的措施。當更高優先級的任務(A)要獲取一個被低優先級任務(B)占用的互斥量時,A不可避免的要等待B,如果B的優先級臨時提高到與A一樣,可以避免優先級介于兩者的任務這時候摻一腳(假設有任務C,優先級順序A>C>B,沒有優先級繼承的話C可能會搶占B,導致A又要等B又要等C執行)。如果不想出現優先級反轉,要硬實時之類的,應該是在一開始的設計階段就要考慮。

在STM32CubeMX中配置FreeRTOS

原本我是計劃學習μC/OS的,因為查學習路徑的時候網上都推薦μC/OS,不過在使用STM32CubeMX的時候發現它提供了FreeRTOS中間件并可以快速配置,所以選擇了先學習FreeRTOS。
STM32CubeMX中FreeRTOS的配置界面。

CMSIS是什么
CMSIS是ARM和一眾廠商一起決定的 Cortex-M 處理器系列的通用接口,相當于大家圍在一起制定了標準并提供了各種外設函數、實時操作系統和中間設備等在該標準下的通用接口的實現,使用通用接口的話,應用程序可以更簡單地在不同廠商的Cortex-M處理器之間反復橫跳(移植更方便),V2表示版本2。
版本選擇不了,STM32CubeMX里面好像只能用新版本的FreeRTOS。
點擊選項可以看見參數說明等。

創建任務
任務、隊列等直接在STM32CubeMX里面就可以創建好,不必在編輯器里面寫。
STM32CubeMX默認創建了一個defaultTask,這是一個啥都不做的任務,每次執行就阻塞1個tick。
我創建了兩個任務


關于優先級
單從配置的名字看不出具體的優先級數字,只能看出優先級先后。我在cmsis_os2.h里面找到它的定義。

/// Priority values. typedef enum {osPriorityNone = 0, ///< No priority (not initialized).osPriorityIdle = 1, ///< Reserved for Idle thread.osPriorityLow = 8, ///< Priority: lowosPriorityLow1 = 8+1, ///< Priority: low + 1osPriorityLow2 = 8+2, ///< Priority: low + 2osPriorityLow3 = 8+3, ///< Priority: low + 3osPriorityLow4 = 8+4, ///< Priority: low + 4osPriorityLow5 = 8+5, ///< Priority: low + 5osPriorityLow6 = 8+6, ///< Priority: low + 6osPriorityLow7 = 8+7, ///< Priority: low + 7osPriorityBelowNormal = 16, ///< Priority: below normalosPriorityBelowNormal1 = 16+1, ///< Priority: below normal + 1osPriorityBelowNormal2 = 16+2, ///< Priority: below normal + 2osPriorityBelowNormal3 = 16+3, ///< Priority: below normal + 3osPriorityBelowNormal4 = 16+4, ///< Priority: below normal + 4osPriorityBelowNormal5 = 16+5, ///< Priority: below normal + 5osPriorityBelowNormal6 = 16+6, ///< Priority: below normal + 6osPriorityBelowNormal7 = 16+7, ///< Priority: below normal + 7osPriorityNormal = 24, ///< Priority: normalosPriorityNormal1 = 24+1, ///< Priority: normal + 1osPriorityNormal2 = 24+2, ///< Priority: normal + 2osPriorityNormal3 = 24+3, ///< Priority: normal + 3osPriorityNormal4 = 24+4, ///< Priority: normal + 4osPriorityNormal5 = 24+5, ///< Priority: normal + 5osPriorityNormal6 = 24+6, ///< Priority: normal + 6osPriorityNormal7 = 24+7, ///< Priority: normal + 7osPriorityAboveNormal = 32, ///< Priority: above normalosPriorityAboveNormal1 = 32+1, ///< Priority: above normal + 1osPriorityAboveNormal2 = 32+2, ///< Priority: above normal + 2osPriorityAboveNormal3 = 32+3, ///< Priority: above normal + 3osPriorityAboveNormal4 = 32+4, ///< Priority: above normal + 4osPriorityAboveNormal5 = 32+5, ///< Priority: above normal + 5osPriorityAboveNormal6 = 32+6, ///< Priority: above normal + 6osPriorityAboveNormal7 = 32+7, ///< Priority: above normal + 7osPriorityHigh = 40, ///< Priority: highosPriorityHigh1 = 40+1, ///< Priority: high + 1osPriorityHigh2 = 40+2, ///< Priority: high + 2osPriorityHigh3 = 40+3, ///< Priority: high + 3osPriorityHigh4 = 40+4, ///< Priority: high + 4osPriorityHigh5 = 40+5, ///< Priority: high + 5osPriorityHigh6 = 40+6, ///< Priority: high + 6osPriorityHigh7 = 40+7, ///< Priority: high + 7osPriorityRealtime = 48, ///< Priority: realtimeosPriorityRealtime1 = 48+1, ///< Priority: realtime + 1osPriorityRealtime2 = 48+2, ///< Priority: realtime + 2osPriorityRealtime3 = 48+3, ///< Priority: realtime + 3osPriorityRealtime4 = 48+4, ///< Priority: realtime + 4osPriorityRealtime5 = 48+5, ///< Priority: realtime + 5osPriorityRealtime6 = 48+6, ///< Priority: realtime + 6osPriorityRealtime7 = 48+7, ///< Priority: realtime + 7osPriorityISR = 56, ///< Reserved for ISR deferred thread.osPriorityError = -1, ///< System cannot determine priority or illegal priority.osPriorityReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization. } osPriority_t;

優先級個數超過32個,可見沒有使用硬件優化任務查找(Cortex-M處理器提供了一個計算前導零的指令‘CLZ’可以優化優先級查找)。

關于Timebase Source
Timebase Source如果使用SysTick,那么在生成代碼前會有這樣的警告。

原因是使用HAL庫的一些函數(比如延時)會依賴一個時鐘源提供周期的節拍,這和操作系統的tick(心跳)是一樣的道理,這里的Timebase Source是設定HA庫函數L所依賴的時鐘源,如果把時鐘源設為內核的SysTick,相當于HAL庫和操作系統共用一個“心臟”,如果HAL庫的某個功能把“心臟”給停了那么操作系統會受到影響。
給HAL庫換一個時鐘源就好了,我這里用的TIM1。

順便一提,HAL庫用來記錄節拍數的全局變量叫uwTick,FreeRTOS用來記錄節拍數的全局變量叫xTickCount。
編寫任務函數
創建好任務后產生代碼,接下來寫任務功能就好了,我這里就是簡單的點燈,把freetros.c里的任務函數補充好。

/* USER CODE BEGIN Header_Task_LED_Red */ /*** @brief Function implementing the TASK_LED_RED thread.* @param argument: Not used* @retval None*/ /* USER CODE END Header_Task_LED_Red */ void Task_LED_Red(void *argument) {/* USER CODE BEGIN Task_LED_Red *//* Infinite loop */for(;;){HAL_GPIO_TogglePin(LED_RED_GPIO_Port,LED_RED_Pin);osDelay(3000);}/* USER CODE END Task_LED_Red */ }/* USER CODE BEGIN Header_Task_LED_Green */ /** * @brief Function implementing the TASK_LED_GREEN thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_Task_LED_Green */ void Task_LED_Green(void *argument) {/* USER CODE BEGIN Task_LED_Green *//* Infinite loop */for(;;){HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port,LED_GREEN_Pin);osDelay(1000);}/* USER CODE END Task_LED_Green */ }

現象
燈光顏色周期性地變化。

總結

以上是生活随笔為你收集整理的FreeRTOS学习记录的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。