FreeRTOS的一些知识点

一、临界区

  1. 临界区是指一段代码,在执行过程中不允许被中断(例如:任务切换或硬件中断)。
  2. 临界区的作用是确保对共享资源的操作是原子性的,避免多个任务或中断同时操作共享资源导致数据错误。
  3. 对于实时系统来说,进入临界区时需要快速完成操作,以免影响系统的实时性。
  4. 在 FreeRTOS 中,可以通过以下方式进入和退出临界区:
    - 进入临界区:使用 taskENTER_CRITICAL() 函数,FreeRTOS 会关闭所有中断,暂停任务调度。这确保在临界区内的代码执行过程中,不会有其他任务或中断打断当前的任务。
    - 退出临界区:使用 taskEXIT_CRITICAL() 函数恢复中断和任务调度。
  5. 注意: 在临界区内应避免进行长时间的操作,尤其是像延时、等待等操作,因为这可能会影响系统的实时性能。理想情况下,进入临界区后,应尽快完成任务并退出临界区。
  6. 中断中进入临界区
    • 中断中也可以使用临界区来保证原子操作
UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR(); // 关闭中断
// 临界区代码
portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus); // 恢复中断

- 举例说明
int __io_putchar(int ch)
{
    UBaseType_t uxSavedInterruptStatus;
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1);
    portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);
    return ch;
}

二、时间管理

  1. FreeRTOS 的时间管理基于硬件定时器 SysTick,这是一个硬件定时器,它每隔 1 毫秒产生一次中断,用来增加系统的时间计数 ticks。
  2. 获取当前时间
    • xTaskGetTickCount():用于普通任务获取当前系统的 tick 计数,即系统启动后经过的时间(以 tick 为单位)。1 个 tick 通常等于 1 毫秒,具体取决于配置。
    • xTaskGetTickCountFromISR():在中断服务程序(ISR)中使用的版本,用来获取当前 tick 值。
  3. 任务睡眠
    • FreeRTOS 提供了 osDelay() 函数,可以让任务暂停执行一段时间
    • 但是,osDelay() 可能不够精确,因为它会让任务在执行完工作后再睡眠指定时间,导致延迟时间包括了任务执行时间。
  4. 更精确的延时
    • FreeRTOS 提供了 vTaskDelayUntil() 函数,它可以保证任务每隔固定时间间隔执行
TickType_t lastWakeTime = xTaskGetTickCount();
for (;;)
{
    do_something();
    vTaskDelayUntil(&lastWakeTime, 500); // 保证每隔 500 ticks 执行一次
}

vTaskDelayUntil() 基于上一次唤醒时间来计算下次睡眠的时长,因此能保证任务周期精确,而不会受任务处理时间的影响。

三、堆栈管理

  1. 在嵌入式系统中,内存分为多个区域,包括代码段、全局变量区、堆区和栈区。
  • 栈:用于存储局部变量、函数参数、返回地址等。栈的空间是有限的。
  • 堆:用于动态分配的内存,例如通过 malloc() 分配的空间。
  1. 在裸机开发中(没有操作系统的情况下),堆和栈是共享的。在 FreeRTOS 中,每个任务都有自己独立的堆栈空间,而这些堆栈空间是从系统的堆中分配的。
  2. freeRTOS的栈空间 从堆区分配
  3. 栈: 自动从heap分配,自动释放不需要我们关心
  4. 在 STM32 开发中,使用 CubeIDE 配置 FreeRTOS 堆大小。可以通过以下路径设置堆大小:
CubeIDE(硬件配置界面) -> System View -> Middleware -> FreeRTOS -> Config Parameters -> TOTAL_HEAP_SIZE
要填入一个完整的数,不要 +-*/ ,而且要随机一点,不要 是512的整数倍

  1. 动态内存分配策略
  • FreeRTOS 提供了四种内存分配策略,常用的是带有碎片整理功能的动态分配策略。即 FreeRTOS 提供了 pvPortMalloc() 和 vPortFree() 这两个函数,类似于标准 C 的 malloc() 和 free(),用来动态分配和释放内存。
  1. 如何检查剩余堆内存
    • 可以使用 xPortGetMinimumEverFreeHeapSize() 函数来获取最小时刻的堆剩余空间。如果堆内存过小,说明系统内存分配有风险,可能需要调整配置。

四. 调度策略

  1. FreeRTOS 采用的是 抢占式调度 和 时间片轮转 的结合:
  • 抢占式:只要有更高优先级的任务,系统会立即中断当前任务,执行高优先级任务。
  • 时间片轮转:当多个任务的优先级相同时,系统会给每个任务分配一个固定的时间片,轮流执行。
  1. 任务的切换时机: 通常在中断发生时,系统会检查是否有更高优先级的任务需要运行,如果有则进行任务切换。

五、FreeRTOS调试

  1. volatile 修饰共享变量:如果多个任务共享某个变量,为了确保每次都从内存中读取最新的值,而不是使用缓存,应该使用 volatile 关键字修饰变量。
  2. 中断优先级的设置:使用 FreeRTOS 时,中断的优先级需要降低到一定范围内,否则可能与 FreeRTOS 的调度机制产生冲突。可以在 CubeIDE 中配置中断优先级。
  3. 初始化顺序:在 main() 函数中,初始化各个模块时要注意顺序。例如,FreeRTOS 的启动必须在所有硬件初始化完成后进行。
  4. 中断中的 API 调用:在中断服务程序中调用 FreeRTOS 的 API 时,要使用以 FromISR 结尾的函数,例如 xQueueSendFromISR() 等。
  5. 断言与错误处理:FreeRTOS 提供了一个断言机制,可以在出现错误时触发断言。例如,在 FreeRTOSConfig.h 中定义:
#define configASSERT(x) if ((x) == 0) { taskDISABLE_INTERRUPTS(); printf("%s-%d\r\n", __func__, __LINE__); for (;;); }

当系统检测到错误时,会进入断言并输出错误信息,帮助定位问题

  1. 栈和堆溢出检测
  • 栈溢出检测: FreeRTOS 提供了栈溢出检测功能,当某个任务的栈溢出时,会调用栈溢出钩子函数:
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
    printf("Stack overflow in task: %s\r\n", pcTaskName);
    // 这里可以添加处理代码,例如重启系统或停止任务
}

要启用栈溢出检测功能,需要设置 configCHECK_FOR_STACK_OVERFLOW = 2。
cubide->middleware-->config parameter--> configCHECK_FOR_STACK_OVERFLOW -->option 2


  • FreeRTOS 也支持堆溢出检测。当内存分配失败时,系统会调用堆溢出钩子函数
void vApplicationMallocFailedHook(void)
{
    printf("Heap memory allocation failed!\r\n");
    // 这里可以添加处理代码
}
启用堆溢出检测
configUSE_MALLOC_FAILED_HOOK=1
方法:cubide->middleware-->config parameter-->configUSE_MALLOC_FAILED_HOOK=1

六、异常处理

  1. 在 FreeRTOS 和嵌入式系统中,异常(如中断错误、数据异常、硬件异常等)是不可避免的。为了方便调试和快速定位问题,我们可以在常见的异常处理函数中加上打印日志。
  2. STM32 的异常处理函数通常实现在 core/stm32f1xx_it.c 文件中,包括以下几个关键函数:
    • HardFault_Handler:处理硬件错误(例如非法内存访问)。
    • MemManage_Handler:内存管理错误(例如堆栈溢出)。
    • UsageFault_Handler:处理非法指令、未对齐的内存访问等错误。
    • DebugMon_Handler:用于监控调试事件。
  3. 为了调试这些异常,可以在每个处理函数中添加调试打印,以便在异常发生时输出详细的错误信息,例如当前的函数名和出错的位置。
void HardFault_Handler(void)
{
    printf("HardFault occurred!\n");
    while (1); // 陷入死循环,方便调试
}

void MemManage_Handler(void)
{
    printf("Memory management fault!\n");
    while (1);
}

void UsageFault_Handler(void)
{
    printf("Usage fault!\n");
    while (1);
}

七、堆栈信息打印

  1. 在 FreeRTOS 中,每个任务都有其独立的栈空间。如果某个任务的栈空间不足,会引发栈溢出,导致系统崩溃。因此,监控任务的栈使用情况非常重要。
  2. 步骤1:获取任务栈的剩余大小
    • 可以使用 uxTaskGetStackHighWaterMark() 函数获取某个任务的栈剩余量,单位是 4 字节。这个函数会返回任务曾经使用过的最小栈空间,即可以用来评估是否需要调整任务的栈大小。
UBaseType_t freestacksz;
freestacksz = uxTaskGetStackHighWaterMark(task1Handle); // 获取任务1的剩余栈大小
printf("Task1 remaining stack: %lu bytes\n", freestacksz * 4);

默认情况下,这个功能是关闭的。可以通过 CubeIDE 打开:
CubeIDE -> Middleware -> FreeRTOS -> Config Parameters -> Include uxTaskGetStackHighWaterMark
  1. 步骤2:任务栈分配调整
  • 创建任务时,建议给任务分配稍大的栈空间,初始值可以是 128 * 3(表示 128 字×3),并通过 uxTaskGetStackHighWaterMark() 动态调整,确保每个任务有足够的栈空间。

八、系统任务状态打印

  • 可以打印系统中所有任务的运行状态,方便整体调试。
  • 获取所有任务的状态:使用 vTaskList() 函数可以获取当前系统中所有任务的运行信息,包括任务名、任务状态、任务优先级、剩余栈大小等。
char *pbuffer = (char *)calloc(1, 512); // 为任务信息分配内存
printf("\r\n-name---------stat---Priority--lastword---ID---\r\n");
vTaskList(pbuffer);  // 获取任务列表
printf("%s", pbuffer);  // 打印任务信息
printf("-X:run-B:block-R:ready-D:del-S:suspend-Heap mini %d-\r\n", xPortGetMinimumEverFreeHeapSize());
free(pbuffer);  // 释放内存

//默认该函数关闭,使能  cubeied-middleware->config parameter->
configUSE_TRACE_FACILITY 和 configUSE_STATS_FORMATTING_FUNCTIONS 使能 

示例输出
-name---------stat---Priority--lastword---ID---
taskPrint      	X	0	129	5
IDLE           	R	0	106	6
taskStackDetect	B	0	83	2
taskStackDetect	B	0	143	3
taskStackDetect	B	0	143	4
defaultTask    	B	3	97	1
-X:run-B:block-R:ready-D:del-S:suspend-Heap mini 3592-


X 代表正在运行的任务
B 代表阻塞状态
R 代表就绪状态
S 代表挂起状态
D 代表删除的任务

输出的堆内存剩余量可以通过 xPortGetMinimumEverFreeHeapSize() 获取,帮助我们监控系统堆的使用情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值