任务优先级和Tick
在FreeRTOS中,任务的优先级和Tick是两个关键的概念,它们直接影响任务的调度和执行。
任务优先级
每个任务都被分配一个优先级,用于决定任务在系统中的调度顺序。
优先级是一个无符号整数,通常从0开始,数值越小,优先级越高。最高优先级是0,最低是configMAX_PRIORITIES - 1。
优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。
FreeRTOS的调度器可以使用2种方法来快速找出优先级最高的、可以运行的任务。使用不同的方法时,configMAX_PRIORITIES 的取值有所不同。
(1)通用方法
使用C函数实现,对所有的架构都是同样的代码。对configMAX_PRIORITIES的取值没有限制。但是configMAX_PRIORITIES的取值还是尽量小,因为取值越大越浪费内存,也浪费时间。configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为0、或者未定义时,使用此方法。
(2)架构相关的优化的方法
架构相关的汇编指令,可以从一个32位的数里快速地找出为1的最高位。使用这些指令,可以快速找出优先级最高的、可以运行的任务。使用这种方法时,configMAX_PRIORITIES的取值不能超过32。configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为1时,使用此方法。
在FreeRTOS中,优先级为0的任务是IDLE任务,用于在没有其他任务执行时占用CPU。
任务的创建时通过指定参数设置任务的优先级,如下所示:
xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, &Task1_Handle);
Tick
Tick是FreeRTOS内核用于计时和任务调度的基本时间单元。
FreeRTOS通过硬件定时器或者软件定时器,每隔一定时间(即Tick间隔)产生一次中断。
Tick的间隔由configTICK_RATE_HZ定义,它表示每秒钟产生的Tick数。
关系
任务的调度是基于优先级的,具有更高优先级的任务将在具有较低优先级的任务之前执行。
Tick间隔决定了系统时钟的精度,同时也影响了任务的延时和时间控制。
任务在等待一定时间或进行延时时,使用vTaskDelay()等函数,参数是以Tick为单位的时间。
vTaskDelay(100 / portTICK_PERIOD_MS); // 暂停任务100ms
任务的优先级和Tick的概念结合在一起,形成了FreeRTOS中任务调度和时间控制的基础。通过调整任务的优先级和配置Tick的间隔,可以灵活地控制系统中任务的执行顺序和时间行为。
以下是一个基于FreeRTOS的STM32F103芯片的简单优先级实验案例代码。该例子创建了两个任务,分别以不同的优先级运行,以演示优先级如何影响任务的调度。
#include "stm32f1xx.h"
#include "FreeRTOS.h"
#include "task.h"
// 函数原型
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
// 任务函数
TaskHandle_t Task1_Handle, Task2_Handle;
void vTask1(void *pvParameters) {
while (1) {
// 任务1的处理逻辑
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 切换LED状态
vTaskDelay(1000 / portTICK_PERIOD_MS); // 每隔1秒执行一次
}
}
void vTask2(void *pvParameters) {
while (1) {
// 任务2的处理逻辑
vTaskDelay(2000 / portTICK_PERIOD_MS); // 每隔2秒执行一次
}
}
int main(void) {
// 硬件初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
// 创建任务1(优先级1)
xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, &Task1_Handle);
// 创建任务2(优先级2)
xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, &Task2_Handle);
// 启动调度器
vTaskStartScheduler();
// 代码不应该运行到这里,如果运行到这里说明发生了错误
while (1) {
// 处理错误
}
}
// 系统时钟配置
void SystemClock_Config(void) {
}
// GPIO初始化
static void MX_GPIO_Init(void) {
}
在这个例子中,任务1和任务2分别在不同的时间间隔内切换LED的状态。任务1的优先级较高,它每隔1秒执行一次,而任务2的优先级较低,它每隔2秒执行一次。
任务状态
在FreeRTOS中,较高优先级的任务将优先于较低优先级的任务执行。任务优先级是通过任务创建时指定的参数来设置的(在xTaskCreate函数的第5个参数)。在这个案例中,任务1的优先级为1,任务2的优先级为2。
FreeRTOS中的任务可以处于不同的状态,这些状态反映了任务在系统中的执行阶段。任务的状态是通过eTaskState枚举类型表示的,定义在task.h头文件中。以下是任务可能的状态:
eRunning(运行中): 任务正在执行。这是任务处于活动状态的时候。
eReady(就绪): 任务已经准备好执行,但由于有其他高优先级任务在运行,该任务暂时没有被调度。
eBlocked(阻塞): 任务由于等待某些事件而被阻塞,例如等待定时器、消息队列、信号量等。
eSuspended(挂起): 任务被显式地挂起,不再参与调度。
eDeleted(已删除): 任务已被删除,但其资源尚未被释放。
eInvalid(无效): 无效状态,通常用于表示错误或未知状态。
你可以使用eTaskGetState函数来获取任务的当前状态。以下是一个示例代码:
#include "FreeRTOS.h"
#include "task.h"
TaskHandle_t xTaskHandle;
void vTaskFunction(void *pvParameters) {
while (1) {
// 任务的处理逻辑
}
}
int main(void) {
// 创建任务
xTaskCreate(vTaskFunction, "Task", configMINIMAL_STACK_SIZE, NULL, 1, &xTaskHandle);
// 启动调度器
vTaskStartScheduler();
// 代码不应该运行到这里,如果运行到这里说明发生了错误
while (1) {
// 处理错误
}
}
void vApplicationIdleHook(void) {
// 空闲钩子函数
eTaskState taskState = eTaskGetState(xTaskHandle);
switch (taskState) {
case eRunning:
// 任务正在运行
break;
case eReady:
// 任务就绪
break;
case eBlocked:
// 任务被阻塞
break;
case eSuspended:
// 任务被挂起
break;
case eDeleted:
// 任务已删除
break;
case eInvalid:
// 无效状态
break;
}
}
在这个例子中,vApplicationIdleHook是一个空闲钩子函数,用于在系统空闲时检查任务的状态。函数eTaskGetState返回任务的当前状态,然后可以根据任务的状态进行相应的处理。
阻塞状态(Blocked State)
在FreeRTOS中,任务的阻塞状态(Blocked State)表示任务由于等待某些事件而无法执行。任务可以因为多种原因而被阻塞,包括等待定时器、等待消息队列、等待信号量等。当任务处于阻塞状态时,它将不会被调度执行,直到满足了其阻塞条件。
以下是几个常见的阻塞状态的示例:
等待定时器:
// 创建定时器
TimerHandle_t xTimer = xTimerCreate("MyTimer", pdMS_TO_TICKS(1000), pdTRUE, 0, vTimerCallback);
// 启动定时器,在任务中使用xTimerPendFunctionCallFromISR等函数
xTimerStart(xTimer, 0);
在任务中可能会使用xTimerPendFunctionCallFromISR等函数等待定时器的超时事件。
等待消息队列:
// 创建消息队列
QueueHandle_t xQueue = xQueueCreate(5, sizeof(int));
// 在任务中等待消息队列
xQueueReceive(xQueue, &data, portMAX_DELAY);
任务通过xQueueReceive函数等待消息队列中的消息,如果队列为空,任务将被阻塞,直到队列中有数据。
等待信号量:
// 创建信号量
SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
// 在任务中等待信号量
xSemaphoreTake(xSemaphore, portMAX_DELAY);
任务通过xSemaphoreTake函数等待信号量,如果信号量被其他任务取得,当前任务将被阻塞,直到信号量可用。
在上述示例中,portMAX_DELAY表示任务将一直等待,直到满足其阻塞条件为止。任务也可以使用超时值来设置阻塞的最大等待时间。一旦阻塞条件得到满足,任务将被置为就绪状态,等待调度器调度执行。
暂停状态(Suspended State)
在FreeRTOS中,任务的暂停状态(Suspended State)表示任务被显式地挂起,使得该任务不再参与调度,即不会被执行。任务在创建后可以通过调用vTaskSuspend()函数将其挂起,然后通过调用vTaskResume()函数将其恢复执行。
以下是一个简单的示例代码,演示了任务的暂停和恢复:
#include "FreeRTOS.h"
#include "task.h"
TaskHandle_t xTaskHandle;
void vTaskFunction(void *pvParameters) {
while (1) {
// 任务的处理逻辑
}
}
int main(void) {
// 创建任务
xTaskCreate(vTaskFunction, "Task", configMINIMAL_STACK_SIZE, NULL, 1, &xTaskHandle);
// 启动调度器
vTaskStartScheduler();
// 代码不应该运行到这里,如果运行到这里说明发生了错误
while (1) {
// 处理错误
}
}
// 在某个事件或条件下挂起任务
void vSuspendTask(void) {
vTaskSuspend(xTaskHandle);
}
// 在某个事件或条件下恢复任务
void vResumeTask(void) {
vTaskResume(xTaskHandle);
}
在这个例子中,vTaskSuspend()函数用于挂起任务,vTaskResume()函数用于恢复任务。通常,这样的操作可以用于在某些条件满足或事件发生时挂起任务,然后在其他条件下恢复任务的执行。
需要注意的是,挂起任务不会立即停止任务的执行,而是在任务下一次被调度执行时生效。此外,挂起任务并不会释放任务所占用的资源,因此在使用这些函数时需要谨慎,以免引起资源泄漏。
就绪状态(Ready)
在FreeRTOS中,任务的就绪状态(Ready State)表示任务已经准备好被调度执行,但由于有其他高优先级的任务正在运行,该任务暂时还未被调度。
在FreeRTOS中,任务被创建后,它的状态一开始就是就绪状态,等待调度器来选择合适的时机执行它。任务的就绪状态可以由以下几种情况触发:
任务创建: 任务被创建后,会进入就绪状态,等待调度执行。
xTaskCreate(vTaskFunction, "Task", configMINIMAL_STACK_SIZE, NULL, 1, &xTaskHandle);
任务挂起状态解除: 任务从挂起状态(Suspended State)被恢复后,会进入就绪状态。
vTaskResume(xTaskHandle);
等待事件结束: 任务等待某个事件的发生,一旦事件发生,任务从阻塞状态切换到就绪状态。
xSemaphoreTake(xSemaphore, portMAX_DELAY);
任务等待定时器超时: 任务等待一个定时器超时,一旦超时,任务从阻塞状态切换到就绪状态。
xTaskDelay(1000 / portTICK_PERIOD_MS);
在以上例子中,xTaskCreate创建了一个任务,vTaskResume恢复了一个挂起的任务,xSemaphoreTake和xTaskDelay是任务等待事件或超时的示例。
任务的就绪状态是由FreeRTOS调度器根据任务的优先级和调度算法来决定的。当调度器判定某个任务处于就绪状态时,该任务将会被调度器选中,并执行相应的任务函数。