单片机内核架构外设解析:图文详解+代码下载与运行原理总结(万字长文)

目录

1.内核与单片机的关系

2.单片机的内核理解

组成:

(1)指令集架构(ISA)

什么是“架构”(Architecture)?

(2)算术逻辑单元(ALU)

(3)寄存器组(Registers)

(4)控制单元(Control Unit)

(5)总线接口(Bus Interface)

(6)中断控制器(Interrupt Controller)

(7)时钟管理单元(Clock Management Unit)

(8)调试接口(Debug Interface)

内核的工作流程

3.单片机存储器

(1) 非易失性存储器(Non-Volatile Memory)

(2) 易失性存储器(Volatile Memory)

存储器在单片机中的位置

(1) 内部存储器

(2) 外部存储器

存储器的具体作用

(1) Flash

(2) RAM

(3) ROM

(4) EEPROM

单片机执行代码的具体流程

注意:

ARM芯片 ≠ 内核,但ARM芯片包含内核

程序为什么在Flash中运行,而不是RAM?

stm32的Jlink下载原理:

寄存器(R0-R12、PC、LR等)在哪里?

可执行文件的存储位置

为什么操作系统需要虚拟地址内存?MMU又是怎么将虚拟地址映射到物理地址的?

内存布局的核心作用

什么是MPU?

总线系统的工作机制

volatile

1.内核与单片机的关系

单片机 = 内核 + 存储器 + 外设:

内核负责执行程序指令。

存储器(如 Flash、RAM)用于存储程序和数据。

外设(如 GPIO、UART、SPI)用于与外部设备交互

2.单片机的内核理解

内核就是CPU(Central Processing Unit,中央处理器)

组成:

(1)指令集架构(ISA)

定义:指令集架构是内核的底层设计,定义了 CPU 支持的指令集和操作方式。

什么是“架构”(Architecture)?

架构的定义:

在计算机领域,“架构”指的是 计算机系统的设计蓝图,它定义了硬件和软件之间的交互规则。

类比:就像一座房子的设计图,规定了房间布局、门窗位置、水电管道等。计算机架构决定了CPU如何执行指令、如何访问内存、如何处理数据等核心功能。

指令集架构(ISA) 是架构的核心部分,可以理解为 CPU的“语言”:

ISA本身是规范,既需要硬件实现,也需要软件遵循。

硬件角度:ISA定义了CPU能理解和执行的所有指令(如加法、跳转、数据加载等)。

软件角度:程序员用这些指令编写程序,编译器将这些程序翻译成CPU能执行的机器码。

指令集架构(ISA)是硬件和软件的桥梁,它本身既不是纯软件也不是纯硬件,而是两者的接口规范:

硬件实现:

CPU的物理电路(硬件)必须按照ISA的规定实现每条指令的功能。

例如:加法指令在硬件中通过加法器电路实现。

软件依赖:

软件(如操作系统、应用程序)必须按照ISA的规则编写,才能被CPU正确执行。

例如:用ARM指令集编写的程序无法直接在x86 CPU上运行。

指令集架构的核心内容

ISA具体定义了以下内容:

支持的指令:

每条指令的功能(如加法、逻辑运算、跳转等)。

指令的格式(如操作码、寄存器编号、内存地址等)。

例如:ADD R1, R2, R3 表示将寄存器R2和R3的值相加,结果存入R1。

寄存器设计:

CPU内部寄存器的数量、用途和访问方式。

例如:通用寄存器、程序计数器(PC)、状态寄存器(如标志位)。

内存访问规则:

如何读写内存(如字节对齐、寻址模式)。

例如:LOAD R1, [0x1000] 表示将内存地址0x1000处的数据加载到寄存器R1。

异常和中断处理:

如何处理错误(如除零错误)和外部事件(如键盘输入)。

常见指令集架构类型

(1) CISC(复杂指令集,Complex Instruction Set Computer)

特点:指令功能复杂,一条指令可完成多步操作(如内存访问+运算)。

指令长度可变,硬件设计复杂。

代表架构:

x86(Intel/AMD CPU,用于个人电脑、服务器)。

8051(经典单片机架构)。

(2) RISC(精简指令集,Reduced Instruction Set Computer)

特点:指令功能简单,每条指令仅完成一个基本操作。

指令长度固定,硬件设计简洁,适合高性能和低功耗场景。

代表架构:

ARM(手机、嵌入式设备)。

RISC-V(开源架构,近年快速发展)。

MIPS(早期嵌入式设备)。

(2)算术逻辑单元(ALU)

功能:执行算术运算(如加、减、乘、除)和逻辑运算(如与、或、非)。

作用:是内核的核心计算单元,负责处理数据。

(3)寄存器组(Registers)

功能:寄存器是 CPU 内部的高速存储单元,用于临时存放数据、地址和指令。

常见寄存器:

通用寄存器:用于存放数据和地址。

程序计数器(PC):存放下一条要执行的指令地址。

堆栈指针(SP):指向堆栈的顶部,用于函数调用和中断处理。

状态寄存器(PSR):存放 CPU 的状态信息(如溢出、进位、中断标志等)。

(4)控制单元(Control Unit)

控制单元(Control Unit, CU) 是负责协调和指挥所有操作的“指挥官”,它确保指令按正确顺序执行,并协调 CPU 内部各部件(如 ALU、寄存器、总线)的协作。以下是控制单元的详细解析:

指令解码(Instruction Decode)

输入:从内存或缓存中取出的机器指令(二进制代码,如 0xBEEF)。

操作:

解析指令的操作码(Opcode),确定需要执行的操作类型(如加法、跳转、存储等)。

识别指令所需的操作数(如寄存器编号、内存地址、立即数)。

示例:

指令 ADD R1, R2, R3 被解码为“将 R2 和 R3 的值相加,结果存入 R1”。

时序控制(Timing Control)

生成时钟信号:

控制单元根据系统时钟(Clock)生成时序信号,协调 CPU 各部件的工作节奏。

例如:在时钟上升沿触发寄存器写入,下降沿触发 ALU 运算。

流水线管理:

在流水线 CPU 中,控制单元划分指令执行的阶段(取指、解码、执行、访存、写回),并确保各阶段不冲突。

1.取指阶段(Fetch)

控制单元操作:

发送 PC 寄存器的值到内存地址总线。

触发 MemRead 信号,从内存中读取指令。

将指令存入指令寄存器(IR),并更新 PC 指向下一条指令。

2. 解码阶段(Decode)

控制单元操作:

解析 IR 中的操作码和操作数。

生成对应的控制信号(如 ALU_OP=ADD)。

3. 执行阶段(Execute)

控制单元操作:

将操作数从寄存器文件传输到 ALU。

触发 ALU 执行运算,并将结果暂存。

4. 访存阶段(Memory Access)

控制单元操作:

若指令需要访问内存(如 LOAD 或 STORE),发送地址和数据到总线。

触发 MemRead 或 MemWrite 信号。

5. 写回阶段(Write Back)

控制单元操作:

将运算结果或内存数据写入目标寄存器。

更新 PC 或处理分支跳转。

数据路径控制(Data Path Control)

控制数据流向:

通过多路选择器(MUX)和总线开关,决定数据在寄存器、ALU、内存之间的传输路径。

示例:执行 LOAD R1, [0x1000] 时,控制单元:

发送内存地址 0x1000 到地址总线。

触发内存读取操作。

将读取的数据通过数据总线传输到寄存器 R1。

生成控制信号(Control Signals)

向各部件发送控制信号:

例如:告诉 ALU 执行加法(ALU_OP=ADD),或通知寄存器文件写入数据(RegWrite=1)。

异常和中断处理

响应异常事件:

当发生除零错误、非法指令或外部中断时,控制单元暂停当前指令流,保存现场,并跳转到中断处理程序。

示例:

按下键盘时,控制单元暂停当前任务,优先处理键盘输入。

(5)总线接口(Bus Interface)

功能:连接内核与存储器(如 Flash、RAM)和外设(如 GPIO、UART、SPI 等)。

常见总线:

AHB(高级高性能总线):用于高速数据传输。

APB(高级外设总线):用于低速外设连接。

(6)中断控制器(Interrupt Controller)

NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器) 是 ARM Cortex-M 系列内核中专门用于管理中断的硬件模块。

NVIC 的组成部分

NVIC 主要由以下寄存器组和功能模块构成:

寄存器组:

1.中断优先级寄存器(Priority Registers)

功能:为每个中断源分配优先级,支持中断嵌套和抢占。

寄存器:NVIC_IPR0 ~ NVIC_IPR7(具体数量取决于芯片的中断数量)。

优先级字段:

Cortex-M 的优先级字段通常为 8 位,但实际可配置位数由芯片厂商决定(如 STM32 使用 4 位)。

优先级分为 抢占优先级(Preemption Priority) 和 子优先级(Subpriority),通过优先级分组寄存器(AIRCR.PRIGROUP)配置。

// 设置 UART 中断的优先级(抢占优先级 1,子优先级 2) NVIC_SetPriority(UART_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 1, 2));

2.中断使能寄存器(Interrupt Set-Enable Registers, ISER)

功能:全局控制中断的使能或禁用。

寄存器:NVIC_ISER0 ~ NVIC_ISER7(每个寄存器控制 32 个中断源)。

操作:

置位对应位使能中断(如 NVIC_ISER0 |= (1

禁用中断使用 NVIC_ICER(Interrupt Clear-Enable Registers)。

3. 中断挂起寄存器(Interrupt Set-Pending Registers, ISPR)

功能:手动触发或记录未处理的中断请求(挂起状态)。

寄存器:NVIC_ISPR0 ~ NVIC_ISPR7。

应用场景:

软件触发中断(如 NVIC_SetPendingIRQ(EXTI0_IRQn))。

中断触发后,硬件自动置位挂起标志,直到中断被处理。

4. 中断激活寄存器(Interrupt Active Bit Registers, IABR)

功能:记录当前正在处理的中断(激活状态)。

寄存器:NVIC_IABR0 ~ NVIC_IABR7。

只读属性:CPU 自动管理,无法直接写入。

5. 中断控制器类型寄存器(ICTR)

功能:描述 NVIC 的硬件配置(如支持的中断数量)。

关键字段:

ICTR.INTLINESNUM:指示支持的中断线数量(如 240 个中断)。

6. 向量表偏移寄存器(Vector Table Offset Register, VTOR)

功能:定义向量表在内存中的起始地址。

寄存器:SCB->VTOR(属于系统控制块,但由 NVIC 使用)。

应用:

默认向量表位于 Flash 起始地址(0x0000_0000 或 0x0800_0000)。

可将向量表重定位到 RAM 或其它地址(如 Bootloader 场景)。

7. 软件触发中断寄存器(Software Trigger Interrupt Register, STIR)

功能:通过软件触发中断请求。

寄存器:NVIC_STIR。

应用场景:

测试中断处理逻辑。

任务间通信(如 RTOS 中触发任务切换)。

功能模块:

1.优先级仲裁器(Priority Arbiter)

功能:比较当前中断与正在处理的中断的优先级,决定是否抢占。

规则:

高抢占优先级中断可打断低优先级中断(嵌套)。

同优先级中断按子优先级顺序执行,无嵌套。

2. 中断向量表处理单元

功能:

根据中断号(如 EXTI0_IRQn)跳转到向量表中对应的中断服务函数(ISR)。

支持向量表的动态重定位(通过 VTOR 寄存器)。

3. 自动现场保存与恢复逻辑

功能:

中断触发时,自动将关键寄存器(PC、PSR、R0-R3 等)压入堆栈。

中断返回时,自动从堆栈恢复寄存器状态。

优势:减少中断响应延迟,无需软件手动保存上下文。

NVIC 的工作流程

中断触发:外设(如 EXTI、定时器)发送中断请求到 NVIC。

优先级检查:

NVIC 比较当前中断与正在处理中断的优先级。

若允许抢占,挂起当前中断,响应更高优先级中断。

中断响应:

从向量表获取 ISR 入口地址。

自动保存现场(寄存器压栈)。

执行 ISR:用户编写的中断处理代码。

中断返回:

恢复现场(寄存器出栈)。

继续执行原任务或处理挂起的中断。

EXTI 与 NVIC 的关联

中断号映射:每个 EXTI 线路对应一个 NVIC 中断号。

例如:EXTI0_IRQn 对应引脚 PA0/PB0/… 的中断。

不同 GPIO 引脚的中断可能共享同一 EXTI 线路(如 EXTI0 对应所有 GPIO 端口的 Pin 0)。

常见中断控制器类型

(1) NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)

架构:ARM Cortex-M 系列专用。

特点:

支持嵌套中断(高优先级中断可打断低优先级)。

向量表可重定位(通过 SCB->VTOR 寄存器)。

低延迟中断响应(通常为 12 个时钟周期)。

(2) APIC(Advanced Programmable Interrupt Controller)

架构:x86 架构(如 Intel/AMD CPU)。

特点:

支持多核 CPU 的中断分发。

通过 IOAPIC 和 LAPIC 协作管理中断。

(3) INTC(Interrupt Controller)

架构:常见于早期单片机(如 8051)。

特点:

简单优先级管理,无嵌套中断。

中断向量表固定(如 8051 的中断向量位于 0x0003、0x000B 等)。

NVIC 是中断系统的“大脑”:负责全局中断管理,决定响应顺序和优先级。

EXTI 是 GPIO 中断的“传感器”:检测引脚电平变化,并向 NVIC 发送中断请求。

协作关系:EXTI 负责检测中断事件,NVIC 负责裁决和响应。

举个例子:

EXTI 像门铃,检测是否有人按门铃。

NVIC 像管家,听到门铃后决定是否立即开门(根据优先级),并通知主人(执行 ISR)。

(7)时钟管理单元(Clock Management Unit)

时钟管理单元(Clock Management Unit, CMU) 是负责生成、分配和管理时钟信号的核心模块。时钟信号是数字电路的“心跳”,所有 CPU 操作(指令执行、数据传输、外设同步)均依赖于稳定的时钟信号。时钟源(Clock Source)是 CMU 的输入信号来源,而 CMU 则通过分频、倍频、门控等技术,将时钟源转换为不同频率和用途的时钟信号,供 CPU 内核和外设使用。

时钟管理单元(CMU)的核心功能

时钟生成:将原始时钟源转换为系统所需的多种频率信号。

时钟分配:将不同频率的时钟信号分配给 CPU 内核、总线、外设等模块。

动态调频:根据负载调整 CPU 主频,平衡性能与功耗(如 ARM 的 DVFS 技术)。

低功耗管理:关闭未使用模块的时钟(时钟门控),降低功耗。

时钟监控:检测时钟源故障并切换备用源(如主晶振失效时切换内部 RC 振荡器)。

时钟管理单元的组成

1.时钟源(Clock Sources)

外部时钟源:

主晶振(Main Oscillator, XTAL):高精度、高稳定性,用于系统主时钟(如 8 MHz、16 MHz)。

副晶振(Low-Speed Oscillator, LSE):低频(如 32.768 kHz),用于实时时钟(RTC)。

内部时钟源:

内部 RC 振荡器(HSI/LSI):低成本、低精度,用于备用或低功耗模式(如 HSI 8 MHz)。

锁相环(PLL):通过倍频生成高频时钟(如将 8 MHz 倍频到 72 MHz)。

2. 锁相环(Phase-Locked Loop, PLL)

功能:通过反馈控制实现倍频,生成高频、低抖动的时钟信号。

N,M,P 为可配置的分频/倍频系数。

应用:为 CPU 内核提供高频时钟(如 STM32 将 8 MHz HSE 倍频到 72 MHz)。

3.分频器(Divider)与倍频器(Multiplier)

分频器:将高频时钟分频为低频信号(如将 72 MHz 分频为 36 MHz 供外设使用)。

倍频器:与 PLL 配合,提升时钟频率。

4. 时钟门控(Clock Gating)

功能:通过逻辑门控制时钟信号的开启/关闭,关闭未使用模块的时钟以节省功耗。

示例:关闭空闲状态 UART 模块的时钟。

5. 多路选择器(MUX)

功能:选择不同的时钟源作为系统主时钟。

应用:在运行时动态切换时钟源(如从 HSI 切换到 PLL)。

6. 时钟监控与故障恢复

时钟安全系统(CSS):检测主时钟失效,自动切换到备用时钟(如 HSI)。

时钟失效中断:触发中断通知 CPU 处理故障。

典型时钟分配流程(以 STM32 为例):

选择主时钟源(HSE 或 HSI)。

通过 PLL 倍频生成高频系统时钟(如 72 MHz)。

分频得到总线时钟(HCLK = SYSCLK / 1)、APB 外设时钟(PCLK = HCLK / 2)。

门控控制各外设时钟的开启/关闭。

(8)调试接口(Debug Interface)

功能:提供调试功能,支持单步执行、断点设置等。

常见接口:

JTAG:标准的调试接口。

SWD(串行调试接口):ARM 架构常用的调试接口。

内核的工作流程

取指令:

从程序存储器(Flash)中读取指令。

解码指令:

将指令解码为具体的操作。

执行指令:

由 ALU 执行算术或逻辑运算,或由控制单元执行控制操作。

写回结果:

将结果写回寄存器或存储器。

更新程序计数器:

更新 PC,指向下一条指令。

单片机存储器

单片机中的存储器分为 非易失性存储器(断电不丢失数据)和 易失性存储器(断电丢失数据)。

(1) 非易失性存储器(Non-Volatile Memory)

用于存储 程序代码、配置参数 等需要长期保存的数据。

Flash – 可重复擦写(约10万次寿命)

容量较大(几KB到几MB)

速度较慢 | 存储程序代码、固件、配置数据(如STM32的内部Flash)。

ROM(只读存储器) – 出厂时固化,不可修改

成本低,可靠性高 | 存储厂商预置的Bootloader或固定程序(如8051的掩膜ROM)。

EEPROM – 可逐字节擦写(寿命约100万次)

容量小(几KB) | 存储需要频繁修改的小数据(如校准参数、用户设置)。

(2) 易失性存储器(Volatile Memory)

用于存储 临时数据、变量和堆栈,运行时需要快速访问。

RAM(SRAM) – 读写速度快

容量较小(几KB到几百KB)

断电数据丢失 | 存储程序运行时的变量、堆栈、动态数据。

DRAM – 容量大,成本低

需要刷新电路

单片机中较少使用 | 主要用于外部扩展的大容量存储器(如某些高端单片机外接SDRAM)。

存储器在单片机中的位置

(1) 内部存储器

大多数单片机将 Flash、RAM、ROM 集成在芯片内部,直接通过总线与内核连接。

示例(STM32F103):

内部Flash:64 KB ~ 512 KB(存储程序代码)。

内部SRAM:20 KB ~ 64 KB(存储运行时数据)。

内部Boot ROM:厂商预置的Bootloader(用于固件更新)。

(2) 外部存储器

如果内部存储器不足,可通过接口扩展外部存储器:

外部Flash:通过SPI或QSPI接口扩展(存储大容量数据或程序)。

外部RAM:通过FSMC接口扩展(如STM32连接外部SRAM或SDRAM)。

存储器的具体作用

(1) Flash

存储程序代码:单片机上电后,CPU从Flash中读取指令并执行。

存储常量数据:如字符串、查找表等。

示例:STM32的程序烧录到内部Flash中,掉电后代码不丢失。

(2) RAM

存储变量:全局变量、局部变量、静态变量。

动态内存分配:通过堆(Heap)管理动态内存(需谨慎使用,单片机通常避免动态内存)。

堆栈(Stack):保存函数调用时的返回地址、局部变量和中断上下文。

示例:程序运行时,变量int a = 10;会存储在RAM中。

(3) ROM

厂商预置代码:如Bootloader(STM32的系统存储器中预置了UART/USB Bootloader)。

固定数据:如字体库、加密密钥(某些单片机使用OTP-ROM存储)。

(4) EEPROM

存储用户配置:如Wi-Fi密码、校准参数。

示例:通过I2C接口访问外部EEPROM芯片(如24C02)。

单片机执行代码的具体流程

以STM32单片机为例,假设程序已烧录到Flash中:

步骤1:上电或复位

硬件初始化:

CPU从Flash的固定地址(如0x0800_0000)读取复位向量(程序入口地址)。

初始化堆栈指针(SP)和程序计数器(PC)。

步骤2:从Flash加载代码

取指令(Fetch):

CPU根据PC的值,从Flash中读取下一条指令(例如ADD R1, R2, R3)。

Flash通过预取缓冲区和总线将指令传输给CPU。

(预取的指令数量 取决于 CPU 的设计和配置。对于某些 CPU,预取缓冲区可能只存储 下一条指令;而对于其他 CPU,可能会预取 多条指令(如 2 条、4 条甚至更多)。

例如,ARM Cortex-M 系列处理器通常支持 双字预取(即预取 2 条指令)。)

预取的工作流程

执行当前指令:CPU 执行 PC 指向的指令。

预取后续指令:同时,CPU 从 Flash 中读取后续指令,并将其存储在预取缓冲区中。

更新 PC:PC 指向下一条指令的地址。

从预取缓冲区读取指令:如果下一条指令已经在预取缓冲区中,则直接读取;否则,从 Flash 中读取。

解码指令(Decode):

CPU的控制单元解析指令,确定需要执行的操作(如加法运算)。

步骤3:从RAM加载数据

读取数据(Read Data):

如果指令需要数据(例如LOAD R0, [0x2000_0000]),CPU会通过总线从RAM的地址0x2000_0000读取数据到寄存器R0。

注意:常量数据(如const int a = 100;)可能直接存储在Flash中,运行时无需加载到RAM。

步骤4:执行运算

执行指令(Execute):

CPU的**算术逻辑单元(ALU)**执行运算(如加法、逻辑操作)。

运算结果暂存于寄存器(如R1)。

步骤5:将结果写回RAM

写回数据(Write Back):

如果指令需要保存结果(例如STORE R1, [0x2000_0004]),CPU将寄存器R1的值写入RAM的地址0x2000_0004。

注意:ARM内核本身不存储数据,所有数据最终存放在RAM或寄存器中。

步骤6:更新程序计数器

循环执行:

PC指向下一条指令地址,重复步骤2~6,直到程序结束。

注意:

ARM芯片 ≠ 内核,但ARM芯片包含内核

ARM公司 是一家设计 CPU内核架构 的公司,它并不生产芯片,而是通过授权其内核设计给其他芯片厂商(如ST、NXP、TI等)。

ARM芯片 是指由其他厂商基于ARM内核设计的 完整芯片,这些芯片不仅包含ARM内核,还集成了外设、存储器、时钟等模块。

内核 是ARM芯片中的核心部分,但芯片本身是一个更复杂的系统。

类比:

ARM内核 就像汽车的引擎(核心动力来源)。

ARM芯片 就像整辆汽车(包含引擎、轮胎、方向盘、刹车系统等)。

2. ARM内核的定位

ARM公司设计了多种不同的内核架构,主要分为几大类:

(1) Cortex-M系列

定位:微控制器(MCU)内核,用于单片机(如STM32、ESP32)。

特点:

低功耗、低成本、实时性强。

适合工业控制、物联网设备等嵌入式场景。

常见内核:Cortex-M0、M3、M4、M7。

(2) Cortex-A系列

定位:应用处理器内核,用于高性能设备(如手机、平板、树莓派)。

特点:

支持复杂操作系统(如Linux、Android)。

多核设计,适合通用计算。

常见内核:Cortex-A53、A72、A78。

(3) Cortex-R系列

定位:实时处理器内核,用于高可靠性场景(如汽车电子、航空航天)。

特点:

强实时性、容错能力高。

适合需要快速响应的控制系统。

3. ARM芯片的组成

以常见的 STM32单片机(ARM芯片) 为例,它包含以下部分:

ARM内核(如Cortex-M3/M4):负责执行程序指令,处理数据。

Flash存储器:存储程序代码。

SRAM:存储临时数据。

外设模块:GPIO、UART、SPI、I2C、ADC、定时器等。

时钟系统:提供时钟信号,控制芯片运行速度。

电源管理:调节电压和功耗。

程序为什么在Flash中运行,而不是RAM?

Flash是非易失性存储器:程序需要长期保存。

RAM容量有限:大多数单片机的RAM不足以存储整个程序。

性能优化:Flash支持预取和缓存,STM32的Flash访问速度可达几十MHz,足够多数应用。

程序运行时,变量 会从 Flash 复制到 RAM 中。这是因为:

Flash 的特点:

Flash 是非易失性存储器,适合存储程序代码和常量数据。

Flash 的读取速度较慢,且不支持直接写入(需要擦除和写入操作)。

RAM 的特点:

RAM 是易失性存储器,读写速度非常快。

RAM 适合存储程序运行时需要频繁访问的变量和数据。

因此,为了提高程序运行效率,变量通常会被复制到 RAM 中。

以嵌入式系统为例,程序运行时的过程如下:

启动阶段:

系统启动时,CPU 从 Flash 中加载可执行文件。

程序的代码段(.text)和只读数据段(.rodata)通常保留在 Flash 中,因为它们是只读的。

变量初始化:

程序中的全局变量和静态变量会被复制到 RAM 中。

这些变量的初始值通常存储在 Flash 中,启动时会被加载到 RAM 中。

堆和栈:

程序的堆(heap)和栈(stack)在 RAM 中动态分配。

堆用于动态内存分配(例如 malloc),栈用于存储局部变量和函数调用信息。

程序运行:

CPU 从 Flash 中读取程序代码,并在 RAM 中操作变量和数据。

#include <stdio.h> int global_var = 10; // 全局变量,存储在 RAM 中

#include

int global_var = 10; // 全局变量,存储在 RAM 中

int main() {

int local_var = 20; // 局部变量,存储在栈中

printf("global_var = %d, local_var = %d\n", global_var, local_var);

return 0;

}

stm32的Jlink下载原理:

. J-Link 让 CPU 停止运行

在固件下载开始之前,J-Link 通过调试接口(如 JTAG 或 SWD)向 STM32 发送调试请求,使 CPU 停止运行(进入 Halt 模式)。

这是为了确保在下载过程中,CPU 不会干扰 Flash 操作。

2. 下载驱动程序到 RAM

J-Link 将一段Flash 编程算法(Flash Driver)下载到 STM32 的 RAM 中。这段算法是一个小型程序,专门用于执行 Flash 存储器的擦除、编程和校验操作。

Flash 编程算法通常由芯片厂商(如 ST)提供,并集成在 J-Link 的软件工具(如 J-Flash 或 Keil MDK)中。

3. CPU 恢复运行,PC 指针指向驱动程序

J-Link 通过调试接口将 CPU 的 PC(程序计数器)指向 RAM 中的 Flash 编程算法入口地址。

然后,J-Link 恢复 CPU 运行,CPU 开始执行 RAM 中的 Flash 编程算法。

4. 下载 HEX 文件到 RAM

J-Link 将待下载的固件(HEX 或 BIN 文件)分块传输到 STM32 的 RAM 中。RAM 中会分配一块缓冲区用于存储这些数据。

在传输过程中,J-Link 可能会对固件数据进行校验(如 CRC 校验),以确保数据的完整性。

5. 调用总线,刷写 Flash

Flash 编程算法从 RAM 中读取固件数据,并通过总线(如 AHB 或 APB)将数据写入 Flash 存储器。

刷写 Flash 的过程包括:

擦除 Flash:根据固件的大小和位置,擦除 Flash 的相应扇区。

编程 Flash:将固件数据写入 Flash 存储器。

校验 Flash:验证写入的数据是否正确。

6. CPU 恢复运行

当 Flash 刷写完成后,J-Link 再次停止 CPU 运行,并将 PC 指针恢复到复位向量(通常是 0x08000000,指向 Flash 的起始地址)。

然后,J-Link 恢复 CPU 运行,STM32 开始执行 Flash 中的新固件。

start:0x200000000是ram地址,size:0x1000是驱动程序的大小

寄存器(R0-R12、PC、LR等)在哪里?

寄存器是 CPU 内部的存储单元,不属于 RAM 或 Flash。

物理位置:位于 CPU 芯片内部,直接与运算单元(ALU)相连。

功能:用于暂存指令、数据和地址,速度极快(与 CPU 时钟同步)。

示例:

R0-R12:通用寄存器,存放临时数据。

PC(程序计数器):指向下一条要执行的指令地址(在 Flash 中)。

LR(链接寄存器):保存函数返回地址。

可执行文件的存储位置

在单片机中,编译生成的可执行文件(如 .hex 或 .bin)会直接烧录到 Flash 存储器 中,而不是虚拟内存空间。

单片机没有虚拟内存:所有内存操作都是基于物理地址(Flash 和 RAM 的地址)。

程序的内存布局:

代码段(.text):存储程序指令(如函数代码),位于 Flash 中。

只读数据段(.rodata):存储常量(如 const int a = 10;),位于 Flash 中。

已初始化数据段(.data):存储初始化的全局变量(如 int b = 20;),启动时从 Flash 复制到 RAM。

未初始化数据段(.bss):存储未初始化的全局变量(如 int c;),在 RAM 中分配并清零。

堆(Heap):动态内存分配区域(如 malloc()),位于 RAM 中。

栈(Stack):存储局部变量和函数调用上下文,位于 RAM 中。

虚拟内存空间 vs 物理内存空间(不了解可以AI,或者b站查阅)

虚拟内存空间:

是操作系统(如 Linux、Windows)的概念,通过 MMU(内存管理单元)将虚拟地址映射到物理地址。

单片机通常没有 MMU,因此直接使用物理地址,没有虚拟内存空间。

物理内存空间:

单片机的物理地址直接对应 Flash 和 RAM 的实际存储位置。

Flash:存放代码和常量(地址范围如 0x0800_0000 ~ 0x0807_FFFF)。

RAM:存放变量和运行时数据(地址范围如 0x2000_0000 ~ 0x2000_7FFF)。

为什么操作系统需要虚拟地址内存?MMU又是怎么将虚拟地址映射到物理地址的?

场景:没有虚拟内存的世界

假设你正在同时运行 微信 和 浏览器,两者直接操作物理内存:

内存地址冲突:

微信的代码假设自己从 0x0000_0000 开始存放。

浏览器也认为自己从 0x0000_0000 开始存放。

结果:两者数据互相覆盖,程序崩溃。

安全性问题:

微信可以随意读写浏览器的内存,窃取密码或篡改数据。

恶意程序可直接攻击操作系统内核的内存。

内存碎片化:

物理内存被不同程序分割成碎片,新程序可能找不到连续内存空间。

虚拟内存的解决方案

操作系统通过 虚拟内存 和 MMU 实现以下目标:

每个程序拥有独立的虚拟地址空间:

微信和浏览器都认为自己独占 0x0000_0000 ~ 0xFFFF_FFFF 的内存。

实际物理内存由操作系统动态分配。

内存隔离与保护:

微信无法直接访问浏览器的物理内存,也无法修改内核内存。

扩展可用内存:

通过硬盘模拟内存(Swap),让程序使用比物理内存更大的空间。

MMU 如何映射虚拟地址到物理地址?

MMU(Memory Management Unit)是 CPU 的硬件模块,负责将虚拟地址转换为物理地址,其核心机制是 分页(Paging)。

基础概念

(1) 分页机制

内存被划分为固定大小的 页(Page),常见页大小为 4KB。

虚拟页(Virtual Page):程序看到的连续地址空间。

物理页(Physical Page Frame):实际物理内存中的页。

页表(Page Table):记录虚拟页到物理页的映射关系。

(2) 虚拟地址结构

一个 32 位虚拟地址分为两部分:

页号(VPN, Virtual Page Number):高位部分,用于查找页表。

页内偏移(Offset):低位部分,直接保留到物理地址。

例如,对于 4KB 页大小(12 位偏移):虚拟地址 = 20 位页号(VPN) + 12 位偏移(Offset)

地址转换全流程

假设虚拟地址为 0x12345678,页大小为 4KB,物理内存布局如下:

步骤1:拆分虚拟地址

虚拟地址:0x12345678(32 位)

页号(VPN):高 20 位 → 0x12345

页内偏移(Offset):低 12 位 → 0x678

虚拟地址:0x12345678 → VPN = 0x12345, Offset = 0x678

步骤2:查询页表

页表基址寄存器(CR3):存储当前进程的页表物理地址(例如 0x1000)。

页表项(PTE):每个 PTE 对应一个虚拟页,存储物理页号、有效位、权限等。

MMU 通过以下公式计算 PTE 的地址:PTE地址 = 页表基址(CR3) + VPN * PTE大小

假设PTE的大小是4字节:

PTE地址 = 0x1000 + 0x12345 * 4 = 0x1000 + 0x48D14 = 0x58D14

读取 PTE:从物理地址 0x58D14 读取 PTE 内容 → 0xABCDE(物理页号) + 有效位为 1 + 权限为读/写。

步骤3:生成物理地址

物理页号(PPN):从 PTE 中获取 → 0xABCDE

物理地址 = 物理页号 + 偏移 → 0xABCDE678

物理地址 = (PPN

步骤4:检查权限

MMU 检查 PTE 中的权限是否允许当前操作(例如写操作需要“写”权限)。

若权限不足,触发 段错误(Segmentation Fault)。

多级页表

实际系统中,页表通常是 多级结构(如二级页表),以节省内存。

以 二级页表 为例(32 位地址,10 位一级页号 + 10 位二级页号 + 12 位偏移):

(1) 拆分虚拟地址

虚拟地址 = 10 位一级页号 + 10 位二级页号 + 12 位偏移

(2) 查询过程

一级页表(Page Directory):根据一级页号找到二级页表的地址。

二级页表(Page Table):根据二级页号找到物理页号。

(3) 示例

虚拟地址:0x12345678

拆分:

一级页号:0x48

二级页号:0x345

偏移:0x678

一级页表查询:

一级页表基址 = CR3 = 0x1000

一级 PTE 地址 = 0x1000 + 0x48 * 4 = 0x1120

读取一级 PTE → 二级页表基址 0x2000

二级页表查询:

二级 PTE 地址 = 0x2000 + 0x345 * 4 = 0x2000 + 0xD14 = 0x2D14

读取二级 PTE → 物理页号 0xABCDE

生成物理地址:0xABCDE678

.TLB(Translation Lookaside Buffer)

为了加速地址转换,MMU 内置了 TLB(快表),缓存最近使用的页表项。

TLB 查询流程

MMU 先检查 TLB 是否存在虚拟页号 0x12345 的条目。

若命中(TLB Hit),直接获取物理页号。

若未命中(TLB Miss),需查询页表,并将结果缓存到 TLB。

MMU 的地址转换流程可归纳为:

拆分虚拟地址 → 页号 + 偏移。

查询页表 → 多级页表逐级查找。

生成物理地址 → 物理页号 + 偏移。

权限检查 → 确保操作合法性。

TLB 加速 → 缓存常用页表项。

具体转换流程图

+---------------------+       +---------------------+
|   虚拟地址 (VA)       |       |                     |
| 0x12345678          |       |                     |
+----------+----------+       |                     |
           |                   |                     |
           | 拆分虚拟地址       |                   |
           | (VPN + Offset)    |                   |
           v                   |                     |
+----------+----------+       |                     |
| 页号 (VPN): 0x12345  |       |                     |
| 偏移 (Offset): 0x678 |       |                     |
+----------+----------+       |                     |
           |                   |                     |
           | 查询 TLB         |                     |
           |                   |                     |
           v                   |                     |
+----------+----------+       |                     |
|   TLB 命中?         +-------+                     |
+----------+----------+       |                     |
           |                   |                     |
           | Yes               | No                  |
           v                   v                     |
+---------------------+       |                     |
| 从 TLB 获取物理页号   |       | 查询页表              |
| PPN: 0xABCDE         |       | (多级页表遍历)         |
+----------+----------+       |                     |
           |                   |                     |
           v                   v                     |
+---------------------+       |                     |
| 生成物理地址 (PA)     |       | 更新 TLB              |
| PA = 0xABCDE678      |       |                     |
+----------+----------+       |                     |
           |                   |                     |
           | 检查权限           |                     |
           | (读/写/执行)        |                     |
           v                   |                     |
+---------------------+       |                     |
| 权限允许?            +-------+                     |
+----------+----------+       |                     |
           |                   |                     |
           | Yes               | No                  |
           v                   v                     |
+---------------------+       |                     |
| 访问物理内存          |       | 触发异常               |
| (读/写操作)           |       | (页错误/段错误)         |
+---------------------+       |                     |

通过这种机制,操作系统实现了:

内存隔离:程序无法直接访问物理内存。

虚拟连续空间:程序看到统一的地址空间。

内存保护:防止非法读写。

内存布局的核心作用

(1) 确保程序正确运行

代码和数据的分离:

代码段(.text)必须存储在只读的 Flash 中,而变量需要存储在可读写的 RAM 中。内存布局明确了这种分离,避免数据覆盖代码或非法修改。

示例:若未正确分配内存,代码段被误写入数据,可能导致程序崩溃或硬件错误。

(2) 优化存储空间

合理分配 Flash 和 RAM:

常量数据(如字库、配置表)放在 Flash,节省 RAM。

频繁修改的变量放在 RAM,避免反复擦写 Flash 影响寿命。

示例:将大型只读数组声明为 const,编译器会将其放入 Flash,而非占用宝贵 RAM。

(3) 避免内存溢出

堆栈冲突预防:

栈(Stack)和堆(Heap)在 RAM 中动态增长,内存布局需预留足够空间,防止两者相互覆盖。

示例:栈溢出会导致函数调用混乱,堆溢出可能破坏全局变量。

(4) 提升执行效率

缓存和预取优化:

连续代码段和频繁访问的数据集中存储,可提升 CPU 缓存命中率和 Flash 预取效率。

示例:关键循环代码紧凑排列,减少指令跳转,加快执行速度。

单片机中的内存布局配置

以 STM32 为例,开发者通过 链接脚本(.ld 文件) 定义内存布局:

/* 定义 Flash 和 RAM 的物理地址及大小 */
MEMORY {
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 256K
    RAM (xrw)   : ORIGIN = 0x20000000, LENGTH = 64K
}

/* 指定各段的位置 */
SECTIONS {
    .text : { *(.text) } > FLASH   /* 代码段放入 Flash */
    .data : { *(.data) } > RAM     /* 数据段放入 RAM  */
    .bss  : { *(.bss)  } > RAM     /* BSS 段放入 RAM  */
}

在单片机开发中,内存布局(即程序各段在 Flash 和 RAM 中的分配)通常会被开发工具链默认配置好。

什么是MPU?

FreeRTOS 的 MPU 支持

为每个任务分配独立内存区域,防止任务间数据篡改。

若任务越界访问,MPU 触发异常,系统记录错误并终止任务。

MPU 与 CPU 的协作关系

MPU 作为 CPU 的辅助模块:

MPU 通过总线监听 CPU 的内存访问请求,实时检查权限。

若访问合法,CPU 继续执行;若非法,MPU 触发异常,CPU 暂停当前任务并处理异常。

示例流程:

步骤1:CPU 执行指令 STR R0, [R1](将寄存器 R0 的值写入 R1 指向的内存地址)。

步骤2:MPU 检查地址 [R1] 所在内存区域的写权限。

步骤3:若区域为只读,MPU 触发异常,CPU 跳转到异常处理程序;若可写,CPU 完成写入操作。

总线系统的工作机制

总线系统的本质作用

总线(Bus)是计算机系统中用于传输数据、地址和控制信号的公共通道,它像神经系统一样连接CPU、存储器和外设。总线的三大核心功能:

数据传输:在设备间搬运二进制数据

地址指定:确定数据的目标位置

控制协调:管理传输时序和优先级

总线层级体系(经典冯·诺依曼架构)

现代计算机采用分层总线结构提高效率:

1.芯片内部总线

指令总线(Instruction Bus)

哈佛架构特征:独立传输指令

例如:ARM Cortex-M3的I-Code总线(32位,专取指令)

数据总线(Data Bus)

哈佛架构特征:独立传输数据

例如:D-Code总线(32位,访问数据存储器)

系统总线(System Bus)

连接CPU核心与芯片内部模块(如NVIC中断控制器)

哈佛架构 vs 冯·诺依曼架构:

哈佛:指令/数据总线物理分离(提升并行性)

冯·诺依曼:共享总线(成本更低)

总线系统的拓扑互联

以ARM Cortex-M4 + AHB/APB架构为例的总线网络拓扑:

                            ┌───────────┐
                            │  CPU Core │
                            └─────┬─────┘
           ┌──────────────────────┼──────────────────────┐
           │ 指令总线           系统总线               数据总线 │
           ▼                      ▼                      ▼
┌─────────────────────┐     ┌──────────────┐    ┌─────────────────────┐
│    I-Code总线        │    │ 系统总线矩阵  │    │    D-Code总线       │
│ (哈佛架构指令通道)    │    │ (路由+仲裁)  │     │ (哈佛架构数据通道)   │
└──────────┬───────────┘    └──────┬───────┘    └──────────┬──────────┘
           │                       │                       │
           ▼                       ▼                       ▼
┌─────────────────────┐    ┌──────────────┐    ┌─────────────────────┐
│   Flash控制器       │    │  SRAM控制器   │    │   AHB2APB桥接器     │
└─────────────────────┘    └──────────────┘    └──────────┬──────────┘
                                                           │
                                                           ▼
                                                    ┌──────────────┐
                                                    │ APB总线网络   │
                                                    │ (树形拓扑)    │
                                                    ├──────┬───────┤
                                                    ▼      ▼       ▼
                                             ┌──────┐  ┌──────┐  ┌──────┐
                                             │ UART │  │ GPIO │  │ SPI  │
                                             └──────┘  └──────┘  └──────┘

各总线的工作机制详解:

指令总线(I-Code Bus)

作用:专用取指通道,与数据总线物理隔离(哈佛架构优势)

工作流程:

CPU预取指单元发出PC地址

总线矩阵将地址路由到Flash控制器

Flash返回指令代码(32位ARM指令)

流水线持续填充(每周期1条指令)

关键信号:

IADDR[31:0]:指令地址

IDATA[31:0]:指令数据

IREQ:取指请求

数据总线(D-Code Bus)

作用:数据存取专用通道,支持原子操作

访问类型:

加载(Load):内存 → 寄存器

存储(Store):寄存器 → 内存

AHB总线(Advanced High-performance Bus)

协议分层:

传输阶段:

地址周期(Address Phase):HADDR有效

数据周期(Data Phase):HWDATA/HRDATA有效

// 主控接口
input  HREADY,   // 从设备就绪
output HTRANS,   // 传输类型(IDLE/BUSY/NONSEQ/SEQ)
output HBURST,   // 突发类型
output HPROT     // 保护属性

// 从设备接口
output HRESP     // 响应状态(OKAY/ERROR/RETRY/SPLIT)
HCLK     _/ ̄\_/ ̄\_/ ̄\_/ ̄\_
HADDR    0x20000000  <无效>    
HWDATA              0xA5A5A5A5
HWRITE    1          0        
HTRANS   NONSEQ      IDLE     
HREADY    ̄\__________/        从设备插入等待周期

APB总线(Advanced Peripheral Bus)

两阶段操作:

Setup Phase:

PSELx拉高选中外设

PADDR/PWRITE稳定

Access Phase:

PENABLE拉高

数据在PWDATA或PRDATA传输

低速外设访问示例(GPIO写操作):

*((volatile uint32_t*)0x40020010) = 0x01; // 写入GPIOA_BSRR寄存器

对应APB信号:

PCLK    __| ̄|__| ̄|__| ̄|__
PSEL     _______| ̄|________
PENABLE _________| ̄|______
PADDR   0x40020010
PWDATA  0x00000001

三、总线间的协同工作流程

以「从Flash读取数据并写入UART」为例:

步骤1:指令获取(I-Code总线)

CPU发出PC=0x08000000

总线矩阵路由到Flash控制器

Flash返回LDR R0, [PC+0x04]指令

步骤2:数据加载(D-Code总线)

解码LDR指令后,生成数据地址0x08000004

AHB突发读取4字节数据到R0

总线矩阵检测到地址属于Flash区域,复用数据通道

步骤3:外设写入(APB总线)

1.CPU执行STR R0, [0x4000_8000](UART_DR地址)

2.总线矩阵解码0x4000_8000属于APB域

3.AHB2APB桥接器启动协议转换:

*将AHB写操作转换为APB两阶段操作

*时钟分频(100MHz → 50MHz)

4.UART接收数据并启动发送

哈佛架构 vs 冯·诺依曼架构

总线信号差异:

冯·诺依曼:

同一组数据总线交替传输指令和数据

需要总线复用器(Bus Multiplexer)

哈佛:

指令总线:32位独立通道(如:ARM Cortex-M的I-Code总线)

数据总线:32位独立通道(如:D-Code总线)

volatile

volatile 关键字的作用

当变量被 volatile 修饰时,编译器会确保每次访问该变量时都直接从内存中读取其值,而不是使用寄存器中的缓存值。这里的“内存”通常指的是 RAM(随机存取存储器),而不是 Flash。

为什么需要 volatile?

在某些情况下,变量的值可能会在程序的控制之外被改变(例如硬件寄存器、多线程共享变量等)。如果编译器对变量进行了优化,可能会使用寄存器中的缓存值,而不是从内存中读取最新值,这会导致程序行为错误。

volatile 告诉编译器:“这个变量的值可能会意外改变,不要优化它。”

内存、寄存器、Flash 的关系

内存(RAM)

RAM 是程序运行时存储数据的地方。变量通常存储在 RAM 中,程序可以直接读写 RAM 中的数据。

当程序访问一个变量时,如果没有 volatile 修饰,编译器可能会将变量的值缓存在寄存器中,以提高访问速度。

寄存器

寄存器 是 CPU 内部的高速存储单元,用于临时存储数据和指令。

CPU 在处理数据时,通常会从 RAM 中加载数据到寄存器中进行计算,然后将结果写回 RAM。

如果变量没有被 volatile 修饰,编译器可能会将变量的值缓存在寄存器中,后续访问时直接从寄存器读取,而不是从 RAM 中读取。

Flash

Flash 是一种非易失性存储器,通常用于存储程序代码和常量数据。

在嵌入式系统中,Flash 通常用于存储程序代码和只读数据,而 RAM 用于存储运行时变量。

volatile 修饰的变量通常存储在 RAM 中,而不是 Flash。

为什么寄存器会存值?

寄存器是 CPU 处理数据的核心单元。为了提高性能,CPU 会尽量将频繁使用的数据保存在寄存器中,而不是每次都从 RAM 中读取。这是因为访问寄存器的速度比访问 RAM 快得多。

int x = 10;  // 变量 x 存储在 RAM 中
int y = x + 5;  // CPU 从 RAM 中读取 x 的值,加载到寄存器中进行计算

如果没有 volatile 修饰,编译器可能会将 x 的值缓存在寄存器中,后续访问 x 时直接从寄存器读取。

问题:

如果 x 的值在程序之外被改变了(例如硬件修改了 x 的值),而程序仍然使用寄存器中的缓存值,就会导致错误。

作者:要做朋鱼燕

物联沃分享整理
物联沃-IOTWORD物联网 » 单片机内核架构外设解析:图文详解+代码下载与运行原理总结(万字长文)

发表回复