FreeRTOS入门基础
RTOS是为了更好地在嵌入式系统上实现多任务处理和时间敏感任务而设计的系统。它能确保任务在指定或预期的时间内得到处理。FreeRTOS是一款免费开源的RTOS,它广泛用于需要小型、预测性强、灵活系统的嵌入式设备。
创建第一个任务
- 任务函数:任务是通过函数来定义的。函数通常看起来像这样的无限循环
void vTaskFunction( void * pvParameters ) { for( ;; ) { // 任务代码 } }
- 创建任务:使用
xTaskCreate()
函数来创建一个任务。xTaskCreate( vTaskFunction, // 任务函数 "TaskName", // 任务名称 STACK_SIZE, // 堆栈大小 NULL, // 参数 TASK_PRIORITY, // 任务优先级 NULL ); // 用来传回创建的任务的句柄
- 启动调度器:创建任务后,需要启动调度器,这样RTOS就能开始管理这些任务了。
vTaskStartScheduler();
freertos基本组件
FreeRTOS作为一个实时操作系统(RTOS),提供了多种基础组件来组织代码并有效地管理任务。以下是一些FreeRTOS的基本组件:
-
任务 (Tasks):
- 任务是FreeRTOS中的基本执行单位,相当于一个独立的线程。每个任务都有自己的优先级,调度器根据这些优先级来决定运行哪个任务。
-
队列 (Queues):
- 队列用于在任务之间发送和接收数据。它们可以帮助实现任务同步,并提供一种安全传递消息的方法,如事件、内存块等。
#include "FreeRTOS.h" #include "queue.h" // 队列句柄,用于后续的队列操作如发送和接收 QueueHandle_t xQueue; // main() 或者任何初始化函数中 void main() { // 创建一个可以存储10个元素的队列,每个元素大小为sizeof( BaseType_t ) xQueue = xQueueCreate(10, sizeof(BaseType_t)); if (xQueue == NULL) { // 队列创建失败,可能是由于内存不足 // 错误处理代码 } else { // 队列成功创建 // 可以继续使用队列 } // ... 其余初始化代码 ... // 启动任务,开始调度器 vTaskStartScheduler(); // ... 其余代码 ... }
在这个例子中:
xQueueCreate
函数的第一个参数(10
)指定了队列能够存储的元素的数量。- 第二个参数(
sizeof(BaseType_t)
)指定了队列中每个元素的大小。 -
注意,在使用
xQueueCreate()
创建队列之前,必须确保已经调用了vTaskStartScheduler()
,因为队列的使用依赖于FreeRTOS的内存管理函数,它们在调度器启动时初始化。此外,创建队列通常在系统的初始化阶段进行,然后各个任务可以通过队列句柄进行数据的发送和接收。创建队列后,可以使用
xQueueSend()
、xQueueReceive()
等函数来在任务之间传递数据。如果队列创建成功,xQueueCreate()
将返回一个非NULL的QueueHandle_t
句柄,用于后续的队列操作。如果内存不足或者有其他原因导致队列创建失败,则返回NULL。
- 队列用于在任务之间发送和接收数据。它们可以帮助实现任务同步,并提供一种安全传递消息的方法,如事件、内存块等。
-
信号量 (Semaphores):
- 信号量是一种同步机制,可以用来控制对共享资源的访问,或者在任务之间同步操作。在FreeRTOS中有两种主要类型的信号量:二进制信号量和计数信号量。
#include "FreeRTOS.h" #include "semphr.h" SemaphoreHandle_t xSemaphore = NULL; void main_demo( void ) { // 二进制信号量创建 xSemaphore = xSemaphoreCreateBinary(); if (xSemaphore != NULL) { // 信号量创建成功,可以被使用 } else { // 信号量创建失败,处理错误情况 } // ... // 后续代码,如启动调度器和任务等 // ... }
#include "FreeRTOS.h" #include "semphr.h" SemaphoreHandle_t xCountingSemaphore; void main_demo( void ) { // 计数信号量创建,最大计数和初始计数 xCountingSemaphore = xSemaphoreCreateCounting(maxCount, initialCount); if (xCountingSemaphore != NULL) { // 信号量创建成功 } else { // 信号量创建失败,处理错误情况 } // ... // 后续代码,如启动调度器和任务等 // ... }
在计数信号量的创建中,
maxCount
表示信号量能够达到的最大计数值,而initialCount
是信号量的初始计数值。信号量通常用于同步任务或中断服务程序 (ISR),例如,保护共享资源,协调任务的执行,任务通知等。创建后的信号量句柄可以通过任务和中断服务程序进行给出 (Give) 和取得 (Take) 操作。
- 信号量是一种同步机制,可以用来控制对共享资源的访问,或者在任务之间同步操作。在FreeRTOS中有两种主要类型的信号量:二进制信号量和计数信号量。
-
互斥量 (Mutexes):
- 互斥量是特殊类型的信号量,专门用于管理资源访问。与二元信号量不同,互斥量具有所有权的概念,使其在处理优先级反转问题时更加有效。
-
定时器 (Timers):
- 定时器可以在一个定义好的时间之后运行一个函数。FreeRTOS支持一次性定时器和周期性定时器。
#include "FreeRTOS.h" #include "timers.h" TimerHandle_t xTimer; void vTimerCallback(TimerHandle_t xTimer) { // 这里处理定时器到期时的逻辑 } void main_demo( void ) { const TickType_t xTimerPeriod = pdMS_TO_TICKS( 1000 ); // 定时周期1000毫秒 // 创建软件定时器 xTimer = xTimerCreate( "Timer", // 定时器的文本名称,用于调试 xTimerPeriod, // 定时器的周期,以tick计数 pdTRUE, // pdFALSE为单次定时器,pdTRUE为周期性定时器 ( void * ) 0, // 可用于传递给回调函数的标识符,通常是NULL vTimerCallback // 定时器到期时调用的回调函数 ); if (xTimer == NULL) { // 定时器创建失败,可能是由于内存不足 } else { // 定时器创建成功,可以启动定时器 if (xTimerStart(xTimer, 0) != pdPASS) { // 定时器启动失败 } } // ... // 后续代码,如启动调度器和任务等 // ... }
在这个例子中:
- 第一个参数("Timer")是定时器名称。
- 第二个参数(
xTimerPeriod
)设置定时器周期,通过pdMS_TO_TICKS
宏转换了毫秒到tick。 - 第三个参数指定了定时器是单次的 (
pdFALSE
) 还是自动重载的周期性定时器 (pdTRUE
)。 -
定时器创建后,你需要调用
xTimerStart()
函数来启动定时器,此后定时器将按照设定的周期运行。在资源有限的嵌入式系统里,软件定时器是一种资源节约的实现定时功能的方式,因为你可以在一个定时服务中管理多个定时器,而无需为每个定时任务创建单独的线程。 - 第四个参数是指针,它会被传递给定时器回调函数,可以用作计数器或存储状态信息。
- 第五个参数是定时器到期时调用的回调函数。
- 定时器可以在一个定义好的时间之后运行一个函数。FreeRTOS支持一次性定时器和周期性定时器。
-
事件组 (Event Groups):
- 事件组是一种可以等待或设置一组事件标志的机制。这允许任务在多种事件中等待任意组合的事件。
#include "FreeRTOS.h" #include "event_groups.h" EventGroupHandle_t xEventGroup; void main_demo( void ) { // 创建事件组 xEventGroup = xEventGroupCreate(); if(xEventGroup == NULL) { // 事件组创建失败,通常是因为内核没有足够的可用堆空间 // 错误处理代码 } else { // 事件组成功创建,可以使用xEventGroup来设置、清除和等待事件标志 } // ... // 启动任务和调度器等 // ... }
在这个例子中,
xEventGroupCreate
用于创建一个新的事件组,并返回一个EventGroupHandle_t
类型的句柄,以供后续的事件组操作使用。如果事件组创建失败,则返回NULL
,通常是由于内存不足。成功创建事件组后,可以使用如下函数操作事件标志:
xEventGroupSetBits
:设置事件组中的一个或多个事件标志。xEventGroupClearBits
:清除事件组中的一个或多个事件标志。xEventGroupWaitBits
:等待事件组中的一个或多个特定事件标志变为设置状态。- 事件组是处理多个任务和中断共享状态或事件同步时的一个非常有用的工具,能够以线程安全的方式进行复杂的事件标志操作。
- 事件组是一种可以等待或设置一组事件标志的机制。这允许任务在多种事件中等待任意组合的事件。
-
任务通知 (Task Notifications):
- 任务通知是一种轻量级的信号量替代方式,可以快速地向任务发送信号。
-
发送任务通知
// 取得需要通知的任务的句柄,可以在任务创建时获取 TaskHandle_t xTaskToNotify = ...; // 发送通知给任务,'ulValue' 是通知值 uint32_t ulValue = 10; BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 仅在中断服务程序中使用 // 任务级代码 xTaskNotify(xTaskToNotify, ulValue, eSetValueWithOverwrite); // 或者在 ISR 中 xTaskNotifyFromISR(xTaskToNotify, ulValue, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
在上面的代码中,
xTaskToNotify
是要接收通知的任务的句柄。ulValue
是要发送的通知值。eSetValueWithOverwrite
指示即使之前的通知未被任务接收,也将覆盖通知值。 -
等待任务通知
// 该变量将接收通知值 uint32_t ulNotificationValue; BaseType_t xResult; // 等待通知 xResult = xTaskNotifyWait(0x00, // 在进入等待时不清除任何位 ULONG_MAX, // 在退出等待时将清除所有位 &ulNotificationValue, // 存储接收到的通知值 portMAX_DELAY); // 一直等待直到接收到通知 if(xResult == pdPASS) { // 任务接收到通知,`ulNotificationValue` 包含收到的值 }
在上面的等待通知代码中,
xTaskNotifyWait
第一个参数为零表示调用任务进入等待通知状态时,不清除任务的任何通知状态位。第二个参数ULONG_MAX
表示调用任务在接收到通知时将清除任务的所有通知状态位。第四个参数是阻塞时间,这个例子中使用portMAX_DELAY
,任务会无限期地等待直到收到通知。通过
xTaskNotifyGive
和ulTaskNotifyTake
的组合,任务通知机制也可以模仿二进制信号量的行为。任务通知作为一种轻量级的同步机制,在许多场合可以替代信号量和事件组,以节省系统资源。
资源地址
FreeRTOS - Market leading RTOS (Real Time Operating System) for embedded systems with Internet of Things extensions
Free RTOS Book and Reference Manual