很多人第一次拿到STM32MP157F-DK2,第一反应是先折腾A7双核,跑个Linux、点个屏幕、看看桌面。但真正把这块板子的价值发挥出来,绕不开那个藏在里面的Cortex-M4核。M4核负责实时控制、高速IO、低延迟中断,跟A7上跑Linux做业务是完全互补的两条路线。于是我一开始的想法很简单:用STM32CubeProgrammer,把M4的固件烧进去,像以前玩STM32F103那样“Download”一下就跑起来。结果真上手才发现,MP1的M4核不是你想烧就能烧的,连“烧录”这个概念都和单片机不太一样。这篇文章就围绕STM32MP157F-DK2上用STM32CubeProgrammer给M4 Core烧录这件事,把原理、步骤、坑和部署方式都完整讲一遍。
1. M4核在MP1里的真实角色:为什么它不能“一键烧录”
1.1 A7与M4的分工,以及你该把什么代码放M4上
STM32MP157F-DK2使用的是STM32MP157F芯片,双核Cortex-A7负责跑操作系统,Cortex-M4则是独立的实时处理单元。A7上面跑的是OpenSTLinux这类完整Linux发行版,进程调度、文件系统、网络协议栈这些都是它的主场。但Linux天然不适合做硬实时任务,中断响应延迟、调度不确定性,在工控、电机控制、音频采集这些场景里就是硬伤。M4核的存在就是为了接住这些“不能等”的活,它可以直接访问外设寄存器,跑裸机或者FreeRTOS,响应延迟可以做到微秒级甚至更低。
所以你在做项目规划的时候,要先想清楚哪些代码放A7,哪些放M4。比如A7上做用户界面、网络服务、日志存储,M4上做传感器数据采集、PWM输出、编码器读取、通信协议解析。现实里的典型做法是:M4跑一个状态机,处理实时性要求高的外设事件,然后通过共享内存和A7交换数据。理解了这一点,你就知道为什么M4核的固件加载方式和A7不一样——它不是启动链里的一环,而是由A7侧“拉起来”的一个协处理器。
1.2 M4核没有独立启动能力:烧录与加载的本质区别
这是新手最容易懵的地方。STM32MP157的启动流程走的是BootROM -> TF-A -> U-Boot -> Linux,整个链条里默认没有M4的位置。M4核没有自己的BootROM启动入口,它的代码要能被A7侧的软件主动加载到M4专用的SRAM(地址从0x10000000开始),然后让M4从那里执行。这跟普通单片机从内部Flash取指是完全不同的逻辑。
所以“Flashing M4 Core”这个说法,严格拆开有两种含义。第一种是用STM32CubeProgrammer通过ST-LINK,把M4固件直接下载到M4 SRAM并运行,这适合调试阶段,掉电就没了。第二种是把M4固件文件持久化放到SD卡、eMMC或NOR Flash里,然后由U-Boot或Linux的remoteproc机制在每次上电时加载运行,这才是真正意义上的“部署”。很多人烧录完发现板子重启后M4不跑,就是因为只做了第一种,或者做了第二种但缺少A7侧的加载配置。这篇文章会把这两条路都走一遍。
2. 动手前的准备工作:工具链、固件材料和板卡接线
2.1 先装好STM32CubeProgrammer:版本与驱动的坑
STM32CubeProgrammer是ST官方的烧录工具,集成了下载、调试、Flash编程、OTP读写等功能。当前建议直接从ST官网下载最新版,第一次使用前注意几个细节。
以Windows为例,CubeProgrammer安装完成后,板载ST-LINK驱动不一定自动生效。你可以在设备管理器里检查有没有出现“STMicroelectronics STLink dongle”或者“STM32 STLink”相关设备。如果看不到,需要手动安装安装目录下的ST-LINK USB驱动,路径一般是C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\drivers\ST-LINK_USB_V2_1_Driver,更新驱动时指向这个目录即可。
Linux下操作则要注意权限问题。直接把用户加入plugdev组是比较常规的做法,否则STM32CubeProgrammer连接ST-LINK时会报权限不足。命令行工具的位置在安装目录的bin/STM32_Programmer_CLI,Linux下通常叫STM32_Programmer_CLI,Windows下是STM32_Programmer_CLI.exe。写这篇文章时我用的版本是2.15,后来换到2.17,操作接口基本没变化。需要特别注意,CubeProgrammer版本不要太老,老版本对MP1系列的支持不够完整,识别芯片时容易出问题。
2.2 M4固件从哪里来:CubeMX生成、官方例程和编译产物
烧录M4核之前,你手上得有一个专门针对M4核编译出来的固件文件,常见格式是.elf或.bin。获取渠道主要有三条:
第一,用STM32CubeMX生成。新建工程时选择芯片STM32MP157F-DK2,注意有一个选项要你选择“Cortex-M4”作为目标核,然后按常规方式配置外设、生成代码,最后用STM32CubeIDE编译,产出.elf文件。CubeMX生成M4工程时会自动帮你处理好内存地址,链接脚本直接指向M4 SRAM区域。
第二,使用STM32Cube_FW_MP1固件包里的现成例程。ST官方把M4的例程全部放在固件包里面,路径类似Projects/STM32MP157F-DK2/Examples/,里面包含GPIO、UART、I2C、SPI各种外设示例。第一次调试建议直接用这些例程编译出来的elf,省去自己写代码出问题排查的时间。
第三,自己的裸机或者FreeRTOS工程。这种就要特别注意链接脚本,M4固件的加载地址必须是0x10000000起始的SYSRAM区域,链接脚本错了,烧进去也跑不起来。
我建议你调试阶段就把Debug目录下生成的.elf文件单独拷贝到一个好找的位置,后面一系列操作都要用到它。
2.3 板卡接线和ST-LINK枚举确认
STM32MP157F-DK2板子上的USB口有好几个,给M4烧录用的板载ST-LINK是独立的Micro-USB口,通常标记为“ST-LINK USB”,接到电脑后板子会通过ST-LINK供电。千万不要插到旁边那个USB Type-C口,那是A7的USB OTG口,跟调试器没有任何关系。
先把线接好,然后打开设备管理器或者Linux的lsusb,确认ST-LINK已经被系统识别。Windows下如果看到“STM32 STLink”以及一个“Virtual COM Port”,说明枚举成功。如果只看到COM口但看不到STLink,多半是驱动没加载好。另外,DK2板载ST-LINK的版本不同,识别出来的名称可能是ST-LINK/V2-1也可能是ST-LINK/V3,这都不影响CubeProgrammer操作。
还要多说一句,板子的BOOT拨码开关(SW1)在烧录M4时不是必需调整的,只要ST-LINK能连上目标芯片,就可以直接下载M4固件。但是如果你希望M4固件能持久化部署,并且上电后由Linux自动加载,那就需要保证板子正常从SD卡或者eMMC启动OpenSTLinux系统,这块后面专门讲。
3. 核心实操:用STM32CubeProgrammer把M4固件跑起来
3.1 快速验证:GUI方式在线下载到M4 SRAM
打开STM32CubeProgrammer,界面左边选择“ST-LINK”接口,右边点“Connect”。如果连接成功,软件会读出芯片系列信息,显示为STM32MP157Fxx。实际连接中经常遇到A7侧Linux已经跑起来的情况,这可能导致连接失败,因为A7的调试访问权限可能被Linux占用。我的经验是连接时勾选“HOTPLUG”模式,或者在点击Connect的瞬间按住板上的NRST复位键不放,让CubeProgrammer在复位窗口期内抓到芯片。
连接成功之后,选择“Open file”,找到之前编译好的M4固件elf文件。加载进来后,CubeProgrammer会解析出固件中各个段的地址。这时不要去看A7侧的Flash地址,M4固件的段应该分布在0x10000000附近的SYSRAM区域。点击“Download”按钮,软件会把代码写入M4的SRAM。然后点击“Run”或者“Start execution”,M4核就开始运行了。
怎么判断M4确实跑了?最简单就是用官方例程。比如GPIO例程会让板子上的LED闪烁,UART例程会在调试串口打印日志。如果屏幕没有任何反应,先去确认你选的例程对应的外设和板子丝印一致。
这种在线下载方式胜在速度快、不需要改启动配置,调试M4逻辑非常方便。但缺点也很明显,断电后固件就没了,每次上电都要重新下载。
3.2 命令行烧录:用CLI方式把流程脚本化
命令行方式适合批量测试和集成到自动化流程里。STM32CubeProgrammer的命令行工具是STM32_Programmer_CLI,基本连接命令如下:
STM32_Programmer_CLI -c port=SWD mode=HOTPLUG这段命令表示通过ST-LINK的SWD接口连接目标芯片,mode=HOTPLUG对应GUI里的热插拔模式,可以绕过Linux对调试口的占用问题。连接后直接下载M4固件:
STM32_Programmer_CLI -c port=SWD mode=HOTPLUG -d ./Debug/stm32mp157f-dk2-m4-fw.elf-d参数表示下载(Download)。下载完成后软件会提示“Download verified successfully”之类的信息。如果你希望下载后立即运行,可以加上-rst让芯片复位,复位后M4是否启动取决于当前A7侧是否配置了加载动作。更稳妥的做法是,下载后通过调试接口直接设置M4的PC指针为固件入口地址,但这已经属于调试器的高级玩法了,日常用到不多。
还有一个实用参数是-v,打印详细日志,遇到下载失败时一定要加这个参数看具体报错。
STM32_Programmer_CLI -c port=SWD mode=HOTPLUG -v -d ./Debug/xxx.elf命令行方式的优势在于可重复、可脚本化,提交代码后一键烧录,对开发效率提升很明显。
3.3 烧录到NOR Flash:能行,但别用默认方式直接干
有些人想让M4固件持久化到板载NOR Flash里,觉得这样最可靠。STM32CubeProgrammer里确实支持外部Flash编程,方式是在连接界面点击“External Loader”按钮,选择MX25L25645G_STM32MP15x-NOR.stldr,这是DK2板载NOR Flash的加载器。选择后即可通过ST-LINK直接读写NOR内容。
但我必须提醒一下,直接用External Loader方式烧M4的elf文件到NOR里,是很多人踩坑的重灾区。原因在于M4固件elf内部的段地址是0x10000000对应的SYSRAM地址,而External Loader是按NOR Flash的存储地址来写的。直接把elf烧进去,NOR里存的数据虽然是完整的,但启动时U-Boot或者Linux不知道该从哪里读取,也不知道怎么把这段数据搬运到M4的SRAM里。所以M4自然跑不起来。
我试过的可行方案是,把M4固件转换成裸二进制镜像,然后自己定义一个NOR偏移,比如0x100000,用External Loader把bin写进去。之后在U-Boot里通过命令手动把NOR中的数据读出来,加载到M4 SRAM再启动。这个方案可行,但配置起来比较麻烦,而且不同OpenSTLinux版本的NOR布局不一样,偏移地址很容易冲突。我的建议是,除非你非常熟悉自己的Flash布局表,否则部署M4固件用后面要讲的SD卡文件系统方式,比直接折腾NOR省心得多。
4. 让M4上电自启:Linux侧remoteproc与U-Boot加载
4.1 把M4固件放到SD卡文件系统里,由Linux自动加载
在OpenSTLinux系统上,部署M4固件的标准做法是把它放到/lib/firmware/目录下,然后通过remoteproc框架加载。M4固件文件直接复制过去就行,例如:
sudo cp stm32mp157f-dk2-m4-fw.elf /lib/firmware/然后通过sysfs接口指定固件文件名并启动:
echo stm32mp157f-dk2-m4-fw.elf > /sys/class/remoteproc/remoteproc0/firmware echo start > /sys/class/remoteproc/remoteproc0/state这里的remoteproc0是M4核对应的remoteproc设备节点,具体编号取决于设备树配置,有的板上可能是remoteproc1。可以通过ls /sys/class/remoteproc/查看实际情况。启动后可以用cat /sys/class/remoteproc/remoteproc0/state确认,正常会显示running。
如果想每次开机自动加载,可以在系统启动脚本里加入上述命令,或者写一个systemd service。还有一点要注意,/lib/firmware/里的M4固件文件名必须和设备树里firmware-name属性配置的一致。默认的设备树一般已经预留了M4的加载节点,但如果你是自己改的设备树,就得检查一下节点下有没有这一项。
4.2 U-Boot阶段手动加载M4固件
如果你需要在Linux启动之前就把M4跑起来,比如某些实时逻辑不希望等Linux完全起来,那么可以在U-Boot阶段手动加载。首先确保M4固件文件放在SD卡的可访问分区里,比如bootfs分区(FAT格式)。进入U-Boot命令行后,执行以下系列操作:
load mmc 0:4 0xC0000000 stm32mp157f-dk2-m4-fw.elf rproc init rproc load 0 0xC0000000 $filesize rproc start 0第一条命令把固件从SD卡的第四个分区加载到内存地址0xC0000000,第二条初始化remoteproc,第三条把内存里的固件加载到M4核心,最后启动M4。注意分区编号可能与实际SD卡布局有关,最好先用mmc part命令查一下bootfs在哪个分区。
U-Boot方式的好处是依赖少、启动早,适合对开机时间有要求的产品。但配置起来要理解U-Boot环境变量和分区布局,排错难度也高一些,适合有一定基础之后再玩。
4.3 设备树与资源预留:为什么M4要用外设必须先“分家”
M4和A7共享大量外设,如果两边同时操作同一个UART或者GPIO,轻则功能异常,重则整个系统崩溃。所以在Linux侧,M4用到的外设必须通过设备树和ETZPC(Extended TrustZone Protection Controller)资源管理器做好隔离。
CubeMX在生成M4工程时,会自动生成一个M4侧的设备树描述片段,里面列出了M4独占的外设和对应的资源。你在OpenSTLinux的Linux内核设备树里,需要把M4占用的那部分外设节点从A7的可用列表里剔除,或者在节点上添加状态标记防止驱动加载。这一步如果漏了,最容易看到的现象是M4固件里初始化UART,Linux那边同时把这个UART当控制台用,两边不断抢,板子看起来就像卡死了一样。
推荐的做法是先在CubeMX里把外设归属规划好,再一次性生成A7和M4两侧的工程配置,不要手动去改,手动改很容易留下隐患。
5. 高频踩坑记录:连接失败、烧录后不工作、外设冲突
5.1 ST-LINK连接不上:先查驱动,再查复位窗口
连接问题是所有人都会遇到的第一个坎。报错信息通常是“No ST-LINK detected”或者“Error: ST-LINK error (DEV_TARGET_HELD_UNDER_RESET)”。前者是PC和ST-LINK之间的USB通信问题,优先检查USB线、驱动和接口;后者是目标芯片复位状态问题,说明ST-LINK连上了,但芯片处于复位状态,需要使用HOTPLUG模式或者在连接瞬间松开复位键。
还有一个容易被忽略的场景:Linux正在运行A7,并且调试接口被系统配置为不可访问,这时候ST-LINK连接也会失败。我遇到这种情况的做法是:先把板子完全断电,然后按住复位键,上电,再在CubeProgrammer里点击Connect,连接成功后松开复位键。多试几次,找到那个时序窗口之后,后面就顺利了。
5.2 固件下载成功但M4不运行
下载成功只说明数据写进去了,不代表M4会执行。这里最常见的原因是A7侧根本没有发起加载。
如果你用的是在线下载方式,CubeProgrammer下载的是整个elf镜像,M4的PC指针初始值也在elf里,理论上下载后直接运行是可以的,但前提是A7侧没有把M4置于reset状态或者没有占用M4的调试权限。如果A7侧的remoteproc驱动已经加载并接管了M4,你再用CubeProgrammer下载,就可能在启动M4时被Linux顶回去。
解决办法有两种,一是先在Linux里执行echo stop > /sys/class/remoteproc/remoteproc0/state释放M4,再通过CubeProgrammer下载;二是确认elf链接地址正确。M4固件的链接脚本一定要基于0x10000000地址,如果你拿一个A7的elf去下载,下载是成功的,但M4根本没法按那个地址执行,自然就没反应。
5.3 M4运行后Linux异常:八成是外设资源冲突
我印象最深的是一次UART例程调试。M4固件能跑,串口也有输出,但系统运行一段时间后A7侧也出现日志混乱,最后整个Linux变得卡顿。排查下来,问题出在M4把UART4初始化了,而A7侧Linux的设备树里UART4也处于启用状态。两边同时对同一个外设寄存器做读写,结果就是数据互相覆盖。
这种问题要从两个层面解决。第一层是设备树,把M4专用的外设节点在A7侧禁用或标记为不可用;第二层是ETZPC配置,确保对应的资源隔离寄存器把外设分配给了M4。STM32CubeMX生成代码时会同时配置这两个层面,所以一定要用CubeMX来维护工程,而不是自己手工加外设。
另外,M4与A7要通信的话,共享内存区域也要在设备树里预留,并且要使用reserved-memory节点声明,防止Linux把这段内存分配给其他进程。
5.4 版本不匹配:CubeProgrammer、OpenSTLinux、固件包三方对齐
MP1平台的软件栈比较复杂,CubeProgrammer、OpenSTLinux系统版本、STM32Cube_FW_MP1固件包版本三者之间如果差距太大,就会出现一些莫名其妙的问题。比如老版本CubeProgrammer可能不支持新板子的NOR Flash External Loader,新版本OpenSTLinux里remoteproc节点的属性名和老设备树不兼容。
我的习惯是:安装CubeProgrammer前看一眼官方Release Note,确认它支持STM32MP157F系列;部署M4固件前,先查OpenSTLinux版本对应的Wiki页面,看看M4固件推荐的编译工具链和加载方式。保持整体工具链同一批发布版本,能省掉大量排查时间。
6. 烧录M4核这件事,我的几点体会
如果让我给一个新手规划学习路径,我会建议先不要碰NOR Flash烧录,也不要去折腾U-Boot环境变量。第一步,用STM32CubeProgrammer在线下载官方GPIO例程,看到LED闪烁,建立信心。第二步,把elf文件放到SD卡上,通过Linux的remoteproc自动加载,让M4固件能够实现上电自启。第三步,再试着用U-Boot命令行手动加载,理解整个启动链路的各个环节。这三步走完,你对MP1异构架构的理解会上一个台阶。
还有一个实用小技巧,调试M4固件时,不一定非要用CubeProgrammer,STM32CubeIDE里也可以直接连接M4核进行在线调试,断点、变量监控都比单纯烧录方便得多。但烧录和部署的场景,CubeProgrammer还是不可替代的。每次操作前多看一眼串口日志,多用CLI的-v参数打印细节,很多坑都能在日志里找到答案。
从这个项目里我最大的收获是,MP1平台不是把两个CPU摆在同一个芯片里就完事,它需要你把整个系统当作一个整体来设计:A7跑应用,M4跑实时逻辑,两者之间用共享内存和资源隔离划清边界。理解了M4的“从属”地位,烧录、加载、启动这些问题就都顺理成章了。