STM32MP135D嵌入式Linux开发入门:从零构建到应用实战
1. 项目概述为什么选择STM32MP135D作为嵌入式Linux的起点最近在整理工作室的物料翻出来一块吃灰已久的ODYSSEY开发板主控正是STM32MP135D。这让我想起几年前刚开始接触嵌入式Linux时面对各种复杂的SoC和开发环境那种无从下手的迷茫感。市面上关于STM32MP1系列的资料要么是官方晦涩的参考手册要么是过于零散的社区帖子缺少一个能让人“扶着走”的完整路径。所以我决定以这块板子为蓝本写一份真正面向新手的入门指南。这份指南的目标很明确让一个熟悉单片机编程但没碰过Linux的开发者能顺利地在STM32MP135D上跑起第一个Linux应用并理解这背后每一步在做什么。STM32MP135D是ST在2022年推出的产品定位非常清晰高性价比的工业级单核Cortex-A7应用处理器。它不像MP157那样追求双核高性能而是把成本、功耗和实时性做到了一个很好的平衡点。对于物联网网关、工业HMI、智能家居中控这些不需要复杂图形界面但要求稳定可靠和一定连接能力的场景它是个绝佳的选择。更重要的是ST为其提供了完整的开源软件支持包括主线Linux内核、Buildroot/Yocto构建系统这让学习和开发的门槛降低了不少。如果你之前只用过STM32F1/F4这类MCU第一次看到MP1系列可能会有点懵它既有能跑Linux的Cortex-A核也有能跑实时任务的Cortex-M核还有一堆外设。别担心我们这篇指南会聚焦在A核的Linux世界把最核心的“上电-编译-烧录-运行”这条链路打通。你会发现从“点灯”到“联网”其思维模式和工具链与单片机开发有相通之处也有需要跨越的鸿沟。准备好了吗我们这就开始。2. 开箱与硬件初识不只是另一块“单片机”开发板拿到ODYSSEY板子第一件事不是急着上电而是花十分钟认识一下你的新伙伴。这能帮你避免很多低级错误比如插错电源烧了芯片。2.1 核心硬件配置与接口速览ODYSSEY板载的STM32MP135DAF7可以拆解为几个关键部分来理解应用处理器核心单核Arm Cortex-A7主频高达1GHz。这是运行Linux操作系统和主要应用程序的大脑。对比你熟悉的单片机它的算力足以处理TCP/IP协议栈、文件系统、甚至轻量级的图形界面。实时协处理器Arm Cortex-M4运行在209MHz。它独立于A核可以单独供电运行专门用于处理对时间要求极其苛刻的任务比如电机控制、ADC高速采集。在Linux系统中它通常被视为一个外设通过RPMsg远程处理器消息框架与A核通信。内存与存储板载512MB DDR3L内存对于运行一个精简的Linux系统绰绰有余。存储方面它采用了MicroSD卡槽作为主要启动和存储介质这极大地方便了我们的学习和调试——更新系统就像换一张SD卡那么简单。板上还有一个SPI Flash可用于存储启动阶段的固件TF-A、U-Boot。关键外设与接口网络一个10/100M以太网口RJ45。这是你后续通过SSH登录、传输文件的主要通道务必确认你的路由器或电脑能直连。USB一个USB 2.0 OTG Type-C接口。它功能强大既可以用于供电也能在开发初期作为串口调试终端后期还能模拟成网卡USB RNDIS/Ethernet Gadget。调试一个标准的4线串口UART接口通常连接到A7核心是输出内核启动信息、进行命令行操作的最可靠方式。扩展接口双排针引出了大量的GPIO、I2C、SPI、ADC等接口兼容树莓派GPIO布局这意味着你有海量的传感器和执行器模块可以即插即用。注意给板子供电时强烈建议使用Type-C接口并搭配一个至少5V/2A的电源适配器。虽然排针上也有5V输入引脚但直接接调试器或不当操作容易引入噪声或导致电源不稳定。第一次上电前请检查SD卡槽是否为空避免从旧的、可能损坏的镜像直接启动。2.2 搭建最小开发环境你的“武器库”玩转嵌入式Linux本地需要一个强大的Linux开发环境。我强烈建议你不要在Windows下折腾交叉编译那会徒增无数烦恼。最平滑的方式是在你的Windows电脑上安装WSL2Windows Subsystem for Linux并选择Ubuntu 22.04 LTS作为分发版。在WSL2的Ubuntu终端里依次安装以下工具这些是我们后续所有操作的基础# 1. 更新软件包列表并安装基础工具 sudo apt update sudo apt install -y git wget make gcc g device-tree-compiler \ bison flex libssl-dev libncurses-dev \ python3 python3-pip python3-venv # 2. 安装ARM交叉编译工具链 # ST官方推荐使用Linaro或Arm官方的GCC工具链。这里我们安装Arm GNU Toolchain。 # 前往Arm官网下载最新版本或直接使用apt安装版本可能稍旧但稳定 sudo apt install -y gcc-arm-linux-gnueabihf g-arm-linux-gnueabihf # 安装后验证 arm-linux-gnueabihf-gcc --version如果能看到类似arm-linux-gnueabihf-gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0的输出说明工具链安装成功。为什么是gnueabihf这里的hf代表“硬浮点”Hard Float。STM32MP135D的Cortex-A7内核带有硬浮点运算单元VFPv4使用hf后缀的工具链编译出的程序可以直接利用硬件进行浮点计算速度远快于软件模拟的软浮点(gnueabi)。这是性能优化的第一个小细节。3. 获取与构建官方SDK从源码到可启动镜像ST为MP1系列提供了名为“STM32MP1 OpenSTLinux Distribution”的完整软件发行版。它基于Yocto项目构建包含了Linux内核、U-Boot、TF-A以及根文件系统。对于新手我建议先从ST的GitHub仓库获取“开发者包”Developer Package它提供了一个更易于上手的构建框架。3.1 获取源代码打开WSL2终端找一个空间充足的目录建议至少预留50GB执行以下命令克隆代码仓库# 创建并进入工作目录 mkdir -p ~/stm32mp1-workspace cd ~/stm32mp1-workspace # 使用ST的repo工具管理多个仓库 # 首先下载repo工具 mkdir -p ~/.bin curl https://storage.googleapis.com/git-repo-downloads/repo ~/.bin/repo chmod ax ~/.bin/repo export PATH${HOME}/.bin:${PATH} # 临时添加到PATH建议将此行添加到~/.bashrc中 # 初始化并同步代码 repo init -u https://github.com/STMicroelectronics/oe-manifest.git -b refs/tags/openstlinux-6.1-yocto-mickledore-mp1-v23.06.21 repo sync -j4 # -j4 表示用4个线程并行下载速度更快这个过程会下载数十个Git仓库包括Linux内核、U-Boot、各种软件包等耗时较长取决于你的网络状况请耐心等待。实操心得repo sync过程中很可能会因为网络问题中断。如果中断了直接重新运行repo sync -j4即可它会自动续传。同步完成后务必检查layers/meta-st等目录是否存在且非空。3.2 配置与构建镜像代码同步完成后我们就可以开始配置构建环境了。ST的SDK使用DISTRO和MACHINE两个关键变量来定义目标。# 进入源码目录 cd ~/stm32mp1-workspace # 设置构建环境会自动创建一个build-DISTRO-MACHINE的目录 DISTROopenstlinux-weston MACHINEstm32mp135d-dk source layers/meta-st/scripts/envsetup.sh执行上面的命令后它会提示你接受EULA协议输入‘y’确认。接着它会自动跳转到新建的build-openstlinuxweston-stm32mp135d-dk目录。现在开始构建整个系统镜像。这是一个“喝杯咖啡”甚至“吃顿饭”的长时间操作因为Yocto会从源头编译所有软件包。# 启动构建过程。bitbake是Yocto的构建引擎。 bitbake st-image-weston这个命令在做什么它正在执行一个庞大的“配方”下载从互联网下载所有所需软件包的源代码如Linux内核、BusyBox、Weston显示服务器等。解压与打补丁解压源码并应用ST或社区提供的特定补丁。配置根据目标平台stm32mp135d-dk配置每个软件包比如开启或关闭特定内核模块。编译使用我们安装的交叉编译工具链将源代码编译成ARM架构的二进制文件。打包将编译好的内核、设备树、驱动模块、应用程序、库文件等按照预定的目录结构打包成一个完整的根文件系统镜像最后生成一个可烧录的SD卡镜像文件。构建成功的标志是在tmp-glibc/deploy/images/stm32mp135d-dk/目录下找到st-image-weston-stm32mp135d-dk.wic文件这就是我们最终需要的SD卡镜像。避坑指南第一次构建极有可能失败常见原因有网络超时Yocto需要从全球各地的开源镜像站下载软件包网络不稳定会导致下载失败。解决方案是配置本地代理或者使用bitbake -c fetchall st-image-weston命令预先下载所有资源包期间可以断点续传。依赖缺失虽然我们安装了基础工具但Yocto可能需要一些额外的库。仔细阅读错误日志通常会明确告诉你缺少哪个dev包用apt install安装即可。硬盘空间不足构建目录最终会占用30GB以上空间请确保磁盘充足。4. 烧录与首次启动见证Linux的诞生拿到.wic镜像文件后下一步就是把它“刻录”到SD卡让板子能从中启动。4.1 准备SD卡与烧录你需要一张至少8GB的MicroSD卡和一个读卡器。在WSL2中SD卡通常被识别为/dev/sdX如/dev/sdb。操作前务必确认设备号误操作会格式化你的电脑硬盘# 1. 插入SD卡后在WSL2中查看磁盘标识 lsblk你会看到类似输出找到容量符合你SD卡大小的那个设备比如sdb注意是设备本身sdb不是它的分区sdb1。NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 465.8G 0 disk ├─sda1 8:1 0 512M 0 part /boot/efi └─sda2 8:2 0 465.3G 0 part / sdb 8:16 1 29.7G 0 disk # -- 这就是你的SD卡 └─sdb1 8:17 1 29.7G 0 part使用dd命令进行烧录# 2. 卸载SD卡上已挂载的分区如果WSL2自动挂载了的话 sudo umount /dev/sdb1 # 3. 使用dd命令写入镜像。if输入文件of输出设备bs块大小status进度显示 sudo dd iftmp-glibc/deploy/images/stm32mp135d-dk/st-image-weston-stm32mp135d-dk.wic of/dev/sdb bs4M statusprogress oflagsyncdd命令是磁盘级别的原始写入速度很快但需要谨慎。oflagsync确保数据完全写入后才返回避免拔出SD卡时镜像不完整。4.2 连接串口与上电观察烧录完成后将SD卡插入ODYSSEY板子的卡槽。接下来是激动人心的第一次上电调试串口终端是我们的“眼睛”。硬件连接用一根USB转TTL串口线连接电脑和ODYSSEY板子的调试串口通常是UART4丝印上会标TX/RX/GND。注意交叉连接板的TX接调试器的RX板的RX接调试器的TXGND对接。软件准备在电脑上打开一个串口终端软件如minicomLinux或PuTTYWindows。设置参数为波特率115200数据位8停止位1无奇偶校验无流控。上电观察给ODYSSEY板通电通过Type-C口。立即观察串口终端你应该会看到如瀑布般滚动的启动日志。一个正常的启动日志序列大致如下[TF-A] 阶段初始化安全世界DDR... [U-Boot] 阶段加载设备树初始化更多外设寻找启动分区... [Linux内核] 阶段解压内核初始化CPU、内存、外设挂载根文件系统... [系统初始化] 阶段启动systemd或init进程加载服务最后出现登录提示符。最终你应该看到类似这样的登录提示stm32mp135d-dk login:恭喜你的STM32MP135D已经成功运行起了完整的Linux系统。默认用户名是root密码为空直接回车。常见问题速查无任何输出检查串口线连接是否正确、接触是否良好确认终端波特率是否为115200确认SD卡镜像烧录成功。卡在U-Boot阶段可能是设备树dtb不匹配或SD卡分区问题。检查U-Boot启动环境变量。内核panic最常见的原因是根文件系统rootfs挂载失败。检查SD卡上的bootfs和rootfs分区是否存在且镜像完整。5. 系统定制与驱动开发从使用者到创造者成功启动原生系统只是第一步。接下来我们要学习如何定制这个系统比如修改内核配置、添加自己的驱动、编译一个简单的应用程序。5.1 单独编译Linux内核与设备树我们不需要每次都耗时数小时去完整构建整个Yocto镜像。在开发驱动或调整内核参数时可以单独编译内核。# 在构建目录下启动一个针对内核开发的环境 cd ~/stm32mp1-workspace/build-openstlinuxweston-stm32mp135d-dk source ../layers/meta-st/scripts/envsetup.sh # 进入内核源码目录并启动配置菜单 bitbake -c menuconfig virtual/kernel这会打开一个经典的ncurses配置界面你可以在这里启用或关闭内核模块。例如如果你想增加对某个USB WiFi芯片的支持可以在这里搜索并选中对应的驱动。配置保存后开始编译内核和设备树# 清除旧编译结果并重新编译 bitbake -c clean virtual/kernel bitbake -c compile -f virtual/kernel bitbake -c deploy virtual/kernel编译产物zImage内核镜像和stm32mp135d-dk.dtb设备树会输出到部署目录。你可以只更新SD卡boot分区里的这两个文件然后重启板子就能验证新的内核是否工作。5.2 编写一个最简单的内核模块让我们真正深入一点写一个经典的“Hello World”内核模块。这能帮你理解Linux驱动的基本骨架。在你的工作空间创建一个新目录并新建文件hello_mp1.c// hello_mp1.c #include linux/init.h #include linux/module.h #include linux/kernel.h MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(A simple hello world module for STM32MP135D); static int __init hello_init(void) { printk(KERN_INFO Hello, STM32MP135D! Kernel module loaded.\n); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO Goodbye, STM32MP135D! Kernel module unloaded.\n); } module_init(hello_init); module_exit(hello_exit);同时创建一个简单的Makefile# Makefile obj-m : hello_mp1.o KDIR : /path/to/your/workspace/build/tmp-glibc/work/stm32mp135d_dk-linux-gnueabi/linux-stm32mp/5.15.y-r0/build PWD : $(shell pwd) default: $(MAKE) -C $(KDIR) M$(PWD) modules注意KDIR需要替换为你实际的内核构建目录路径。这个路径可以在Yocto构建目录的tmp-glibc/work下找到具体路径根据你的构建版本会有所不同。编译这个模块make ARCHarm CROSS_COMPILEarm-linux-gnueabihf-成功后会产生hello_mp1.ko文件。将这个文件通过SCP或SD卡拷贝到板子的文件系统中然后加载它# 在板子的Linux终端上操作 insmod hello_mp1.ko # 加载模块 dmesg | tail -2 # 查看内核日志应该能看到我们的Hello信息 lsmod | grep hello # 查看已加载模块列表 rmmod hello_mp1 # 卸载模块 dmesg | tail -2 # 再次查看看到Goodbye信息当你看到内核日志中打印出你设定的信息时那种成就感是无与伦比的——你刚刚让这块芯片执行了你编写的、运行在最高特权级别内核空间的代码。5.3 交叉编译用户空间应用程序内核模块通常用于驱动硬件而大部分应用逻辑运行在用户空间。我们来交叉编译一个简单的C程序。创建hello_world.c#include stdio.h int main() { printf(Hello World from STM32MP135D Userspace!\n); return 0; }使用交叉编译工具链进行编译arm-linux-gnueabihf-gcc -static -o hello_world hello_world.c-static参数表示静态链接这样生成的可执行文件包含了所有需要的库可以直接在任何同架构的Linux系统上运行无需担心目标板缺少动态库。将生成的hello_world文件拷贝到板子上赋予执行权限并运行chmod x hello_world ./hello_world你应该能看到Hello World from STM32MP135D Userspace!的输出。至此你已经完成了从系统构建、烧录、启动到内核模块和用户程序开发的完整闭环。6. 网络配置与远程开发提升效率的关键一直盯着串口终端和反复插拔SD卡不是长久之计。配置好网络开启SSH服务才能实现高效的远程开发。6.1 配置有线网络与SSHODYSSEY板子启动Weston桌面后通常已经启动了网络服务。我们可以通过串口来配置静态IP或确认DHCP获取的IP。# 查看网络接口和IP地址 ip addr show eth0 # 如果显示类似 192.168.1.xxx 的地址说明DHCP成功 # 如果需要设置静态IP假设网关是192.168.1.1 ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up route add default gw 192.168.1.1 # 为了使配置永久生效需要修改 /etc/network/interfaces 文件ST的镜像默认可能没有开启SSH。我们需要安装并启动OpenSSH服务器# 更新软件包列表并安装openssh-server opkg update # 如果使用opkg包管理器 opkg install openssh-server openssh-sftp-server # 启动SSH服务 /etc/init.d/sshd start # 设置开机自启 /etc/init.d/sshd enable现在你就可以从你的开发电脑上通过SSH连接到板子了ssh root192.168.1.100告别串口线文件传输可以使用scp命令# 将本地文件拷贝到开发板 scp my_program root192.168.1.100:/home/root/ # 从开发板拷贝文件到本地 scp root192.168.1.100:/var/log/messages ./log.txt6.2 使用VSCode进行远程开发进阶如果你追求极致的开发体验可以配置VSCode的远程开发插件。这需要在你的开发电脑Windows/WSL2上安装VSCode并安装“Remote - SSH”扩展。在VSCode中点击左侧活动栏的远程资源管理器图标。点击“”号输入ssh root192.168.1.100回车。选择SSH配置文件保存位置默认即可。在弹出框中选择“Linux”然后输入root用户的密码默认为空直接回车。连接成功后VSCode会在开发板上安装一个轻量级服务端然后你就可以像操作本地文件夹一样在远程的板子文件系统上浏览、编辑、运行和调试代码了。你可以直接在上面编写、编译和运行我们之前提到的hello_world程序。这种方式将开发环境代码编辑、版本控制和运行环境ARM板完美分离又无缝结合是嵌入式Linux应用开发的效率利器。7. 实战打造一个简单的GPIO控制应用理论说得再多不如动手做一个实际的项目。我们用用户空间最常用的GPIO控制方式——sysfs接口虽然已逐渐被libgpiod取代但最简单直观来实现一个控制LED灯闪烁的程序。首先在板子上找到一颗可用的LED。查看原理图或板子丝印假设我们控制连接在PG12引脚上的LED。在Linux中GPIO通过/sys/class/gpio目录暴露给用户空间。操作步骤如下# 在开发板的SSH终端中操作 # 1. 导出GPIO引脚PG12对应GPIO号需要计算每组32个G组是第6组A0,B1...所以GPIO号 6*32 12 204 echo 204 /sys/class/gpio/export # 2. 查看是否出现gpio204目录 ls /sys/class/gpio/ # 3. 设置方向为输出 echo out /sys/class/gpio/gpio204/direction # 4. 控制LED亮灭 echo 1 /sys/class/gpio/gpio204/value # 亮 echo 0 /sys/class/gpio/gpio204/value # 灭现在写一个C程序led_blink.c来自动化这个闪烁过程#include stdio.h #include stdlib.h #include unistd.h #define GPIO_PATH /sys/class/gpio/gpio204 #define GPIO_VALUE GPIO_PATH /value #define GPIO_DIRECTION GPIO_PATH /direction int main() { // 确保GPIO已导出并设置为输出在实际产品代码中需增加错误检查 system(echo 204 /sys/class/gpio/export 2/dev/null); system(echo out GPIO_DIRECTION); FILE *value_fp fopen(GPIO_VALUE, w); if (value_fp NULL) { perror(Failed to open GPIO value file); return 1; } for (int i 0; i 10; i) { fprintf(value_fp, 1); fflush(value_fp); // 立即写入 sleep(1); fprintf(value_fp, 0); fflush(value_fp); sleep(1); printf(Blink %d\n, i1); } fclose(value_fp); // 可选取消导出GPIO // system(echo 204 /sys/class/gpio/unexport); return 0; }交叉编译并运行arm-linux-gnueabihf-gcc -static -o led_blink led_blink.c scp led_blink root192.168.1.100:/home/root/ # 在板子上运行 ./led_blink你应该能看到LED以1秒的间隔闪烁10次同时在终端输出计数。这个简单的例子打通了从应用层代码到硬件控制的全路径是理解嵌入式Linux应用开发本质的绝佳起点。注意事项与进阶方向sysfsGPIO接口已过时内核社区推荐使用新的libgpiod库。它提供了更稳定、功能更丰富的C库和命令行工具gpiodetect,gpioinfo,gpioget/set。在生产项目中应优先考虑。实时性这个用户空间的闪烁程序受Linux系统调度影响间隔并不精确。如果需要精确的定时控制比如PWM应该考虑编写内核驱动或者利用Cortex-M4核来负责实时任务。资源管理程序中直接使用system()调用和硬编码的GPIO号是不严谨的。好的做法是解析设备树动态确定GPIO编号并进行完善的错误处理。从点亮一颗LED开始你已经掌握了STM32MP135D嵌入式Linux开发的精髓理解软硬件接口利用强大的Linux生态系统将想法转化为现实。接下来你可以探索更复杂的领域比如通过I2C/SPI驱动传感器、配置以太网或Wi-Fi实现物联网通信、使用Qt框架构建图形界面甚至利用Cortex-M4核实现硬实时控制。这块小小的板子是一个广阔世界的入口。