STM32CubeMX學(xué)習(xí)筆記(36)——FreeRTOS實時操作系統(tǒng)使用(中斷管理)

一、FreeRTOS簡介

FreeRTOS 是一個可裁剪收苏、可剝奪型的多任務(wù)內(nèi)核,而且沒有任務(wù)數(shù)限制愤兵。FreeRTOS 提供了實時操作系統(tǒng)所需的所有功能鹿霸,包括資源管理、同步秆乳、任務(wù)通信等懦鼠。

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

最新版本 V9.0.0(2016年)忧陪,盡管現(xiàn)在 FreeRTOS 的版本已經(jīng)更新到 V10.4.1 了扣泊,但是我們還是選擇 V9.0.0,因為內(nèi)核很穩(wěn)定嘶摊,并且網(wǎng)上資料很多延蟹,因為 V10.0.0 版本之后是亞馬遜收購了FreeRTOS之后才出來的版本,主要添加了一些云端組件叶堆,一般采用 V9.0.0 版本足以阱飘。

二、新建工程

1. 打開 STM32CubeMX 軟件虱颗,點擊“新建工程”

2. 選擇 MCU 和封裝

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


選擇 Clock Configuration,配置系統(tǒng)時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后忘渔,輸入回車高帖,軟件會自動修改所有配置

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

三散址、SYS Timebase Source

System Core 中選擇 SYS ,對 Timebase Source 進行設(shè)置宣赔,選擇 TIM1 作為HAL庫的時基(除了 SysTick 外都可以)预麸。

在基于STM32 HAL的項目中,一般需要維護的 “時基” 主要有2個:

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

而這些 “時基” 該去如何維護吏祸,主要分為兩種情況考慮:

  • 裸機運行
    可以通過 SysTick(滴答定時器)或 (TIMx)定時器 的方式來維護 SYS Timebase Source,也就是HAL庫中的 uwTick椅棺,這是HAL庫中維護的一個全局變量犁罩。在裸機運行的情況下,我們一般選擇默認的 SysTick(滴答定時器) 方式即可两疚,也就是直接放在 SysTick_Handler() 中斷服務(wù)函數(shù)中來維護床估。

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

    在使用了OS(操作系統(tǒng))之后丐巫,OS的運行也需要一個時鐘基準(zhǔn)(簡稱“時基”),來對任務(wù)和時間等進行管理勺美。而OS的這個 時基 一般也都是通過 SysTick(滴答定時器) 來維護的递胧,這時就需要考慮 “HAL的時基” 和 “OS的時基” 是否要共用 SysTick(滴答定時器) 了。

    如果共用SysTick赡茸,當(dāng)我們在CubeMX中選擇啟用FreeRTOS之后缎脾,在生成代碼時,CubeMX一定會報如下提示:


強烈建議用戶在使用FreeRTOS的時候占卧,不要使用 SysTick(滴答定時器)作為 “HAL的時基”遗菠,因為FreeRTOS要用,最好是要換一個;选U尬场!如果共用叭喜,潛在一定風(fēng)險贺拣。

四、FreeRTOS

4.1 參數(shù)配置

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


CMSIS是一種接口標(biāo)準(zhǔn)譬涡,目的是屏蔽軟硬件差異以提高軟件的兼容性。RTOS v1使得軟件能夠在不同的實時操作系統(tǒng)下運行(屏蔽不同RTOS提供的API的差別)启绰,而RTOS v2則是拓展了RTOS v1昂儒,兼容更多的CPU架構(gòu)和實時操作系統(tǒng)。因此我們在使用時可以根據(jù)實際情況選擇委可,如果學(xué)習(xí)過程中使用STM32F1渊跋、F4等單片機時沒必要選擇RTOS v2,更高的兼容性背后時更加冗余的代碼着倾,理解起來比較困難拾酝。

Config parameters 進行具體參數(shù)配置。

Kernel settings:

  • USE_PREEMPTION: Enabled:RTOS使用搶占式調(diào)度器卡者;Disabled:RTOS使用協(xié)作式調(diào)度器(時間片)蒿囤。
  • TICK_RATE_HZ: 值設(shè)置為1000,即周期就是1ms崇决。RTOS系統(tǒng)節(jié)拍中斷的頻率材诽,單位為HZ底挫。
  • MAX_PRIORITIES: 可使用的最大優(yōu)先級數(shù)量。設(shè)置好以后任務(wù)就可以使用從0到(MAX_PRIORITIES - 1)的優(yōu)先級脸侥,其中0位最低優(yōu)先級建邓,(MAX_PRIORITIES - 1)為最高優(yōu)先級。
  • 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)先級的用戶任務(wù)跳仿。
  • USE_MUTEXES: 為1時使用互斥信號量诡渴,相關(guān)的API函數(shù)會被編譯。
  • USE_RECURSIVE_MUTEXES: 為1時使用遞歸互斥信號量塔嬉,相關(guān)的API函數(shù)會被編譯玩徊。
  • USE_COUNTING_SEMAPHORES: 為1時啟用計數(shù)型信號量, 相關(guān)的API函數(shù)會被編譯谨究。
  • QUEUE_REGISTRY_SIZE: 設(shè)置可以注冊的隊列和信號量的最大數(shù)量恩袱,在使用內(nèi)核調(diào)試器查看信號量和隊列的時候需要設(shè)置此宏,而且要先將消息隊列和信號量進行注冊胶哲,只有注冊了的隊列和信號量才會在內(nèi)核調(diào)試器中看到畔塔,如果不使用內(nèi)核調(diào)試器的話次宏設(shè)置為0即可。
  • USE_APPLICATION_TASK_TAG: 為1時可以使用vTaskSetApplicationTaskTag函數(shù)鸯屿。
  • ENABLE_BACKWARD_COMPATIBILITY: 為1時可以使V8.0.0之前的FreeRTOS用戶代碼直接升級到V8.0.0之后澈吨,而不需要做任何修改。
  • USE_PORT_OPTIMISED_TASK_SELECTION: FreeRTOS有兩種方法來選擇下一個要運行的任務(wù)寄摆,一個是通用的方法谅辣,另外一個是特殊的方法,也就是硬件方法婶恼,使用MCU自帶的硬件指令來實現(xiàn)桑阶。STM32有計算前導(dǎo)零指令嗎,所以這里強制置1勾邦。
  • USE_TICKLESS_IDLE: 置1:使能低功耗tickless模式蚣录;置0:保持系統(tǒng)節(jié)拍(tick)中斷一直運行。假設(shè)開啟低功耗的話可能會導(dǎo)致下載出現(xiàn)問題眷篇,因為程序在睡眠中萎河,可用ISP下載辦法解決。
  • USE_TASK_NOTIFICATIONS: 為1時使用任務(wù)通知功能,相關(guān)的API函數(shù)會被編譯虐杯。開啟了此功能玛歌,每個任務(wù)會多消耗8個字節(jié)。
  • RECORD_STACK_HIGH_ADDRESS: 為1時棧開始地址會被保存到每個任務(wù)的TCB中(假如棧是向下生長的)擎椰。

Memory management settings:

  • Memory Allocation: Dynamic/Static 支持動態(tài)/靜態(tài)內(nèi)存申請
  • TOTAL_HEAP_SIZE: 設(shè)置堆大小沾鳄,如果使用了動態(tài)內(nèi)存管理,F(xiàn)reeRTOS在創(chuàng)建 task, queue, mutex, software timer or semaphore的時候就會使用heap_x.c(x為1~5)中的內(nèi)存申請函數(shù)來申請內(nèi)存确憨。這些內(nèi)存就是從堆ucHeap[configTOTAL_HEAP_SIZE]中申請的。
  • 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:使用時間片鉤子(Tick Hook)圈膏;置0:忽略時間片鉤子塔猾。
  • USE_MALLOC_FAILED_HOOK: 使用內(nèi)存申請失敗鉤子函數(shù)。
  • CHECK_FOR_STACK_OVERFLOW: 大于0時啟用堆棧溢出檢測功能稽坤,如果使用此功能用戶必須提供一個棧溢出鉤子函數(shù)丈甸,如果使用的話此值可以為1或者2,因為有兩種棧溢出檢測方法尿褪。

Run time and task stats gathering related definitions:

  • GENERATE_RUN_TIME_STATS: 啟用運行時間統(tǒng)計功能睦擂。
  • USE_TRACE_FACILITY: 啟用可視化跟蹤調(diào)試。
  • USE_STATS_FORMATTING_FUNCTIONS: 與宏configUSE_TRACE_FACILITY同時為1時會編譯下面3個函數(shù)prvWriteNameToBuffer()杖玲、vTaskList()顿仇、vTaskGetRunTimeStats()。

Co-routine related definitions:

  • USE_CO_ROUTINES: 啟用協(xié)程摆马。
  • MAX_CO_ROUTINE_PRIORITIES: 協(xié)程的有效優(yōu)先級數(shù)目臼闻。

Software timer definitions:

  • USE_TIMERS: 啟用軟件定時器。

Interrupt nesting behaviour configuration:

  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 中斷最低優(yōu)先級囤采。
  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 系統(tǒng)可管理的最高中斷優(yōu)先級述呐。

4.2 創(chuàng)建隊列Queue

Tasks and Queues 進行配置。

創(chuàng)建一個消息隊列TestQueue蕉毯,


  • Queue Name: 隊列名稱
  • Queue Size: 隊列能夠存儲的最大單元數(shù)目乓搬,即隊列深度
  • Queue Size: 隊列中數(shù)據(jù)單元的長度,以字節(jié)為單位
  • Allocation: 分配方式:Dynamic 動態(tài)內(nèi)存創(chuàng)建
  • Buffer Name: 緩沖區(qū)名稱
  • Buffer Size: 緩沖區(qū)大小
  • Conrol Block Name: 控制塊名稱

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

我們創(chuàng)建兩個任務(wù)恕刘,一個消息接收任務(wù)缤谎,一個消息發(fā)送任務(wù)。



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

4.4 創(chuàng)建二值信號量Binary Semaphore

Timers and Semaphores 進行配置着降。

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

五、EXTI外部中斷

5.1 參數(shù)配置

System Core 中選擇 GPIO 設(shè)置拗军。


在右邊圖中找到按鍵對應(yīng)引腳饵蒂,選擇 GPIO_EXTIx
這里的 x 是指掛載在中斷線幾上疙赠,如 GPIO_EXTI0 就是掛載在中斷線0上枫浙。

  • 開啟下降沿觸發(fā)中斷:即在 按下按鍵時 電平由高變?yōu)榈蜁r觸發(fā),則在 GPIO mode 中選擇 External Interrupt Mode with Falling edge trigger detection
  • 開啟上升沿觸發(fā)中斷:即在 按下按鍵后松開時 電平由低變?yōu)楦邥r觸發(fā)刃鳄,則在 GPIO mode 中選擇 External Interrupt Mode with Rising edge trigger detection
  • 開啟下降沿上升沿都觸發(fā)中斷:即在 按下時觸發(fā)盅弛,松開時再次觸發(fā),則在 GPIO mode 中選擇 External Interrupt Mode with Rising/Falling edge trigger detection
  • 如果硬件上已外部上拉或下拉叔锐,則在GPIO Pull-up/Pull-down 中選擇 No pull-up and no pull-down 既不上拉也不下拉挪鹏。
  • 如果硬件外部沒有上拉,則在GPIO Pull-up/Pull-down 中選擇 Pull-up 內(nèi)部上拉電阻愉烙。

    配置 NVIC
    中斷優(yōu)先級分組規(guī)則 Priority Group 默認為4個比特位讨盒,一般情況下不改。
    勾選剛剛配置的外部中斷線0和13步责,并配置搶占優(yōu)先級 Preemption Priority 和響應(yīng)優(yōu)先級 Sub Priority催植。在 FreeRTOS 中,優(yōu)先級最低為 5勺择。

六创南、UART串口打印

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

七、生成代碼

輸入項目名和項目路徑


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

每個外設(shè)生成獨立的 ’.c/.h’ 文件
不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應(yīng)的外設(shè)文件省核。 如 GPIO 初始化代碼生成在 gpio.c 中稿辙。

點擊 GENERATE CODE 生成代碼

八、中斷管理

8.1 基本概念

8.1.1 異常

異常是導(dǎo)致處理器脫離正常運行轉(zhuǎn)向執(zhí)行特殊代碼的任何事件气忠,如果不及時進行處理邻储,輕則系統(tǒng)出錯,重則會導(dǎo)致系統(tǒng)毀滅性癱瘓旧噪。所以正確地處理異常吨娜,避免錯誤的發(fā)生是提高軟件魯棒性(穩(wěn)定性)非常重要的一環(huán),對于實時系統(tǒng)更是如此淘钟。

異常是指任何打斷處理器正常執(zhí)行宦赠,并且迫使處理器進入一個由有特權(quán)的特殊指令執(zhí)行的事件。異常通常可以分成兩類:同步異常和異步異常勾扭。由內(nèi)部事件(像處理器指令運行產(chǎn)生的事件)引起的異常稱為同步異常毡琉,例如造成被零除的算術(shù)運算引發(fā)一個異常,又如在某些處理器體系結(jié)構(gòu)中妙色,對于確定的數(shù)據(jù)尺寸必須從內(nèi)存的偶數(shù)地址進行讀和寫操作桅滋。從一個奇數(shù)內(nèi)存地址的讀或?qū)懖僮鲗⒁鸫鎯ζ鞔嫒∫粋€錯誤事件并引起一個異常(稱為校準(zhǔn)異常)。

異步異常主要是指由于外部異常源產(chǎn)生的異常身辨,是一個由外部硬件裝置產(chǎn)生的事件引起的異步異常丐谋。同步異常不同于異步異常的地方是事件的來源,同步異常事件是由于執(zhí)行某些指令而從處理器內(nèi)部產(chǎn)生的煌珊,而異步異常事件的來源是外部硬件裝置笋鄙。例如按下設(shè)備某個按鈕產(chǎn)生的事件。同步異常與異步異常的區(qū)別還在于怪瓶,同步異常觸發(fā)后,系統(tǒng)必須立刻進行處理而不能夠依然執(zhí)行原有的程序指令步驟践美;而異步異常則可以延緩處理甚至是忽略洗贰,例如按鍵中斷異常,雖然中斷異常觸發(fā)了陨倡,但是系統(tǒng)可以忽略它繼續(xù)運行(同樣也忽略了相應(yīng)的按鍵事件)敛滋。

8.1.2 中斷

中斷,中斷屬于異步異常兴革。所謂中斷是指中央處理器 CPU 正在處理某件事的時候绎晃,外部發(fā)生了某一事件,請求 CPU 迅速處理杂曲,CPU 暫時中斷當(dāng)前的工作庶艾,轉(zhuǎn)入處理所發(fā)生的事件,處理完后擎勘,再回到原來被中斷的地方咱揍,繼續(xù)原來的工作,這樣的過程稱為中斷棚饵。

中斷能打斷任務(wù)的運行煤裙,無論該任務(wù)具有什么樣的優(yōu)先級,因此中斷一般用于處理比較緊急的事件噪漾,而且只做簡單處理硼砰,例如標(biāo)記該事件,在使用 FreeRTOS 系統(tǒng)時欣硼,一般建議使用信號量题翰、消息或事件標(biāo)志組等標(biāo)志中斷的發(fā)生,將這些內(nèi)核對象發(fā)布給處理任務(wù),處理任務(wù)再做具體處理遍愿。

通過中斷機制存淫,在外設(shè)不需要 CPU 介入時,CPU 可以執(zhí)行其他任務(wù)沼填,而當(dāng)外設(shè)需要 CPU 時通過產(chǎn)生中斷信號使 CPU 立即停止當(dāng)前任務(wù)轉(zhuǎn)而來響應(yīng)中斷請求桅咆。這樣可以使 CPU 避免把大量時間耗費在等待、查詢外設(shè)狀態(tài)的操作上坞笙,因此將大大提高系統(tǒng)實時性以及執(zhí)行效率岩饼。

此處讀者要知道一點,F(xiàn)reeRTOS 源碼中有許多處臨界段的地方薛夜,臨界段雖然保護了關(guān)鍵代碼的執(zhí)行不被打斷籍茧,但也會影響系統(tǒng)的實時,任何使用了操作系統(tǒng)的中斷響應(yīng)都不會比裸機快梯澜。比如寞冯,某個時候有一個任務(wù)在運行中,并且該任務(wù)部分程序?qū)⒅袛嗥帘蔚敉砘铮簿褪沁M入臨界段中吮龄,這個時候如果有一個緊急的中斷事件被觸發(fā),這個中斷就會被掛起咆疗,不能得到及時響應(yīng)漓帚,必須等到中斷開啟才可以得到響應(yīng),如果屏蔽中斷時間超過了緊急中斷能夠容忍的限度午磁,危害是可想而知的尝抖。所以,操作系統(tǒng)的中斷在某些時候會有適當(dāng)?shù)闹袛嘌舆t迅皇,因此調(diào)用中斷屏蔽函數(shù)進入臨界段的時候昧辽,也需快進快出。當(dāng)然 FreeRTOS 也能允許一些高優(yōu)先級的中斷不被屏蔽掉登颓,能夠及時做出響應(yīng)奴迅,不過這些中斷就不受系統(tǒng)管理,也不允許調(diào)用 FreeRTOS 中與中斷相關(guān)的任何 API 函數(shù)接口挺据。

FreeRTOS 的中斷管理支持:

  • 開/關(guān)中斷取具。
  • 恢復(fù)中斷。
  • 中斷使能扁耐。
  • 中斷屏蔽暇检。
  • 可選擇系統(tǒng)管理的中斷優(yōu)先級。

1.3 中斷延遲

即使操作系統(tǒng)的響應(yīng)很快了婉称,但對于中斷的處理仍然存在著中斷延遲響應(yīng)的問題块仆,我們稱之為中斷延遲(Interrupt Latency) 构蹬。

中斷延遲是指從硬件中斷發(fā)生到開始執(zhí)行中斷處理程序第一條指令之間的這段時間。也就是:系統(tǒng)接收到中斷信號到操作系統(tǒng)作出響應(yīng)悔据,并完成換到轉(zhuǎn)入中斷服務(wù)程序的時間庄敛。也可以簡單地理解為:(外部)硬件(設(shè)備)發(fā)生中斷,到系統(tǒng)執(zhí)行中斷服務(wù)子程序(ISR)的第一條指令的時間科汗。

中斷的處理過程是:外界硬件發(fā)生了中斷后藻烤,CPU 到中斷處理器讀取中斷向量,并且查找中斷向量表头滔,找到對應(yīng)的中斷服務(wù)子程序(ISR)的首地址怖亭,然后跳轉(zhuǎn)到對應(yīng)的 ISR 去做相應(yīng)處理。這部分時間坤检,我稱之為:識別中斷時間兴猩。

在允許中斷嵌套的實時操作系統(tǒng)中,中斷也是基于優(yōu)先級的早歇,允許高優(yōu)先級中斷搶斷正在處理的低優(yōu)先級中斷倾芝,所以,如果當(dāng)前正在處理更高優(yōu)先級的中斷箭跳,即使此時有低優(yōu)先級的中斷晨另,也系統(tǒng)不會立刻響應(yīng),而是等到高優(yōu)先級的中斷處理完之后衅码,才會響應(yīng)。而即使在不支持中斷嵌套脊岳,即中斷是沒有優(yōu)先級的逝段,中斷是不允許被中斷的,所以割捅,如果當(dāng)前系統(tǒng)正在處理一個中斷奶躯,而此時另一個中斷到來了,系統(tǒng)也是不會立即響應(yīng)的亿驾,而只是等處理完當(dāng)前的中斷之后嘹黔,才會處理后來的中斷。此部分時間莫瞬,我稱其為:等待中斷打開時間儡蔓。

在操作系統(tǒng)中,很多時候我們會主動進入臨界段疼邀,系統(tǒng)不允許當(dāng)前狀態(tài)被中斷打斷喂江,故而在臨界區(qū)發(fā)生的中斷會被掛起,直到退出臨界段時候打開中斷旁振。此部分時間获询,我稱其為:關(guān)閉中斷時間涨岁。

中斷延遲可以定義為,從中斷開始的時刻到中斷服務(wù)例程開始執(zhí)行的時刻之間的時間段吉嚣。中斷延遲 = 識別中斷時間 + [等待中斷打開時間] + [關(guān)閉中斷時間]梢薪。
注意:“[ ]”的時間是不一定都存在的,此處為最大可能的中斷延遲時間尝哆。

8.2 運作機制

當(dāng)中斷產(chǎn)生時秉撇,處理機將按如下的順序執(zhí)行:

  1. 保存當(dāng)前處理機狀態(tài)信息
  2. 載入異常或中斷處理函數(shù)到 PC 寄存器
  3. 把控制權(quán)轉(zhuǎn)交給處理函數(shù)并開始執(zhí)行
  4. 當(dāng)處理函數(shù)執(zhí)行完成時较解,恢復(fù)處理器狀態(tài)信息
  5. 從異承蠹玻或中斷中返回到前一個程序執(zhí)行點

中斷使得 CPU 可以在事件發(fā)生時才給予處理,而不必讓 CPU 連續(xù)不斷地查詢是否有相應(yīng)的事件發(fā)生印衔。通過兩條特殊指令:關(guān)中斷和開中斷可以讓處理器不響應(yīng)或響應(yīng)中斷啡捶,在關(guān)閉中斷期間,通常處理器會把新產(chǎn)生的中斷掛起奸焙,當(dāng)中斷打開時立刻進行響應(yīng)瞎暑,所以會有適當(dāng)?shù)难訒r響應(yīng)中斷,故用戶在進入臨界區(qū)的時候應(yīng)快進快出与帆。中斷發(fā)生的環(huán)境有兩種情況:在任務(wù)的上下文中了赌,在中斷服務(wù)函數(shù)處理上下文中。

  • 任務(wù)在工作的時候玄糟,如果此時發(fā)生了一個中斷勿她,無論中斷的優(yōu)先級是多大,都會打斷當(dāng)前任務(wù)的執(zhí)行阵翎,從而轉(zhuǎn)到對應(yīng)的中斷服務(wù)函數(shù)中執(zhí)行逢并。
  • 在執(zhí)行中斷服務(wù)例程的過程中,如果有更高優(yōu)先級別的中斷源觸發(fā)中斷郭卫,由于當(dāng)前處于中斷處理上下文環(huán)境中砍聊,根據(jù)不同的處理器構(gòu)架可能有不同的處理方式,比如新的中斷等待掛起直到當(dāng)前中斷處理離開后再行響應(yīng)贰军;或新的高優(yōu)先級中斷打斷當(dāng)前中斷處理過程玻蝌,而去直接響應(yīng)這個更高優(yōu)先級的新中斷源。后面這種情況词疼,稱之為中斷嵌套俯树。在硬實時環(huán)境中,前一種情況是不允許發(fā)生的贰盗,不能使響應(yīng)中斷的時間盡量的短聘萨。而在軟件處理(軟實時環(huán)境)上,F(xiàn)reeRTOS 允許中斷嵌套童太,即在一個中斷服務(wù)例程期間米辐,處理器可以響應(yīng)另外一個優(yōu)先級更高的中斷胸完。

8.3 FreeRTOS中斷管理

ARM Cortex-M 系列內(nèi)核的中斷是由硬件管理的,而 FreeRTOS 是軟件翘贮,它并不接管由硬件管理的相關(guān)中斷(接管簡單來說就是赊窥,所有的中斷都由 RTOS 的軟件管理,硬件來了中斷時狸页,由軟件決定是否響應(yīng)锨能,可以掛起中斷,延遲響應(yīng)或者不響應(yīng))芍耘,只支持簡單的開關(guān)中斷等址遇,所以 FreeRTOS 中的中斷使用其實跟裸機差不多的,需要我們自己配置中斷斋竞,并且使能中斷倔约,編寫中斷服務(wù)函數(shù),在中斷服務(wù)函數(shù)中使用內(nèi)核 IPC 通信機制坝初,一般建議使用信號量浸剩、消息或事件標(biāo)志組等標(biāo)志事件的發(fā)生,將事件發(fā)布給處理任務(wù)鳄袍,等退出中斷后再由相關(guān)處理任務(wù)具體處理中斷绢要。

用戶可以自定義配置 系統(tǒng)可管理的最高中斷優(yōu) 先 級 的宏定義 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY ,它是用于配置內(nèi)核中的 basepri 寄存器的拗小,當(dāng) basepri 設(shè)置為某個值的時候重罪,NVIC 不會響應(yīng)比該優(yōu)先級低的中斷,而優(yōu)先級比之更高的中斷則不受影響哀九。就是說當(dāng)這個宏定義配置為 5 的時候剿配,中斷優(yōu)先級數(shù)值在 0、1勾栗、2惨篱、3盏筐、4 的這些中斷是不受 FreeRTOS 屏蔽的围俘,也就是說即使在系統(tǒng)進入臨界段的時候,這些中斷也能被觸發(fā)而不是等到退出臨界段的時候才被觸發(fā)琢融,當(dāng)然界牡,這些中斷服務(wù)函數(shù)中也不能調(diào)用 FreeRTOS 提供的 API 函數(shù)接口,而中斷優(yōu)先級在 5 到 15 的這些中斷是可以被屏蔽的漾抬,也能安全調(diào)用 FreeRTOS 提供的 API 函數(shù)接口宿亡。

ARM Cortex-M NVIC 支持中斷嵌套功能:當(dāng)一個中斷觸發(fā)并且系統(tǒng)進行響應(yīng)時,處理器硬件會將當(dāng)前運行的部分上下文寄存器自動壓入中斷棧中纳令,這部分的寄存器包括 PSR挽荠,R0克胳,R1,R2圈匆,R3 以及 R12 寄存器漠另。當(dāng)系統(tǒng)正在服務(wù)一個中斷時,如果有一個更高優(yōu)先級的中斷觸發(fā)跃赚,那么處理器同樣的會打斷當(dāng)前運行的中斷服務(wù)例程笆搓,然后把老的中斷服務(wù)例程上下文的 PSR,R0纬傲,R1满败,R2,R3 和 R12 寄存器自動保存到中斷棧中叹括。這些部分上下文寄存器保存到中斷棧的行為完全是硬件行為算墨,這一點是與其他 ARM 處理器最大的區(qū)別(以往都需要依賴于軟件保存上下文)。

另外领猾,在 ARM Cortex-M 系列處理器上米同,所有中斷都采用中斷向量表的方式進行處理,即當(dāng)一個中斷觸發(fā)時摔竿,處理器將直接判定是哪個中斷源面粮,然后直接跳轉(zhuǎn)到相應(yīng)的固定位置進行處理。而在 ARM7继低、ARM9 中熬苍,一般是先跳轉(zhuǎn)進入 IRQ 入口,然后再由軟件進行判斷是哪個中斷源觸發(fā)袁翁,獲得了相對應(yīng)的中斷服務(wù)例程入口地址后柴底,再進行后續(xù)的中斷處理。ARM7粱胜、ARM9 的好處在于柄驻,所有中斷它們都有統(tǒng)一的入口地址,便于 OS 的統(tǒng)一管理焙压。而 ARM Cortex-M 系列處理器則恰恰相反鸿脓,每個中斷服務(wù)例程必須排列在一起放在統(tǒng)一的地址上(這個地址必須要設(shè)置到 NVIC 的中斷向量偏移寄存器中)。中斷向量表一般由一個數(shù)組定義(或在起始代碼中給出)涯曲,在 STM32 上野哭,默認采用起始代碼給出:

__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler

; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
………

FreeRTOS 在 Cortex-M 系列處理器上也遵循與裸機中斷一致的方法,當(dāng)用戶需要使用自定義的中斷服務(wù)例程時幻件,只需要定義相同名稱的函數(shù)覆蓋弱化符號即可拨黔。所以,F(xiàn)reeRTOS 在 Cortex-M 系列處理器的中斷控制其實與裸機沒什么差別绰沥。

8.4 FreeRTOS開關(guān)中斷

FreeRTOS開關(guān)中斷函數(shù)為portENABLE_INTERRUPTS()和portDISABLE_INTERRUPTS()篱蝇,這兩個函數(shù)其實是宏定義贺待,在portmacro.h中有定義,如下:

#define portDISABLE_INTERRUPTS()                    vPortRaiseBASEPRI() 
#define portENABLE_INTERRUPTS()                     vPortSetBASEPRI(0)

函數(shù)vPortBASEPRI()傳遞了一個0零截,這就對應(yīng)了上面說到的狠持,開啟中斷是將0寫入BASEPRI寄存器。

8.5 臨界區(qū)


CMSIS-RTOS沒有臨界區(qū)瞻润,但FreeRTOS與臨界段代碼保護有關(guān)的函數(shù)有4個:taskENTER_CRITICAL()喘垂、
taskEXIT_CRITICAL()taskENTER_CRITICAL_FROM_ISR()绍撞、taskEXIT_CRITICAL_FROM_ISR()正勒,這四個函數(shù)其實是宏定義,在task.h文件中有定義傻铣。這四個函數(shù)的區(qū)別是前兩個是任務(wù)級的臨界段代碼保護章贞,后兩個是中斷級的臨界段代碼保護。所以必要時要是加上非洲。

8.5.1 任務(wù)級臨界段代碼保護

taskENTER_CRITICAL()和taskEXIT_CRITICAL()是任務(wù)級的臨界代碼保護鸭限,一個是進入臨界段,一個是退出臨界段两踏,這兩個函數(shù)是成對使用的败京,這函數(shù)的定義如下:

#define taskENTER_CRITICAL()            portENTER_CRITICAL() 
#define taskEXIT_CRITICAL()             portEXIT_CRITICAL()

而portENTER_CRITICAL()和portEXIT_CRITICAL()也是宏定義,在文件 portmacro.h中有定義梦染。
任務(wù)級臨界代碼保護使用方法如下:

void CriticalTask_TEST(void const * argv)
{
    taskENTER_CRITICAL();                                                       //進入臨界區(qū)
    total_num += 0.01f;   
    printf("total_num 的值為: %.4f\r\n",total_num);
    taskEXIT_CRITICAL();                                                        //退出臨界區(qū)
    vTaskDelay(1000);
}

當(dāng)進入臨界區(qū)時赡麦,中斷被屏蔽,臨界區(qū)代碼無法被打斷帕识,只有當(dāng)所有的臨界段代碼都退出以后才會使能中斷泛粹!
注:當(dāng)進入臨界區(qū)時,優(yōu)先級低于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中斷得不到及響應(yīng)肮疗,所以臨界區(qū)代碼一定要精簡晶姊。

8.5.2 中斷級臨界段代碼保護

函數(shù)taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()中斷級別臨界段代碼保護,是用在中斷服務(wù)程序中的伪货,而且這個中斷的優(yōu)先級一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY们衙。這兩個函數(shù)在文件task.h中有如下定義:

#define taskENTER_CRITICAL_FROM_ISR()        portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR(x)        portCLEAR_INTERRUPT_MASK_FROM_ISR(x)

中斷級臨界代碼保護使用方法如下:

void TIM3_IRQHandler(void) 
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中斷
    {
        status_value=taskENTER_CRITICAL_FROM_ISR();            //進入臨界區(qū)
        total_num += 1;
        printf("float_num 的值為: %d\r\n",total_num);
        taskEXIT_CRITICAL_FROM_ISR(status_value);              //退出臨界區(qū)
    }
    TIM_ClearITPendingBit(TIM3,TIM_IT_Update);   //清除中斷標(biāo)志位
}

九、示例

9.1 中斷管理實驗

中斷管理實驗是在 FreeRTOS 中創(chuàng)建了兩個任務(wù)分別獲取信號量與消息隊列超歌,并且定義了兩個按鍵 KEY1 與 KEY2 的觸發(fā)方式為中斷觸發(fā)砍艾,其觸發(fā)的中斷服務(wù)函數(shù)則跟裸機一樣蒂教,在中斷觸發(fā)的時候通過消息隊列將消息傳遞給任務(wù)巍举,任務(wù)接收到消息就將信息通過串口調(diào)試助手顯示出來。而且中斷管理實驗也實現(xiàn)了一個串口的 DMA 傳輸+空閑中斷功能凝垛,當(dāng)串口接收完不定長的數(shù)據(jù)之后產(chǎn)生一個空閑中斷懊悯,在中斷中將信號量傳遞給任務(wù)蜓谋,任務(wù)在收到信號量的時候?qū)⒋诘臄?shù)據(jù)讀取出來并且在串口調(diào)試助手中回顯。

修改main.c

/* 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 */
#define BUFFER_SIZE 256
/* 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 ReceiveHandle;
osThreadId SendHandle;
osMessageQId TestQueueHandle;
osSemaphoreId BinarySemHandle;
/* USER CODE BEGIN PV */
uint8_t recvBuff[BUFFER_SIZE];  //接收數(shù)據(jù)緩存數(shù)組
volatile uint8_t recvLength = 0;  //接收一幀數(shù)據(jù)的長度
volatile uint8_t recvDndFlag = 0; //一幀數(shù)據(jù)接收完成標(biāo)志
/* 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 ReceiveTask(void const * argument);
void SendTask(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 */
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中斷
  HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);    
  /* USER CODE END 2 */

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

  /* Create the semaphores(s) */
  /* definition and creation of BinarySem */
  osSemaphoreDef(BinarySem);
  BinarySemHandle = osSemaphoreCreate(osSemaphore(BinarySem), 1);

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

  /* Create the queue(s) */
  /* definition and creation of TestQueue */
  osMessageQDef(TestQueue, 16, uint32_t);
  TestQueueHandle = osMessageCreate(osMessageQ(TestQueue), NULL);

  /* 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 Receive */
  osThreadDef(Receive, ReceiveTask, osPriorityIdle, 0, 128);
  ReceiveHandle = osThreadCreate(osThread(Receive), NULL);

  /* definition and creation of Send */
  osThreadDef(Send, SendTask, osPriorityIdle, 0, 128);
  SendHandle = osThreadCreate(osThread(Send), 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_IT_RISING;
  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_IT_RISING;
  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);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI0_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(EXTI0_IRQn);

  HAL_NVIC_SetPriority(EXTI15_10_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

}

/* 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_ReceiveTask */
/**
* @brief Function implementing the Receive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_ReceiveTask */
void ReceiveTask(void const * argument)
{
  /* USER CODE BEGIN ReceiveTask */
  osEvent event;
  /* Infinite loop */
  for(;;)
  {
    event = osMessageGet(TestQueueHandle, /* 消息隊列的句柄 */ 
                          osWaitForever); /* 等待時間 一直等 */ 
    if(osEventMessage == event.status) 
    {
        printf("interrupt Key:%d\n\n", event.value.v); 
    }
    else 
    {
        printf("error: 0x%d\n", event.status); 
    }
  }
  /* USER CODE END ReceiveTask */
}

/* USER CODE BEGIN Header_SendTask */
/**
* @brief Function implementing the Send thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_SendTask */
void SendTask(void const * argument)
{
  /* USER CODE BEGIN SendTask */
  osStatus xReturn = osErrorValue;
  /* Infinite loop */
  for(;;)
  {
    // 獲取二值信號量 xSemaphore,沒獲取到則一直等待
    xReturn = osSemaphoreWait(BinarySemHandle, /* 二值信號量句柄 */ 
                               osWaitForever); /* 等待時間 */ 
    if(osOK == xReturn) 
    {
        printf("receive data:%s\n", recvBuff);
        memset(recvBuff, 0, BUFFER_SIZE);       /* 清零 */
    }
  }
  /* USER CODE END SendTask */
}

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

修改stm32f1xx_it.c

/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "cmsis_os.h"
#include <string.h>
/* USER CODE END Includes */

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

/* USER CODE END TD */

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

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define BUFFER_SIZE 256
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
static uint32_t send_data1 = 1;
static uint32_t send_data2 = 2;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

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

/* USER CODE END 0 */

/* External variables --------------------------------------------------------*/
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
extern UART_HandleTypeDef huart1;
extern TIM_HandleTypeDef htim1;

/* USER CODE BEGIN EV */
extern osMessageQId TestQueueHandle;
extern osSemaphoreId BinarySemHandle;
extern uint8_t recvBuff[BUFFER_SIZE];  //接收數(shù)據(jù)緩存
extern volatile uint8_t recvLength;  //接收一幀數(shù)據(jù)的長度
extern volatile uint8_t recvDndFlag; //一幀數(shù)據(jù)接收完成標(biāo)志
/* USER CODE END EV */

/******************************************************************************/
/*           Cortex-M3 Processor Interruption and Exception Handlers          */
/******************************************************************************/
/**
  * @brief This function handles Non maskable interrupt.
  */
void NMI_Handler(void)
{
  /* USER CODE BEGIN NonMaskableInt_IRQn 0 */

  /* USER CODE END NonMaskableInt_IRQn 0 */
  /* USER CODE BEGIN NonMaskableInt_IRQn 1 */

  /* USER CODE END NonMaskableInt_IRQn 1 */
}

/**
  * @brief This function handles Hard fault interrupt.
  */
void HardFault_Handler(void)
{
  /* USER CODE BEGIN HardFault_IRQn 0 */

  /* USER CODE END HardFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_HardFault_IRQn 0 */
    /* USER CODE END W1_HardFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Memory management fault.
  */
void MemManage_Handler(void)
{
  /* USER CODE BEGIN MemoryManagement_IRQn 0 */

  /* USER CODE END MemoryManagement_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
    /* USER CODE END W1_MemoryManagement_IRQn 0 */
  }
}

/**
  * @brief This function handles Prefetch fault, memory access fault.
  */
void BusFault_Handler(void)
{
  /* USER CODE BEGIN BusFault_IRQn 0 */

  /* USER CODE END BusFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_BusFault_IRQn 0 */
    /* USER CODE END W1_BusFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Undefined instruction or illegal state.
  */
void UsageFault_Handler(void)
{
  /* USER CODE BEGIN UsageFault_IRQn 0 */

  /* USER CODE END UsageFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_UsageFault_IRQn 0 */
    /* USER CODE END W1_UsageFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Debug monitor.
  */
void DebugMon_Handler(void)
{
  /* USER CODE BEGIN DebugMonitor_IRQn 0 */

  /* USER CODE END DebugMonitor_IRQn 0 */
  /* USER CODE BEGIN DebugMonitor_IRQn 1 */

  /* USER CODE END DebugMonitor_IRQn 1 */
}

/******************************************************************************/
/* STM32F1xx Peripheral Interrupt Handlers                                    */
/* Add here the Interrupt Handlers for the used peripherals.                  */
/* For the available peripheral interrupt handler names,                      */
/* please refer to the startup file (startup_stm32f1xx.s).                    */
/******************************************************************************/

/**
  * @brief This function handles EXTI line0 interrupt.
  */
void EXTI0_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */

  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(KEY1_Pin);
  /* USER CODE BEGIN EXTI0_IRQn 1 */

  /* USER CODE END EXTI0_IRQn 1 */
}

/**
  * @brief This function handles DMA1 channel4 global interrupt.
  */
void DMA1_Channel4_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel4_IRQn 0 */

  /* USER CODE END DMA1_Channel4_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart1_tx);
  /* USER CODE BEGIN DMA1_Channel4_IRQn 1 */

  /* USER CODE END DMA1_Channel4_IRQn 1 */
}

/**
  * @brief This function handles DMA1 channel5 global interrupt.
  */
void DMA1_Channel5_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel5_IRQn 0 */

  /* USER CODE END DMA1_Channel5_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart1_rx);
  /* USER CODE BEGIN DMA1_Channel5_IRQn 1 */

  /* USER CODE END DMA1_Channel5_IRQn 1 */
}

/**
  * @brief This function handles TIM1 update interrupt.
  */
void TIM1_UP_IRQHandler(void)
{
  /* USER CODE BEGIN TIM1_UP_IRQn 0 */

  /* USER CODE END TIM1_UP_IRQn 0 */
  HAL_TIM_IRQHandler(&htim1);
  /* USER CODE BEGIN TIM1_UP_IRQn 1 */

  /* USER CODE END TIM1_UP_IRQn 1 */
}

/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  uint32_t tmpFlag = 0;
  uint32_t temp;
  tmpFlag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //獲取IDLE標(biāo)志位
  if((tmpFlag != RESET))//idle標(biāo)志被置位
  { 
      osSemaphoreRelease(BinarySemHandle);// 給出計數(shù)信號量
      
      __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除標(biāo)志位
        
      HAL_UART_DMAStop(&huart1); //
      temp  =  __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 獲取DMA中未傳輸?shù)臄?shù)據(jù)個數(shù)   
      recvLength  =  BUFFER_SIZE - temp; //總計數(shù)減去未傳輸?shù)臄?shù)據(jù)個數(shù)炭分,得到已經(jīng)接收的數(shù)據(jù)個數(shù)
      recvDndFlag  = 1;   // 接受完成標(biāo)志位置1    
      recvLength = 0;//清除計數(shù)
      recvDndFlag = 0;//清除接收結(jié)束標(biāo)志位

      memset(recvBuff,0,recvLength);
      HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);//重新打開DMA接收桃焕,不然只能接收一次數(shù)據(jù)
     } 
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

/**
  * @brief This function handles EXTI line[15:10] interrupts.
  */
void EXTI15_10_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI15_10_IRQn 0 */

  /* USER CODE END EXTI15_10_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(KEY2_Pin);
  /* USER CODE BEGIN EXTI15_10_IRQn 1 */

  /* USER CODE END EXTI15_10_IRQn 1 */
}

/* USER CODE BEGIN 1 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    osEvent xReturn;
    uint32_t ulReturn;
    /* 進入臨界段,臨界段可以嵌套 */
    ulReturn = taskENTER_CRITICAL_FROM_ISR();
    
    if(GPIO_Pin == KEY1_Pin)
    {
        xReturn.status = osMessagePut(TestQueueHandle, /* 消息隊列的句柄 */ 
                                      send_data1,      /* 發(fā)送的消息內(nèi)容 */ 
                                      0);              /* 等待時間 0 */ 
    }
    else if(GPIO_Pin == KEY2_Pin)
    {
        xReturn.status = osMessagePut(TestQueueHandle, /* 消息隊列的句柄 */ 
                                      send_data2,      /* 發(fā)送的消息內(nèi)容 */ 
                                      0);              /* 等待時間 0 */ 
    }
    /* 退出臨界段 */
    taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
/* USER CODE END 1 */

查看打优趺:


9.2 工程代碼

鏈接:https://pan.baidu.com/s/1qEtLf2QV0DrLrQxxz2dizw?pwd=npeq 提取碼:npeq

十观堂、注意事項

用戶代碼要加在 USER CODE BEGIN NUSER CODE END N 之間,否則下次使用 STM32CubeMX 重新生成代碼后呀忧,會被刪除师痕。


? 由 Leung 寫于 2022 年 1 月 12 日

? 參考:STM32CubeMX+FreeRTOS學(xué)習(xí)筆記(二)
    野火FreeRTOS視頻與PDF教程
    STM32CubeIDE(十一):FreeRTOS選項中Disable、CMSIS_V1和CMSIS_V2的區(qū)別
    HAL庫中的 SYS Timebase Source 和 SysTick_Handler()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末而账,一起剝皮案震驚了整個濱河市胰坟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泞辐,老刑警劉巖笔横,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異咐吼,居然都是意外死亡吹缔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門锯茄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涛菠,“玉大人,你說我怎么就攤上這事撇吞∷锥常” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵牍颈,是天一觀的道長迄薄。 經(jīng)常有香客問我,道長煮岁,這世上最難降的妖魔是什么讥蔽? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮画机,結(jié)果婚禮上冶伞,老公的妹妹穿的比我還像新娘。我一直安慰自己步氏,他們只是感情好响禽,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般芋类。 火紅的嫁衣襯著肌膚如雪隆嗅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天侯繁,我揣著相機與錄音胖喳,去河邊找鬼。 笑死贮竟,一個胖子當(dāng)著我的面吹牛丽焊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咕别,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼粹懒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了顷级?” 一聲冷哼從身側(cè)響起凫乖,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弓颈,沒想到半個月后帽芽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡翔冀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年导街,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纤子。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡搬瑰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出控硼,到底是詐尸還是另有隱情泽论,我是刑警寧澤漓库,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布廷支,位于F島的核電站冯遂,受9級特大地震影響淹办,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜阅仔,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一甚脉、第九天 我趴在偏房一處隱蔽的房頂上張望猾瘸。 院中可真熱鬧误堡,春花似錦古话、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杖们。三九已至,卻和暖如春膊毁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背基跑。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工婚温, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人媳否。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓栅螟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親篱竭。 傳聞我的和親對象是個殘疾皇子力图,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

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