当前位置: 首页 > news >正文

嵌入式 ARM架构与汇编

处理器的区别

CPU、MPU、MCU、AP、SoC

CPU:中央处理器,在PC机它是一个独立的芯片。在嵌入系统中,它是芯片里的一个单元,跟其他模块比如USB、UART、音频组成一个芯片。
CPU的发展有两个路线:MPU、MCU。
MPU只是一个处理器,需要搭配内存等非常多的其他外设才可以构成一个系统;MCU内部有处理器、内存、Flash及其他模块,仅仅需要搭配少量外设就可以构成一个系统。MCU MPU 最主要的区别就睡能否直接运行代码:MCU有内部的RAM ROM,而MPU是增强版的CPU, 需要添加外部RAM ROM才可以运行代码。
AP:在手机中,这个主芯片一般用来处理显示、输入,运行用户的程序,所以称它为“Application
Processors”。
SoC:在一个芯片上就可以搭建完整的系统。
MCU上一般不运行操作系统,或是运行一些资源耗费较小的小型实时操作系统(RTOS)。
AP基本上都会运行比较复杂的操作系统(比如Linux),在操作系统上运行多个APP

指令集

CISC(复杂指令集):通过设计复杂的指令,减少程序指令数量,以硬件复杂度换取编程便利性。
RISC(精简指令集):简化指令集,每条指令执行时间接近单周期,通过编译器优化提高效率。

如上图所示:a = a * b。它需要4个步骤:读出a的值、读出b的值、相乘、写结果到a

使用CISC提供的乘法指令,只需要一条指令即可完成这4步操作。当然,这一个指令需要多个CPU周期才可以完成;而RISC不提供“一站式”的乘法指令,需调用四条单CPU周期指令完成两数相乘:内存a加载到寄存器,内存b加载到寄存器,两个寄存器中数相乘,寄存器结果存入内存a

CISC与RISC的区别

1. 指令能力:CISC的指令能力强,单多数指令使用率低却增加了CPU的复杂度,指令是可变长格式;RISC的指令大部分为单周期指令,指令长度固定。RISC对内存只有load/store操作,数据的运算都是在CPU内部实现。例如CISC可以使用: MOV [AX], [BX] (一步完成内存到内存的数据传输),RISC可以使用: ADD R1, R2, R3 (寄存器间加法,单周期完成);
2. 寻址方式:CISC支持多种寻址方式;RISC支持的寻址方式少;
3. 实现方式:CISC通过微程序控制技术实现;RISC增加了通用寄存器,硬布线逻辑控制为主,采用流水线方式执行;
4. 研发周期:CISC的研制周期长;RISC硬件简单,需要优化编译器;

哈弗结构和冯诺依曼结构

冯诺依曼结构釆用指令和数据统一编址,使用同条总线传输,CPU读取指令和数据的操作无法重叠。
哈佛结构釆用指令和数据独立编址,使用两条独立的总线传输,CPU读取指令和数据的操作可以重叠。

ARM的工作模式

1. 用户模式(USR):用户模式是用户程序的工作模式,它运行在操作系统的用户态,它没有权限去操作其它硬件资源,只能执行处理自己的数据,也不能切换到其它模式下,要想访问硬件资源或切换到其它模式只能通过软中断或产生异常。
2. 系统模式(SYS):系统模式是特权模式,不受用户模式的限制。用户模式和系统模式共用一套寄存器,操作系统在该模式下可以方便的访问用户模式的寄存器,而且操作系统的一些特权任务可以使用这个模式访问一些受控的资源。
3. 一般中断模式(IRQ):一般中断模式也叫普通中断模式,用于处理一般的中断请求,通常在硬件产生中断信号之后自动进入该模式,该模式为特权模式,可以自由访问系统硬件资源。
4. 快速中断模式(FIQ):快速中断模式是相对一般中断模式而言的,它是用来处理对时间要求比较紧急的中断请求,主要用于高速数据传输及通道处理中。
5. 管理模式(SVC):管理模式是CPU上电后默认模式,因此,在该模式下主要用来做系统的初始化,软中断处理也在该模式下。当用户模式下的用户程序请求使用硬件资源时,通过软件中断进入该模式。
6. 终止模式(ABT):中止模式用于支持虚拟内存或存储器保护,当用户程序访问非法地址,没有权限读取的内存地址时,会进入该模式。
7. 未定义模式(UND): 未定义模式用于支持硬件协处理器的软件仿真,CPU在指令的译码阶段不能识别该指令操作时,会进入未定义模式

嵌入式系统内存分配

1. 系统存储区:系统存储区主要用于存放一些特定的引导程序等内容,并不是用于常规用户代码执行的主要区域,它主要是为了系统启动、引导等功能服务,不是执行用户代码性能最优的选择。
2. RAM(易失性存储器):随机存取存储器,其读写速度非常快。代码在RAM中执行时,CPU可以快速地读取指令和数据,减少了等待时间,能够极大地提高执行性能。STM32内部的RAM为代码执行提供了高速的运行空间。
3. FLASH(非易失性存储器):虽然FLASH用于长期存储程序代码等信息,但FLASH的读取速度相对RAM来说较慢。代码直接从FLASH执行时,由于FLASH的特性,会存在一定的读取延迟,相比在RAM中执行,性能会受到影响。
4. SRAM:SRAM即静态随机存取存储器,它是RAM的一种类型。在STM32中,一般提到的RAM就包含了SRAM等。

Flash 存储程序代码和常量数据:

Flash 起始地址
├── **.text 段**(程序代码)
├── **.rodata 段**(只读数据,如 `const` 变量、字符串常量)
├── **.data 段的初始值**(启动时加载到 RAM)
└── **其他自定义段**(如配置数据)
Flash 结束地址

单片机的RAM通常按以下顺序划分:

RAM起始地址
├── 已初始化数据(.data段) // 全局/静态变量(初始值非零)
├── 未初始化数据(.bss段) // 全局/静态变量(初始值为零)
├── 堆(Heap) // 动态分配区域
│ ↑
│ (向高地址增长)

└── 栈(Stack) // 从RAM顶端向低地址增长
RAM结束地址

单片机的内存通常由 Flash(ROM) 和 RAM 组成:
Flash(程序存储器):
存储 程序代码:二进制代码 (.text 段)。
存储 只读数据(.rodata 段,如常量字符串、const 变量)。
存储 初始化数据(.data 段的初始值)(在启动时拷贝到 RAM)。
RAM(数据存储器):
栈(Stack):存储局部变量、函数调用信息。
堆(Heap):动态内存分配(如 malloc / free )。
全局/静态变量:
.data 段(已初始化全局变量)。
.bss 段(未初始化或初始化为 0 的全局变量)。

RAM起始地址 0x2000 0000 ROM起始地址0x0800 0000

程序编译过程

gcc使用示例:

gcc hello.c // 输出一个名为a.out的可执行程序,然后可以执行./a.out
gcc -o hello hello.c // 输出名为hello的可执行程序,然后可以执行./hello
gcc -o hello hello.c -static // 静态链接
gcc -c -o hello.o hello.c // 先编译(不链接)
gcc -o hello hello.o // 再链接

hello.c(预处理)->hello.i(编译)->hello.s(汇编)->hello.o(链接)->hello

gcc -E -o hello.i hello.c
gcc -S -o hello.s hello.i
gcc -c -o hello.o hello.s
gcc -o hello hello.o

单片机从上电到开始执行用户程序的主要步骤

1. 通过Boot引脚设定,寻找初始地址(Flash:0x08000000;BootLoader:0x1fff0000;SRAM:
0x20000000)
2. 初始化栈指针 __initial_sp
3. 指向复位程序 Reset_Handler
4. 设置中断异常 HandFault_Handler
5. 设置系统时钟 Systeminit
6. 调用c库函数 _main(将.data段复制到SRAM中,将.bss段中的变量清零)

STM32中startup.s文件

1. 设置栈的大小 默认为1kb
2. 设置堆的大小,默认为0.5kb
3. 设置中断向量表
4. 复位程序
5. 中断服务函数
6. 堆栈初始化

寄存器操作

GPIO相关寄存器结构:
1. 有多组GPIO,每组有多个GPIO
2. 使能:电源/时钟
3. 模式(Mode):引脚可用于GPIO或其他功能
4. 方向:引脚Mode设置为GPIO时,可以继续设置它是输出引脚,还是输入引脚
5. 数值:对于输出引脚,可以设置寄存器让它输出高、低电平;对于输入引脚,可以读取寄存器得到引脚的当前电平

可以对寄存器进行操作点亮LED:

查看LED原理图(假设我们使能红灯则为PB0)

使能GPIOB GPIOB位于APB2时钟线,找到APB2寄存器原理图并设置为1即可。需要注意他的地址为0x18,那么我们在编写代码时需要在RCC源地址上0x18(偏移地址)

RCC_APB2ENR地址: 0x40021000 + 0x18

设置GPIOB0为GPIO,用做输出
需要注意他的地址为0x00,那么我们在编写代码时需要在GPIOB源地址上0x00(偏移地址)

GPIOB_CRL地址: 0x40010C00 + 0x00

4. 设置GPIOB0的输出电平

方法1:读寄存、修改值、写回去(低效)

方法2:直接写寄存器,一次操作即可,高效

5. 代码如下:

void delay(int d)
{
while(d--);
}
int main()
{
unsigned int *pReg;
/* 使能GPIOB */
pReg = (unsigned int *)(0x40021000 + 0x18);
*pReg |= (1<<3);
/* 设置GPIOB0为输出引脚 */
pReg = (unsigned int *)(0x40010C00 + 0x00);
*pReg |= (1<<0); //这里是选择PB0为01模式(MODE0对应PB0,下面的两个bit对应模式选择,这里亮红灯)

/* GPIOB的数据输出寄存器 修改pReg的值即可让灯亮或灭 */
pReg = (unsigned int *)(0x40010C00 + 0x0C);
while(1)
{
/* 设置GPIOB0输出1 */
*pReg |= (1<<0); //ODR0对应PB0,红灯亮
delay(100000);
/* 设置GPIOB0输出0 */
*pReg &= ~(1<<0); //ODR0对应PB0,红灯灭
delay(100000);
}
return 0;
}

PRESERVE8
THUMB
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
__Vectors DCD 0
DCD Reset_Handler ; Reset Handler
AREA |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT main
LDR SP, =(0x20000000+0x10000)
BL main
ENDP
END

ARM内部寄存器

寄存器R0–R12为通用目的寄存器,前8个(RO–R7)也被称作低寄存器。由于指令中可用的空间有限,许多16位指令只能访问低寄存器。高寄存器(R8–R12)则可以用于32位指令和几个16位指令,如
MOV(move)。RO–R12的初始值是未定义的。
R13为栈指针,可通过PUSH和POP操作实现栈存储的访问。物理上存在两个栈指针:主栈指针(MSP,
有些ARM文献也称其为SP_main)为默认的栈指针,在复位后或处理器处于处理模式时,其会被处理器选择使用。另外一个栈指针名为进程栈指针(PSP,有些ARM文献也称其为SP_process),其只能用于线程模式。对于一般的程序,这两个寄存器只会有一个可见。MSP和PSP都是32位的,不过指针(MSP或PSP)的最低两位总是为0,对这两位的写操作不起作用。对于ARMCortex-M处理器,PUSH和POP总是32位的,栈操作的地址也必须对齐到32位的字边界上。(.s文件创建的堆栈使用的是MSP,给系统运行后用户在内存中创建的任务使用的是PSP)
R14也被称作链接寄存器(LR),用于函数或子程序调用时返回地址的保存(用来保存返回地址)。在函
数或子程序结束时,程序控制可以通过将LR的数值加载程序计数器(PC)中返回调用程序处并继续执
行。当执行了函数或子程序调用后,LR的数值会自动更新。若某函数需要调用另外一个函数或子程
序,则它需要首先将LR的数值保存在栈中,否则,当执行了函数调用后,R的当前值会丢失。
R15为程序计数器(PC),是可读可写的,读操作返回当前指令地址+4(+4是因为这里是ARM指令,异常类型属于ARM状态)。写PC(例如,使用数据传输/处理指令)会引起跳转操作。(表示当前指令地址,写入新值即可跳转) 在ARM状态下,位[1:0]为0,位[31:2]用于保存PC;在Thumb 状态下,位[0]为0,位[31:1]用于保存PC。

对于cortex-M3/M4来说,xPSR实际上对应3个寄存器: ① APSR:Application PSR,应用PSR ②
IPSR:Interrupt PSR,中断PSR ③ EPSR:Exectution PSR,执行PSR
PUSH {r0, r1, LR} 在栈中的存放顺序是从上往下(高地址到低地址):LR--->r1--->r0 即指令
中的从左到右是低地址到高地址 POP指令也是如此
POP {r1, r2 ,r3} 在栈中的出栈顺序从下往上 即将栈中当前SP指针指向的寄存器的值赋给r1 然后就SP+4指向的值赋给r2 以此类推

ARM汇编

概述

ARM指令集,这是32位的,每条指令占据32位,高效,但是太占空间
Thumb指令集,这是16位的,每条指令占据16位,节省空间
要节省空间时用Thumb指令,要效率时用ARM指令。
程序状态寄存器中有一位,名为“T”,能够区分当前的运行指令是Thumb还是ARM

假设函数A是使用Thumb指令写的,函数B是使用ARM指令写的,怎么调用A/B? 我们可以往PC寄存器里写入函数A或B的地址,就可以调用A或B, 但是怎么让CPU在执行A函数是进入Thumb状态,在执行B函数时进入ARM状态? 调用函数A时,让PC寄存器的BIT0等于1,即:PC=函数A地址+(1<<0); 调用函数B时,让PC寄存器的BIT0等于0:,即:PC=函数B地址

常用汇编指令

MOV: 将数据从一个位置移动到另一个位置。

MOV AX, BX ; 将 BX 的值移动到 AX
MOV [address], AL ; 将 AL 的值存储到指定地址

PUSH: 将数据压入栈中。

PUSH AX ; 将 AX 的值压入栈(低标号存入低地址)

POP: 从栈中弹出数据。

POP BX ; 从栈中弹出值到 BX

LDR: 在 ARM 中,从内存加载数据到寄存器。

LDR R0, [R1] ; 从 R1 指向的内存地址加载数据到 R0

STR:将寄存器的值存储到内存。

STR R0, [R1] ; 将 R0 的值存储到 R1 指向的内存地址

ADD: 加法。

ADD AX, BX ; AX = AX + BX

SUB: 减法。

SUB AX, BX ; AX = AX - BX

B: 无条件分支。

B label ; 跳转到指定标签

BL: 分支并链接(调用子程序)

BL function ; 调用函数并保存返回地址

CMP: 比较两个操作数。

CMP AX, BX ; 比较 AX 和 BX

立即数

立即数(Immediate Value) 是指直接嵌入到指令中的常量值,它是指令的一部分,而非从内存或寄存器中读取的数据。简单来说,立即数是 “写死” 在指令里的固定数值,在程序运行时不需要通过额外的内存访问或寄存器操作来获取

立即数需要符合某些规定:

伪指令

像上面的立即数需要符合某些规定,那么我们在给寄存器写入值的时候,想要可以为任意值时就需要使用LDR伪指令。伪指令就是假的,不存在的指令。注意LDR作为“伪指令”时,指令中有一个“=”,否则它就是真实的LDR指令了。

使用伪指令时有两种情况

写入值为立即数:

LDR R0 , =0x12
汇编器将其转换成MOV指令:MOV R0, #0x12

写入值为非立即数:

LDR R0, =0x12345678
汇编器会将其替换为:Label DCD 0x12345678 // 1. 编译器在程序某个地方保存有这个值
LDR R0, [PC, #offset] // 2. 使用Load Register读内存指令读出
值,offset是链接程序时确定的

代码重定位

段的概念

char g_char1 = 'A';
const char g_char2 = 'B';


put_s_hex("g_Char's addr = ", (unsigned int)&g_char1); //0x20000000
put_s_hex("g_Char2's addr = ", (unsigned int)&g_char2); //0x0800031C


putchar(g_char1);
putchar('\n');

putchar('\r');


putchar(g_char2);
putchar('\n');
putchar('\r');

上述代码中,g_char1能被打印,但是g_char2会出现乱码。我们通过打印其地址发现:g_char1指向内
存地址RAM,该区域是可读可写的;g_char2指向ROM,该区域表现为只读。

char g_Char = 'A'; // 可读可写,不能放在ROM上,应该放在RAM里
const char g_Char2 = 'B'; // 只读变量,可以放在ROM上
int g_A = 0; // 初始值为0,干嘛浪费空间保存在ROM上?没必要
int g_B; // 没有初始化,干嘛浪费空间保存在ROM上?没必要

所以程序分为下面几个段:
代码段(RO-CODE):就是程序本身,不会被修改
可读可写的数据段(RW-DATA):有初始值的全局变量、静态变量,需要从ROM上复制到内存
只读的数据段(RO-DATA):可以放在ROM上,不需要复制到内存
BSS段或ZI段:
初始值为0的全局变量或静态变量,没必要放在ROM上,使用之前清零就可以
未初始化的全局变量或静态变量,没必要放在ROM上,使用之前清零就可以
局部变量:保存在栈中,运行时生成
堆:一块空闲空间,使用malloc函数来管理它,malloc函数可以自己写

对于keil来说,一个本该放到BSS段的变量,如果它所占据的空间小于等于8字节自己,keil仍然会把它放在data段里。只有当它所占据的空间大于8字节时,才会放到BSS段

int g_A[3] = {0, 0}; // 放在BSS段
char g_B[9]; // 放在BSS段
int g_A[2] = {0, 0}; // 放在data段
char g_B[8]; // 放在data段

描述一下嵌入式基于ROM的运行方式和基于RAM的运行方式有什么区别?
基于RAM
需要把硬盘和其他介质的代码先加载到RAM中,加载过程中一般有重定位的操作。
速度比基于ROM的快,可用RAM比基于ROM的少,因为所有的代码,数据都必须存放在RAM中。
基于ROM
速度较基于RAM的慢,因为会有一个把变量,部分代码等从存储器(硬盘,flash)搬移到RAM的 过程。
可用RAM资源比基于RAM的多

重定位概念

保存在ROM上的全局变量的值,在使用前要复制到内存,这就是数据段重定位。
想把代码移动到其他位置,这就是代码重定位

异常与中断

异常中断执行流程

在ARM系统中如何使用异常(中断)?
初始化
设置中断源,让它可以产生中断
设置中断控制器(可以屏蔽某个中断,优先级)
设置CPU总开关,使能中断
执行其他程序:正常程序
产生中断,举例:按下按键--->中断控制器--->CPU
cpu每执行完一条指令都会检查有无中断/异常产生
发现有中断/异常产生,开始处理:
保存现场
分辨异常/中断,调用对于异常/中断的处理函数
恢复现场
不同的芯片,不同的架构,在这方面的处理稍有差别:
保存/恢复现场:cortex M3/M4是硬件实现的,cortex A7是软件实现的
CPU中止当前执行,跳转去执行处理异常的代码:也有差异
cortex M3/M4在向量表上放置的是函数地址
cortex A7在向量表上放置的是跳转指令

中断的执行过程包括中断请求、中断控制器响应、中断响应、中断向量确定、中断处理程序执行和中断处理程序结束。
单片机的中断处理流程:cpu接受中断->保存中断上下文(保存 PC(程序计数器)和寄存器状态到堆栈)->跳转到中断处理历程(根据中断源编号,从向量表中读取对应 ISR 的地址)->执行中断上半部->执行中断下半部->恢复中断上下文(ISR 执行完毕后,从堆栈恢复 PC 和寄存器,继续执行主程序)。

为什么要保存现场

任何程序,最终都会转换为机器码,上述C代码可以转换为右边的汇编指令。 对于这4条指令,它们可能随时被异常打断,怎么保证异常处理完后,被打断的程序还能正确运行?
这4条指令涉及R0、R1寄存器,程序被打断时、恢复运行时,R0、R1要保持不变
执行完第3条指令时,比较结果保存在程序状态寄存器里,程序被打断时、恢复运行时,程序状态寄存器保持不变
这4条指令,读取a、b内存,程序被打断时、恢复运行时,a、b内存保持不变
内存保持不变,这很容易实现,程序不越界就可以。 所以,关键在于R0、R1、程序状态寄存器要保持不变(当然不止这些寄存器):
在处理异常前,把这些寄存器保存在栈中,这称为保存现场
在处理完异常后,从栈中恢复这些寄存器,这称为恢复现场

什么是未定义指令

未定义指令,即使"还没有定义的指令",也就是CPU不认识的指令。 很多时候,我们故意在代码里插入一些伪造的指令,故意让CPU执行到它时触发错误。 调试中的软件断电就是使用未定义指令来实现的。

什么是SVC指令

在ARM指令中,有一条指令:

SVC #VAL

它会触发一个异常。 在操作系统中,比如各类RTOS或者Linux,都会使用 SVC 指令故意触发异常,从而导致内核的异常处理函数被调用,进而去使用内核的服务。

中断和轮询

中断和轮询哪个效率高?怎样决定是采用中断方式还是采用轮询方式去实现驱动?
中断是CPU处于被动状态下来接受设备的信号,而轮询是CPU主动去查询该设备是否有请求。
如果是请求设备是一个频繁请求cpu的设备,或者有大量数据请求的网络设备,那么轮询的效率是比中断高。如果是一般设备,并且该设备请求cpu的频率比较低,则用中断效率要高一些。主要是看请求频率。

中断与异常有何区别

中断是指外部硬件产生的一个电信号从CPU的中断引脚进入,打断CPU的运行。 处理器可能完成当前指令后再处理中断。
异常是指软件运行过程中发生了一些必须作出处理的事件,CPU自动产生一个陷入来打断CPU的运行。CPU无法继续执行后续指令,需先处理异常。
异常在处理的时候必须考虑与处理器的时钟同步,实际上异常也称为同步中断,在处理器执行到因编译错误而导致的错误指令时,或者在执行期间出现特殊错误,必须靠内核处理的时候,处理器就会产生一个异常。

中断与DMA有何区别

DMA不需要CPU参与,而中断是需要CPU参与的。

为什么FIQ比IRQ要快

1. ARM的FIQ模式提供了更多的banked寄存器,r8到r14还有SPSR,而IRQ模式就没有那么多,R8,R9,R10,R11,R12对应的banked的寄存器就没有,这就意味着在ARM的IRQ模式下,中断处理程序自己要保存R8到R12这几个寄存器,然后退出中断处理时程序要恢复这几个寄存器,而FIQ模式由于这几个寄存器都有banked寄存器,模式切换时CPU自动保存这些值到banked寄存器,退出FIQ模式时自动恢复,所以这个过程FIQ比IRQ快.不要小看这几个寄存器,ARM在编译的时候,果你FIQ中断处理程序足够用这几个独立的寄存器来运作,它就不会进行通用寄存器的压栈,这样也省了一些时间。
2. FIQ比IRQ有更高优先级,如果FIQ和IRQ同时产生,那么FIQ先处理。
3. 在symbian系统里,当CPU处于FIQ模式处理FIQ中断的过程中,预取指令异常,未定义指令异常,软件中断全被禁止,所有的中断被屏蔽。所以FIQ就会很快执行,不会被其他异常或者中断打断,所以它又比IRQ快了。而IRQ不一样,当ARM处理IRQ模式处理IRQ中断时,如果来了一个FIQ中断请求,那正在执行的IRQ中断处理程序会被抢断,ARM切换到FIQ模式去执行这个FIQ,所以FIQ比IRQ快多了。
4. 另外FIQ的入口地址是0x1c,IRQ的入口地址是0x18。写过完整汇编系统的都比较明白这点的差别,18只能放一条指令,为了不与1C处的FIQ冲突,这个地方只能跳转,而FIQ不一样,1C以后没有任何中断向量表了,这样可以直接在1C处放FIQ的中断处理程序,由于跳转的范围限制,至少少了一条跳转指令。

硬中断和软中断的区别

1. 硬件中断是由硬件外设引发的, 软中断是执行中断指令产生的;
2. 硬件中断的中断号是由中断控制器提供的, 软中断的中断号由指令直接指出, 无需使用中断控制器;
3. 硬件中断可设置CPU的屏蔽位进行屏蔽, 软中断不可屏蔽;
4. 硬件中断处理程序要确保它能快速地完成任务, 这样程序执行时才不会等待较长时间, 称为上半部;
5. 软中断处理硬中断未完成的工作, 是一种推后执行的机制, 属于下半部。

系统异常(比如进入HandFault)如何定位源头

使能 Fault 相关寄存器:在系统启动或初始化代码里,开启 FAULTMASK 、 BASEPRI 、 CONTROL 等寄存器的访问,让故障状态能被捕获。

void Fault_Enable(void)

{
// 清除FAULTMASK,允许所有故障被响应(默认情况下已清除)
__set_FAULTMASK(0);
// 设置BASEPRI为0,不屏蔽任何优先级的中断/故障
__set_BASEPRI(0);
// 使能总线故障、用法故障的精确寻址(方便定位)
SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA_Msk; // 使能总线故障
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk; // 使能用法故障
}
// 在系统初始化时调用
int main(void)

{
HAL_Init();
Fault_Enable(); // 使能故障捕获
// 其他初始化...
}

目的:开启 CPU 对故障的捕获能力,确保所有类型的故障(如总线错误、内存访问越界)能触发
HardFault。

配置 HardFault 处理函数:在 HardFault 发生时,保存关键寄存器状态(栈帧)。

void HardFault_Handler(void)

{
__asm volatile (
"TST LR, #4 \n" // 检查栈使用的是MSP还是PSP
"ITE EQ \n"
"MRSEQ R0, MSP \n" // 若使用MSP,将栈指针存入R0
"MRSNE R0, PSP \n" // 若使用PSP,将栈指针存入R0
"B HardFault_Handler_C \n" // 跳转到C语言处理函数
);
}
// 定义栈帧结构体(与Cortex-M4寄存器布局对应)
typedef struct {
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;

uint32_t r12;
uint32_t lr; // 链接寄存器
uint32_t pc; // 程序计数器(故障发生位置)
uint32_t psr; // 程序状态寄存器
} HardFault_StackFrame_t;
void HardFault_Handler_C(HardFault_StackFrame_t *frame)

{
// 保存栈帧信息到全局变量(方便调试查看)
volatile HardFault_StackFrame_t fault_frame = *frame;
}

汇编部分通过 LR 寄存器判断当前栈(MSP 主栈 / PSP 进程栈),将栈指针传给 C 函数。
HardFault_StackFrame_t 结构体保存了故障发生时的关键寄存器,其中 pc 是故障代码的地址,psr 包含故障状态标志。

数组越界一定会产生Hardfault吗,不产生Hardfault的话有什么其他的

不一定。

不触发HardFault时的影响(数组越界访问会有什么后果):
越界写入可能覆盖相邻变量、堆栈或关键数据结构(如函数返回地址),导致程序行为异常或崩溃。
越界读取可能获取无效数据(如未初始化内存),导致逻辑错误

http://www.cnnetsun.cn/news/2114548.html

相关文章:

  • Linux(静态动态库,缓冲区输出问题,fork()父子进程,逻辑物理地址,僵死进程,孤儿进程,主函数参数)
  • 多层 FPC 叠构设计:柔性电路板的 “骨架工程” 与性能基石
  • WASM模块在Docker中无法热更新?手把手修复OCI镜像层绑定缺陷,实现亚秒级边缘函数灰度发布(附patch源码与e2e测试脚本)
  • 终极指南:用Ryujinx模拟器在PC上畅玩任天堂Switch游戏
  • 书匠策AI:毕业论文“智造”新引擎,解锁学术写作新姿势
  • PostgreSQL 高负载 Load Average 暴涨 | BufferMapping LWLock 锁竞争 完整排查优化实战
  • reactor
  • 2026年Geo专家综合能力与执行效果深度评测:AI时代下的信任重构
  • 深度解析开源自动化框架:从图像识别到黑盒测试的完整技术实践
  • Trippy网络诊断工具深度解析:现代网络故障排查的专业利器
  • Outfit字体:现代设计的无声革命者
  • VS Code Dev Containers 优化终极指南(2024年Linux/macOS/WSL3实测版):从22秒冷启动到3.1秒的全链路调优路径
  • Unity GPU 波前(Wavefront / Warp)对 Shader 设计的影响
  • 终极指南:3步快速解密QQ聊天记录数据库
  • 智能办公新方式:OpenClaw 一键部署与配置
  • 3分钟快速部署i茅台自动预约系统:告别手动抢购的Java神器
  • 语义分割调参避坑:你的ASPP模块dilation rate选对了吗?PyTorch实验对比告诉你答案
  • Radeon ProRender Blender插件实战指南:三步实现专业级渲染效果
  • PitchDetect:基于Web Audio API的实时音高检测完整解决方案
  • 只要中一个,就说明你已经找到了对抗加班文化的正确方法
  • VTube Studio终极指南:如何通过API打造个性化虚拟主播体验
  • KUKA iiwa机器人FRI控制实战:手把手教你用Java在Sunrise Workbench 1.7里写第一个实时运动程序
  • 基于MCP协议的AI智能体工具集成平台:Klavis AI架构与应用实践
  • Go语言构建高可用分布式任务调度框架:从Cron到Copaw的实践
  • 5分钟终极指南:如何用untrunc免费快速修复损坏的MP4/MOV视频文件
  • Open WebUI本地部署:构建企业级AI对话平台的技术实践
  • 机器学习入门指南:从零基础到实战应用
  • 3个核心问题,1个终极解决方案:G-Helper让华硕笔记本性能管理变得简单
  • 大语言模型智能体的记忆安全攻防实践
  • Elasticsearch高级搜索实战:多字段相关性得分融合技巧全解