一峰档、FreeRTOS簡介
FreeRTOS 是一個可裁剪败匹、可剝奪型的多任務(wù)內(nèi)核,而且沒有任務(wù)數(shù)限制讥巡。FreeRTOS 提供了實時操作系統(tǒng)所需的所有功能掀亩,包括資源管理、同步尚卫、任務(wù)通信等归榕。
FreeRTOS 是用 C 和匯編來寫的,其中絕大部分都是用 C 語言編寫的吱涉,只有極少數(shù)的與處理器密切相關(guān)的部分代碼才是用匯編寫的刹泄,F(xiàn)reeRTOS 結(jié)構(gòu)簡潔外里,可讀性很強(qiáng)!最主要的是非常適合初次接觸嵌入式實時操作系統(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
進(jìn)行設(shè)置,選擇 TIM1
作為HAL庫的時基(除了 SysTick
外都可以)畏腕。
在基于STM32 HAL的項目中缴川,一般需要維護(hù)的 “時基” 主要有2個:
- HAL的時基,SYS Timebase Source
- OS的時基(僅在使用OS的情況下才考慮)
而這些 “時基” 該去如何維護(hù)描馅,主要分為兩種情況考慮:
裸機(jī)運行:
可以通過SysTick
(滴答定時器)或 (TIMx
)定時器 的方式來維護(hù)SYS Timebase Source
,也就是HAL庫中的uwTick
而线,這是HAL庫中維護(hù)的一個全局變量铭污。在裸機(jī)運行的情況下,我們一般選擇默認(rèn)的SysTick
(滴答定時器) 方式即可膀篮,也就是直接放在SysTick_Handler()
中斷服務(wù)函數(shù)中來維護(hù)嘹狞。-
帶OS運行:
前面提到的SYS Timebase Source
是STM32的HAL庫中的新增部分,主要用于實現(xiàn)HAL_Delay()
以及作為各種 timeout 的時鐘基準(zhǔn)誓竿。在使用了OS(操作系統(tǒng))之后磅网,OS的運行也需要一個時鐘基準(zhǔn)(簡稱“時基”),來對任務(wù)和時間等進(jìn)行管理筷屡。而OS的這個 時基 一般也都是通過
SysTick
(滴答定時器) 來維護(hù)的涧偷,這時就需要考慮 “HAL的時基” 和 “OS的時基” 是否要共用SysTick
(滴答定時器) 了簸喂。如果共用SysTick,當(dāng)我們在CubeMX中選擇啟用FreeRTOS之后燎潮,在生成代碼時喻鳄,CubeMX一定會報如下提示:
強(qiáng)烈建議用戶在使用FreeRTOS的時候,不要使用
SysTick
(滴答定時器)作為 “HAL的時基”确封,因為FreeRTOS要用除呵,最好是要換一個!WΥ颜曾!如果共用,潛在一定風(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等單片機(jī)時沒必要選擇RTOS v2荠雕,更高的兼容性背后時更加冗余的代碼,理解起來比較困難驶赏。
在 Config parameters
進(jìn)行具體參數(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è)置此宏睦疫,而且要先將消息隊列和信號量進(jìn)行注冊,只有注冊了的隊列和信號量才會在內(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)零指令嗎圣勒,所以這里強(qiáng)制置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)建任務(wù)Task
要想使用任務(wù)通知必須在 Config parameters
中把 USE_TASK_NOTIFICATIONS
選擇 Enabled
來使能善炫。
我們創(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: 控制塊名稱
五撒妈、KEY
5.1 參數(shù)配置
在 System Core
中選擇 GPIO
設(shè)置。
在右邊圖中找到按鍵對應(yīng)引腳排监,選擇
GPIO_Input
狰右。六、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 生成代碼
八、任務(wù)通知
8.1 基本概念
FreeRTOS 從 V8.2.0 版本開始提供任務(wù)通知這個功能谷暮,每個任務(wù)都有 一個 32 位 的通知值蒿往,在大多數(shù)情況下,任務(wù)通知可以 替代二值信號量湿弦、計數(shù)信號量瓤漏、事件組,也可以替代長度為 1 的隊列(可以保存一個 32 位整數(shù)或指針值)颊埃。
相對于以前使用 FreeRTOS 內(nèi)核通信的資源蔬充,必須創(chuàng)建隊列、二進(jìn)制信號量竟秫、計數(shù)信號量或事件組的情況娃惯,使用任務(wù)通知顯然更靈活。按照 FreeRTOS 官方的說法肥败,使用任務(wù)通知比通過信號量等 ICP 通信方式解除阻塞的任務(wù)要快 45%趾浅,并且更加省 RAM 內(nèi)存空間(使用 GCC 編譯器,-o2 優(yōu)化級別)馒稍,任務(wù)通知的使用無需創(chuàng)建隊列皿哨。 想要使用任務(wù)通知,必須將
FreeRTOSConfig.h
中的宏定義configUSE_TASK_NOTIFICATIONS
設(shè)置為1
纽谒,其實FreeRTOS 默認(rèn)是為 1 的证膨,所以任務(wù)通知是默認(rèn)使能的。
FreeRTOS 提供以下幾種方式發(fā)送通知給任務(wù) :
- 發(fā)送通知給任務(wù)鼓黔, 如果有通知未讀央勒,不覆蓋通知值。
- 發(fā)送通知給任務(wù)澳化,直接覆蓋通知值崔步。
- 發(fā)送通知給任務(wù),設(shè)置通知值的一個或者多個位 缎谷,可以當(dāng)做事件組來使用井濒。
- 發(fā)送通知給任務(wù),遞增通知值列林,可以當(dāng)做計數(shù)信號量使用瑞你。
通過對以上任務(wù)通知方式的合理使用,可以在一定場合下替代 FreeRTOS 的信號量希痴,隊列者甲、事件組等。
當(dāng)然砌创,凡是都有利弊过牙,不然的話 FreeRTOS 還要內(nèi)核的 IPC 通信機(jī)制干嘛甥厦,消息通知雖然處理更快纺铭,RAM 開銷更小寇钉,但也有以下限制 :
- 只能有一個任務(wù)接收通知消息,因為必須指定接收通知的任務(wù)舶赔。
- 只有等待通知的任務(wù)可以被阻塞扫倡,發(fā)送通知的任務(wù),在任何情況下都不會因為發(fā)送失敗而進(jìn)入阻塞態(tài)竟纳。
8.2 運作機(jī)制
由于任務(wù)通知的數(shù)據(jù)結(jié)構(gòu)包含在任務(wù)控制塊中撵溃,只要任務(wù)存在,任務(wù)通知數(shù)據(jù)結(jié)構(gòu)就已經(jīng)創(chuàng)建完畢锥累,可以直接使用缘挑,所以使用的時候很是方便。
任務(wù)通知可以在任務(wù)中向指定任務(wù)發(fā)送通知桶略,也可以在中斷中向指定任務(wù)發(fā)送通知语淘,F(xiàn)reeRTOS 的每個任務(wù)都有一個 32 位的通知值,任務(wù)控制塊中的成員變量 ulNotifiedValue 就是這個通知值际歼。只有在任務(wù)中可以等待通知惶翻,而不允許在中斷中等待通知。如果任務(wù)在等待的通知暫時無效鹅心,任務(wù)會根據(jù)用戶指定的阻塞超時時間進(jìn)入阻塞狀態(tài)吕粗,我們可以將等待通知的任務(wù)看作是消費者;其它任務(wù)和中斷可以向等待通知的任務(wù)發(fā)送通知旭愧,發(fā)送通知的任務(wù)和中斷服務(wù)函數(shù)可以看作是生產(chǎn)者颅筋,當(dāng)其他任務(wù)或者中斷向這個任務(wù)發(fā)送任務(wù)通知,任務(wù)獲得通知以后输枯,該任務(wù)就會從阻塞態(tài)中解除议泵,這與 FreeRTOS 中內(nèi)核的其他通信機(jī)制一致。
九用押、相關(guān)API說明
9.1 osSignalSet
向指定的任務(wù)發(fā)送一個任務(wù)通知肢簿,帶有通知值并且用戶可以指定通知值的發(fā)送方式。該函數(shù)可以在中斷函數(shù)中使用蜻拨。
函數(shù) | int32_t osSignalSet (osThreadId thread_id, int32_t signal) |
---|---|
參數(shù) |
thread_id: 接收通知的任務(wù)ID signal: 任務(wù)通知值池充,一般按位操作數(shù)字,一個事件用一個類似這樣的值表示(即0x0001,0x0002,0x0004,0x0008,0x0010......) |
返回值 | 錯誤碼 |
9.2 osSignalWait
用于實現(xiàn)等待任務(wù)通知缎讼,根據(jù)用戶指定的參數(shù)的不同收夸,可以靈活的用于實現(xiàn)輕量級的消息隊列隊列、二值信號量血崭、計數(shù)信號量和事件組功能卧惜,并帶有超時等待厘灼。函數(shù)不允許在中斷函數(shù)中使用。
函數(shù) | osEvent osSignalWait (int32_t signals, uint32_t millisec) |
---|---|
參數(shù) |
signals: 接收完成后等待被清零的數(shù)據(jù)位咽瓷,比如一個任務(wù)狀態(tài)切換设凹,由四個不同的事件分別影響著,通知osSignalSet中的signal分別為0x0001,0x0002,0x0004,0x0008,那么接收端osSignalWait 中的signals應(yīng)該為0x000F(0x0001|0x0002|0x0004|0x0008) millisec: 等待超時時間茅姜,單位為系統(tǒng)節(jié)拍周期闪朱。宏 pdMS_TO_TICKS 用于將單位毫秒轉(zhuǎn)化為系統(tǒng)節(jié)拍數(shù) |
返回值 | 錯誤碼 |
十、示例
按下按鍵1觸發(fā)任務(wù)通知到接收任務(wù)1Receive1Task钻洒,按下按鍵2觸發(fā)任務(wù)通知到接收任務(wù)2Receive2Task奋姿。
/* 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 SendHandle;
osThreadId Receive1Handle;
osThreadId Receive2Handle;
/* USER CODE BEGIN PV */
#define KEY1_EVENT (0x01 << 0)//設(shè)置事件掩碼的位 0
#define KEY2_EVENT (0x01 << 1)//設(shè)置事件掩碼的位 1
/* 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 SendTask(void const * argument);
void Receive1Task(void const * argument);
void Receive2Task(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 */
/* USER CODE END 2 */
/* 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 Send */
osThreadDef(Send, SendTask, osPriorityIdle, 0, 128);
SendHandle = osThreadCreate(osThread(Send), NULL);
/* definition and creation of Receive1 */
osThreadDef(Receive1, Receive1Task, osPriorityIdle, 0, 128);
Receive1Handle = osThreadCreate(osThread(Receive1), NULL);
/* definition and creation of Receive2 */
osThreadDef(Receive2, Receive2Task, osPriorityIdle, 0, 128);
Receive2Handle = osThreadCreate(osThread(Receive2), 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_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 */
/* Infinite loop */
for(;;)
{
//如果 KEY1 被按下
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_SET)
{
printf("KEY1 down\n");
/* 觸發(fā)一個事件 1 */
osSignalSet(Receive1Handle, KEY1_EVENT);
}
//如果 KEY2 被按下
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_SET)
{
printf("KEY2 down\n");
/* 觸發(fā)一個事件 2 */
osSignalSet(Receive2Handle, KEY2_EVENT);
}
osDelay(100);
}
/* USER CODE END SendTask */
}
/* USER CODE BEGIN Header_Receive1Task */
/**
* @brief Function implementing the Receive1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Receive1Task */
void Receive1Task(void const * argument)
{
/* USER CODE BEGIN Receive1Task */
osEvent event;
/* Infinite loop */
for(;;)
{
event = osSignalWait(KEY1_EVENT, /* 接收任務(wù)感興趣的事件 */
osWaitForever); /* 指定超時事件,一直等 */
if(event.status == osEventSignal) //如果接收到通知
{
if(event.value.signals & KEY1_EVENT)//接收的通知為KEY1_EVENT
{
printf("Receive1Task\n");
}
}
}
/* USER CODE END Receive1Task */
}
/* USER CODE BEGIN Header_Receive2Task */
/**
* @brief Function implementing the Receive2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Receive2Task */
void Receive2Task(void const * argument)
{
/* USER CODE BEGIN Receive2Task */
osEvent event;
/* Infinite loop */
for(;;)
{
event = osSignalWait(KEY2_EVENT, /* 接收任務(wù)感興趣的事件 */
osWaitForever); /* 指定超時事件,一直等 */
if(event.status == osEventSignal) //如果接收到通知
{
if(event.value.signals & KEY2_EVENT)//接收的通知為KEY2_EVENT
{
printf("Receive2Task\n");
}
}
}
/* USER CODE END Receive2Task */
}
/**
* @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/1qS0uYFJ1YgMi2G0nAVaB8g 提取碼:r53a
十二素标、注意事項
用戶代碼要加在 USER CODE BEGIN N
和 USER CODE END N
之間称诗,否則下次使用 STM32CubeMX 重新生成代碼后,會被刪除头遭。
? 由 Leung 寫于 2022 年 1 月 5 日
? 參考:CMSIS中的FreeRTOS第一篇——任務(wù)通知:osSignalSet和osSignalWait
FreeRTOS記錄(五寓免、FreeRTOS任務(wù)通知)
STM32CubeIDE(十一):FreeRTOS選項中Disable、CMSIS_V1和CMSIS_V2的區(qū)別
HAL庫中的 SYS Timebase Source 和 SysTick_Handler()