当前位置: 首页 > news >正文

RT-Thread 移植教程 基于GD32F4XX

1. 移植所涉及到的文件

1.1 Cortex-M 芯片内核移植代码:

  1. context_rvds.s
  2. cpuport.c

1.2 Kernel 文件包括:

  1. clock.c
  2. components.c
  3. device.c
  4. idle.c
  5. ipc.c
  6. irq.c
  7. kservice.c
  8. mem.c
  9. mempool.c
  10. object.c
  11. scheduler.c
  12. thread.c
  13. timer.c

1.3 配置文件:

  1. board.c
  2. rtconfig.h



2. 文件解读

Cortex-M 芯片内核移植代码:

2.1 context_rvds.s

RT-Thread RTOS中用于上下文切换和中断处理的汇编实

好的,我们来逐段解读 context_rvds.S 这个文件。这是 RT-Thread 实时操作系统(RTOS)针对 ARM Cortex-M 系列处理器(特别是 M3/M4/M7)编写的上下文切换核心汇编代码。它负责在任务(线程)之间进行切换,以及处理中断的开关和硬件错误。

文件核心功能:

  1. 中断控制: 提供全局中断的禁用和启用函数。
  2. 任务上下文切换:
    • 主动切换: 当前任务主动让出 CPU 时调用。
    • 中断中切换: 在中断服务程序(ISR)中请求任务切换。
    • 首次切换: 系统启动时,从无任务状态切换到第一个任务。
  3. PendSV 异常处理: 实际执行上下文保存和恢复的地方,利用 PendSV 的可延迟特性实现高效切换。
  4. 硬件错误处理: 增强的 HardFault 处理程序,用于收集错误现场信息。

代码解读:

  1. 常量定义 (EQU)

    SCB_VTOR        EQU     0xE000ED08   ; 向量表偏移寄存器地址
    NVIC_INT_CTRL   EQU     0xE000ED04   ; 中断控制状态寄存器 (ICSR),用于触发 PendSV
    NVIC_SYSPRI2    EQU     0xE000ED20   ; 系统优先级寄存器 2,用于设置 PendSV/SysTick 优先级
    NVIC_PENDSV_PRI EQU     0xFFFF0000   ; 设置 PendSV 和 SysTick 为最低优先级 (0xFF)
    NVIC_PENDSVSET  EQU     0x10000000   ; 设置 PendSV 挂起位 (PENDSVSET) 的值
    
    • 定义了操作 NVIC (Nested Vectored Interrupt Controller) 和 SCB (System Control Block) 相关寄存器所需的地址和位掩码。
  2. 区域和属性 (AREA, THUMB, REQUIRE8, PRESERVE8)

    AREA |.text|, CODE, READONLY, ALIGN=2
    THUMB
    REQUIRE8
    PRESERVE8
    
    • AREA: 定义一个名为 .text 的代码区域,属性为只读 (READONLY),按 4 字节对齐 (ALIGN=2)。
    • THUMB: 指示后续代码使用 Thumb/Thumb-2 指令集。
    • REQUIRE8 / PRESERVE8: 指示栈需要 8 字节对齐(符合 AAPCS 标准),并且该函数本身保持栈的 8 字节对齐。
  3. 导入符号 (IMPORT)

    IMPORT rt_thread_switch_interrupt_flag
    IMPORT rt_interrupt_from_thread
    IMPORT rt_interrupt_to_thread
    IMPORT rt_hw_hard_fault_exception
    
    • 声明这些符号(全局变量/函数)是在其他 C 语言文件中定义的,本汇编文件需要引用它们。
    • rt_thread_switch_interrupt_flag: 一个标志变量,通常为 0 或 1,表示是否需要执行上下文切换。
    • rt_interrupt_from_thread: 指向即将被切换出去的任务的控制块(通常包含其栈顶指针)。
    • rt_interrupt_to_thread: 指向即将被切换进来的任务的控制块(通常包含其栈顶指针)。
    • rt_hw_hard_fault_exception: C 语言编写的硬件错误处理函数。
  4. rt_hw_interrupt_disable (禁用中断)

    rt_hw_interrupt_disable    PROCEXPORT  rt_hw_interrupt_disable ; 导出此函数供外部调用MRS     r0, PRIMASK      ; 将 PRIMASK 寄存器的当前值读入 r0CPSID   I                ; 设置 PRIMASK=1, 禁用所有可屏蔽中断BX      LR               ; 返回,返回值 r0 是进入函数时的中断状态
    ENDP
    
    • 功能: 禁用所有可屏蔽中断,并返回之前的中断状态。
    • 流程:
      1. 读取 PRIMASK 寄存器的值到 r0(保存旧状态)。
      2. 执行 CPSID I 指令设置 PRIMASK=1,禁用中断。
      3. 返回,r0 包含了调用前的中断状态(0 表示中断开启,1 表示中断关闭)。
  5. rt_hw_interrupt_enable (启用中断)

    rt_hw_interrupt_enable    PROCEXPORT  rt_hw_interrupt_enable ; 导出此函数供外部调用MSR     PRIMASK, r0      ; 将 r0 的值写入 PRIMASK 寄存器BX      LR               ; 返回
    ENDP
    
    • 功能: 根据传入的参数 level(通常由 rt_hw_interrupt_disable 返回)恢复中断状态。
    • 流程:
      1. 将传入的参数 r0 写入 PRIMASK 寄存器。
      2. 返回。如果 r0 是 0,则启用中断;如果是 1,则保持中断禁用(虽然通常传入的是之前保存的状态)。
  6. rt_hw_context_switch / rt_hw_context_switch_interrupt (请求上下文切换)

    rt_hw_context_switch_interrupt ; 在中断中调用的切换入口点EXPORT rt_hw_context_switch_interrupt
    rt_hw_context_switch          ; 在任务中调用的切换入口点EXPORT rt_hw_context_switch; 检查切换标志是否已设置 (防止重复设置)LDR     r2, =rt_thread_switch_interrupt_flagLDR     r3, [r2]         ; 加载标志值到 r3CMP     r3, #1           ; 比较标志是否为 1BEQ     _reswitch        ; 如果已经是 1,跳过设置标志,直接设置目标线程; 设置切换标志为 1MOV     r3, #1STR     r3, [r2]         ; rt_thread_switch_interrupt_flag = 1; 保存"从哪个线程切换"的指针LDR     r2, =rt_interrupt_from_threadSTR     r0, [r2]         ; rt_interrupt_from_thread = r0 (from)
    _reswitch; 保存"切换到哪个线程"的指针LDR     r2, =rt_interrupt_to_threadSTR     r1, [r2]         ; rt_interrupt_to_thread = r1 (to); 触发 PendSV 异常 (实际切换将在 PendSV 处理程序中发生)LDR     r0, =NVIC_INT_CTRL ; 加载 ICSR 地址LDR     r1, =NVIC_PENDSVSET ; 加载 PENDSVSET 位掩码STR     r1, [r0]         ; 设置 PENDSVSET 位,挂起 PendSV 异常BX      LR               ; 返回
    ENDP
    
    • 功能: 请求进行一次任务切换。rt_hw_context_switch_interrupt 是专门在中断服务程序 (ISR) 中调用的入口点,但两者共享大部分代码。
    • 参数:
      • r0: 指向要切换出去的线程控制块(通常包含其当前栈顶指针)。
      • r1: 指向要切换进来的线程控制块(通常包含其保存的栈顶指针)。
    • 流程:
      1. 检查全局切换标志 rt_thread_switch_interrupt_flag
      2. 如果标志为 0(未设置):
        • 设置标志为 1。
        • r0from)保存到全局变量 rt_interrupt_from_thread
      3. r1to)保存到全局变量 rt_interrupt_to_thread
      4. 向 NVIC 的 ICSR 寄存器的 PENDSVSET 位写入 1,挂起 PendSV 异常。这是关键步骤! 实际的寄存器保存和恢复工作将由 PendSV 异常处理程序(PendSV_Handler)在稍后(通常是在退出所有中断后)执行。这种设计使得在中断内部请求切换不会立即发生,避免了在中断嵌套复杂时进行上下文切换的风险,提高了系统的实时性和可靠性。
      5. 函数返回。此时 CPU 继续执行当前代码(任务或中断服务程序),直到中断处理完毕,PendSV 异常被处理时才会真正切换任务。
  7. PendSV_Handler (实际执行上下文切换)

    PendSV_Handler   PROCEXPORT PendSV_Handler ; 导出 PendSV 异常处理程序; === 保护阶段 ===MRS     r2, PRIMASK   ; 保存当前中断状态 (PRIMASK) 到 r2CPSID   I             ; 在切换过程中禁用中断 (防止被更高优先级中断打断切换); 检查切换标志LDR     r0, =rt_thread_switch_interrupt_flagLDR     r1, [r0]      ; 加载标志值到 r1CBZ     r1, pendsv_exit ; 如果标志为 0 (未设置),直接退出 PendSV (异常被错误触发); 清除切换标志 (表示切换即将处理)MOV     r1, #0x00STR     r1, [r0]      ; rt_thread_switch_interrupt_flag = 0; === 保存旧任务上下文 ===LDR     r0, =rt_interrupt_from_thread ; 获取"from"线程指针地址LDR     r1, [r0]      ; 加载"from"线程指针本身到 r1CBZ     r1, switch_to_thread ; 如果指针是 NULL (第一次切换或特殊切换),跳过保存; 保存非自动保存的寄存器 (r4-r11) 和 FPU 寄存器 (如果需要)MRS     r1, psp       ; 获取当前任务的栈指针 (PSP) 到 r1; [FPU 处理 - 可选] 检查 EXC_RETURN[4] (FPCA 位)IF      {FPU} != "SoftVFP" ; 如果使用硬件 FPUTST     lr, #0x10      ; 测试 LR (EXC_RETURN) 的 bit4VSTMFDEQ  r1!, {d8 - d15} ; 如果 bit4=0 (FPU 上下文活跃),保存 FPU 寄存器 s16-s31 (d8-d15)ENDIF; 保存通用寄存器 r4-r11STMFD   r1!, {r4 - r11} ; 将 r4-r11 压入当前任务栈 (PSP 指向的栈),并更新 PSP; [FPU 处理 - 可选] 保存 FPU 活跃标志IF      {FPU} != "SoftVFP"MOV     r4, #0x00      ; 初始化标志为 0 (FPU 未使用)TST     lr, #0x10      ; 再次检查 EXC_RETURN[4]MOVEQ   r4, #0x01      ; 如果 bit4=0 (FPU 活跃),设置标志为 1STMFD   r1!, {r4}      ; 将 FPU 活跃标志压入栈ENDIF; 更新"from"线程的栈指针LDR     r0, [r0]       ; 再次加载"from"线程指针到 r0 (STR 需要目标地址)STR     r1, [r0]       ; 将更新后的 PSP (保存了上下文后的栈顶) 存入"from"线程控制块
    switch_to_thread; === 恢复新任务上下文 ===LDR     r1, =rt_interrupt_to_thread ; 获取"to"线程指针地址LDR     r1, [r1]      ; 加载"to"线程指针本身到 r1LDR     r1, [r1]      ; 加载"to"线程保存的栈顶指针到 r1 (指向它之前保存的上下文); [FPU 处理 - 可选] 弹出 FPU 活跃标志IF      {FPU} != "SoftVFP"LDMFD   r1!, {r3}     ; 从新任务栈弹出 FPU 活跃标志到 r3ENDIF; 恢复通用寄存器 r4-r11LDMFD   r1!, {r4 - r11} ; 从新任务栈弹出 r4-r11; [FPU 处理 - 可选] 恢复 FPU 寄存器 (如果需要) 并调整 EXC_RETURNIF      {FPU} != "SoftVFP"CMP     r3,  #0       ; 检查弹出的 FPU 标志 (r3)VLDMFDNE  r1!, {d8 - d15} ; 如果标志 !=0 (FPU 需要恢复),弹出 FPU 寄存器 s16-s31; 根据标志更新 EXC_RETURN (LR) 以指示 FPU 状态ORR     lr, lr, #0x10 ; 设置 EXC_RETURN[4]=1 (FPCA=1, 表示下次异常进入时 FPU 上下文不自动保存)CMP     r3,  #0       ; 再次检查标志BICNE   lr, lr, #0x10 ; 如果标志 !=0 (FPU 需要活跃),清除 EXC_RETURN[4]=0 (FPCA=0)ENDIF; 更新新任务的栈指针 (PSP)MSR     psp, r1       ; 将更新后的栈顶 (弹出上下文后的位置) 设置回 PSP
    pendsv_exit; === 清理和返回 ===MSR     PRIMASK, r2   ; 恢复进入 PendSV 时的中断状态 (PRIMASK)ORR     lr, lr, #0x04 ; 确保 EXC_RETURN 的 bit2=1 (返回后使用 PSP)BX      lr            ; 异常返回。CPU 从新任务的栈(PSP)恢复 r0-r3, r12, LR, PC, xPSR
    ENDP
    
    • 功能: 这是上下文切换的核心。当 PendSV 异常被触发后,CPU 会跳转到这里执行。它负责:
      • 保存当前运行任务(被切换出去的任务)的上下文(寄存器)到其栈中。
      • 从即将运行任务(被切换进来的任务)的栈中恢复其上下文(寄存器)。
      • 切换任务栈指针(PSP)。
    • 关键点:
      • 中断保护: 进入后立即保存 PRIMASK 并禁用中断 (CPSID I),确保切换过程原子化。
      • 标志检查: 检查 rt_thread_switch_interrupt_flag 确保切换是合法的请求。
      • 首次切换处理: 如果 rt_interrupt_from_thread 为 NULL(通常是系统启动后的第一次切换),则跳过保存旧任务上下文的步骤(因为没有旧任务)。
      • PSP 的使用: 任务模式使用 Process Stack Pointer (PSP)。MRS psp, r1MSR psp, r1 用于读写 PSP。
      • 寄存器保存/恢复: 保存和恢复的是在发生中断/异常时不会被 CPU 自动压栈的寄存器,即 r4-r11r0-r3, r12, LR, PC, xPSR 会在进入和退出异常时由 CPU 自动处理。
      • FPU 支持 (Lazy Stacking): 代码包含条件编译块 (IF {FPU} != "SoftVFP") 来处理硬件浮点单元 (FPU)。这是 Cortex-M4F/M7 等支持浮点的芯片的关键优化:
        • 保存: 检查 EXC_RETURN[4] (FPCA 位)。如果为 0,表示当前任务使用了 FPU 寄存器 (s16-s31),需要将它们 (d8-d15) 保存到任务栈。同时保存一个标志位记录是否保存了 FPU。
        • 恢复: 从新任务栈弹出标志位。如果标志指示该任务使用了 FPU,则恢复 s16-s31 (d8-d15)。根据标志位修改 EXC_RETURN[4] (FPCA),告诉 CPU 在下次异常进入时是否需要自动保存 FPU 寄存器 (s0-s15FPSCR)。ORR lr, #0x10 设置 FPCA=1 (不自动保存),BICNE lr, #0x10 清除 FPCA=0 (需要自动保存)。
      • 栈帧结构: 任务栈顶保存的内容顺序通常是 (从高地址到低地址):FPU Flag? (可选), r11, r10, r9, r8, r7, r6, r5, r4, FPU Regs d8-d15? (可选)psp 指向的位置在保存后是 r4 的保存位置之下。
      • 异常返回: ORR lr, #0x04 确保 EXC_RETURN 的 bit2 为 1,表示异常返回后使用 PSP 作为栈指针,并返回到任务模式(或可嵌套中断的 Handler 模式)。BX lr 触发异常返回机制,CPU 自动从新任务的栈 (PSP) 中弹出 r0-r3, r12, LR (任务代码的 LR,通常无意义), PC (任务恢复点), xPSR
  8. rt_hw_context_switch_to (首次任务切换)

    rt_hw_context_switch_to    PROCEXPORT rt_hw_context_switch_to; 设置目标线程 ("to")LDR     r1, =rt_interrupt_to_threadSTR     r0, [r1]      ; rt_interrupt_to_thread = r0 (to); [FPU 处理 - 可选] 初始化 FPU 控制 (Cortex-M4F/M7)IF      {FPU} != "SoftVFP"; 清除 CONTROL.FPCA (指示初始任务没有活跃的 FPU 上下文)MRS     r2, CONTROL   ; 读取 CONTROL 寄存器BIC     r2, #0x04     ; 清除 bit2 (FPCA)MSR     CONTROL, r2   ; 写回 CONTROLENDIF; 清除"from"线程指针 (表示没有旧任务)LDR     r1, =rt_interrupt_from_threadMOV     r0, #0x0STR     r0, [r1]      ; rt_interrupt_from_thread = NULL; 设置切换标志LDR     r1, =rt_thread_switch_interrupt_flagMOV     r0, #1STR     r0, [r1]      ; rt_thread_switch_interrupt_flag = 1; 设置 PendSV 和 SysTick 的异常优先级为最低LDR     r0, =NVIC_SYSPRI2    ; 系统优先级寄存器 2 地址LDR     r1, =NVIC_PENDSV_PRI ; 最低优先级掩码 (0xFFFF0000)LDR.W   r2, [r0, #0x00]     ; 读取当前值ORR     r1, r1, r2          ; 合并新优先级 (PendSV/SysTick 位置为 0xFF)STR     r1, [r0]            ; 写回寄存器; 触发 PendSV 异常LDR     r0, =NVIC_INT_CTRL  ; ICSR 地址LDR     r1, =NVIC_PENDSVSET ; PENDSVSET 掩码STR     r1, [r0]            ; 挂起 PendSV; === 初始化主栈指针 (MSP) ===LDR     r0, =SCB_VTOR       ; 向量表偏移寄存器地址LDR     r0, [r0]            ; 读取向量表起始地址LDR     r0, [r0]            ; 读取向量表第一个项 (主栈指针初始值 MSP)MSR     msp, r0             ; 设置 MSP 为向量表定义的初始值; === 启用中断 ===CPSIE   F                   ; 启用可配置故障 (如 HardFault, MemManage)CPSIE   I                   ; 启用所有可屏蔽中断; === 永不返回 ===
    ENDP
    
    • 功能: 在操作系统启动阶段,从没有任务运行的状态(或特权模式)切换到第一个用户任务。这是启动调度器后调用的关键函数。
    • 参数: r0: 指向要运行的第⼀个任务的控制块。
    • 流程:
      1. 设置 rt_interrupt_to_thread 为目标任务。
      2. (FPU) 初始化 CONTROL.FPCA 位为 0,表示初始任务没有活跃的 FPU 上下文。
      3. 设置 rt_interrupt_from_thread 为 NULL (表示没有前一个任务需要保存)。
      4. 设置切换标志 rt_thread_switch_interrupt_flag 为 1。
      5. 配置 PendSV 和 SysTick 异常的优先级为最低(0xFF),确保它们不会抢占其他中断。
      6. 手动触发 PendSV 异常 (STR NVIC_PENDSVSET, [NVIC_INT_CTRL])。
      7. 关键初始化: 从向量表 (SCB->VTOR) 加载初始主栈指针 (MSP) 的值并设置 MSP。这是确保系统拥有有效栈的关键步骤。
      8. 全局启用中断 (CPSIE I) 和可配置故障 (CPSIE F)。
      9. 函数在此结束,但永不返回。因为 PendSV 已经被触发,CPU 会立即(或在退出当前执行流后)进入 PendSV_Handler。在 PendSV_Handler 中,由于 rt_interrupt_from_thread 是 NULL,它会跳过保存旧上下文,直接加载第一个任务的上下文,并通过异常返回到第一个任务开始执行。
  9. rt_hw_interrupt_thread_switch (兼容性函数)

    rt_hw_interrupt_thread_switch PROCEXPORT rt_hw_interrupt_thread_switchBX      lr ; 直接返回,不做任何事
    ENDP
    
    • 这是一个空函数,仅用于兼容旧版本代码。现代 RT-Thread 使用 rt_hw_context_switch_interrupt
  10. HardFault_Handler (硬件错误处理)

    EXPORT HardFault_Handler
    HardFault_Handler    PROC; 确定错误发生时使用的栈指针 (MSP 还是 PSP?)TST     lr, #0x04       ; 检查 EXC_RETURN[2] (SPSEL)ITE     EQ              ; IF-THEN-ELSE 块MRSEQ   r0, msp         ; 如果 EXC_RETURN[2]=0 (使用 MSP), 将 MSP 读入 r0MRSNE   r0, psp         ; 如果 EXC_RETURN[2]=1 (使用 PSP), 将 PSP 读入 r0; 在错误栈帧上模拟保存 r4-r11 (为了统一上下文结构)STMFD   r0!, {r4 - r11} ; 将 r4-r11 压入错误栈帧 (r0 指向的栈); [FPU 处理 - 可选] 压入一个占位符 (dummy) 代替 FPU 标志IF      {FPU} != "SoftVFP"STMFD   r0!, {lr}       ; 压入一个占位符 (dummy) 代替 FPU 标志ENDIF; 保存 EXC_RETURN 值STMFD   r0!, {lr}       ; 将 LR (此时的 EXC_RETURN) 压入栈; 更新对应的栈指针 (MSP 或 PSP)TST     lr, #0x04       ; 再次检查 EXC_RETURN[2]ITE     EQMSREQ   msp, r0         ; 如果之前用 MSP, 更新 MSP 为修改后的值MSRNE   psp, r0         ; 如果之前用 PSP, 更新 PSP 为修改后的值; 调用 C 语言错误处理函数PUSH    {lr}            ; 保存 LR (可能包含 EXC_RETURN? 这里保存的是进入 HardFault 时的 LR)BL      rt_hw_hard_fault_exception ; 调用处理函数,传入的 r0 是构造的栈帧指针POP     {lr}            ; 恢复 LR; 异常返回 (虽然通常不会再返回)ORR     lr, lr, #0x04   ; 确保 EXC_RETURN[2]=1 (返回时使用 PSP, 虽然通常不返回)BX      lr              ; 尝试返回
    ENDP
    
    • 功能: 当发生硬件错误(如访问非法内存、执行非法指令等)时,CPU 会跳转到这里。它的主要目的是收集错误发生时的现场信息(寄存器、栈内容),并传递给 C 语言处理函数 rt_hw_hard_fault_exception 进行错误诊断、打印或系统复位。
    • 流程:
      1. 确定栈帧位置: 检查 EXC_RETURN[2] 确定错误发生时 CPU 使用的是主栈 (MSP) 还是进程栈 (PSP)。将对应的栈指针加载到 r0
      2. 构造统一上下文帧: CPU 在进入 HardFault 时已经自动压入了 r0-r3, r12, LR, PC, xPSR。为了匹配 RT-Thread 任务上下文的结构(包含 r4-r11 和可能的 FPU 标志),这段代码手动将 r4-r11 压入错误栈帧。如果需要 FPU 支持,它压入一个占位符 (lr 的当前值,无实际意义) 代替 FPU 标志位。最后,将进入 HardFault 时的 LR (即 EXC_RETURN 值) 也压入栈。这样,r0 指向的栈顶就构成了一个模拟的、完整的任务上下文帧
      3. 更新栈指针: 将修改后的栈指针值(指向新构造的上下文帧顶部)写回对应的 MSPPSP
      4. 调用 C 处理函数: 调用 rt_hw_hard_fault_exception,并将构造好的栈帧指针 (r0) 作为参数传入。这个 C 函数可以解析这个栈帧,获取所有寄存器的值、返回地址 (PC) 等关键信息用于调试。
      5. 尝试返回: 修改 EXC_RETURN 并尝试返回。但在实际发生 HardFault 后,系统通常处于严重错误状态,这个返回很可能不会成功,或者 C 函数内部已经决定复位系统。

总结:

context_rvds.S 是 RT-Thread 在 ARM Cortex-M 平台上实现多任务的核心。它利用 Cortex-M 的特性(特别是 PendSV 异常和双栈指针 MSP/PSP)高效、安全地完成任务切换:

  1. 开关中断: 提供基础的原子操作保护。
  2. 请求切换: rt_hw_context_switch(_interrupt) 设置标志并触发 PendSV,实现延迟切换
  3. 执行切换: PendSV_Handler 在特权模式下,安全地保存旧任务寄存器到其栈 (PSP),从新任务栈恢复寄存器,并切换 PSP。它巧妙地处理了硬件 FPU 的懒加载 (Lazy Stacking)。
  4. 首次切换: rt_hw_context_switch_to 初始化关键数据、设置 PendSV 优先级、加载初始 MSP、触发 PendSV 并最终跳转到第一个任务。
  5. 错误处理: HardFault_Handler 收集错误现场信息供调试。

这份代码是 RTOS 内核中与硬件架构紧密耦合的关键部分,其设计和实现直接影响系统的实时性、可靠性和效率。


2.2 cpuport.c

负责任务栈的初始化​(为第一次切换准备上下文)和高级异常处理​(错误诊断、报告)。

好的,我们来详细解读 cpuport.c 这个文件。这是 RT-Thread 实时操作系统(RTOS)针对 ARM Cortex-M 系列处理器的 CPU 移植层核心代码。它主要完成以下关键任务:

  1. 任务栈初始化: 为每个新创建的任务初始化其栈空间,使其准备好被调度执行。
  2. 异常处理:
    • 提供硬件错误(HardFault)的详细诊断信息。
    • 允许注册自定义的异常钩子函数。
  3. CPU 控制: 提供 CPU 复位功能。
  4. 位操作辅助: 提供快速查找最低有效位(FFS)的函数(用于调度器等)。

代码解读:

  1. FPU 检测 (USE_FPU)

    #if ... (复杂的编译器宏检测) ...
    #define USE_FPU   1
    #else
    #define USE_FPU   0
    #endif
    
    • 这段代码根据编译器和目标平台配置,自动检测是否使用了硬件浮点单元(FPU)。USE_FPU 的值会影响后续栈帧结构定义和初始化。
  2. 全局变量

    rt_uint32_t rt_interrupt_from_thread;
    rt_uint32_t rt_interrupt_to_thread;
    rt_uint32_t rt_thread_switch_interrupt_flag;
    static rt_err_t (*rt_exception_hook)(void *context) = RT_NULL;
    
    • rt_interrupt_from_thread: 指向即将被切换出去的任务控制块(通常包含其栈顶指针)。
    • rt_interrupt_to_thread: 指向即将被切换进来的任务控制块(通常包含其栈顶指针)。
    • rt_thread_switch_interrupt_flag: 上下文切换请求标志(0 表示无请求,1 表示有请求)。
    • rt_exception_hook: 指向用户自定义的异常处理钩子函数的指针。默认为 NULL
  3. 栈帧结构定义 (struct exception_stack_frame, struct stack_frame, struct exception_stack_frame_fpu, struct stack_frame_fpu)

    • exception_stack_frame: 定义了 CPU 在发生异常(如中断、SysTick、PendSV)时自动压入栈的寄存器。这是 Cortex-M 架构的标准行为。
      struct exception_stack_frame {rt_uint32_t r0;rt_uint32_t r1;rt_uint32_t r2;rt_uint32_t r3;rt_uint32_t r12;rt_uint32_t lr;   // 发生异常时的 LR (EXC_RETURN)rt_uint32_t pc;   // 发生异常时的指令地址rt_uint32_t psr;  // 程序状态寄存器
      };
      
    • stack_frame: 定义了在任务上下文切换时(主要在 PendSV_Handler 中),RT-Thread 手动保存和恢复的额外寄存器 (r4-r11),以及一个可选的 FPU 标志位 (flag)。这个结构包含一个 exception_stack_frame 成员,模拟了异常发生时 CPU 自动压栈的部分。任务第一次被调度时,栈顶就是这样一个完整的 stack_frame
      struct stack_frame {
      #if USE_FPUrt_uint32_t flag; // 指示该任务是否使用了 FPU 寄存器 (s16-s31)
      #endif /* USE_FPU *//* r4 ~ r11 register */rt_uint32_t r4;... // r5-r10rt_uint32_t r11;struct exception_stack_frame exception_stack_frame; // CPU自动压栈的部分
      };
      
    • exception_stack_frame_fpustack_frame_fpu: 这些是当 USE_FPU 为 1 时使用的扩展结构。它们在标准的异常栈帧和任务栈帧基础上,增加了保存 FPU 寄存器 (S0-S15, FPSCR) 和扩展 FPU 寄存器 (S16-S31) 的空间。exception_stack_frame_fpu 对应 CPU 自动压栈的 FPU 部分(如果 FPCA=0),stack_frame_fpu 对应手动保存的 FPU 部分 (S16-S31) 和标志位。
  4. 核心函数 rt_hw_stack_init (任务栈初始化)

    rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit) {struct stack_frame *stack_frame;rt_uint8_t *stk;unsigned long i;// 1. 计算栈顶位置 (向下生长栈)stk = stack_addr + sizeof(rt_uint32_t); // 通常跳过栈底的一个字 (可选)stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8); // 8字节对齐 (AAPCS)stk -= sizeof(struct stack_frame); // 为 stack_frame 结构预留空间stack_frame = (struct stack_frame *)stk; // stack_frame 指向预留空间的起始// 2. 用 0xdeadbeef 填充整个 stack_frame (调试用,检测栈溢出)for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i++) {((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;}// 3. 初始化模拟的“异常栈帧”部分 (任务第一次运行的初始上下文)stack_frame->exception_stack_frame.r0 = (unsigned long)parameter; // 任务入口参数stack_frame->exception_stack_frame.r1 = 0; // r1-r3, r12 初始化为 0stack_frame->exception_stack_frame.r2 = 0;stack_frame->exception_stack_frame.r3 = 0;stack_frame->exception_stack_frame.r12 = 0;stack_frame->exception_stack_frame.lr = (unsigned long)texit; // 任务退出函数地址stack_frame->exception_stack_frame.pc = (unsigned long)tentry; // 任务入口函数地址stack_frame->exception_stack_frame.psr = 0x01000000L; // Thumb 状态, 默认优先级// 4. 初始化 FPU 标志 (如果使用 FPU)
    #if USE_FPUstack_frame->flag = 0; // 初始化为 0 (未使用 FPU s16-s31)
    #endif /* USE_FPU */// 5. 返回初始化后的栈顶指针 (指向 stack_frame 结构开始处)return stk;
    }
    
    • 功能: 为即将创建的任务初始化其栈空间,使其在第一次被调度时能够正确开始执行。
    • 参数:
      • tentry: 任务入口函数指针。
      • parameter: 传递给任务入口函数的参数。
      • stack_addr: 分配给该任务的栈空间的起始地址(通常是栈空间的最高地址,因为栈是向下生长的)。
      • texit: 任务退出函数指针(当任务函数 tentry 返回时调用的函数,通常用于资源清理)。
    • 流程:
      1. 计算栈顶:stack_addr(栈底)开始,跳过可能的一个字(可选),然后进行 8 字节对齐。最后减去 struct stack_frame 的大小,为初始上下文预留空间。这个计算后的 stk 就是任务第一次运行时 PSP 应该指向的位置。
      2. 填充调试值:0xdeadbeef 填充整个 stack_frame 区域。这有助于在调试时检测栈溢出(未使用的栈空间保持 0xdeadbeef,如果被覆盖了就能发现)。
      3. 初始化 CPU 自动保存部分: 设置 exception_stack_frame 成员:
        • r0: 任务参数 (parameter)。
        • r1, r2, r3, r12: 初始化为 0。
        • lr: 设置为任务退出函数 texit 的地址。当任务函数 tentry 执行完毕返回时,会跳转到这里。
        • pc: 设置为任务入口函数 tentry 的地址。这是任务第一次被调度时开始执行的地方。
        • psr: 设置为 0x01000000。最低字节 (EPSR) 的 T 位为 1,表示 Thumb 状态;其他位通常表示默认优先级。
      4. 初始化 FPU 标志: 如果使用 FPU,将 flag 初始化为 0,表示该任务尚未使用过 s16-s31 FPU 寄存器(采用 Lazy Stacking 优化)。
      5. 返回栈顶指针: 返回指向初始化好的 stack_frame 结构开始的指针。这个指针会被保存在任务控制块 (TCB) 中,当任务第一次被调度时,PendSV_Handler 会把它加载到 PSP。
  5. 异常钩子安装 rt_hw_exception_install

    void rt_hw_exception_install(rt_err_t (*exception_handle)(void *context)) {rt_exception_hook = exception_handle;
    }
    
    • 功能: 允许用户注册一个自定义的异常处理函数 (exception_handle)。当发生异常(如 HardFault)时,RT-Thread 的内核处理程序会先调用这个钩子函数。
    • 参数: exception_handle: 用户自定义的异常处理函数指针。该函数接收一个 void *context 参数(指向异常发生时的寄存器上下文),并返回 rt_err_t(如果返回 RT_EOK,内核将不再进行默认的异常处理)。
  6. 硬件错误诊断 (rt_hw_hard_fault_exception)

    • 功能: 这是 RT-Thread 默认的硬件错误 (HardFault) 处理函数。当发生无法恢复的硬件错误时,CPU 会跳转到 HardFault_Handler(通常在汇编文件如 context_rvds.S 中定义),该处理程序收集错误现场信息后调用此 C 函数进行详细诊断和报告。
    • 参数: exception_info: 指向一个结构体,包含错误发生时的 exc_return 值和保存的寄存器上下文 (stack_frame)。
    • 流程:
      1. 调用钩子函数: 如果用户通过 rt_hw_exception_install 注册了钩子函数,则首先调用它。如果钩子函数返回 RT_EOK,则此函数直接返回(用户已处理错误)。
      2. 打印寄存器状态: 如果钩子函数不存在或未处理,则打印发生错误时所有关键寄存器的值 (psr, r0-r12, lr, pc)。
      3. 判断错误位置: 检查 exc_return 的 bit2 (SPSEL):
        • 如果为 1,表示错误发生在线程模式。打印当前线程名称,并调用 list_thread()(如果启用 Finsh)显示所有线程状态。
        • 如果为 0,表示错误发生在Handler模式(中断服务程序内部)。打印相应信息。
      4. 检查 FPU 状态: 检查 exc_return 的 bit4 (FPCA)。如果为 0,表示 FPU 上下文是活跃的(FPU 寄存器可能包含有效数据),打印提示信息。
      5. 详细错误跟踪: 如果启用了 Finsh (RT_USING_FINSH),调用 hard_fault_track() 函数。该函数读取 Cortex-M 的 System Control Block (SCB) 中的各种故障状态寄存器 (CFSR, HFSR, MMAR, BFAR),解析具体的错误原因(内存访问错误、总线错误、用法错误等)并打印出来。
      6. 死循环: 最后进入一个无限循环 (while (1);),系统挂起。用户需要复位设备。
  7. 错误跟踪辅助函数 (usage_fault_track, bus_fault_track, mem_manage_fault_track, hard_fault_track)

    • 这些函数(仅在 RT_USING_FINSH 时编译)负责解析 SCB 寄存器中的错误状态位,提供人类可读的错误原因描述。例如:
      • usage_fault_track(): 解析 UsageFault 状态寄存器 (UFSR),报告未定义指令、非法状态、非法加载/存储、除零等错误。
      • bus_fault_track(): 解析 BusFault 状态寄存器 (BFSR),报告指令预取错误、精确/非精确数据访问错误、栈错误等,并可能打印错误地址 (BFAR)。
      • mem_manage_fault_track(): 解析 MemManage 状态寄存器 (MFSR),报告指令/数据访问违规、栈错误等,并可能打印错误地址 (MMAR)。
      • hard_fault_track(): 解析 HardFault 状态寄存器 (HFSR),判断是向量表读取失败、由其他错误升级而来,还是调试事件触发,并调用上述具体错误跟踪函数。
  8. CPU 控制函数

    void rt_hw_cpu_shutdown(void) {rt_kprintf("shutdown...\n");RT_ASSERT(0); // 通常需要根据具体硬件实现
    }
    RT_WEAK void rt_hw_cpu_reset(void) {SCB_AIRCR = SCB_RESET_VALUE; // 写 AIRCR 寄存器触发复位
    }
    
    • rt_hw_cpu_shutdown(): 关机函数。这里只是一个占位符实现,打印信息并触发断言。实际使用时需要根据目标硬件平台实现具体的关机操作(如进入低功耗模式、切断电源等)。
    • rt_hw_cpu_reset(): 复位函数。使用 RT_WEAK 定义为弱符号,允许用户在其他文件中覆盖此实现。默认实现是向 SCB->AIRCR 寄存器写入特定的值 (0x05FA0004) 来触发 CPU 复位。
  9. FFS 函数 (__rt_ffs)

    #ifdef RT_USING_CPU_FFS
    ... // 根据不同编译器 (ARMCC, Clang, IAR, GCC) 实现 __rt_ffs
    #endif
    
    • 功能: Find First Set。给定一个整数 value,返回其二进制表示中最低有效位(Least Significant Bit, LSB)为 1 的位置索引(从 1 开始计数)。如果 value 为 0,则返回 0。
    • 实现: 针对不同的编译器(ARMCC/Keil, Clang, IAR, GCC)提供了最优化的汇编或内置函数实现。核心思想是利用 Cortex-M 的 RBIT(位反转)和 CLZ(前导零计数)指令高效计算。
    • 用途: 在调度器、位图操作等需要快速查找最低位 1 的场景中非常高效。

总结:

cpuport.c 文件是 RT-Thread 在 ARM Cortex-M 平台上实现多任务的核心 CPU 移植层代码,主要完成以下工作:

  1. 任务栈初始化 (rt_hw_stack_init): 为新任务构建初始运行上下文,模拟一次“异常返回”后的状态,使任务第一次被调度时能正确开始执行。
  2. 异常处理框架:
    • 提供默认的 HardFault 详细诊断功能,打印寄存器、分析错误原因。
    • 允许用户通过 rt_hw_exception_install 注册自定义异常处理钩子。
  3. CPU 控制: 提供复位 (rt_hw_cpu_reset) 和关机 (rt_hw_cpu_shutdown - 需定制) 的接口。
  4. 位操作辅助 (__rt_ffs): 提供高效的查找最低有效位 1 的函数,用于内核调度等。

这个文件与之前分析的汇编文件 context_rvds.S 紧密协作:

  • context_rvds.S 负责底层的上下文切换机制(寄存器保存/恢复、PendSV 处理)。
  • cpuport.c 负责任务栈的初始化(为第一次切换准备上下文)和高级异常处理(错误诊断、报告)。

它们共同构成了 RT-Thread 在 Cortex-M 处理器上运行多任务的基础。


http://www.dtcms.com/a/446327.html

相关文章:

  • wordpress网站换主机网站设计形式
  • 音视频学习(六十八):视频采集原理
  • 实习小结。
  • 怎么做百度提交入口网站企业注册查询官网
  • 归并排序算法的实现和原理
  • 语言散在风中已无远弗届:从语言的角度聊聊中国的未来
  • php做的网站收录百度排行榜
  • C++基于 brpc 的 Channel 管理封装
  • OpenWrt 的 Overlay 文件系统到底是怎么回事?
  • 优选算法-双指针:2.复写零解析
  • Leetcode 3703. Remove K-Balanced Substrings
  • 创意网站设计团队常州金坛网站建设
  • 浅聊一下网页显示过程
  • h 函数的运用场景=== 函数式封装组件 (弹窗调用)
  • 数据结构——排序算法全解析(入门到精通)
  • 建设装饰网站创客贴做网站吗
  • 爆炸特效-Unity-04-shader粒子系统
  • 公司做网站一般用什么域名网店设计师是干什么的
  • 【Redis】RedLock算法讲解
  • 网站专题页功能河北省住宅和城乡建设厅网站
  • stp root secondary 概念及题目
  • 马尔可夫链蒙特卡洛(MCMC):高维迷宫里的 “智能导航仪”—— 从商场找店到 AI 参数模拟
  • 无穿戴动捕大空间交互:如何靠摄像头实现全感官沉浸体验?
  • 求个没封的w站2022高端网站建设的要求
  • 网站经常修改好不好拼多多网店注册
  • 题解:洛谷P14127 [SCCPC 2021] K-skip Permutation
  • FreeBSD14.1 安装中文输入法fcitx
  • C++STL反向迭代器设计
  • 一文学会《C++》进阶系列之C++11
  • 腊肉网站的建设前景网页版微信可以发朋友圈吗