什么是任务
在FreeRTOS中,任务(Task)是操作系统中最基本的执行单元。任务可以理解为一个独立的执行线程,它具有自己的堆栈空间和程序计数器,可以独立运行、被调度和管理。
特点和属性:
-
独立性:
- 每个任务在执行时是独立的,拥有自己的堆栈空间,这使得任务之间的数据和执行状态相互独立,互不影响。
-
优先级:
- 每个任务都有一个优先级,通过优先级调度算法决定何时运行。在FreeRTOS中,优先级数值越低,优先级越高(0是最高优先级)。
-
堆栈:
- 每个任务都有自己的堆栈空间,用于存储局部变量、函数调用的返回地址以及任务的状态信息。堆栈的大小可以根据任务的需求进行配置。
-
任务函数:
- 每个任务都有一个任务函数,它定义了任务的行为和操作。任务函数通常是一个无限循环结构,任务在循环中执行特定的操作,如传感器读取、数据处理等。
-
调度:
- FreeRTOS会根据任务的优先级和调度算法来决定下一个运行的任务,任务可以被挂起、恢复或删除,以响应系统的需求。
任务在FreeRTOS下创建及实现的步骤
以创建两个任务:一个任务让Led灯闪烁,第二个定时的任务每隔3s让串口打印 Hello World为例
1,首先必须包含头文件
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
// 假设以下函数用于LED控制和串口操作,具体实现需要根据实际情况自行实现
void LED_Init();
void LED_Toggle();
void vSerialPutString(const char *pcString);
2,定义函数任务
// LED控制任务函数
void vTaskLED(void *pvParameters) {
(void) pvParameters;
// 初始化LED
LED_Init();
while (1) {
// 控制LED闪烁
LED_Toggle();
// 暂停500ms,即LED每秒闪烁2次
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// 定时打印任务函数
void vTaskPrint(void *pvParameters) {
(void) pvParameters;
while (1) {
// 每隔3秒打印一次"Hello World"
vSerialPutString("Hello World\r\n");
vTaskDelay(pdMS_TO_TICKS(3000));
}
}
3,在main函数中创建任务并启动调度器
int main() {
// 初始化硬件等操作
// 创建LED控制任务
xTaskCreate(vTaskLED, // 任务函数
"TaskLED", // 任务名称
configMINIMAL_STACK_SIZE, // 任务堆栈大小
NULL, // 任务参数
1, // 任务优先级,1是较高的优先级
NULL); // 不需要返回任务句柄
// 创建定时打印任务
xTaskCreate(vTaskPrint, // 任务函数
"TaskPrint", // 任务名称
configMINIMAL_STACK_SIZE, // 任务堆栈大小
NULL, // 任务参数
2, // 任务优先级,较低的优先级
NULL); // 不需要返回任务句柄
// 启动FreeRTOS调度器
vTaskStartScheduler();
// 如果启动失败,可能会出错
while (1) {}
return 0;
}
注意事项
-
在实际应用中,需要根据具体的硬件和接口情况来实现LED控制和串口操作的具体功能。
-
确保在调用
vTaskStartScheduler()
之前,所有的硬件初始化和配置已经完成,包括时钟初始化、外设初始化等。同时,任务的创建一定要在相关硬件和系统配置初始化之后,在调度器之前。
-
调整任务的堆栈大小和优先级根据实际需求和系统资源进行合理设置。
-
FreeRTOS中任务函数不应该返回,可以使用
vTaskDelete(NULL)
或者taskEXIT()
等函数显式删除任务。
xTaskCreate()
任务创建函数参数分析
- 任务函数 (
vTaskFunction
):这是实际执行任务操作的函数。 - 任务名称 (
TaskName
):用于调试目的,可以在调试器中看到任务的名称。 - 任务堆栈大小 (
configMINIMAL_STACK_SIZE
):任务需要的堆栈大小,具体大小根据任务所需的内存和RTOS的配置而定。 - 任务参数 (
NULL
):传递给任务函数的参数,如果任务函数不需要参数,可以设为NULL
。 - 任务优先级 (
1
):任务的优先级,数字越小优先级越高,0为最高优先级。 - 任务句柄 (
NULL
):用来接收任务句柄的指针,通常在创建任务时不需要返回任务句柄,所以设为NULL
。
BaseType_t xTaskCreateResult;
这是一个变量的声明,用于存储 xTaskCreate()
函数的返回值。具体来说,xTaskCreate()
函数返回一个 BaseType_t
类型的值,用于表示任务是否成功创建。通常情况下,这个返回值可以是 pdPASS
或 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
等,具体取决于任务创建过程中是否发生了错误。
pdPASS
表示任务成功创建。- 其他值可能表示任务创建失败,原因可能是内存分配失败等。
使用 xTaskCreateResult
可以检查任务是否成功创建,例如:
BaseType_t xTaskCreateResult;
xTaskCreateResult = xTaskCreate(...);
if (xTaskCreateResult != pdPASS) {
// 处理任务创建失败的情况
}
任务句柄(Task Handle)
任务句柄是一个指向任务控制块的指针,通过任务句柄可以在运行时管理任务,例如挂起任务、删除任务等。在 FreeRTOS 中,任务句柄通常是 TaskHandle_t
类型的变量,它可以用来标识和操作特定的任务。
当调用 xTaskCreate()
函数创建任务时,可以通过传递一个 TaskHandle_t *pxCreatedTask
参数来接收任务的句柄。如果不需要接收任务句柄,可以将这个参数设为 NULL
。
示例中的任务创建代码可以修改为如下形式:
TaskHandle_t xTaskHandle;
xTaskCreate(vTaskFunction, // 任务函数
"TaskName", // 任务名称,用于调试
configMINIMAL_STACK_SIZE, // 任务堆栈大小
NULL, // 传递给任务函数的参数,这里不传递任何参数
1, // 任务优先级,数字越小优先级越高,0为最高优先级
&xTaskHandle); // 任务句柄,用来接收任务句柄
// 检查任务是否创建成功
if (xTaskHandle == NULL) {
// 处理任务创建失败的情况
}
在这个例子中,xTaskHandle
将会存储新创建的任务的句柄,可以在后续的代码中使用该句柄来操作和管理任务。
启动FerrRTOS调度器
调用 vTaskStartScheduler()
函数来启动FreeRTOS的任务调度器。一旦调度器启动,RTOS将开始管理任务并根据任务的优先级和调度算法来调度任务的执行。
任务在CMSIS-RTOS下的创建与实现
void MX_FREERTOS_Init(void) {
......
/* Create the thread(s) */
/* definition and creation of defaultTask */
/**
* @brief 用于创建一个线程(任务)并启动它
* @param osThreadDef() 是一个宏,用于定义一个线程(任务)的模板。
* defaultTask 是线程的名称,用于标识这个线程。
* StartDefaultTask 是线程的入口函数(任务函数),即线程创建后会执行的第一个函数。
* osPriorityNormal 指定了线程的优先级,这里是正常优先级。
* 0 是线程的实例数,表示创建一个线程。
* 128 是线程的堆栈大小,单位是字节。
*
* osThreadCreate() 函数用于根据之前定义的线程模板创建一个线程。
* osThread(defaultTask) 通过 osThread() 宏将线程名称 defaultTask 转换为 osThreadDef_t 类型,以便 osThreadCreate() 函数使用。
* NULL 是传递给线程函数的参数,这里不传递任何参数。
* defaultTaskHandle 是一个 osThreadId 类型的变量,用于存储创建的线程(任务)的句柄(标识符)。
* @retval
*/
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
/* definition and creation of Task1 */
osThreadDef(Task1, StartTask1, osPriorityIdle, 0, 128);
Task1Handle = osThreadCreate(osThread(Task1), NULL);
/* definition and creation of Task2 */
osThreadDef(Task2, StartTask2, osPriorityIdle, 0, 128);
Task2Handle = osThreadCreate(osThread(Task2), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
}
/* 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 StartDefaultTask */
HAL_UART_Receive_IT(&huart1,(uint8_t *)RxTemp, REC_LENGTH);
/* Infinite loop */
for(;;)
{
osDelay(1);//osDelay函数会释放资源使任务进入阻塞状态
}
/* USER CODE END StartDefaultTask */
}
/* USER CODE BEGIN Header_StartTask1 */
/**
* @brief Function implementing the Task1 thread.即定义任务函数
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask1 */
void StartTask1(void const * argument)
{
/* USER CODE BEGIN StartTask1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOC, Led_Red_Pin);
//osDelay(1000);//延迟1000个tick
vTaskDelay(pdMS_TO_TICKS(500));
}
/* USER CODE END StartTask1 */
}
/* USER CODE BEGIN Header_StartTask2 */
/**
* @brief Function implementing the Task2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask2 */
void StartTask2(void const * argument)
{
/* USER CODE BEGIN StartTask2 */
/* Infinite loop */
for(;;)
{
/* HAL_GPIO_TogglePin(GPIOC, Led_Green_Pin);
printf("Task2 Running\r\n");
if (HAL_GPIO_ReadPin(Key1_GPIO_Port, Key1_Pin) == GPIO_PIN_RESET)
{
printf("挂起Task2 任务!\r\n");
osThreadSuspend(Task2Handle);
}
if (HAL_GPIO_ReadPin(Key2_GPIO_Port, Key2_Pin) == GPIO_PIN_RESET)
{
printf("恢复Task2 任务!\r\n");
osThreadResume(Task2Handle);
}*/
printf("Hello World\r\n");
vTaskDelay(pdMS_TO_TICKS(3000));
}
/* USER CODE END StartTask2 */
}
......
注:上述代码是在STM32CubeMX中进行配置后生成的,在freertos.c文件中,主要是进行了任务函数的定义与创建
任务的调度依旧是在main.c文件中实现
vTaskDelay()函数和osDelay()函数的区别
vTaskDelay()
函数是FreeRTOS中用于任务延时的函数,参数单位是FreeRTOS的时基单位(Ticks)。- 在任务中调用
vTaskDelay()
函数会使得当前任务进入阻塞状态,直到指定的时间延时结束,然后再继续执行任务。 - 在调用
vTaskDelay()
函数时,当前任务将释放处理器资源,允许其他任务执行,直到延时时间结束后再次激活当前任务。// 延时500毫秒 vTaskDelay(pdMS_TO_TICKS(500));
osDelay()
函数是CMSIS-RTOS中用于任务延时的函数,参数单位是毫秒。- 与
vTaskDelay()
类似,调用osDelay()
函数会使得当前任务进入阻塞状态,直到指定的时间延时结束,然后再继续执行任务。// 延时500毫秒 osDelay(500);
- 这两个函数在不同的RTOS中实现方式和用法有所不同,但目的都是为了实现任务的时间控制和任务调度。