RTOS-F429-HAL-(二值,计数,互斥)信号量(2026/8/2)
2026/8/2 9:47:49 网站建设 项目流程

目录

一:信号量简介

1:信号量分类

2:创建函数API

3:队列和信号量的区别

4:信号量不太占内存

5:操作API

6:二值信号量的初始状态需要我们给

二:二值信号量

1:rtos\11\ 改动清单

freertos_demo.c

main.c

2:实际用法(正点实验过于简单)

3:二值信号量的本质

三:计数信号量

1:rtos\12\ 改动清单

freertos_demo.c

main.c

FreeRTOSConfig.h

2:计数信号量时间轴

3:几个用到的API

① xSemaphoreCreateCounting() — 创建

② xSemaphoreGive() — 释放(计数 +1)

③ xSemaphoreTake() — 获取(计数 -1)

④ uxSemaphoreGetCount() — 查询当前计数

⑤ 跟队列 API 的底层关系

4:实际用法(正点实验过于简单)

四:优先级翻转

rtos\13\ 改动清单

freertos_demo.c

main.c

实验现象:

实验原理:

五:互斥信号量

1:简介

互斥锁 = 二值信号量 + 优先级继承 + 所有权

优先级继承做了什么

继承不能完全消除翻转

中断里不能用互斥锁

2:实验工程差异

3:时间轴对比

4:互斥锁和二值信号量的区别

5:互斥锁 API

① xSemaphoreCreateMutex() — 创建互斥锁

② xSemaphoreGetMutexHolder() — 查锁在谁手上

6:rtos\14\ 改动清单

freertos_demo.c

FreeRTOSConfig.h

main.c

实验现象:

7:实际用法(正点实验过于简单)

裸机做法

FreeRTOS 做法


一:信号量简介

1:信号量分类

信号量(Semaphore) │ ├── 计数信号量 → 0 ~ 上限,管 N 个同类资源(停车场) │ ├── 二值信号量 → 只有 0 和 1,管"一个事件有没有发生" │ └── 互斥锁 → 二值信号量 + 两个特权技能: │ ① 记持有者(谁拿的必须谁还) │ ② 优先级继承(防止高优先级被低优先级的锁憋死) │ └── 递归互斥锁 → 互斥锁 + 同一个人能锁 N 次

2:创建函数API

/* ── 计数信号量:停车场 5 个位,初始 3 个空位 ── */ SemaphoreHandle_t parking = xSemaphoreCreateCounting(5, 3); ​ /* ── 二值信号量:0 或 1,初始 1 表示"就绪" ── */ SemaphoreHandle_t ready = xSemaphoreCreateBinary(); ​ /* ── 互斥锁:保护共享资源,初始是"开锁"状态 ── */ SemaphoreHandle_t lock = xSemaphoreCreateMutex(); ​ /* ── 递归互斥锁:同一个任务能反复锁 ── */ SemaphoreHandle_t rlock = xSemaphoreCreateRecursiveMutex();
API返回值初始值上限谁都能给?
xSemaphoreCreateCounting(5,3)句柄/NULL35
xSemaphoreCreateBinary()句柄/NULL0 或设 11
xSemaphoreCreateMutex()句柄/NULL1(开锁)1❌ 谁拿的谁给
xSemaphoreCreateRecursiveMutex()句柄/NULL递归计数 0递归计数累加❌ 谁拿的谁给

xSemaphoreCreateBinary(),xSemaphoreCreateBinary(),xSemaphoreCreateRecursiveMutex()三个都不用参数:

// semphr.h 里的宏/函数声明 ​ SemaphoreHandle_t xSemaphoreCreateBinary( void ); // 行 148,无参数 SemaphoreHandle_t xSemaphoreCreateMutex( void ); // 行 704,无参数 SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void ); // 无参数 内部都是调 `xQueueGenericCreate` 或 `xQueueCreateMutex`,参数自己填好了: // 二值信号量 → 底层调的是 xQueueGenericCreate(1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE) // ↑ ↑ ↑ // 长度=1 大小=0(信号量不存数据) 类型=二值 ​ // 互斥锁 → 底层调的也是 queue.c 的函数,长度=1, 大小=0, 类型=MUTEX // 递归互斥锁 → 同上,类型=RECURSIVE_MUTEX

不需要传参数,因为信号量/互斥锁的参数都是固定的——长度永远是 1(或计数信号量自己传上限),uxItemSize永远是 0。所以 API 直接封装好了,你无参调就行。

3:队列和信号量的区别

队列和信号量创建的结构体一模一样,区别只在有没有数据区。

创建队列: 创建信号量: xQUEUE 控制区(固定) xQUEUE 控制区(固定) ┌──────────────────────┐ ┌──────────────────────┐ │ uxLength = 5 │ │ uxLength = 5(上限) │ │ uxItemSize = 4 │ ← 每条 4 字节 │ uxItemSize = 0 │ ← 不存数据! │ uxMessagesWaiting = 0│ ← 当前 0 条信息 │ uxMessagesWaiting = 3│ ← 当前计数 3 │ pcHead ──────────┐ │ │ pcHead = NULL │ ← 没有数据区 │ pcWriteTo │ │ │ pcWriteTo = NULL │ │ xTasksWaiting... │ │ │ xTasksWaiting... │ └──────────────────┼───┘ └──────────────────────┘

举例子:xQueueCreate(2, sizeof(uint8_t)) 创建队列 和 xSemaphoreCreateCounting(5, 3)创建计数信号量

xQueueCreate(2, sizeof(uint8_t)) xSemaphoreCreateCounting(5, 3) │ │ ▼ ▼ uxLength = 2 ← 第一个参数 uxLength = 5 ← 第一个参数(上限) uxItemSize = 1 ← 第二个参数(sizeof) uxItemSize = 0 ← 固定为0,信号量不存数据 uxMessagesWaiting = 0 ← Reset 归零 uxMessagesWaiting = 3 ← Reset归零后,手动覆盖成3 ↑ 第二个参数(初始值) ​ 数据区 = 2×1 = 2 字节 数据区 = 5×0 = 0(不分配) pcHead = 指向数据区 pcHead = 指向结构体自己(占位用)

一句话:队列两个参数分别对应uxLengthuxItemSize。信号量两个参数对应uxLength(上限)和uxMessagesWaiting(初始计数),uxItemSize内部填 0。计数信号量多一步——Reset 归零后,把第二个参数手动写进uxMessagesWaiting

那为啥创建队列这个uxMessagesWaiting 为0???????????????????

它初始就是 0——这个值是对的。

队列创建时: uxMessagesWaiting = 0 → "队列里还没存任何消息" ✅ 合理,刚创建当然是空的 ​ 计数信号量创建时: uxMessagesWaiting = 0 → "停车场一个位都没有" ❌ 不对!我们想先放 3 个位进去 → 手动改成 3 → "初始有 3 个空位" ✅

队列创建完是空的,没人往里存数据,uxMessagesWaiting = 0刚好就是对的。信号量不一样——经常创建时就希望有初始资源(比如 3 个缓冲区空闲位),所以要多一步手动把计数设成初始值。

4:信号量不太占内存

信号量不存数据,所以没有"每条多大"这个参数。

// 队列:要告诉它存多大 xQueueCreate(5, sizeof(MyData)); // ↑ 每个槽多大 ​ // 信号量:不存数据,不传大小 xSemaphoreCreateCounting(5, 3); // 只有上限和初始值 xSemaphoreCreateBinary(); // 啥都不传 xSemaphoreCreateMutex(); // 啥都不传

内部uxItemSize = 0pvPortMalloc不分配数据区,只分配结构体本身。这就是信号量比队列省内存的原因。

5:操作API

Take 和 Give(操作 API)

/* 拿资源(计数 -1) */ BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait ); ​ /* 还资源(计数 +1) */ BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore ); ​ /* 中断里专用的 Give */ BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );
API计数变化阻塞条件唤醒谁
Take获取信号量,信号量-1信号量值=0 时 阻塞下一个 Give 的人
Give让出信号量,信号量+1信号量值=上限时 阻塞下一个 Take 的人

6:二值信号量的初始状态需要我们给

SemaphoreHandle_t ready = xSemaphoreCreateBinary(); 创建后初始 = 0(空的!) // 所以通常紧接着调一次 Give 设成 1: xSemaphoreGive(ready);

跟互斥锁不一样——互斥锁创建后自动是"开锁"状态(=1),二值信号量创建后是 0,需要手动 Give。

二:二值信号量

1:rtos\11\ 改动清单

freertos_demo.c
行号内容
15新增#include "semphr.h"— 信号量 API
48SemaphoreHandle_t sem— 二值信号量句柄
65-72xSemaphoreCreateBinary()创建信号量,初始值=0
113-128task1:扫 KEY1 →xSemaphoreGive(sem),计数 0→1,唤醒 task2
145-151task2:xSemaphoreTake(sem, portMAX_DELAY),死等,被唤醒后打印
main.c
行号内容
26printf 标题 →"FreeRTOS Binary Semaphore Test!"

运行效果:串口打印标题 → task2 阻塞 → 每按一次 KEY1(PA0) → 打印"信号量释放成功" + "获取信号量成功"。连按两次不消费,第二次 Give 会打印"释放失败(已满)"。

那就是我们创建一个二值信号量,然后里面是无信号量,,task2死等,task1 给一个信号量后,task 二就读到了信号量,打印获取信号量成功,此时信号量又是0,是不是又得task1按下才会打印获取信号量成功

2:实际用法(正点实验过于简单)

裸机一般用法: volatile uint8_t dma_rx_flag = 0; while(1) { if(dma_rx_flag) { dma_rx_flag = 0; process_data(); } } CPU轮询,一直运行没有办法做其他事情。 FreeRTOS用法: 任务 | | xSemaphoreTake() | | 阻塞 | ↓ 进入Blocked任务阻塞状态 CPU:任务A (串口处理)→睡眠 任务B→ 执行 任务C→ 执行 直到串口来数据了,中断打断了其他任务,在我们的中断回调里面执行 xSemaphoreGiveFromISR() 任务A 死等信号量在等到的一瞬间唤醒 → 执行串口任务

3:二值信号量的本质

理解成:一个只有0和1状态的事件通知器 状态:0 没有事件 1 事件发生 初始化:uart_sem = xSemaphoreCreateBinary(),此时信号量为0 中断里:xSemaphoreGiveFromISR();此时信号量为1, 任务中:等待信号量 xSemaphoreTake();发现此时信号量为1,拿走,执行事务,然后清0

三:计数信号量

1:rtos\12\ 改动清单

freertos_demo.c
行号内容
46xSemaphoreCreateCounting(10, 0)— 上限 10,初始 0
113-124task1:扫 KEY1 →Give(计数 +1)→uxSemaphoreGetCount()查剩余
139-146task2:Take(死等,计数 -1)→ 查剩余 →vTaskDelay(500)睡半秒让 task1 攒
main.c
行号内容
26printf 标题 →"FreeRTOS Counting Semaphore Test!"

运行效果:按一次 KEY1 → 计数+1,task2 醒了拿走,打印"剩余:0"。task2 睡 500ms 期间,你按 3 次 KEY1 → 计数攒到 3,task2 醒后每 500ms 取一次,连续取 3 次,打印"剩余:2""剩余:1""剩余:0"。

FreeRTOSConfig.h
行号内容
110configUSE_COUNTING_SEMAPHORES0→1 — 开启计数信号量

2:计数信号量时间轴

初始:信号量计数 = 0,上限 10

t=0 task2(prio=3) 先跑 xSemaphoreTake(sem, 死等) → 计数=0 → 阻塞睡觉 t=0 task1(prio=2) 拿到 CPU 扫按键...没按... vTaskDelay(10) → 睡 10ms t=20 用户按 KEY1 xSemaphoreGive(sem) → 计数 0→1 → 唤醒 task2! ↓ task2(prio=3) > task1(prio=2) → 抢占! t=20 task2 醒了 Take 成功 → 计数 1→0 打印 "剩余:0" vTaskDelay(500) → 睡 500ms t=30 task1 继续跑(醒了) 扫按键... t=50 用户连按 KEY1 三次(task2 还在睡) Give → 计数 0→1 → task2 被唤醒!但 task1 还没跑完这行就切了? 不——task2 在 vTaskDelay 阻塞着,不是等着 Take! 所以 task2 没被唤醒(它还没开始等 Take) 更正:task2 在 vTaskDelay(500) 里睡着,不是卡在 Take。 所以 task1 这 3 次 Give 只是把计数往上加: Give → 1→2 Give → 2→3 Give → 3→4 打印 "当前计数:4" t=520 task2 睡够 500ms,醒了 while(1) 循环 → xSemaphoreTake(sem, 死等) 计数=4 > 0 → 立刻拿到!计数 4→3 打印 "剩余:3" vTaskDelay(500) → 睡 t=1020 task2 醒了 Take → 计数 3→2 → 打印 "剩余:2" vTaskDelay(500) → 睡 t=1520 同上 → 计数 2→1 → "剩余:1" t=2020 同上 → 计数 1→0 → "剩余:0" t=2520 Take → 计数=0 → 阻塞!又回到初始状态,等 task1 按

关键:task2 取了一个之后vTaskDelay(500)睡了——不是在 Take 上阻塞,是在延时。所以 task1 在这 500ms 内按的键全攒成计数,task2 下次醒来一口气吃完,直到吃光才重新卡在 Take 上睡觉。

3:几个用到的API

xSemaphoreCreateCounting()— 创建
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount );
形参含义
uxMaxCount计数上限(最多到多少)
uxInitialCount初始计数值

返值:成功返回句柄,失败返回NULL

// 上限 10,初始 0 SemaphoreHandle_t sem = xSemaphoreCreateCounting(10, 0);

xSemaphoreGive()— 释放(计数 +1)
// 宏 → 底层 xQueueGenericSend BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
形参含义
xSemaphore信号量句柄

返值pdPASS= 成功,pdFALSE= 失败(计数已达上限)。


xSemaphoreTake()— 获取(计数 -1)
// 宏 → 底层 xQueueGenericReceive,第二个参数填 NULL(不读数据,只减计数) BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
形参含义
xSemaphore信号量句柄
xTicksToWait阻塞超时(portMAX_DELAY= 死等,0= 不等)

返值pdPASS= 拿到,pdFALSE= 失败(超时或空)。


uxSemaphoreGetCount()— 查询当前计数
UBaseType_t uxSemaphoreGetCount( SemaphoreHandle_t xSemaphore );
形参含义
xSemaphore信号量句柄

返值:当前还剩多少。二值信号量返回 0 或 1,计数信号量返回 0 ~ 上限。


⑤ 跟队列 API 的底层关系
xSemaphoreGive(sem) → xQueueGenericSend(sem, NULL, 0, queueSEND_TO_BACK) ↑ 不拷数据,只 +1 计数 xSemaphoreTake(sem, wait) → xQueueGenericReceive(sem, NULL, wait, pdFALSE) ↑ 不读数据,只 -1 计数

信号量把队列的"数据缓冲区"去掉了,只留下uxMessagesWaiting当计数器。

4:实际用法(正点实验过于简单)

裸机做法 // 3 个 DMA 缓冲区,用标志位管 volatile uint8_t buf_free[3] = {1, 1, 1}; // 1=空闲 // 找一个空闲 buf int get_buf(void) { for (int i = 0; i < 3; i++) { if (buf_free[i]) { buf_free[i] = 0; return i; } } return -1; // 全在用,失败——但裸机只能死等或返回错误 } // 还 buf void release_buf(int i) { buf_free[i] = 1; } // 用的时候: while (1) { int i = get_buf(); if (i < 0) continue; // 没空闲 buf,CPU 空转死等 // 装数据 → 启动 DMA... } 问题:buf 全忙的时候,CPU 死等空转,别的任务干不了活。 FreeRTOS 做法 计数信号量:初始 = 3(有 3 个空闲 buf) 任务 A(串口发送) │ │ xSemaphoreTake(buf_sem, 死等) │ ├── 计数 > 0 → 计数 -1,立刻拿到一个 buf │ └── 计数 = 0 → 阻塞睡觉(3 个 buf 都在用) │ │ 拿到 buf → 装数据 → 启动 DMA 发送 │ │ DMA 发送完成中断: │ xSemaphoreGiveFromISR(buf_sem, &wake) → 计数 +1,归还 buf │ └── 再取 → 循环 CPU 视图: 任务A(等待buf) → 阻塞 任务B → 在跑 任务C → 在跑 空闲 → 在跑 3 个 buf 都在发数据 ↑ CPU 不闲着 ↓ DMA 中断 → Give → 计数 0→1 → 任务A醒了 → 又拿到 buf 干活 跟裸机的区别 裸机: buf 全忙 → while(buf_free == 0); → CPU 100% 死等 → 别的任务饿死 FreeRTOS: buf 全忙 → xSemaphoreTake 阻塞 → CPU 切给任务B/C → 谁也不浪费 DMA 中断 Give → 自动唤醒 → 无缝衔接 计数信号量 = 裸机里你手写的"资源计数值 + 忙等循环",FreeRTOS 帮你变成"计数 -1/阻塞/中断 Give 唤醒",CPU 零浪费。

四:优先级翻转

rtos\13\ 改动清单

freertos_demo.c
行号内容
37-39LOW(2) / MIDDLE(3) / HIGH(4) 三个优先级
67xSemaphoreGive(sem)— 初始给一次,low 先拿到
104-116low_task:Take → delay_ms 3000 忙等(被 middle 抢) → Give(永远跑不到)
123-128middle_task:不碰信号量,纯打印+vTaskDelay,就是路障
139-148high_task:Take 死等 → 永远卡住,"high_task 正在运行" 不会出现
main.c
行号内容
26printf 标题 →"FreeRTOS Priority Inversion Test!"
实验现象:
FreeRTOS Priority Inversion Test! 二值信号量创建成功! high_task 获取信号量 high_task 正在运行!!! high_task 释放信号量 middle_task 正在运行!!! low_task 获取信号量 low_task 正在运行!!! high_task 获取信号量 middle_task 正在运行!!! middle_task 正在运行!!! middle_task 正在运行!!!

实验原理:

初始化时, xSemaphoreGive(sem); /* ★ 给一次,初始=1,high_task 先拿到 */

随后释放后,高优先级任务进入睡觉状态,调度器切到优先级中等任务,打印:middle_task 正在运行!!!,随后调度

切到最低优先级任务,

static void low_task(void *pvParameters) { while (1) { printf("low_task 获取信号量\r\n"); xSemaphoreTake(sem, portMAX_DELAY); /* 拿到信号量 */ printf("low_task 正在运行!!!\r\n"); delay_ms(3000); /* ★ 忙等 3 秒!middle 会抢走 CPU */ printf("low_task 释放信号量\r\n"); xSemaphoreGive(sem); /* 还信号量 → 但永远跑不到这行! */ vTaskDelay(1000); } }

执行到delay_ms(3000); 时,cpu 切换任务,信号量没释放,这下高优先级死等这个信号量然后阻塞了,

xSemaphoreTake 发现计数=0,当场把 high_task 挂到信号量的等待列表上,任务状态变成 Blocked。CPU 转身就分给下一个就绪的人(middle_task,优先级 3)。 high_task 执行 xSemaphoreTake(sem, 死等) → 计数 = 0 → 状态绳从 ReadyList 摘下 → 挂到 sem 的 xTasksWaitingToReceive → PendSV → 找 ReadyList 里最高优先级 → middle_task(prio=3) → 🎉 middle 在跑了 → high_task 阻塞睡觉,不占任何 CPU 串口那句 "high_task获取信号量" 是在调用 Take 之前打印的,所以你能看到它。打完那行之后立刻进了 Take → 阻塞 → CPU 切走。之后你再也看不到 "high_task正在运行"——因为 low 手里的信号量被 middle 挡着永远还不了。

五:互斥信号量

1:简介

互斥锁 = 二值信号量 + 优先级继承 + 所有权
二值信号量: 计数 0/1,谁都能 Give,谁都能 Take,没有"主人"概念 互斥锁: 计数 0/1,谁拿的谁还(xMutexHolder 记着持有者), 高优先级等的时候 → 把低优先级持有者临时抬到自己的优先级
优先级继承做了什么
没有继承(二值信号量): 有继承(互斥锁): high(4) 等锁 → 阻塞 high(4) 等锁 → 阻塞 middle(3) 占 CPU → low 跑不了 同时 low(2) 被抬到 4 low(2) 拿锁但跑不了 → 还不了 middle(3) < 4 → 抢不了 low → high 死等 → low 跑完 → 还锁 → prio 恢复 2 → high 拿到锁 ✅
继承不能完全消除翻转

互斥锁只在拿锁被阻塞时触发继承。如果你不拿锁,低优先级还是被中优先级抢。所以互斥锁解决的是"持有锁期间被无关任务挡路"的问题,不是万能药。

中断里不能用互斥锁

两个原因:

  1. 中断不是任务,没有优先级——优先级继承找不到"继承源"

  2. 中断不能阻塞——互斥锁被人拿着时你要等,中断里一阻塞 CPU 就卡死了(没有任务上下文可以切回去)

所以中断里只能用xSemaphoreGiveFromISR给二值/计数信号量,不能 Take 互斥锁

2:实验工程差异

跟优先级翻转实验的代码几乎一模一样——只改了一行

// 优先级翻转实验(rtos\13\): sem = xSemaphoreCreateBinary(); // 二值信号量 → 无优先级继承 // 互斥锁实验(这个): mutex = xSemaphoreCreateMutex(); // 互斥锁 → ★ 有优先级继承

互斥锁如何解决优先级翻转

low_task 拿到互斥锁后,high_task 也来要——互斥锁内部发现"有个高优先级的人在等我",立刻把 low_task 临时抬到 high 的优先级

low_task(prio=2) Take(mutex) → 拿到锁 │ ├── delay_ms(3000) 忙等中... │ high_task(prio=4) Take(mutex) │ → 锁被 low 拿着 │ → low.pri = 2 → 抬到 4!★ 优先级继承 │ → high 阻塞 │ │ 现在 low 优先级 = 4 > middle(3) → middle 抢不进去了! ├── delay_ms 跑完 → Give(mutex) │ → low.pri 恢复 = 2(继承撤销) │ → high 醒了,拿到锁

middle_task 再也插不进来——因为 low 临时变成了最高优先级。

3:时间轴对比

二值信号量(昨天): 互斥锁(今天): low 拿锁(prio=2) low 拿锁(prio=2) middle 抢 → low 被挤走 high 也想要 → low 被抬到 prio=4 high 要锁 → 没人能还 → 卡死 middle 想抢 → 抢不动!low 现在比它高 low 干完活 → 还锁 → prio 恢复 2 high 拿到锁 → 继续跑

4:互斥锁和二值信号量的区别

二值信号量互斥锁
创建xSemaphoreCreateBinary()xSemaphoreCreateMutex()
初始值0(需要 Give 一次)1(直接可用)
优先级继承❌ 无✅ 有
谁能还谁都能 Give谁拿的谁还(记了 xMutexHolder)
Take/Give API一样一样
配置configUSE_MUTEXES = 1

5:互斥锁 API

xSemaphoreCreateMutex()— 创建互斥锁
// 宏 → 底层 xQueueCreateMutex(queueQUEUE_TYPE_MUTEX) SemaphoreHandle_t xSemaphoreCreateMutex( void );

无参数。创建后自动开锁(计数=1),谁都可以先 Take。

返值:成功返回句柄,失败返回 NULL。

需要宏configUSE_MUTEXES = 1


xSemaphoreGetMutexHolder()— 查锁在谁手上
// 宏 → 底层 xQueueGetMutexHolder TaskHandle_t xSemaphoreGetMutexHolder( SemaphoreHandle_t xMutex );

返值:当前持有者的任务句柄。没人拿(开锁状态)返回 NULL。

####

6:rtos\14\ 改动清单

freertos_demo.c
行号内容
49xSemaphoreCreateMutex()— 创建互斥锁,初始=1,不需要手动 Give
100-116low_task
123-128middle_task:不碰锁,纯占 CPU,这次挡不住low
139-149high_task:Take → delay_ms 1000 → Give。这次能拿到了!
FreeRTOSConfig.h
行号内容
97configUSE_MUTEXES0→1
main.c
行号内容
26printf 标题 →"FreeRTOS Mutex Test!"
实验现象:
FreeRTOS Mutex Test! 互斥锁创建成功! high_task 获取互斥锁 high_task 正在运行!!! high_task 释放互斥锁 middle_task 正在运行!!! low_task 获取互斥锁 low_task 正在运行!!! high_task 获取互斥锁 low_task 释放互斥锁 high_task 正在运行!!!

7:实际用法(正点实验过于简单)

裸机做法
// 天真的想法:加个全局标志位 volatile uint8_t spi_busy = 0; void task_log_write(void) { while (spi_busy); // 等总线空闲 —— 但这里可能被更高优先级任务抢! spi_busy = 1; // W25Q256 写一页数据... spi_busy = 0; } void task_data_collect(void) { while (spi_busy); spi_busy = 1; // W25Q256 读传感器数据... spi_busy = 0; } 问题:while(spi_busy); 和 spi_busy = 1; 之间不是原子操作——任务 B 可以在你刚跳出 while、还没设 1 的时候抢走 CPU,然后两个任务同时以为总线是自己的 → Flash 数据全乱。
FreeRTOS 做法
互斥锁 = SPI 总线的"门禁卡",只有一张 任务 A(日志写入,prio=2) 任务 B(数据采集,prio=4,更高) │ │ │ xSemaphoreTake(spi_mutex) │ │ → 门禁卡在,拿走 → 锁门 │ │ → 开始写 Flash... │ xSemaphoreTake(spi_mutex) │ (忙等 delay_ms,占着锁) │ → 门禁卡不在!阻塞! │ │ → ★ 但任务 A 被抬到 prio=4 │ │ (优先级继承!) │ │ │ 其他任务(prio=3) 想抢 CPU: │ │ 抢不动!任务A 现在是 4 │ ← middle_task 插不进来! │ │ │ xSemaphoreGive(spi_mutex) │ │ → 开锁 │ │ → 优先级恢复到 2 │ → 任务 B 拿到门禁卡! │ │ → 继续写 Flash... │ │ │ │ xSemaphoreGive(spi_mutex) │ │ → 开锁 跟二值信号量的区别(为什么这里必须用互斥锁) 场景 二值信号量 互斥锁 taskA(prio=2) 拿着锁写 Flash ✅ 正常 ✅ 正常 taskB(prio=4) 来抢 SPI ❌ 被 middle(prio=3) 挡着,永远抢不到 → 优先级翻转! ✅ 继承 prio=4,middle 挡不住 → 写完就还 你学过的 SPI Flash、I2C EEPROM、UART 发送——任何"多个任务共享一个外设"的场景,都用互斥锁。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询