RTOS-F429-HAL-基础内容认识(2026/7/26)

目录

[一:rtos 和裸机的特点对比](#一:rtos 和裸机的特点对比)

1:裸机

2:RTOS

3:RTOS的四个特点:

4:两个注意事项

5:configMAX_SYSCALL_INTERRUPT_PRIORITY

6:configUSE_TIME_SLICING

7:vTaskDelay不能在中断里调用:

[二:中断优先级 vs 任务优先级](#二:中断优先级 vs 任务优先级)

1:两个世界,两套规则

2:硬件中断永远优先于任务

3:中断内的一根分界线

[4:为什么 0~4 不能用](#4:为什么 0~4 不能用)

[5:调 FreeRTOS API 是什么意思](#5:调 FreeRTOS API 是什么意思)

[三:FreeRTOS 三种调度方式](#三:FreeRTOS 三种调度方式)

1:定义

2"抢占式

3:时间片

4:协程式

5:实际只会用到抢占+时间片

四:任务的运行状态

1:四种状态

2:状态切换

3:三个列表

4:一句话

五:FreeRtosConfig.H

1:PendSV_Handler中断做什么?

2:SVC_Handler中断做什么?

3:上电后执行流程

[4: 为什么 PendSV 不直接在 SysTick 中断里切](#4: 为什么 PendSV 不直接在 SysTick 中断里切)


一:rtos 和裸机的特点对比

RTOS全称为:Real Time OS,就是实时操作系统,强调的是:实时性

*RTOS 四大特点**:

  1. 分而治之:实现功能划分为多个任务。

  2. 延时函数:任务调度。

  3. 抢占式:高优先级任务抢占低优先级任务。

  4. 任务堆栈:每个任务都有自己的栈空间。

注意事项

  • 注意1:中断可以打断任意任务。

  • 注意2:任务可以同等优先级。

1:裸机

复制代码
main()
{
    Init();
    while(1)
    {
        任务A();        // 跑完
        任务B();        // 才轮到
        任务C();        // 最后
    }
}

特点:一行等一行。 任务 A 里有个 HAL_Delay(1000),B 和 C 就得跟着等 1 秒。CPU 全速空转,什么也干不了。

2:RTOS

复制代码
main()
{
    Init();
    xTaskCreate(任务A);
    xTaskCreate(任务B);
    xTaskCreate(任务C);
    vTaskStartScheduler();    ← 永不返回
}

特点:调度器在中间切。 任务 A 调 vTaskDelay(1000) → 调度器立刻把 CPU 给 B → B 跑一阵也 delay → 调度器给 C。1 秒到了,A 被按时唤醒。

3:RTOS的四个特点:

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_SLICING=1 时,同优先级任务每个 SysTick 心跳轮一次,实现时间片轮转。两个 LedTask 优先级一样 → 一人闪一下。

5:configMAX_SYSCALL_INTERRUPT_PRIORITY

控制:哪些中断能调 FreeRTOS API。

ARM Cortex-M 的优先级数字越小越高。FreeRTOS 用它画了一条分界线:

复制代码
优先级 0 ──── 最高,紧急硬件故障
优先级 1
优先级 2
优先级 3
优先级 4
────────── configMAX_SYSCALL_INTERRUPT_PRIORITY = 0x50 (= 5) ← 这里画线
优先级 5
...
优先级 15 ─── 最低
线以上的 ISR(0~4) 线以下的 ISR(5~15)
能调 xQueueSendFromISR ❌ 不能 ✅ 能
能调 vTaskDelay ❌ 不能 ❌ 不能(ISR 里本来就不能)
能被 FreeRTOS 关中断屏蔽 ❌ 不能 ✅ 能
适合放什么 紧急故障处理、电机刹车 CAN 中断、UART 中断、定时器

为什么线以上不能调? 因为这些超高优先级中断不会被 FreeRTOS 临界区屏蔽,但 ISR 里调 API 可能改任务链表,调度器同时也在改 → 数据冲突。所以一刀切:线以上禁止调 API。


6:configUSE_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 就饿死别人。

7:vTaskDelay不能在中断里调用:

vTaskDelay 的意思是"让当前任务睡 n 个 tick后再继续执行"。但 ISR 不是任务------它是硬件中断,CPU 暂停当前任务、冲进去处理紧急事件,处理完必须立刻退出。你让 ISR 里面 sleep,等于让消防员到了火场先喝杯茶。

所以 FreeRTOS 把 API 分两类:

场景 用什么 例子
任务里调 普通 API vTaskDelay()xQueueSend()
ISR 里调 带 FromISR 后缀的 xQueueSendFromISR()xSemaphoreGiveFromISR()

vTaskDelay 根本没有 FromISR 版本------因为这件事在 ISR 里毫无意义。ISR 是来干活走的,不是来睡觉的。

二:中断优先级 vs 任务优先级

1:两个世界,两套规则

硬件中断优先级(NVIC) FreeRTOS 任务优先级
在哪设 HAL_NVIC_SetPriority(IRQn, 抢占, 响应) xTaskCreate(..., priority, ...)
数字方向 小 = 高优先级 大 = 高优先级
为什么 ARM 芯片架构规范 人读代码 3>1 更直观
范围 0~15(4 位抢占时) 0 ~ configMAX_PRIORITIES-1(我们设置宏为32)
0 是什么 最高优先级中断 最低优先级任务(空闲任务)
最高值是什么 最低优先级中断 最高优先级任务

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_PREEMPTION=1
时间片调度 同优先级任务每人一个 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 x=1 表示优先级 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 芯片:

复制代码
// STM32(Cortex-M)
#define xPortPendSVHandler PendSV_Handler

// 某个 RISC-V 芯片(假设向量表里叫 Machine_Timer_Handler)
#define xPortPendSVHandler Machine_Timer_Handler

// ESP32(Xtensa 架构)
#define xPortPendSVHandler _xt_timer_handler

FreeRTOS 的 port.c 一行不改,只换这个宏,就能跑到不同芯片上。 这就是跨平台移植的精髓------内核代码不动,只改 Config 头文件里跟硬件对接的那几行。

1:PendSV_Handler中断做什么?

2:SVC_Handler中断做什么?

中断 谁触发 干什么 频率
SVC 软件手动 svc 0 启动第一个任务:从裸机跨进 RTOS 一生一次
PendSV SysTick 说"该切了" 保存当前任务 → 恢复下一个任务 每秒 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 负责"执行切"。 一个决策层,一个执行层,中间隔了所有硬件中断。

相关推荐
Discipline~Hai2 小时前
ARM03-蜂鸣器和中断
c语言·arm开发·单片机·嵌入式硬件·嵌入式
冻柠檬飞冰走茶3 小时前
PTA基础编程题目集 7-7 12-24小时制(C语言实现)
c语言·开发语言·数据结构·算法
runafterhit3 小时前
python基础语法命令(C程序员刷leetcode)
c语言·python·leetcode
笨笨饿4 小时前
#102_Codex无在VSCold无法打开
java·c语言·数据库·笔记
wuyk5556 小时前
66.嵌入式C语言进阶:共用体(Union)实战指南——单片机内存省一半的神级技巧
c语言·开发语言·stm32·单片机·嵌入式硬件
2601_9516443215 小时前
C语言二级备考指南
c语言·编程基础·编译环境·二级考试·题库选择
日拱一卒——功不唐捐20 小时前
红黑树删除(C语言)
c语言·数据结构
ziguo11221 天前
C/C++ 错误处理全解:从 errno 到 C++ 异常
linux·c语言·c++·windows·visual studio
小+不通文墨1 天前
--no-multibyte-chars 解决OLED_data.c移植后中文乱码问题
c语言·经验分享·笔记·学习