先吃颗定心丸(写给怕难的同学):本文啃的是
startup_stm32mp15xx.S——CPU 上电后跑的第一段汇编。好消息是:它基本就是 ST 官方原版,我们只改了 2 处(第 14 篇会说)。所以你真正要"懂"的不是"怎么写",而是"它到底在替你做什么"。把这件事想明白,移植时你就能从容判断"哪里能动、哪里坚决不能动"。
本文路线图(三站):
- 🚪开机读什么——上电瞬间硬件为什么先读"栈顶"、再读"复位入口",那 8 字节"入场券"。
- 🪜五步启动法——
Reset_Handler逐行对照,5 步搭好 C 环境、交棒 main。- 📒向量表电话簿——前 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(),永不回头 */还是用"入住毛坯房"来记这五步:
| 步 | 汇编动作 | 大白话 | 不做的后果 |
|---|---|---|---|
| 1 | ldr sp, =_estack | 定柜子:把"储物柜(栈)"放在 256KB SRAM 顶端0x10040000 | 之后任何函数调用压栈都会写飞 |
| 2 | CopyDataInit | 搬家具:把.data(已初始化的全局变量)从加载区搬到运行区 | 本工程全 SRAM 单区域、LMA == VMA,是幂等自拷贝(无害),官方逻辑原样保留别删 |
| 3 | FillZerobss | 扫空房:清零.bss(未初始化全局变量) | 内核堆g_liteosHeap[80KB]就躺在.bss——不清零,堆里是随机垃圾,内核LOS_MemInit当场翻车 |
| 4 | bl 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 这三个。
📌三句话先记住(看完下表再回来读一遍就懂了):
- 第 0 槽是栈顶,不是函数——最特殊的一个。
- 1=复位、14=PendSV、15=SysTick是你以后天天打交道的三个。
- 7~10、13 是 ARM 故意留的空坑,必须填 0,千万别填别的。
3.1 16 槽全表(0~15):一个都不能少
| 槽 | 异常 | CMSISIRQn | 优先级 | 干什么 / 本工程落在谁手里 |
|---|---|---|---|---|
| 0 | _estack | — | — | 栈顶(不是函数!是 MSP 初值,来自 lds 的_estack) |
| 1 | Reset | — | −3(固定) | 上电/复位入口 → 本文件Reset_Handler |
| 2 | NMI | −14 | −2(固定) | 不可屏蔽中断 → 内核HalExcNMI |
| 3 | HardFault | −13 | −1(固定) | 硬错误,所有"没人管"的错误最终兜底 → 内核HalExcHardFault |
| 4 | MemManage | −12 | 可配(默认 0) | MPU 越权访问 → 内核HalExcMemFault |
| 5 | BusFault | −11 | 可配(默认 0) | 访问不存在 / 未开时钟的总线地址 → 内核HalExcBusFault |
| 6 | UsageFault | −10 | 可配(默认 0) | 未定义指令、Thumb 位清 0、未对齐访问、除零(需开DIV_0_TRP)→ 内核HalExcUsageFault |
| 7~10 | Reserved | — | — | ARM 保留,硬件永不触发,必须填 0 |
| 11 | SVCall | −5 | 可配(默认 0) | 系统调用入口;内核注册HalExcSvcCall(见下方注) |
| 12 | DebugMonitor | −4 | 可配(默认 0) | 调试监控(M3/M4 才有,M0/M0+ 没这个槽)→Default_Handler死循环 |
| 13 | Reserved | — | — | 保留,填 0 |
| 14 | PendSV | −2 | 可配 →本工程 15(最低) | 可挂起的延迟调度,真正动手切上下文→ 内核HalPendSV |
| 15 | SysTick | −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 个槽,逐条对:
| 偏移 | 槽 | 应该看到什么 | 不对的解法 |
|---|---|---|---|
0x00 | 0 | 栈顶,小端读出 = lds 的_estack(本工程00 00 04 10→0x10040000),必须偶数 | 奇数 =_estack写错;全 0 = lds 没把.isr_vector放进去 |
0x04 | 1 | Reset_Handler地址,必须奇数(Thumb 位) | 偶数 = 函数符号没被识别成%function |
0x1C~0x28 | 7~10 | 全00000000 | 非 0 = 被人手贱填了 |
0x2C | 11 | Default_Handler(本工程实测如此,正常) | 不是"接管失败"——内核接管在运行期,见 3.1 的注 |
0x34 | 13 | 00000000 | 非 0 = 同上 |
0x38 | 14 | Default_Handler(同上,正常) | 同上 |
0x3C | 15 | SysTick_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 篇)。
参考文档
- 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:系统异常优先级寄存器与故障状态寄存器。
- ST — STM32MP157 RM0436:M4 复位引导机理与中断向量表(150 个外设 IRQ)。
- CMSIS —
core_cm4.h/stm32mp157cxx_cm4.h:IRQn_Type枚举(系统异常为负数、外设从 0 起)、__NVIC_PRIO_BITS = 4、SCB->VTOR/SHCSR/SHPR定义。 - 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等异常入口。
- 启动文件(本工程在用):
targets/board/startup_stm32mp15xx.S(166 槽向量表;相对 ST 官方 GCC 模板只改 2 处,见第 14 篇)。官方原始出处为 ST CubeMX 生成的 GCC 版startup_stm32mp15xx.s(CubeMX CM4 工程模板)。
术语表(本文出现)
| 缩写/符号 | 含义 |
|---|---|
| PC | Program Counter,程序计数器,CPU 当前执行指令地址 |
| SP / MSP | Stack 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_liteosHeap | board.c 的 80KB 外部堆,躺在 .bss 里 |
| SystemInit | CMSIS 约定入口,开 FPU/VTOR,第 4 步 bl 调用 |
bl | ARM 汇编:带返回地址的函数调用 |
| PendSV / SVC / SysTick | RTOS 调度用的三个特殊中断(任务切换扳机/系统调用/1000Hz 心跳) |
| HardFault_Handler | 硬错误(最严重异常)处理函数 |
| 异常号 / 槽号 | 向量表下标,ARM 架构定死:0=栈顶、1=Reset、…、14=PendSV、15=SysTick、16+=外设 IRQ |
IRQn | CMSIS 中断编号,系统异常为负数;换算IRQn = 槽号 − 16 |
OS_SYS_VECTOR_CNT | LiteOS-M 宏 = 16,内核g_hwiForm[IRQn + 16]的下标偏移量 |
g_hwiForm | 内核侧的中断向量镜像数组,下标 =IRQn + OS_SYS_VECTOR_CNT;运行期 VTOR 指向它(本工程 0x10006100,第 15 篇) |
| NMI | Non-Maskable Interrupt,不可屏蔽中断,优先级 −2 |
| MemManage / BusFault / UsageFault | MPU 越权 / 总线访问错误 / 用法错误(未定义指令、未对齐、除零),默认关闭并升级为 HardFault |
| DebugMonitor | 12 号槽调试监控异常,Cortex-M3/M4 才有,M0/M0+ 无 |
SHCSR | System Handler Control and State Register,置0x00070000打开 Mem/Bus/Usage 三个故障异常 |
CFSR/HFSR/MMAR/BFAR | 故障状态与故障地址寄存器(MMAR/BFAR记录出错地址),第 20/21 篇用 |
SHPR1/2/3 | System 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 槽必须偶数 |
| IPSR | xPSR的 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