freeRTOS
- FreeRTOS 是一个开源的实时操作系统(RTOS),专为嵌入式系统特别是微控制器设计。
- 它是轻量级的,提供了一个多任务环境,允许多个任务在单个处理器上并发运行。
- 主要特性:抢占式多任务、任务调度、可移植性,任务间通信,内存管理
- 虽然 FreeRTOS 使用了“任务”这一术语,但它们在功能和概念上类似于传统操作系统中的“线程”。这两个术语都表示了并行执行的基本单位,并且都涉及到优先级、堆栈管理、调度等关键概念。
- 在 FreeRTOS 中,任务的管理和调度是通过 FreeRTOS 提供的 API 实现的,而在传统操作系统中,线程的管理则是由操作系统的线程库或内核提供的。
常用函数解析
TaskHandle_t 创建任务句柄
TaskHandle_t 实际上是一个指向任务控制块(TCB)的指针,TCB 是 FreeRTOS 内部用于存储任务状态的数据结构。
用法
保存任务句柄: 在调用 xTaskCreate 创建任务时,可以将任务句柄传递到一个 TaskHandle_t 变量中。这个句柄可以用来引用任务,以便后续对任务进行操作。
TaskHandle_t taskFlushled;
void task_fun_flushled(void *args) {
while (1) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8);
osDelay(1000);
}
}
void setup() {
BaseType_t ret = xTaskCreate(
task_fun_flushled,
"taskFlushled",
128,
NULL,
osPriorityNormal,
&taskFlushled
);
if (ret == pdFALSE) {
printf("任务创建失败\n");
}
}
xTaskCreate() 任务创建函数
BaseType_t xTaskCreate(TaskFunction_t pvTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask);
参数
TaskFunction_t pvTaskCode: 指向任务执行的函数,这个函数需要符合 void func(void *) 的形式。
const char * const pcName: 任务的名字,用于调试。
const configSTACK_DEPTH_TYPE usStackDepth: 任务的堆栈大小,以字为单位。
void *pvParameters: 传递给任务的参数。
UBaseType_t uxPriority: 任务的优先级,数值越大,优先级越高。
TaskHandle_t *pxCreatedTask: 用于传出任务句柄的指针,可以用于后续的任务引用或删除等操作。即为任务本身
返回值
pdFALSE:表示任务创建失败
-------------------优先级--------------------------
typedef enum {
osPriorityIdle = -3,
osPriorityLow = -2,
osPriorityBelowNormal = -1,
osPriorityNormal = 0,
osPriorityAboveNormal = +1,
osPriorityHigh = +2,
osPriorityRealtime = +3,
osPriorityError = 0x84
} osPriority;
-------------------------------------------------------------
具体用法
xTaskCreate(task_fun_printf,"taskPrintf",128,NULL,osPriorityNormal,&taskPrintHandle);
-----以下是具体执行函数----------
任务不是只执行一次所以有一个函数体
并且不能光是你一个人执行,故有osDelay函数
void task_fun_printf(void *args)
{
while(1)
{
printf("%s-%d\r\n",__func__,__LINE__);
osDelay(1000);
}
}
----------以下是任务句柄,当然它应该放在最前面----------------
TaskHandle_t taskPrintHandle;
portSET_INTERRUPT_MASK_FROM_ISR() 禁用中断
这个宏在中断服务例程(ISR)中使用。
确保操作的原子性和数据的一致性。
防止中断处理过程中被其他中断打断。
portSET_INTERRUPT_MASK_FROM_ISR(): 设置中断掩码,禁用中断。
portCLEAR_INTERRUPT_MASK_FROM_ISR(): 恢复中断掩码,重新启用中断。
用法
int __io_putchar(int ch)
{
UBaseType_t vxx;
vxx = portSET_INTERRUPT_MASK_FROM_ISR();
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1);
portCLEAR_INTERRUPT_MASK_FROM_ISR(vxx);
return ch;
}
defaultTask 默认任务
defaultTask 在系统中扮演了多个角色,包括初始化系统、**保持**系统运行(占位)、优化任务调度、处理异常和调试开发。
该任务由STM32CubeIDE自动创建,代码如下
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
osThreadId defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
void StartDefaultTask(void const * argument)
{
for(;;)
{
osDelay(1);
}
}
osKernelStart() 启动内核调度器
osKernelStart() 是 FreeRTOS 中的一个函数,用于启动 RTOS 内核的调度器。
在调用 osKernelStart() 后,系统的控制权将转交给 RTOS 内核。通常,main() 函数中的 while (1) 循环会继续存在,但它不会执行任何操作,因为控制权已交给内核调度。
vTaskResume 恢复(激活)被挂起的任务
函数原型
void vTaskResume(TaskHandle_t xTaskToResume);
它的作用是将任务从挂起状态重新设置为就绪状态,使得该任务可以被调度和执行。
升级版
xTaskResumeFromISR()
允许在 ISR 中将任务的状态从挂起变为就绪,从而使任务能够在 ISR 处理完后尽快被调度执行。
vTaskSuspend 挂起任务
函数原型:
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
用于将一个正在运行或就绪的任务挂起。挂起的任务将不会被调度或执行,直到它被恢复。
升级版
vTaskSuspendFromISR
vTaskDelete 删除任务
函数原型:
void vTaskDelete(TaskHandle_t xTaskToDelete);
用于删除一个任务。调用这个函数会释放与任务相关的所有资源,并将任务从系统中移除。
xTaskToDelete: 要删除的任务的句柄。如果句柄为 NULL,则当前正在运行的任务将被删除。如果要删除的任务不是当前任务,传入任务句柄即可。
注意,删除任务的时候要注意同步问题;一般是 自我删除;
void task_test_fun(void *args)
{
while(1){
干活; if(干完了){break;}
}
vTaskDelete(NULL);
}
疑问为什么删除osDelay后,其它函数也会执行
- 各个任务在 FreeRTOS 中是独立的,除非有显式的同步机制(如信号量、互斥量等),一个任务的执行状态不会直接影响其他任务。
osDelay和HAL_Delay的区别
osDelay
- osDelay 是 FreeRTOS 提供的延迟函数
- osDelay 会将当前任务挂起一段指定的时间,在这段时间内,调度器可以将 CPU 分配给其他任务执行。等延迟时间结束后,当前任务会被重新放入就绪队列,等待调度器再次调度。
HAL_Delay
- HAL_Delay 是 STM32 HAL 库中的一个延迟函数。它的工作原理是利用 SysTick 定时器,通过轮询计数器来产生延迟。在调用 HAL_Delay() 时,程序会进入一个忙等待循环(也叫阻塞等待),直到指定的时间过去。这意味着在这段延迟时间内,微控制器不会执行其他任务,完全被阻塞住了。
在freeRTOS中禁止使用HAL_Delay
- 在多任务环境中使用 HAL_Delay 会阻塞整个系统,导致其他任务无法执行,系统无法实现真正的并发。
代码示例
1.RCC设置Crystal
2.USART1设置为异步通信
3.FREERTOS 的interface选择CMSIS_C1(简单但功能有限)
4.配置测试管脚,我用的是PC6,7,8作为LED灯管脚
具体代码如下 功能:交替打印连续数
#include "main.h"
#include "cmsis_os.h"
UART_HandleTypeDef huart1;
osThreadId defaultTaskHandle;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
void StartDefaultTask(void const * argument);
int __io_putchar(int ch)
{
UBaseType_t vxx;
vxx = portSET_INTERRUPT_MASK_FROM_ISR();
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1);
portCLEAR_INTERRUPT_MASK_FROM_ISR(vxx);
return ch;
}
int cnt = 0;
TaskHandle_t taskOuHandle;
TaskHandle_t taskJiHandle;
void task_fun_ou(void *args)
{
int Acnt = 0;
while (1)
{
printf(" %d \r\n", Acnt);
Acnt += 2;
vTaskResume(taskJiHandle);
vTaskSuspend(taskOuHandle);
}
}
void task_fun_ji(void *args)
{
vTaskSuspend(taskJiHandle);
int Bcnt = 1;
while (1)
{
printf(" %d \r\n", Bcnt);
Bcnt += 2;
vTaskResume(taskOuHandle);
vTaskSuspend(taskJiHandle);
osDelay(1000);
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
BaseType_t ret;
ret = xTaskCreate(task_fun_ou, "taskPrintfOU", 128, NULL, osPriorityNormal, &taskOuHandle);
if (ret == pdFALSE)
{
printf("xTaskCreate for print err\r\n");
return -34;
}
ret = xTaskCreate(task_fun_ji, "taskPrintfODD", 128, NULL, osPriorityNormal, &taskJiHandle);
if (ret == pdFALSE)
{
printf("xTaskCreate for flushled err\r\n");
return -34;
}
osKernelStart();
while (1)
{
}
}