STM32CubeMX學(xué)習(xí)筆記(31)——FreeRTOS實(shí)時(shí)操作系統(tǒng)使用(互斥量)

一剧辐、FreeRTOS簡介

FreeRTOS 是一個(gè)可裁剪夹攒、可剝奪型的多任務(wù)內(nèi)核蜘醋,而且沒有任務(wù)數(shù)限制。FreeRTOS 提供了實(shí)時(shí)操作系統(tǒng)所需的所有功能咏尝,包括資源管理压语、同步、任務(wù)通信等编检。

FreeRTOS 是用 C 和匯編來寫的无蜂,其中絕大部分都是用 C 語言編寫的,只有極少數(shù)的與處理器密切相關(guān)的部分代碼才是用匯編寫的蒙谓,F(xiàn)reeRTOS 結(jié)構(gòu)簡潔,可讀性很強(qiáng)训桶!最主要的是非常適合初次接觸嵌入式實(shí)時(shí)操作系統(tǒng)學(xué)生累驮、嵌入式系統(tǒng)開發(fā)人員和愛好者學(xué)習(xí)。

最新版本 V9.0.0(2016年)舵揭,盡管現(xiàn)在 FreeRTOS 的版本已經(jīng)更新到 V10.4.1 了谤专,但是我們還是選擇 V9.0.0,因?yàn)閮?nèi)核很穩(wěn)定午绳,并且網(wǎng)上資料很多置侍,因?yàn)?V10.0.0 版本之后是亞馬遜收購了FreeRTOS之后才出來的版本,主要添加了一些云端組件,一般采用 V9.0.0 版本足以蜡坊。

二杠输、新建工程

1. 打開 STM32CubeMX 軟件,點(diǎn)擊“新建工程”

2. 選擇 MCU 和封裝

3. 配置時(shí)鐘
RCC 設(shè)置秕衙,選擇 HSE(外部高速時(shí)鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)


選擇 Clock Configuration蠢甲,配置系統(tǒng)時(shí)鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后,輸入回車据忘,軟件會(huì)自動(dòng)修改所有配置

4. 配置調(diào)試模式
非常重要的一步鹦牛,否則會(huì)造成第一次燒錄程序后續(xù)無法識(shí)別調(diào)試器
SYS 設(shè)置,選擇 Debug 為 Serial Wire

三勇吊、SYS Timebase Source

System Core 中選擇 SYS 曼追,對(duì) Timebase Source 進(jìn)行設(shè)置,選擇 TIM1 作為HAL庫的時(shí)基(除了 SysTick 外都可以)汉规。

在基于STM32 HAL的項(xiàng)目中礼殊,一般需要維護(hù)的 “時(shí)基” 主要有2個(gè):

  1. HAL的時(shí)基,SYS Timebase Source
  2. OS的時(shí)基(僅在使用OS的情況下才考慮)

而這些 “時(shí)基” 該去如何維護(hù)鲫忍,主要分為兩種情況考慮:

  • 裸機(jī)運(yùn)行
    可以通過 SysTick(滴答定時(shí)器)或 (TIMx)定時(shí)器 的方式來維護(hù) SYS Timebase Source膏燕,也就是HAL庫中的 uwTick,這是HAL庫中維護(hù)的一個(gè)全局變量悟民。在裸機(jī)運(yùn)行的情況下坝辫,我們一般選擇默認(rèn)的 SysTick(滴答定時(shí)器) 方式即可,也就是直接放在 SysTick_Handler() 中斷服務(wù)函數(shù)中來維護(hù)射亏。

  • 帶OS運(yùn)行
    前面提到的 SYS Timebase Source 是STM32的HAL庫中的新增部分近忙,主要用于實(shí)現(xiàn) HAL_Delay() 以及作為各種 timeout 的時(shí)鐘基準(zhǔn)。

    在使用了OS(操作系統(tǒng))之后智润,OS的運(yùn)行也需要一個(gè)時(shí)鐘基準(zhǔn)(簡稱“時(shí)基”)及舍,來對(duì)任務(wù)和時(shí)間等進(jìn)行管理。而OS的這個(gè) 時(shí)基 一般也都是通過 SysTick(滴答定時(shí)器) 來維護(hù)的窟绷,這時(shí)就需要考慮 “HAL的時(shí)基” 和 “OS的時(shí)基” 是否要共用 SysTick(滴答定時(shí)器) 了锯玛。

    如果共用SysTick,當(dāng)我們?cè)贑ubeMX中選擇啟用FreeRTOS之后兼蜈,在生成代碼時(shí)攘残,CubeMX一定會(huì)報(bào)如下提示:


強(qiáng)烈建議用戶在使用FreeRTOS的時(shí)候,不要使用 SysTick(滴答定時(shí)器)作為 “HAL的時(shí)基”为狸,因?yàn)镕reeRTOS要用歼郭,最好是要換一個(gè)!7簟病曾!如果共用牍蜂,潛在一定風(fēng)險(xiǎn)。

四泰涂、FreeRTOS

4.1 參數(shù)配置

Middleware 中選擇 FREERTOS 設(shè)置鲫竞,并選擇 CMSIS_V1 接口版本


CMSIS是一種接口標(biāo)準(zhǔn),目的是屏蔽軟硬件差異以提高軟件的兼容性负敏。RTOS v1使得軟件能夠在不同的實(shí)時(shí)操作系統(tǒng)下運(yùn)行(屏蔽不同RTOS提供的API的差別)贡茅,而RTOS v2則是拓展了RTOS v1,兼容更多的CPU架構(gòu)和實(shí)時(shí)操作系統(tǒng)其做。因此我們?cè)谑褂脮r(shí)可以根據(jù)實(shí)際情況選擇顶考,如果學(xué)習(xí)過程中使用STM32F1、F4等單片機(jī)時(shí)沒必要選擇RTOS v2妖泄,更高的兼容性背后時(shí)更加冗余的代碼驹沿,理解起來比較困難。

Config parameters 進(jìn)行具體參數(shù)配置蹈胡。

Kernel settings:

  • USE_PREEMPTION: Enabled:RTOS使用搶占式調(diào)度器渊季;Disabled:RTOS使用協(xié)作式調(diào)度器(時(shí)間片)。
  • TICK_RATE_HZ: 值設(shè)置為1000罚渐,即周期就是1ms却汉。RTOS系統(tǒng)節(jié)拍中斷的頻率,單位為HZ荷并。
  • MAX_PRIORITIES: 可使用的最大優(yōu)先級(jí)數(shù)量合砂。設(shè)置好以后任務(wù)就可以使用從0到(MAX_PRIORITIES - 1)的優(yōu)先級(jí),其中0位最低優(yōu)先級(jí)源织,(MAX_PRIORITIES - 1)為最高優(yōu)先級(jí)翩伪。
  • MINIMAL_STACK_SIZE: 設(shè)置空閑任務(wù)的最小任務(wù)堆棧大小,以字為單位谈息,而不是字節(jié)缘屹。如該值設(shè)置為128 Words,那么真正的堆棧大小就是 128*4 = 512 Byte侠仇。
  • MAX_TASK_NAME_LEN: 設(shè)置任務(wù)名最大長度轻姿。
  • IDLE_SHOULD_YIELD: Enabled 空閑任務(wù)放棄CPU使用權(quán)給其他同優(yōu)先級(jí)的用戶任務(wù)。
  • USE_MUTEXES: 為1時(shí)使用互斥信號(hào)量逻炊,相關(guān)的API函數(shù)會(huì)被編譯互亮。
  • USE_RECURSIVE_MUTEXES: 為1時(shí)使用遞歸互斥信號(hào)量,相關(guān)的API函數(shù)會(huì)被編譯嗅骄。
  • USE_COUNTING_SEMAPHORES: 為1時(shí)啟用計(jì)數(shù)型信號(hào)量, 相關(guān)的API函數(shù)會(huì)被編譯饼疙。
  • QUEUE_REGISTRY_SIZE: 設(shè)置可以注冊(cè)的隊(duì)列和信號(hào)量的最大數(shù)量溺森,在使用內(nèi)核調(diào)試器查看信號(hào)量和隊(duì)列的時(shí)候需要設(shè)置此宏慕爬,而且要先將消息隊(duì)列和信號(hào)量進(jìn)行注冊(cè),只有注冊(cè)了的隊(duì)列和信號(hào)量才會(huì)在內(nèi)核調(diào)試器中看到屏积,如果不使用內(nèi)核調(diào)試器的話次宏設(shè)置為0即可医窿。
  • USE_APPLICATION_TASK_TAG: 為1時(shí)可以使用vTaskSetApplicationTaskTag函數(shù)。
  • ENABLE_BACKWARD_COMPATIBILITY: 為1時(shí)可以使V8.0.0之前的FreeRTOS用戶代碼直接升級(jí)到V8.0.0之后炊林,而不需要做任何修改姥卢。
  • USE_PORT_OPTIMISED_TASK_SELECTION: FreeRTOS有兩種方法來選擇下一個(gè)要運(yùn)行的任務(wù),一個(gè)是通用的方法渣聚,另外一個(gè)是特殊的方法独榴,也就是硬件方法,使用MCU自帶的硬件指令來實(shí)現(xiàn)奕枝。STM32有計(jì)算前導(dǎo)零指令嗎棺榔,所以這里強(qiáng)制置1。
  • USE_TICKLESS_IDLE: 置1:使能低功耗tickless模式隘道;置0:保持系統(tǒng)節(jié)拍(tick)中斷一直運(yùn)行症歇。假設(shè)開啟低功耗的話可能會(huì)導(dǎo)致下載出現(xiàn)問題,因?yàn)槌绦蛟谒咧刑饭#捎肐SP下載辦法解決忘晤。
  • USE_TASK_NOTIFICATIONS: 為1時(shí)使用任務(wù)通知功能,相關(guān)的API函數(shù)會(huì)被編譯激捏。開啟了此功能设塔,每個(gè)任務(wù)會(huì)多消耗8個(gè)字節(jié)。
  • RECORD_STACK_HIGH_ADDRESS: 為1時(shí)棧開始地址會(huì)被保存到每個(gè)任務(wù)的TCB中(假如棧是向下生長的)缩幸。

Memory management settings:

  • Memory Allocation: Dynamic/Static 支持動(dòng)態(tài)/靜態(tài)內(nèi)存申請(qǐng)
  • TOTAL_HEAP_SIZE: 設(shè)置堆大小壹置,如果使用了動(dòng)態(tài)內(nèi)存管理,F(xiàn)reeRTOS在創(chuàng)建 task, queue, mutex, software timer or semaphore的時(shí)候就會(huì)使用heap_x.c(x為1~5)中的內(nèi)存申請(qǐng)函數(shù)來申請(qǐng)內(nèi)存表谊。這些內(nèi)存就是從堆ucHeap[configTOTAL_HEAP_SIZE]中申請(qǐng)的钞护。
  • Memory Management scheme: 內(nèi)存管理策略 heap_4

Hook function related definitions:

  • USE_IDLE_HOOK: 置1:使用空閑鉤子(Idle Hook類似于回調(diào)函數(shù))爆办;置0:忽略空閑鉤子难咕。
  • USE_TICK_HOOK: 置1:使用時(shí)間片鉤子(Tick Hook);置0:忽略時(shí)間片鉤子距辆。
  • USE_MALLOC_FAILED_HOOK: 使用內(nèi)存申請(qǐng)失敗鉤子函數(shù)余佃。
  • CHECK_FOR_STACK_OVERFLOW: 大于0時(shí)啟用堆棧溢出檢測功能,如果使用此功能用戶必須提供一個(gè)棧溢出鉤子函數(shù)跨算,如果使用的話此值可以為1或者2爆土,因?yàn)橛袃煞N棧溢出檢測方法。

Run time and task stats gathering related definitions:

  • GENERATE_RUN_TIME_STATS: 啟用運(yùn)行時(shí)間統(tǒng)計(jì)功能诸蚕。
  • USE_TRACE_FACILITY: 啟用可視化跟蹤調(diào)試步势。
  • USE_STATS_FORMATTING_FUNCTIONS: 與宏configUSE_TRACE_FACILITY同時(shí)為1時(shí)會(huì)編譯下面3個(gè)函數(shù)prvWriteNameToBuffer()氧猬、vTaskList()、vTaskGetRunTimeStats()坏瘩。

Co-routine related definitions:

  • USE_CO_ROUTINES: 啟用協(xié)程盅抚。
  • MAX_CO_ROUTINE_PRIORITIES: 協(xié)程的有效優(yōu)先級(jí)數(shù)目。

Software timer definitions:

  • USE_TIMERS: 啟用軟件定時(shí)器倔矾。

Interrupt nesting behaviour configuration:

  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 中斷最低優(yōu)先級(jí)妄均。
  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 系統(tǒng)可管理的最高中斷優(yōu)先級(jí)。

4.2 創(chuàng)建互斥量Mutex

Mutexes 進(jìn)行配置哪自。

  • Mutex Name: 互斥量名稱
  • Allocation: 分配方式:Dynamic 動(dòng)態(tài)內(nèi)存創(chuàng)建
  • Conrol Block Name: 控制塊名稱

4.3 創(chuàng)建任務(wù)Task

我們創(chuàng)建三個(gè)任務(wù)丰包,一個(gè)高優(yōu)先級(jí)任務(wù),一個(gè)中優(yōu)先級(jí)任務(wù)提陶,一個(gè)低優(yōu)先級(jí)任務(wù)烫沙。



  • Task Name: 任務(wù)名稱
  • Priority: 優(yōu)先級(jí),在 FreeRTOS 中隙笆,數(shù)值越大優(yōu)先級(jí)越高锌蓄,0 代表最低優(yōu)先級(jí)
  • Stack Size (Words): 堆棧大小,單位為字撑柔,在32位處理器(STM32)瘸爽,一個(gè)字等于4字節(jié),如果傳入512那么任務(wù)大小為512*4字節(jié)
  • Entry Function: 入口函數(shù)
  • Code Generation Option: 代碼生成選項(xiàng)
  • Parameter: 任務(wù)入口函數(shù)形參铅忿,不用的時(shí)候配置為0或NULL即可
  • Allocation: 分配方式:Dynamic 動(dòng)態(tài)內(nèi)存創(chuàng)建
  • Buffer Name: 緩沖區(qū)名稱
  • Conrol Block Name: 控制塊名稱

五剪决、UART串口打印

查看 STM32CubeMX學(xué)習(xí)筆記(6)——USART串口使用

六、生成代碼

輸入項(xiàng)目名和項(xiàng)目路徑


選擇應(yīng)用的 IDE 開發(fā)環(huán)境 MDK-ARM V5

每個(gè)外設(shè)生成獨(dú)立的 ’.c/.h’ 文件
不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對(duì)應(yīng)的外設(shè)文件檀训。 如 GPIO 初始化代碼生成在 gpio.c 中柑潦。

點(diǎn)擊 GENERATE CODE 生成代碼

七、互斥量

7.1 基本概念

互斥量又稱互斥信號(hào)量(本質(zhì)是信號(hào)量)峻凫,是一種特殊的二值信號(hào)量渗鬼,它和信號(hào)量不同的是,它支持互斥量所有權(quán)荧琼、遞歸訪問以及防止優(yōu)先級(jí)翻轉(zhuǎn)的特性譬胎,用于實(shí)現(xiàn)對(duì)臨界資源的獨(dú)占式處理。任意時(shí)刻互斥量的狀態(tài)只有兩種命锄,開鎖或閉鎖堰乔。當(dāng)互斥量被任務(wù)持有時(shí),該互斥量處于閉鎖狀態(tài)脐恩,這個(gè)任務(wù)獲得互斥量的所有權(quán)镐侯。當(dāng)該任務(wù)釋放這個(gè)互斥量時(shí),該互斥量處于開鎖狀態(tài)驶冒,任務(wù)失去該互斥量的所有權(quán)苟翻。當(dāng)一個(gè)任務(wù)持有互斥量時(shí)搭伤,其他任務(wù)將不能再對(duì)該互斥量進(jìn)行開鎖或持有。持有該互斥量的任務(wù)也能夠再次獲得這個(gè)鎖而不被掛起袜瞬,這就是遞歸訪問,也就是遞歸互斥量的特性身堡,這個(gè)特性與一般的信號(hào)量有很大的不同邓尤,在信號(hào)量中,由于已經(jīng)不存在可用的信號(hào)量贴谎,任務(wù)遞歸獲取信號(hào)量時(shí)會(huì)發(fā)生主動(dòng)掛起任務(wù)最終形成死鎖汞扎。

如果想要用于實(shí)現(xiàn)同步(任務(wù)之間或者任務(wù)與中斷之間),二值信號(hào)量或許是更好的選擇擅这,雖然互斥量也可以用于任務(wù)與任務(wù)澈魄、任務(wù)與中斷的同步,但是互斥量更多的是用于保護(hù)資源的互鎖仲翎。

用于互鎖的互斥量可以充當(dāng)保護(hù)資源的令牌痹扇,當(dāng)一個(gè)任務(wù)希望訪問某個(gè)資源時(shí),它必須先獲取令牌溯香。當(dāng)任務(wù)使用完資源后鲫构,必須還回令牌,以便其它任務(wù)可以訪問該資源玫坛。是不是很熟悉结笨,在我們的二值信號(hào)量里面也是一樣的,用于保護(hù)臨界資源湿镀,保證多任務(wù)的訪問井然有序炕吸。當(dāng)任務(wù)獲取到信號(hào)量的時(shí)候才能開始使用被保護(hù)的資源,使用完就釋放信號(hào)量勉痴,下一個(gè)任務(wù)才能獲取到信號(hào)量從而可用使用被保護(hù)的資源喳钟。但是信號(hào)量會(huì)導(dǎo)致的另一個(gè)潛在問題,那就是任務(wù)優(yōu)先級(jí)翻轉(zhuǎn)煤篙。而 FreeRTOS 提供的互斥量可以通過優(yōu)先級(jí)繼承算法舷嗡,可以降低優(yōu)先級(jí)翻轉(zhuǎn)問題產(chǎn)生的影響,所以莉钙,用于臨界資源的保護(hù)一般建議使用互斥量廓脆。

7.2 運(yùn)作機(jī)制


用互斥量處理不同任務(wù)對(duì)臨界資源的同步訪問時(shí),任務(wù)想要獲得互斥量才能進(jìn)行資源訪問磁玉,如果一旦有任務(wù)成功獲得了互斥量停忿,則互斥量立即變?yōu)殚]鎖狀態(tài),此時(shí)其他任務(wù)會(huì)因?yàn)楂@取不到互斥量而不能訪問這個(gè)資源蚊伞,任務(wù)會(huì)根據(jù)用戶自定義的等待時(shí)間進(jìn)行等待席赂,直到互斥量被持有的任務(wù)釋放后吮铭,其他任務(wù)才能獲取互斥量從而得以訪問該臨界資源,此時(shí)互斥量再次上鎖颅停,如此一來就可以確保每個(gè)時(shí)刻只有一個(gè)任務(wù)正在訪問這個(gè)臨界資源谓晌,保證了臨界資源操作的安全性。

7.3 互斥量與遞歸互斥量

  • 互斥量更適合于可能會(huì)引起優(yōu)先級(jí)翻轉(zhuǎn)的情況癞揉。
  • 遞歸互斥量更適用于任務(wù)可能會(huì)多次獲取互斥量的情況下纸肉。這樣可以避免同一任務(wù)多次遞歸持有而造成死鎖的問題。

八喊熟、相關(guān)API說明

8.1 osMutexCreate

用于創(chuàng)建一個(gè)互斥量柏肪,并返回一個(gè)互斥量ID。

函數(shù) osMutexId osMutexCreate (const osMutexDef_t *mutex_def)
參數(shù) mutex_def: 引用由osMutexDef定義的互斥量
返回值 成功返回互斥量ID芥牌,失敗返回0

8.2 osRecursiveMutexCreate

用于創(chuàng)建一個(gè)遞歸互斥量烦味,不是遞歸的互斥量由函數(shù) osMutexCreate() 創(chuàng)建,且只能被同一個(gè)任務(wù)獲取一次壁拉,如果同一個(gè)任務(wù)想再次獲取則會(huì)失敗谬俄。遞歸信號(hào)量則相反,它可以被同一個(gè)任務(wù)獲取很多次弃理,獲取多少次就需要釋放多少次凤瘦。遞歸信號(hào)量與互斥量一樣,都實(shí)現(xiàn)了優(yōu)先級(jí)繼承機(jī)制案铺,可以減少優(yōu)先級(jí)反轉(zhuǎn)的反生蔬芥。

函數(shù) osMutexId osRecursiveMutexCreate (const osMutexDef_t *mutex_def)
參數(shù) mutex_def: 引用由osMutexDef定義的互斥量
返回值 成功返回互斥量ID,失敗返回0

要想使用該函數(shù)必須在 Config parameters 中把 USE_RECURSIVE_MUTEXES 選擇 Enabled 來使能控汉。

8.3 osMutexDelete

用于刪除一個(gè)互斥量笔诵。

函數(shù) osStatus osMutexDelete (osMutexId mutex_id)
參數(shù) mutex_id: 互斥量ID
返回值 錯(cuò)誤碼

8.4 osMutexWait

用于獲取互斥量,但是遞歸互斥量并不能使用這個(gè) API 函數(shù)獲取姑子。

函數(shù) osStatus osMutexWait (osMutexId mutex_id, uint32_t millisec)
參數(shù) mutex_id: 互斥量ID

millisec:等待信號(hào)量可用的最大超時(shí)時(shí)間乎婿,單位為 tick(即系統(tǒng)節(jié)拍周期)。如果宏 INCLUDE_vTaskSuspend 定義為 1 且形參 xTicksToWait 設(shè)置為 portMAX_DELAY 街佑,則任務(wù)將一直阻塞在該信號(hào)量上(即沒有超時(shí)時(shí)間)
返回值 錯(cuò)誤碼

8.5 osRecursiveMutexWait

用于獲取遞歸互斥量的宏谢翎,與互斥量的獲取函數(shù)一樣,osMutexWait()也是一個(gè)宏定義沐旨,它最終使用現(xiàn)有的隊(duì)列機(jī)制森逮,實(shí)際執(zhí)行的函數(shù)是 xQueueTakeMutexRecursive() 。 獲取遞歸互斥量之前必須由 osRecursiveMutexCreate() 這個(gè)函數(shù)創(chuàng)建磁携。要注意的是該函數(shù)不能用于獲取由函數(shù) osMutexCreate() 創(chuàng)建的互斥量褒侧。

函數(shù) osStatus osRecursiveMutexWait (osMutexId mutex_id, uint32_t millisec)
參數(shù) mutex_id: 互斥量ID

millisec:等待信號(hào)量可用的最大超時(shí)時(shí)間,單位為 tick(即系統(tǒng)節(jié)拍周期)。如果宏 INCLUDE_vTaskSuspend 定義為 1 且形參 xTicksToWait 設(shè)置為 portMAX_DELAY 闷供,則任務(wù)將一直阻塞在該信號(hào)量上(即沒有超時(shí)時(shí)間)
返回值 錯(cuò)誤碼

要想使用該函數(shù)必須在 Config parameters 中把 USE_RECURSIVE_MUTEXES 選擇 Enabled 來使能烟央。

8.6 osMutexRelease

用于釋放互斥量,但不能釋放由函數(shù) osRecursiveMutexCreate() 創(chuàng)建的遞歸互斥量歪脏。

函數(shù) osStatus osMutexRelease (osMutexId mutex_id)
參數(shù) mutex_id: 互斥量ID
返回值 錯(cuò)誤碼

8.7 osRecursiveMutexRelease

用于釋放一個(gè)遞歸互斥量疑俭。已經(jīng)獲取遞歸互斥量的任務(wù)可以重復(fù)獲取該遞歸互斥量。使用 osRecursiveMutexWait() 函數(shù)成功獲取幾次遞歸互斥量婿失,就要使用 osRecursiveMutexRelease() 函數(shù)返還幾次怠硼,在此之前遞歸互斥量都處于無效狀態(tài),別的任務(wù)就無法獲取該遞歸互斥量移怯。使用該函數(shù)接口時(shí),只有已持有互斥量所有權(quán)的任務(wù)才能釋放它这难,每釋放一該遞歸互斥量舟误,它的計(jì)數(shù)值就減 1。當(dāng)該互斥量的計(jì)數(shù)值為 0 時(shí)(即持有任務(wù)已經(jīng)釋放所有的持有操作)姻乓,互斥量則變?yōu)殚_鎖狀態(tài)嵌溢,等待在該互斥量上的任務(wù)將被喚醒。如果任務(wù)的優(yōu)先級(jí)被互斥量的優(yōu)先級(jí)翻轉(zhuǎn)機(jī)制臨時(shí)提升蹋岩,那么當(dāng)互斥量被釋放后赖草,任務(wù)的優(yōu)先級(jí)將恢復(fù)為原本設(shè)定的優(yōu)先級(jí)。

函數(shù) osStatus osRecursiveMutexRelease (osMutexId mutex_id)
參數(shù) mutex_id: 互斥量ID
返回值 錯(cuò)誤碼

要想使用該函數(shù)必須在 Config parameters 中把 USE_RECURSIVE_MUTEXES 選擇 Enabled 來使能剪个。

九秧骑、示例

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;

osThreadId defaultTaskHandle;
osThreadId LowPriorityHandle;
osThreadId MidPriorityHandle;
osThreadId HighPriorityHandle;
osMutexId MuxSemHandle;
/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
void StartDefaultTask(void const * argument);
void LowPriorityTask(void const * argument);
void MidPriorityTask(void const * argument);
void HighPriorityTask(void const * argument);

/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
    
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
    printf("The default value of parking space is 5, Press key1 to apply for parking space, and press key2 to release parking space!\n\n");
  /* USER CODE END 2 */

  /* Create the mutex(es) */
  /* definition and creation of MuxSem */
  osMutexDef(MuxSem);
  MuxSemHandle = osMutexCreate(osMutex(MuxSem));

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* definition and creation of defaultTask */
  osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
  defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

  /* definition and creation of LowPriority */
  osThreadDef(LowPriority, LowPriorityTask, osPriorityIdle, 0, 128);
  LowPriorityHandle = osThreadCreate(osThread(LowPriority), NULL);

  /* definition and creation of MidPriority */
  osThreadDef(MidPriority, MidPriorityTask, osPriorityIdle, 0, 128);
  MidPriorityHandle = osThreadCreate(osThread(MidPriority), NULL);

  /* definition and creation of HighPriority */
  osThreadDef(HighPriority, HighPriorityTask, osPriorityIdle, 0, 128);
  HighPriorityHandle = osThreadCreate(osThread(HighPriority), NULL);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  /* USER CODE END RTOS_THREADS */

  /* 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 WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  /* DMA1_Channel5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, LED_G_Pin|LED_B_Pin|LED_R_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin : KEY2_Pin */
  GPIO_InitStruct.Pin = KEY2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : KEY1_Pin */
  GPIO_InitStruct.Pin = KEY1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : LED_G_Pin LED_B_Pin LED_R_Pin */
  GPIO_InitStruct.Pin = LED_G_Pin|LED_B_Pin|LED_R_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */
/**
  * @brief 重定向c庫函數(shù)printf到USARTx
  * @retval None
  */
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}
 
/**
  * @brief 重定向c庫函數(shù)getchar,scanf到USARTx
  * @retval None
  */
int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;
}
/* USER CODE END 4 */

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END 5 */
}

/* USER CODE BEGIN Header_LowPriorityTask */
/**
* @brief Function implementing the LowPriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_LowPriorityTask */
void LowPriorityTask(void const * argument)
{
  /* USER CODE BEGIN LowPriorityTask */
  static uint32_t i; 
  osStatus xReturn;
  /* Infinite loop */
  for(;;)
  {
    printf("LowPriority_Task Get Mutex\n");
    //獲取互斥量 MuxSem,沒獲取到則一直等待 
    xReturn = osMutexWait(MuxSemHandle,     /* 互斥量句柄 */ 
                          osWaitForever);   /* 等待時(shí)間 */
    if(osOK == xReturn) 
    {
        printf("LowPriority_Task Runing\n\n"); 
    }
    
    for(i = 0; i < 2000000; i++) 
    { //模擬低優(yōu)先級(jí)任務(wù)占用互斥量 
        taskYIELD();//發(fā)起任務(wù)調(diào)度 
    } 
    
    printf("LowPriority_Task Release Mutex\r\n"); 
    xReturn = osMutexRelease(MuxSemHandle);//給出互斥量 
    
    osDelay(1000);
  }
  /* USER CODE END LowPriorityTask */
}

/* USER CODE BEGIN Header_MidPriorityTask */
/**
* @brief Function implementing the MidPriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_MidPriorityTask */
void MidPriorityTask(void const * argument)
{
  /* USER CODE BEGIN MidPriorityTask */
  /* Infinite loop */
  for(;;)
  {
    printf("MidPriority_Task Runing\n"); 
    osDelay(1000);
  }
  /* USER CODE END MidPriorityTask */
}

/* USER CODE BEGIN Header_HighPriorityTask */
/**
* @brief Function implementing the HighPriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_HighPriorityTask */
void HighPriorityTask(void const * argument)
{
  /* USER CODE BEGIN HighPriorityTask */
  osStatus xReturn;
  /* Infinite loop */
  for(;;)
  {
    printf("HighPriority_Task Get Mutex\n"); 
    //獲取互斥量 MuxSem,沒獲取到則一直等待 
    xReturn = osMutexWait(MuxSemHandle,     /* 互斥量句柄 */ 
                          osWaitForever);   /* 等待時(shí)間 */
    if(osOK == xReturn) 
    {
        printf("HighPriority_Task Runing\n"); 
    }
  
    printf("HighPriority_Task Release Mutex\r\n"); 
    xReturn = osMutexRelease(MuxSemHandle);//給出互斥量

    osDelay(1000);
  }
  /* USER CODE END HighPriorityTask */
}

/**
  * @brief  Period elapsed callback in non blocking mode
  * @note   This function is called  when TIM1 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM1) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

查看打印:


十扣囊、工程代碼

鏈接:https://pan.baidu.com/s/1X3erVkBowNZrdMIWbzciQg 提取碼:amcv

十一乎折、注意事項(xiàng)

用戶代碼要加在 USER CODE BEGIN NUSER CODE END N 之間,否則下次使用 STM32CubeMX 重新生成代碼后侵歇,會(huì)被刪除骂澄。


? 由 Leung 寫于 2021 年 12 月 30 日

? 參考:STM32CubMx+FreeRTOS互斥鎖和遞歸互斥鎖(五)
    STM32CubeIDE(十一):FreeRTOS選項(xiàng)中Disable、CMSIS_V1和CMSIS_V2的區(qū)別
    HAL庫中的 SYS Timebase Source 和 SysTick_Handler()

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惕虑,一起剝皮案震驚了整個(gè)濱河市坟冲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌溃蔫,老刑警劉巖健提,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伟叛,居然都是意外死亡矩桂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雹锣,“玉大人,你說我怎么就攤上這事癞蚕。” “怎么了桦山?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵攒射,是天一觀的道長恒水。 經(jīng)常有香客問我,道長钉凌,這世上最難降的妖魔是什么咧最? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任矢沿,我火速辦了婚禮,結(jié)果婚禮上酸纲,老公的妹妹穿的比我還像新娘。我一直安慰自己闽坡,他們只是感情好栽惶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布疾嗅。 她就那樣靜靜地躺著,像睡著了一般宪迟。 火紅的嫁衣襯著肌膚如雪酣衷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天次泽,我揣著相機(jī)與錄音穿仪,去河邊找鬼。 笑死意荤,一個(gè)胖子當(dāng)著我的面吹牛啊片,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播玖像,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼紫谷,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起笤昨,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤祖驱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后瞒窒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捺僻,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年崇裁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匕坯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拔稳,死狀恐怖葛峻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巴比,我是刑警寧澤术奖,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站匿辩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏榛丢。R本人自食惡果不足惜铲球,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晰赞。 院中可真熱鬧稼病,春花似錦、人聲如沸掖鱼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽戏挡。三九已至芍瑞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間褐墅,已是汗流浹背拆檬。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妥凳,地道東北人竟贯。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像逝钥,于是被迫代替她去往敵國和親屑那。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容