跳转到内容
新建笔记

FreeRTOS M7 配置:堆栈、调度与中断优先级

阅读 FreeRTOSConfig.h 时,要先得到预处理后的有效配置,再讨论调度、内存和中断。本文以原笔记中的 FreeRTOS Kernel V10.4.3、i.MX8MP Cortex-M7 配置为对象;芯片定义参照 MCUXpresso SDK MCUX_2.16.000 的 MIMX8ML8,不把这两个版本混称为同一套 SDK。

原配置的三个关键结论是:堆大小实际为 10 KiB;200 Hz 节拍间隔为 5 ms;四个 NVIC 优先级位、系统调用阈值为 2 时,逻辑优先级 2~15 的外设中断才可在满足其他条件时使用 …FromISR(),0 和 1 不可使用。

原配置先定义 10 KiB,后面的 40 KiB 被 #ifndef 包住:

#define configTOTAL_HEAP_SIZE ((size_t)(10 * 1024))
#ifndef configTOTAL_HEAP_SIZE
#define configTOTAL_HEAP_SIZE ((size_t)(40 * 1024))
#endif

因为进入条件判断前该宏已经定义,第二个分支被跳过。它没有发生“后一次覆盖前一次”。configUSE_MUTEXES 的 #undef 后重新定义为 1,则只是重复确认启用互斥锁。整理配置时应只保留一处有效定义。

SystemCoreClock 是软件维护的变量,configCPU_CLOCK_HZ 引用它不代表自动侦测硬件。修改 M7 时钟后,应按 BSP 更新该值,并重新处理节拍时钟;调度器运行后改变量本身不会自动重配 SysTick。

原配置有效值含义与限制
configUSE_PREEMPTION1启用抢占式调度;高优先级就绪任务可以抢占低优先级任务
configTICK_RATE_HZ200一个 tick 为 5 ms;延时到期只使任务就绪,实际执行还受其他任务和中断影响
configMAX_PRIORITIES5任务优先级为 0~4,数值越大任务优先级越高;不要与 NVIC 的方向混淆
configUSE_TIME_SLICING0关闭由周期 tick 触发的同优先级时间片轮转;阻塞、主动让出和其他调度事件仍可能切换同级任务
configIDLE_SHOULD_YIELD1空闲任务可向其他空闲优先级就绪任务让出处理器,不是所有任务的让出开关
configUSE_TICKLESS_IDLE0未启用 tickless idle;设为 1 仍需端口和板级低功耗时钟、唤醒与计时配合
configUSE_16_BIT_TICKS0本端口使用 32 位 tick;200 Hz 下完整计数周期约 248.55 天,应用仍须按回绕语义比较时间
configMAX_TASK_NAME_LEN20包含字符串结束符的任务名称存储上限;原文另提的 16 是可选值

pdMS_TO_TICKS(1) 在 200 Hz 配置下可能为 0。需要等待至少一个调度 tick 的场景,应明确处理转换结果;微秒级外设时序由合适的硬件定时器完成。xTaskDelayUntil() 用于保持相对固定的周期基准,仍不保证任务在到期的同一瞬间开始执行。

原配置启用动态分配、关闭静态分配,选择 10 KiB FreeRTOS 堆。configFRTOS_MEMORY_SCHEME=4 是 SDK 工程选择分配实现的约定,上游内核不会单凭这个宏自动链接 heap_4.c;构建系统必须实际编入相应文件,而且不能同时编入多个提供 pvPortMalloc() 的 heap 实现。

原配置有效值解释
configSUPPORT_DYNAMIC_ALLOCATION1允许动态创建相应内核对象;不是所有系统都必须启用
configSUPPORT_STATIC_ALLOCATION0原工程未启用静态创建;静态模式可单独使用或与动态模式共存
configTOTAL_HEAP_SIZE10240 字节供 heap_4.c 的堆区域;不等于芯片总 RAM,也不包含所有静态数据和中断栈
configAPPLICATION_ALLOCATED_HEAP0由 heap 实现定义堆数组;设为 1 时应用需提供合适的 ucHeap[]
configMINIMAL_STACK_SIZE90 个 StackType_t动态空闲任务的栈深度基准,不是对所有任务强制规定的最小值;本 32 位端口为 360 字节
configTIMER_TASK_STACK_DEPTH180 个 StackType_t定时器服务任务为 720 字节;复杂回调可能需要更大栈
configNUM_THREAD_LOCAL_STORAGE_POINTERS5每个任务提供五个 TLS 指针槽,增加任务控制块占用;不自动分配指针所指向的对象
configUSE_NEWLIB_REENTRANT0未为任务启用该 Newlib 重入支持;库线程安全和分配行为仍须检查

估算应包含任务栈、实际版本/编译配置的 TCB、队列结构、队列元素、定时器服务任务、定时器命令队列及分配器开销和对齐。原文“TCB 约 100 字节、队列结构约 80 字节、预留 20%”只能作为早期估算思路,不能当 ABI 大小或容量保证。应结合链接 map、创建返回值、xPortGetFreeHeapSize()、xPortGetMinimumEverFreeHeapSize() 和任务栈高水位评估峰值;空闲堆总量也不能独自说明最大连续可分配空间。

原文列出的 10/20/40 KiB 堆、5/7 个任务优先级、90/128 个栈字,以及 200/1000 Hz 节拍都属于应用选择。增加数值会改变资源消耗或调度粒度,不会自动让配置适合“生产环境”。

MIMX8ML8 的 CMSIS 头给出 __NVIC_PRIO_BITS=4,因此逻辑优先级为 0~15,共 16 级。CMSIS 的 NVIC_SetPriority() 接收未左移的逻辑值;FreeRTOS 端口的寄存器屏蔽值放在 8 位优先级字段高四位:

配置原值用途
configLIBRARY_LOWEST_INTERRUPT_PRIORITY15最低逻辑优先级
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY2可调用中断安全 API 的最高紧迫度边界,边界本身允许
configKERNEL_INTERRUPT_PRIORITY15 << 4 = 0xF0供端口使用的最低优先级编码
configMAX_SYSCALL_INTERRUPT_PRIORITY2 << 4 = 0x20供端口临界区使用的屏蔽阈值,不能为 0

在所有有效位均用于抢占优先级、端口设置正确的前提下:

  • 逻辑优先级 0、1:高于系统调用边界,不调用 FreeRTOS 内核 API,包括 …FromISR()。
  • 逻辑优先级 2~15:可调用该 ISR 场景支持的 …FromISR(),不能改用会阻塞的普通任务 API;需要切换任务时遵守端口的 ISR 退出让出约定。
  • 中断默认优先级常为 0,应在启用中断前主动配置。四个优先级位并不代表自动配置好了优先级分组。

V10.4.3 Cortex-M7 端口直接断言当前中断的编码优先级 >= 系统调用阈值,并检查 NVIC 分组。taskDISABLE_INTERRUPTS() 在此端口通过 BASEPRI 屏蔽可与内核交互的中断,不等于屏蔽 NMI、HardFault 或所有高紧迫度中断。

5. 同步、定时器、调试及可选 API

跳转到“5. 同步、定时器、调试及可选 API”
宏组原配置使用要点
任务通知、互斥锁、递归互斥锁、计数信号量均为 1按对象语义选择;互斥锁供任务使用,不能在 ISR 中获取
队列注册表、队列集8、0注册表便于调试识别;队列集未启用
软件定时器configUSE_TIMERS=1回调在定时器服务任务上下文执行,避免长时间阻塞
定时器任务优先级、队列长度4、10服务任务属于最高任务优先级;命令队列仍可能满,要检查 API 返回值
idle、tick、malloc-failed、daemon-startup hooks均为 0启用对应钩子时要实现正确签名与上下文约束;idle hook 不应阻塞,tick hook 在 ISR 中执行
栈溢出检查configCHECK_FOR_STACK_OVERFLOW=0原配置没有此检查;调试时可评估 2,并实现 vApplicationStackOverflowHook(),它不能发现所有内存损坏
trace、运行时间统计、格式化统计1、0、0trace 开关不是自动保存完整跟踪日志;运行时间统计需提供独立时间基准和配套宏
协程、协程优先级数量0、2协程未启用,后一个参数不使其自动启用
新旧 API 兼容configENABLE_BACKWARD_COMPATIBILITY=0关闭旧类型名称兼容;不应与某一宏仍支持旧拼写混淆
已废弃 alternative APIconfigUSE_ALTERNATIVE_API=0上游 V10.4.3 不支持将该值设为非零
tasks.c 扩展头configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H=1构建必须提供 freertos_tasks_c_additions.h;常用于 SDK 调试扩展,不是上游必需文件

原配置开启的可选入口为 vTaskPrioritySet、uxTaskPriorityGet、vTaskDelete、vTaskSuspend、周期延时、vTaskDelay、xTaskGetSchedulerState、xTaskGetCurrentTaskHandle、xTimerPendFunctionCall、xTaskResumeFromISR。关闭的是栈高水位、空闲任务句柄、eTaskGetState、xTaskAbortDelay 和按名称查任务句柄。对应的每个 INCLUDE_… 在下面头文件中保留,避免“功能已编入”的误判。

V10.4.3 仍识别 INCLUDE_vTaskDelayUntil 并将它映射为 INCLUDE_xTaskDelayUntil;两者不能同时定义。下面使用新拼写,数值仍为 1。

这是保留原有效参数的学习基线。选择实际 MIMX8ML8 CPU 型号、配置工具链浮点 ABI、加入对应 Cortex-M7 端口和 heap_4.c、提供启动代码并连接三个异常入口,仍是工程必须完成的部分。这里只把原 SDK 专用的 tasks.c 附加头开关改为 0,以便独立核对上游内核;若项目依赖该调试扩展,应恢复为 1 并提供真实文件。

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#include <stddef.h>
#include <stdint.h>
#include "fsl_device_registers.h"
extern uint32_t SystemCoreClock;
#define configUSE_PREEMPTION 1
#define configUSE_TICKLESS_IDLE 0
#define configCPU_CLOCK_HZ (SystemCoreClock)
#define configTICK_RATE_HZ ((TickType_t)200)
#define configMAX_PRIORITIES 5
#define configMINIMAL_STACK_SIZE ((unsigned short)90)
#define configMAX_TASK_NAME_LEN 20
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configUSE_MUTEXES 1
#define configUSE_RECURSIVE_MUTEXES 1
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_ALTERNATIVE_API 0
#define configQUEUE_REGISTRY_SIZE 8
#define configUSE_QUEUE_SETS 0
#define configUSE_TIME_SLICING 0
#define configUSE_NEWLIB_REENTRANT 0
#define configENABLE_BACKWARD_COMPATIBILITY 0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5
#define configFRTOS_MEMORY_SCHEME 4
#define configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H 0
#define configSUPPORT_STATIC_ALLOCATION 0
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configTOTAL_HEAP_SIZE ((size_t)(10 * 1024))
#define configAPPLICATION_ALLOCATED_HEAP 0
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
#define configGENERATE_RUN_TIME_STATS 0
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 0
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES 2
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1)
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2)
#define configASSERT(x) do { if (!(x)) { taskDISABLE_INTERRUPTS(); for (;;) { } } } while (0)
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_xTaskGetSchedulerState 1
#define INCLUDE_xTaskGetCurrentTaskHandle 1
#define INCLUDE_uxTaskGetStackHighWaterMark 0
#define INCLUDE_xTaskGetIdleTaskHandle 0
#define INCLUDE_eTaskGetState 0
#define INCLUDE_xTimerPendFunctionCall 1
#define INCLUDE_xTaskAbortDelay 0
#define INCLUDE_xTaskGetHandle 0
#define INCLUDE_xTaskResumeFromISR 1
#define configPRIO_BITS __NVIC_PRIO_BITS
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY ((1U << (configPRIO_BITS)) - 1)
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 2
#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
#define INCLUDE_xTaskDelayUntil 1
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
#endif

原来的 configX = value 三组“模板”不构成 C 预处理配置,不能直接粘贴为 FreeRTOSConfig.h。采用上面的 #define 形式后,可分别建立资源受限、功能增多和调试构建,但必须按应用实测填写容量及时间参数。调试构建可逐项打开栈高水位、栈溢出/分配失败钩子、运行时间统计和格式化输出;启用的钩子、统计时钟与线程安全打印必须一起实现。

首先检查预处理结果中的唯一宏值,再确认构建确实使用指定 heap 和移植端口。对堆容量、逻辑/寄存器优先级值、tick 转换、栈字节换算做编译断言;检查 xTaskCreate() 和对象创建结果;最后在板上测栈高水位、最差中断延迟、最长任务执行时间和耗电。能够编译只验证声明和配置一致性,不能证明 360 字节空闲栈、720 字节定时器栈或 10 KiB 堆满足具体应用。