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、平头哥玄铁 |
2.2 内存映射:ROM、RAM与外设地址空间
嵌入式系统的地址空间通常被划分为多个区域:代码存储区(Flash/ROM)、数据运行区(SRAM)、外设寄存器区(MMIO)以及系统控制区。不同架构有默认的地址映射规则,但实际布局由芯片厂商的Linker Script决定。
- 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)
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设计,通常为慢速时钟 |
2.5 外设地址空间与MMIO
嵌入式CPU通过内存映射I/O(MMIO)访问外设寄存器。每个外设被分配一段连续的地址空间,CPU使用普通的加载/存储指令读写这些地址,从而控制外设行为。
- 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是连接硬件与软件的桥梁。
- 复位向量表是启动的第一张地图,错误的向量表配置将导致系统无法启动。