一庇配、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
進(jìn)行設(shè)置矾踱,選擇 TIM1
作為HAL庫的時基(除了 SysTick
外都可以)。
在基于STM32 HAL的項目中疏哗,一般需要維護(hù)的 “時基” 主要有2個:
- HAL的時基呛讲,SYS Timebase Source
- OS的時基(僅在使用OS的情況下才考慮)
而這些 “時基” 該去如何維護(hù),主要分為兩種情況考慮:
裸機運行:
可以通過SysTick
(滴答定時器)或 (TIMx
)定時器 的方式來維護(hù)SYS Timebase Source
沃斤,也就是HAL庫中的uwTick
圣蝎,這是HAL庫中維護(hù)的一個全局變量。在裸機運行的情況下衡瓶,我們一般選擇默認(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一定會報如下提示:
強烈建議用戶在使用FreeRTOS的時候瞻想,不要使用
SysTick
(滴答定時器)作為 “HAL的時基”压真,因為FreeRTOS要用,最好是要換一個D⑾铡5沃住!如果共用佃迄,潛在一定風(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
進(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)零指令嗎驱证,所以這里強制置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
在 Tasks and Queues
進(jìn)行配置。
默認(rèn)空閑任務(wù)是在系統(tǒng)無其它任務(wù)執(zhí)行時執(zhí)行聚蝶。
然后我們創(chuàng)建兩個LED任務(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: 控制塊名稱
五凿宾、LED
5.1 參數(shù)配置
在 System Core
中選擇 GPIO
設(shè)置。
在右邊圖中找到 LED 燈對應(yīng)引腳兼蕊,選擇
GPIO_Output
初厚。在
GPIO output level
中選擇 Low
輸出低電平點亮,可以添加自定義標(biāo)簽(這樣生成代碼也會根據(jù)標(biāo)簽設(shè)置引腳的宏定義)孙技。六产禾、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ù)創(chuàng)建與啟動
8.1 相關(guān)API說明
8.1.1 osThreadId
任務(wù)ID哈雏。例如楞件,對osThreadCreate
的調(diào)用返回衫生。可用作參數(shù)到osThreadTerminate
以刪除任務(wù)土浸。
/// Thread ID identifies the thread (pointer to a thread control block).
/// \note CAN BE CHANGED: \b os_thread_cb is implementation specific in every CMSIS-RTOS.
typedef TaskHandle_t osThreadId;
8.1.2 osThreadCreate
使用動態(tài)/靜態(tài)內(nèi)存的方法創(chuàng)建一個任務(wù)罪针。
函數(shù) | osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument) |
---|---|
參數(shù) |
thread_def: 引用由osThreadDef定義的任務(wù) argument: 任務(wù)入口函數(shù)形參 |
返回值 | 成功返回任務(wù)ID,失敗返回0 |
8.1.3 osThreadTerminate
刪除任務(wù)黄伊。任務(wù)被刪除后就不復(fù)存在泪酱,也不會再進(jìn)入運行態(tài)。
函數(shù) | osStatus osThreadTerminate (osThreadId thread_id) |
---|---|
參數(shù) | thread_id: 被刪除任務(wù)的ID |
返回值 | 錯誤碼 |
要想使用該函數(shù)必須在 Include parameters
中把 vTaskDelete
選擇 Enabled
來使能还最。
8.1.4 osKernelStart
在創(chuàng)建完任務(wù)的時候西篓,我們需要開啟調(diào)度器,因為創(chuàng)建僅僅是把任務(wù)添加到系統(tǒng)中憋活,還沒真正調(diào)度,并且空閑任務(wù)也沒實現(xiàn)虱黄,定時器任務(wù)也沒實現(xiàn)悦即,這些都是在開啟調(diào)度函數(shù) osKernelStart() 中實現(xiàn)的。為什么要空閑任務(wù)橱乱?因為 FreeRTOS 一旦啟動辜梳,就必須要保證系統(tǒng)中每時每刻都有一個任務(wù)處于運行態(tài)(Runing),并且空閑任務(wù)不可以被掛起與刪除泳叠,空閑任務(wù)的優(yōu)先級是最低的作瞄,以便系統(tǒng)中其他任務(wù)能隨時搶占空閑任務(wù)的 CPU 使用權(quán)。
函數(shù) | osStatus osKernelStart (void) |
---|---|
參數(shù) | 無 |
返回值 | 錯誤碼 |
8.2 示例
/* 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;
/****************** 任務(wù)句柄 ******************/
/*
* 任務(wù)句柄是一個指針危纫,用于指向一個任務(wù)宗挥,當(dāng)任務(wù)創(chuàng)建好之后,它就具有了一個任務(wù)句柄
* 以后我們要想操作這個任務(wù)都需要通過這個任務(wù)句柄种蝶,如果是自身的任務(wù)操作自己契耿,那么
* 這個句柄可以為NULL。
*/
/* 空閑任務(wù)句柄 */
osThreadId defaultTaskHandle;
/* LED1 任務(wù)句柄 */
osThreadId LED1Handle;
/* LED2 任務(wù)句柄 */
osThreadId LED2Handle;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/*
*************************************************************************
* 函數(shù)聲明
*************************************************************************
*/
/* 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 LED1_Task(void const * argument); /* LED1_Task 任務(wù)實現(xiàn) */
void LED2_Task(void const * argument); /* LED2_Task 任務(wù)實現(xiàn) */
/* 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 */
/* 創(chuàng)建任務(wù) */
/* Create the thread(s) */
/* definition and creation of defaultTask */
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
/* definition and creation of LED1 */
osThreadDef(LED1, LED1_Task, osPriorityIdle, 0, 128);
LED1Handle = osThreadCreate(osThread(LED1), NULL);
/* definition and creation of LED2 */
osThreadDef(LED2, LED2_Task, osPriorityIdle, 0, 128);
LED2Handle = osThreadCreate(osThread(LED2), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
/* 啟動任務(wù)調(diào)度 */
/* Start scheduler */
osKernelStart(); /* 啟動任務(wù)螃征,開啟調(diào)度 */
/* 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_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_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 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_LED1_Task */
/**
* @brief Function implementing the LED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_LED1_Task */
void LED1_Task(void const * argument)
{
/* USER CODE BEGIN LED1_Task */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB, LED_G_Pin);
printf("LED1_Task\n");
osDelay(1000); /* 延時 1000 個 tick */
}
/* USER CODE END LED1_Task */
}
/* USER CODE BEGIN Header_LED2_Task */
/**
* @brief Function implementing the LED2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_LED2_Task */
void LED2_Task(void const * argument)
{
/* USER CODE BEGIN LED2_Task */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB, LED_B_Pin);
printf("LED2_Task\n");
osDelay(500); /* 延時 500 個 tick */
}
/* USER CODE END LED2_Task */
}
/**
* @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 */
8.3 工程代碼
鏈接:https://pan.baidu.com/s/1hcYRL-bKEKX8i_s0vnGW0g 提取碼:ax4a
九搪桂、任務(wù)延時
9.1 相關(guān)API說明
9.1.1 osDelay
相對延時函數(shù)。用于阻塞延時盯滚,調(diào)用該函數(shù)后踢械,任務(wù)將進(jìn)入阻塞狀態(tài),進(jìn)入阻塞態(tài)的任務(wù)將讓出 CPU 資源魄藕。
函數(shù) | osStatus osDelay (uint32_t millisec) |
---|---|
參數(shù) | millisec: 毫秒數(shù) |
返回值 | 錯誤碼 |
要想使用該函數(shù)必須在 Include parameters
中把 vTaskDelay
選擇 Enabled
來使能内列。
9.1.2 osThreadTerminate
絕對延時函數(shù)。常用于較精確的周期運行任務(wù)泼疑,比如我有一個任務(wù)德绿,希望它以固定頻率定期執(zhí)行荷荤,而不受外部的影響,任務(wù)從上一次運行開始到下一次運行開始的時間間隔是絕對的移稳,而不是相對的蕴纳。
函數(shù) | osStatus osDelayUntil (uint32_t *PreviousWakeTime, uint32_t millisec) |
---|---|
參數(shù) |
PreviousWakeTime: 任務(wù)上一次離開阻塞態(tài)(被喚醒)的時刻。這個時刻被用作一個參考點來計算該任務(wù)下一次離開阻塞態(tài)的時刻 millisec: 毫秒數(shù) |
返回值 | 錯誤碼 |
要想使用該函數(shù)必須在 Include parameters
中把 vTaskDelayUntil
選擇 Enabled
來使能个粱。
9.2 示例
9.2.1 相對延時
void vTaskA( void * pvParameters )
{
while (1)
{
// ...
// 這里為任務(wù)主體代碼
// ...
/* 調(diào)用相對延時函數(shù),阻塞 1000 個 tick */
osDelay( 1000 );
}
}
9.2.2 絕對延時
注意:在使用的時候要將延時時間轉(zhuǎn)化為系統(tǒng)節(jié)拍古毛,在任務(wù)主體之前要調(diào)用延時函數(shù)。
void vTaskA( void * pvParameters )
{
/* 用于保存上次時間都许。調(diào)用后系統(tǒng)自動更新 */
static portTickType PreviousWakeTime;
/* 設(shè)置延時時間稻薇,將時間轉(zhuǎn)為節(jié)拍數(shù) */
const portTickType TimeIncrement = pdMS_TO_TICKS(1000);
/* 獲取當(dāng)前系統(tǒng)時間 */
PreviousWakeTime = osKernelSysTick();
while (1)
{
/* 調(diào)用絕對延時函數(shù),任務(wù)時間間隔為 1000 個 tick */
osDelayUntil( &PreviousWakeTime,TimeIncrement );
// ...
// 這里為任務(wù)主體代碼
// ...
}
}
十胶征、任務(wù)掛起與恢復(fù)
10.1 相關(guān)API說明
10.1.1 osThreadSuspend
掛起指定任務(wù)塞椎。被掛起的任務(wù)絕不會得到 CPU 的使用權(quán),不管該任務(wù)具有什么優(yōu)先級睛低。
函數(shù) | osStatus osThreadSuspend (osThreadId thread_id) |
---|---|
參數(shù) | thread_id: 掛起指定任務(wù)的任務(wù)ID |
返回值 | 錯誤碼 |
要想使用該函數(shù)必須在 Include parameters
中把 vTaskSuspend
選擇 Enabled
來使能案狠。
10.1.2 osThreadSuspendAll
將所有的任務(wù)都掛起。
函數(shù) | osStatus osThreadSuspendAll (void) |
---|---|
參數(shù) | 無 |
返回值 | 錯誤碼 |
10.1.3 osThreadResume
讓掛起的任務(wù)重新進(jìn)入就緒狀態(tài)钱雷,恢復(fù)的任務(wù)會保留掛起前的狀態(tài)信息骂铁,在恢復(fù)的時候根據(jù)掛起時的狀態(tài)繼續(xù)運行。如果被恢復(fù)任務(wù)在所有就緒態(tài)任務(wù)中罩抗,處于最高優(yōu)先級列表的第一位拉庵,那么系統(tǒng)將進(jìn)行任務(wù)上下文的切換。
函數(shù) | osStatus osThreadResume (osThreadId thread_id) |
---|---|
參數(shù) | thread_id: 掛起指定任務(wù)的任務(wù)ID |
返回值 | 錯誤碼 |
10.1.4 osThreadResume
讓掛起的任務(wù)重新進(jìn)入就緒狀態(tài)套蒂,恢復(fù)的任務(wù)會保留掛起前的狀態(tài)信息钞支,在恢復(fù)的時候根據(jù)掛起時的狀態(tài)繼續(xù)運行。如果被恢復(fù)任務(wù)在所有就緒態(tài)任務(wù)中泣懊,處于最高優(yōu)先級列表的第一位伸辟,那么系統(tǒng)將進(jìn)行任務(wù)上下文的切換豺总∷谷耄可用在中斷服務(wù)程序中秩铆。
函數(shù) | osStatus osThreadResume (osThreadId thread_id) |
---|---|
參數(shù) | thread_id: 掛起指定任務(wù)的任務(wù)ID |
返回值 | 錯誤碼 |
要想在中斷服務(wù)程序中使用該函數(shù)必須在 Include parameters
中把 vTaskResumeFromISR
選擇 Enabled
來使能魂务。
10.1.5 osThreadResumeAll
將所有的任務(wù)都恢復(fù)盏档。
函數(shù) | osStatus osThreadResumeAll (void) |
---|---|
參數(shù) | 無 |
返回值 | 錯誤碼 |
10.2 示例
10.2.1 掛起
/**************************** 任務(wù)句柄 ********************************/
/*
* 任務(wù)句柄是一個指針虱痕,用于指向一個任務(wù)域仇,當(dāng)任務(wù)創(chuàng)建好之后葫哗,它就具有了一個任務(wù)句柄
* 以后我們要想操作這個任務(wù)都需要通過這個任務(wù)句柄匈辱,如果是自身的任務(wù)操作自己振湾,那么
* 這個句柄可以為 NULL。
*/
osThreadId LED1Handle;/* LED 任務(wù)句柄 */
static void KEY_Task(void* parameter)
{
while (1)
{
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
{
/* K1 被按下 */
printf("掛起 LED 任務(wù)亡脸!\n");
osThreadSuspend(LED1Handle);/* 掛起 LED 任務(wù) */
}
osDelay(20);/* 延時 20 個 tick */
}
}
10.2.2 恢復(fù)
/**************************** 任務(wù)句柄 ********************************/
/*
* 任務(wù)句柄是一個指針押搪,用于指向一個任務(wù)树酪,當(dāng)任務(wù)創(chuàng)建好之后,它就具有了一個任務(wù)句柄
* 以后我們要想操作這個任務(wù)都需要通過這個任務(wù)句柄大州,如果是自身的任務(wù)操作自己续语,那么
* 這個句柄可以為 NULL。
*/
osThreadId LED1Handle;/* LED 任務(wù)句柄 */
static void KEY_Task(void* parameter)
{
while (1)
{
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
{
/* K2 被按下 */
printf("恢復(fù) LED 任務(wù)厦画!\n");
osThreadResume(LED1Handle);/* 恢復(fù) LED 任務(wù)疮茄! */
}
osDelay(20);/* 延時 20 個 tick */
}
}
10.2.3 中斷服務(wù)中恢復(fù)
使用 xTaskResumeFromISR()的時候有幾個需要注意的地方:
- 當(dāng)函數(shù)的返回值為 pdTRUE 時:恢復(fù)運行的任務(wù)的優(yōu)先級等于或高于正在運行的任務(wù),表明在中斷服務(wù)函數(shù)退出后必 須進(jìn)行一次上下文切換 根暑, 使用 portYIELD_FROM_ISR() 進(jìn)行上下文切換力试。當(dāng)函數(shù)的返回值為 pdFALSE 時:恢復(fù)運行的任務(wù)的優(yōu)先級低于當(dāng)前正在運行的任務(wù),表明在中斷服務(wù)函數(shù)退出后不需 要進(jìn)行上下文切換排嫌。
- xTaskResumeFromISR() 通常被認(rèn)為是一個危險的函數(shù)畸裳,因為它的調(diào)用并非是固定的,中斷可能隨時來來臨淳地。所以 xTaskResumeFromISR()不能用于任務(wù)和中斷間的同步躯畴,如果中斷恰巧在任務(wù)被掛起之前到達(dá),這就會導(dǎo)致一次中斷丟失(任務(wù)還沒有掛起薇芝,調(diào)用 xTaskResumeFromISR()函數(shù)是沒有意義的,只能等下一次中斷)丰嘉。這種情況下夯到,可以使用信號量或者任務(wù)通知來同步就可以避免這種情況。
void vAnExampleISR( void )
{
BaseType_t xYieldRequired;
/* 恢復(fù)被掛起的任務(wù) */
xYieldRequired = osThreadResume( xHandle );
if ( xYieldRequired == pdTRUE )
{
/* 執(zhí)行上下文切換饮亏, ISR 返回的時候?qū)⑦\行另外一個任務(wù) */
portYIELD_FROM_ISR();
}
}
10.2.4 全部恢復(fù)
osThreadResumeAll 函數(shù)的使用方法很簡單耍贾,但是要注意,調(diào)用了多少次 osThreadSuspendAll() 函數(shù)就必須同樣調(diào)用多少次 osThreadResumeAll() 函數(shù)路幸。
void vDemoFunction( void )
{
osThreadSuspendAll();
/* 處理 xxx 代碼 */
osThreadSuspendAll();
/* 處理 xxx 代碼 */
osThreadSuspendAll();
/* 處理 xxx 代碼 */
osThreadResumeAll();
osThreadResumeAll();
osThreadResumeAll();
}
十一荐开、獲取任務(wù)狀態(tài)
11.1 相關(guān)API說明
11.1.1 osThreadGetState
獲取任務(wù)當(dāng)前狀態(tài)。
函數(shù) | osThreadState osThreadGetState(osThreadId thread_id) |
---|---|
參數(shù) | thread_id: 任務(wù)ID |
返回值 | 以下值 |
/* Thread state returned by osThreadGetState */
typedef enum {
osThreadRunning = 0x0, /* A thread is querying the state of itself, so must be running. */
osThreadReady = 0x1 , /* The thread being queried is in a read or pending ready list. */
osThreadBlocked = 0x2, /* The thread being queried is in the Blocked state. */
osThreadSuspended = 0x3, /* The thread being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
osThreadDeleted = 0x4, /* The thread being queried has been deleted, but its TCB has not yet been freed. */
osThreadError = 0x7FFFFFFF
} osThreadState;
十二简肴、注意事項
用戶代碼要加在 USER CODE BEGIN N
和 USER CODE END N
之間晃听,否則下次使用 STM32CubeMX 重新生成代碼后,會被刪除砰识。
? 由 Leung 寫于 2021 年 12 月 20 日
? 參考:STM32CubeMX之FreeRTOS
STM32CUBEMX的freertos一般使用方法筆記
STM32CubeIDE(十一):FreeRTOS選項中Disable能扒、CMSIS_V1和CMSIS_V2的區(qū)別
HAL庫中的 SYS Timebase Source 和 SysTick_Handler()