FreeRTOS简单使用

FreeRTOS简单使用

迁移

迁移真的遇到好多坑,最多的还是中断优先级的问题,优先级太高的中断会破坏FreeRTOS的调度,所以与FreeRTOS调度相关的中断优先级必须高于其他中断

配置

在 Cortex-M 架构中,优先级数值越小,优先级越高。
FreeRTOS的配置主要集中在FreeRTOSConfig.h文件中,但是感觉最重要的配置是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* configKERNEL_INTERRUPT_PRIORITY sets the priority of the tick and context
* switch performing interrupts. Not supported by all FreeRTOS ports. See
* https://www.freertos.org/RTOS-Cortex-M3-M4.html for information specific to
* ARM Cortex-M devices. */
#define configKERNEL_INTERRUPT_PRIORITY (15 << 4)

/* configMAX_SYSCALL_INTERRUPT_PRIORITY sets the interrupt priority above which
* FreeRTOS API calls must not be made. Interrupts above this priority are never
* disabled, so never delayed by RTOS activity. The default value is set to the
* highest interrupt priority (0). Not supported by all FreeRTOS ports.
* See https://www.freertos.org/RTOS-Cortex-M3-M4.html for information specific to
* ARM Cortex-M devices. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (5 << 4)

configKERNEL_INTERRUPT_PRIORITY

configKERNEL_INTERRUPT_PRIORITY 用于设置 FreeRTOS 内核相关中断(如系统时钟节拍和上下文切换)的优先级。
在 Cortex-M 架构中,优先级数值越小,优先级越高。这里的 (15 << 4) 表示将优先级设置为最低(数值最大),左移 4 位是因为优先级寄存器只使用高 4 位。
该参数决定了 FreeRTOS 内部使用的中断优先级,确保这些中断不会被更高优先级的中断抢占,从而保证内核调度的正常进行。
一般建议将 configKERNEL_INTERRUPT_PRIORITY 设置为最低优先级,以避免与其他高优先级中断冲突,提升系统的稳定性和可靠性。

configMAX_SYSCALL_INTERRUPT_PRIORITY

configMAX_SYSCALL_INTERRUPT_PRIORITY 用于设置允许调用 FreeRTOS API 的中断的最高优先级。
高于该优先级5(数值更小)的中断不能调用 FreeRTOS 的 API(如队列、信号量、任务通知等),否则可能导致系统崩溃或不可预期的行为。
这是因为 FreeRTOS 在临界区会屏蔽低优先级中断,但不会屏蔽高优先级中断。只有优先级等于或低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断,才能安全地与 FreeRTOS 内核交互。
正确配置该参数有助于保证 RTOS 的调度和资源管理的稳定性,避免高优先级中断打断内核关键操作。
实际使用时,建议查阅芯片手册和 FreeRTOS 官方文档,确保优先级配置符合硬件和应用需求。

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

TaskHandle_t TaskHandle;
void taskFunc(void* msg){
while(1){
```c
// 等待任务通知,直到收到通知为止。如果不需要获取参数可传 NULL
uint32_t value;
xTaskNotifyWait(0, 0xFFFFFFFF, &value, portMAX_DELAY);
printf("Received value: %u\n", value);

// 延时 35 毫秒,控制任务执行频率
vTaskDelay(pdMS_TO_TICKS(35));

// 在此处编写任务具体执行逻辑
// do something
}
}
// 任务通知函数,向任务发送通知
void callTaskFunc(uint32_t value ){
xTaskNotify(TaskHandle, value, eSetValueWithOverwrite);
}
// 中断服务函数,向任务发送通知
// 注意:此函数必须在中断上下文中调用
void callTaskFuncFromISR(uint32_t value ){
BaseType_t xHigherPriorityTaskWoken = pdTRUE; // 用于判断是否需要任务切换
xTaskNotifyFromISR(TaskHandle, // 目标任务句柄
value, // 要传递的值
eSetValueWithOverwrite, // 覆盖方式
&xHigherPriorityTaskWoken // 用于判断是否需要任务切换
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
int main(){
if (xTaskCreate(taskFunc, "taskFunc", configMINIMAL_STACK_SIZE, NULL, 3, &TaskHandle) != pdPASS) {
printf("taskFunc create failed!\n");
}
// 启动 FreeRTOS 调度器(程序将在此处阻塞,由 FreeRTOS 接管)
vTaskStartScheduler();

return 0; // 如果调度器启动成功,这行代码不会被执行
}

创建任务

1
2
3
if (xTaskCreate(taskFunc, "taskFunc", configMINIMAL_STACK_SIZE, NULL, 3, &TaskHandle) != pdPASS) {
printf("taskFunc create failed!\n");
}

传入参数

  • taskFunc
    任务函数指针。这个函数就是任务的入口点,任务调度器会周期性地调用它。

  • “taskFunc”
    任务名称。用于调试和跟踪,通常是一个字符串。

  • configMINIMAL_STACK_SIZE
    任务栈大小。以字(不是字节)为单位,决定该任务可用的栈空间。

  • NULL
    任务参数。传递给任务函数的参数,如果不需要可以填 NULL,由taskFunc方法中的msg接收。

  • 3
    任务优先级。数值越大优先级越高,决定任务调度的优先顺序。

  • &TaskHandle
    任务句柄指针。用于保存任务的句柄,可以后续用于操作该任务(如删除、挂起等)。

如果创建任务失败(返回值不是 pdPASS),会输出错误信息。

等待

vTaskDelay

vTaskDelay 是 FreeRTOS 中用于让任务延时(挂起)一段时间的 API。它的作用是让当前任务进入阻塞状态,直到指定的时间周期过去后再恢复运行。

基本用法

参数是以 tick 为单位的延时时间。tick 是 FreeRTOS 的时钟节拍,具体时长取决于系统配置(比如 1 tick = 1ms)。
延时期间,任务不会占用 CPU,调度器可以运行其他任务。
常用于任务周期性执行或等待某些事件。

注意事项

vTaskDelay 只能用于任务函数内部,不能在中断服务程序中调用。
如果需要精确的周期性执行,建议使用 vTaskDelayUntil。

vTaskDelayUntil

vTaskDelayUntil 是 FreeRTOS 中用于让任务延时到下一个周期的 API。它与 vTaskDelay 的区别在于,vTaskDelayUntil 可以确保任务以固定的时间间隔执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement);

```c
void PeriodicTask(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = pdMS_TO_TICKS(100); // 100ms周期

// 初始化上次唤醒时间为当前时间
xLastWakeTime = xTaskGetTickCount();

for(;;)
{
// 执行任务内容
printf("PeriodicTask running...\n");

// 延时到下一个周期
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
基本用法
  • 第一个参数是一个指向上次唤醒时间的变量,通常是一个静态变量或全局变量。
  • 第二个参数是以 tick 为单位的周期
  • 任务会在每个周期结束时自动唤醒,确保任务以固定的时间间隔执行。
注意事项
  • vTaskDelayUntil 只能在任务函数中使用,不能在中断服务程序中使用
  • 需要在任务开始时初始化上次唤醒时间,通常使用 xTaskGetTickCount() 获取当前 tick 值。
  • 如果任务执行时间超过周期时间,可能会导致任务错过下一个周期的唤醒,因此需要确保任务执行时间小于周期时间。

事件通知

事件通知是 FreeRTOS 中用于任务间通信的一种机制,允许一个任务向另一个任务发送信号或数据。它比信号量和消息队列更轻量级,适用于简单的任务间同步和通知。

发送通知

非中断中发送通知
1
xTaskNotify(TaskHandle, value, eSetValueWithOverwrite);
  • TaskHandle:目标任务的句柄。
  • value:要发送的值,可以是任意整数。
  • eSetValueWithOverwrite:通知方式,表示如果目标任务已经有通知值,则覆盖它。
中断中发送通知
1
2
3
4
5
6
7
8
9
void callTaskFuncFromISR(uint32_t value ){
BaseType_t xHigherPriorityTaskWoken = pdTRUE; // 用于判断是否需要任务切换
xTaskNotifyFromISR(TaskHandle, // 目标任务句柄
value, // 要传递的值
eSetValueWithOverwrite, // 覆盖方式
&xHigherPriorityTaskWoken // 用于判断是否需要任务切换
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
  • TaskHandle:目标任务的句柄。
  • value:要发送的值,可以是任意整数。
  • eSetValueWithOverwrite:通知方式,表示如果目标任务已经有通知值,则覆盖它。
  • xHigherPriorityTaskWoken:指向一个 BaseType_t 变量的指针,用于判断是否需要进行任务切换。如果发送通知后,目标任务的优先级高于当前任务,则会设置该变量为 pdTRUE,表示需要进行任务切换。如果为pdFALSE,则不需要切换任务。
  • portYIELD_FROM_ISR(xHigherPriorityTaskWoken):如果 xHigherPriorityTaskWoken 被设置为 pdTRUE,则会触发任务切换,确保高优先级任务能够立即运行。如果为pdFALSE,则不会进行任务切换,当前中断服务程序将继续执行。

结束任务

结束任务可以使用 vTaskDelete() 函数。该函数会将指定的任务从调度器中删除,并释放其占用的资源。

1
void vTaskDelete(TaskHandle_t xTaskToDelete);
  • xTaskToDelete:要删除的任务句柄。如果传入 NULL,则会删除当前任务。

动态内存分配

pvPortMalloc 与 vPortFree
用法与 malloc与free类似,但是使用的是 FreeRTOS 的内存管理函数。由于 FreeRTOS 的内存管理是基于堆的,因此需要在 FreeRTOSConfig.h 中配置堆的大小和类型。

1
2
//分配20KB的堆内存
#define configTOTAL_HEAP_SIZE 1024*20

后记

  • 在通过串口通讯进行异步操作时,例如发送串口消息,串口消息返回触发中断的模式,需要在串口信息发送完成后进行一小段时间的vTaskDelay操作,以切出当前调度,开始接收中断。因为为了保护FreeRTOS的内部调度,系统中断的优先级都设定的比FreeRTOS的调度中断的等级低。只有手动进行delay操作才能更加实时的接收中断信息