5分钟学习笔记(FreeRTOS)(四)

发布时间:2026/7/7 11:49:42
5分钟学习笔记(FreeRTOS)(四) 1.二值信号量1二值信号量可以实现类似互斥的效果因为它只有 1资源可用0资源不可用但是在 FreeRTOS 里不推荐用二值信号量做互斥保护。二值信号量主要用于任务同步尤其是中断和任务之间的同步。互斥量 Mutex 才是专门用于保护共享资源的。因为Mutex 有优先级继承二值信号量没有优先级继承。所以如果你要保护串口打印、I2C、SPI、共享变量应该优先用xSemaphoreCreateMutex();而不是xSemaphoreCreateBinary();2任务中既可以获取信号量也可以释放信号量。中断中通常只释放信号量用来通知任务中断里不能阻塞等待。任务里可以xSemaphoreTake(sem, portMAX_DELAY); xSemaphoreGive(sem);比如任务 A 做完 ADC 数据处理后可以Give一个信号量通知任务 B。中断里常见写法是xSemaphoreGiveFromISR(sem, xHigherPriorityTaskWoken);因为中断里不能使用xSemaphoreTake(sem, portMAX_DELAY)进行阻塞等待;3}任务通知在很多情况下可以替代二值信号量。二值信号量一个“公共信号对象”任务去等它。任务通知直接给“某一个任务”发信号。所以很多一对一通知场景任务通知可以替代二值信号量。二值信号量的典型用法比如 ADC DMA 采样完成后通知任务处理数据。SemaphoreHandle_t adcSem; void App_Init(void) { adcSem xSemaphoreCreateBinary(); } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { BaseType_t xHigherPriorityTaskWoken pdFALSE; xSemaphoreGiveFromISR(adcSem, xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } void AdcTask(void *argument) { while(1) { xSemaphoreTake(adcSem, portMAX_DELAY); // ADC DMA 完成开始处理数据 Adc_Process(); } }这个逻辑是ADC DMA完成中断 ↓ 释放二值信号量 ↓ AdcTask 获取到信号量 ↓ AdcTask 开始处理数据同样的功能用任务通知可以这样写。TaskHandle_t adcTaskHandle NULL; void App_CreateTask(void) { xTaskCreate(AdcTask, AdcTask, 256, NULL, 3, adcTaskHandle); } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { BaseType_t xHigherPriorityTaskWoken pdFALSE; vTaskNotifyGiveFromISR(adcTaskHandle, xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } void AdcTask(void *argument) { while(1) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // ADC DMA 完成开始处理数据 Adc_Process(); } }它的流程是ADC DMA完成中断 ↓ 直接通知 AdcTask ↓ AdcTask 从 Blocked 变成 Ready ↓ AdcTask 运行后处理数据你会发现它和二值信号量很像。为什么任务通知可以替代二值信号量因为二值信号量常见用途是某个事件发生了通知某个任务起来干活。ADC DMA 完成 - 通知 AdcTask USART IDLE 中断 - 通知 UartRxTask CAN 接收中断 - 通知 CanRxTask 按键中断 - 通知 KeyTask 定时器中断 - 通知 ControlTask这些都是一个中断 / 一个任务——通知一个固定任务。这种场景下用任务通知更合适。因为二值信号量需要创建一个内核对象adcSem xSemaphoreCreateBinary();它本质上也是基于队列机制实现的所以需要额外 RAM。任务通知不需要额外创建对象。每个任务本身就自带一个通知值类似这样理解typedef struct { ... uint32_t ulNotifiedValue; ... } TCB_t;也就是说每个任务天然带了一个“小信号量”。所以任务通知可以理解成不用额外创建信号量对象直接往目标任务自己的通知值里写信号。2.计数信号量1二值信号量可以理解为最大计数值为 1 的信号量计数信号量可以理解为最大计数值大于 1 的信号量。使用信号量时任务通常只关心计数值是否大于 0而不关心具体消息内容。因为信号量本身不传数据。2计数信号量常用于两类场景事件计数和资源管理。第一类是事件计数。每发生一次事件就 Give 一次信号量使计数值加 1任务每处理一次事件就 Take 一次信号量使计数值减 1。因此计数值表示已经发生但还没有被处理的事件数量。这个场景下创建计数信号量时通常希望初始计数值为 0。第二类是资源管理。计数值表示当前可用资源的数量。任务想使用资源时先 Take 信号量使可用资源数量减 1任务使用完资源后Give 信号量使可用资源数量加 1。当计数值为 0 时表示没有空闲资源后续任务会阻塞等待。这个场景下创建计数信号量时通常希望初始计数值等于最大计数值。3计数信号量只记录数量不保存具体数据。如果需要传递具体数据应使用队列。这是我让AI帮我生成的一个信号量Give和Take的流程图3.两者区别二值信号量和计数信号量都不传递具体数据只通过计数值表示信号量是否可用。二值信号量的计数值只能是 0 或 1主要用于任务同步常见场景是中断通知任务或者任务通知任务。它表示“事件是否发生”。计数信号量的计数值可以从 0 增加到最大值主要用于事件计数和资源管理。用于事件计数时count 表示还有多少事件没有处理用于资源管理时count 表示还有多少资源可用。两者创建 API 不同二值信号量使用 xSemaphoreCreateBinary()计数信号量使用 xSemaphoreCreateCounting()。但获取和释放通常都使用 xSemaphoreTake() 和 xSemaphoreGive()。