FLASH
类别 | NOR | NAND |
---|---|---|
读 | 内存类接口和访问SRAM一样,随机访问任意地址数据 | 有严格的时许要求 |
写 | 慢,只能从1写到0,写入1需要擦除 | 快,只能从1写到0,写入1需要擦除 |
擦除 | 非常慢,5s | 快,3ms |
XIP | YES | NO |
可靠性 | 高 | 低,必须要校验 |
接口 | 地址与数据分开 | I/O 地址与数据共用 |
其它 | 寿命短、容量小、价格高 | 与NOR相反 |
场景 | MCU内的存储 | SD卡、U盘、固态 |
CPU、MPU、MCU、SOC、SOPC
类别 | 别名 | 定义 | 理解 |
---|---|---|---|
CPU(Central Processing Unit) | 中央处理器 | 由运算器、控制器和寄存器及实现它们之间联系的数据、控制及状态的总线构成 | 通用的CPU |
MPU(Micro Processor Unit) | 微处理器 | 增强版的CPU,往往是个人计算机和高端工作站的核心CPU。最常见的微处理器是Motorola的68K系列和Intel的X86系列 | 增强版的CPU |
MCU(Micro Control Unit) | 微控制器 | 将计算机的 CPU、RAM、ROM、定时计数器和多种I/O接口集成在一片芯片上,形成芯片级的芯片,比如51,avr这些芯片 | MCU MPU 最主要的区别就是能否直接运行代码。MCU有内部的RAM ROM,而MPU是增强版的CPU,需要添加外部RAM ROM才可以运行代码。 |
SOC(System on Chip) | 片上系统 | MCU只是芯片级的芯片,而SOC是系统级的芯片,它既像MCU(51,avr)那样有内置RAM,ROM,同时又像MPU(arm)那样强大的,不单单是放简单的代码,可以放系统级的代码,也就是说可以运行操作系统 | 将就认为是MCU集成化与MPU强处理力各优点二合一 |
SOPC(System On a Programmable Chip) | 可编程片上系统(FPGA就是其中一种) | 硬件配置,软件配置都可以修改,没有下载前,叫“白片”,什么芯片都不是,把硬件配置信息下载进去了,他就是相应的芯片了,可以让他变成51,也可以是avr,甚至arm,同时SOPC是在SOC基础上来的,所以他也是系统级的芯片,所以记得当把他变成arm时还得加外围ROM,RAM之类的,不然就是MPU了 | 可编程硬件 |
交叉编译
定义: 在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码,我们就称这种编译器支持交叉编译。简单地说,就是在一个平台上生成另一个平台上的可执行代码。
这里需要注意的是所谓平台,实际上包含两个概念:体系结构(Architecture)、操作系统(OperatingSystem)。同一个体系结构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。举例来说,我们常说的x86 Linux平台实际上是Intel x86体系结构和Linux for x86操作系统的统称;而x86 WinNT平台实际上是Intel x86体系结构和Windows NT for x86操作系统的简称。
有时是因为目的平台上不允许或不能够安装我们所需要的编译器,而我们又需要这个编译器的某些特征;有时是因为目的平台上的资源贫乏,无法运行我们所需要编译器;有时又是因为目的平台还没有建立,连操作系统都没有,根本谈不上运行什么编译器。
ARM Cortex-M内核启动流程
一、 启动流程:5 个核心步骤
- 内核上电复位: 芯片硬件层面完成初始化的步骤。
- PC 指向复位中断向量: 硬件强制程序计数器 (PC) 跳转到中断向量表的复位中断函数。
- 执行
Reset_Handler
和SystemInit
: 在复位中断函数中,完成时钟、中断向量表等初始化。 - 执行
__main
函数: 完成 C 语言运行环境的关键初始化,包括全局/静态变量的初始化和重定位,以及堆栈和库函数的设置。 - 跳转到
main
函数: 最终进入用户编写的应用程序入口点。
二、 核心步骤详细解析
步骤 | |
---|---|
1. 内核上电复位 | - 触发: 当单片机上电或外部复位信号(如按下复位按键)有效时,芯片内部的硬件复位逻辑被触发。 - 硬件操作: 对于 ARM Cortex-M 内核,硬件会自动执行以下两个关键操作: - 从地址 0x00000000 读取一个 32 位值,并将其加载到 主堆栈指针 (MSP) 寄存器。这定义了堆栈的初始顶部地址。- 从地址 0x00000004 读取一个 32 位值,并将其加载到 程序计数器 (PC) 寄存器。这个值就是 复位中断服务程序 (Reset_Handler) 的入口地址。- 本质: 实际上,单片机启动的本质就是由硬件自动发起了一次“复位中断”。 |
2.PC 指向复位中断向量 | - 中断向量表: 0x00000000 和 0x00000004 正是芯片内部 Flash 中 中断向量表 的头两个条目。中断向量表是处理器在响应中断或异常时查找相应处理函数地址的表格。- 强制执行: 硬件直接将 PC 指向 Reset_Handler 的入口地址,从而强制程序从此处开始执行。 |
3.执行 Reset_Handler 和 SystemInit | - Reset_Handler (汇编): 这是芯片启动代码中第一个被执行的函数,通常是用汇编语言编写(位于 startup_xxx.s 文件中)。它的主要任务是搭建 C 语言环境的前置条件。- 调用 SystemInit (C 函数): Reset_Handler 的核心操作是调用 SystemInit 函数(通常在 system_xxx.c 文件中),该函数由芯片厂商提供。- SystemInit 的主要工作:- 时钟初始化: 这是最关键的一步。配置内部/外部晶振、PLL(锁相环)倍频/分频,以确定系统时钟 (SYSCLK)、AHB/APB 总线时钟等。在此之前,单片机通常运行在低速内部 RC 振荡器上。 - FPU (浮点单元) 使能: 如果芯片包含浮点运算单元且程序需要使用,在此处使能。 - 中断向量表重定位 (可选): 如果需要将中断向量表从默认的 Flash 地址重定位到 RAM 中运行(例如,为了支持 Bootloader 和应用程序的切换),会在此处配置 VTOR 寄存器。- 外部存储器初始化 (可选): 如果系统连接了外部 SRAM 或 SDRAM,会进行必要的初始化。 |
4. 执行 __main 函数 | - C 库入口: 当 SystemInit 返回后,Reset_Handler 会跳转到 C 语言运行时库的入口函数 __main 。这个函数由编译器工具链(如 Keil MDK 的 ARMCC 或 GNU GCC)提供,负责完成 C 语言运行环境的最终设置。- __main 的主要工作 (分散加载 - Scatter-Loading):1. 复制 .data 段:- .data 段: 存放已初始化且初始值 不为零 的全局变量和静态变量。- 原理: 这些变量的初始值固化在 Flash 中(称为 加载域 Load Region / LMA)。但变量在程序运行时需要被修改,因此必须存在于 RAM 中(称为 执行域 Execution Region / VMA)。 - 操作: __main 会将 Flash 中 .data 段的内容,精确地拷贝到 RAM 中对应的位置。2. 清零 .bss 段:- .bss 段: 存放 未初始化 或 初始化为零 的全局变量和静态变量。- 原理: 为了节省 Flash 空间,编译器不会在 Flash 中为这些变量存储大量的零。它只在 .map 文件中记录需要预留的 RAM 空间。- 操作: __main 会将 RAM 中 .bss 段对应的内存区域全部填充为零,符合 C 语言标准规定。- 其他工作: __main 还会完成堆 (heap) 和栈 (stack) 的初始化,以及为 C 标准库函数(如 printf )准备必要的底层环境。 |
5. 跳转到 `main() | - 用户代码入口: 当所有 C 语言运行环境的准备工作都完成后,__main 函数的最后一步就是执行一条跳转指令,将程序计数器 (PC) 指向用户编写的 main 函数的入口。- 程序运行: 至此,单片机开始执行用户自己的应用程序逻辑。 |
三、 关键概念补充和辨析
1. 变量存储与内存区域
_init_data
(Flash):存放.data
段变量的初始值,在 Flash 中。.data
(RAM):存放已初始化且初始值 不为零 的全局变量和静态变量。启动时从 Flash 复制到此。.bss
(RAM):存放 未初始化 或 零初始化 的全局变量和静态变量。启动时被清零。.rodata
(Flash):存放常量(如const
关键字修饰的全局变量和字符串字面量)。它们不需要被修改,直接从 Flash 读取。.text
(Flash):存放程序的机器码(指令)。- 堆 (Heap):动态分配内存的区域(如
malloc
/free
)。运行时按需分配。 - 栈 (Stack):存放函数参数、局部变量和返回地址。在函数调用时创建,函数返回时销毁。栈空间大小可在 IDE 中设置。
2. “单片机”与“ARM”的代码执行模式
- 单片机 (MCU) / ARM Cortex-M 系列 (如 STM32):
- 就地执行 (Execute-In-Place, XIP) 系统。其内部 Flash 存储器访问速度经过高度优化(指令缓存、预取缓冲等),足以匹配 CPU 运行速度。
- 代码(
.text
段)通常直接在 Flash 中执行,不需要也不能完全拷贝到 RAM。这节省了宝贵的 RAM 资源并加快启动速度。 - 例外: 极少数对性能要求极致的函数(如某些中断服务程序)可通过编译器指令指定加载到 RAM 中运行。
- 应用处理器 (APU) / ARM Cortex-A 系列 (如树莓派、手机处理器):
- 通常运行大型操作系统(如 Linux)。启动时面对的是相对较慢的外部存储介质(如 eMMC、NAND Flash)。
- 为了提高执行效率,APU 的启动过程会先将操作系统内核等核心代码从慢速存储 完整拷贝到高速的 DDR RAM 中,再从 RAM 中执行。
- 结论: 您所说的“单片机不需要将代码从 ROM 搬移到 RAM”是对 Cortex-M MCU 的准确描述。“ARM 需要”则主要指 Cortex-A APU。
3. 内存地址分配
- 全局变量、静态变量和常量的地址在编译时就已被分配好,可以在
.map
文件中查看其具体地址和大小。 - 局部变量的地址则是在程序运行时,在栈上动态分配的。
四、 流程示意图
c
上电/复位
|
v
-----------------------------
| 硬件初始化 (PC=0x04, MSP=0x00) |
-----------------------------
|
v
-----------------------------
| Reset_Handler (汇编) |
| - 调用 SystemInit() |
-----------------------------
|
v
-----------------------------
| SystemInit() (C函数) |
| - 配置系统时钟 |
| - FPU 使能 (如果需要) |
| - 中断向量表重定位 (如果需要) |
| - 外部存储器初始化 (如果需要) |
-----------------------------
|
v
-----------------------------
| __main() (C库函数) |
| - 复制 .data 段 (Flash -> RAM)|
| - 清零 .bss 段 (RAM) |
| - 堆栈初始化 |
| - C库其他初始化 |
-----------------------------
|
v
-----------------------------
| main() (用户应用程序入口) |
-----------------------------
通过这份笔记,希望能帮助您更深入、更准确地理解单片机从加电那一刻到您编写的 main
函数开始执行的完整生命周期。