一、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 版本足以阱飘。
- FreeRTOS官網(wǎng):http://www.freertos.org/
- 代碼托管網(wǎng)站:https://sourceforge.net/projects/freertos/files/FreeRTOS/
二、新建工程
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個:
- HAL的時基儒将,SYS Timebase Source
- 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í)行:
- 保存當(dāng)前處理機狀態(tài)信息
- 載入異常或中斷處理函數(shù)到 PC 寄存器
- 把控制權(quán)轉(zhuǎn)交給處理函數(shù)并開始執(zhí)行
- 當(dāng)處理函數(shù)執(zhí)行完成時较解,恢復(fù)處理器狀態(tài)信息
- 從異承蠹玻或中斷中返回到前一個程序執(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 N
和 USER 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()