在 FreeRTOS 中,任务调度器会不断地扫描所有的任务,选择具有最高优先级的就绪任务来运行。当有新任务就绪或者当前任务完成时,调度器会再次扫描任务,选择下一个就绪任务来运行。这种运行方式叫做"任务轮转"。
在任务调度器切换任务时,会进行上下文切换。上下文切换会在切换前和切换后保存和恢复 CPU 寄存器状态,并在切换后切换新任务的堆栈。这样可以保证每个任务的状态和环境不受其它任务的影响。
此外 FreeRTOS 也提供了可重入的调度器函数,如xTaskIncrementTick,在处理硬件定时器或中断时使用。这种机制可以让中断和任务在上下文切换时进行切换。
通过上述机制,FreeRTOS能支持在单核CPU上多任务并行运行,各个任务相互独立,互不影响,调度策略满足系统要求,实现了多任务并发处理。
就绪状态 (Ready):任务已经创建,并且可以被调度器运行。当任务被创建时,它处于就绪状态。
运行状态 (Running):当前在 CPU 上运行的任务。只有一个任务能处于运行状态。
阻塞状态 (Blocked):任务被阻塞了,不能被调度器运行。当任务执行阻塞操作时,如等待信号量、邮箱、消息队列等,它将进入阻塞状态。
挂起状态 (Suspended):任务被挂起了,不能被调度器运行。当任务被调用 vTaskSuspend() 挂起时,它将进入挂起状态。
删除状态 (Deleted):任务已被删除,不能被调度器运行。当任务调用 vTaskDelete() 或者调度器自动删除任务时,它将进入删除状态。
任务状态的转换由调度器控制。例如:一个任务从就绪状态转换到运行状态,当前运行的任务从运行状态转换到就绪状态,调用阻塞函数的任务从就绪状态转换到阻塞状态。
FreeRTOS 中每个任务都有一个优先级。优先级越高的任务越容易被调度器选中,被分配到更多的 CPU 时间。
FreeRTOS 使用优先级调度算法来确定哪个任务应该在 CPU 上运行。当任务调度器每次被调用时,它会扫描所有就绪任务,选择具有最高优先级的任务来运行。
默认情况下,FreeRTOS 使用升序优先级调度算法,即优先级越高,值越小。其中默认最低优先级为0,最高优先级为(configMAX_PRIORITIES-1)。
开发人员可以使用函数 xTaskCreate() 来创建新任务并指定其优先级,也可以使用函数vTaskPrioritySet()更改已有任务的优先级。
在实际应用中,优先级需要结合系统的实际需求进行设置,确保每个任务都能得到足够的运行时间,确保系统的正常运行。
FreeRTOS 中有一个特殊的任务叫做空闲任务 (Idle task)。这个任务是由 FreeRTOS 自动创建的,它的优先级是最低的,并且当所有其它任务都处于阻塞状态时,调度器会自动切换到这个任务上运行。
空闲任务的主要目的是在系统空闲时执行后台操作,如调整 CPU 的频率,执行计数器或收集统计信息等。
可以通过实现 xApplicationIdleHook() 函数来指定空闲任务的具体行为,以实现自己的空闲处理逻辑。此函数在空闲任务调用时运行。此函数的默认实现为空函数,如果没有被重定义,空闲任务就不会执行其他任何操作。
需要注意的是,空闲任务会一直运行,因此需要保证它不会占用过多 CPU 资源,否则可能会影响其它任务的调度。
空闲任务作为系统级别的任务提供资源回收及其他一些系统维护操作,比如进行资源回收或者统计系统信息等操作,它在系统处理能力充足时可以满足效率要求,如果在系统空闲时间很多的时候会成为系统的瓶颈。
FreeRTOS 使用任务调度器 (task scheduler) 来管理和调度任务。当系统中有新的任务就绪或者当前任务完成时,调度器会被唤醒,选择下一个就绪任务来运行。
FreeRTOS 默认使用的是单次调度 (pre-emptive scheduling) 的方式。即当优先级更高的任务就绪时,调度器会立即将 CPU 切换到该任务上,即使当前任务还有剩余时间片。这样可以确保高优先级任务能尽快得到处理。
调度器会不断地扫描所有的任务,选择具有最高优先级的就绪任务来运行。如果有多个任务具有相同的优先级,调度器会使用先到先服务 (FIFO) 的方式来选择下一个任务。
开发人员可以通过配置调度器行为来实现不同的调度策略,例如使用时间片轮转 (round-robin) 的方式来支持多任务共享 CPU 时间。
需要注意的是,调度器是在中断上运行的,这样可以保证在任何时候都可以进行任务调度,即使是在任务执行过程中。
FreeRTOS 提供了一系列的宏来配置调度器行为。这些宏定义可以在 FreeRTOSConfig.h 文件中进行配置,需要注意的是,配置的方式要结合实际情况来进行,不同的配置会带来不同的效果。
下面是一些常用的调度器配置宏定义:
configUSE_PREEMPTION: 如果设置为 1,表示使用单次抢占式调度,当优先级更高的任务就绪时,调度器会立即将 CPU 切换到该任务上。
configUSE_TIME_SLICING: 如果设置为 1,表示使用时间片轮转调度,即每个任务有固定的时间片来使用 CPU。
configIDLE_SHOULD_YIELD: 如果设置为 1,表示空闲任务应该让出 CPU 给优先级更高的任务。
FreeRTOS 提供了一系列 API 来管理任务,主要分为以下几类:
功能 | 函数 |
---|---|
任务创建和销毁 | xTaskCreate(),vTaskDelete() |
任务挂起和唤醒 | vTaskSuspend(),vTaskResume() |
任务延迟 | vTaskDelay(), vTaskDelayUntil() |
任务优先级调整 | vTaskPrioritySet(), uxTaskPriorityGet() |
任务状态查询 | eTaskGetState() |
将STM32F103C8T6单片机的PC13和PA1引脚设置为OUTPUT模式,并上拉。
时钟源选择外部高速时钟,并将系统时钟设置为TIM4。时钟树设置如下。
双击tasks框中的默认任务在弹出的窗口中按如下参数设置。接着点击Add创建第二个任务,参数设置如下。
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "gpio.h"
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Init scheduler */
osKernelInitialize(); /* RTOS内核初始化 */
MX_FREERTOS_Init();
/* Start scheduler */
osKernelStart();/*启动RTOS*/
/* We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Definitions for Task_LED1 */
osThreadId_t Task_LED1Handle;//任务Task_LED1的句柄
const osThreadAttr_t Task_LED1_attributes = {//任务Task_LED1的属性
.name = "Task_LED1",//任务名称
.stack_size = 128 * 4,//栈空间大小
.priority = (osPriority_t) osPriorityNormal,//任务优先级
};
/* Definitions for Task_LED2 */
osThreadId_t Task_LED2Handle;
const osThreadAttr_t Task_LED2_attributes = {
.name = "Task_LED2",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityLow,
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void AppTask_LED1(void *argument);//任务Task_LED1的函数声明
void AppTask_LED2(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of Task_LED1 */
Task_LED1Handle = osThreadNew(AppTask_LED1, NULL, &Task_LED1_attributes);//创建任务Task_LED1的函数
/* creation of Task_LED2 */
Task_LED2Handle = osThreadNew(AppTask_LED2, NULL, &Task_LED2_attributes);//创建任务Task_LED2的函数
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
/* USER CODE BEGIN Header_AppTask_LED1 */
/**
* @brief Function implementing the Task_LED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_LED1 */
void AppTask_LED1(void *argument)//任务Task_LED1的函数
{
/* USER CODE BEGIN AppTask_LED1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);//板载LED翻转
osDelay(1000);//同vTaskDelay(1000);osDelay是CubeMX将vTaskDelay封装后的函数。
}
/* USER CODE END AppTask_LED1 */
}
/* USER CODE BEGIN Header_AppTask_LED2 */
/**
* @brief Function implementing the Task_LED2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_LED2 */
void AppTask_LED2(void *argument)
{
/* USER CODE BEGIN AppTask_LED2 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1);//翻转PA1引脚
osDelay(500);//间隔500ms
}
/* USER CODE END AppTask_LED2 */
}
代码运行后,会先执行任务Task_LED2,PA1连接的LED每500ms翻转一次。然后任务Task_LED1开始执行,板载LED每1000ms翻转一次。
代码下载