news 2026/10/11 8:24:32

板级适配 · 启动文件 startup ①:五步启动与向量表 8 字节入场券

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
板级适配 · 启动文件 startup ①:五步启动与向量表 8 字节入场券

先吃颗定心丸(写给怕难的同学):本文啃的是startup_stm32mp15xx.S——CPU 上电后跑的第一段汇编。好消息是:它基本就是 ST 官方原版,我们只改了 2 处(第 14 篇会说)。所以你真正要"懂"的不是"怎么写",而是"它到底在替你做什么"。把这件事想明白,移植时你就能从容判断"哪里能动、哪里坚决不能动"。

本文路线图(三站):

  1. 🚪开机读什么——上电瞬间硬件为什么先读"栈顶"、再读"复位入口",那 8 字节"入场券"。
  2. 🪜五步启动法——Reset_Handler逐行对照,5 步搭好 C 环境、交棒 main。
  3. 📒向量表电话簿——前 16 个系统异常槽一次说全(含常被忽略的保留槽、槽号与IRQn的换算、优先级玄机、Thumb 位,以及用 objdump 验表的"照妖镜")。

内核怎么在运行期接管这张表,留到第 14 篇。本篇是板级适配系列第 5 篇的第 1 部分。

🏠零基础导读(接第 10/11 篇那栋 256KB 公寓楼):本篇讲"交房到你入住之间的验房流程"。开门先看管家(复位入口)在哪、楼顶杂物区边界(栈顶)画在哪;然后才是搬家具、扫空房、通水电、正式入住。

一、启动文件在干什么:CPU 的"开机自检脚本"

一句话理解:芯片上电不会直接进 main,而是先照一张"交房清单"(启动文件)做几件事——搭好 C 环境、配好时钟、再交棒给 main。

你按下开发板电源,CPU 不会"啪"一下跳进main()。和电脑开机先跑 BIOS 一个道理:Cortex-M 上电后,硬件自动做一件事——去向量表第 1 项取出Reset_Handler的地址,把 PC 指过去。

Reset_Handler比main()还早,是 CPU 睡醒后执行的第一段手写汇编。(PC= 程序计数器,就是 CPU 当前正在执行的指令地址,先记住这个名词就行。)

它干三件大事:① 把 C 语言运行环境搭起来(栈、全局变量);② 调SystemInit配时钟/FPU/向量表(第 09 篇);③ 调main(),把控制权交给你的业务代码。

💡为什么值得骄傲:这个文件基本是 ST 官方原版,我们只做 2 处微调(第 14 篇说)。移植的功力,往往在"哪里坚决不改"。

为什么上电要先读"栈顶"、再读"复位向量"?

芯片刚通电,CPU 是"刚睡醒、啥也不记得"的状态——不知道栈在哪、第一句代码去哪。ARM 给所有 Cortex-M 定了一条写死在硬件里的铁规矩,上电后自动完成:

上电 → 内核去"向量表开头"取两样东西: ① 头 4 字节 ──→ 塞进 SP(栈指针) = 栈顶在哪(内存"天花板") ② 紧接 4 字节 ──→ 塞进 PC(程序计数器)= 复位函数 Reset_Handler 在哪

为什么非得"先栈顶、再复位"?因为 C 语言一上来就要用栈(调函数、存局部变量、保护中断现场)……没有合法的栈指针,CPU 连"跳去执行复位函数"都可能写飞。

还是用毛坯房打比方:开发商先在你家门口钉张纸条(栈顶),写着"你的储物柜在顶楼"——你之后放任何东西都得先知道柜子在哪儿。纸条没钉好,你搬进去第一件家具就堆错地方、把家搞乱。所以硬件先钉好栈顶(开门先在楼顶杂物区画好边界线),再告诉你第一件事去复位函数报到(开门第一眼看管家在哪)。

这里的"地址 0"指"向量表基址 + 0"这个偏移。基址存在VTOR里——本工程VTOR = 0x10000000(第 10/11 篇 lds 把.isr_vector钉在 SRAM 起始),所以硬件实际从0x10000000读"栈顶值"、从0x10000004读"复位向量地址"。注意区分:0x10000000是"放栈顶的那格",不是栈顶本身;栈顶值来自 lds 的_estack。

📌一句话记住:向量表最开头 8 字节是硬件唯二认的"入场券"——第 0 项 = 栈顶、第 1 项 = 复位入口。任何 Cortex-M 都一样,换芯移植也跑不掉。

💡可复用经验:任何 Cortex-M 上电只认向量表头 8 字节:“第 0 项 = 栈顶、第 1 项 = 复位入口”。移植新芯片时,只要栈顶填对(来自 lds 的_estack)、复位指向Reset_Handler,CPU 就能醒——这是跨芯片不变的铁律。

二、Reset_Handler 五步法(逐行对照)

一句话理解:Reset_Handler 就五步:设栈顶 → 拷 .data → 清 .bss → 调 SystemInit → 调库构造器并进 main。

Reset_Handler: ldr sp, =_estack /* 1. 设栈顶 */ CopyDataInit: /* 2. .data 初值搬运 */ ... LoopFillZerobss: /* 3. .bss 清零 */ ... bl SystemInit /* 4. 时钟/FPU/VTOR(第09篇) */ bl __libc_init_array /* 5. C 库构造器 */ bl main /* 进 main(),永不回头 */

还是用"入住毛坯房"来记这五步:

步汇编动作大白话不做的后果
1ldr sp, =_estack定柜子:把"储物柜(栈)"放在 256KB SRAM 顶端0x10040000之后任何函数调用压栈都会写飞
2CopyDataInit搬家具:把.data(已初始化的全局变量)从加载区搬到运行区本工程全 SRAM 单区域、LMA == VMA,是幂等自拷贝(无害),官方逻辑原样保留别删
3FillZerobss扫空房:清零.bss(未初始化全局变量)内核堆g_liteosHeap[80KB]就躺在.bss——不清零,堆里是随机垃圾,内核LOS_MemInit当场翻车
4bl SystemInit通水电:开 FPU、向量表搬 SRAM、算SystemCoreClock=64MHz(第 09 篇)(bl= 带返回地址调用,执行完回下一行)
5__libc_init_array→main正式入住:跑完 C 库构造器进main(),永不回头LoopForever兜底——万一main返回就死循环,方便调试器断点

📌顺序玄机(铁律):设 SP → 拷 .data → 清 .bss → bl SystemInit → bl __libc_init_array → bl main。SystemInit 必须排在 C 环境就绪之后——否则它一碰全局变量就是未定义行为。这顺序不是 ST 拍脑袋,是 C 语言运行模型的要求。

三、向量表 g_pfnVectors:16 个系统槽全表

一句话理解:向量表是"中断/异常电话簿",每槽 4 字节存一个处理函数地址;但第 0 槽存的不是函数,而是栈顶。

启动文件后半段是g_pfnVectors——每个中断/异常占一个"插槽",存处理函数地址。前 16 个槽(0~15)是 Cortex-M 内核自带的系统异常——这一节把它们一次说全,别再只记得 11/14/15 这三个。

📌三句话先记住(看完下表再回来读一遍就懂了):

  1. 第 0 槽是栈顶,不是函数——最特殊的一个。
  2. 1=复位、14=PendSV、15=SysTick是你以后天天打交道的三个。
  3. 7~10、13 是 ARM 故意留的空坑,必须填 0,千万别填别的。

3.1 16 槽全表(0~15):一个都不能少

槽异常CMSISIRQn优先级干什么 / 本工程落在谁手里
0_estack——栈顶(不是函数!是 MSP 初值,来自 lds 的_estack)
1Reset—−3(固定)上电/复位入口 → 本文件Reset_Handler
2NMI−14−2(固定)不可屏蔽中断 → 内核HalExcNMI
3HardFault−13−1(固定)硬错误,所有"没人管"的错误最终兜底 → 内核HalExcHardFault
4MemManage−12可配(默认 0)MPU 越权访问 → 内核HalExcMemFault
5BusFault−11可配(默认 0)访问不存在 / 未开时钟的总线地址 → 内核HalExcBusFault
6UsageFault−10可配(默认 0)未定义指令、Thumb 位清 0、未对齐访问、除零(需开DIV_0_TRP)→ 内核HalExcUsageFault
7~10Reserved——ARM 保留,硬件永不触发,必须填 0
11SVCall−5可配(默认 0)系统调用入口;内核注册HalExcSvcCall(见下方注)
12DebugMonitor−4可配(默认 0)调试监控(M3/M4 才有,M0/M0+ 没这个槽)→Default_Handler死循环
13Reserved——保留,填 0
14PendSV−2可配 →本工程 15(最低)可挂起的延迟调度,真正动手切上下文→ 内核HalPendSV
15SysTick−1可配 →本工程 15(最低)24 位倒计时节拍,1000Hz 心跳 →SysTick_Handler

📌槽位编号是 ARM 写死的,一个都不能挪。每个异常的编号(表下标)由 ARM 架构定死:0=栈顶(特殊)、1=Reset、11=SVC、14=PendSV、15=SysTick……硬件 SysTick 到点就往"15 号槽"跳,PendSV 触发就往"14 号槽"跳。不能把 PendSV 挪到别的槽,挪了硬件就找不到它。向量表"下标 = 异常号"是铁律。

📌"落在谁手里"的真正含义:启动表占位 ≠ 运行时接管。上表写的内核函数(HalExcNMI/HalExcHardFault/HalPendSV…)不是由启动文件向量表直接指过去的。实测本工程固件,启动表里 2~6/11/12/14 槽全是Default_Handler弱别名——这完全正常,不是 bug。

打个比方:启动表像是酒店开业前的"总机占位"——每家分机暂时都接到"总台"(Default_Handler) 占位,省得空号。等内核正式入住(LOS_KernelInit→HalHwiInit),它才在运行期把自己的"分机表"g_hwiForm接上,并把VTOR这个"呼叫转移"改指向它(本工程实测在0x10006100),再按IRQn+16把真正的处理函数注册进去。这是LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT = 1的效果(第 14/15 篇展开)。

所以记住:"objdump 看启动表"和"运行时谁真正接中断"是两回事,别混。启动表看到一片Default_Handler别慌,内核接管在运行期。

📌为什么 7~10、13 是空的?ARMv7-M 在 0~15 里故意留了 5 个坑位(7/8/9/10/13),硬件永远不会往这些槽跳,规范也要求表里写 0。两个反直觉的点:
①别自作主张填Default_Handler。填了,万一真有异常跳进来会被死循环"默默吞掉",问题反而更难查;填 0 则会立刻炸,好定位。ST 原版就是填 0,照抄别改。
②换芯片时这张表要重新对齐:Cortex-M0/M0+ 是 ARMv6-M,没有 4/5/6 三个故障槽(也没有 12 号 DebugMonitor),内核系统异常只有 0~2 + 11 + 14/15。移植时"抄邻居的 startup"最容易在这里埋雷。

3.2 槽号 ↔ IRQn:一个"−16"的换算(最容易记混)

CMSIS 头文件stm32mp157cxx_cm4.h里的IRQn_Type是这么写的——系统异常是负数,外设中断从 0 开始:

NonMaskableInt_IRQn=-14,/* 2 NMI */HardFault_IRQn=-13,/* 3 Hard */MemoryManagement_IRQn=-12,/* 4 MemMan */BusFault_IRQn=-11,/* 5 Bus */UsageFault_IRQn=-10,/* 6 Usage */SVCall_IRQn=-5,/* 11 SVC */DebugMonitor_IRQn=-4,/* 12 Debug */PendSV_IRQn=-2,/* 14 PendSV */SysTick_IRQn=-1,/* 15 SysTick*/WWDG1_IRQn=0,/* 16 第一个外设槽 */

换算公式就一句:IRQn = 槽号 − 16,反过来槽号 = IRQn + 16。

  • 证据在内核源码里:kernel/arch/arm/cortex-m4/gcc/los_interrupt.c中g_hwiForm[PendSV_IRQn + OS_SYS_VECTOR_CNT] = HalPendSV;,而#define OS_SYS_VECTOR_CNT 16—— 内核那张镜像表就是拿"IRQn+16"当下标的(第 15 篇展开)。
  • 踩坑预警:NVIC_EnableIRQ()/NVIC_SetPriority()传的是IRQn,不是槽号。想配置 PendSV 却写NVIC_EnableIRQ(14)是错的——14 是DMA1_Stream3_IRQn,你打开的是 DMA 中断,不是 PendSV。要写NVIC_SetPriority(PendSV_IRQn, ...),用 CMSIS 的负数枚举,别手写裸数字。

3.3 优先级:三个"负数优先"和一个"最低优先"的玄机

固定优先级(改不了,永远最高):

异常优先级含义
Reset−3最高,上电复位
NMI−2连CPSID I关中断都拦不住它
HardFault−1除 Reset/NMI 外最高,永远可以抢占任何可配置中断

可配置优先级(0~15,数字越小越优先):4/5/6/11/12/14/15 这 7 个槽的优先级写在三个系统处理优先级寄存器里——SHPR1(0xE000ED18:MemManage/BusFault/UsageFault)、SHPR2(0xE000ED1C:SVCall)、SHPR3(0xE000ED20:SysTick/PendSV/DebugMonitor)。本工程__NVIC_PRIO_BITS = 4→ 共 16 档,值存字节的高 4 位(所以优先级 15 写成0xF0)。

LiteOS-M 的关键一手(kernel/arch/arm/cortex-m4/gcc/los_dispatch.S):

.equ OS_NVIC_SYSPRI2, 0xE000ED20 @ SHPR3 .equ OS_NVIC_PENDSV_PRI, 0xF0F00000 @ SysTick=0xF0, PendSV=0xF0 ldr r4, =OS_NVIC_SYSPRI2 ldr r5, =OS_NVIC_PENDSV_PRI str r5, [r4]

即SysTick 和 PendSV 都被设成 0xF0 = 优先级 15(最低)。为什么?调度是"最不紧急的事"——把 PendSV 压到最低,任务切换就被自动推迟到所有外设中断都处理完的最后一刻,上下文切换永远不会打断 ISR。这是所有 RTOS 的共同做法。

💡为什么专挑HalStartToRun来写 SHPR3?这三行发生在"调度器开跑前"——先把 PendSV/SysTick 落到最低档,再让第一个任务上场。首任务上场走的是bx r6直跳(不走异常返回,为什么"首任务没有断点可恢复、只能伪造栈帧再直跳",详见《切换汇编 03 篇》)。从这一刻起任务代码跑在 PSP 上,PendSV/SysTick 随时能被外设中断压着。
一句话串起来:15 号槽(SysTick)每 1ms 问一次"该不该切"→ 14 号槽(PendSV)真正动手切;11 号槽(SVC)留给系统调用,普通业务循环里用得少。三个槽是一条流水线,不是一个清单。

3.4 每个函数地址的第 0 位必须是 1(Thumb 位)

Cortex-M只跑 Thumb 指令集,向量表里存的地址bit0 = 1表示 Thumb 状态。汇编里标了.type xxx, %function后,链接器自动把符号值 +1:实测本工程Reset_Handler的符号地址是0x100002a0(偶数),而表里第 1 槽写的是0x100002a1(奇数)——差的那 1 就是 Thumb 位。

后果:如果你手工在 C 里造向量表、或写 bootloader 跳转表时忘了| 1,CPU 会认为"要切到 ARM 状态" → 立刻 UsageFault/HardFault(INVSTATE)。这是自制 bootloader 最经典的翻车点。

反过来验第 0 槽:它是数据不是函数,所以必须是偶数(本工程0x10040000)。dump 里要是看到栈顶是奇数,说明 lds 的_estack写错了,或者你把栈顶当函数填了。

3.5 三个"降级"陷阱:为什么你永远只看到 HardFault

MemManage / BusFault / UsageFault默认是关闭的——SCB->SHCSR里的MEMFAULTENA/BUSFAULTENA/USGFAULTENA复位后都是 0。一旦这三类错误发生,会被**升级(escalate)**成 HardFault 进 3 号槽。

所以新手调试时 99% 的崩溃都停在HardFault_Handler,看不出是内存越界还是总线访问。想分清,先在SystemInit里把三位全开:

SCB->SHCSR|=0x00070000;/* bit16/17/18 = MEM / BUS / USG FAULT ENA */

之后它们才会各自进自己的槽,你才能去读CFSR(可配置故障状态)、HFSR(硬错误状态)、MMAR(内存故障地址)、BFAR(总线故障地址)——这是第 20/21 篇定位死机的起点。

顺带记住 HardFault 的另外两种来源,它们连"降级"都算不上:① 在 HardFault/NMI 里再出错(优先级提升 escalation);②取向量本身失败(读槽地址时总线报错)。这两种会直接进lockup(锁定),连 Handler 都进不去,只能靠看门狗或复位解救。

3.6 表有多大、为什么必须 1KB 对齐

  • 本工程 startup 的表共166 槽= 16 个系统槽 +150 个外设槽(槽 16~165,最后一个是WAKEUP_PIN_IRQHandler,IRQn = 149)。
  • 166 × 4B = 664B。
  • ARMv7-M 规定:VTOR 要求表基址按2ⁿ 对齐,且 2ⁿ ≥ 表字节数(最小 128B)。664 向上取 2 的幂 =1024 → 必须 1KB 对齐。
  • 本工程 lds 把.isr_vector钉在0x10000000——这个地址天然满足任意 2ⁿ 对齐;实测固件里这一段恰好0x298 = 664 字节,一槽不多一槽不少。注意:对齐要求针对的是表基址的"地址",不是要你把段撑大到 1KB。
  • 换芯必查:外设槽数量随芯片变(小片可能只有 32 个 IRQ),表大小一变,对齐要求也可能从 1KB 降到 128B/256B/512B。抄别家 startup 时,只抄前 16 槽是安全的,外设槽必须换成你自己芯片的。

3.7 照妖镜:怎么验这张表真的对

arm-none-eabi-objdump-s-j.isr_vector build/liteos_m.elf

每行 16 字节 = 4 个槽,逐条对:

偏移槽应该看到什么不对的解法
0x000栈顶,小端读出 = lds 的_estack(本工程00 00 04 10→0x10040000),必须偶数奇数 =_estack写错;全 0 = lds 没把.isr_vector放进去
0x041Reset_Handler地址,必须奇数(Thumb 位)偶数 = 函数符号没被识别成%function
0x1C~0x287~10全00000000非 0 = 被人手贱填了
0x2C11Default_Handler(本工程实测如此,正常)不是"接管失败"——内核接管在运行期,见 3.1 的注
0x341300000000非 0 = 同上
0x3814Default_Handler(同上,正常)同上
0x3C15SysTick_Handler(内核同名弱定义在链接期生效,实测0x1000331d)是Default_Handler= 心跳没接上,任务全都不动

想看运行时硬件实际认哪张表,GDB 里两行搞定:

p/x *(uint32_t*)0xE000ED08 /* SCB->VTOR:进 LOS_KernelInit 前 = 0x10000000;跑起来后应指向内核 g_hwiForm(本工程实测 0x10006100) */ x/20xw 0x10000000 /* 直接把启动表前 20 个槽打出来 */

💡死机时先看 IPSR,比看"停在哪个 Handler"更快。xPSR的 IPSR 位域(bit 8:0)在异常里就等于异常号:0=线程模式、3=HardFault、15=SysTick、≥16 则是IRQn+16(如 37 = IRQn 21 = FDCAN1_IT1)。GDB 里p/x $xpsr & 0x1FF一行就能定位"卡在几号槽",这是第 20/21 篇追溯现场的第一步。

3.8 回头看两个"最特殊"

  • 第 0 项不是函数,是栈顶_estack。硬件先取它装进 SP,再从第 1 项取 PC。头 4 字节是数据不是代码——新手最易看走眼。
  • 1KB 对齐:来源见 3.6(166 槽 × 4B = 664B → 向上取 2 的幂 = 1024)。lds 把.isr_vector钉在0x10000000(天然对齐),SystemInit里VTOR = 0x10000000一行就满足。

💡PendSV / SVC / SysTick 什么来头?它们不是普通外设中断,是给 RTOS 调度预留的"特殊中断":SysTick1000Hz 心跳(到点提醒该不该切任务),PendSV真正动手切任务的扳机(优先级压到最低,等紧要中断退了再切),SVC系统调用入口。LiteOS-M 靠前俩跑起多任务;它们占固定槽位,运行期由内核经g_hwiForm接管(下篇与第 15 篇)。


本文一句话总结(建议收藏):上电先读 8 字节"入场券"(栈顶 + 复位入口)→Reset_Handler五步搭好 C 环境、交棒 main → 向量表是"电话簿",前 16 槽系统异常里你只常打交道 1/14/15 三个,7~10/13 是空坑别碰,启动表里看到一片Default_Handler别慌,那是占位,内核运行期才经g_hwiForm真正接管(第 14 篇)。

参考文档

  1. ARM — 《ARMv7-M Architecture Reference Manual》:
    • B1.5Reset behaviour:上电从向量表基址(VTOR 设定)依次取 MSP 初值与复位向量。
    • B1.5 / B3.4Exception model:异常号 0~15 的系统异常清单、7~10/13 保留、优先级(Reset −3 / NMI −2 / HardFault −1)与优先级升级(escalation → HardFault → lockup)。
    • B3.2.2VTOR:向量表基址须按 2ⁿ 对齐且 2ⁿ ≥ 表字节数(最小 128B)。
    • B3.4SHPR1/2/3、SHCSR、CFSR/HFSR/MMAR/BFAR:系统异常优先级寄存器与故障状态寄存器。
  2. ST — STM32MP157 RM0436:M4 复位引导机理与中断向量表(150 个外设 IRQ)。
  3. CMSIS —core_cm4.h/stm32mp157cxx_cm4.h:IRQn_Type枚举(系统异常为负数、外设从 0 起)、__NVIC_PRIO_BITS = 4、SCB->VTOR/SHCSR/SHPR定义。
  4. LiteOS-M 内核源码:
    • kernel/arch/arm/cortex-m4/gcc/los_interrupt.c:HalHwiInit()里g_hwiForm[IRQn + OS_SYS_VECTOR_CNT] = …的注册逻辑。
    • kernel/arch/arm/cortex-m4/gcc/los_dispatch.S:HalStartToRun设SHPR3 = 0xF0F00000后bx r6五步直跳启动首任务;HalPendSV上下文切换。
    • kernel/arch/arm/cortex-m4/gcc/los_exc.S:HalExcHardFault等异常入口。
  5. 启动文件(本工程在用):targets/board/startup_stm32mp15xx.S(166 槽向量表;相对 ST 官方 GCC 模板只改 2 处,见第 14 篇)。官方原始出处为 ST CubeMX 生成的 GCC 版startup_stm32mp15xx.s(CubeMX CM4 工程模板)。

术语表(本文出现)

缩写/符号含义
PCProgram Counter,程序计数器,CPU 当前执行指令地址
SP / MSPStack Pointer / Main Stack Pointer,主栈指针,函数调用压栈/出栈用
Reset_Handler复位处理例程,芯片上电后执行的第一段手写汇编
g_pfnVectors启动文件里的向量表数组(每槽存处理函数地址)
VTOR向量表偏移寄存器,指向当前向量表(第 09 篇设 0x10000000)
.isr_vector向量表段,lds 钉在 SRAM 起始 0x10000000、1KB 对齐
_estack栈顶初值 = 0x10040000,启动第 1 步装进 SP
.data已初始化全局变量段,CopyDataInit 搬运(本工程自拷贝)
.bss未初始化全局变量段,FillZerobss 清零(含 80KB 内核堆)
g_liteosHeapboard.c 的 80KB 外部堆,躺在 .bss 里
SystemInitCMSIS 约定入口,开 FPU/VTOR,第 4 步 bl 调用
blARM 汇编:带返回地址的函数调用
PendSV / SVC / SysTickRTOS 调度用的三个特殊中断(任务切换扳机/系统调用/1000Hz 心跳)
HardFault_Handler硬错误(最严重异常)处理函数
异常号 / 槽号向量表下标,ARM 架构定死:0=栈顶、1=Reset、…、14=PendSV、15=SysTick、16+=外设 IRQ
IRQnCMSIS 中断编号,系统异常为负数;换算IRQn = 槽号 − 16
OS_SYS_VECTOR_CNTLiteOS-M 宏 = 16,内核g_hwiForm[IRQn + 16]的下标偏移量
g_hwiForm内核侧的中断向量镜像数组,下标 =IRQn + OS_SYS_VECTOR_CNT;运行期 VTOR 指向它(本工程 0x10006100,第 15 篇)
NMINon-Maskable Interrupt,不可屏蔽中断,优先级 −2
MemManage / BusFault / UsageFaultMPU 越权 / 总线访问错误 / 用法错误(未定义指令、未对齐、除零),默认关闭并升级为 HardFault
DebugMonitor12 号槽调试监控异常,Cortex-M3/M4 才有,M0/M0+ 无
SHCSRSystem Handler Control and State Register,置0x00070000打开 Mem/Bus/Usage 三个故障异常
CFSR/HFSR/MMAR/BFAR故障状态与故障地址寄存器(MMAR/BFAR记录出错地址),第 20/21 篇用
SHPR1/2/3System Handler Priority Registers(0xE000ED18/1C/20),配置 4/5/6、11、12/14/15 号槽优先级
__NVIC_PRIO_BITS优先级位数,本工程 = 4 → 16 档(0~15),值存字节高 4 位(15 写作 0xF0)
固定优先级Reset −3 / NMI −2 / HardFault −1,改不了,永远高于可配置优先级
Thumb 位向量表函数地址 bit0 必须 = 1(Cortex-M 只跑 Thumb);栈顶第 0 槽必须偶数
IPSRxPSR的 bit 8:0,异常中等于异常号,看它就知道卡在几号槽
Default_Handler启动文件里的默认弱处理函数,内部是死循环(保留现场给调试器);本工程启动表 2~6/11/12/14 槽链接后都是它,属正常
lockup内核锁定状态:HardFault 里再出错或取向量失败时进入,连 Handler 都进不去

系列导航
上一篇:第 12 篇 · 链接脚本 lds(下)② 接口契约、ASSERT 与照妖镜 |本篇:第 13 篇 · 启动文件 startup ① 五步启动与向量表 8 字节入场券| 下一篇:第 14 篇 · 内核如何接管中断

板级适配与应用 07–25:07 工程全景 | 08 BSP | 09 SystemInit | 10 lds上 | 11 lds下·解剖 | 12 lds下·照妖镜 |13 启动文件| 14 接管中断 | 15 target_config上 | 16 FPU栈档 | 17 改错速查 | 18 LED详解 | 19 LED实战 | 20 los_exc | 21 FPU栈帧 | 22 八环节 | 23 全文件对账 | 24 六类判据 | 25 经验增删改留

代码仓库:stm32mp157-liteos-m @ Gitee

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 8:22:32

WzComparerR2 实战:WZ 文件解析、资源导出与版本对比

简介:WzComparerR2是一款面向冒险岛玩家、MOD制作者与游戏数据分析爱好者的免费WZ文件读取与比较工具,用于解析客户端base.wz中的地图、装备、技能、怪物属性等核心数据,解决游戏数据难以直观查看与版本差异对比的问题。资源包共32个文件&…

作者头像 李华
网站建设 2026/10/11 8:21:32

趣博思 AI|避开毕业论文返修陷阱,用结构化思维完成高质量学位文稿

写毕业论文最折磨人的不是第一次动笔,而是反复返修。很多同学花费数月写完初稿,交到导师手中,收到的修改意见往往是:研究问题不清晰、文献综述缺少评述、论证逻辑断层、创新点不突出、格式错误多。反复修改、多次返修,…

作者头像 李华
网站建设 2026/10/11 8:21:28

MyBatis自动生成Mapper与XML:选型、底层原理与避坑指南

如果你们项目里还在手工维护mapper目录下的 XML 文件,我劝你先停下来把这篇看完。作为一个被 MyBatis 折磨过也被它救过的人,我可以负责任地说,mybatis自动生成mapper和xml文件这件事,早该成为团队的基本习惯,而不是某…

作者头像 李华
网站建设 2026/10/11 8:20:09

云平台矩阵:跨浏览器测试的自动化方案与实践

做前端的人最怕听到一句话:“我这边浏览器打开是好的啊。”用户不会告诉你他用的是哪个浏览器哪个版本,也不会告诉你是在Windows上还是在MacBook上,更不会告诉你屏幕是多宽。跨浏览器测试这件事,说得实在一点,就是一场…

作者头像 李华
网站建设 2026/10/11 8:18:47

【STM32C5教程】01. STM32CubeMX2 安装与STM32C5闪灯

欢迎关注 youcansxidian【嵌入式软件AI编程】专栏 【STM32C5教程】01. STM32CubeMX2 安装与STM32C5闪灯 1. 引言 STM32CubeMX2 是 ST 推出的新一代图形化配置工具。与旧版 CubeMX 相比,它最显著的变化是原生支持 CMake 工程和 VS Code 工作流,生成速度也…

作者头像 李华
网站建设 2026/10/11 8:17:34

极域卸载工具JyTeacherUnTools:彻底清除卸载残留的完整方案

简介:极域完全卸载工具JyTeacherUnTools是一款专门面向学校机房管理员、信息技术教师和运维人员的极域教育软件卸载组件。它用于彻底移除极域电子教室及JyTeacherTools在系统中留下的注册表项、配置文件、临时数据和动态链接库残件,避免因卸载不净而出现…

作者头像 李华