Selaa lähdekoodia

wiki: 从B站视频转录补充 FreeRTOS 笔记讲解细节

OpenCode 4 päivää sitten
vanhempi
sitoutus
6d601514e5

+ 5 - 1
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/01-实时操作系统基础.md

@@ -43,7 +43,7 @@ FreeRTOS 是所有实时操作系统中最受欢迎的一款。
 - 2017 年:被亚马逊收购,改名为 AWS FreeRTOS
 
 ### 核心优势
-- **开源免费**:MIT 许可证
+- **开源免费**:MIT 许可证(注意:开源≠免费,有些开源协议只允许个人学习不允许商用,FreeRTOS MIT 两者都允许)
 - **轻量级**:适用于资源受限的嵌入式系统
 - **广泛应用**:工业自动化、医疗设备、消费电子、汽车电子
 - **多平台支持**:可移植到不同硬件平台
@@ -53,6 +53,10 @@ FreeRTOS 是所有实时操作系统中最受欢迎的一款。
 
 *单核处理器通过任务间快速切换制造"同时运行"的假象。上图:感知到的并发(所有任务同时运行),下图:实际执行(时间上串行)。*
 
+> **并发 vs 并行**:就像三个坑位的卫生间——3人×3坑 = **并行**(同时上),6人×3坑 = **并发**(轮流上)。单核 RTOS 只能做到并发,做不到并行。
+>
+> **炒菜类比**:一位厨师(单核)炒三道菜。裸机编程 = 做完一道再做下一道。RTOS = 同时准备所有食材,然后轮流翻炒——这就是"快速切换"的并发效果。
+
 ### 关键特点
 - 任务调度:支持不同优先级的任务有序执行
 - 任务通信和同步:队列、信号量等机制

+ 15 - 3
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/02-任务调度与状态管理.md

@@ -36,10 +36,12 @@ FreeRTOS 中共 4 种任务状态:
 |------|------|
 | **运行态**(Running) | 任务实际执行中,单核下仅一个任务处于此状态 |
 | **就绪态**(Ready) | 任务可执行但尚未运行,因同/更高优先级任务正在运行 |
-| **阻塞态**(Blocked) | 任务等待延时或外部事件 |
-| **挂起态**(Suspended) | 调用 `vTaskSuspend()` 进入,需 `vTaskResume()` 恢复 |
+| **阻塞态**(Blocked) | 任务等待延时或外部事件(**主动**行为) |
+| **挂起态**(Suspended) | 调用 `vTaskSuspend()` 进入,需 `vTaskResume()` 恢复(**被动**行为——任务被"踢出局") |
 
-只有就绪态可转变成运行态,其他状态必须先转为就绪态。
+> **阻塞 vs 挂起的本质区别**:阻塞是任务**主动**说"我等会儿再跑"(延时/等资源),调度器还认得它;挂起是任务被**被动**标记为"出局",调度器完全忽略它,直到被明确恢复。就像员工自己去茶水间接水(阻塞)vs 被领导叫去面壁(挂起)。
+>
+> **核心规则**:**只有就绪态可转为运行态**。其他所有状态必须先进入就绪态。运行态不维护自己的链表——"运行是最终目标,不需要排队"。
 
 ![FreeRTOS 任务状态转换图:运行态 ↔ 就绪态 ↔ 阻塞态 ↔ 挂起态](assets/task_state_diagram.png)
 
@@ -51,8 +53,12 @@ FreeRTOS 中共 4 种任务状态:
 ![就绪列表位图:每个 bit 代表一个优先级是否有任务就绪](assets/ready_list_bitmap.png)
 ![同优先级多任务链在同一就绪列表上](assets/ready_list_same_priority.png)
 
+> **实现细节**:就绪列表本质是一个**链表数组**——每个优先级对应一个链表。一个 32 位位图变量记录哪些优先级有就绪任务(bit n = 1 表示优先级 n 有任务就绪)。调度器从最高 bit 向下搜索,找到第一个置 1 的 bit 即可。**运行态不维护链表**——"运行是最终目标,不需要排队"。
+
 调度器总是在所有就绪列表中选择最高优先级的任务执行。
 
+> **时间片提前结束**:如果任务在时间片用完前就主动让出 CPU(调用 `vTaskDelay()` 或等信号量),切换**立即发生**,不会浪费剩余的时间片。就像员工提前干完活,不会非要等下班才走。
+
 ## FreeRTOS 滴答(Tick)
 
 FreeRTOS 通过滴答计数变量测量时间。定时器中断(RTOS 滴答中断)以严格时间精度增加滴答数。每次滴答增加时,内核检查是否需要解除阻塞或唤醒任务。
@@ -74,4 +80,10 @@ FreeRTOS 通过滴答计数变量测量时间。定时器中断(RTOS 滴答中
 
 调度器启动时自动创建空闲任务(最低优先级),确保始终有任务可运行。空闲任务负责释放被删除任务的内存。
 
+> **桶的类比**:空闲任务就像没人用坑位时在坑里放一个桶占位——有人来了把桶拿走。空闲任务就是那个"桶"。
+>
+> **异步删除**:`vTaskDelete()` 只是打了一个"删除标记",内存空间要到空闲任务运行时才真正清理。就像有人说"回头再打扫",但真正打扫要等没人用时才进行。
+>
+> **空闲任务的三个作用**:① 确保始终有任务可运行 ② 清理已删除任务的内存 ③ 低功耗模式的入口。
+
 > 后续章节:[[05-任务创建删除与挂起恢复]]、[[06-中断管理与时间管理]]

+ 3 - 3
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/05-任务创建删除与挂起恢复.md

@@ -248,9 +248,9 @@ void task3(void *pvParameters)
 | **删除态** | `vTaskDelete()` | 回不来了 | 回收了 |
 
 > **打个比方**:
-> - 挂起 = 员工长期休假(工位保留)
-> - 阻塞 = 员工去茶水间接水(一会儿就回来)
-> - 删除 = 员工被开除了(工位回收)
+> - 挂起 = 员工长期休假(工位保留,**被动**——领导让休的
+> - 阻塞 = 员工去茶水间接水(一会儿就回来,**主动**行为
+> - 删除 = 员工被开除了(工位回收,回不来了
 
 ### vTaskSuspendAll 的特殊性
 `vTaskSuspendAll()` 只是暂停任务切换,**但中断不受影响**。这就像 CEO 说"都别动",但快递员(中断)来了照样可以送快递。而 `taskENTER_CRITICAL()` 是直接把公司大门锁了,连快递都进不来。

+ 23 - 0
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/06-中断管理与时间管理.md

@@ -193,4 +193,27 @@ void task2(void *pvParameters)
 - `code/05_时间片调度/`
 - `code/08_时间延时函数/`
 
+---
+
+## 补充:PendSV 与 SysTick 中断设计哲学
+
+### PendSV(上下文切换中断)
+
+**为什么选择 PendSV?**
+- PendSV 是一个系统级异常,可以**通过写寄存器手动触发**(ICSR bit28)
+- 可以**在其他 ISR 中设置**——Tick ISR 发现需要切换时,只需"挂起 PendSV"然后继续执行,等 Tick ISR 退出后再真正切换
+- 被设为**最低中断优先级**,避免任务切换干扰 UART、定时器、SPI 等外设中断
+
+**Tick ISR 的完整工作流程**:
+1. tick 计数加 1
+2. 遍历阻塞列表,找出到期任务并移至就绪列表
+3. 检查就绪列表是否需要切换(更高优先级就绪 / 同优先级时间片用完)
+4. 如果需要切换,挂起 PendSV(等 Tick ISR 退出后再切,不抢占当前中断)
+
+**主动切换**:任务提前结束时(如调用 `vTaskDelay()`),直接主动调用任务切换函数,无需等 tick 中断——这就是"时间片提前结束不浪费"的实现机制。
+
+### SysTick 的两个作用
+1. 为时间片轮转提供基准时钟
+2. 为阻塞列表中的任务提供超时计时
+
 > 后续章节:[[07-消息队列与队列集]]

+ 8 - 0
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/07-消息队列与队列集.md

@@ -34,6 +34,8 @@ xQueueSend(queue1, &key, portMAX_DELAY);
 // 也可以设 0 = "满了就放弃" 或者 100 = "等100个tick"
 ```
 
+> **阻塞 / 非阻塞**:就像排队吃饭——队列满了你可以等(设超时),也可以直接走人(超时=0 不等待)。接收同理:队列空了可以等数据来,也可以直接放弃。
+
 ### 接收消息(出队)
 ```c
 uint8_t receive;
@@ -41,6 +43,10 @@ xQueueReceive(queue1, &receive, portMAX_DELAY);
 // 同样 portMAX_DELAY = "队列空了就死等"
 ```
 
+> **Receive vs Peek**:`xQueueReceive()` = **剪切**(从队列中删除数据),`xQueuePeek()` = **复制**(数据保留在队列中,只看不取)。就像看信箱——Receive 是取走信,Peek 是只看看不拿。
+>
+> **Send to front**:`xQueueSendToFront()` = **插队**(把消息放到队首),`xQueueSendToBack()` = 正常排队(默认)。
+
 ### 小数据 vs 大数据
 
 ```c
@@ -139,6 +145,8 @@ void task3(void *pvParameters)
 
 假设你管理两个信箱(队列)和一个通知铃(信号量)。不用队列集时,你必须轮询检查每个信箱——"1号有信吗?2号有信吗?铃响了吗?"很麻烦。
 
+> **痛点导向**:当需要从多个队列读取数据时,如果先阻塞在队列 A 的读取上,但队列 B 先到了数据,就会卡住——永远等不到 B 的数据。队列集让任务一次性阻塞在所有队列上,哪个先到就处理哪个。
+
 **队列集 = 统一管理的"消息中心"**。你把队列和信号量都注册进去,然后只需要等一个函数返回,它就告诉你"是哪个有消息了"。
 
 ### 核心代码

+ 7 - 1
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/08-信号量与互斥信号量.md

@@ -84,6 +84,8 @@ void task2(void *pvParameters)
 ### 二值信号量的本质
 
 > 二值信号量本质上就是一个**长度=1、项目大小=0 的特殊队列**。你不关心"消息内容"是什么,只关心"有没有消息"。
+>
+> **源码实现**:信号量底层就是用队列实现的——`xSemaphoreCreateBinary()` 内部调用了 `xQueueCreate(1, 0)`。就像火车卫生间的指示灯——红灯=有人(已获取),绿灯=空闲(已释放)。队列什么都能干,信号量能干的队列都能干,但队列能干的信号量不一定能干。
 
 ---
 
@@ -195,6 +197,8 @@ void task3(void *pvParameters)  // 高优先级 Task3(4)
 }
 ```
 
+> **优先级翻转的本质**:"倒反天罡"——低优先级任务反而阻塞了高优先级任务。中优先级任务"山中无老虎,猴子称霸王"——不受限制地运行,持续抢占持有锁的低优先级任务,导致高优先级等待时间无限延长。这是信号量的典型问题,不是所有同步机制都有。
+
 **执行流程**(问题所在):
 ```
 时间 →
@@ -223,7 +227,9 @@ sem_handle = xSemaphoreCreateMutex();        // 互斥信号量:自动处理
 
 ### 互斥信号量做了什么?
 
-系统检测到"高优先级任务在等低优先级任务释放信号量"时,**临时把低优先级任务的优先级提升到和高优先级一样高**。这样中优先级任务就不能插队了,低优先级任务赶紧执行完释放信号量,高优先级任务就能拿到。
+系统检测到"高优先级任务在等低优先级任务释放信号量"时,**临时把低优先级任务的优先级提升到和高优先级一样高**。这样中优先级任务就不能插队了,低优先级任务赶紧执行完释放信号量,高优先级任务就能拿到。用完就丢——资源释放后优先级再降回去。
+
+> **创建时自动释放**:创建互斥信号量时会**自动执行一次 Give 操作**,确保初始状态为"可用"。
 
 ```
 互斥信号量下:

+ 9 - 1
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/09-事件标志组与任务通知.md

@@ -24,6 +24,8 @@ created: 2026-07-12
 
 事件标志组是一个整数(24位或8位),每个 bit 代表一个独立事件:
 
+> **位宽计算**:实际可用位数 = tick 类型的位数 - 8 位预留。由 `configUSE_16_BIT_TICKS` 决定——置 1 → 8 位事件标志(16-8=8),置 0 → 24 位(32-8=24)。事件标志组占用 tick 类型的高 8 位。
+
 ```
 Bit:  23 ... 2  1  0
      +---------+--+--+
@@ -114,6 +116,10 @@ void task2(void *pvParameters)
 
 任务通知**不需要创建中间对象**。每个任务内部都有 32 位通知值,其他任务可以直接修改它。
 
+> **信箱类比**:每个任务的 TCB 中有一个"邮箱",其他任务直接把信放进去——不需要队列、信号量等中间对象。发送方不能阻塞(只能覆盖或放弃),只有接收方可以阻塞。
+>
+> **发送非阻塞**:发送方总是非阻塞的——要么覆盖旧值,要么放弃。这跟队列不同——队列满了发送方可以等,通知满了就只能覆盖或丢弃。
+
 ### 三种典型用法
 
 项目16/17/18 分别演示了三种用法:
@@ -177,10 +183,12 @@ if (val & EVENTBIT_1) { /* 事件B发生了 */ }
 | `eNoAction` | 只发通知不改值 |
 
 ### 注意事项
-- ⚠️ 任务通知**不能在 ISR 中接收**(没有 FromISR 版本的接收函数)
+- ⚠️ 任务通知**不能在 ISR 中接收**(没有 FromISR 版本的接收函数)——因为 ISR 没有自己的 TCB
 - ✅ 发送可以在 ISR 中:`xTaskNotifyFromISR()` / `vTaskNotifyGiveFromISR()`
 - 通知默认启用,禁用时设置 `configUSE_TASK_NOTIFICATIONS = 0`
 
+> **v10.4 新增通知数组**:旧版本每个任务只有一个 32 位通知值。新版本支持**通知数组**(`configTASK_NOTIFICATION_ARRAY_ENTRIES`),可接收来自多个不同任务的独立通知。发送时通过 `xTaskNotifyIndexed()` 指定数组索引。
+
 ### 配套代码
 - `code/15_事件标志组/`
 - `code/16_任务通知模拟信号量/`

+ 14 - 4
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/10-软件定时器与Tickless低功耗.md

@@ -20,6 +20,10 @@ created: 2026-07-12
 > - **一次性定时器**:设一个闹钟,响了就没了(比如"30分钟后关火")
 > - **周期性定时器**:设一个重复闹钟,每天这个点都响(比如"每天早上7点起床")
 
+> **精度限制**:软件定时器的精度由 tick 周期决定(如 1ms 节拍 → 最大精度 1ms),无法达到微秒级——这是与硬件定时器的本质区别。
+>
+> **由专用任务管理**:软件定时器由调度器启动时自动创建的**定时器服务任务**(`prvTimerTask`)负责管理。所有定时器命令(启动/停止/复位)通过**命令队列**发送给该任务。回调函数在该任务上下文中执行,因此**不能调用任何阻塞 API**,否则会卡住整个定时器服务。
+
 ### 硬件 vs 软件定时器
 
 | 特性 | 硬件定时器 | 软件定时器 |
@@ -140,14 +144,20 @@ void timer_callback(TimerHandle_t xTimer)
 
 ```
 普通模式:  醒→看→没事→睡→醒→看→没事→睡→醒→看→有事→干→醒→看...
-               ↑                ↑                ↑
-            1秒醒一次       1秒醒一次        1秒醒一次(浪费电)
+                ↑                ↑                ↑
+             1秒醒一次       1秒醒一次        1秒醒一次(浪费电)
 
 Tickless:   醒→看→没事→睡大觉→有事→醒了→干活
-               ↑                          ↑
-            睡很久                     有人叫醒(省电!)
+                ↑                          ↑
+             睡很久                     有人叫醒(省电!)
 ```
 
+> **核心机制**:当所有任务都阻塞时,系统可以**预测空闲任务的持续时长**(从最近的阻塞任务超时时间计算),然后在此时间段内**禁用 SysTick 中断**,醒来时**一次性补偿**漏掉的 tick 计数。
+>
+> **睡觉被手机吵醒类比**:"好不容易有半个小时要睡一会儿,结果你的手机一分钟响一次"——这就是不禁用 SysTick 的感受。Tickless 模式相当于睡前把手机调成静音,睡醒了再看所有未接来电。
+>
+> **最小空闲时长配置**:可设置进入低功耗所需的最短空闲时间——如果空闲时间太短,"进入退出的开销得不偿失",就不进低功耗模式。
+
 ### 配置方法
 
 ```c

+ 19 - 7
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/11-内存管理.md

@@ -26,18 +26,20 @@ created: 2026-07-12
 
 ## 五种 heap 算法对比
 
-| 算法 | 合并空闲块 | 允许释放 | 适用场景 | 推荐? |
-|------|-----------|---------|---------|:----:|
-| heap_1 | 不适用 | ❌ 不能释放 | 只创建不删除的项目 | |
-| heap_2 | ❌ | ✅ | 任务栈大小固定 | ❌ 已过时 |
-| heap_3 | 由C库决定 | ✅ | 标准库可用时 | |
-| **heap_4** | ✅ | ✅ | **通用场景** | **⭐ 推荐** |
-| heap_5 | ✅(跨区域) | ✅ | 内存地址不连续 | |
+| 算法 | 策略 | 合并空闲块 | 允许释放 | 适用场景 | 推荐? |
+|------|------|-----------|---------|---------|:----:|
+| heap_1 | 简单数组分配 | 不适用 | ❌ 不能释放 | 只创建不删除的项目 | |
+| heap_2 | 最佳适应(Best-fit) | ❌ | ✅ | 任务栈大小固定 | ❌ 已过时 |
+| heap_3 | C 库 malloc/free |  C 库决定 | ✅ | 标准库可用时 | |
+| **heap_4** | **首次适应(First-fit)** | **** | ✅ | **通用场景** | **⭐ 推荐** |
+| heap_5 | 跨区域 First-fit | ✅(跨区域) | ✅ | 内存地址不连续 | |
 
 ![heap_1 内存分配示意图:简单数组,只分配不释放](assets/heap1.png)
 
 ### heap_1:最简单(不能释放)
 
+> **"蹲坑不冲水"**:只申请不释放,就像上厕所不冲水——简单粗暴,但只适用于项目启动时一次性创建所有对象的场景。
+
 ```
 初始化: [         整个堆空间          ]
 分配后: [ Task1 ][ Task2 ][ Task3 ][ 剩余 ]
@@ -49,6 +51,8 @@ created: 2026-07-12
 
 ### heap_2:最佳适应(过时)
 
+> **"伤过的心已经变成玻璃碎片"**:最佳适应算法(Best-fit)从满足要求的块中选择最接近请求大小的。问题是不合并相邻空闲块——时间长满是小碎片,大块分配失败。
+
 ```
 释放后: [ Task1 ][ 空闲 ][ Task3 ][ 空闲 ]
          ↑不合并          ↑不合并
@@ -59,8 +63,14 @@ created: 2026-07-12
 
 ![heap_4 第一适应算法:合并相邻空闲块减少碎片](assets/heap4_first_fit.png)
 
+### heap_3:C 库包装
+
+> 标准 C 库 `malloc()`/`free()` 的简单线程安全包装。如果开发环境自带 C 库就可以用,但和裸机 malloc 一样有非确定性问题。
+
 ### heap_4:第一适应 + 合并(推荐)
 
+> **首次适应算法**(First-fit):按地址顺序找到第一个足够大的块就用,不追求最优匹配(更快)。**自动合并相邻空闲块**——有人走了,把旁边空位合并成大空间。
+
 ```
 释放后: [ Task1 ][ 空闲 ][ Task3 ][ 空闲 ]
          ↓自动合并相邻空闲块
@@ -72,6 +82,8 @@ created: 2026-07-12
 
 ### heap_5:跨区域 heap_4
 
+> **"杀鸡焉用牛刀"**:能合并不连续的内存区域,但开销大。除非你的内存物理上就不连续(比如外扩 SARM 和片内 SRAM 要统一管理),否则用 heap_4 就够了。
+
 ```
 内存布局: [ 区域A 128KB ]    [ 区域B 64KB ]    [ 区域C 32KB ]
            ↑ 不连续但heap_5能统一管理

+ 3 - 1
X-Knowledge-Base/wiki/index.md

@@ -8,11 +8,13 @@ created: 2026-07-12
 
 ## FreeRTOS 实时操作系统
 
-> 来源:[[source-freertos|尚硅谷 FreeRTOS 教程]]
+> 来源:[[source-freertos|尚硅谷 FreeRTOS 教程]] + B 站 FreeRTOS 视频转录
 
 原始笔记(含代码 + 通俗解释):
 `raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/`
 
+已从 B 站视频教程的 55 个视频中提取字幕/语音讲解内容,丰富笔记中的类比和细节。
+
 Wiki 页面:
 - [[freertos-intro|FreeRTOS 概念]] — OS vs RTOS,数据类型,命名规范
 - [[freertos-task-scheduling|任务调度与状态]] — 调度策略,任务状态,上下文切换

+ 21 - 0
X-Knowledge-Base/wiki/log.md

@@ -23,6 +23,27 @@ type: log
 
    更新 `wiki/index.md`、`wiki/log.md`
 
+## 2026-07-13
+
+- `wiki`: 从 B 站 FreeRTOS 教程视频转录 55 个视频的字幕/语音,丰富 10 篇 raw 笔记
+
+    补充来源:B 站"freertos零基础教程,FreeRTOS项目实战(零基础手把手教程)"55 个视频
+    - 34 个视频有中文字幕 → 直接解析 SRT
+    - 21 个视频无字幕 → VAD + ASR API 转写
+
+    补充内容:
+    - `01` — 开源≠免费、并行vs并发类比
+    - `02` — 就绪位图实现、时间片提前结束、阻塞vs挂起本质、空闲任务三作用
+    - `05` — 挂起vs阻塞vs删除深度对比
+    - `06` — PendSV设计哲学、Tick ISR完整流程、SysTick两作用
+    - `07` — Peek、SendToFront、队列集痛点
+    - `08` — 信号量=队列实现、mutex自动give、优先级翻转"倒反天罡"
+    - `09` — 位宽计算、ISR规则、v10.4通知数组
+    - `10` — 精度限制、命令队列、Tickless核心机制、min idle配置
+    - `11` — heap_1~5 算法细节与类比
+
+    更新 `wiki/source-freertos.md`、`wiki/index.md`、`wiki/log.md`
+
 - `lint`: 清理 FreeRTOS 图片资源,重写 11 篇 raw 笔记
 
    清理:

+ 2 - 0
X-Knowledge-Base/wiki/source-freertos.md

@@ -12,6 +12,8 @@ created: 2026-07-12
 
 11 篇原始笔记位于 `raw/Joplin/嵌入式+Linux/FreeRTOS学习笔记/`,含完整代码和通俗解释。
 
+> 2026-07-13 补充:从 B 站 FreeRTOS 教程视频("freertos零基础教程,FreeRTOS项目实战")转录 55 个视频的字幕/语音内容,融入笔记作为补充讲解视角。
+
 ## 笔记列表
 
 | # | 笔记 | 代码项目 |