本文适用人群:需要落地MP157 M4 GCC工程、适配LiteOS-M、VSCode+Makefile开发、Windows编译频繁报错的开发者
🔗 GitHub/Gitee 源码下载地址:stm32mp157‑liteos‑‑m v3.0‑native‑pendsv
核心结论:MP157 M4有专属的极简启动流程与编译规范,适配SRAM-only架构+RTOS运行需求,同时可彻底解决Windows Make玄学编译报错,提供一套可量产的工程规范与参考实现,按规范落地即可稳定运行。
配套说明:本文与《第一篇·原理避坑篇》为一组。第一篇已论证"禁用 Keil→GNU 手转启动文件、用 Cube 包 gcc 原生文件",并指出".data 拷不拷取决于链接脚本"。本文即采用"自定义 LMA==VMA 单 SRAM 区"简化布局,因此"删除 .data 拷贝"是合法做法,与第一篇口径完全统一。
一、工程基础文件提取(基于官方文件改写,非零修改)
直接从正点原子 V1.2.0 固件 gcc 目录取两份官方原生文件作为模板,再按本系列"LMA==VMA 极简布局"改写,不是原样零修改:
startup_stm32mp15xx.s:M4 专属 GCC 纯汇编启动文件(原厂小写后缀),作为改写基底;stm32mp15xx_m4.ld:M4 内核 SRAM 专属链接脚本,作为改写基底。
为何必须改写(否则"零修改"会跑不起来):
- 官方启动文件含
CopyDataInit循环(.data 拷贝)+bl __libc_init_array(C++ 构造器),而官方 .ld 的.data是LMA≠VMA(带AT())——你若原样用官方两份,就必须保留 .data 拷贝、且要链 C 库,与本系列"极简/RTOS"目标相反; - 本系列改用"单 SRAM 区、
.dataLMA==VMA"布局,于是可以删掉 .data 拷贝、删掉__libc_init_array,工程更精简。
路径核对 & 与第一篇"原样用即可跑"的关系:本篇"改写"与第一篇第五节第1条"初学者原样用即可跑",取的是同一份 Cube 包 gcc 目录原生文件(
Templates/gcc/startup_stm32mp15xx.s与Templates/gcc/linker/stm32mp15xx_m4.ld),取文件路径完全相同,区别只在"改不改":
- 第一篇"原样用" = 不改动启动文件、保留官方
.data拷贝与bl __libc_init_array,并链system_stm32mp1xx.c,可直接跑;- 本篇"改写" = 以同一份文件为模板,按 LMA==VMA 极简布局删改几处(删
.data拷贝 / 删bl __libc_init_array/bl SystemInit按需),更精简、适配 RTOS。
务必核对:取的是gcc目录(非 arm/iar),否则语法体系不对。
下面第二节给出改写后的完整源码
图1:MP157 M4 启动文件 —— 原样用 vs 改写
注:本篇图序承接第一篇,从图11起编。
二、适配LiteOS-M的标准Reset_Handler(最终修正版)
摒弃网上错误的裸机极简写法,完全适配MP157 M4架构与LiteOS-M内核运行机制。本版基于官方startup_stm32mp15xx.s改写,相对原厂删两处、留三处、按需一处:
- ❌删除
CopyDataInit循环:本布局.data的 LMA==VMA,无需拷贝(详见配套 .ld); - ❌删除
bl __libc_init_array:极简/LiteOS 工程不需要 C++ 构造器,留着反而链接报缺符号; - ✅保留VTOR 向量表重定向:适配 SRAM
0x10000000运行地址; - ✅保留FPU 硬件浮点使能:避免浮点指令触发硬件异常;
- ✅保留.bss 清零:RTOS 内核运行必备;
- ⚙️按需
bl SystemInit:见下方"系统初始化"说明。
核心设计要点
- ❌ 不拷贝 .data 段:本布局 M4 SRAM 单区运行,
.data的 LMA==VMA,标准 ELF 加载器会把初值写到该同一地址,无需启动拷贝(若改用官方 LMA≠VMA 的 .ld,则必须保留拷贝——见第一篇); - ✅ 强制清零 .bss 段:RTOS 内核运行必备;
- ✅ VTOR 向量表重定向:用
ldr r1, =g_pfnVectors符号写法,自动跟随链接脚本的向量表位置; - ✅ FPU 硬件浮点使能:避免浮点指令触发硬件异常;
- ⚙️ 按需保留 SystemInit:适配纯 M4 独立启动场景(保留则需链
system_stm32mp1xx.c,详见下文); - ✅ 剔除冗余 C 库初始化(已删
__libc_init_array),工程更精简。
完整启动汇编源码
Reset_Handler: /* 0. 设置栈指针:MSP = _estack * 手动起跑会绕过硬件复位读向量表,必须手动配置栈指针 */ ldr sp, =_estack /* 1. 设置向量表偏移:SCB->VTOR = g_pfnVectors * SRAM运行必须重定向,否则中断跳转异常。 * 用 g_pfnVectors 符号(而非写死常数),自动跟随 .ld 的向量表位置: * 本布局向量表在 0x10000000;若换官方 .ld(向量表在 0x00000000)也无需改此行 */ ldr r0, =0xE000ED08 /* SCB->VTOR 地址 */ ldr r1, =g_pfnVectors str r1, [r0] /* 2. 使能 FPU:CPACR.CP10|CP11 = 11 * 未使能浮点会触发 UsageFault 卡死 */ ldr r0, =0xE000ED88 /* CPACR 地址 */ ldr r1, [r0] orr r1, r1, #(0xF << 20) /* CP10/CP11 全使能 */ str r1, [r0] dsb isb /* 3. 清零 .bss 段(上电 SRAM 数据随机,RTOS 必备) * 重点:本布局 MP157 M4 无需 .data 拷贝(LMA==VMA) */ ldr r0, =__bss_start__ ldr r1, =__bss_end__ movs r2, #0 bss_loop: cmp r0, r1 bcs bss_done str r2, [r0], #4 b bss_loop bss_done: /* 4. 系统初始化(按需调用,与第一篇口径一致) * - 保留此行:需把 Cube 包 Templates/system_stm32mp1xx.c 链进工程, * 由它提供 SystemInit 实现(仅完成FPU使能、VTOR向量表配置、EXTI外设中断掩码清零等最小初始化,**不关闭Cortex-M内核全局中断,也不配置系统时钟**); * - HSI 默认 64MHz 纯 M4 场景:可直接删除此行, * 前面 FPU+VTOR 已够,免得链接报 undefined reference to SystemInit; * - PLL 高频场景:需另行配置 RCC(HAL_RCC 或寄存器直写),与是否调用 SystemInit 无关 */ bl SystemInit /* 纯 HSI 64MHz:可删除此行;PLL 高频:另行配置 RCC,与 SystemInit 无关 */ /* 5. 跳转主函数,启动 LiteOS 内核 */ bl main b . /* 防御死循环 */图2:实战版 Reset_Handler —— 删 2 / 留 3 / 按需 1(共六步)
系统初始化文件来源:Cube 包
STM32Cube_FW_MP1_V1.2.0/Drivers/CMSIS/Device/ST/STM32MP1xx/Source/Templates/system_stm32mp1xx.c。保留bl SystemInit时务必把它加入编译文件列表;走纯 HSI 场景删掉bl SystemInit后则无需此文件。
📝 补充说明(消除源码对照困惑):本工程
system_stm32mp1xx.c的SystemInit为精简版,相对官方 ST 文件省略了EXTI_C2->IMR1..EMR3六个寄存器的中断掩码清零;原因是 M4 复位后这些寄存器复位值即全 0(纯 M4 独立启动无 A7 干预),该清零属幂等保险,省略不影响行为。其余 FPU 使能、VTOR 重定向与官方一致,且同样不配置系统时钟、不关闭Cortex-M内核全局中断。若你直接用官方原版system_stm32mp1xx.c(含 HAL),则 EXTI 清零会照常执行,行为与上文一致。
配套链接脚本规范(完整可链接版,关键符号全部对齐)
下面是一份完整、可直接用于链接的.ld(单 SRAM 区、.dataLMA==VMA)。注意:第一节说的"官方文件"只是模板,这份才是你工程真正使用的脚本——它相对官方stm32mp15xx_m4.ld做了精简(合并为单 SRAM 区、去掉AT())。
/* stm32mp15xx_m4.ld — 自定义单 SRAM 区布局(LMA == VMA) * 改编自 Cube 包官方 stm32mp15xx_m4.ld,精简为 M4 极简裸机/RTOS 布局: * - 官方原版把 m_interrupts / m_text / m_data 分三段、.data 带 AT() 使 LMA≠VMA; * - 本脚本合并为单一 SRAM 区,.data 的 LMA==VMA,启动文件可省 .data 拷贝。 */ MEMORY { SRAM (rwx) : ORIGIN = 0x10000000, LENGTH = 256K /* M4 SRAM 256KB */ } _estack = ORIGIN(SRAM) + LENGTH(SRAM); /* SRAM 顶端,作为 MSP 初值;布局无关,跟随 MEMORY 定义 */ ENTRY(Reset_Handler) SECTIONS { /* 向量表:放在 SRAM 首地址 0x10000000,VTOR 指向此处 */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) . = ALIGN(4); } > SRAM /* 代码与只读数据 */ .text : { . = ALIGN(4); *(.text) *(.text*) *(.rodata) *(.rodata*) . = ALIGN(4); _etext = .; } > SRAM /* .data 段:LMA == VMA(同一 SRAM 区),无需 _sidata、无需启动拷贝 */ .data : { . = ALIGN(4); _sdata = .; *(.data) *(.data*) . = ALIGN(4); _edata = .; } > SRAM /* .bss 段:启动文件统一清零(汇编/链接脚本符号完全对齐,无链接告警) */ .bss : { . = ALIGN(4); __bss_start__ = .; _sbss = .; *(.bss) *(.bss*) *(COMMON) . = ALIGN(4); __bss_end__ = .; _ebss = .; } > SRAM }图3:单 SRAM 区 .ld 布局 —— LMA==VMA 极简(0x10000000 ~ 0x10040000,256KB 一区到底)
报错避坑:若链接出现
undefined reference to '_sidata',说明你混用了"官方启动文件(用_sidata做 .data 拷贝)+ 本简化 .ld(不定义_sidata)"。根因是 .s 与 .ld 符号没对齐——按上文把官方启动文件的CopyDataInit循环删掉即可(本 .ld 本就不需要它)。
三、LiteOS-M 标准main函数模板(固定规范)
启动文件适配后,必须遵循LiteOS-M官方初始化流程,不可裸机直接运行:
#include"los_kernel.h"intmain(void){/* 内核初始化(依赖 bss 正确清零) */UINT32 ret=LOS_KernelInit();if(ret==LOS_OK){/* 任务创建、外设初始化、中断配置 */LOS_Start();/* 启动任务调度器 */}while(1);}图4:LiteOS-M main 标准初始化三步(依赖 .bss 正确清零)
四、最终标准编译命令(仅2条,无冗余、参数修正)
全程使用fpv4-sp-d16(Cortex-M4F 专属,纠正全网 fpv5 错误,fpv5 仅适配 M7 内核),仅保留无FPU/有FPU两条标准命令。
文件后缀说明:按本篇第五节 5.3 工程规范,Windows 下板级启动文件统一使用大写.S(规避 mingw32-make 的 vpath 小写匹配缺陷,详见 5.2 节)。
# 1、普通无FPU编译(基础裸机工程,文件后缀按 5.3 规范统一为 .S)arm-none-eabi-gcc-c-mcpu=cortex-m4-mthumbstartup_stm32mp15xx.S-ostartup.o# 2、带FPU硬件加速编译(RTOS工程必备,标准正确参数)# M4F专属 FPv4-SP-D16,严禁使用 M7 的 FPv5 参数arm-none-eabi-gcc-c-mcpu=cortex-m4-mthumb-mfpu=fpv4-sp-d16 -mfloat-abi=hard startup_stm32mp15xx.S-ostartup.o图5:M4F vs M7 FPU 参数对照(fpv4-sp-d16 vs fpv5,MP157 M4 唯一正确)
五、移植终极踩坑复盘(.s/.S 玄学报错全解析:跨平台机制 + Windows 迁移高发)
5.1 汇编后缀 .s/.S 核心争议:原厂规范 vs Windows工程实践
折中说明:原厂文件默认小写.s,符合 GCC 纯汇编语法规范;GNU Make 的 vpath/后缀替换为大小写敏感字符串匹配(全平台行为完全一致);Windows 下因文件系统大小写不敏感,文件‘明明存在’却报找不到,迷惑性最强、迁移最高频,工程落地需要折中适配,二者并不矛盾——前者是"应该是什么",后者是"工程上怎么避免踩坑"。
语法标准定义:
- 小写
.s:纯汇编文件,无预处理,适合 startup 启动文件(原厂规范); - 大写
.S:带 C 预处理,支持宏与头文件,适合 LiteOS 内核汇编文件。
5.2 Windows Make 致命玄学报错根源
报错现象:
make: *** No rule to make target 'xxx/startup_stm32mp15xx.o', needed by 'liteos_m.elf'. Stop.根因:Windows 文件系统大小写不敏感,但 GNU Make vpath 匹配为严格字符串大小写敏感,小写%.s无法匹配实际后缀为.S的文件,导致编译规则失效;Linux/Mac 同样会报错(根因差异见 5.2.4),Windows 的特殊性仅在于文件系统能正常打开该文件、报错更具迷惑性。
5.2.1 最小复现案例
构造目录结构:
demo/ ├── Makefile └── src/ └── startup_stm32mp15xx.S ← 实际文件后缀大写 .S用 PowerShell 验证实际文件名(Windows 资源管理器看不出大小写差异):
PSD:\demo>Get-ChildItemsrc|Select-ObjectName Name----startup_stm32mp15xx.S ← 实际是大写.SMakefile 内容:
vpath %.s src startup.o: startup_stm32mp15xx.s arm-none-eabi-gcc -c $< -o $@执行 mingw32-make:
PS D:\demo> mingw32-make make: *** No rule to make target 'startup_stm32mp15xx.s', needed by 'startup.o'. Stop.文件明明存在,make 却报"找不到",玄学报错由此产生。
5.2.2 三种验证方法
注意:下面三种方法仅用于验证"文件存在但 make 找不到"的根因,均不是工程落地建议。Windows 工程的标准规范统一见 5.3 节(启动文件统一大写
.S,彻底规避大小写匹配坑)。
| 验证方法 | 操作 | 预期结果 |
|---|---|---|
| 方法 1:改 vpath 模式 | 把vpath %.s src改为vpath %.S src,重新 make | ✅ 编译成功 |
| 方法 2:改文件后缀 | 把实际文件startup_stm32mp15xx.S重命名为.s,重新 make | ✅ 编译成功 |
| 方法 3:改 Makefile 依赖 | 把startup.o: startup_stm32mp15xx.s改为%.o: %.S,重新 make | ✅ 编译成功 |
5.2.3 与文件系统能否访问无关
注意一个反直觉的现象:Windows 文件系统大小写不敏感,下面这段 C 代码可以正常打开文件:
FILE*f1=fopen("src/startup_stm32mp15xx.s","r");// 实际文件是大写 .SFILE*f2=fopen("src/startup_stm32mp15xx.S","r");// 两者都能成功打开同一个文件但 make 的 vpath 不是用文件系统去列目录再匹配,而是先用%.s字符串过滤候选名单,字符串层面%.s不匹配startup_stm32mp15xx.S,候选名单为空,根本没机会到文件系统那一步。
这就是 Windows 下 vpath"找不到文件"的根因所在。
图6:Windows vpath “找不到文件” 根因 —— FS 不敏感 != Make 字符串敏感(两层机制分离)
5.2.4 Linux/Mac 对照实验
同样的 Makefile 和文件名(实际后缀.S),在 Linux 下执行 make:
$ make make: *** No rule to make target 'startup_stm32mp15xx.s', needed by 'startup.o'. Stop.Linux 同样会报错,但根因不同——Linux 文件系统大小写敏感,根本就没有startup_stm32mp15xx.s这个文件(只有.S)。
所以 Linux 下你早就会在写 vpath 时意识到要匹配大小写;而 Windows 下因为文件系统能访问,开发者误以为"vpath 也应该能匹配",结果栽在 make 的字符串匹配层。
5.3 量产级统一工程规范
- 内核汇编文件(
los_exc.S、los_dispatch.S):保留大写.S,适配预处理逻辑,不修改内核源码; - 板级启动文件:Windows 工程统一改为大写
.S,规避 Make 匹配 BUG,文件内容无任何改动、无副作用; - Makefile 统一单套
.S编译规则,彻底杜绝混合后缀冲突。
5.4 隐藏头文件路径坑
LiteOS-M 内核源文件内部大量使用相对引用#include "xxx.h",仅配置顶层内核头文件路径无法编译通过,必须手动添加kernel/src头文件检索路径。该问题属于隐藏编译依赖,不会在增量编译暴露,仅在全量 clean 编译时触发,极易被忽略。
六、最终量产总结(8条核心规范)
- 严禁手动转换 MP157 启动文件,原厂 GCC 文件为最优方案(取其作模板改写,而非 ARMCC→GNU 手转);
- .data 拷不拷取决于链接脚本:本篇采用自定义 LMA==VMA 简化 .ld(
.data直接> SRAM),故无需 .data 拷贝;若用官方 .ld(LMA≠VMA)仍必须保留拷贝——详见第一篇;无论哪种布局,.bss 清零绝对不能省略; - 纯 M4 独立启动必须确保时钟正确(默认 HSI 64MHz 或显式配置 PLL),并完成 FPU/VTOR 初始化,保证硬件与 RTOS 正常运行(VTOR 用
ldr r1, =g_pfnVectors符号写法最稳); - M4F 内核 FPU 参数固定为
fpv4-sp-d16,禁止使用 M7 的fpv5参数; - 汇编后缀按场景区分:纯汇编
.s、带预处理.S,原厂小写规范合规; - Windows 工程折中适配:启动文件统一大写
.S,规避 Make 大小写匹配坑; - 链接脚本严格匹配段定义(
_estack/__bss_start__/__bss_end__等符号齐全),杜绝_sidata未定义链接错误、bss 符号不匹配告警; - LiteOS 工程必须遵循标准初始化流程,依赖 .bss 正确清零。
原创不易,转载请注明出处,全套适配 STM32MP157+LiteOS-M 的极简参考工程