设备树、Bootloader、NAND Flash与STM32启动流程详解

发布于:2025-03-15 ⋅ 阅读:(13) ⋅ 点赞:(0)

设备树、Bootloader、NAND Flash与STM32启动流程详解

引言

在嵌入式系统开发中,设备树(Device Tree)、Bootloader(引导加载程序)和NAND Flash是三个常见的概念,它们在不同的嵌入式平台中扮演着不同的角色。对于STM32微控制器,理解这些概念以及启动流程对于开发稳定可靠的应用至关重要。本文将详细介绍这些概念,并重点解析STM32微控制器的启动过程。

设备树详解

基本概念与作用

设备树是一种描述硬件配置的数据结构,以树状结构表示系统中的硬件组件、它们的连接关系和属性。

主要作用

  • 将硬件配置与软件代码分离,提高代码可移植性
  • 动态描述硬件资源,避免硬编码
  • 简化驱动开发,统一硬件描述方式
  • 支持运行时硬件配置的修改

设备树通常以.dts(Device Tree Source)文件编写,经过编译后生成.dtb(Device Tree Blob)二进制文件,供操作系统或Bootloader读取。

在STM32中的应用

对于普通的STM32项目,通常不需要使用设备树。设备树主要应用于以下场景:

  • 运行嵌入式Linux的复杂系统(如STM32MP1系列)
  • 使用高级RTOS且需要硬件抽象的复杂应用
  • 需要支持多种硬件配置的通用平台

对于标准STM32应用,硬件配置通常直接在代码中定义,使用HAL库或寄存器直接操作硬件,无需设备树的抽象层。

Bootloader详解

基本概念与功能

Bootloader是上电后执行的第一段程序,负责初始化硬件并加载主应用程序。

主要功能

  • 基础硬件初始化(时钟、内存等)
  • 检测启动模式和条件
  • 加载应用程序到RAM或直接从Flash执行
  • 提供程序更新机制
  • 执行安全检查和验证

类型与特点

Bootloader可分为多个层次:

  1. 一级Bootloader:通常固化在芯片ROM中,不可修改

    • STM32的系统内存Bootloader就属于这类
    • 功能简单但高度可靠
  2. 二级Bootloader:存储在Flash中,可更新

    • 提供更丰富的功能,如多应用选择、加密验证等
    • 可根据应用需求定制

在STM32中的应用

STM32微控制器内置了出厂Bootloader,存储在系统内存中,不可修改。此外,开发者还可以实现自定义Bootloader。

STM32内置Bootloader特点

  • 支持通过UART、USB、CAN等接口更新固件
  • 通过BOOT引脚配置激活(通常BOOT0=1)
  • 支持读写Flash、SRAM和选项字节
  • 支持跳转执行应用程序

自定义Bootloader应用场景

  • 实现固件加密和安全启动
  • 支持远程固件更新(OTA)
  • 实现多固件管理和回滚机制
  • 添加自定义通信协议

NAND Flash详解

基本概念与特点

NAND Flash是一种非易失性存储器,以块(Block)为单位擦除,以页(Page)为单位读写。

主要特点

  • 高存储密度,适合大容量存储
  • 擦写次数有限(通常10,000-100,000次)
  • 需要坏块管理和错误检测与修正(ECC)
  • 不支持随机字节访问,需要按页读写

与NOR Flash的对比

特性 NAND Flash NOR Flash
存储密度
成本
读取速度 较慢
写入速度
擦除速度
擦除单位 大块(64KB-2MB) 小块(4KB-128KB)
随机访问 页级访问 字节级访问
XIP支持 不支持 支持
主要应用 大容量数据存储 代码存储和执行

在STM32中的应用场景

大多数STM32项目不需要使用NAND Flash,通常使用:

  • 内部Flash:STM32自带NOR Flash,从几KB到2MB不等
  • 外部SPI NOR Flash:如W25Qxx系列,容量从512KB到16MB
  • SD卡/eMMC:需要文件系统时的常用选择

需要NAND Flash的场景

  • 需要大容量存储(数GB)的应用
  • 需要高速数据记录的场景
  • 成本敏感但需要大容量的产品

使用NAND Flash需要额外的控制器或软件支持,处理ECC和坏块管理,增加了开发复杂度。

STM32启动流程

STM32微控制器上电后的启动流程相对简单但灵活,下面详细介绍从上电到应用程序运行的完整过程。

1. 上电复位阶段

当STM32上电或收到复位信号时:

  • 复位电路激活,将芯片重置到初始状态
  • 所有寄存器恢复默认值
  • 内部RC振荡器(HSI)自动启动作为系统时钟
  • GPIO引脚设置为默认状态(通常为浮空输入)

2. 启动模式选择

STM32根据BOOT引脚的状态选择启动模式:

  • 主闪存启动:BOOT0=0

    • 从内部Flash启动用户程序(最常用模式)
  • 系统存储器启动:BOOT0=1, BOOT1=0

    • 启动内置的出厂Bootloader
    • 用于通过串口、USB等接口下载程序
  • SRAM启动:BOOT0=1, BOOT1=1

    • 从内部SRAM执行代码
    • 主要用于调试或特殊应用

3. 向量表加载

无论哪种启动模式,STM32都会:

  • 加载向量表(从0x00000000或重映射地址)
  • 获取初始堆栈指针值(向量表第一个条目)
  • 获取复位处理程序地址(向量表第二个条目)

向量表结构:

地址 0x00000000: 初始堆栈指针值
地址 0x00000004: 复位处理程序地址
地址 0x00000008: NMI处理程序地址
地址 0x0000000C: 硬故障处理程序地址
...其他中断向量...

4. 系统初始化

STM32执行复位处理程序,通常包括:

  • 调用SystemInit()函数配置时钟系统
  • 初始化C/C++运行环境
  • 跳转到main()函数开始执行用户代码

启动文件中的典型代码(以ARM汇编为例):

; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  SystemInit
                IMPORT  __main
                LDR     R0, =SystemInit   ; 加载SystemInit函数地址
                BLX     R0                ; 调用SystemInit函数
                LDR     R0, =__main       ; 加载__main函数地址
                BX      R0                ; 跳转到__main函数
                ENDP

5. 应用程序执行

用户程序通常执行以下初始化步骤:

  • 配置系统时钟(如使用外部晶振、PLL倍频等)
  • 初始化所需外设(GPIO、UART、SPI等)
  • 初始化中断系统(优先级、使能等)
  • 初始化应用程序所需的其他组件
  • 进入主循环,开始执行应用逻辑

典型的用户程序结构:

int main(void)
{
    /* 禁用中断 */
    __disable_irq();
    
    /* 配置系统时钟 */
    SystemClock_Config();
    
    /* 初始化外设 */
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_SPI1_Init();
    
    /* 初始化外部组件 */
    W25QXX_Init();  // 例如外部Flash
    
    /* 启用中断 */
    __enable_irq();
    
    /* 主循环 */
    while (1)
    {
        /* 应用代码 */
    }
}

STM32启动流程图

┌─────────────┐
│  上电/复位  │
└──────┬──────┘
       ▼
┌─────────────┐
│ 检查BOOT引脚│
└──────┬──────┘
       ▼
       ┌───────────────┐
       │  启动模式选择 │
       └───┬───────┬───┘
           │       │
┌──────────▼──┐   │   ┌──────────▼──┐
│ 主闪存启动  │   │   │系统存储器启动│
│ (BOOT0=0)   │   │   │ (BOOT0=1)    │
└──────┬──────┘   │   └──────┬───────┘
       │          │          │
       │          │          ▼
┌──────▼──────┐   │   ┌─────────────┐
│ 加载用户    │   │   │ 执行出厂    │
│ 向量表      │   │   │ Bootloader  │
└──────┬──────┘   │   └──────┬──────┘
       │          │          │
       │          │    ┌─────▼─────┐
       │          │    │ 等待并处理│
       │          │    │ 下载命令  │
       │          │    └─────┬─────┘
       │          │          │
       │          └─────┐    │
       │                ▼    ▼
┌──────▼──────────────────────┐
│ 初始化堆栈指针              │
└──────┬──────────────────────┘
       ▼
┌─────────────────────────────┐
│ 跳转到复位处理程序          │
└──────┬──────────────────────┘
       ▼
┌─────────────────────────────┐
│ 系统初始化                  │
│ (时钟、外设、中断等)        │
└──────┬──────────────────────┘
       ▼
┌─────────────────────────────┐
│ 执行main函数                │
└─────────────────────────────┘

不同复杂度STM32系统的启动需求

根据系统复杂度,STM32的启动需求可分为三类:

1. 简单系统(如传感器节点、控制器)

典型配置

  • 使用内部Flash存储程序
  • 直接从主Flash启动
  • 无需自定义Bootloader
  • 无需设备树
  • 无需NAND Flash

启动流程

  1. 上电
  2. 从内部Flash加载程序
  3. 初始化系统
  4. 执行应用程序

2. 中等复杂度系统(如工业控制器、智能设备)

典型配置

  • 使用内部Flash + 外部SPI NOR Flash
  • 自定义简单Bootloader
  • 无需设备树
  • 可能使用外部SDRAM
  • 通常不使用NAND Flash

启动流程

  1. 上电
  2. 执行内部Flash中的Bootloader
  3. 初始化外部存储器
  4. 加载并验证应用程序
  5. 跳转到应用程序

3. 高复杂度系统(如STM32MP1运行Linux)

典型配置

  • 多级Bootloader(如ROM、SPL、U-Boot)
  • 设备树描述硬件
  • 可能使用NAND Flash或eMMC
  • 外部DDR内存
  • 完整操作系统

启动流程

  1. ROM Bootloader初始化基本硬件
  2. 加载第一阶段Bootloader
  3. 初始化内存控制器和关键外设
  4. 加载主Bootloader
  5. 加载设备树和内核
  6. 启动操作系统
  7. 运行应用程序

实际开发建议

开发板上电准备

  1. 检查跳线配置

    • 确认BOOT0/BOOT1引脚设置正确
    • 通常开发时设置BOOT0=0(从主Flash启动)
    • 下载程序时可能需要设置BOOT0=1(进入Bootloader)
  2. 电源连接

    • 确保电源稳定且电压正确
    • 使用USB供电时,确保USB电缆质量良好
  3. 调试器连接

    • 如使用ST-Link/J-Link,确保正确连接
    • 检查驱动程序是否已安装

系统初始化顺序示例

int main(void)
{
    /* 1. 禁用中断,防止初始化过程被打断 */
    __disable_irq();
    
    /* 2. 配置时钟系统 - 首要任务 */
    SystemClock_Config();
    
    /* 3. 初始化核心外设 */
    MX_GPIO_Init();
    MX_USART1_UART_Init();  // 调试串口优先初始化
    
    /* 4. 初始化其他外设 */
    MX_SPI1_Init();
    MX_I2C1_Init();
    
    /* 5. 初始化外部组件 */
    W25QXX_Init();      // 外部Flash
    LCD_Init();         // 显示屏
    
    /* 6. 初始化应用层组件 */
    FileSystem_Init();  // 文件系统
    
    /* 7. 启用中断,所有初始化完成后再开启 */
    __enable_irq();
    
    /* 8. 应用启动提示 */
    printf("System initialized successfully\r\n");
    
    /* 9. 主循环 */
    while (1)
    {
        /* 应用代码 */
    }
}

错误处理示例

void Error_Handler(void)
{
    /* 关闭所有中断 */
    __disable_irq();
    
    /* 指示错误(如闪烁LED) */
    while (1)
    {
        HAL_GPIO_TogglePin(ERROR_LED_GPIO_Port, ERROR_LED_Pin);
        HAL_Delay(200);
    }
}

总结

回到原问题:“如果要启动一个STM32板子,需要用到这些吗,上电之后的流程和过程是什么呢?”

是否需要这些技术?

  1. 设备树:对于普通STM32项目,不需要。设备树主要用于运行Linux的复杂系统。

  2. Bootloader:STM32已内置出厂Bootloader,所以基本功能已经具备,可以直接使用。但对于需要特殊功能(如OTA升级、安全启动)的项目,可能需要开发自定义Bootloader。

  3. NAND Flash:大多数STM32项目不需要。通常使用内部Flash或外部SPI NOR Flash即可满足存储需求。

STM32上电后的流程

  1. 上电/复位:芯片重置到初始状态
  2. 启动模式选择:根据BOOT0/BOOT1引脚状态选择启动模式
  3. 向量表加载:加载堆栈指针和复位处理程序地址
  4. 系统初始化:配置时钟、外设和中断系统
  5. 应用程序执行:跳转到main函数,执行用户代码

对于大多数STM32应用,这个启动流程是自动完成的,开发者只需要关注应用程序的编写。只有在需要特殊功能(如安全启动、固件更新)时,才需要深入了解和定制启动流程。

理解这些概念和启动流程,将有助于开发更可靠、更高效的STM32应用程序,特别是在调试启动问题或实现高级功能时。