尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32MP13x裸跑实践:在Cortex-A7上像MCU一样点灯

STM32MP13x裸跑实践:在Cortex-A7上像MCU一样点灯 说实话第一次看到“在Cortex-A核上裸跑应用程序”这个说法时我愣了一下。A核不是应该老老实实跑Linux的吗后来认真研究了一下STM32MP13x的资料才明白ST官方特意出了一篇资料思路非常明确这颗芯片完全可以像单片机一样用不开Linux不上文件系统程序直接从复位向量开始跑跟你在F103上写点灯程序没有本质区别。如果你最近在评估STM32MP13x或者你是一个从MCU转过来的嵌入式工程师对庞大的Linux开发体系有点发怵只想先把硬件跑起来、把外设调通那这个方向非常适合你。这篇文章我尽量把从开发环境搭建、启动流程、链接脚本到实际点灯运行的全过程讲透也会把我在实操中踩过的坑一并列出来照着操作基本能把工程跑起来。1. 为什么好好的MPU非要“裸跑”1.1 有人问A7裸跑和MCU裸跑有什么区别先明确一个概念这里说的裸跑是指不加载Linux内核、不经过bootloader引导操作系统而是让Cortex-A7核心从上电后直接执行我们自己编译的裸机程序就像STM32F407、GD32这类MCU一样。区别在于MCU通常内部自带Flash上电后直接从Flash取指执行而STM32MP13x没有内部Flash程序必须先被加载到内部SRAM或外部DDR中然后CPU跳过去执行。这个区别带来的第一个变化是加载方式变了你不能再像用J-Link烧写MCU那样直接把bin文件写进内部Flash而是需要通过UART、USB DFU或JTAG把程序放进RAM运行。第二个变化是启动代码不同A7内核有更多的工作模式比如SVC、IRQ、FIQ、Abort、Undefined启动时需要逐个设置好栈指针。但从写应用的角度看外设寄存器操作、GPIO翻转、UART发送这些事跟MCU开发基本是一模一样的。1.2 哪些场景适合“一脚踢开Linux”不是所有项目都需要Linux。很多工业控制、电机驱动、传感器采集类项目核心逻辑就一个循环加几个中断硬塞一个操作系统进去反而添乱。我整理了几个适合裸跑的实际场景板卡回板自检硬件工程师刚拿到新打样的板子最想干的事是验证DDR、串口、网口、GPIO这些外设有没有焊接问题。如果每次都要烧写整个Linux系统、等内核起来、再挂文件系统一次自检可能要花好几分钟。裸机程序几秒钟就能完成一轮检测效率完全不在一个量级。高性能单任务控制比如FOC电机控制电流环要求微妙级响应Linux的调度器再实时也免不了抖动。A7主频1GHz裸跑一个FOC算法绰绰有余而且中断延迟可以做到纳秒级别这是A7裸跑相对于Linux的天然优势。低成本产品原型验证你的产品最终可能不需要系统级的复杂功能只是想快速验证某个外设能不能正常工作裸机开发可以让你在一个晚上就写出测试代码比配置一个Linux驱动简单太多。1.3 为什么是STM32MP13x而不是MP15xSTM32MP15x系列里有一颗Cortex-M4协处理器很多开发者习惯把实时性要求高的任务丢给M4核A7核继续跑Linux。这种做法当然可行但也带来了新的复杂度两个核之间的通信、内存共享、启动顺序协调都要花不少心思。STM32MP13x不一样它没有M4核整颗芯片就是一颗单核Cortex-A7。理论上你说这芯片“回到了MCU的简单形态”也不为过。没有多核同步问题没有AMP/SMP的调度复杂度你只需要关注一个核而且这个核的性能比老一代MPU强不少。更关键的是ST官方专门围绕这颗芯片发布了裸跑应用的参考文档说明官方是支持这种用法的不是你自己瞎折腾。2. STM32MP13x的硬件底子启动方式与内存布局2.1 单核A7的真实性能STM32MP13x系列采用ARMv7-A架构单核Cortex-A7主频最高能做到1GHz左右具体性能参数以当前型号的参考手册为准。它具备NEON SIMD扩展和VFPv4浮点单元这意味着在裸机程序里可以做浮点运算而且速度比用软件模拟快得多。内部集成的缓存也不是MCU能比的L1 Cache分为指令缓存和数据缓存大小在32KB级别L2 Cache也有几百KB。不过在你刚开始写裸机程序时我建议先把D-Cache关掉或者保持默认状态因为缓存一致性问题是A核裸跑最容易踩的坑。等程序稳定运行了再去考虑通过开启缓存来榨干性能。2.2 从BootROM到App的启动链路STM32MP13x的启动过程大致是上电后芯片内部的BootROM程序会先运行根据BOOT引脚的配置选择启动源。常见的启动源有SD卡、eMMC、NAND、NOR Flash、UART串口、USB DFU等。BootROM会把程序从启动源读取到内部SRAM或者直接跳转到加载地址执行。这里面有一个关键点既然芯片没有内部Flash那BootROM本身是固定的用户程序完全在外部。对于裸机开发来说最简单的路径是让BootROM通过UART或USB DFU接收你的程序然后放到SRAM里跑。如果你的程序超过了内部SRAM容量那就需要先把DDR初始化好再把程序加载到DDR里执行。DDR初始化并不简单但STM32CubeProgrammer里带有对应板卡的DDR初始化脚本直接调用就行。2.3 内存资源SRAM与外部DDR怎么选STM32MP13x内部有一块SRAM容量大约256KB这个空间对于很多裸机应用来说其实已经够用了。比如你要跑一个电机FOC控制、做一个I2C传感器采集、写一个简单的通信协议解析256KB绰绰有余。SRAM最大的优点是无需初始化上电就能访问加载程序进去就能执行特别适合刚起步调试。如果应用真的需要更大的内存比如要跑一个轻量级的神经网络推理或者要缓存大量的传感器数据那就必须用到外部DDR。DDR的容量和类型取决于你板子上贴的DDR颗粒常见的有DDR3L、DDR4、LPDDR2/3/4。使用DDR前必须正确配置DDR控制器和PHY这个初始化过程在MCU开发中根本不存在算是A核裸跑和MCU开发最大的区别之一。一个实用的建议是前期调试阶段程序尽量放在SRAM里跑避免一上来就处理DDR初始化问题。等外设功能都验证完了再考虑切换到DDR模式用更大的内存去做复杂应用。3. 裸跑开发环境搭建工具链、CubeMX与调试器选型3.1 arm-none-eabi-gcc 还是 STM32CubeIDEA核裸机程序的编译工具链和MCU开发非常接近甚至可以直接使用GNU工具链。你既可以用ST官方的STM32CubeIDE也可以自己搭建命令行环境。STM32CubeIDE的好处是集成度高编译、下载、调试一条龙而且它对STM32MP13x的芯片支持包可以直接下载很快就能建好工程。如果你习惯用命令行的方式那就安装arm-none-eabi-gcc建议用10.3版本以上的对Cortex-A7的支持更加完善。编译时加入对应的CPU参数-mcpucortex-a7、-mfloat-abihard、-mfpuneon-vfpv4。如果条件允许用-mthumb让代码以Thumb-2指令集运行可以减小代码体积也可以用-marm强制ARM指令集运行效率在部分场景下更高。两种模式Cortex-A7都支持选一个顺手的方向即可。另外GDB调试工具和OpenOCD建议一并配好。学习阶段用STM32CubeIDE自带的调试功能更省心后面做复杂项目时用命令行工具链更灵活。3.2 用STM32CubeMX生成裸机工程的正确姿势STM32CubeMX早就支持STM32MP13x系列了。打开CubeMX选择对应型号的芯片比如STM32MP135然后在菜单里选择不带Linux的裸机工程模板。这个操作说白了跟你用CubeMX生成一个STM32F4的工程没什么两样只不过目标芯片变成了A核。关键步骤是配置时钟树。A7核心有自己的时钟域需要通过PLL1来生成CPU主频。在CubeMX的Clock Configuration页面里你可以直观地看到PLL1的输入频率、倍频系数、分频系数把目标主频设置为芯片允许的最高值即可。同时要确保HCLK、PCLK1/2等总线时钟设置正确否则后面外设会工作异常。做完引脚配置和外设配置后在Project Manager里选择工具链可以生成Makefile工程或者STM32CubeIDE工程。生成的代码里包含了完整的启动文件、系统初始化代码和HAL库。需要说明的是这里的HAL库是STM32MP13x专用的版本外设API风格和STM32F系列非常接近很多代码可以直接迁移这个设计对MCU转型开发者特别友好。3.3 加载与调试方案串口、USB DFU、JTAG程序编译出来之后怎么把它跑起来我常用三种方式STM32CubeProgrammer UART/USB DFU这是最快速的验证方式。把开发板上的BOOT引脚配置成UART或USB启动模式用STM32CubeProgrammer连接直接把ELF或者bin文件下载到指定内存地址然后跳转执行。如果目标地址是DDR需要先加载一个DDR初始化脚本这个脚本通常由板卡厂商提供。OpenOCD 调试器用ST-Link或者J-Link连接SWD或JTAG接口OpenOCD里加载对应芯片的target配置然后通过GDB调试。这种方式的最大优势是能打断点、看变量、单步执行非常适合排查启动阶段的问题。Eclipse或VS Code配合Cortex-Debug插件可以做出很舒服的调试界面。把程序做成启动引导的一部分如果你最终希望芯片上电后自动运行裸机程序可以把程序打包成FSBL能识别的镜像格式放到SD卡启动由FSBL在初始化DDR后跳转执行。这种方式最接近量产形态但前期调试阶段没必要搞这么复杂。4. 实操手把手写一个A7裸机点灯工程4.1 工程目录与启动文件写一个能跑起来的点灯工程建议从CubeMX生成的模板开始这样启动文件和系统初始化代码都是现成的。一个典型的工程目录如下├── Core │ ├── Inc │ │ ├── main.h │ │ └── stm32mp13x_hal_conf.h │ ├── Src │ │ ├── main.c │ │ ├── stm32mp13x_it.c │ │ └── system_stm32mp13x.c │ └── Startup │ └── startup_stm32mp135x.s ├── Drivers │ ├── CMSIS │ └── STM32MP13x_HAL_Driver └── Makefile启动文件是整个程序的起点。Cortex-A7的启动文件负责建立异常向量表、初始化各个工作模式的栈指针、调用SystemInit和main函数。和MCU启动文件相比A7的启动文件要设置的栈指针更多因为SVC、IRQ、FIQ、Abort、Undefined这几个模式下CPU都有自己独立的SP寄存器。如果不把每个模式的SP都设置好一旦触发对应异常程序就会跑飞。4.2 链接脚本内存地址与栈指针别配错链接脚本是整个裸机工程里最需要认真对待的文件。如果你的程序放在内部SRAM里跑链接脚本的内存区域可以写成这样MEMORY { RAM (rwx) : ORIGIN 0x2FFC0000, LENGTH 256K } ENTRY(Reset_Handler) SECTIONS { .text : { KEEP(*(.isr_vector)) *(.text) *(.rodata*) } RAM .data : { *(.data) } RAM .bss : { *(.bss) } RAM }这段脚本的含义是把整个程序从0x2FFC0000这个SRAM起始地址开始放置栈顶指针通常也放在内存末尾。这里有两个高频踩坑点第一加载地址必须和程序实际运行地址一致因为裸机程序没有重定位机制不一致的话CPU跳过去就执行了错误的指令第二栈空间一定要足够大如果栈区和堆区重叠就会产生内存踩踏程序表现会很诡异。4.3 时钟配置与GPIO/UART初始化CubeMX生成的基础工程里已经初始化好了系统时钟。如果你需要手动调整核心代码大致是调用HAL_RCC_OscConfig配置PLL再用HAL_RCC_ClockConfig设置总线时钟。GPIO初始化和MCU开发几乎没有区别下面这段代码可以用来点灯GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOI_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_5; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOI, GPIO_InitStruct); while (1) { HAL_GPIO_WritePin(GPIOI, GPIO_PIN_5, GPIO_PIN_RESET); HAL_Delay(500); HAL_GPIO_WritePin(GPIOI, GPIO_PIN_5, GPIO_PIN_SET); HAL_Delay(500); }UART初始化的写法和F系列也类似UART_HandleTypeDef huart1; huart1.Instance USART1; huart1.Init.BaudRate 115200; huart1.Init.WordLength UART_WORDLENGTH_8B; huart1.Init.StopBits UART_STOPBITS_1; huart1.Init.Parity UART_PARITY_NONE; huart1.Init.Mode UART_MODE_TX_RX; HAL_UART_Init(huart1);如果你想用printf打印调试信息需要重定向底层写函数。HAL库环境下通常重写这个函数int _write(int file, char *ptr, int len) { HAL_UART_Transmit(huart1, (uint8_t *)ptr, len, 0xFFFF); return len; }注意UART引脚的复用功能一定要在CubeMX里配置好否则发送永远不会有输出。这是新手最容易忽略的地方MCU开发同样如此但A7的引脚复用选项更多选错概率更大。4.4 编译、烧录和运行验证如果工程是CubeMX生成的Makefile工程编译只需要一条命令make编译完成后会生成elf文件和bin文件。用STM32CubeProgrammer连接目标板选择UART或USB DFU方式加载地址填SRAM和链接脚本对应的起始地址然后点击下载并运行。如果程序恰好被加载到DDR区域那下载之前必须先加载DDR初始化脚本顺序不能反。下载完成后正常情况下应该能看到LED在闪烁串口输出你写的调试信息。如果一切正常恭喜你你已经在A7核心上完成了第一次裸机运行。这个过程看起来简单但它意味着你已经掌握了A核裸跑的核心路径编译、加载、跳转、执行。5. 踩坑记录从“跑不起来”到“稳定运行”的排查清单5.1 程序加载后CPU“毫无反应”程序下载完看起来没有任何反应这类问题在A核裸跑中太常见了。按照我的排查经验优先检查这三个环节加载地址是否和链接脚本一致。这是最容易出错的地方链接脚本写的ORIGIN是0x2FFC0000下载时填的地址却变成0x80000000程序被放到完全错误的位置CPU自然不知道执行什么。下载完后可以先读一下PC寄存器如果PC指向的是一个奇怪的地址十有八九是地址不匹配。程序是否被放到DDR但DDR没有初始化。DDR不像SRAM那样上电可用它需要经过控制器初始化、PHY训练等一系列步骤。如果你的程序大于256KB必须放到DDR里那下载前一定要确认DDR初始化脚本已经执行成功。一个快速验证办法是先用CubeProgrammer读DDR地址空间的数据能正确读到数据说明DDR已经初始化完成。UART的引脚复用和时钟是否配置正确。串口没有输出时先不要怀疑代码逻辑用逻辑分析仪或示波器看TX引脚有没有电平变化。如果没有信号优先检查时钟树里USART时钟是否打开、引脚复用是否选对。5.2 中断不触发GIC那点事很多MCU开发者第一次到A核裸跑时会习惯性以为配置好外设中断就万事大吉结果发现中断函数根本不进。原因很简单Cortex-A7的中断控制器是GIC通用中断控制器不是MCU里的NVIC。NVIC是直接把外设中断映射到内核异常向量而GIC多了一层分发和CPU接口的配置。在使用外设中断之前需要先初始化GIC并配置中断分组和使能通路大致步骤是HAL_GIC_Init(); HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn);看起来和STM32F4的NVIC写法很像但底层实现完全不同。如果你跳过HAL_GIC_Init中断来了也不会被CPU响应。另外GIC的中断分为Group0和Group1Group0通常对应FIQGroup1对应IRQ初始化时要确保中断分组和你使用的中断类型匹配。5.3 Cache导致的外设数据错乱这一条是在裸机跑起来之后才会遇到的坑。D-Cache开启后CPU访问内存时会优先命中缓存不会实时读写实际的物理内存。在点灯这种简单程序里完全无感但一旦涉及DMA传输问题就来了你让DMA从内存读取数据但数据还躺在Cache里没有写回物理内存DMA读到的全是旧数据。反过来DMA往内存写入的数据CPU从Cache里读到的也可能是旧值。解决办法有两个要么在DMA传输前后手动执行Cache的Clean和Invalidate操作要么干脆在裸机开发初期把D-Cache关掉。对于外设地址空间建议配置为Device或Strongly-ordered属性这样CPU访问外设寄存器时不会经过Cache避免很多莫名其妙的问题。5.4 一些很难发现的隐藏坑启动文件里异常向量表必须4字节对齐如果对齐不正确异常跳转会失败系统在触发中断时直接卡死。中断服务函数的名字必须和启动文件里向量表的符号完全一致比如USART1_IRQHandler不能多一个字母。CubeMX自动生成的工程通常没问题但你自己新建文件时很容易写错。如果你用了STM32CubeProgrammer下载elf文件到DDR下载完不要立刻按复位键部分板卡在复位后不会自动跳转到DDR需要手动执行跳转命令或者把启动方式设置成能引导应用的模式。AFIO、GPIO的速度等级这些看似不起眼的配置在高速UART和以太网通信时会变成致命因素GPIO速度等级配置过低会导致信号边沿变缓通信不稳定。现象最可能原因快速排查手段下载后无反应地址不匹配或DDR未初始化检查PC寄存器确认加载地址串口无输出UART时钟/引脚复用未配置示波器测TX引脚电平中断不进GIC未初始化确认HAL_GIC_Init已调用DMA数据错乱D-Cache使能后未Clean/Invalidate先关D-Cache测试程序跑到HardFault栈溢出或向量表不对齐检查栈区大小查看LR寄存器最后分享一个我自己摸索出来的小技巧在A核裸机工程里调试信息输出越早越好。我习惯把UART初始化放在SystemInit之后、main函数一开始的地方这样从程序启用的第一毫秒开始每一步关键操作都能通过串口打印出来。哪怕是一个简单的Hello from A7也能帮你快速确认启动流程是否正常。总之A7裸跑没有想象中那么神秘把启动链路和内存布局弄明白之后它就是你手上的一颗1GHz大号单片机剩下的就是尽情发挥。
返回列表