2. 嵌入式系统硬件基础:CPU架构、内存映射与复位向量表

嵌入式系统的启动流程,本质上是从硬件复位到软件初始化的过程。理解CPU架构、内存布局和外设地址空间,是掌握Bootloader与系统启动的第一步。本章聚焦ARM与RISC-V两大主流架构,剖析硬件层面的关键概念。

2.1 CPU架构:ARM与RISC-V

ARM和RISC-V是当前嵌入式领域最广泛使用的指令集架构(ISA)。ARM采用授权模式,提供Cortex-M/R/A系列;RISC-V则基于开源标准,模块化设计,适合定制化SoC。

特性 ARM (Cortex-M4为例) RISC-V (RV32IMAC)
指令集 Thumb/Thumb-2混合,固定/可变长度 基础整数(I)+ 乘除(M)+ 原子(A)+ 压缩(C)
异常模型 向量表基地址寄存器(VTOR),固定向量偏移 mtvec/stvec,支持直接/向量模式
启动模式 主堆栈指针(MSP)从0x00000000加载,PC从0x00000004加载 PC从复位向量(mtvec)指向地址开始执行
特权级 Handler模式 / Thread模式(特权/非特权) 机器模式(M-mode)、监管模式(S-mode)、用户模式(U-mode)
典型应用 STM32、NXP i.MX RT、TI Tiva SiFive FE310、GD32V、平头哥玄铁
架构选择提示: ARM Cortex-M系列在MCU领域生态成熟,工具链完善;RISC-V在灵活性和成本控制上优势明显,尤其适合AIoT和定制化加速器场景。启动流程上,两者核心逻辑一致:复位 → 取向量 → 初始化堆栈 → 跳转C入口。

2.2 内存映射:ROM、RAM与外设地址空间

嵌入式系统的地址空间通常被划分为多个区域:代码存储区(Flash/ROM)、数据运行区(SRAM)、外设寄存器区(MMIO)以及系统控制区。不同架构有默认的地址映射规则,但实际布局由芯片厂商的Linker Script决定。

典型32位MCU地址空间布局(以STM32F4为例):
  • 0x0000 0000 - 0x000F FFFF: 别名区或Flash映射(启动时可选映射)
  • 0x0800 0000 - 0x080F FFFF: 主Flash(代码存储)
  • 0x2000 0000 - 0x2001 FFFF: SRAM(栈、堆、全局变量)
  • 0x4000 0000 - 0x4002 3FFF: 外设总线(GPIO、UART、TIM、DMA等)
  • 0xE000 0000 - 0xE00F FFFF: 系统控制块(SCB、NVIC、MPU)
嵌入式系统典型内存映射结构(ARM Cortex-M) Flash / ROM 0x0800 0000 代码 + 只读数据 SRAM 0x2000 0000 栈、堆、全局变量 外设寄存器 0x4000 0000 GPIO / UART / TIM 系统控制 0xE000 0000 NVIC / MPU / SCB 地址增长 → 地址映射关键点 1. 启动时Flash可重映射到0x0000 0000 2. 向量表必须位于可执行区域起始 3. 外设寄存器通过MMIO访问 4. SRAM分为栈区、堆区和.data/.bss 5. MPU可配置区域访问权限 Linker Script示例片段: MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K } SECTIONS { .text : { *(.vectors) *(.text) } > FLASH .data : { *(.data) } > SRAM AT > FLASH }

2.3 复位向量表:系统启动的第一张地图

复位向量表是CPU上电后读取的第一组数据,它定义了系统从哪里开始执行、堆栈指针的初始值以及各异常/中断的服务入口。ARM Cortex-M与RISC-V在向量表设计上有显著差异。

2.3.1 ARM Cortex-M 向量表结构

Cortex-M强制要求向量表起始地址存放主堆栈指针(MSP),第二个字(偏移4字节)存放复位向量(Reset_Handler)。CPU复位后自动从0x00000000(或VTOR指向的地址)加载这两个值。

/* ARM Cortex-M 向量表示例(startup_stm32f4xx.s) */
.section .vectors
.word   _estack            /* 0x0000 0000: 栈顶地址 */
.word   Reset_Handler      /* 0x0000 0004: 复位入口 */
.word   NMI_Handler        /* 0x0000 0008: NMI */
.word   HardFault_Handler  /* 0x0000 000C: 硬错误 */
.word   MemManage_Handler  /* 0x0000 0010: 内存管理 */
.word   BusFault_Handler   /* 0x0000 0014: 总线错误 */
.word   UsageFault_Handler /* 0x0000 0018: 用法错误 */
...

2.3.2 RISC-V 向量表结构

RISC-V通过mtvec(机器模式)或stvec(监管模式)寄存器设置异常入口基地址。支持两种模式:直接模式(所有异常跳转到同一地址)和向量模式(按异常类型偏移)。复位后,CPU从mtvec指向的地址开始执行第一条指令。

/* RISC-V 复位与异常向量示例(汇编) */
.section .text.init
.globl _start
_start:
    /* 设置栈指针 */
    la      sp, _stack_top
    /* 设置 mtvec 为向量模式(基地址 + 模式位) */
    la      t0, trap_vector
    csrw    mtvec, t0
    /* 跳转到 C 语言 main */
    jal     main

trap_vector:
    /* 根据 mcause 跳转不同处理函数 */
    csrr    t0, mcause
    bltz    t0, handle_interrupt
    j       handle_exception
常见启动陷阱:
  • 向量表对齐: ARM要求向量表按256字节对齐(VTOR位域限制);RISC-V要求mtvec按4字节(直接模式)或64字节(向量模式)对齐。
  • 栈指针初始化: 如果_estack指向的地址不存在有效RAM,复位后第一条指令就会触发HardFault。
  • 中断优先级: ARM Cortex-M复位后默认所有中断优先级为0(最高),需在启动早期配置NVIC。

2.4 复位后CPU状态与第一条指令

理解CPU复位后的初始状态,是编写Bootloader的前提。下表对比了ARM与RISC-V复位后的关键寄存器状态。

寄存器/状态 ARM Cortex-M4 复位后 RISC-V RV32 复位后
PC 加载自 0x00000004(Reset_Handler) 加载自 mtvec(通常指向 _start)
SP 加载自 0x00000000(MSP) 未定义,需软件初始化
模式 Handler模式,特权级 机器模式(M-mode),最高特权级
中断 全局中断使能(PRIMASK=0) 全局中断禁用(mstatus.MIE=0)
MPU 禁用(需配置后才能启用) 通常禁用(pmpcfg 未配置)
时钟 内部RC振荡器,低速运行 依赖具体SoC设计,通常为慢速时钟
复位后第一条指令执行流程 复位事件 加载向量表 设置PC / SP 执行 ARM vs RISC-V 第一条指令差异 • ARM:自动加载MSP和PC,第一条指令在Reset_Handler中执行,通常为关闭看门狗、初始化时钟、搬运.data段。 • RISC-V:需手动初始化SP、设置mtvec、配置时钟,第一条指令在_start标签处,通常为栈指针初始化。 • 共同点:都需要在第一条指令中关闭全局中断、配置关键外设时钟,然后跳转到C语言初始化函数。

2.5 外设地址空间与MMIO

嵌入式CPU通过内存映射I/O(MMIO)访问外设寄存器。每个外设被分配一段连续的地址空间,CPU使用普通的加载/存储指令读写这些地址,从而控制外设行为。

MMIO访问注意事项:
  • volatile关键字: 必须使用volatile声明外设指针,防止编译器优化掉看似无用的读写操作。
  • 对齐访问: 多数外设寄存器要求32位对齐访问,未对齐访问可能导致总线错误。
  • 位带操作(ARM): Cortex-M3/M4支持位带别名区,可将单个bit映射到字地址,实现原子位操作。
  • 内存屏障: 在RISC-V中,外设访问前后可能需要插入fence指令保证顺序。
/* MMIO 访问示例:操作 GPIO 输出寄存器(ARM Cortex-M) */
/* 假设 GPIOA 基地址为 0x40020000,ODR 偏移 0x14 */
#define GPIOA_BASE      0x40020000UL
#define GPIOA_ODR       (*(volatile uint32_t *)(GPIOA_BASE + 0x14))

/* 设置 PA5 输出高电平 */
GPIOA_ODR |= (1UL << 5);

/* RISC-V 类似,但需注意内存顺序 */
#define GPIO_BASE       0x10012000UL
#define GPIO_OUTPUT_VAL (*(volatile uint32_t *)(GPIO_BASE + 0x0C))

/* 使用 fence 保证外设访问顺序 */
__asm__ volatile ("fence iorw, iorw");
GPIO_OUTPUT_VAL |= (1UL << 5);
__asm__ volatile ("fence iorw, iorw");

2.6 本章小结

嵌入式系统启动的硬件基础可以归纳为三个核心要素:

  • CPU架构决定了异常模型、特权级和向量表格式,ARM与RISC-V在复位流程上同大于异。
  • 内存映射定义了代码、数据和外设的物理地址范围,Linker Script是连接硬件与软件的桥梁。
  • 复位向量表是启动的第一张地图,错误的向量表配置将导致系统无法启动。