整個專欄主要是博主結合自身對FreeRTOS的實戰學習以及源碼分析,基於STM32F767 Nucleo-144平台,在CubeIDE下進行開發,結合官方的HAL庫,將硬件環節的問題減少到最小,將精力主要放在RTOS的學習上.
相關文章
【FreeRTOS實戰匯總】小白博主的RTOS學習實戰快速進階之路(持續更新)
1 FreeRTOS
FreeRTOS是免費的嵌入式實時系統,可以訪問官網,至少目前是免費,並且社區相當活躍,但是以后會不會被割韭菜就不清楚了,所以感覺還是支持國產比較好,大家可以參考一下RT-Thread;
1.1 獲取源碼
我們可以登陸到官網下載源碼的壓縮包,如下圖所示;
FreeRTOS還將源碼托管到github上,登陸官網可以看到下圖的兩個倉庫;
- FreeRTOS-Kernel:這個倉庫是RTOS最核心的東西,很純凈,包括調度算法,信號量,內存管理等等,它同時作為一個子模塊存在於FreeRTOS倉庫中;
- FreeRTOS:這個倉庫是比較全的源碼,除了核心的部分,還包括對各個芯片平台的支持以及各個主流IDE的demo,因此如果要移植的話,主要還是下載這個源碼;
但是本文暫不會介紹如何移植RTOS,在遠古時期,因為第三方的支持力度不夠,因此有一些平台只能自己移植,現在第三方的支持相當於給力,直接拿來用即可.
1.2 源碼結構
自動生成的代碼中找到FreeRTOS的源碼如下;
對於相應的文件夾和源碼文件做一下介紹;
CMSIS_RTOS_V2
:這是API的版本,對應的還有CMSIS_RTOS_V1;portable
:這里主要是移植的時候需要修改的一些文件,針對不同的MCU以及不同的編譯器,需要對這塊進行修改;portmacro.h
:定義編譯器相關的數據類型和中斷處理的宏定義;port.c
:實現任務的堆棧初始化、systick
和任務上下文切換;
MemMang
:內存管理的文件,目前主要用heap_1.c
,heap_2.c
,heap_3.c
,heap_4.c
,heap_5.c
;list.c
:雙向鏈表的實現,主要供給內核調度器使用;queue.c
: 隊列的實現,主要支持中斷環境和信號量控制;croutine.c
:任務使用同一個堆棧,這樣使得減小RAM的使用,但是在使用上會受到相當大的限制;task.c
:每個任務都有各自的獨立堆棧,支持完全的搶占式調度;
2 CubeMX 整合 RTOS
- 在配置工程的時候或者打開后綴名為
.ioc
的cubemx
配置文件; - 點擊菜單欄
Pinout&Configuration
; - 點擊
Middleware
選擇FREERTOS
;
具體如下圖所示;
最終生成的文件結構如下圖所示;
這時候已經將FreeRTOS集成到工程中了,*****,簡直不能再方便了;
3 新建RTOS任務
直接生成的工程中,系統已經創建好了一個任務StartDefaultTask
;
整體的main.c
代碼如下;
#include "main.h"
#include "cmsis_os.h"
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 128 * 4
};
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
void StartDefaultTask(void *argument);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
osKernelInitialize();
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* Start scheduler */
osKernelStart();
/* We should never get here as control is now taken by the scheduler */
while (1)
{
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage */
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
/** Initializes the CPU, AHB and APB busses clocks */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
}
void StartDefaultTask(void *argument)
{
for(;;)
{
osDelay(1);
}
}
void Error_Handler(void)
{
}
#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)
{
}
#endif /* USE_FULL_ASSERT */
這里可以看到整體的流程為兩部分HAL層和OS層;
HAL:
- 硬件初始化
HAL_Init
; - 時鍾配置
SystemClock_Config
; - BSP初始化
MX_GPIO_Init
;
OS:
- 內核初始化
osKernelInitialize
; - 創建任務/線程
osThreadNew
; - 任務調度
osKernelStart
整體流程圖如下所示;
從OS的設計上來講,`Thread`擁有自己的`id`,並且可以分配`timer` 並且擁有一個類似線程的任務函數或者稱為任務回調函數(可能不太嚴謹,下面統一稱之為任務回調函數),因此這里定義一個FreeRTOS任務需要三點;
osThreadId_t defaultTaskHandle;
定義一個變量,后面創建任務會分配一個id
作為該任務的唯一標識符/身份證;osThreadAttr_t defaultTask_attributes;
作為定義一個任務時的參數,包括任務的優先級,所需要分配的棧空間大小.以及任務的名字等等;具體結構體osThreadAttr_t
如下所示;
typedef struct {
const char *name; ///< name of the thread
uint32_t attr_bits; ///< attribute bits
void *cb_mem; ///< memory for control block
uint32_t cb_size; ///< size of provided memory for control block
void *stack_mem; ///< memory for stack
uint32_t stack_size; ///< size of stack
osPriority_t priority; ///< initial thread priority (default: osPriorityNormal)
TZ_ModuleId_t tz_module; ///< TrustZone module identifier
uint32_t reserved; ///< reserved (must be 0)
} osThreadAttr_t;
- 創建任務回調函數,這里有一個生命周期的問題,通常在任務中放一個死循環,以便於任務保持存活,可以被一直調度;
void StartDefaultTask(void *argument)
{
for(;;)
{
osDelay(1);
}
}
最后,調用osThreadNew
創建任務;該函數聲明如下所示;
osThreadId_t osThreadNew ( osThreadFunc_t func,
void *argument,
const osThreadAttr_t *attr);
4 總結
通過cube自動生成了一個FREERTOS工程,簡單分析了一下如何創建一個任務,后續需要在實踐中深入到源碼中學習FreeRTOS
;