ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FreeRTOS二进制信号量实战:从按键消抖到LED同步的任务间通信

FreeRTOS二进制信号量实战:从按键消抖到LED同步的任务间通信 1. 项目概述从“点灯”到“同步”的思维跃迁搞嵌入式开发的朋友对“按键控制LED”这个经典实验一定不陌生。在裸机编程时代我们通常在一个while(1)主循环里轮询按键状态一旦检测到按下就翻转一下LED的亮灭。代码简单直接但问题也显而易见如果主循环里还有其他耗时任务按键响应就会变得迟钝LED的翻转也可能不及时整个系统的实时性无从谈起。当我们把场景切换到FreeRTOS这样的实时操作系统下这个简单的任务就变成了理解多任务协作和资源同步的绝佳入口。FreeRTOS Binary Semaphore Demo: Button-LED Synchronization这个项目标题精准地指向了RTOS编程的核心痛点之一——任务间同步。它不再是简单地“点亮一个灯”而是要求我们实现“按键任务”和“LED任务”这两个独立执行体之间的精准握手。二进制信号量在这里扮演了那个关键的“信使”角色。这个Demo的价值在于它用一个极其直观的物理现象按键按下LED状态改变揭示了RTOS中抽象但至关重要的通信机制。无论你是刚开始接触FreeRTOS还是已经用它做过项目但对其内核机制一知半解通过亲手实现这个同步过程你都能深刻理解任务如何从“各自为政”的混乱状态通过信号量这类同步原语变得井然有序、响应及时。接下来我们就从设计思路开始一步步拆解这个Demo看看如何用二进制信号量这根“线”把按钮和LED这两个“铃铛”巧妙地串联起来。2. 核心思路为什么是二进制信号量在动手写代码之前我们必须先想清楚面对“按键控制LED”这个需求在FreeRTOS下有多种通信方式可选比如队列、事件标志组、甚至直接使用任务通知。为什么这里偏偏要选用二进制信号量2.1 需求场景与同步原语选型分析我们首先剖析一下这个Demo的核心需求事件驱动LED的状态改变完全由“按键按下”这一外部事件触发。这是一个典型的“发生某事然后做某事”的模式。一对一同步一个生产者按键扫描任务一个消费者LED控制任务。生产者产生事件消费者等待并处理事件。资源计数简单事件本身没有负载数据我们不需要知道按键按了多久或者哪个键被按了在这个Demo里只关心“按下了”这个动作只需要一个“有”或“无”的状态标志。实时性要求按键按下后LED应尽可能快地响应避免肉眼可察的延迟。基于以上分析我们对比几种常见的FreeRTOS通信机制队列功能强大可以传递数据。但在这个场景里我们不需要传递额外数据使用队列有点“杀鸡用牛刀”还会引入不必要的内存和性能开销。事件标志组适合多个任务等待多个事件或者一个任务等待多个事件组合。对于这种简单的一对一同步显得不够直接。任务通知FreeRTOS中非常高效的单任务通知机制。它确实可以模拟二进制信号量的行为并且速度更快、内存占用更小。但对于初学者来说信号量的概念更为通用和经典几乎所有RTOS都有理解信号量是理解更复杂同步机制的基础。二进制信号量它本质上就是一个计数器最大值为1。初始状态为0“无事件”。按键任务释放give信号量将其置1“有事件”LED任务等待take信号量成功取到后信号量恢复为0。它完美匹配了“事件通知”、“一对一”、“无数据”的需求概念清晰API简单。注意在追求极致效率和资源紧张的系统中用任务通知替代二进制信号量是更优选择。但本Demo以教学和理解为先采用二进制信号量更能巩固核心概念。理解了信号量再迁移到任务通知会非常容易。2.2 系统任务划分与数据流设计根据上述选型我们设计两个主要任务Button_Task按键扫描任务职责循环扫描按键GPIO的状态进行消抖处理检测到有效的按键释放事件通常检测释放比检测按下更稳定后释放xSemaphoreGive一个二进制信号量。优先级设置为中等优先级。它需要及时响应硬件中断但又不至于抢占所有其他任务。行为模式它是一个事件生产者主动产生信号。LED_TaskLED控制任务职责尝试获取xSemaphoreTake同一个二进制信号量。如果获取成功说明按键事件发生了则翻转LED的状态亮变灭或灭变亮然后继续等待下一个信号量。优先级可以设置为与按键任务相同或略低的优先级。它的执行完全由信号量触发。行为模式它是一个事件消费者被动等待并处理事件。数据流变得非常清晰硬件中断按键物理动作 - Button_Task检测并消抖- 释放二进制信号量 - LED_Task等待并获取信号量- 执行LED翻转 - 等待下一个信号量这个过程中两个任务之间没有直接函数调用而是通过内核管理的信号量进行解耦。按键任务不知道也不关心是谁处理了事件LED任务不知道事件具体如何产生只关心事件是否发生。这种解耦的设计是构建复杂、可维护RTOS系统的基石。3. 开发环境搭建与工程配置工欲善其事必先利其器。在开始编码前我们需要一个合适的开发环境。这里以最常见的STM32系列MCU配合STM32CubeIDE为例进行说明。其他平台如ESP32、GD32等整体思路完全一致只是工具链和硬件抽象层略有不同。3.1 硬件准备与原理图对接首先你需要一块支持FreeRTOS的开发板比如STM32F103蓝桥杯、STM32F407、ESP32-DevKitC等。确保板上至少有一个可用的用户按键和一个可控制的LED。按键通常接在某个GPIO上配置为上拉输入模式。按键未按下时GPIO读到的应该是高电平由于上拉按下时连接到地变为低电平。LED通常通过一个限流电阻接在GPIO上配置为推挽输出模式。根据电路是共阳极还是共阴极设置输出高电平或低电平来点亮LED。在STM32CubeIDE中你可以通过图形化界面Pinout Configuration轻松配置这些GPIO。例如将PC13假设是用户按键配置为GPIO_Input并选择上拉Pull-up。将PA5假设是LED配置为GPIO_Output。3.2 FreeRTOS在CubeMX/IDE中的使能与配置这是关键一步。在CubeIDE的.ioc文件配置视图中在左侧分类中找到Middleware - FREERTOS。在Mode下拉框中将Disabled改为CMSIS_V2。CMSIS-RTOS V2是一个标准化的RTOS API层它封装了FreeRTOS的原生API使得代码在不同RTOS间移植性更好也更为推荐。进入Configuration选项卡进行关键参数配置Tasks and Queues这里我们可以预先创建任务但为了理解透彻我们选择在代码中动态创建。所以可以先不管。Config parameters展开后重点关注以下几项USE_PREEMPTION: 启用抢占式调度。一定要设为Enabled这才是RTOS的核心。TICK_RATE_HZ: 系统时钟节拍频率。默认1000Hz (1ms)。对于这个Demo100Hz (10ms) 也足够但更高的频率意味着更精细的时间片和更准确的延时。保持1000Hz即可。MAX_PRIORITIES: 最大优先级数。默认7足够用。MINIMAL_STACK_SIZE: 最小任务栈大小单位是字Word32位系统是4字节。默认128字即512字节。这是一个极易踩坑的地方对于简单的任务128字可能够用但为了安全起见尤其是新手建议将自己创建的任务栈大小设置为至少128 * 4 512字节即configMINIMAL_STACK_SIZE * 4或者在代码中直接定义#define TASK_STACK_SIZE 128单位是字即512字节。TOTAL_HEAP_SIZE: FreeRTOS动态管理的内存堆大小。默认是configTOTAL_HEAP_SIZE在FreeRTOSConfig.h中定义。确保它足够大能容纳你的任务栈、信号量、队列等对象。对于这个Demo默认值通常足够。生成代码点击GENERATE CODECubeIDE会自动生成FreeRTOS的初始化代码在freertos.c中、FreeRTOSConfig.h配置文件以及硬件外设的初始化代码。3.3 创建任务与信号量的代码框架生成代码后我们进入main.c的/* USER CODE BEGIN */和/* USER CODE END */注释对之间编写我们的应用代码。这是安全的区域不会被重新生成代码覆盖。首先定义任务句柄和信号量句柄全局变量方便管理/* Private variables ---------------------------------------------------------*/ osThreadId_t buttonTaskHandle; osThreadId_t ledTaskHandle; osSemaphoreId_t binarySemHandle;然后在main()函数中在硬件初始化HAL_Init()SystemClock_Config()和FreeRTOS内核启动osKernelStart()之间创建我们的信号量和任务。int main(void) { HAL_Init(); SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART2_UART_Init(); // 假设初始化了串口用于调试 /* USER CODE BEGIN 2 */ // 1. 创建二进制信号量 binarySemHandle osSemaphoreNew(1, 0, NULL); // 最大计数1 初始计数0 if (binarySemHandle NULL) { Error_Handler(); // 信号量创建失败进入错误处理 } // 2. 创建按键任务 const osThreadAttr_t buttonTask_attributes { .name ButtonTask, .stack_size 128 * 4, // 512字节 .priority (osPriority_t) osPriorityNormal, }; buttonTaskHandle osThreadNew(Button_Task, NULL, buttonTask_attributes); // 3. 创建LED任务 const osThreadAttr_t ledTask_attributes { .name LedTask, .stack_size 128 * 4, // 512字节 .priority (osPriority_t) osPriorityNormal, }; ledTaskHandle osThreadNew(LED_Task, NULL, ledTask_attributes); /* USER CODE END 2 */ osKernelStart(); // 启动RTOS调度器从此处开始任务调度 while (1) {} // 正常情况下不会执行到这里 }这里使用的是CMSIS-RTOS V2的APIosSemaphoreNew,osThreadNew。osSemaphoreNew的第二个参数initial_count设为0意味着信号量初始状态是“不可用”无事件。stack_size我们设置为128 * 4即128字Words在Cortex-M内核中一个字是4字节所以栈大小是512字节。osPriorityNormal是一个中间优先级。4. 核心任务实现按键扫描与信号量释放现在我们来填充Button_Task函数的具体实现。这个任务的核心是可靠的按键检测和准确的信号量释放。4.1 按键消抖的可靠实现方案机械按键在闭合和断开时由于簧片抖动会在几毫秒到几十毫秒内产生一系列毛刺信号。如果不处理一次物理按压可能会被误判为多次按下。软件消抖是必须的。一个经典且可靠的消抖逻辑是“检测稳定状态”法首次检测到按键电平变化比如从高变低表示按下。延时一段时间例如20ms避开抖动期。再次读取按键电平如果仍然是目标状态低电平则确认是一次有效的按键动作。继续等待直到检测到按键释放电平从低变高同样进行消抖确认后才认为一次完整的“按下-释放”动作完成。在RTOS中我们不能使用HAL_Delay()这类阻塞延时因为它会阻塞整个任务甚至可能影响其他任务和系统心跳。必须使用FreeRTOS提供的延时函数osDelay()它会使当前任务进入阻塞状态让出CPU给其他就绪任务。void Button_Task(void *argument) { uint8_t button_state 1; // 假设初始为上拉高电平 uint8_t last_state 1; const TickType_t debounce_delay pdMS_TO_TICKS(20); // 20ms消抖延时转换为系统节拍数 for(;;) { // 读取当前按键GPIO状态 (假设按键按下为0 释放为1) button_state HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin); // 检测下降沿之前是高(1)现在是低(0) - 可能按下了 if ((last_state 1) (button_state 0)) { osDelay(debounce_delay); // 延时消抖 button_state HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin); // 再次确认 if (button_state 0) { // 确认按下 // 等待按键释放 while (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) 0) { osDelay(10); // 每10ms检查一次避免忙等待消耗CPU } // 检测到释放进行释放消抖 osDelay(debounce_delay); if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) 1) { // 确认释放 // 关键步骤释放二进制信号量通知LED任务 osSemaphoreRelease(binarySemHandle); // 可以在这里添加调试信息如通过串口打印Button Pressed Released } } } last_state button_state; // 更新上一次的状态 osDelay(10); // 每10ms扫描一次按键降低CPU占用 } }实操心得osDelay()的参数是TickType_t类型表示延迟的系统节拍数。使用pdMS_TO_TICKS()宏可以将毫秒时间转换为节拍数这样代码更易读且可移植。例如如果configTICK_RATE_HZ 1000那么pdMS_TO_TICKS(20)就等于20个节拍即20ms。如果Tick Rate是100Hz那么pdMS_TO_TICKS(20)就等于2个节拍20ms。务必使用这个宏而不是直接写数字。4.2 信号量释放的时机与注意事项在上面的代码中信号量释放的时机选择在确认按键释放之后。这是有讲究的为什么不在“按下确认”时释放如果按下就触发用户手指按住不放LED会持续翻转吗这取决于LED任务的处理方式。如果LED任务处理很快且信号量被快速取走那么只要按键任务持续产生信号比如在while等待释放的循环里不断释放LED就会疯狂闪烁这不是我们想要的“按一次变一次”的效果。在“释放确认”时释放这模拟了大多数物理交互的逻辑——一个完整的“按压-释放”动作被认为是一次有效的“单击”事件。这符合用户直觉也更容易控制。注意事项osSemaphoreRelease是一个非阻塞调用无论是否有任务在等待该信号量它都会将信号量计数加1如果未达最大值。如果之前计数为0并且有任务正在等待那么等待的任务中优先级最高的那个会被解除阻塞进入就绪状态。要检查osSemaphoreRelease的返回值osStatus_t类型虽然在这个简单Demo中失败概率极低但在严谨的产品代码中必须处理创建或释放失败的情况例如返回osErrorResource表示资源不足。5. 核心任务实现LED控制与信号量等待LED任务相对简单它的核心是耐心等待事件并在事件到来时执行动作。5.1 阻塞等待信号量的最佳实践LED_Task函数将使用osSemaphoreAcquire来等待信号量。这个API有一个关键参数超时时间。void LED_Task(void *argument) { const TickType_t wait_forever osWaitForever; // 定义为无限等待 osStatus_t sem_status; for(;;) { // 尝试获取二进制信号量无限期等待 sem_status osSemaphoreAcquire(binarySemHandle, wait_forever); if (sem_status osOK) { // 成功获取到信号量说明按键事件发生了 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 翻转LED状态 // 可以添加调试信息如翻转计数器 } else { // 获取失败例如超时但这里设置的是无限等待所以理论上不会进来 // 应进行错误处理比如重置系统或记录日志 } // 注意这里没有osDelay任务将在下一次osSemaphoreAcquire调用时阻塞。 } }关键点解析osWaitForever这是一个特殊值表示任务将无限期等待直到信号量可用。这非常适合这种纯粹由事件驱动的任务。任务状态切换当LED_Task执行到osSemaphoreAcquire且信号量计数为0时内核会将此任务置于阻塞态Blocked并将其从就绪列表中移除。此时CPU会去执行其他就绪任务比如Button_Task。当Button_Task释放信号量后内核会将LED_Task重新放回就绪列表。如果LED_Task是当前最高优先级的就绪任务调度器会立刻切换上下文让它开始运行执行LED翻转。没有osDelay任务循环的末尾没有使用osDelay。因为任务在osSemaphoreAcquire处已经阻塞了。如果加了osDelay任务在翻转LED后还会主动阻塞一段时间这会降低对连续按键事件的响应速度。我们的设计目标是“事件到来立即处理”所以不需要额外的延时。5.2 LED状态翻转与任务响应性分析HAL_GPIO_TogglePin是一个简单的硬件操作执行时间极短微秒级。这意味着LED_Task在获取信号量后会非常快地完成工作然后立刻回到等待状态。这保证了系统对下一次按键事件的响应能力。我们可以思考一个边界情况如果用户以极快的速度连续按键会怎样第一次按键释放Button_Task释放信号量计数从0-1。LED_Task获取信号量计数从1-0翻转LED。在LED_Task翻转LED的极短时间内第二次按键动作已经完成并释放了信号量计数从0-1。由于LED_Task很快又回到了osSemaphoreAcquire等待状态它会立刻再次获取到信号量因为计数已经是1并翻转LED。只要Button_Task产生事件的速度不超过LED_Task处理事件的速度系统就能跟上。在这个Demo中处理速度远快于人类按键速度所以响应是实时的。注意这里揭示了一个重要概念——信号量的存储性。二进制信号量像一个开关事件释放操作会被记录下来计数变为1即使当时没有任务在等待。当后续有任务尝试获取时能立即成功。这与“事件标志”的某些模式不同事件标志如果任务未及时响应事件可能会被后续事件覆盖。6. 调试技巧与常见问题排查即使代码逻辑清晰在实际硬件上运行时你仍可能会遇到一些问题。下面是一些常见的坑和调试方法。6.1 调试手段串口打印与逻辑分析仪串口打印最常用在Button_Task中释放信号量后和LED_Task中翻转LED后添加printf语句需重定向printf到串口。输出内容如[BTN] Sem Released\n和[LED] Toggled, Count: %d\n。作用可以清晰看到两个任务的执行顺序和频率确认信号量是否被正确释放和获取。如果看到[BTN]打印了但很久才有[LED]打印说明LED_Task可能被高优先级任务抢占或阻塞了。逻辑分析仪或示波器硬件级将LED引脚和按键引脚接到逻辑分析仪上。作用可以精确测量从按键电平变化到LED电平变化之间的延迟微秒级量化系统的实时性。同时可以观察按键抖动情况验证消抖代码是否有效。6.2 常见问题速查表现象可能原因排查步骤与解决方案按键无反应LED不翻转1. 任务根本没被调度。2. 信号量创建失败。3. 按键GPIO配置错误输入/上拉下拉。4. 消抖逻辑过于严格误过滤了有效按键。1. 检查osKernelStart()是否被调用。在main()的while(1)前设断点看能否执行到。2. 检查osSemaphoreNew和osThreadNew的返回值是否为NULL。3. 用调试器或万用表测量按键按下/释放时GPIO的实际电平与代码逻辑对比。4. 暂时简化消抖逻辑比如只做一次osDelay(20)后直接判断看是否有效。LED状态变化混乱或连续翻转1. 按键消抖失败一次物理按压产生多次触发。2. 信号量释放时机不对如在按下检测中释放且未等待释放。3.LED_Task中翻转后忘了回到等待或在等待前加了不必要的osDelay。1. 用逻辑分析仪看按键波形确认抖动时间调整debounce_delay如从20ms增至30ms。2. 确保信号量在“确认释放”后释放。3. 检查LED_Task循环确保其核心是“等待信号量-处理-等待信号量”。系统运行一段时间后死机1.栈溢出最常见。任务栈分配不足。2. 堆内存耗尽。创建了过多内核对象未删除。3. 在中断服务程序(ISR)中错误使用了阻塞API。1.启用FreeRTOS的栈溢出检测在FreeRTOSConfig.h中将configCHECK_FOR_STACK_OVERFLOW设为1或2。运行时如果溢出会触发钩子函数或断言。2. 增大TOTAL_HEAP_SIZE。使用xPortGetFreeHeapSize()函数打印剩余堆空间监控。3. 确保在ISR中只使用xxxGiveFromISR和xxxReceiveFromISR这类API。按键响应感觉延迟1.Button_Task优先级过低被其他高优先级任务长时间阻塞。2. 系统Tick频率太低如100HzosDelay精度差。3. 消抖延时过长。1. 适当提高Button_Task的优先级。2. 将configTICK_RATE_HZ提高到10001ms。3. 在满足消抖要求下减少debounce_delay或用更高效的消抖算法如状态机。6.3 栈溢出检测的实战配置栈溢出是RTOS开发中最隐蔽的Bug之一。强烈建议在开发阶段启用它。在FreeRTOSConfig.h中找到并修改#define configCHECK_FOR_STACK_OVERFLOW 2然后实现溢出钩子函数void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) { (void)xTask; printf(“[ERROR] Stack Overflow in Task: %s\r\n”, pcTaskName); while(1); // 死循环便于捕获错误 }当检测到栈溢出时这个函数会被调用你可以通过串口看到是哪个任务出了问题然后去相应增加该任务的栈大小。7. 项目进阶与扩展思考当你成功实现了基本的按键LED同步后可以尝试以下扩展这能让你对FreeRTOS和嵌入式系统的理解更深一层。7.1 从二进制信号量扩展到计数型信号量与互斥量计数型信号量想象一个场景一个仓库资源最多可以存放10件货物信号量计数最大为10。生产者Button_Task每按一次键就生产一件货物放入仓库释放信号量计数1。消费者LED_Task每次取走一件货物获取信号量计数-1。如果仓库满了计数10生产者再按键盘就无效释放失败或等待如果仓库空了计数0消费者就要等待。这就是计数型信号量用于管理多个同类资源。只需将osSemaphoreNew的第一个参数改为大于1的值即可。扩展实验修改Demo让按键任务可以连续快速按多次信号量计数累加。LED任务每次获取一个信号量才翻转一次LED观察LED翻转次数是否与快速按键次数匹配。互斥量它本质上是特殊的二进制信号量引入了优先级继承机制。用于保护共享资源如一个全局变量、一个SPI总线。假设有两个任务都要写一个全局数组如果不加保护可能会互相覆盖数据。用互斥量保护任务A在写数组前获取互斥量写完后释放任务B同理。如果任务B尝试获取时发现已被A占用则阻塞。关键是如果低优先级的任务A占有了互斥量而高优先级的任务B在等待系统会临时将A的优先级提升到与B相同以防止中优先级的任务C抢占A导致B被无限期阻塞这就是优先级反转问题。互斥量解决了这个问题。扩展实验创建第三个任务Print_Task它也尝试去翻转LED操作同一个GPIO但GPIO操作不是原子的需要保护。用互斥量来同步LED_Task和Print_Task对LED的访问。7.2 中断服务程序(ISR)中释放信号量在实际项目中按键扫描常用外部中断来实现以获得最快的响应速度而不是在任务中轮询。FreeRTOS允许在中断服务程序(ISR)中释放信号量但必须使用专用的FromISR结尾的API。配置按键GPIO为外部中断模式下降沿/上升沿触发。在CubeMX中生成中断代码在对应的回调函数如HAL_GPIO_EXTI_Callback中编写逻辑。在回调函数中释放信号量void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { BaseType_t xHigherPriorityTaskWoken pdFALSE; // 必须初始化为pdFALSE if (GPIO_Pin KEY_Pin) { // 简单延时消抖在中断中不推荐复杂消抖可用定时器或任务通知。 // 这里假设硬件消抖很好或我们只做简单标记。 // 使用FromISR版本的API xSemaphoreGiveFromISR(binarySemHandle, xHigherPriorityTaskWoken); } // 如果有任务被唤醒且其优先级高于当前被中断的任务需要进行上下文切换 portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }重要xSemaphoreGiveFromISR是FreeRTOS原生API如果你使用CMSIS-RTOS V2需要调用osSemaphoreRelease但要注意CMSIS-RTOS V2的API设计是否完全兼容在ISR中调用通常它内部会处理。最稳妥的方法是查阅你使用的CMSIS-RTOS V2适配层代码。使用原生API通常更直接。此时Button_Task可以简化为一个空循环或者直接删除。按键事件的产生完全由硬件中断驱动实时性最高。7.3 系统资源监控与性能评估一个健壮的系统需要知道自己的运行状态。FreeRTOS提供了丰富的运行时统计功能。任务运行时间统计启用configGENERATE_RUN_TIME_STATSconfigUSE_TRACE_FACILITY等宏并实现portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()和portGET_RUN_TIME_COUNTER_VALUE()。可以获取每个任务占用CPU时间的百分比。堆空间监控定期调用xPortGetFreeHeapSize()和xPortGetMinimumEverFreeHeapSize()了解内存使用情况预防内存泄漏。任务状态查询使用vTaskList()函数需启用configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS可以将所有任务的状态运行、就绪、阻塞、挂起、优先级、栈高水位线等信息格式化成字符串通过串口打印出来。这对于调试复杂任务调度问题非常有用。通过这个看似简单的Button-LED SynchronizationDemo我们实际上串联起了FreeRTOS的任务管理、内存管理、同步机制、中断处理等多个核心概念。从裸机思维过渡到RTOS思维的关键就在于理解并熟练运用这些“通信与同步”的基石。当你下次面对更复杂的多任务系统时不妨先问问自己这几个任务之间是什么关系是生产者-消费者还是需要互斥访问该用信号量、队列还是事件标志想清楚了这些代码结构自然就清晰了。
返回列表