
很多接触过STM32MP135的朋友都会有这样一个疑问这明明是一颗跑Linux的MPU为什么有人非要把 Cortex-A7 当单片机用其实这个问题的答案很简单当你只想点个灯、读个按键、验证一个外设驱动或者做一个小批量控制类产品时为它移植完整的Linux系统是一种巨大的资源浪费。这次我就在MP135DK开发板上完整走了一遍“把MP135当单片机”的流程通过SD卡脱机运行一个LED闪烁程序全程不需要连接调试器也不需要依赖上位机上电之后小板子自己就能跑起来。这篇内容适合三类读者刚入手MP135DK想快速上手的人、准备评估MPU裸机方案可行性的工程师以及对MPU/MCU跨界玩法感兴趣的嵌入式爱好者。我会把从CubeMX工程生成、编译、SD卡准备到U-Boot自动执行的完整链路都写出来顺便把我在这个过程中踩过的坑和排查思路一并整理成速查表看完你基本可以照着操作复现。1. 为什么要把MP135降级成“单片机”来用1.1 MP135和普通MCU的边界在哪STM32MP135用的是单核Cortex-A7主频最高能到1GHz级别带DDR控制器、GPU、LVDS等接口从硬件规格上看它确实是一颗标准的MPU。但MP135有个特点它在ST的MP1系列里属于入门偏实用的一颗没有复杂的多核异构调试需求裸机起来反而比MP157那一类更清爽。很多人不理解为什么要跳过Linux直接跑裸机。我的看法是MP135这颗芯片的定位本身就是“可以跑Linux的高性能MCU替代品”它的很多外设和内部总线结构跟传统STM32有千丝万缕的联系。当你的产品不需要网络协议栈、不需要多进程调度、不需要文件系统只是要做数据采集、IO控制、简单通信时上一套Linux意味着要处理启动时间、文件系统损坏、驱动适配、看门狗喂狗策略等一系列额外问题。裸机方案下程序上电就执行逻辑简单实时性可控内存占用极低对量产和维护都友好得多。1.2 为什么选择MP135DK开发板来折腾MP135DK是意法半导体的官方评估板板载资源对裸机开发足够友好ST-LINK调试器、双千兆网口、LCD接口、USB Type-C、TF卡槽都在板子上尤其重要的是这块板的启动配置非常灵活可以通过板上拨码开关强制从SD卡启动这就为“脱机运行”提供了天然的硬件基础。如果你手里还有ST官方提供的STM32CubeMP1固件包里面通常包含了针对DK板的裸机例程包括GPIO、UART、I2C这些基础外设。我们这次只需要自己创建一个简单的LED工程就行流程和你在F1/F4上点灯几乎一样只是启动方式和链接地址有一些差别。这也是我强烈建议用DK板而不是自己画底板的原因省去了一堆硬件排查的时间翻车概率小很多。1.3 裸机方案到底解决什么问题把MP135当单片机用的核心价值在于你不需要写设备树不需要构建rootfs不需要理解复杂的Linux启动链依然可以完整使用这颗芯片的外设资源。对于很多从STM32转过来的工程师这几乎是无缝切换。你依然用STM32CubeMX配置引脚依然用HAL库操作GPIO、UART、DMA唯一变化的是程序存放和加载的方式从内部Flash变成了外部DDR。另外一个容易被忽视的好处是开发效率。Linux下调试一个外设驱动你往往要反复改内核、编译、打包镜像、烧写SD卡一次循环几十分钟。裸机环境下编译一个工程只需要几秒钟通过SD卡加载运行也就是一次重启的功夫尤其在做外设寄存器级验证时这个速度优势非常明显。2. 脱机运行的核心原理从MCU思维切换到MPU思维2.1 单片机启动和MPU启动有哪些本质区别传统STM32单片机上电后内部ROM会按照Option Bytes的配置直接从内部Flash加载程序并执行整个过程不需要额外的引导程序。你在Keil里点一下Download程序就写进Flash了重新上电之后程序自动运行。这已经成了嵌入式开发者的肌肉记忆以至于很多人拿到MP135之后下意识想找片内Flash来烧程序。MP135不一样它没有可执行代码的片内Flash它的固化ROM只负责做两件事初始化基本硬件然后从外部存储介质读取FSBLFirst Stage Boot Loader到内部RAM执行。FSBL一般是TF-A或者U-Boot SPL这种级别的东西它完成DDR初始化后把第二级引导程序也就是我们常见的U-Boot主程序加载到DDR里运行。U-Boot起来之后再根据你设定的启动参数去加载Linux内核或者用户程序。也就是说在MP135上要实现“脱机运行一个裸机程序”你绕不开U-Boot这一环。用途上你可以把它理解成单片机里的Bootloader只不过它功能更强可以读取FAT文件系统、从网络加载、甚至用交互式命令行手动控制加载过程。我们的LED程序本质上就是由U-Boot把它从SD卡读到DDR里然后跳转过去执行。2.2 SD卡在整个启动流程里扮演的角色MP135DK支持多种启动介质包括SD卡、eMMC、USB、NAND等。用SD卡启动最方便的地方在于存储空间灵活、可插拔、不需要额外烧录工具。你只要把官方镜像写进一张TF卡再拨到SD启动模式开发板自己就能把FSBL和U-Boot跑起来。关键点在于官方OpenSTLinux镜像的SD卡分区是预设好的通常第一个分区是FSBL和SSBL后面会有FAT分区用于存放内核镜像和设备树最后是ext4的根文件系统。我们的裸机LED程序并不需要文件系统支持只要利用U-Boot自带的fatload命令把编译好的bin文件从FAT分区读到DDR指定地址然后用go命令跳转过去运行即可。这也是为什么我们不需要在裸机代码里移植FATFS的原因引导阶段全交给U-Boot处理了。2.3 为什么链接地址和加载地址必须一致传统单片机开发里你很少关心链接地址毕竟Flash基地址是芯片定死的。到了MP135裸机运行时程序要放在DDR里执行。STM32MP1系列DDR的起始映射地址是0xC0000000如果你的程序打算从这个地址开始跑那么编译时的链接地址Linker Script以及U-Boot加载程序的内存地址就必须统一为0xC0000000。一旦这两者不一致程序执行会立刻崩溃因为Cortex-A7的PC指针跳到一个地址上去取指令但那个地址上并不是期望的指令。这是一个非常隐蔽的坑尤其对刚从MCU转过来的人报错往往表现为“go之后串口完全无反应”或者“跑飞死机”很难联想到是地址不匹配导致的。后面的实操环节我会再强调一次。2.4 什么叫真正意义上的“脱机运行”在MCU时代“脱机”意味着程序固化在Flash里上电直接跑。在MP135上我们可以通过修改U-Boot的环境变量bootcmd让它每次上电自动执行“从SD卡加载LED程序并跳转执行”的命令这样你只需要给开发板插上电源它就会自己进入我们的裸机程序不再需要连接电脑、不需要打开终端、不需要手工输入命令效果上等同于单片机脱机运行。这里要区分一下另一种常见的加载方式也就是用STM32CubeProgrammer通过USB把程序直接烧到DDR里运行。这种方式方便是方便但必须要连电脑、装驱动、手动操作上位机而且断电后程序就没了谈不上脱机。我们这次选的路子是从SD卡引导最接近真实产品的使用形态。3. 开发环境与SD卡准备3.1 你需要准备哪些硬件和软件在动手之前先把物料清单理清楚免得做到一半发现缺东西。硬件MP135DK开发板一块、TF卡一张建议8GB以上Class10杂牌卡容易出奇怪问题、TYPE-C数据线一根用于供电和串口、如果有ST-LINK线也可以接上备用。软件STM32CubeIDE版本建议新版STM32CubeMP1固件包在CubeIDE的包管理器里下载一个串口终端工具比如MobaXterm或者PuTTY烧写SD卡的工具Windows下用Win32DiskImagerLinux/macOS下用dd命令即可。有一点值得提醒TF卡质量直接影响U-Boot读取的稳定性我踩过用老旧低速卡导致U-Boot加载一半卡死的坑换了正规的高速卡就正常了。做这类实验省什么都不能省存储卡。3.2 制作一张能启动U-Boot的SD卡最简单可靠的做法是直接使用ST官方提供的OpenSTLinux SD卡镜像注意这里不是去下载整个Linux系统安装包而是找一个sd.img格式的完整卡镜像烧进TF卡后它就已经自带了FSBL和U-Boot我们只需要借用它的引导环境。烧卡过程在Windows下就是打开Win32DiskImager选择镜像路径确认盘符点Write等待完成。Linux下用dd更直接sudo dd ifsd.img of/dev/sdX bs1M convfsync这里的/dev/sdX是你的TF卡设备名一定要确认清楚别把电脑硬盘覆盖了。烧完之后在Windows下通常只能看到一个BOOT分区其他分区因为格式问题不显示这很正常。烧好之后把开发板的启动拨码开关拨到SD启动位置插上TF卡接入USB转串口打开串口终端波特率设置115200上电。正常情况会看到U-Boot的启动日志最后进入提示符。如果能在命令行输入help或mmc list说明U-Boot环境已经准备好了。3.3 确认FAT分区编号是后续一切的基础U-Boot里操作SD卡时设备名通常是mmc 0后面的数字代表分区号。OpenSTLinux镜像的分区布局大致如下1和2号分区放FSBL备份3号分区是U-Boot主程序4号分区是FAT格式的bootfs存放内核和设备树5号之后是根文件系统。我们要把LED程序放到bootfs也就是4号FAT分区。在U-Boot命令行可以先输入fatls mmc 0:4查看一下里面的内容如果能列出一堆类似uImage和stm32mp135f-dk.dtb的文件就说明分区号找对了。这也是为后面手动加载做的一次预检省得把文件放到了错误的分区还一脸茫然。4. 用CubeMX生成LED裸机工程并编译4.1 新建工程与芯片选型打开STM32CubeIDE新建STM32 Project搜索STM32MP135。这里要注意MP135和传统的STM32芯片不太一样它没有一个固定的片上Flash型号让你选你在CubeMX里选择的是一个基于MP135的应用工程类型。选择芯片型号时对应MP135DK板载的芯片即可通常是STM32MP135D系列。创建工程时可以选空工程模板然后手动配置。CubeMX会要求你选择工程类型这里选“Empty”即可因为它支持裸机应用开发不需要进入TrustZone或Linux相关的高级配置流程。如果你在包里找不到MP135相关的选项先到STM32CubeMX的固件包管理器里下载STM32CubeMP1固件包这是最常遇到的坑。4.2 配置LED引脚和初始化代码在MP135DK的原理图上找到板载用户LED不同批次的板子引脚可能有差异这里我强烈建议你以自己手上板卡的原理图为准。假设板载LED连接在某一组GPIO上高电平点亮。在CubeMX的Pinout视图里找到对应引脚点击设置为GPIO_Output然后在System Core里的GPIO子项中配置初始输出电平、推挽模式、无上下拉、低速。生成代码之后核心的初始化代码在main.c里HAL库会帮我们处理好GPIO时钟的使能这一点和传统STM32开发完全一致。整个代码逻辑也很简单循环里把LED拉高、延时、拉低、延时如此反复即可。有些示例还会配合按键来改变闪烁频率但我们的目标是验证脱机运行所以保持最简单的闪烁逻辑就好。4.3 时钟配置里的隐藏风险这里我要花大篇幅讲一个很容易导致翻车的点在MP135上做裸机程序时钟树的配置方式和传统单片机有微妙差异。CubeMX在生成代码时会在SystemClock_Config()里重新配置PLL和各个总线时钟这在MCU上是标准操作但在MP135上如果我们的程序是由U-Boot引导执行的U-Boot在启动时已经完成DDR初始化并设置了系统时钟此时你的裸机代码又去重新配置RCC有可能引发系统运行异常。稳妥的做法是在裸机工程里要么直接沿用U-Boot已经配置好的时钟不调用SystemClock_Config()要么非常清楚自己的开发板上HSE晶振频率和U-Boot配置一致之后再重新配置。第一次做实验时我建议先注释掉SystemClock_Config()的调用让CPU沿用U-Boot的时钟设置先把LED点亮验证整体链路跑通再回头研究时钟树也不迟。4.4 链接脚本与bin文件生成到这一步和传统STM32开发差异最大的地方就来了。编译前需要确认链接脚本里程序的运行地址是0xC0000000。STM32CubeMP1固件包里其实提供了MP135的链接脚本模板注意选用DDR模式的那个。如果你完全使用CubeIDE默认生成的链接脚本可能会出现基地址不对的问题后面跑起来必挂。编译成功后会生成elf文件但是U-Boot的fatload加载裸机程序用bin文件更省事。在CubeIDE的Post-build steps里添加一行命令或者直接在终端手动执行arm-none-eabi-objcopy -O binary -S led.elf led.bin转换得到led.bin之后记得查看一下文件大小看起来应该只有几十到几百字节级别才对毕竟一个GPIO点灯程序非常短小。如果bin文件大得离谱一般说明链接地址或者段配置有问题挤进了很多无用数据。5. SD卡脱机运行实战记录5.1 把led.bin放进FAT分区把TF卡从开发板上拔下来插到电脑上Windows下通常能直接看到BOOT分区。把编译好的led.bin拷贝到根目录最好重命名成led.bin避免空格和中文。如果你的Windows不显示FAT分区可以先看一下资源管理器里有没有多余的可移动磁盘。要是看不到建议先重新插拔一次或者在磁盘管理里确认分区状态。拷贝完成后安全弹出SD卡插回开发板重新上电进入U-Boot命令行。先在U-Boot里用fatls mmc 0:4验证一下文件确实存在如果列表里能看到led.bin这一步闭环就完成了。5.2 在U-Boot命令行手动加载并执行进入提示符后按顺序执行以下命令fatload mmc 0:4 0xC0000000 led.bin dcache off icache off go 0xC0000000第一行命令是把FAT分区里的led.bin加载到DDR地址0xC0000000。U-Boot会打印读取的字节数比如“518 bytes read in 3 ms”看到这个说明文件读取成功。第二、三行关闭数据Cache和指令Cache这一步是为了避免缓存一致性导致执行异常。因为go命令本质上只是把PC指针指向目标地址它不会帮你维护Cache一致性如果Cache里残留了旧数据程序可能跑飞。这里补充一点不同版本的U-Boot对go的处理略有不同有些会自动清理Cache但保险起见手动关掉是最稳的。最后一行go就是跳转到0xC0000000执行Cortex-A7的第一条指令。如果一切正常你会看到板载LED开始以设定的频率闪烁。到这一步程序已经在MP135上以裸机方式运行了。5.3 修改bootcmd实现上电自动运行手动敲命令虽然能跑但离“脱机”还差一点。要实现插电即跑需要修改U-Boot的环境变量。在U-Boot命令行执行setenv bootcmd fatload mmc 0:4 0xC0000000 led.bin; dcache off; icache off; go 0xC0000000 saveenvsetenv是设置环境变量bootcmd是U-Boot在正常启动流程中自动执行的第一条命令。这里有分号表示顺序执行多条命令注意U-Boot环境变量的语法中分号前需要加反斜杠转义或者用单引号包裹整个命令串否则分号会被解释成命令分隔符而导致提前执行。saveenv是把修改后的环境变量写入SD卡的环境变量存储区掉电不丢失。设置完成后再重新上电观察串口日志可以看到U-Boot不再进入Linux而是直接执行我们预设的加载和跳转命令LED程序自动运行。如果不接串口线只插电源也是一样效果这才叫真正的脱机运行。调试完成之后想恢复原样就在U-Boot命令行执行env default -a saveenv这样会恢复默认的启动命令重新进入正常的Linux引导流程。5.4 对比几种加载裸机程序的方式在实际操作中你可能会看到网上各种不同的加载方式这里我把常见的几种放在一起对比一下方便你判断自己在哪个阶段该用哪种方法加载方式是否需要连接电脑是否可脱机适用场景STM32CubeProgrammer USB加载到DDR是否单步调试、快速验证寄存器配置U-Boot手动fatload go需要串口终端敲命令否调试阶段、确认程序本身是否正常U-Boot修改bootcmd自动执行仅在第一次配置时需要串口是产品原型、脱机演示、阈值测试对于刚接触MP135的人来说建议按这个顺序逐步推进先USB加载跑通代码再手动U-Boot加载最后改bootcmd实现脱机。如果一上来就直接改bootcmd中间某一步出错会很难定位问题出在编译、加载还是执行阶段。6. 常见问题与排查技巧实录6.1 从现象到根因的速查对照表我在整个过程中遇到过不少莫名其妙的情况排查到最后基本都是几个固定原因。这里整理成一张对照表你如果遇到类似现象可以直接对着查。现象表现最可能原因处理方式串口完全无输出上电没反应启动拨码开关没拨到SD启动位置检查拨码开关位置重新上电U-Boot启动一半卡死TF卡速度太慢或镜像没烧写好换一张正规高速卡重新烧写镜像fatls/fatload找不到led.bin文件没有拷贝进FAT分区或分区号错误用fatls确认分区内容检查分区编号fatload加载成功但go后无反应链接地址与加载地址不一致检查链接脚本基地址是否为0xC0000000go后跑飞或频繁死机Cache一致性问题或时钟被重配跳转前关Cache先注释SystemClock_ConfigLED不亮GPIO引脚或点亮电平极性搞反对照原理图确认引脚和有效电平修改bootcmd后依然进了Linuxenv保存失败或命令语法错误重新setenv并saveenv检查分号转义上电自动执行没问题但复位后失效上电时序或复位按钮导致环境变量丢失检查TF卡写保护开关重新saveenv6.2 三个最容易被忽略的致命细节第一个是Cortex-A7的异常向量表。和Cortex-M不同A7的向量表不是默认从0地址开始的如果你的程序里用到了中断就必须在启动代码里设置VBAR寄存器指向0xC0000000。CubeMX生成的启动文件通常会处理这个问题但如果你是自己手写的裸机工程很容易忽略这一项导致中断一触发程序就跑飞。第二个是关闭Cache的必要性。很多人觉得U-Boot里已经执行过go了程序应该会自动处理好一切其实不会。我实测中遇到过一种情况第一次加载运行没问题但修改代码后重新加载运行程序还在跑旧逻辑。排除了半天最后发现是指令Cache没有清理CPU一直是拿着缓存里的旧指令在跑。所以只要有“重新编译、重新加载”这个动作加载前关Cache就绝不能省略。第三个是U-Boot的DDR初始化不能跳过。有时候你会想既然我的裸机程序只用GPIO为什么我不能直接从内部RAM运行呢问题是MP135的片内RAM容量有限而且U-Boot在进入命令行之前已经把DDR初始化好了你完全没必要绕开它。反过来如果你想让程序脱离U-Boot从电源启动就直接运行那需要自己实现FSBL来初始化DDR复杂度会高一个数量级我建议入门阶段不要碰这条路线。6.3 先跑官方例程再改自己的代码如果你在操作过程中始终觉得哪里不对最大的建议是先把STM32CubeMP1固件包里MP135 DK板的GPIO例程完整编译、加载、运行一遍。官方例程的链接脚本、启动文件、时钟配置都是验证过的先确保这整套工具链和硬件环境是好的再回头改自己的代码。我见过不少人卡在第一步其实不是代码问题而是根本没搞明白U-Boot和CubeMX裸机工程之间的协作关系一上来就想着定制把官方的安全路径丢掉了。先用官方例程打通“编译-拷贝-加载-执行-自动运行”整条链路再着手改成自己的逻辑这才是最高效的路径。写在最后的一点经验把STM32MP135当单片机用这个思路实际操作完之后会觉得没那么玄乎核心无非是换了一套启动加载方式代码层面的写法和STM32几乎一致。我个人体会最深的是当系统里没有Linux之后调试外设变得非常直观对寄存器行为的理解也更深了。如果你后续想把这条路走得更远可以在这个基础上加UART打印顺着同样的加载方式跑一个串口回环程序再进阶可以尝试裸机下用FreeRTOS管理几个任务。每一步的调试速度都比Linux下快得多这种感觉只有亲身试过才知道。