)
目录一rtos 和裸机的特点对比1裸机2RTOS3RTOS的四个特点4两个注意事项5configMAX_SYSCALL_INTERRUPT_PRIORITY6configUSE_TIME_SLICING7vTaskDelay不能在中断里调用二中断优先级 vs 任务优先级1两个世界两套规则2硬件中断永远优先于任务3中断内的一根分界线4为什么 0~4 不能用5调 FreeRTOS API 是什么意思三FreeRTOS 三种调度方式1定义2“抢占式3时间片4协程式5实际只会用到抢占时间片四任务的运行状态1四种状态2状态切换3三个列表4一句话五FreeRtosConfig.H1PendSV_Handler中断做什么2SVC_Handler中断做什么3上电后执行流程4: 为什么 PendSV 不直接在 SysTick 中断里切一rtos 和裸机的特点对比RTOS全称为Real Time OS就是实时操作系统强调的是实时性。*RTOS 四大特点**分而治之实现功能划分为多个任务。延时函数任务调度。抢占式高优先级任务抢占低优先级任务。任务堆栈每个任务都有自己的栈空间。注意事项注意1中断可以打断任意任务。注意2任务可以同等优先级。1裸机main() { Init(); while(1) { 任务A(); // 跑完 任务B(); // 才轮到 任务C(); // 最后 } }特点一行等一行。任务 A 里有个HAL_Delay(1000)B 和 C 就得跟着等 1 秒。CPU 全速空转什么也干不了。2RTOSmain() { Init(); xTaskCreate(任务A); xTaskCreate(任务B); xTaskCreate(任务C); vTaskStartScheduler(); ← 永不返回 }特点调度器在中间切。任务 A 调vTaskDelay(1000)→ 调度器立刻把 CPU 给 B → B 跑一阵也 delay → 调度器给 C。1 秒到了A 被按时唤醒。3RTOS的四个特点1. 分而治之✅ 没错。一个大 while(1) 拆成多个独立任务每个任务只管自己的事。2. 延时函数 任务调度不准确。调用vTaskDelay不是调用调度器——调度器不靠延时函数触发是靠 SysTick 中断每次 1ms 检查有没有任务到期。vTaskDelay干的事是把我挂起来让调度器下次扫到我时再唤醒。任务切换是 PendSV 中断干的不是 delay 函数。3. 抢占式✅ 没错。SysTick 中断发现高优先级任务到期 → 立马切过去正在跑的低优先级任务被抢走 CPU。4. 任务堆栈✅ 没错。每个任务创建时xTaskCreate的第三个参数就是栈大小单位 word。A 和 B 各自有栈A 的局部变量不会串到 B 里去。4两个注意事项中断可以打断任务✅ 但要补一句不是所有中断都能调 FreeRTOS API。优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断不能调xQueueSend这类 API否则可能破坏调度器状态。同优先级轮流跑✅ 补充configUSE_TIME_SLICING1时同优先级任务每个 SysTick 心跳轮一次实现时间片轮转。两个LedTask优先级一样 → 一人闪一下。5configMAX_SYSCALL_INTERRUPT_PRIORITY控制哪些中断能调 FreeRTOS API。ARM Cortex-M 的优先级数字越小越高。FreeRTOS 用它画了一条分界线优先级 0 ──── 最高紧急硬件故障 优先级 1 优先级 2 优先级 3 优先级 4 ────────── configMAX_SYSCALL_INTERRUPT_PRIORITY 0x50 ( 5) ← 这里画线 优先级 5 ... 优先级 15 ─── 最低线以上的 ISR0~4线以下的 ISR5~15能调xQueueSendFromISR❌ 不能✅ 能能调vTaskDelay❌ 不能❌ 不能ISR 里本来就不能能被 FreeRTOS 关中断屏蔽❌ 不能✅ 能适合放什么紧急故障处理、电机刹车CAN 中断、UART 中断、定时器为什么线以上不能调因为这些超高优先级中断不会被 FreeRTOS 临界区屏蔽但 ISR 里调 API 可能改任务链表调度器同时也在改 → 数据冲突。所以一刀切线以上禁止调 API。6configUSE_TIME_SLICING控制同优先级任务要不要轮流跑。configUSE_TIME_SLICING 1时间片轮转 LedTask1 (优先级1): ██░░░░░░░░████░░░░░░░░ LedTask2 (优先级1): ░░████░░░░░░░░████░░░░ ↑每1ms心跳两个TASK轮流上阵 谁也别想独占 CPU每人一个 tick 就换。 configUSE_TIME_SLICING 0不轮转 LedTask1 (优先级1): ████████████████████████ LedTask2 (优先级1): ░░░░ ↑只有上面的被vTaskDelay了底下才有机会跑 先拿到 CPU 的就一直跑除非自己主动让vTaskDelay/等队列。开还是不开工控一般开1时间片轮转让同优先级任务公平共享 CPU避免一个任务因为没调 delay 就饿死别人。7vTaskDelay不能在中断里调用vTaskDelay的意思是让当前任务睡 n 个 tick后再继续执行。但 ISR 不是任务——它是硬件中断CPU 暂停当前任务、冲进去处理紧急事件处理完必须立刻退出。你让 ISR 里面 sleep等于让消防员到了火场先喝杯茶。所以 FreeRTOS 把 API 分两类场景用什么例子任务里调普通 APIvTaskDelay()、xQueueSend()ISR 里调带 FromISR 后缀的xQueueSendFromISR()、xSemaphoreGiveFromISR()vTaskDelay根本没有FromISR版本——因为这件事在 ISR 里毫无意义。ISR 是来干活走的不是来睡觉的。二中断优先级 vs 任务优先级1两个世界两套规则硬件中断优先级NVICFreeRTOS 任务优先级在哪设HAL_NVIC_SetPriority(IRQn, 抢占, 响应)xTaskCreate(..., priority, ...)数字方向小 高优先级大 高优先级为什么ARM 芯片架构规范人读代码 31 更直观范围0~154 位抢占时0 ~ configMAX_PRIORITIES-1我们设置宏为320 是什么最高优先级中断最低优先级任务空闲任务最高值是什么最低优先级中断最高优先级任务2硬件中断永远优先于任务ARM 硬件规定任何中断来了正在跑的任务必须让路。跟 FreeRTOS 任务优先级无关。3中断内的一根分界线FreeRTOS 画的一条安全红线值 5。划分了两个区域优先级 0 ─┐ 优先级 1 │ 能打断任务 ✅ 优先级 2 │ 能打断任何中断 ✅ 优先级 3 │ 能调 FreeRTOS API ❌ 优先级 4 ─┘ ────────── configMAX_SYSCALL_INTERRUPT_PRIORITY 5 ────────── 优先级 5 ─┐ 优先级 6 │ 能打断任务 ✅ 优先级 ... │ 可以被 FreeRTOS 临界区屏蔽 ✅ 优先级 15 ─┘ 能调 FromISR API ✅优先级5~5的硬件中断打断当前运行的任务时可以调用rtos 的库函数优先级0~4的硬件中断不可以。4为什么 0~4 不能用FreeRTOS 操作任务链表时会进入临界区设置 BASEPRI 5暂时屏蔽5~15的中断。但0~~4 的中断拦不住——如果此时冲进来并调了 FreeRTOS API正在修改的任务链表是半成品一碰就崩。跟权限无关纯粹是时序冲突——调度器改数据改到一半你闯进来读读到的是垃圾。5调 FreeRTOS API 是什么意思就是调 FreeRTOS 库函数。ISR 里必须用带FromISR后缀的版本普通 API任务里用FromISR 版中断里用干什么xQueueSend(Queue, data, 0)xQueueSendFromISR(Queue, data, NULL)发数据给任务xSemaphoreGive(Sem)xSemaphoreGiveFromISR(Sem, NULL)释放信号量通知任务vTaskDelay(100)不存在 FromISR 版本ISR 不能睡觉所以没有xQueueReceive(...)不存在 FromISR 版本ISR 不能阻塞等数据实际代码长这样// ── 刹车中断优先级 0不准碰 FreeRTOS ── HAL_NVIC_SetPriority(BRK_IRQn, 0, 0); void BRK_IRQHandler(void) { // 直接操作寄存器关 MOSFET不调任何 FreeRTOS API TIM1-BDTR ~TIM_BDTR_MOE; } // ── CAN 中断优先级 6≥5可以调 FromISR API ── HAL_NVIC_SetPriority(CAN_IRQn, 6, 0); void CAN_IRQHandler(void) { CAN_Frame frame; HAL_CAN_Receive(...); // HAL 自己的 API随便调 xQueueSendFromISR(can_queue, frame, NULL); // FromISR ✅ 通知任务处理 } // ── 任务数字大的优先 ── xTaskCreate(CriticalTask, CRIT, 256, NULL, 3, NULL); // 优先级 3高 xTaskCreate(IdleWork, IDLE, 128, NULL, 1, NULL); // 优先级 1低硬件中断 FreeRTOS 任务 数字小 高 数字大 高 优先级最高: 中断 0 Task 31优先级最高 ↓ ↑ 中断 4 Task 3 (vTaskDelay 可被中断打断) ─ ─ configMAX 5 ─ ─ 中断 5 Task 0 (空闲永远最低) ↓ 优先级最低: 中断 15三FreeRTOS 三种调度方式1定义调度器 决定当前该跑哪个任务的算法。FreeRTOS 支持三种调度方式规则现行状态抢占式调度高优先级任务就绪 → 立刻抢走 CPU✅ 主力configUSE_PREEMPTION1时间片调度同优先级任务每人一个 SysTick tick轮流用 CPU✅ 配合抢占式用协程式调度任务自己主动放 CPU高优先级也不抢低优先级❌ 官方不再更新2“抢占式优先级 2: Task2 ██████░░░░░░██████ 高优先级就绪立刻抢 优先级 1: Task1 ░░░░████░░░░░░░░░░ 被抢走排队等优先级高的任务只要就绪立刻抢占正在跑的低优先级任务。3时间片Task1 (优先级1): ██░░░░██░░░░██░░░░ Task2 (优先级1): ░░██░░░░██░░░░██░░ Task3 (优先级1): ░░░░██░░░░██░░░░██ ├── 1 tick 1ms ──┤同优先级任务轮流跑每次 SysTick 中断切换一次。一个时间片 一个 SysTick 周期你设的 1ms。前提configUSE_PREEMPTION 1抢占式必须开时间片是抢占式的子功能configUSE_TIME_SLICING 1同优先级的多个任务都处于就绪态4协程式Task2 (优先级2): ______ ← 高优先级也没用 Task1 (优先级1): ████████████████ 不管优先级多低占住就不放 ├──── 一直跑 ────┤ Task2 只能干等任务不主动放 CPU谁也赶不走它。这跟裸机while(1)一个德行——一个任务卡住全院熄灯。官方已弃坑。5实际只会用到抢占时间片configUSE_PREEMPTION 1 // 高优先级抢低优先级 configUSE_TIME_SLICING 1 // 同优先级轮流跑 效果 优先级 3: 紧急任务 ← 一来就跑无人能挡 优先级 2: TaskA █░█░█░█░ ← 高优先级不在时跑 优先级 2: TaskB ░█░█░█░█ ← 跟 TaskA 同优先级轮流 优先级 1: TaskC ░░░░░░░░░ ← 没人跑才轮到你空闲任务同级四任务的运行状态任务状态1四种状态状态含义怎么进去怎么出来运行态CPU 正在跑这个任务调度器选中被抢占/主动让出/调阻塞 API就绪态能跑了但 CPU 在忙别人被唤醒/解挂调度器选中你 → 运行态阻塞态暂时不能跑在等vTaskDelay()/ 等队列 / 等信号量时间到 / 事件到 → 就绪态挂起态人为暂停不参与调度vTaskSuspend()vTaskResume()→ 就绪态STM32 同一时刻只有一个任务处于运行态单核 CPU一次只能跑一个。2状态切换调度器选中 就绪态 ───────────────────────→ 运行态 ↑ │ │ 被抢占 / 主动让出 │ └──────────────────────────────┘ ↑ 事件到 / 时间到 │ vTaskDelay() / 等队列 │ ↓ 阻塞态 ←────────────────────── 运行态 ↑ vTaskResume() │ vTaskSuspend() │ ↓ 挂起态 ←────────────────────── 运行态 阻塞态 ── vTaskSuspend() ──→ 挂起态3三个列表除了运行态其他三种状态各有一个任务列表调度器从列表里找人列表变量名干什么就绪列表pxReadyTasksLists[x]x 优先级每个优先级一个链表。调度器找最高优先级链表里第一个任务开跑阻塞列表pxDelayedTaskList按唤醒时间排序。SysTick 每次检查队首是不是到时间了到就挪去就绪列表挂起列表xSuspendedTaskList纯手工操作。不调vTaskResume永远回不去调度器当它不存在就绪列表是数组每个优先级一个槽。32 个优先级的系统就有pxReadyTasksLists[0]到pxReadyTasksLists[31]。调度器用位图一个 32 位变量快速查哪个槽非空——bit x1 表示优先级 x 有任务在排队。三个任务优先级都是 1 的时候才在同一个槽pxReadyTasksLists[1] → task1 → task2 → task3链表串起来 pxReadyTasksLists[2] → 空 pxReadyTasksLists[3] → 空 调度器找[3]空 → [2]空 → [1]非空 → 取链表第一个 task1 跑 一个 tick 后 → task1 挪到链表尾 → task2 跑 再一个 tick → task2 挪到尾 → task3 跑谁建的优先级就进谁的槽同优先级的用链表串在同一个槽里。4一句话运行态只有 1 个就绪态排队等 CPU阻塞态在等时间/事件挂起态死了——得手动复活。五FreeRtosConfig.H#define xPortPendSVHandler PendSV_Handler #define vPortSVCHandler SVC_Handler两个宏为了兼容不同内核架构的芯片比如换成 RISC-V 芯片// STM32Cortex-M #define xPortPendSVHandler PendSV_Handler // 某个 RISC-V 芯片假设向量表里叫 Machine_Timer_Handler #define xPortPendSVHandler Machine_Timer_Handler // ESP32Xtensa 架构 #define xPortPendSVHandler _xt_timer_handlerFreeRTOS 的port.c一行不改只换这个宏就能跑到不同芯片上。这就是跨平台移植的精髓——内核代码不动只改 Config 头文件里跟硬件对接的那几行。1PendSV_Handler中断做什么2SVC_Handler中断做什么中断谁触发干什么频率SVC软件手动svc 0启动第一个任务从裸机跨进 RTOS一生一次PendSVSysTick 说该切了保存当前任务 → 恢复下一个任务每秒 1000 次3上电后执行流程上电全流程① 芯片上电 │ ▼ ② 硬件读向量表首地址 → Reset_Handler │ SystemInit()配置 Flash 延迟、FPU │ 进 main() │ ▼ ③ main() 裸机 │ HAL_Init() → SysTick_Init() → TIM7_Init() │ LED_Init() → Usart1_Init() → ... │ xTaskCreate(Led_Task) │ xTaskCreate(Uart_Task) │ ▼ ④ vTaskStartScheduler() │ 配 SysTick 1ms │ 创建空闲任务 │ svc 0 ← 手动触发 SVC │ ▼ ⑤ SVC_Handler ( vPortSVCHandler) │ 切到第一个任务的栈PSP │ 弹出任务寄存器 │ 跳转任务函数 → Led_Task 开跑 │ ▼ ⑥ FreeRTOS 永远循环 │ ├─ SysTick 每 1ms 中断 │ xPortSysTickHandler() → xTickCount │ 看看要不要切任务 │ ├─ 不用切 → 返回当前任务继续跑 │ └─ 要切 → 挂起 PendSV │ ├─ PendSV_Handler ( xPortPendSVHandler) │ 压栈当前任务 → 弹栈下个任务 → 切过去 │ └─ Task A ↔ Task B ↔ Idle Task ... 无穷4: 为什么 PendSV 不直接在 SysTick 中断里切如果 SysTick 中断里直接切任务 硬件中断比如 UART优先级 SysTick → UART 中断来SysTick 切任务切到一半被抢走 → 任务链表是半成品 → 崩 用 PendSV SysTick 只标记该切了 → 等所有硬件中断跑完 → PendSV 优先级最低没中断了才上场 → 安安静静切换不会被打断SysTick 负责判断要不要切PendSV 负责执行切。一个决策层一个执行层中间隔了所有硬件中断。