多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

STM32从裸机到FreeRTOS入门:任务调度、队列通信与工程实践

STM32从裸机到FreeRTOS入门:任务调度、队列通信与工程实践 很多人在学习嵌入式时都会遇到一个典型的困惑STM32 的裸机开发也就是“超级大循环”风格已经能跑通流水灯、串口打印、按键扫描了接下来到底该学什么网上各种资料东一块西一块今天看 GPIO明天看中断后天又跳到某个外设驱动学了很久还是觉得自己只会“点灯”面对一个稍微复杂的项目完全不知道如何组织代码。我的判断是STM32 入门之后第一个真正的分水岭不是某个复杂外设而是从“裸机顺序执行”升级到“实时操作系统RTOS”。从裸机到 FreeRTOS不是把代码换个写法那么简单而是思维模型的整体切换你要开始把“一个程序”拆成“多个任务”要考虑任务的优先级、调度、通信和资源竞争。这篇文章要解决的正是从零开始学习 STM32 FreeRTOS 的核心问题为什么要学、怎么入门、代码怎么写、遇到问题怎么排查。这篇文章适合两类读者一类是已经能独立完成 STM32 基础外设开发GPIO、定时器、串口、中断正在准备进入 RTOS 阶段的人另一类是已经在项目中使用了 FreeRTOS但对任务调度、队列、信号量等概念还停留在“能用但说不清”状态的人。读完这篇文章你会对 FreeRTOS 的任务、调度、同步与通信机制有一个完整认识并且能照着示例代码在真实的 STM32 工程里跑起来。1. 这篇文章真正要解决的问题很多初学者学 FreeRTOS 都会踩同一个坑跟着教程把代码下载到板子上灯确实闪了串口也打印了但回到自己的项目里还是不知道怎么写。为什么因为大部分教程只教你“怎么把例程跑起来”没教你“为什么这样设计任务”“任务之间如何通信”“遇到优先级和资源竞争怎么处理”。从工程角度看裸机开发和 FreeRTOS 开发面对的复杂度完全不同。裸机程序通常是一个while(1)大循环加上几个中断服务函数。代码一多就会出现几个典型问题实时性无法保证。大循环里某个函数执行时间太长其他模块就会被阻塞。按键扫描、LED 刷新、通信处理互相抢占 CPU。中断和主循环共享变量容易出错。在中断里修改标志位在主循环里查询标志位看似没问题实际上存在竞态条件数据可能被破坏。扩展性差。新加一个功能就要往大循环里再塞一段代码函数之间耦合越来越深最后没人敢改。FreeRTOS 解决的不只是“多个任务同时跑”的问题它提供了一套完整的任务管理、时间管理、内存管理和任务间通信机制。学习 FreeRTOS 的真正价值在于让你用工程化的方式组织嵌入式代码让系统具备更强的实时性、可维护性和可扩展性。从求职面试的角度看熟练使用 FreeRTOS 也是嵌入式岗位的常见要求。2. 核心概念与基础原理FreeRTOS 是一个开源的实时操作系统内核专门为嵌入式设备设计。它的规模很小核心代码只有几个 C 文件但对任务管理、时间管理、内存管理、任务间通信的支持却非常完整。在学习任何代码之前先理解下面这些核心概念。2.1 任务Task任务是 FreeRTOS 中最基本的执行单元。从代码层面看任务就是一个永远不会返回的 C 函数函数原型如下void vTaskFunction(void *pvParameters);任务函数内部通常是一个死循环因为任务在被调度器启动后不应该“结束”。如果任务函数返回了FreeRTOS 会触发断言这通常意味着你的代码写错了。任务创建时每个任务都有自己的栈空间。这正是 RTOS 与裸机的最大区别之一裸机程序只有一个调用栈所有函数共享而 FreeRTOS 中每个任务拥有独立栈任务之间不会互相覆盖栈空间。2.2 调度器Scheduler调度器是 FreeRTOS 内核的核心它决定了“当前时刻哪个任务在运行”。FreeRTOS 支持三种调度方式最常用的是优先级抢占式调度每个任务有一个优先级数值越大优先级越高。高优先级任务就绪时会立即抢占当前正在运行的低优先级任务。相同优先级的任务采用时间片轮转调度。理解抢占式调度时最容易混淆的是“优先级”和“执行时间”。高优先级并不意味着“一直运行”而是“一旦就绪优先获得 CPU”。如果高优先级任务有阻塞等待比如等待队列消息低优先级任务就有机会执行。2.3 延时与阻塞在裸机中延时通常使用HAL_Delay()这是一个忙等待函数会一直占用 CPU。在 FreeRTOS 中建议使用vTaskDelay()或vTaskDelayUntil()。两者最大的区别是vTaskDelay()延时指定的时间是相对当前时刻的延时。vTaskDelayUntil()延时到指定的绝对时间适合周期性任务避免时间漂移。任务调用延时函数后会进入阻塞态CPU 转而执行其他就绪任务。这正是 RTOS 提升 CPU 利用率的关键。2.4 任务间通信真实项目中任务之间往往需要传递数据或同步状态。FreeRTOS 提供了多种机制最常用的是队列Queue用于任务与任务、中断与任务之间传递数据。数据是拷贝传递不是指针传递。信号量Semaphore用于同步和互斥。二值信号量适合“事件发生”通知互斥信号量Mutex用于保护共享资源。事件组Event Group用于任务等待多个事件组合满足条件。2.5 内存管理FreeRTOS 默认提供了 5 种内存管理方案文件名类似heap_1.c到heap_5.c。在 STM32 CubeMX 生成的工程中默认使用的是heap_4.c它支持分配和释放并且能合并相邻的空闲内存块是实际项目中最常用的一种。3. 环境准备与前置条件在开始写代码之前先确认你的开发环境和硬件。下面以市面上最常见的 STM32F103C8T6 最小系统板为例进行说明如果你使用其他型号操作流程完全一致只是引脚和外设配置略有不同。3.1 硬件准备STM32 开发板一块STM32F103C8T6 蓝色板或任意 STM32 系列开发板ST-Link V2 调试器或板载调试器USB 转 TTL 串口模块用于查看串口输出杜邦线若干LED 和按键各一个3.2 软件工具STM32CubeMX图形化配置工具用于生成初始化代码。建议使用较新的稳定版本具体版本以实际安装为准。Keil MDK-ARM编译和调试环境。使用 V5 版本相对稳定兼容 STM32F1 系列。ST-Link Utility 或 STM32CubeProgrammer用于烧录程序。STM32CubeProgrammer 是官方推荐的烧录工具支持 ST-Link、串口、USB 等多种烧录方式。3.3 固件包安装在 STM32CubeMX 中首次创建 STM32F1 系列工程时需要先下载对应的固件包。这里有一个很常见的坑官网下载太慢或者下载到一半失败。更稳妥的方式是在 STM32CubeMX 的 Help 菜单中选择 Manage embedded software packages找到 STM32F1 系列并点击安装。如果网络不稳定可以到 ST 官网手动下载固件包再导入到 CubeMX。4. 基于 STM32CubeMX 创建 FreeRTOS 基础工程STM32CubeMX 最大的价值是让 FreeRTOS 的移植变得非常简单。你不需要手动复制FreeRTOS源码目录也不需要手工配置FreeRTOSConfig.hCubeMX 会自动帮你完成绝大部分工作。这也意味着如果初学者希望理解移植细节可以先不用 CubeMX手动移植一次如果目标是快速把项目跑起来直接用 CubeMX 是最高效的。4.1 CubeMX 工程配置步骤打开 STM32CubeMX新建工程选择STM32F103C8Tx然后按以下步骤配置。第一步配置时钟。在 System Core - RCC 中将 HSE 设置为Crystal/Ceramic Resonator。然后进入 Clock Configuration将系统时钟配置为 72MHz。F103C8 的最大主频是 72MHz这是标准配置。第二步配置调试接口。在 System Core - SYS 中将 Debug 设置为Serial Wire。这一步很关键如果配置成No Debug下载一次程序后下一次 ST-Link 可能无法连接芯片因为 SWDIO/SWCLK 引脚被复用了。如果已经出现“Failed to connect”的情况可以在 STM32CubeProgrammer 里选择 Connect under reset把芯片恢复出来。第三步配置 GPIO。在 Pinout Configuration 视图下点击左侧引脚图。将一个普通引脚设置为 GPIO_Output用于控制 LED比如 PA5对应 STM32F103C8T6 蓝色板上的板载 LED再选一个引脚作为按键输入比如 PA0配置为 GPIO_Input。第四步配置串口。左侧 Categories 中选择 Connectivity - USART1开启 UART 模式波特率 115200数据位 8无校验停止位 1。串口用于打印任务运行状态。第五步配置 FreeRTOS。左侧 Middleware and Software Packs 中选择 FREERTOSInterface 选择CMSIS_V1。CMSIS_V1 是 ARM 官方的 RTOS API 标准封装适合初学者。然后在 Tasks 标签页中删除默认的defaultTask我们后面会创建自己的任务。到这里CubeMX 的配置就完成了。点击右上角的GENERATE CODE选择一个工程目录Toolchain/IDE 选择MDK-ARM V5生成代码。注意工程路径不要存在中文字符否则 Keil 编译可能报错。4.2 生成的工程结构CubeMX 生成的代码中FreeRTOS 相关的文件位于Middlewares/Third_Party/FreeRTOS目录下Middlewares/Third_Party/FreeRTOS/Source/ ├── croutine.c ├── event_groups.c ├── list.c ├── queue.c ├── stream_buffer.c ├── tasks.c ├── timers.c └── portable/其中tasks.c是任务调度核心queue.c是队列和信号量的底层实现portable目录存放与具体芯片架构相关的移植代码。这些文件不需要你修改编译时直接参与编译即可。5. 手写第一个 FreeRTOS 程序多任务 LED 与串口打印下面进入本文的核心实操部分。我们创建三个任务LED 闪烁任务以 500ms 周期翻转 LED。串口打印任务周期性打印一条系统运行信息。按键监测任务检测按键按下并通过队列通知打印任务输出一条按键消息。通过这三个任务你可以同时掌握任务创建、任务延时、队列通信、任务优先级这几个 FreeRTOS 最核心的用法。5.1 头文件与全局变量在main.c中添加 FreeRTOS 相关的头文件和变量/* 文件路径Core/Src/main.c */ #include FreeRTOS.h #include task.h #include queue.h /* 任务句柄 */ TaskHandle_t ledTaskHandle NULL; TaskHandle_t printTaskHandle NULL; TaskHandle_t keyTaskHandle NULL; /* 按键消息队列句柄 */ QueueHandle_t keyQueueHandle NULL; /* 按键消息结构体 */ typedef struct { uint8_t key_id; uint8_t event; } KeyMessage_t;任务句柄用于控制任务比如删除任务、获取任务状态等。队列句柄用于向队列发送和接收数据。5.2 任务函数实现在main.c中编写三个任务函数/* 文件路径Core/Src/main.c */ /* LED 闪烁任务 */ void vLED_Task(void *argument) { while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); vTaskDelay(pdMS_TO_TICKS(500)); } } /* 串口打印任务 */ void vPrint_Task(void *argument) { uint8_t counter 0; KeyMessage_t key_msg; while (1) { /* 每隔 1000ms 从队列读取一次消息设置超时时间为 0不阻塞任务 */ if (xQueueReceive(keyQueueHandle, key_msg, 0) pdPASS) { char buf[64]; sprintf(buf, [KEY] id%d event%d\r\n, key_msg.key_id, key_msg.event); HAL_UART_Transmit(huart1, (uint8_t *)buf, strlen(buf), 100); } else { char buf[64]; sprintf(buf, [SYS] counter%d, free heap%d\r\n, counter, (int)xPortGetFreeHeapSize()); HAL_UART_Transmit(huart1, (uint8_t *)buf, strlen(buf), 100); } vTaskDelay(pdMS_TO_TICKS(1000)); } } /* 按键监测任务 */ void vKey_Task(void *argument) { KeyMessage_t key_msg; while (1) { if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET) { /* 按键按下 */ key_msg.key_id 0; key_msg.event 1; /* 向队列发送消息如果队列满等待 10ms */ xQueueSend(keyQueueHandle, key_msg, pdMS_TO_TICKS(10)); /* 等待按键释放避免重复触发 */ while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET) { vTaskDelay(pdMS_TO_TICKS(20)); } } vTaskDelay(pdMS_TO_TICKS(10)); } }5.3 在 main 函数中创建任务在main.c的main函数中在osKernelStart()之前创建任务和队列/* 文件路径Core/Src/main.c */ int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); /* 创建按键消息队列队列长度为 5每个消息占用一个 KeyMessage_t 大小 */ keyQueueHandle xQueueCreate(5, sizeof(KeyMessage_t)); /* 创建任务 */ xTaskCreate(vLED_Task, led, 128, NULL, 1, ledTaskHandle); xTaskCreate(vPrint_Task, print, 256, NULL, 2, printTaskHandle); xTaskCreate(vKey_Task, key, 128, NULL, 1, keyTaskHandle); /* 启动调度器 */ osKernelStart(); while (1) { } }注意任务栈大小的单位是“字”不是“字节”。STM32F103 是 32 位处理器1 个字等于 4 字节。所以上面的 128 字 512 字节256 字 1024 字节。5.4 代码关键逻辑说明先看xTaskCreate的参数。第一个参数是任务函数指针第二个是任务名字主要供调试使用第三个是任务栈大小如果任务里有sprintf这种需要较大栈空间的调用建议给足 256 字以上第四个是传递给任务函数的参数没有则填 NULL第五个是优先级第六个是任务句柄指针。关于优先级的取值FreeRTOS 中优先级数值越大优先级越高。所以上面的代码中print任务优先级为 2比led和key都高。当print任务阻塞在vTaskDelay时CPU 才能执行低优先级任务。再说队列。xQueueCreate(5, sizeof(KeyMessage_t))创建一个可以存放 5 条按键消息的队列。发送端使用xQueueSend接收端使用xQueueReceive。队列中的数据是拷贝传递也就是说发送端修改局部变量key_msg不会影响队列中已经保存的数据。5.5 注意事项在 FreeRTOS 的中断服务函数中不能直接调用我们上面写的这些任务函数。中断中如果需要发数据给任务需要使用带FromISR后缀的 API比如xQueueSendFromISR。如果要在中断里调用 HAL 自带的回调函数也要尽量保持精简不要在中断里做耗时操作。另一个容易出错的地方是sprintf。虽然上面的示例代码里打印任务用了sprintf但实际项目中要小心sprintf格式化比较复杂会占用较多栈空间而且运行时间不稳定。如果只是打印整数可以自己写一个简单的整数转字符串函数或者使用snprintf并限制输出长度。在嵌入式项目中标准库的printf重定向也经常引起堆栈问题建议使用更轻量的日志输出方式。6. 运行结果与效果验证将代码编译下载到开发板打开串口调试助手波特率设置为 115200你会看到类似如下的输出[SYS] counter0, free heap8464 [SYS] counter1, free heap8464 [KEY] id0 event1 [SYS] counter2, free heap8464 [SYS] counter3, free heap8464同时LED 以约 500ms 的间隔翻转。按下按键后串口会多输出一条[KEY] id0 event1消息。这里有一个值得注意的点free heap的值。它表示 FreeRTOS 堆中剩余可用的内存大小。如果这个值不断减少说明有内存泄漏。常见的内存泄漏原因包括任务里动态分配了内存但没有释放或者队列、信号量创建次数过多。验证任务是否正常运行的另一个方法是观察 LED 闪烁频率是否稳定。如果 LED 闪烁有明显抖动通常意味着某个高优先级任务占用了太多 CPU 时间或者任务里存在阻塞调用导致调度不及时。可以从以下几个方向排查打开调试模式在vLED_Task里打断点看是否能正常进入。使用逻辑分析仪测量 LED 引脚电平变化的时间间隔。检查configUSE_TIME_SLICING和configTICK_RATE_HZ配置项是否合理。7. 常见问题与排查思路FreeRTOS 在 STM32 上的移植虽然简单但运行起来后新手遇到的问题往往非常集中。下面把最高频的几类问题整理成表格方便你遇到问题时快速定位。问题现象可能原因排查方式解决方案下载一次程序后 ST-Link 连接失败CubeMX 中 Debug 配置成了 No Debug按住复位键再点下载或在 STM32CubeProgrammer 中选 Connect under reset将 SYS - Debug 设置为 Serial Wire重新烧录任务不运行系统卡死在osKernelStart()堆空间不足或任务栈空间不足检查configTOTAL_HEAP_SIZE查看串口 HardFault 信息增大configTOTAL_HEAP_SIZE为任务分配更大栈空间串口打印乱码波特率不匹配或时钟配置错误检查串口助手波特率与 CubeMX 中设置是否一致统一波特率检查 HSE 时钟电路和 SystemClock_Config任务只在开机运行一次后面不再执行任务函数返回了或任务被vTaskDelete(NULL)删除在任务函数的 while(1) 里打断点查看任务状态任务函数必须为无限循环不要有 return 语句按键按下后串口没有输出队列创建失败或按键任务优先级太低检查xQueueCreate返回值是否为 NULL增大堆空间检查按键初始化是否正常程序中调用HAL_Delay()后任务调度异常HAL_Delay()使用 SysTick 中断与 FreeRTOS 心跳冲突在 FreeRTOS 工程中尽量避免使用HAL_Delay()改用vTaskDelay()或osDelay()中断中调用队列发送函数后系统崩溃在中断里使用了普通 API而不是 FromISR 版本查看编译器警告信息和任务栈回溯中断中使用xQueueSendFromISR、xSemaphoreGiveFromISR等带 FromISR 后缀的 API值得一提是HAL_Delay()的问题。CubeMX 生成的 FreeRTOS 工程默认使用SysTick作为时间基准而 HAL 库的HAL_Delay()也依赖于SysTick。两者会互相干扰导致延时异常或者调度器不工作。如果在 FreeRTOS 模式下使用 HAL 库可以在 CubeMX 中把HAL_InitTick()切换到其他定时器作为时间基准或者直接禁用 HAL 的 tick 中断统一使用 FreeRTOS 的延时函数。7.1 堆栈溢出检测任务栈溢出是 FreeRTOS 中非常隐蔽的问题表现可能是任务随机卡死、数据被改写、或者运行一段时间后程序崩溃。FreeRTOS 提供了两种栈溢出检测方法在FreeRTOSConfig.h中设置#define configCHECK_FOR_STACK_OVERFLOW 2configCHECK_FOR_STACK_OVERFLOW设为 1 时只在任务切换时检测栈指针是否越界设为 2 时还会检测任务栈末尾的“金丝雀”值是否被破坏。启用检测后如果栈溢出发生系统会调用vApplicationStackOverflowHook你可以在该函数里设置一个断点或者点亮一个错误 LED 用于提示。在实际使用时不要依赖栈溢出检测来保底。更稳妥的做法是在任务调试阶段把任务栈大小故意改小触发溢出后用vApplicationStackOverflowHook打印栈使用情况再反过来确定合理栈大小。7.2 优先级反转问题当多个任务共享一个临界资源时有可能出现“低优先级任务占用资源高优先级任务无法运行中优先级任务不断抢占”的情况。FreeRTOS 提供了互斥信号量Mutex以及优先级继承机制来解决这个问题。优先级继承的意思是当一个低优先级任务持有互斥信号量时如果高优先级任务在等待这个信号量低优先级任务会临时提高自己的优先级避免被中优先级任务抢占从而尽快释放信号量。在实际项目中如果发现一个高优先级任务总是被低优先级任务拖累最终运行时间不达标优先检查是否使用了互斥信号量保护共享资源而不仅仅是普通的二值信号量。8. 从入门到进阶FreeRTOS 工程中的最佳实践8.1 任务划分的思维模型任务划分是 FreeRTOS 工程设计的起点也是经验积累的核心。一个常用的划分原则是按功能模块划分每个外设或功能模块一个任务如按键任务、显示任务、通信任务。按实时性划分对实时性要求高的放在高优先级如电机控制、电流环对实时性要求低的放在低优先级如界面刷新、日志打印。按频率划分执行频率高的任务任务周期短优先级可以高一些执行频率低的任务优先级可以低一些。新手最容易犯的错误是任务划分过细。比如把 LED 闪烁、按键扫描、温度采集分别创建三个任务却没有考虑它们之间是否存在依赖关系。任务越多调度开销越大任务间通信越复杂。切记任务不是越多越好够用、层次清晰才是核心。8.2 优先级设计原则优先级设计是整个系统稳定性的关键。常见的做法是给实时性任务一个明确的优先级等级划分比如优先级任务类型举例最高7高速控制、安全保护电机电流环、故障保护较高5-6中等实时性控制通信协议处理、传感器读取普通2-4过程性任务键盘扫描、液晶显示、逻辑控制较低1后台任务、统计任务日志记录、系统监控项目设计初期不要把所有任务的优先级都放在同一档否则调度器的时间片轮转机制会让每个任务都均匀分到时间实时性无法保证。同一优先级任务过多时还要注意时间片长度的配置。8.3 资源保护与通信规范多个任务访问同一个全局变量时不能简单地在变量定义前加volatile就认为解决了问题。volatile只是告诉编译器不要优化对变量的访问它并不能解决多任务并发访问的竞态条件。正确的做法是使用临界区taskENTER_CRITICAL()/taskEXIT_CRITICAL()互斥信号量或者把数据放到队列中进行传递。从架构角度讲最推荐的方式是任务之间不直接共享全局变量而是通过队列传递数据。这样不仅避免了竞态问题还让代码的逻辑更清晰——每个任务只关心自己的输入和输出。8.4 中断与任务的交互中断是嵌入式系统中响应外部事件最及时的手段。在 FreeRTOS 工程中中断服务函数应该足够短只做两件事清除中断标志位。使用带FromISR后缀的 API 向任务发送通知或向队列/信号量写入数据。真正的数据处理放在任务中完成。使用 FreeRTOS 的任务通知Task Notification功能通常比信号量更高效但任务通知一次只能通知一个任务并且不支持队列缓冲所以要根据场景选择合适的机制。8.5 低功耗与 Tickless 模式如果项目对功耗有要求可以启用 FreeRTOS 的 Tickless 低功耗模式。启用后当系统空闲时会进入低功耗状态并且动态调整系统节拍从而减少唤醒次数。在 CubeMX 中找到 FREERTOS 配置将configUSE_TICKLESS_IDLE选项打开即可。但要注意进入低功耗前需要正确处理与外设的关系比如关闭不必要的时钟和电源域。8.6 使用 Trace 工具辅助开发当系统任务数量变多调度行为变得复杂时单靠日志很难定位问题。FreeRTOS 支持SystemView 等可视化跟踪工具。通过配置configUSE_TRACE_FACILITY、configUSE_STATS_FORMATTING_FUNCTIONS你可以输出每个任务的运行状态、CPU占用率和栈使用情况。这些数据对排查“某个任务为什么不跑”“哪个任务占用CPU过高”非常有用。8.7 代码命名与注释规范FreeRTOS 官方 API 都有清晰的前缀规则v表示返回 voidx表示返回非 voidpv表示返回 void 指针ux表示返回无符号整数。自己在编写任务函数时沿用这种风格代码会更容易阅读。同时每个任务函数顶部应该写清楚任务的职责、优先级选择原因、与哪些任务通信、使用哪个队列或信号量。9. 总结与后续学习方向这篇文章从“为什么裸机开发不够用”开始完整走了一遍 STM32 FreeRTOS 零基础入门流程用 STM32CubeMX 创建基础工程手写三个任务演示 LED 控制、串口打印和按键处理再结合队列实现任务间通信。这些都跑通之后你就不再只是“会点灯”而是拥有了一套组织复杂嵌入式系统的思维框架。接下来值得深入的方向有三个第一个是手动移植一次 FreeRTOS。不使用 CubeMX从一个空的 STM32 工程开始把 FreeRTOS 源码加进来手动配置FreeRTOSConfig.h。这个过程会让你真正理解移植需要哪些文件、哪些配置与芯片相关面试被问到“FreeRTOS 怎么移植”时也能答得清楚而不是只能说“我用 CubeMX 勾了一下”。第二个是深入看 FreeRTOS 内核源码。任务切换的完整流程、vTaskDelay的实现、队列的阻塞与唤醒机制这些源码并不算难读但价值极高。读源码时建议配合调试器单步跟踪观察任务状态如何从就绪切换到阻塞再切换回来。理解了内核行为很多诡异的问题比如“明明设置了优先级怎么还是没被调度”就会豁然开朗。第三个是把 FreeRTOS 用在实际项目中。可以尝试写一个“多功能传感器数据采集系统”多个传感器任务采集数据一个显示任务刷新 OLED一个通信任务通过串口与上位机交互。项目不一定要很大但它能逼迫你面对真实工程中一定会遇到的任务划分、数据同步和分层设计问题。从“超级大循环”到“事件驱动、多任务协同”这确实是嵌入式架构升级的分水岭也是嵌入式开发从入门走向进阶的必经之路。如果你正在用 STM32 做开发建议把这篇文章收藏备用。当你从 FreeRTOS 基础功能走向实际项目时最需要的不是更多代码而是这些“为什么”层面的判断力为什么任务要这样划分为什么优先级要这样设计为什么通信要选择队列而不是全局变量。这些判断才是入门与进阶之间真正的分界线。
返回列表