1. ARMv7-M 异常模型

在 操作系统开发:(4) 系统级程序员模型、模式、特权、栈、异常与函数序言(实现 portable.h )中,我们讲了 ARMv7-M 对异常的定义、支持的4大类异常、异常的3种状态、异常的优先级和异常返回,这里详细介绍 ARMv7-M 的各个异常。

Armv7-M配置文件与其他Armv7配置文件的不同之处在于,在异常进入和退出时使用硬件保存和恢复关键上下文状态,并使用向量表来指示异常入口点。此外,Armv7-M配置文件中的异常分类与其他Armv7配置文件不同。

异常类型

触发条件

优先级

特性

主要用途

Reset

电源开启或复位信号

固定 -3

有两种复位级别:
- 上电复位:重置全部
- 本地复位:保留部分资源

系统启动和恢复

NMI

硬件生成或软件设置

固定 -2

不可屏蔽中断

紧急情况处理

HardFault

无法被其他异常处理的通用故障

固定 -1

用于不可恢复的系统故障;
故障升级机制(其他故障升级为 HardFault)

故障处理

MemManage

MPU 检测到内存保护故障

可配置 (0=最高)

处理指令/数据内存保护故障;
软件可禁用(禁用则升级为 HardFault)

内存保护

BusFault

内存相关的总线错误

可配置 (0=最高)

检测总线错误;
可报告同步/异步故障;
软件可禁用(禁用则升级为 HardFault)

总线错误处理

UsageFault

非内存相关的指令执行错误

可配置 (0=最高)

触发条件:
- 未定义指令
- 状态无效
- 异常返回错误
- 访问禁用协处理器;
软件可禁用(禁用则升级为 HardFault)

指令错误处理

DebugMonitor

调试事件发生

可配置 (0=最高)

调试事件处理;
同步异常;
调试断点为异步异常

调试支持

SVCall

SVC #n 指令执行

固定 -1

由 SVC 指令显式触发;
仅用于系统调用

系统调用

PendSV

软件设置

可配置 (0=最高)

用于软件生成的系统调用;
RTOS 任务切换核心

任务切换

SysTick

SysTick 定时器

可配置 (0=最高)

系统滴答定时器;
RTOS 时间管理核心

系统定时

Interrupts

外部事件

可配置 (0=最高)

最多 496 个外部中断;
支持中断嵌套;
高优先级中断可抢占低优先级

外部事件处理

如表所示:

特性

SVCall

PendSV

触发方式

执行 SVC #n 指令(同步

设置 ICSR.PENDSVSET 位(异步

触发时机

立即执行(下一条指令就是异常入口)

延迟执行(等所有高优先级中断处理完才执行)

优先级

固定 -1(高优先级)

通常配置为 最低优先级(如 255)

核心用途

用户程序主动请求系统服务

操作系统被动调度(如任务切换)

2. 异常处理与伪造栈帧 port_pu32InitStack

2.1 异常处理(伪造栈帧的原理)

Cortex-M 处理器在进入异常时,会自动将 8 个寄存器的值压入当前栈(称为“自动保存”):

高地址
| xPSR   | ← 栈顶初始位置
| PC     |
| LR     |
| R12    |
| R3     |
| R2     |
| R1     |
| R0     |
低地址

当执行 BX LR  时,硬件检测到 LR → 触发返回流程。

  • 如果 LR 是一个普通地址(如 0x20001235),则正常跳转。
  • 但如果 LR 是 EXC_RETURN 值(如 0xFFFFFFF9),处理器就知道:“这不是普通返回,这是异常返回。”

于是,处理器会自动从栈中弹出之前压入的那 8 个寄存器(R0-R3, R12, LR, PC, xPSR),把栈里保存的寄存器值,原封不动地放回对应的寄存器,从而恢复异常发生前的上下文,并跳转回原来的执行位置。

Cortex-M 的硬件强制约束:只有异常返回能切换栈指针和模式。

// 伪代码:BX LR 指令执行时,硬件内部逻辑
if ((LR & 0xF0000000) == 0xF0000000) {  // 高 4 位 = 0xF(即高 28 位全 1)
    // 这是 EXC_RETURN!执行异常返回流程:
    1. 根据 LR 低 4 位决定:用 MSP 还是 PSP?返回 Thread 还是 Handler?
    2. 从栈中自动弹出 8 个寄存器(R0-R3, R12, LR, PC, xPSR)
    3. 恢复模式(Thread/Handler)和特权级别
} else {
    // 普通跳转:仅设置 PC = LR
    PC = LR;
}

本代码中:

// 57. 初始化任务栈函数定义,头文件 64
// 在创建任务时,手动模拟一个中断返回时的栈帧结构,以便任务第一次被调度运行时,能像从中断返回一样正确地跳转到任务入口函数
StackType_t* port_pu32InitStack(StackType_t* pu32TopOfStack, TaskFunc_t pfCode, void* pvParameters ){
    pu32TopOfStack--;
    *pu32TopOfStack = portINITIAL_XPSR;                                    /* xPSR */
    pu32TopOfStack--;
    *pu32TopOfStack = ( ( StackType_t ) pfCode ) & portSTART_ADDRESS_MASK; /* PC */
    pu32TopOfStack--;
    *pu32TopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS;             /* LR */
    pu32TopOfStack -= 5;                            /* R12, R3, R2 and R1. */
    *pu32TopOfStack = ( StackType_t ) pvParameters; /* R0 */
    pu32TopOfStack--;
    *pu32TopOfStack = portINITIAL_EXC_RETURN;
    pu32TopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
    return pu32TopOfStack;
}

实现了:

上电
  ↓
CPU 读 0x0 / 0x4 → 设置 SP 和 PC
  ↓
执行 Reset_Handler → 调用 main()
  ↓
main() 中创建任务 → 分配栈 + 调用 port_pu32InitStack 伪造栈帧
  ↓
启动调度器
  ↓
触发 PendSV 中断
  ↓
PendSV 设置 PSP = 之前伪造栈地址,然后执行 BX LR(LR 被设置为了特殊值,CPU识别其为异常返回指令)
  ↓
CPU 切换栈指针为 PSP,自动弹出寄存器之前设置的8个寄存器 → 设置 PC 为伪造的 PC 即任务函数地址
  ↓
任务开始执行!

阶段

执行者

堆栈状态

代码作用

1. 任务创建时

调度器/主线程
(运行在 Handler 模式或 Privileged Thread 模式)

使用的是 MSP(主栈)或当前任务的 PSP

port_pu32InitStack 只是向新任务的私有堆栈内存块中写入一些初始值
此时没有任何异常发生,也没有上下文切换
写入的内存尚未被 CPU 当作栈帧使用,它只是一块普通 RAM。

2. 任务首次运行时
(第一次被调度)

Cortex-M 硬件在 PendSV 异常返回时

CPU 将 新任务的堆栈指针(PSP)指向这块内存

CPU 自动从这块内存弹出 8 个字,并将其解释为:
• xPSR
• PC
• LR
• R12, R3–R0
此时,这块内存才真正成为一个“栈帧” , 因为 CPU 把它当作异常返回的现场来恢复

2.2 任务返回地址

本代码中:

*pu32TopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS;             /* LR */
// 42. 任务返回地址定义(默认)
#define portTASK_RETURN_ADDRESS    port_vTaskExitError

这里把返回地址设置为 port_vTaskExitError 函数,是因为任务函数绝对不能 return。

因为任务函数的调用方式不是普通函数调用

  • 任务是通过 伪造中断栈帧 + 异常返回 启动的
  • CPU 是跳转到任务函数的,没有正常的调用栈
  • 任务函数的调用者根本不存在

所以:

  • CPU 会尝试从 LR(Link Register) 跳回调用者
  • 但 LR 被初始化为 0xFFFFFFFF(一个非法地址)
  • 尝试跳转到 0xFFFFFFFF → 触发 HardFault(硬件异常)
  • 系统崩溃

故将 LR(链接寄存器)设置为 port_vTaskExitError 函数的地址(错误处理函数)进行死循环。

3. SVC 异常处理 port_vSVCHandler

port_vSVCHandler 负责将系统从 单线程启动模式 切换到 多任务运行模式将 CPU 从 Handler mode(特权)无缝切换到第一个任务的 Thread mode(非特权 + PSP)

// 59. SVC异常处理函数定义,把系统从“启动模式”切换到“任务运行模式”,让第一个任务开始干活
// msr psp, r0 把栈指针切换到任务专用的栈
void port_vSVCHandler( void ){
    __asm volatile (
        "   ldr r3, pxCurrentTCBConst2      \n" /* Restore the context. */
        "   ldr r1, [r3]                    \n" /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
        "   ldr r0, [r1]                    \n" /* The first item in pxCurrentTCB is the task top of stack. */
        "   ldmia r0!, {r4-r11, r14}        \n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
        "   msr psp, r0                     \n" /* Restore the task stack pointer. */
        "   isb                             \n"
        "   mov r0, #0                      \n"
        "   msr basepri, r0                 \n"
        "   bx r14                          \n"
        "                                   \n"
        "   .align 4                        \n"
        "pxCurrentTCBConst2: .word pxCurrentTCB             \n"
        );
}

3.1 SVC 异常触发时的状态

当执行 svc #0 指令后,硬件自动完成以下操作:

硬件自动行为

说明

寄存器/栈状态

1. 保存 8 个寄存器

压入当前栈(MSP)

栈中:[xPSR, PC, LR, R12, R3, R2, R1, R0]

2. 切换到 Handler mode

进入异常处理模式

CONTROL[0]=0(特权)

3. 设置 LR = 0xFFFFFFF9

标记"返回时用 MSP"

LR = 0xFFFFFFF9

4. 跳转到 SVC 向量

执行 port_vSVCHandler

PC = port_vSVCHandler

此时 CPU 仍在 MSP 栈上运行,但我们需要切换到 任务专用的 PSP 栈。

3.2 执行的操作

步骤

汇编指令

操作说明

寄存器/内存变化

硬件状态

设计目的

0

SVC 异常触发前

执行 svc #0 指令

PC = port_vStartFirstTask+8
SP = MSP = 0x2000_1000

Thread mode
特权 + MSP

触发异常的起点

1

硬件自动压栈
(非软件指令)

硬件自动保存 8 个寄存器

栈内容 (MSP 栈):
0x2000_0FFC: xPSR
0x2000_0FF8: PC (svc 指令后地址)
0x2000_0FF4: LR (调用者地址)
0x2000_0FF0: R12
0x2000_0FEC: R3
0x2000_0FE8: R2
0x2000_0FE4: R1
0x2000_0FE0: R0
SP = MSP = 0x2000_0FE0

切换到 Handler mode
LR = 0xFFFFFFF9
CONTROL[0]=0 (特权)

为异常处理保存现场

2

ldr r3, pxCurrentTCBConst2

加载 pxCurrentTCB 变量地址

r3 = 0x2000_0004
(pxCurrentTCB 变量地址)

无变化

获取任务控制块指针地址

3

ldr r1, [r3]

读取 TCB 指针值

r1 = 0x2000_0100
(task1_TCB 地址)

无变化

获取当前任务的 TCB 地址

4

ldr r0, [r1]

读取任务栈顶指针

r0 = 0x2000_0800
(task1 栈顶地址)

无变化

关键:TCB 首成员 = 栈顶指针

5

ldmia r0!, {r4-r11, r14}

从任务栈恢复 9 个值

加载顺序:
r4 = [0x2000_0800] = 0
r5 = [0x2000_0804] = 0
...
r11 = [0x2000_081C] = 0
r14 = [0x2000_0820] = 0xFFFFFFFD
r0 = 0x2000_0824 (自增 36 字节)

无变化

核心魔法
- 恢复 R4-R11 (硬件未自动保存)
- r14 = EXC_RETURN (0xFFFFFFFD)

6

msr psp, r0

设置 PSP 为任务栈指针

PSP = 0x2000_0824
PSP 栈内容:
0x2000_0840: xPSR = 0x01000000
0x2000_083C: PC = task1_entry
0x2000_0838: LR = port_vTaskExitError
0x2000_0834: R12 = 0
0x2000_0830: R3 = 0
0x2000_082C: R2 = 0
0x2000_0828: R1 = 0
0x2000_0824: R0 = 任务参数

无变化

栈指针切换准备
指向硬件自动保存的 8 个寄存器位置

7

isb

指令同步屏障

无寄存器变化

确保 msr psp 完全生效

防止流水线导致后续指令使用旧 PSP 值

8

mov r0, #0
msr basepri, r0

清除 BASEPRI 屏蔽

BASEPRI = 0

允许所有优先级中断响应

解除中断屏蔽
确保任务运行时能响应中断

9

bx r14

跳转到 r14 指向地址

r14 = 0xFFFFFFFD

硬件自动行为:
1. 检测高28位=0xFFFFFFF → 异常返回
2. 从 PSP 弹出 8 个寄存器
3. PSP = 0x2000_0844 (SP+32)
4. 切换到 Thread mode
5. 切换到 非特权 (CONTROL[0]=1)

模式/栈切换完成:
- 栈指针: MSP → PSP
- 模式: Handler → Thread
- 特权: 特权 → 非特权

10

任务开始运行

硬件跳转到任务入口

PC = task1_entry
R0 = 任务参数
SP = PSP = 0x2000_0844

Thread mode
非特权 + PSP

第一个任务正式启动

  1. 硬件压栈:svc #0 触发后,硬件自动保存 8 个寄存器到 MSP 栈
  2. 软件恢复:从任务栈恢复 R4-R11 + EXC_RETURN (0xFFFFFFFD) 到 r14
  3. 硬件弹栈:bx r14 触发异常返回,硬件自动从 PSP 弹出 8 个寄存器并切换模式/栈

4. 启动第一个任务 port_vStartFirstTask

// 60. 启动第一个任务函数定义,完成最后的系统初始化,然后按下 SVC 0 按钮,触发之前的 port_vSVCHandler,让第一个任务正式开始运行
static void port_vStartFirstTask( void ){
    __asm volatile (
        " ldr r0, =0xE000ED08   \n" /* Use the NVIC offset register to locate the stack. */
        " ldr r0, [r0]          \n"
        " ldr r0, [r0]          \n"
        " msr msp, r0           \n" /* Set the msp back to the start of the stack. */
        " mov r0, #0            \n" /* Clear the bit that indicates the FPU is in use, see comment above. */
        " msr control, r0       \n"
        " cpsie i               \n" /* Globally enable interrupts. */
        " cpsie f               \n"
        " dsb                   \n"
        " isb                   \n"
        " svc 0                 \n" /* System call to start first task. */
        " nop                   \n"
        " .ltorg                \n"
        );
}

步骤

汇编指令

操作说明

寄存器/内存变化

硬件状态

设计目的

0

函数调用前

port_lStartScheduler() 调用此函数

SP = MSP (启动栈)
CONTROL = ? (不确定)

Thread mode
特权状态(因在调度器启动前)

进入启动第一个任务的准备阶段

1

ldr r0, =0xE000ED08

加载 VTOR 寄存器地址

r0 = 0xE000ED08
(SCB->VTOR 地址)

无变化

获取向量表基地址寄存器地址

2

ldr r0, [r0]

读取 VTOR 寄存器值

r0 = 0x08000000
(向量表基地址,通常为 Flash 起始地址)

无变化

获取中断向量表在内存中的实际位置

3

ldr r0, [r0]

读取向量表第 0 项(MSP 初始值)

r0 = 0x20001000
(启动时的主栈顶地址)

无变化

关键:向量表第 0 项 = 复位后的 MSP 初始值

4

msr msp, r0

设置 MSP 为初始栈顶

MSP = 0x20001000

无变化

恢复主栈
确保中断使用正确的栈空间

5

mov r0, #0
msr control, r0

清除 CONTROL 寄存器

CONTROL = 0b00
- bit 0 = 0 → 特权模式
- bit 1 = 0 → 使用 MSP

无变化

强制特权 + MSP
确保后续操作在特权模式下执行

6

cpsie i

使能全局中断

PRIMASK = 0

允许所有可屏蔽中断

开启中断系统
为后续异常处理做准备

7

cpsie f

使能浮点异常(若支持)

FAULTMASK = 0

允许浮点异常

确保 FPU 异常可被处理

8

dsb

数据同步屏障

无寄存器变化

确保之前所有内存操作完成

防止指令重排序导致状态不一致

9

isb

指令同步屏障

无寄存器变化

刷新流水线

确保 cpsie 指令完全生效后再执行下一条

10

svc 0

触发 SVC 异常

PC = 当前指令地址+2
(即将执行 nop

硬件自动行为:
1. 保存 8 个寄存器到 MSP 栈:
- xPSR0x20000FFC
- PC (svc 后地址) → 0x20000FF8
- LR (调用者) → 0x20000FF4
- R12,R3-R00x20000FF0~0x20000FE0
2. SP = MSP = 0x20000FE0
3. LR = 0xFFFFFFF9
4. 切换到 Handler mode
5. PC = VTOR[11] (SVC 向量 → port_vSVCHandler)

模式切换:
- 栈指针: 保持 MSP
- 模式: Thread → Handler
- 特权: 保持特权

11

nop

空操作(永不执行)

无变化

无变化

占位符
防止编译器优化,实际永不执行

12

SVC 处理开始

硬件跳转到 port_vSVCHandler

PC = port_vSVCHandler
SP = MSP = 0x20000FE0

Handler mode
特权 + MSP

调度器启动的临界点
从此进入任务切换流程

  1. 恢复主栈:从向量表读取 MSP 初始值,确保中断有合法栈空间
  2. 强制特权:清零 CONTROL 寄存器,保证后续操作在特权模式下执行
  3. 触发异常svc 0 是唯一能安全启动任务的途径,利用硬件异常机制完成上下文切换

5. 启动调度器 port_lStartScheduler

// 61. 启动调度器函数定义
BaseType_t port_lStartScheduler( void ){
    // 1. CPU 版本检查
    cfgASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
    cfgASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
    // 2. 中断向量表验证(防配置错误)
    #if ( cfgCHECK_HANDLER_INSTALLATION == 1 )
    {
        const portISR_t * const ppfVectorTable = portSCB_VTOR_REG;
        cfgASSERT( ppfVectorTable[ portVECTOR_INDEX_SVC ] == port_vSVCHandler );
        cfgASSERT( ppfVectorTable[ portVECTOR_INDEX_PENDSV ] == port_vPendSVHandler );
    }
    #endif
    #if ( cfgASSERT_DEFINED == 1 )
    {
        volatile uint8_t u8OriginalPriority;
        volatile uint32_t u32ImplementedPrioBits = 0;
        volatile uint8_t * const pu8FirstUserPriorityRegister = ( volatile uint8_t * const ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
        volatile uint8_t u8MaxPriorityValue;
        u8OriginalPriority = *pu8FirstUserPriorityRegister;
        *pu8FirstUserPriorityRegister = portMAX_8_BIT_VALUE;
        u8MaxPriorityValue = *pu8FirstUserPriorityRegister;
        u8MaxSysCallPriority = cfgMAX_SYSCALL_INTERRUPT_PRIORITY & u8MaxPriorityValue;
        cfgASSERT( u8MaxSysCallPriority );
        cfgASSERT( ( cfgMAX_SYSCALL_INTERRUPT_PRIORITY & ( ~u8MaxPriorityValue ) ) == 0U );
        while( ( u8MaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE ){
            u32ImplementedPrioBits++;
            u8MaxPriorityValue <<= ( uint8_t ) 0x01;
        }

        if( u32ImplementedPrioBits == 8 ){
            cfgASSERT( ( cfgMAX_SYSCALL_INTERRUPT_PRIORITY & 0x1U ) == 0U );
            u32MaxPRIGROUPValue = 0;
        }else{
            u32MaxPRIGROUPValue = portMAX_PRIGROUP_BITS - u32ImplementedPrioBits;
        }
        u32MaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
        u32MaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
        *pu8FirstUserPriorityRegister = u8OriginalPriority;
    }
    #endif
    // 3. 优先级配置
    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
    portNVIC_SHPR2_REG = 0;
    port_vSetupTimerInterrupt();
    ulCriticalNesting = 0;
    port_vEnableVFP();
    *( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;
    port_vStartFirstTask();
    task_vSwitchContext();
    port_vTaskExitError();
    return 0;
}

阶段

关键操作

硬件行为

软件责任

1. 安全校验

CPU 版本/向量表检查

防止硬件缺陷和配置错误

2. 优先级配置

设置 SVC/PendSV/SysTick 优先级

构建实时性保障的优先级金字塔

3. 时钟初始化

配置 SysTick 定时器

提供任务调度的时间基准

4. FPU 初始化

启用 FPU + 懒惰保存

优化浮点任务切换性能

5. 任务启动

svc 0 触发异常

硬件自动:
- 压栈 8 寄存器
- 切换到 Handler mode

通过异常机制完成首次任务切换

6. PendSV 异常处理 port_vPendSVHandler

void port_vPendSVHandler( void )
{
    __asm volatile
    (
        "   mrs r0, psp                         \n"
        "   isb                                 \n"
        "                                       \n"
        "   ldr r3, pxCurrentTCBConst           \n" /* Get the location of the current TCB. */
        "   ldr r2, [r3]                        \n"
        "                                       \n"
        "   tst r14, #0x10                      \n" /* Is the task using the FPU context?  If so, push high vfp registers. */
        "   it eq                               \n"
        "   vstmdbeq r0!, {s16-s31}             \n"
        "                                       \n"
        "   stmdb r0!, {r4-r11, r14}            \n" /* Save the core registers. */
        "   str r0, [r2]                        \n" /* Save the new top of stack into the first member of the TCB. */
        "                                       \n"
        "   stmdb sp!, {r0, r3}                 \n"
        "   mov r0, %0                          \n"
        "   msr basepri, r0                     \n"
        "   dsb                                 \n"
        "   isb                                 \n"
        "   bl task_vSwitchContext               \n"
        "   mov r0, #0                          \n"
        "   msr basepri, r0                     \n"
        "   ldmia sp!, {r0, r3}                 \n"
        "                                       \n"
        "   ldr r1, [r3]                        \n" /* The first item in pxCurrentTCB is the task top of stack. */
        "   ldr r0, [r1]                        \n"
        "                                       \n"
        "   ldmia r0!, {r4-r11, r14}            \n" /* Pop the core registers. */
        "                                       \n"
        "   tst r14, #0x10                      \n" /* Is the task using the FPU context?  If so, pop the high vfp registers too. */
        "   it eq                               \n"
        "   vldmiaeq r0!, {s16-s31}             \n"
        "                                       \n"
        "   msr psp, r0                         \n"
        "   isb                                 \n"
        "                                       \n"
        #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata workaround. */
            #if WORKAROUND_PMU_CM001 == 1
                "           push { r14 }                \n"
                "           pop { pc }                  \n"
            #endif
        #endif
        "                                       \n"
        "   bx r14                              \n"
        "                                       \n"
        "   .align 4                            \n"
        "pxCurrentTCBConst: .word pxCurrentTCB  \n"
        ::"i" ( cfgMAX_SYSCALL_INTERRUPT_PRIORITY )
    );
}

步骤

汇编指令

操作说明

寄存器/内存变化

硬件状态

设计目的

0

PendSV 触发前

高优先级中断执行完毕

SP = MSP (中断栈)
PSP = 0x2000_0800 (task1 栈)
LR = 0xFFFFFFFD

Handler mode
特权 + MSP

PendSV 挂起位被置位,等待执行

1

mrs r0, psp

读取当前任务栈指针

r0 = 0x2000_0800
(task1 的 PSP 值)

无变化

获取任务栈顶
为保存上下文做准备

2

isb

指令同步屏障

无寄存器变化

确保 mrs psp 完全生效

防止流水线导致读取到旧的 PSP 值

3

ldr r3, pxCurrentTCBConst

加载 TCB 指针地址

r3 = 0x2000_0004
(pxCurrentTCB 变量地址)

无变化

获取当前任务控制块地址

4

ldr r2, [r3]

读取当前 TCB 指针

r2 = 0x2000_0100
(task1_TCB 地址)

无变化

指向当前运行任务的 TCB

5

tst r14, #0x10
it eq
vstmdbeq r0!, {s16-s31}

条件保存 FPU 寄存器

检查 r14[4] (EXC_RETURN bit4):
- 0 = 任务使用了 FPU → 保存 s16-s31
- 1 = 未使用 FPU → 跳过

无变化

懒惰保存优化
仅当任务实际使用 FPU 时才保存,避免 32 字节开销

6

stmdb r0!, {r4-r11, r14}

保存核心寄存器

栈操作:
[r0-4] = r14 (EXC_RETURN)
[r0-8] = r11
...
[r0-36] = r4
r0 = r0 - 36

无变化

保存硬件未自动保存的寄存器
- R4-R11: 通用寄存器
- R14: EXC_RETURN 值(含 FPU 使用标志)

7

str r0, [r2]

保存更新后的栈顶到 TCB

TCB[0] = r0
(task1_TCB->pxTopOfStack = 0x2000_07DC)

无变化

关键:TCB 首成员 = 栈顶指针,
为下次恢复任务做准备

8

stmdb sp!, {r0, r3}

保存临时寄存器到 MSP 栈

MSP 栈:
[SP-4] = r3 (TCB 地址)
[SP-8] = r0 (task1 栈顶)
SP = SP - 8

无变化

保护寄存器
为调用 C 函数做准备(C 函数会破坏 r0-r3)

9

mov r0, %0
msr basepri, r0

屏蔽低优先级中断

BASEPRI = cfgMAX_SYSCALL_INTERRUPT_PRIORITY
示例: 0x30 (48)

屏蔽优先级 ≤48 的中断

临界区保护
确保任务切换过程不被低优先级中断打断

10

dsb
isb

数据/指令同步屏障

无寄存器变化

确保 BASEPRI 生效

防止指令重排序导致保护失效

11

bl task_vSwitchContext

调用 C 函数切换任务

1. 保存当前任务状态到 TCB
2. 选择下一个任务 (task2)
3. pxCurrentTCB = task2_TCB

无变化

决策层
仅切换 TCB 指针,不操作栈

12

mov r0, #0
msr basepri, r0

恢复中断

BASEPRI = 0

允许所有中断响应

退出临界区

13

ldmia sp!, {r0, r3}

恢复临时寄存器

r0 = 0x2000_07DC (task1 栈顶)
r3 = 0x2000_0004 (TCB 变量地址)
SP = SP + 8

无变化

恢复被 C 函数破坏的寄存器

14

ldr r1, [r3]

读取新任务 TCB 指针

r1 = 0x2000_0200
(task2_TCB 地址)

无变化

指向新任务的 TCB

15

ldr r0, [r1]

读取新任务栈顶

r0 = 0x2000_0C00
(task2 栈顶地址)

无变化

获取新任务的上下文位置

16

ldmia r0!, {r4-r11, r14}

恢复核心寄存器

栈操作:
r4 = [0x2000_0C00]
...
r14 = [0x2000_0C20] (EXC_RETURN)
r0 = 0x2000_0C24

无变化

恢复 R4-R11 + EXC_RETURN

17

tst r14, #0x10
it eq
vldmiaeq r0!, {s16-s31}

条件恢复 FPU 寄存器

检查 r14[4]:
- 0 = 任务使用 FPU → 恢复 s16-s31
- 1 = 未使用 → 跳过

无变化

懒惰恢复:仅当需要时恢复 FPU 状态

18

msr psp, r0

设置 PSP 为新任务栈

PSP = 0x2000_0C24

无变化

栈指针切换
指向新任务的自动保存区(xPSR/PC/LR...)

19

isb

指令同步屏障

无寄存器变化

确保 PSP 更新生效

防止后续 bx r14 使用旧 PSP 值

20

bx r14

触发异常返回

r14 = 0xFFFFFFFD

硬件自动行为:
1. 检测 EXC_RETURN → 异常返回
2. 从 PSP 弹出 8 个寄存器:
- R0-R3, R12 → 通用寄存器
- LR → 返回地址 (port_vTaskExitError)
- PC → task2_entry
- xPSR → 程序状态
3. PSP = PSP + 32
4. 切换到 Thread mode
5. 切换到 非特权

任务切换完成:
- 栈指针: PSP (task1) → PSP (task2)
- 模式: Handler → Thread
- 特权: 特权 → 非特权

21

新任务开始运行

硬件跳转到 task2 入口

PC = task2_entry
R0 = task2 参数
SP = PSP = 0x2000_0C44

Thread mode
非特权 + PSP

task2 正式运行

阶段

关键操作

硬件行为

软件责任

1. 保存旧任务

从 PSP 读取栈指针 → 保存 R4-R11/FPU → 更新 TCB

精确捕获任务状态

2. 任务决策

调用 task_vSwitchContext()

选择下一个运行任务(仅切换 TCB 指针)

3. 恢复新任务

从 TCB 读取栈指针 → 恢复 R4-R11/FPU → 更新 PSP

准备新任务运行环境

4. 异常返回

bx r14 (r14=0xFFFFFFFD)

硬件自动:
- 从 PSP 弹出 8 寄存器
- 切换到 Thread mode + PSP

触发硬件状态机切换

7. Systick 异常处理 port_vSysTickHandler

// 66. SysTick中断处理函数定义
void port_vSysTickHandler( void ){
    portDISABLE_INTERRUPTS();
    traceISR_ENTER();
    {
        if( task_ulIncrementTick() != pdFALSE ){
            traceISR_EXIT_TO_SCHEDULER();
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }else{
            traceISR_EXIT();
        }
    }
    portENABLE_INTERRUPTS();
}

步骤

代码/操作

操作说明

寄存器/内存变化

硬件状态

设计目的

0

SysTick 触发前

定时器计数到 0

SYSTICK_CURRENT = 0
xTickCount = 1000

Thread mode
非特权 + PSP (任务运行中)

周期性时间事件发生(默认 1ms)

1

硬件自动压栈
(非软件指令)

硬件自动保存 8 个寄存器

栈内容 (PSP 栈):
0x2000_0824: xPSR
0x2000_0820: PC (被中断指令地址)
0x2000_081C: LR (EXC_RETURN=0xFFFFFFFD)
0x2000_0818: R12
0x2000_0814: R3
0x2000_0810: R2
0x2000_080C: R1
0x2000_0808: R0
PSP = 0x2000_0808

切换到 Handler mode
LR = 0xFFFFFFF9
CONTROL[0]=0 (特权)

为中断处理保存现场

2

portDISABLE_INTERRUPTS()

禁用所有可屏蔽中断

PRIMASK = 1

屏蔽所有优先级 ≤255 的中断

临界区保护
确保滴答计数原子性,防止被其他中断打断

3

traceISR_ENTER()

调试跟踪(可选)

无变化(若未启用跟踪)

无变化

记录中断进入时间(用于性能分析)

4

task_ulIncrementTick()

核心:更新系统时钟

1. xTickCount++1001
2. 检查延时任务队列:
- 若有任务延时到期 → 标记需切换
- 若时间片用完 → 标记需切换

无变化

调度决策点
- 增加系统节拍计数
- 检查任务状态变化

5

条件分支判断

根据返回值决定是否切换

if (返回值 != pdFALSE) → 需要切换

无变化

最小化中断延迟
仅当真正需要切换时才触发 PendSV

6

traceISR_EXIT_TO_SCHEDULER()

调试跟踪(需切换)

无变化

无变化

记录"将触发调度"事件

7

portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT

挂起 PendSV 异常

ICSR[28] = 1
(PendSV 挂起位)

硬件标记 PendSV 为 Pending 状态

关键设计
- 不直接切换任务(避免阻塞中断)
- 仅设置标志位(1 个周期)
- 等待安全时刻再切换

8

traceISR_EXIT()

调试跟踪(无需切换)

无变化

无变化

记录中断正常退出

9

portENABLE_INTERRUPTS()

恢复中断使能

PRIMASK = 0

允许所有可屏蔽中断响应

快速退出中断
最小化中断禁用时间(通常 < 20 周期)

10

硬件自动弹栈
(非软件指令)

硬件自动恢复 8 个寄存器

从 PSP 弹出:
R0-R3, R12, LR, PC, xPSR
PSP = 0x2000_0828

切换回 Thread mode
CONTROL[0]=1 (非特权)

恢复被中断的任务现场

11

任务继续运行

返回被中断的指令

PC = 原任务地址+2
R0-R3 = 中断前值

Thread mode
非特权 + PSP

任务无缝继续执行

12

PendSV 触发
(后续)

所有高优先级中断完成后

硬件自动触发 PendSV 异常

切换到 Handler mode

安全切换点
在中断安全期执行耗时的任务切换

阶段

关键操作

硬件行为

软件责任

1. 保存旧任务

• 从 PSP 读取栈指针 → 保存 R4-R11/FPU
• 更新 TCB 指针 (pxTopOfStack)


(软件手动操作)

精确捕获任务状态
- 仅保存硬件未自动保存的寄存器
- 条件性保存 FPU 寄存器
- 确保 TCB 指向正确栈位置

2. 任务决策

• 调用 task_vSwitchContext()
• 选择下一个运行任务


(软件手动操作)

仅切换 TCB 指针
- 不操作栈内存
- 不改变 CPU 模式
- 仅更新 pxCurrentTCB 指针

3. 恢复新任务

• 从 TCB 读取新栈指针 → 恢复 R4-R11/FPU
• 更新 PSP 指针


(软件手动操作)

准备新任务环境
- 确保寄存器值正确
- 条件性恢复 FPU 状态
- 为异常返回做准备

4. 异常返回

• 执行 bx r14 (r14=0xFFFFFFFD)
• 触发硬件异常返回

硬件自动
- 从 PSP 弹出 8 寄存器
(R0-R3, R12, LR, PC, xPSR)
- 切换到 Thread mode
- 切换到非特权模式
- 更新 PSP = PSP + 32

触发硬件状态机
- 通过 EXC_RETURN 协议
- 实现栈/模式/特权级切换
- 完成任务无缝切换

8. 定时器中断设置异常处理 port_vSetupTimerInterrupt

// 68. 定时器中断设置函数定义
__attribute__( ( weak ) ) void port_vSetupTimerInterrupt( void ){
    #if ( cfgUSE_TICKLESS_IDLE == 1 )
    {
        ulTimerCountsForOneTick = ( cfgSYSTICK_CLOCK_HZ / cfgTICK_RATE_HZ );
        xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
        ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( cfgCPU_CLOCK_HZ / cfgSYSTICK_CLOCK_HZ );
    }
    #endif
    portNVIC_SYSTICK_CTRL_REG = 0UL;
    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
    portNVIC_SYSTICK_LOAD_REG = ( cfgSYSTICK_CLOCK_HZ / cfgTICK_RATE_HZ ) - 1UL;
    portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}

步骤

代码/操作

操作说明

寄存器/内存变化

硬件状态

设计目的

0

函数调用前

port_lStartScheduler() 调用此函数

SP = MSP
CONTROL = 0x00 (特权+MSP)

Thread mode
特权

调度器初始化阶段,中断已使能

1

条件编译
#if (cfgUSE_TICKLESS_IDLE == 1)

检查是否启用低功耗模式

无变化

无变化

仅当配置低功耗时计算补偿参数

2

ulTimerCountsForOneTick = (cfgSYSTICK_CLOCK_HZ / cfgTICK_RATE_HZ)

计算单个滴答的计数值

示例 (168MHz/1kHz):
ulTimerCountsForOneTick = 168000

无变化

滴答精度基础
1ms 滴答需要 168000 个时钟周期

3

xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick

计算最大可抑制滴答数

0xFFFFFF (16777215) / 168000 ≈ 99

无变化

低功耗安全边界
SysTick 是 24 位计数器,最大计数值 16777215,
防止溢出导致时间计算错误

4

ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / (cfgCPU_CLOCK_HZ / cfgSYSTICK_CLOCK_HZ)

计算定时器停止补偿值

示例:
94 / (168000000/168000000) = 94

无变化

补偿时钟源切换延迟
从低功耗唤醒时,SysTick 可能错过若干计数,
此值用于校正时间误差

5

portNVIC_SYSTICK_CTRL_REG = 0UL

禁用 SysTick 定时器

SYSTICK_CTRL = 0x00000000
- COUNTFLAG=0 (清除标志)
- CLKSOURCE=0 (时钟源关闭)
- TICKINT=0 (中断禁用)
- ENABLE=0 (计数器禁用)

定时器停止计数

安全重配置
避免在配置过程中产生意外中断

6

portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL

清除当前计数值

SYSTICK_CURRENT = 0x00000000

计数器清零

确保精确起始点
避免残留计数值影响滴答精度

7

portNVIC_SYSTICK_LOAD_REG = (cfgSYSTICK_CLOCK_HZ / cfgTICK_RATE_HZ) - 1UL

设置重载值

示例 (168MHz/1kHz):
SYSTICK_LOAD = 167999

无变化

滴答周期定义
- 24 位减法计数器,从 LOAD 值递减到 0
- 重载值 = 周期计数值 - 1 (因包含 0)

8

portNVIC_SYSTICK_CTRL_REG = (portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT)

使能定时器

SYSTICK_CTRL = 0x00000007
- BIT[2]=1: 时钟源 = 处理器时钟 (168MHz)
- BIT[1]=1: 使能滴答中断 (TICKINT)
- BIT[0]=1: 使能计数器 (ENABLE)

定时器开始工作:
- 每个时钟周期递减 1
- 计数到 0 时:
1. 触发 SysTick 异常
2. 自动重载 LOAD 值
3. COUNTFLAG 标志置 1

系统时钟源启动
为任务调度提供精确时间基准 (默认 1ms)

阶段

关键操作

硬件行为

软件责任

设计目的

1. 低功耗参数计算
(条件: cfgUSE_TICKLESS_IDLE=1)

• 计算 ulTimerCountsForOneTick
• 计算 xMaximumPossibleSuppressedTicks
• 计算 ulStoppedTimerCompensation


(纯软件计算)

预计算低功耗参数
- 单滴答计数值 = 时钟频率/滴答频率
- 最大抑制滴答数 = 24位最大值/单滴答计数
- 停止补偿值 = 固定因子/时钟比

为低功耗模式提供精确的时间补偿参数,避免睡眠唤醒后时间漂移

2. 定时器禁用

SYSTICK_CTRL = 0

硬件行为
- 立即停止计数器
- 清除 COUNTFLAG 标志位

安全重配置前提
确保配置过程中无意外中断或计数

防止在重配置过程中产生意外中断,保证配置原子性

3. 计数器清零

SYSTICK_CURRENT = 0

硬件行为
- 写任意值清零当前计数值

精确起始点
消除残留计数值对首次滴答精度的影响

确保首次滴答从精确的 0 点开始,避免时间累积误差

4. 重载值配置

SYSTICK_LOAD = (时钟/频率)-1

无即时行为
(仅存储重载值)

定义滴答周期
- 24位减法计数器特性:周期 = LOAD+1 个时钟
- 示例:168MHz/1kHz → LOAD=167999 → 周期=168000周期=1ms

建立系统时间基准,决定任务调度粒度(默认1ms)

5. 定时器使能

SYSTICK_CTRL = 0x07
(CLKSOURCE|TICKINT|ENABLE)

硬件自动行为
1. 从 LOAD 值开始递减计数
2. 计数到 0 时:
- 触发 SysTick 异常
- 自动重载 LOAD 值
- COUNTFLAG 标志置 1

启动系统心跳
- 选择处理器时钟源 (AHB)
- 使能滴答中断 (TICKINT)
- 启动计数器 (ENABLE)

激活系统时钟源,为 xTaskIncrementTick() 提供周期性触发

9. port_vEnableVFP

#if ( cfgASSERT_DEFINED == 1 )
    // 69. 验证中断优先级函数定义
    void port_vValidateInterruptPriority( void ){
        uint32_t u32CurInter;
        uint8_t u8CurPrio;
        __asm volatile ( "mrs %0, ipsr" : "=r" ( u32CurInter )::"memory" );
        if( u32CurInter >= portFIRST_USER_INTERRUPT_NUMBER ){
            u8CurPrio = pu8InterPrioRegisters[ u32CurInter ];
            cfgASSERT( u8CurPrio >= u8MaxSysCallPriority );
        }
        cfgASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= u32MaxPRIGROUPValue );
    }
#endif

步骤

汇编指令

操作说明

寄存器/内存变化

硬件状态

设计目的

0

函数调用前

port_lStartScheduler() 调用此函数

SP = MSP
r14 = 返回地址
CONTROL = 0x00 (特权+MSP)

Thread mode
特权

调度器初始化阶段,准备启用 FPU

1

ldr.w r0, =0xE000ED88

加载 CPACR 寄存器地址

r0 = 0xE000ED88
(CPACR 寄存器物理地址)

无变化

定位协处理器控制寄存器
CPACR (Coprocessor Access Control Register) 控制 FPU 访问权限

2

ldr r1, [r0]

读取 CPACR 当前值

r1 = [0xE000ED88]
典型值: 0x00000000 (复位后默认禁用)

无变化

获取当前配置
复位后 CPACR 通常为 0,所有协处理器访问被禁止

3

orr r1, r1, #(0xf << 20)

设置 FPU 访问权限位

r1 = r1 | 0x00F00000
位域解析:
- [23:22] = 0b11 → CP11 (FPU) 全访问
- [21:20] = 0b11 → CP10 (FPU) 全访问

无变化

启用 FPU 完全访问
- 0b11 = 特权+非特权模式均可访问 FPU
- 为任务使用浮点指令做准备

4

str r1, [r0]

写回修改后的 CPACR 值

CPACR = 0x00F00000
(内存地址 0xE000ED88)

硬件行为:
- 立即生效 (无延迟)
- 允许执行 VFP 指令 (如 vadd.f32)

激活 FPU 硬件
从此可安全执行浮点指令,否则触发 UsageFault

5

bx r14

返回调用者

PC = r14
(返回到 port_lStartScheduler)

无变化

裸函数返回
因是 __attribute__((naked)),无栈帧操作,直接跳转返回

阶段

关键操作

硬件行为

软件责任

设计目的

1. 定位控制寄存器

• 加载 CPACR 地址 (0xE000ED88)
• 读取当前配置值


(纯内存访问)

精确寻址
- 使用 ldr.w 支持全地址范围 (Thumb-2 指令)
- 读取复位默认值 (通常为 0)

为安全修改做准备,避免覆盖其他协处理器配置

2. 权限位设置

• 按位或操作设置 [23:20] = 0b1111
• 保留其他位不变


(纯寄存器操作)

最小化修改
- 仅修改 FPU 相关位 (0xF << 20)
- 保持其他协处理器配置不变

遵循"最小权限原则",仅启用必需功能,避免意外启用其他协处理器

3. 激活 FPU 硬件

• 写回修改后的 CPACR 值

硬件立即生效
- 允许执行 VFP/NEON 指令
- FPU 寄存器 (S0-S31/D0-D15) 可访问

原子操作
单条 str 指令完成配置

消除浮点指令陷阱
复位后执行 vadd.f32 会触发 UsageFault,启用后可安全使用

4. 安全返回

bx r14 跳转到返回地址


(纯跳转操作)

裸函数规范
- 无栈帧操作 (因 naked 属性)
- 依赖调用约定保持 r14 正确

无缝集成到调度器启动流程,无额外开销

10. 流程总结图

11. port.c 实现

// 硬件平台相关函数实现
#include "RTOS.h"
#include "task.h"
// 00. 检查是否启用硬件浮点支持
#ifndef __VFP_FP__
    #error This port can only be used when the project options are configured to enable hardware floating point support.
#endif
// 01. 中断服务程序(ISR)函数指针类型定义
typedef void ( * portISR_t )( void );
// 02. SysTick控制寄存器地址定义
#define portNVIC_SYSTICK_CTRL_REG             ( *( ( volatile uint32_t * ) 0xe000e010 ) )
// 03. SysTick重载值寄存器地址定义
#define portNVIC_SYSTICK_LOAD_REG             ( *( ( volatile uint32_t * ) 0xe000e014 ) )
// 04. SysTick当前值寄存器地址定义
#define portNVIC_SYSTICK_CURRENT_VALUE_REG    ( *( ( volatile uint32_t * ) 0xe000e018 ) )
// 05. 系统优先级寄存器2地址定义
#define portNVIC_SHPR2_REG                    ( *( ( volatile uint32_t * ) 0xe000ed1c ) )
// 06. 系统优先级寄存器3地址定义
#define portNVIC_SHPR3_REG                    ( *( ( volatile uint32_t * ) 0xe000ed20 ) )
// 07. SysTick时钟位定义
#define portNVIC_SYSTICK_CLK_BIT              ( 1UL << 2UL )
// 08. SysTick中断位定义
#define portNVIC_SYSTICK_INT_BIT              ( 1UL << 1UL )
// 09. SysTick使能位定义
#define portNVIC_SYSTICK_ENABLE_BIT           ( 1UL << 0UL )
// 10. SysTick计数标志位定义
#define portNVIC_SYSTICK_COUNT_FLAG_BIT       ( 1UL << 16UL )
// 11. PendSV清除位定义
#define portNVIC_PENDSVCLEAR_BIT              ( 1UL << 27UL )
// 12. SysTick挂起设置位定义
#define portNVIC_PEND_SYSTICK_SET_BIT         ( 1UL << 26UL )
// 13. SysTick挂起清除位定义
#define portNVIC_PEND_SYSTICK_CLEAR_BIT       ( 1UL << 25UL )
// 14. CPU ID寄存器地址定义
#define portCPUID                             ( *( ( volatile uint32_t * ) 0xE000ed00 ) )
// 15. Cortex-M7 r0p1核心ID定义
#define portCORTEX_M7_r0p1_ID                 ( 0x410FC271UL )
// 16. Cortex-M7 r0p0核心ID定义
#define portCORTEX_M7_r0p0_ID                 ( 0x410FC270UL )
// 17. 最小中断优先级定义
#define portMIN_INTERRUPT_PRIORITY            ( 255UL )
// 18. PendSV中断优先级定义
#define portNVIC_PENDSV_PRI                   ( ( ( uint32_t ) portMIN_INTERRUPT_PRIORITY ) << 16UL )
// 19. SysTick中断优先级定义
#define portNVIC_SYSTICK_PRI                  ( ( ( uint32_t ) portMIN_INTERRUPT_PRIORITY ) << 24UL )
// 20. SCB向量表偏移寄存器地址定义
#define portSCB_VTOR_REG                      ( *( ( portISR_t ** ) 0xE000ED08 ) )
// 21. SVC中断向量索引定义
#define portVECTOR_INDEX_SVC                  ( 11 )
// 22. PendSV中断向量索引定义
#define portVECTOR_INDEX_PENDSV               ( 14 )
// 23. 第一个用户中断号定义
#define portFIRST_USER_INTERRUPT_NUMBER       ( 16 )
// 24. NVIC_IP寄存器偏移量定义
#define portNVIC_IP_REGISTERS_OFFSET_16       ( 0xE000E3F0 )
// 25. AIRCR寄存器地址定义
#define portAIRCR_REG                         ( *( ( volatile uint32_t * ) 0xE000ED0C ) )
// 26. 最大8位值定义
#define portMAX_8_BIT_VALUE                   ( ( uint8_t ) 0xff )
// 27. 字节最高位定义
#define portTOP_BIT_OF_BYTE                   ( ( uint8_t ) 0x80 )
// 28. 最大优先级组位数定义
#define portMAX_PRIGROUP_BITS                 ( ( uint8_t ) 7 )
// 29. 优先级组掩码定义
#define portPRIORITY_GROUP_MASK               ( 0x07UL << 8UL )
// 30. 优先级组移位定义
#define portPRIGROUP_SHIFT                    ( 8UL )
// 31. 向量激活掩码定义
#define portVECTACTIVE_MASK                   ( 0xFFUL )
// 32. VFP上下文控制寄存器地址定义
#define portFPCCR                             ( ( volatile uint32_t * ) 0xe000ef34 )
// 33. VFP ASPEN和LSPEN位定义
#define portASPEN_AND_LSPEN_BITS              ( 0x3UL << 30UL )
// 34. 初始XPSR值定义
#define portINITIAL_XPSR                      ( 0x01000000 )
// 35. 初始异常返回值定义
#define portINITIAL_EXC_RETURN                ( 0xfffffffd )
// 36. 最大24位数值定义
#define portMAX_24_BIT_NUMBER                 ( 0xffffffUL )
// 37. 起始地址掩码定义
#define portSTART_ADDRESS_MASK                ( ( StackType_t ) 0xfffffffeUL )
// 38. 错过计数因子定义
#define portMISSED_COUNTS_FACTOR              ( 94UL )
#ifndef cfgSYSTICK_CLOCK_HZ
    // 39. SysTick时钟频率定义
    #define cfgSYSTICK_CLOCK_HZ               ( cfgCPU_CLOCK_HZ )
    // 40. SysTick时钟位配置定义
    #define portNVIC_SYSTICK_CLK_BIT_CONFIG   ( portNVIC_SYSTICK_CLK_BIT )
#else
    #define portNVIC_SYSTICK_CLK_BIT_CONFIG   ( 0 )
#endif
#ifdef cfgTASK_RETURN_ADDRESS
    // 42. 任务返回地址定义(用户覆盖)
    #define portTASK_RETURN_ADDRESS    cfgTASK_RETURN_ADDRESS
#else
    // 42. 任务返回地址定义(默认)
    #define portTASK_RETURN_ADDRESS    port_vTaskExitError
#endif
// 43. 定时器中断设置函数声明
void port_vSetupTimerInterrupt( void );
// 44. PendSV异常处理函数声明
void port_vPendSVHandler( void ) __attribute__( ( naked ) );
// 45. SysTick中断处理函数声明
void port_vSysTickHandler( void );
// 46. SVC异常处理函数声明
void port_vSVCHandler( void ) __attribute__( ( naked ) );
// 47. 启动第一个任务函数声明
static void port_vStartFirstTask( void ) __attribute__( ( naked ) );
// 48. 启用VFP函数声明
static void port_vEnableVFP( void ) __attribute__( ( naked ) );
// 49. 任务退出错误处理函数声明
static void port_vTaskExitError( void );
// 50. 临界区嵌套计数变量定义
static UBaseType_t ulCriticalNesting = 0xaaaaaaaa;
#if ( cfgUSE_TICKLESS_IDLE == 1 )
    // 51. 一个 tick 周期的 SysTick 计数定义
    static uint32_t ulTimerCountsForOneTick = 0;
    // 52. 最大可抑制的 tick 周期数定义
    static uint32_t xMaximumPossibleSuppressedTicks = 0;
    // 53. 停止计时器补偿值定义
    static uint32_t ulStoppedTimerCompensation = 0;
#endif
#if ( cfgASSERT_DEFINED == 1 )
    // 54. 最大系统调用优先级变量定义
    static uint8_t u8MaxSysCallPriority = 0;
    // 55. 最大PRIGROUP值变量定义
    static uint32_t u32MaxPRIGROUPValue = 0;
    // 56. 中断优先级寄存器指针定义
    static const volatile uint8_t* const pu8InterPrioRegisters = (const volatile uint8_t* const)portNVIC_IP_REGISTERS_OFFSET_16;
#endif


// ------- 函数定义 begin ------- 
// 57. 初始化任务栈函数定义,头文件 64
// 在创建任务时,手动模拟一个中断返回时的栈帧结构,以便任务第一次被调度运行时,能像从中断返回一样正确地跳转到任务入口函数
StackType_t* port_pu32InitStack(StackType_t* pu32TopOfStack, TaskFunc_t pfCode, void* pvParameters ){
    pu32TopOfStack--;
    *pu32TopOfStack = portINITIAL_XPSR;                                    /* xPSR */
    pu32TopOfStack--;
    *pu32TopOfStack = ( ( StackType_t ) pfCode ) & portSTART_ADDRESS_MASK; /* PC */
    pu32TopOfStack--;
    *pu32TopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS;             /* LR */
    pu32TopOfStack -= 5;                            /* R12, R3, R2 and R1. */
    *pu32TopOfStack = ( StackType_t ) pvParameters; /* R0 */
    pu32TopOfStack--;
    *pu32TopOfStack = portINITIAL_EXC_RETURN;
    pu32TopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
    return pu32TopOfStack;
}
// 58. 任务退出错误处理函数定义,当任务函数意外返回(return)时,立即触发错误并死循环,防止系统进入未知状态
static void port_vTaskExitError( void ){
    volatile uint32_t u32Dummy = 0;
    cfgASSERT( ulCriticalNesting == ~0UL );
    portDISABLE_INTERRUPTS();
    while( u32Dummy == 0 ){}
}
// 59. SVC异常处理函数定义,把系统从“启动模式”切换到“任务运行模式”,让第一个任务开始干活
// msr psp, r0 把栈指针切换到任务专用的栈
void port_vSVCHandler( void ){
    __asm volatile (
        "   ldr r3, pxCurrentTCBConst2      \n" /* Restore the context. */
        "   ldr r1, [r3]                    \n" /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
        "   ldr r0, [r1]                    \n" /* The first item in pxCurrentTCB is the task top of stack. */
        "   ldmia r0!, {r4-r11, r14}        \n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
        "   msr psp, r0                     \n" /* Restore the task stack pointer. */
        "   isb                             \n"
        "   mov r0, #0                      \n"
        "   msr basepri, r0                 \n"
        "   bx r14                          \n"
        "                                   \n"
        "   .align 4                        \n"
        "pxCurrentTCBConst2: .word pxCurrentTCB             \n"
        );
}

// 60. 启动第一个任务函数定义,完成最后的系统初始化,然后按下 SVC 0 按钮,触发之前的 port_vSVCHandler,让第一个任务正式开始运行
static void port_vStartFirstTask( void ){
    __asm volatile (
        " ldr r0, =0xE000ED08   \n" /* Use the NVIC offset register to locate the stack. */
        " ldr r0, [r0]          \n"
        " ldr r0, [r0]          \n"
        " msr msp, r0           \n" /* Set the msp back to the start of the stack. */
        " mov r0, #0            \n" /* Clear the bit that indicates the FPU is in use, see comment above. */
        " msr control, r0       \n"
        " cpsie i               \n" /* Globally enable interrupts. */
        " cpsie f               \n"
        " dsb                   \n"
        " isb                   \n"
        " svc 0                 \n" /* System call to start first task. */
        " nop                   \n"
        " .ltorg                \n"
        );
}
// 61. 启动调度器函数定义
BaseType_t port_lStartScheduler( void ){
    // 1. CPU 版本检查
    cfgASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
    cfgASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
    // 2. 中断向量表验证(防配置错误)
    #if ( cfgCHECK_HANDLER_INSTALLATION == 1 )
    {
        const portISR_t * const ppfVectorTable = portSCB_VTOR_REG;
        cfgASSERT( ppfVectorTable[ portVECTOR_INDEX_SVC ] == port_vSVCHandler );
        cfgASSERT( ppfVectorTable[ portVECTOR_INDEX_PENDSV ] == port_vPendSVHandler );
    }
    #endif
    #if ( cfgASSERT_DEFINED == 1 )
    {
        volatile uint8_t u8OriginalPriority;
        volatile uint32_t u32ImplementedPrioBits = 0;
        volatile uint8_t * const pu8FirstUserPriorityRegister = ( volatile uint8_t * const ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
        volatile uint8_t u8MaxPriorityValue;
        u8OriginalPriority = *pu8FirstUserPriorityRegister;
        *pu8FirstUserPriorityRegister = portMAX_8_BIT_VALUE;
        u8MaxPriorityValue = *pu8FirstUserPriorityRegister;
        u8MaxSysCallPriority = cfgMAX_SYSCALL_INTERRUPT_PRIORITY & u8MaxPriorityValue;
        cfgASSERT( u8MaxSysCallPriority );
        cfgASSERT( ( cfgMAX_SYSCALL_INTERRUPT_PRIORITY & ( ~u8MaxPriorityValue ) ) == 0U );
        while( ( u8MaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE ){
            u32ImplementedPrioBits++;
            u8MaxPriorityValue <<= ( uint8_t ) 0x01;
        }

        if( u32ImplementedPrioBits == 8 ){
            cfgASSERT( ( cfgMAX_SYSCALL_INTERRUPT_PRIORITY & 0x1U ) == 0U );
            u32MaxPRIGROUPValue = 0;
        }else{
            u32MaxPRIGROUPValue = portMAX_PRIGROUP_BITS - u32ImplementedPrioBits;
        }
        u32MaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
        u32MaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
        *pu8FirstUserPriorityRegister = u8OriginalPriority;
    }
    #endif
    // 3. 优先级配置
    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
    portNVIC_SHPR2_REG = 0;
    port_vSetupTimerInterrupt();
    ulCriticalNesting = 0;
    port_vEnableVFP();
    *( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;
    port_vStartFirstTask();
    task_vSwitchContext();
    port_vTaskExitError();
    return 0;
}
// 62. 结束调度器函数定义
void port_vEndScheduler( void ){
    cfgASSERT( ulCriticalNesting == 1000UL );
}
// 63. 进入临界区函数定义
void port_vEnterCritical( void ){
    portDISABLE_INTERRUPTS();
    ulCriticalNesting++;
    if( ulCriticalNesting == 1 ){
        cfgASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}
// 64. 退出临界区函数定义
void port_vExitCritical( void ){
    cfgASSERT( ulCriticalNesting );
    ulCriticalNesting--;

    if( ulCriticalNesting == 0 ){
        portENABLE_INTERRUPTS();
    }
}
// 65. PendSV异常处理函数定义
void port_vPendSVHandler( void )
{
    __asm volatile
    (
        "   mrs r0, psp                         \n"
        "   isb                                 \n"
        "                                       \n"
        "   ldr r3, pxCurrentTCBConst           \n" /* Get the location of the current TCB. */
        "   ldr r2, [r3]                        \n"
        "                                       \n"
        "   tst r14, #0x10                      \n" /* Is the task using the FPU context?  If so, push high vfp registers. */
        "   it eq                               \n"
        "   vstmdbeq r0!, {s16-s31}             \n"
        "                                       \n"
        "   stmdb r0!, {r4-r11, r14}            \n" /* Save the core registers. */
        "   str r0, [r2]                        \n" /* Save the new top of stack into the first member of the TCB. */
        "                                       \n"
        "   stmdb sp!, {r0, r3}                 \n"
        "   mov r0, %0                          \n"
        "   msr basepri, r0                     \n"
        "   dsb                                 \n"
        "   isb                                 \n"
        "   bl task_vSwitchContext               \n"
        "   mov r0, #0                          \n"
        "   msr basepri, r0                     \n"
        "   ldmia sp!, {r0, r3}                 \n"
        "                                       \n"
        "   ldr r1, [r3]                        \n" /* The first item in pxCurrentTCB is the task top of stack. */
        "   ldr r0, [r1]                        \n"
        "                                       \n"
        "   ldmia r0!, {r4-r11, r14}            \n" /* Pop the core registers. */
        "                                       \n"
        "   tst r14, #0x10                      \n" /* Is the task using the FPU context?  If so, pop the high vfp registers too. */
        "   it eq                               \n"
        "   vldmiaeq r0!, {s16-s31}             \n"
        "                                       \n"
        "   msr psp, r0                         \n"
        "   isb                                 \n"
        "                                       \n"
        #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata workaround. */
            #if WORKAROUND_PMU_CM001 == 1
                "           push { r14 }                \n"
                "           pop { pc }                  \n"
            #endif
        #endif
        "                                       \n"
        "   bx r14                              \n"
        "                                       \n"
        "   .align 4                            \n"
        "pxCurrentTCBConst: .word pxCurrentTCB  \n"
        ::"i" ( cfgMAX_SYSCALL_INTERRUPT_PRIORITY )
    );
}
// 66. SysTick中断处理函数定义
void port_vSysTickHandler( void ){
    portDISABLE_INTERRUPTS();
    traceISR_ENTER();
    {
        if( task_ulIncrementTick() != pdFALSE ){
            traceISR_EXIT_TO_SCHEDULER();
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }else{
            traceISR_EXIT();
        }
    }
    portENABLE_INTERRUPTS();
}
#if ( cfgUSE_TICKLESS_IDLE == 1 )
    // 67. 抑制 tick 并睡眠函数定义
    __attribute__( ( weak ) ) void port_vSuppressTicksAndSleep( TickType_t u32ExpectedIdleTime ){
        uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements, ulSysTickDecrementsLeft;
        TickType_t xModifiableIdleTime;
        if( u32ExpectedIdleTime > xMaximumPossibleSuppressedTicks ){
            u32ExpectedIdleTime = xMaximumPossibleSuppressedTicks;
        }
        __asm volatile ( "cpsid i" ::: "memory" );
        __asm volatile ( "dsb" );
        __asm volatile ( "isb" );
        if( task_eConfirmSleepModeStatus() == eAbortSleep ){
            __asm volatile ( "cpsie i" ::: "memory" );
        }else{
            portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT );
            ulSysTickDecrementsLeft = portNVIC_SYSTICK_CURRENT_VALUE_REG;
            if( ulSysTickDecrementsLeft == 0 ){
                ulSysTickDecrementsLeft = ulTimerCountsForOneTick;
            }
            ulReloadValue = ulSysTickDecrementsLeft + ( ulTimerCountsForOneTick * ( u32ExpectedIdleTime - 1UL ) );
            if( ( portNVIC_INT_CTRL_REG & portNVIC_PEND_SYSTICK_SET_BIT ) != 0 ){
                portNVIC_INT_CTRL_REG = portNVIC_PEND_SYSTICK_CLEAR_BIT;
                ulReloadValue -= ulTimerCountsForOneTick;
            }
            if( ulReloadValue > ulStoppedTimerCompensation ){
                ulReloadValue -= ulStoppedTimerCompensation;
            }
            portNVIC_SYSTICK_LOAD_REG = ulReloadValue;
            portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
            portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
            xModifiableIdleTime = u32ExpectedIdleTime;
            cfgPRE_SLEEP_PROCESSING( xModifiableIdleTime );
            if( xModifiableIdleTime > 0 ){
                __asm volatile ( "dsb" ::: "memory" );
                __asm volatile ( "wfi" );
                __asm volatile ( "isb" );
            }
            cfgPOST_SLEEP_PROCESSING( u32ExpectedIdleTime );
            __asm volatile ( "cpsie i" ::: "memory" );
            __asm volatile ( "dsb" );
            __asm volatile ( "isb" );
            __asm volatile ( "cpsid i" ::: "memory" );
            __asm volatile ( "dsb" );
            __asm volatile ( "isb" );
            portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT );
            if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 ){
                uint32_t u32CalculatedLoadValue;
                u32CalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
                if( ( u32CalculatedLoadValue <= ulStoppedTimerCompensation ) || ( u32CalculatedLoadValue > ulTimerCountsForOneTick ) ){
                    u32CalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
                }
                portNVIC_SYSTICK_LOAD_REG = u32CalculatedLoadValue;
                ulCompleteTickPeriods = u32ExpectedIdleTime - 1UL;
            }else{
                ulSysTickDecrementsLeft = portNVIC_SYSTICK_CURRENT_VALUE_REG;
                #if ( portNVIC_SYSTICK_CLK_BIT_CONFIG != portNVIC_SYSTICK_CLK_BIT )
                {
                    if( ulSysTickDecrementsLeft == 0 ){
                        ulSysTickDecrementsLeft = ulReloadValue;
                    }
                }
                #endif
                ulCompletedSysTickDecrements = ( u32ExpectedIdleTime * ulTimerCountsForOneTick ) - ulSysTickDecrementsLeft;
                ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;
                portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
            }
            portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
            portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;
            #if ( portNVIC_SYSTICK_CLK_BIT_CONFIG == portNVIC_SYSTICK_CLK_BIT )
            {
                portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
            }
            #else
            {
                portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT;
                if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 ){
                    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0;
                }
                portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
                portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;
            }
            #endif
            task_vStepTick( ulCompleteTickPeriods );
            __asm volatile ( "cpsie i" ::: "memory" );
        }
    }
#endif
// 68. 定时器中断设置函数定义
__attribute__( ( weak ) ) void port_vSetupTimerInterrupt( void ){
    #if ( cfgUSE_TICKLESS_IDLE == 1 )
    {
        ulTimerCountsForOneTick = ( cfgSYSTICK_CLOCK_HZ / cfgTICK_RATE_HZ );
        xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
        ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( cfgCPU_CLOCK_HZ / cfgSYSTICK_CLOCK_HZ );
    }
    #endif
    portNVIC_SYSTICK_CTRL_REG = 0UL;
    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
    portNVIC_SYSTICK_LOAD_REG = ( cfgSYSTICK_CLOCK_HZ / cfgTICK_RATE_HZ ) - 1UL;
    portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}
static void port_vEnableVFP( void ){
    __asm volatile
    (
        "   ldr.w r0, =0xE000ED88       \n" /* The FPU enable bits are in the CPACR. */
        "   ldr r1, [r0]                \n"
        "                               \n"
        "   orr r1, r1, #( 0xf << 20 )  \n" /* Enable CP10 and CP11 coprocessors, then save back. */
        "   str r1, [r0]                \n"
        "   bx r14                      \n"
        "   .ltorg                      \n"
    );
}
#if ( cfgASSERT_DEFINED == 1 )
    // 69. 验证中断优先级函数定义
    void port_vValidateInterruptPriority( void ){
        uint32_t u32CurInter;
        uint8_t u8CurPrio;
        __asm volatile ( "mrs %0, ipsr" : "=r" ( u32CurInter )::"memory" );
        if( u32CurInter >= portFIRST_USER_INTERRUPT_NUMBER ){
            u8CurPrio = pu8InterPrioRegisters[ u32CurInter ];
            cfgASSERT( u8CurPrio >= u8MaxSysCallPriority );
        }
        cfgASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= u32MaxPRIGROUPValue );
    }
#endif
// ------- 函数定义 end ------- 

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐