说实话,树莓派4在国内嵌入式圈子里一度被当成"玩物"——跑个桌面、看个视频、搭个NAS,真正拿它当嵌入式Linux开发平台来用的人反而不多。但我这几年用过的板子多了之后,反而觉得树莓派4是最适合做嵌入式Linux入门和原型验证的板子之一。它便宜、资料多、坏了重新刷个卡就回来,而且从交叉编译Qt应用到编译内核、改设备树、写驱动模块,整套开发流程它都能完整跑一遍。这篇文章就是我基于树莓派4做嵌入式Linux开发的完整过程记录,包括环境搭建、Qt交叉编译、内核与设备树定制、外设调试排错,以及一条适合新手的学习路线。想认真学嵌入式Linux、又不愿意花大价钱买开发板的人,这篇文章应该能帮你省下不少弯路。
1. 先搞清楚:树莓派4在嵌入式Linux开发里到底扮演什么角色
1.1 为什么我最终选了树莓派4而不是其他开发板
这些年陆陆续续接触过IMX6ULL、全志、瑞芯微的各类开发板,也帮朋友调过厂家的SDK。说实话,商用开发板不是不好,而是学习门槛太高——SDK文档残缺、内核版本老旧、社区问答没人回应,这些问题在树莓派上几乎不存在。嵌入式Linux开发的难点从来不在CPU性能,而在于你能不能控制整个软件栈:引导配置、内核、设备树、rootfs、应用部署。树莓派4恰恰把这几个环节全部开放出来,而且坏了能一键恢复。
| 对比项 | 树莓派4 | 常见ARM商用开发板 |
|---|---|---|
| 社区资料 | 极多,搜索即得 | 参差不齐,常靠厂商售后 |
| 内核生态 | 主线内核支持完善 | 多数依赖厂商BSP,升级困难 |
| 性能 | 4核Cortex-A72,1.5GHz | 从A7单核到A53四核不等 |
| 外设接口 | 40pin GPIO、I2C、SPI、UART全引出 | 看具体型号,常需转接板 |
| 可恢复性 | 重刷SD卡即可满血复活 | 部分板子需专用烧录器 |
如果你做的是量产级的商业产品,当然应该选一颗芯片深入做,而不是拿树莓派顶上去。但如果你是在学习、做原型验证、或者给客户做demo,树莓派4这块板子能让你把"做嵌入式开发"这件事完整地体验一遍,而不至于把精力消耗在厂商工具链的坑里。
1.2 硬件资源盘点:你要用到的核心外设
树莓派4的核心规格值得花点时间看一遍,尤其是做开发之前。
| 项目 | 参数 |
|---|---|
| SoC | BCM2711(4x Cortex-A72,1.5GHz) |
| 内存 | 1/2/4/8GB LPDDR4,没焊接、不可换 |
| 存储 | microSD卡,建议A2级别 |
| 网络 | 千兆以太网(PHY:BCM54213PE)、双频WiFi |
| USB | 2x USB3.0 + 2x USB2.0 |
| 显示 | 2x micro HDMI,支持4K@60 |
| 40pin排针 | GPIO、I2C(1)、SPI(0/1)、UART、PWM、I2S |
对嵌入式开发来说,GPIO、I2C、SPI、UART这四样是日常打交道最多的。GPIO做电平控制,I2C接传感器(比如BMP280、MPU6050),SPI接显示屏或ADC,UART接调试串口和GNSS模块。树莓派4的40pin排针把这几种协议都引出来了,这也是我推荐它的一个重要原因。
1.3 用树莓派跑Linux不等于嵌入式Linux开发
很多人说"我会嵌入式Linux,我在树莓派上装过Ubuntu",这句话的分量其实很轻。真正的嵌入式Linux开发,至少要能回答这几个问题:你的板子启动流程里config.txt和cmdline.txt各干了什么?内核是怎么被加载的?你加的一个LED点不亮,是硬件问题、设备树问题、还是驱动问题?你的Qt程序在x86上编译完,怎么跑到ARM板子上?
我自己带新人的时候常用一个分层来验证:
- 第一层:会用系统,apt装包、敲Linux常用命令;
- 第二层:能在PC上交叉编译应用,部署到板子上运行;
- 第三层:能编译内核、改设备树、加载和编写驱动模块;
- 第四层:能独立制作rootfs、定制镜像、做整机bring-up。
大多数人停在第一层,而树莓派4的完整开发流程刚好可以帮你从第一层走到第四层。后面几章,我会按照这个路径一层层展开。
2. 环境三板斧:镜像烧录、串口输出、交叉编译链
2.1 镜像怎么选:别上来就装桌面版
我见过太多人第一件事就是给树莓派装上带桌面的Ubuntu,然后开个浏览器看视频。嵌入式开发的起步阶段,桌面环境不是必需品,反而是负担——它占用内存、拖慢启动、还容易让你"以为自己在用普通电脑"。
我推荐的做法:用Raspberry Pi Imager烧一个Raspberry Pi OS Lite(64-bit)系统。Lite版本没有桌面,系统干净,后续要什么装什么。64-bit的镜像用aarch64架构,这对后面Qt交叉编译、内核编译都有好处,因为PC上的工具链和板子上的目标架构能直接对应上。
烧录本身很简单:
# 用官方图形工具最省事 # Raspberry Pi Imager 里选择: # Raspberry Pi OS Lite (64-bit) # 命令行方式也可以,Linux/macOS下直接dd sudo dd if=2024-xx-xx-raspios-bookworm-arm64-lite.img of=/dev/sdX bs=4M status=progress第一次开机前,我建议在Imager的"高级设置"里直接配置好SSH、用户名和密码,省得插显示器鼠标键盘。没有显示器的场景下,SSH和串口就是你进入系统的两个通道,而在网络还没起来的时候,串口几乎是唯一通道。
2.2 串口链路:嵌入式开发的第一块"屏幕"
做嵌入式Linux,串口是爹。网络起不来、内核panic、bootloader阶段报错,这些场景下你唯一的调试窗口就是串口。树莓派4的调试串口配置其实很简单,在boot分区的config.txt里加上两行:
enable_uart=1 dtoverlay=disable-btenable_uart=1把UART功能打开,dtoverlay=disable-bt把蓝牙占用的那个UART释放出来,让排针上的GPIO14/GPIO15作为调试串口工作。接线也简单,USB转TTL模块(3.3V电平,常见CP2102、CH340都可以)和树莓派这样连:
- 树莓派GPIO14(TXD)接模块RX
- 树莓派GPIO15(RXD)接模块TX
- 两边GND连一起
注意,树莓派的GPIO是3.3V电平,绝对不要用那个带5V跳线的电平转换器往GPIO上怼,一怼一个不吱声,烧过的人很多。接到电脑后,用picocom或者minicom都能看到输出:
sudo apt install picocom picocom -b 115200 /dev/ttyUSB0通电之后,你会在终端里看到VideoCore固件打印、内核启动日志、最后是登录提示符。看到这一串日志,你的调试链路就算打通了。这一步我建议每个人都多做几次——反复刷机、反复观察启动输出,你会慢慢培养出"看日志就知道卡在哪"的感觉。
2.3 交叉编译链:让你的PC替树莓派"打工"
树莓派4虽然性能和PC没法比,但也不是完全不能本地编译。问题是Qt这种工程,在板子上编译一次动辄半小时起步,而在PC上交叉编译只要几秒。交叉编译的思路就是:用PC上的x86编译器和工具链,生成aarch64架构的ARM程序,再部署到树莓派上运行。
Ubuntu主机上安装方法:
sudo apt update sudo apt install crossbuild-essential-arm64 aarch64-linux-gnu-gcc --version顺手可以再装一个qemu-user-static:
sudo apt install qemu-user-staticqemu-user-static的作用是让PC能用模拟方式直接运行ARM程序,后面检查sysroot里的二进制文件、验证编译产物时非常方便。写完交叉编译链验证程序,可以做个最小测试:
#include <stdio.h> int main(void) { printf("hello embedded linux\r\n"); return 0; }aarch64-linux-gnu-gcc hello.c -o hello file hello # 期望输出:ELF 64-bit LSB executable, ARM aarch64 scp hello pi@192.168.x.x:~/树莓派上直接运行./hello,能打印出内容,说明交叉编译的环境就算真正成型了。到这一步,你已经完成了从"会用板子"到"能给板子做软件"的跨越。
3. Qt交叉编译实战:sysroot、CMake工具链和部署坑
3.1 目标端的Qt从哪来:两种路线
Qt交叉编译是很多人卡住的地方,核心问题只有一个:编译时需要的Qt头文件和库,从哪儿来?
大体有两条路线。路线A是在PC上把整个Qt源码交叉编译一遍,得到一套ARM版本的库,再传到板子上。好处是可控性强,缺点是耗时、复杂,新手很容易在源码依赖上栽跟头。路线B是树莓派直接apt安装官方Qt库,然后把板子上相关的头文件和库同步到PC上做sysroot,PC上的CMake针对sysroot里的Qt来编译。我推荐路线B,它最贴近真实商业项目的做法——目标板用发行版/自己构建的库,开发机上用对应的sysroot来交叉编译应用。
先在树莓派上装好Qt基础包:
sudo apt install qtbase5-dev qtdeclarative5-devqtbase5-dev里有Qt5核心Widgets相关头文件,qtdeclarative5-dev是QML那套,按需安装。装了dev包之后,sysroot里才有编译所需的头文件和带符号的链接库。
3.2 rsync同步sysroot:这一步决定你编译能不能过
sysroot说白了就是目标板的根文件系统的一个"剪影",交叉编译时,编译器和链接器从里面找头文件和库。同步建议用rsync,不要用scp,因为rsync可以增量、可以删掉过期文件:
mkdir -p ~/rpi-sysroot rsync -avz --delete pi@192.168.x.x:/usr/include ~/rpi-sysroot/usr/ rsync -avz --delete pi@192.168.x.x:/usr/lib/aarch64-linux-gnu ~/rpi-sysroot/usr/lib/ rsync -avz --delete pi@192.168.x.x:/lib/aarch64-linux-gnu ~/rpi-sysroot/lib/--delete参数很重要,目标板升级了某个库之后,旧的库文件留在sysroot里会造成"编译时用的新头文件、链接时撞上旧库"这种非常隐蔽的错误。我吃过这个亏,查了两个小时才意识到是sysroot里的一个旧so文件在作祟。
另外,Debian系系统的Qt库经常是绝对路径的符号链接,比如/usr/lib/aarch64-linux-gnu/libQt5Core.so -> /usr/lib/aarch64-linux-gnu/libQt5Core.so.5.15.2。rsync回来之后,在PC上这些绝对路径的符号链接会指向错误的地方,常见的解决办法是把绝对符号链接改成相对链接,网上一搜"symlink fix sysroot"能找到现成脚本。
3.3 编写CMake工具链文件
sysroot就绪后,在PC上建一个工具链文件,CMake会说人话就靠它:
# ~/toolchain-rpi4.cmake set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) set(CMAKE_SYSROOT $ENV{HOME}/rpi-sysroot) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) # 程序类工具还是在PC上找,库和头文件都在sysroot里找 set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_BUILD_TYPE Release)三个FIND_ROOT_PATH_MODE是交叉编译的灵魂:PROGRAM设成NEVER,避免CMake在sysroot里找PC工具;LIBRARY、INCLUDE、PACKAGE设成ONLY,强制只在sysroot里找库和头文件。很多人交叉编译失败,就是忘了配置这几个Mode,CMake在PC本机的/usr里找到x86版本的Qt,编译出一堆架构不匹配的错误。
3.4 编译、部署、运行一条龙
先写一个最简单的Qt程序:
#include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("HELLO EMBEDDED QT"); label.resize(320, 120); label.show(); return app.exec(); }配套的CMakeLists.txt:
cmake_minimum_required(VERSION 3.16) project(helloqt) set(CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) find_package(Qt5 REQUIRED COMPONENTS Widgets) add_executable(helloqt main.cpp) target_link_libraries(helloqt Qt5::Widgets)编译命令:
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$HOME/toolchain-rpi4.cmake cmake --build build -j$(nproc)编译完成后,build/helloqt就是aarch64的Qt程序了。部署和运行:
scp build/helloqt pi@192.168.x.x:~/ ssh pi@192.168.x.x # 有桌面环境时直接运行 ./helloqt # 无桌面环境时,可以用offscreen插件 QT_QPA_PLATFORM=offscreen ./helloqt如果只想验证程序能启动,offscreen是最省事的。要是想看界面效果,在树莓派上装个轻量桌面或者用xvfb-run -a ./helloqt跑虚拟显示,都行。
3.5 部署期最大的坑:运行库依赖和插件路径
交叉编译Qt程序,编译只是前半程,真正的坑在运行时。我遇到过的情况:
程序在板子上跑不起来,报类似"error while loading shared libraries"时,先在板子上用ldd看缺什么:
ldd ./helloqt缺的库,从PC的sysroot里同名路径复制到树莓派对应路径,比如:
scp ~/rpi-sysroot/usr/lib/aarch64-linux-gnu/libQt5Xxx.so.5 pi@192.168.x.x:/usr/lib/aarch64-linux-gnu/另一个高频错误是:程序能启动但报"could not find or load the Qt platform plugin"。这个是因为Qt的platform插件(比如linuxfb、eglfs、xcb)没有在默认路径被找到。解决办法是在树莓派上设置环境变量指向插件目录:
export QT_QPA_PLATFORM_PLUGIN_PATH=/usr/lib/aarch64-linux-gnu/qt5/plugins/platforms export QT_QPA_PLATFORM=offscreen ./helloqt最后,别忘了检查架构。如果编译出来的是32位ARM程序,而目标板是纯64位rootfs,运行时会因为缺32位运行库直接Segmentation fault。编译后用file helloqt看一眼,是我调试时的一个习惯。
4. 内核与设备树定制:从编译内核到接一个传感器
4.1 内核源码获取并交叉编译
嵌入式Linux进阶的必经之路是编译自己的内核。树莓派官方内核仓库一直在维护,拉一个稳定分支:
git clone --depth=1 --branch rpi-6.6.y https://github.com/raspberrypi/linux ~/linux-rpi cd ~/linux-rpi make ARCH=arm64 bcm2711_defconfig make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image modules dtbs编译产物里,arch/arm64/boot/Image就是64位内核镜像,arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb是树莓派4的设备树二进制,内核模块则散布在源码树里。
部署前一定先备份原文件:
ssh pi@192.168.x.x sudo cp /boot/kernel8.img /boot/kernel8.img.bak sudo cp /boot/bcm2711-rpi-4-b.dtb /boot/bcm2711-rpi-4-b.dtb.bak然后从PC上传替换:
scp arch/arm64/boot/Image pi@192.168.x.x:/tmp/kernel8.img ssh pi@192.168.x.x "sudo mv /tmp/kernel8.img /boot/kernel8.img" scp arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb pi@192.168.x.x:/tmp/ ssh pi@192.168.x.x "sudo mv /tmp/bcm2711-rpi-4-b.dtb /boot/"重启后uname -a看到自己的内核版本字符串,那种感觉确实不一样。为什么要自己编译内核?除了满足好奇心,更多是因为你要往内核里加一个配置、加一个驱动、或者要调试一个只有你能复现的内核问题。RPi官方内核已经比较完善,但学习内核构建流程本身就是嵌入式Linux的核心技能。
4.2 设备树是硬件说明书,不是驱动里的硬编码
早期Linux内核用一堆board文件硬编码平台设备,换一个板卡就要改代码重编。现在主流做法是设备树——一份描述硬件的数据结构,内核启动时解析它来得知"这块板子上有什么、地址在哪、中断怎么接"。
设备树的基础结构就是一个树:
/dts-v1/; / { compatible = "brcm,bcm2711"; soc { gpio: gpio@7e200000 { compatible = "brcm,bcm2711-gpio"; reg = <0x7e200000 0xb4>; gpio-controller; #gpio-cells = <2>; }; }; };compatible是设备树里最重要的属性,内核driver通过它和device匹配;reg表Address/Size;#gpio-cells = <2>表示GPIO引用需要两个cell(一个管脚号,一个flags)。掌握设备树不需要写得多花哨,关键是看懂、会改。
对于树莓派,官方推荐的做法不是直接改主dtb,而是写overlay——单独编译一个.dtbo文件,在config.txt里用dtoverlay加载。比如我要在I2C1总线上挂一个BMP280气压传感器,overlay写起来大概长这样:
/dts-v1/; /plugin/; / { compatible = "brcm,bcm2711"; fragment@0 { target = <&i2c_arm>; __overlay__ { #address-cells = <1>; #size-cells = <0>; bmp280@76 { compatible = "bosch,bmp280"; reg = <0x76>; }; }; }; };编译:
dtc -@ -I dts -O dtb -o bmp280.dtbo bmp280.dts把bmp280.dtbo传到树莓派的/boot/overlays/里,config.txt加一行dtoverlay=bmp280,重启后:
ls /sys/bus/i2c/devices/1-0076/ cat /sys/bus/i2c/devices/1-0076/name # 输出 bmp280,说明设备树和内核驱动对接成功了这个例子看起来简单,但完整走通后,你对"设备树描述硬件、驱动匹配compatible、sysfs暴露设备"这整条链路会有直观理解。
4.3 写一个最小的字符设备驱动
设备树解决的是"硬件怎么被描述",驱动解决的是"硬件怎么被操作"。学习驱动,从hello模块开始:
#include <linux/init.h> #include <linux/module.h> static int __init hello_init(void) { pr_info("hello: module loaded\n"); return 0; } static void __exit hello_exit(void) { pr_info("hello: module unloaded\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");Makefile:
obj-m += hello.o all: make -C /path/to/linux-rpi M=$(PWD) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules clean: make -C /path/to/linux-rpi M=$(PWD) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- clean编译后得到hello.ko,上传到板子:
scp hello.ko pi@192.168.x.x:~/ ssh pi@192.168.x.x sudo insmod hello.ko dmesg | tail # 看到 hello: module loaded sudo rmmod hello dmesg | tail # 看到 hello: module unloaded这里面有个很现实的坑:insmod报"Invalid module format"时,十有八九是模块的vermagic和当前运行内核不匹配。模块是用哪个内核源码编译的,就必须在哪个内核上加载。所以如果你用的是官方预编译内核,就要拿到对应源码版本来编模块;最简单的方式是先按4.1节自己编译并部署了内核,再用同一份源码编模块,就不会有vermagic冲突。
4.4 进阶话题:PHY的MDIO与I2C、DSA交换芯片
驱动方向走到深处,就会碰到网络相关的硬件。树莓派4的千兆网PHY是BCM54213PE,挂在GENET MAC的MDIO总线上,你在设备树里能看到mdio0节点下挂着phy节点。
有些PHY芯片(比如Marvell 88E1512、部分Realtek型号)除了MDIO还提供I2C配置接口。如果遇到"PHY不响应MDIO报文"的问题,先别急着怀疑代码,查一下硬件上PHY的配置引脚到底选的是MDIO还是I2C模式,设备树里对应写的是哪个总线。走I2C的PHY,节点不在mdio下,而是挂在i2c总线上,用phy的compatible和reg描述。
再往上走,多口交换芯片会用到DSA(Distributed Switch Architecture)框架,代码在drivers/net/dsa里,设备树里用dsa节点加ethernet-ports子节点描述。这个方向属于嵌入式网络进阶内容,和树莓派4关系不大,等基础的内容都掌握之后再看也不迟。
5. 调试三板斧:启动日志、dmesg、逻辑分析仪
5.1 板子启动不了:按阶段看串口日志
我见过太多新手遇到板子起不来就直接格式化SD卡重刷,其实多数情况下串口日志已经把答案写出来了。排查启动问题,我按现象分成几类:
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| 完全无串口输出 | 接线问题或UART未开启 | 检查GPIO14/15接法、enable_uart=1 |
| 只有固件输出,然后彩虹屏 | 内核镜像缺失或损坏 | 检查kernel8.img是否为有效Image |
| 内核启动后panic | dtb与内核不匹配 | 恢复备份dtb,检查overlay加载 |
| 挂载rootfs失败 | cmdline.txt的root分区号错了 | 检查cmdline.txt和SD分区表 |
| 应用跑不起来 | 缺库、架构不匹配 | ldd、file检查依赖和架构 |
"彩虹屏"是树莓派比较特有的信号:屏幕显示彩虹色块说明VideoCore固件活着,但内核没有成功加载。看到彩虹屏,优先检查boot分区里内核文件、config.txt里的kernel=配置、以及SD卡是否接触不良。
启动流程大致分三段:固件阶段(config.txt)、内核阶段(设备树解析、驱动加载)、用户态阶段(init进程、服务启动)。串口日志里每一段都有明显的标志性输出,平时多刷机、多观察,形成肌肉记忆,排错会快很多。
5.2 外设调试:i2cdetect、dmesg、gpiod
应用层外设调试,我常用的三个命令就够开局:
# I2C总线扫描 i2cdetect -y 1 # 实时看内核日志 dmesg -w # GPIO操作 sudo apt install gpiod gpiodetect gpioget 0 17 gpioset 0 17=1i2cdetect -y 1扫描I2C1总线,输出表里出现设备地址,说明硬件连接至少是通的;如果地址显示"UU",说明这个地址已经被内核驱动占用,这也是一种正常的成功态。GPIO操作新内核推荐用libgpiod工具,老旧的sysfs方式在6.x内核里已经逐渐移除了。
5.3 我实际踩过的几个坑
这些坑不写下来,你可能要花好几个晚上去踩:
第一,串口线的RX/TX接反了,症状是完全无输出或者全是乱码。我早期一度以为连接坏了,后来发现是TX接TX了。解决方案就是交叉换线,任何一次新接线都可以先互换再试。
第二,config.txt改坏了导致板子起不来。症状是"彩虹屏",或者连固件阶段都过不去。所以我每次动config.txt前都会先备份一份,哪怕只是加一行注释也习惯性备份。这个习惯救过我很多次。
第三,电源供电不足。树莓派的USB口和网络在供电不稳时会出现各种诡异问题,表现为USB设备随机掉线、千兆网降速、串口偶尔乱码。别在USB口上取太多电,老老实实用5V/3A的适配器从Type-C口供电,能免掉一大批"玄学问题"。
第四,GPIO浮空导致按键误触发。最开始用GPIO读按键信号,不接上拉也不接下拉,结果逻辑电平乱跳。解决方法是启用内部上拉,或者在硬件上加一个10K上拉到3.3V。
第五,把3.3V设备接到5V引脚上,烧掉传感器。这是新人最容易犯的。树莓派GPIO是3.3V电平,接任何外设前先确认电平匹配,再用万用表量一遍再上电。
5.4 性能与稳定性检查
开发到一定阶段,你会开始关心板子的运行状态。树莓派有很方便的专属命令:
vcgencmd measure_temp vcgencmd measure_clock arm vcgencmd measure_volts温度经常超过80度,就要考虑散热片和风扇了,过热降频会直接影响程序时序。压力测试可以用stress-ng:
sudo apt install stress-ng stress-ng --cpu 4 --timeout 60s压测的同时用top观察温度有没有飙到危险区间,用dmesg看有没有出现"thermal throttling"之类的告警。网络性能就直接上iperf3测吞吐,千兆环境能跑到800Mbps以上基本没大问题。内存紧张的场景,可以给树莓派配zram或swap,但这个属于调优话题,用到再说。
6. 一条更适合新手的嵌入式Linux学习路线(个人版)
6.1 我的建议顺序
经常有人问我嵌入式Linux怎么学、从哪里开始。基于我自己带人踩坑的经验,给一个可执行性比较高的顺序:
- 阶段1:Linux命令行和Shell基础。先熟练掌握ls、cd、ps、top、grep、sed、awk、find这些高频命令,配合shell脚本把重复操作自动化。不需要背完,随用随查,但高频命令要形成条件反射。
- 阶段2:交叉编译入门。写出一个C程序,用Makefile/CMake管理,交叉编译部署到树莓派上跑通。这一步建立"PC开发、板子运行"的心智模型。
- 阶段3:启动流程。看串口日志,理解config.txt、cmdline.txt、内核镜像加载顺序。多刷机,多观察启动输出的差异。
- 阶段4:内核模块和简单驱动。从hello模块开始,再到一个真实的GPIO或I2C驱动设备。理解module_init、probe、file_operations这一套驱动生命周期。
- 阶段5:设备树。写一个overlay挂实际传感器,跑通"设备树描述->驱动匹配->sysfs访问"链路。
- 阶段6:应用层。以Qt交叉编译为一个锚点,把线程、socket、定时器、看门狗这些嵌入式常用应用技术一起串起来。
- 阶段7:整体项目。做一个完整的综合Demo,比如温湿度采集加本地显示加网络上报,把前面各层能力整合。
这个路线不是线性的,阶段3到阶段5之间会反复横跳,很正常。关键是每个阶段都要有看得见的成果。
6.2 一些反面教训
学习路上我见过太多半途而废的人,死法基本一致:资料囤了几百个G,文章收藏了上千篇,就是不动手;要么就是过于理想化,跳过了环境基础直接去啃芯片手册,被英文文档和寄存器劝退。
嵌入式Linux是典型的"练出来的技能"。同一个知识点,看文章三分钟就懂,自己上手可能要三小时,但这三小时的收获远远大于看三篇文章。树莓派4特别适合犯错的另一个原因是容错性强:板子起不来就重刷内核,驱动写崩了重刷SD卡,不需要额外买仿真器,试错成本很低。
最后分享一个小技巧:每次做实验之前,把系统状态保持成"可恢复"——config.txt和cmdline.txt做好备份、内核和dtb留好还原副本、关键文件放一份到PC上。有了这个习惯,你可以在树莓派上大胆折腾,反正一分钟就能滚回上一个稳定状态。嵌入式Linux开发的真正门槛不是知识量,而是有没有一条完整可复现的调试链路。树莓派4就是练这条链路最划算的板子,先把串口跑通,再一层层往上打通,剩下的就是时间和耐心的问题了。