# STM32MP157+LiteOS-M GCC极简工程:启动源码适配+Windows Make终极避坑
2026/8/30 5:02:48 网站建设 项目流程

本文适用人群:需要落地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 专属链接脚本,作为改写基底。

为何必须改写(否则"零修改"会跑不起来):

  1. 官方启动文件含CopyDataInit循环(.data 拷贝)+bl __libc_init_array(C++ 构造器),而官方 .ld 的.dataLMA≠VMA(带AT())——你若原样用官方两份,就必须保留 .data 拷贝、且要链 C 库,与本系列"极简/RTOS"目标相反;
  2. 本系列改用"单 SRAM 区、.dataLMA==VMA"布局,于是可以删掉 .data 拷贝、删掉__libc_init_array,工程更精简。

路径核对 & 与第一篇"原样用即可跑"的关系:本篇"改写"与第一篇第五节第1条"初学者原样用即可跑",取的是同一份 Cube 包 gcc 目录原生文件Templates/gcc/startup_stm32mp15xx.sTemplates/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 向量表重定向:适配 SRAM0x10000000运行地址;
  • 保留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.cSystemInit为精简版,相对官方 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 ← 实际是大写.S

Makefile 内容:

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.Slos_dispatch.S):保留大写.S,适配预处理逻辑,不修改内核源码;
  • 板级启动文件:Windows 工程统一改为大写.S,规避 Make 匹配 BUG,文件内容无任何改动、无副作用;
  • Makefile 统一单套.S编译规则,彻底杜绝混合后缀冲突。

5.4 隐藏头文件路径坑

LiteOS-M 内核源文件内部大量使用相对引用#include "xxx.h",仅配置顶层内核头文件路径无法编译通过,必须手动添加kernel/src头文件检索路径。该问题属于隐藏编译依赖,不会在增量编译暴露,仅在全量 clean 编译时触发,极易被忽略。


六、最终量产总结(8条核心规范)

  1. 严禁手动转换 MP157 启动文件,原厂 GCC 文件为最优方案(取其作模板改写,而非 ARMCC→GNU 手转);
  2. .data 拷不拷取决于链接脚本:本篇采用自定义 LMA==VMA 简化 .ld(.data直接> SRAM),故无需 .data 拷贝;若用官方 .ld(LMA≠VMA)仍必须保留拷贝——详见第一篇;无论哪种布局,.bss 清零绝对不能省略;
  3. 纯 M4 独立启动必须确保时钟正确(默认 HSI 64MHz 或显式配置 PLL),并完成 FPU/VTOR 初始化,保证硬件与 RTOS 正常运行(VTOR 用ldr r1, =g_pfnVectors符号写法最稳);
  4. M4F 内核 FPU 参数固定为fpv4-sp-d16,禁止使用 M7 的fpv5参数;
  5. 汇编后缀按场景区分:纯汇编.s、带预处理.S,原厂小写规范合规;
  6. Windows 工程折中适配:启动文件统一大写.S,规避 Make 大小写匹配坑;
  7. 链接脚本严格匹配段定义(_estack/__bss_start__/__bss_end__等符号齐全),杜绝_sidata未定义链接错误、bss 符号不匹配告警;
  8. LiteOS 工程必须遵循标准初始化流程,依赖 .bss 正确清零。

原创不易,转载请注明出处,全套适配 STM32MP157+LiteOS-M 的极简参考工程

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询