第2章:嵌入式开发环境搭建

做BSP开发,第一件事不是写代码,而是搭环境。我见过太多新手,上来就敲代码,结果编译报错半天找不到原因——最后发现是工具链版本不对。说白了,环境搭不好,后面全是坑。

这一章,我把嵌入式开发最核心的几个环境工具串起来讲。交叉编译工具链、调试器、串口终端、网络文件系统,一个一个来。

2.1 交叉编译工具链

嵌入式设备性能有限,没法直接在板子上编译代码。我们得在PC上编译,生成目标板能运行的二进制文件。这就是交叉编译。

工具链的组成:

  • 编译器:gcc(针对ARM、RISC-V等架构)
  • 链接器:ld
  • 调试器:gdb(交叉编译版本)
  • 库文件:glibc / uclibc / musl
  • 头文件:内核头文件、C库头文件

我个人的习惯:用Linaro提供的预编译工具链,省事。比如针对ARM Cortex-A系列,我常用gcc-arm-none-eabi或aarch64-linux-gnu-gcc。

安装示例(Ubuntu 22.04):

sudo apt install gcc-arm-linux-gnueabihf
sudo apt install gcc-aarch64-linux-gnu

为什么推荐这个?因为Linaro的版本跟主流内核配合得好。我曾经自己编译过一次glibc,折腾了两天,最后发现某个补丁没打上,程序跑起来就段错误。从那以后,除非有特殊需求,我直接用官方预编译包。

验证工具链是否正常:

arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc -o hello hello.c
file hello  # 查看生成的文件架构

嗯,这里要注意:交叉编译出来的文件,在PC上跑不了。你得用file命令确认架构对不对。我遇到过有人把ARM的二进制直接扔到x86上跑,然后问我为什么报错……

2.2 调试器:JTAG / SWD

调试嵌入式系统,光靠printf可不够。尤其是BSP开发,你要看寄存器、看内存、看PC指针。这时候就得用调试器。

JTAG vs SWD:

特性 JTAG SWD
引脚数 5(TMS, TCK, TDI, TDO, nTRST) 2(SWDIO, SWCLK)
速度 较高 中等
适用场景 FPGA、复杂SoC ARM Cortex-M系列
调试能力 全功能(边界扫描、Flash编程) 基本调试(断点、单步)

我个人做BSP时,ARM Cortex-A系列用JTAG,Cortex-M系列用SWD。为什么?因为A系列要调试MMU、Cache这些复杂东西,JTAG的边界扫描功能很有用。M系列嘛,SWD两根线就够了,省引脚。

常用调试工具:

  • OpenOCD:开源,支持JTAG/SWD,配合GDB使用
  • J-Link:SEGGER出品,稳定,但贵
  • ST-Link:ST官方,便宜,适合STM32

OpenOCD + GDB 调试示例:

# 启动OpenOCD(假设使用J-Link)
openocd -f interface/jlink.cfg -f target/stm32f4x.cfg

# 另一个终端启动GDB
arm-none-eabi-gdb my_program.elf
(gdb) target remote localhost:3333
(gdb) monitor reset halt
(gdb) load
(gdb) continue

我曾经在调试一个DMA驱动时,发现数据总是传错。用JTAG单步跟踪,发现DMA的源地址寄存器写错了——因为结构体对齐问题。要是没有调试器,这种bug靠看代码根本看不出来。

2.3 串口终端

串口是嵌入式开发者的「眼睛」。板子启动时打印的日志、内核的早期输出、甚至交互式shell,都靠它。

常用工具:

  • minicom:经典,配置稍复杂
  • screen:轻量,一行命令搞定
  • putty:Windows用户最爱
  • picocom:我最近常用,简洁

快速启动串口(Linux):

sudo picocom -b 115200 /dev/ttyUSB0
# 或
sudo screen /dev/ttyUSB0 115200

这里有个坑:串口波特率一定要跟uboot或内核设置的一致。我遇到过有人板子uboot设的是115200,但内核里改成了57600,结果uboot能打印,内核启动后全是乱码。嗯,这种问题排查起来很烦人。

串口参数速查表:

参数 常见值 说明
波特率 115200 / 57600 / 9600 越高越快,但线长时容易丢数据
数据位 8 几乎都是8位
停止位 1 偶尔用2位
校验位 无 大多数场景不用

2.4 TFTP / NFS 网络文件系统搭建

开发阶段,你不可能每次改个驱动都重新烧写整个镜像。太慢了。用网络加载内核和设备树,用NFS挂载根文件系统,这才是高效的做法。

TFTP(Trivial File Transfer Protocol):

说白了,就是通过网络把文件从服务器拉到板子上。uboot里常用。

搭建TFTP服务器(Ubuntu):

sudo apt install tftpd-hpa
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp
# 编辑配置文件 /etc/default/tftpd-hpa
# 确保 TFTP_DIRECTORY="/srv/tftp"

然后在uboot里:

setenv serverip 192.168.1.100
setenv ipaddr 192.168.1.10
tftp 0x82000000 zImage
tftp 0x83000000 myboard.dtb
bootz 0x82000000 - 0x83000000

NFS(Network File System):

根文件系统挂在NFS上,板子启动后直接读写服务器上的文件。改代码、编译、重启验证,一气呵成。

搭建NFS服务器:

sudo apt install nfs-kernel-server
sudo mkdir -p /srv/nfs/rootfs
# 编辑 /etc/exports,添加:
/srv/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)
sudo exportfs -a

内核启动参数里加上:

root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs rw ip=dhcp

注意:NFS的no_root_squash选项很重要。如果不加,板子上的root用户会被映射成nobody,写文件会权限不足。我曾经因为这个,调试一个驱动时死活写不了日志文件,折腾了一下午才发现是NFS权限问题。

知识体系总览

下面这张图,把本章的核心逻辑串起来了。你想想看,从编译到调试,再到运行和文件传输,每一步都有对应的工具。

嵌入式开发环境搭建 - 核心流程 开发机(PC) 交叉编译工具链 目标板(ARM/RISC-V) JTAG / SWD 调试器 串口终端(日志/Shell) TFTP / NFS 网络传输 编译生成二进制 调试/烧写 日志输出/交互 内核/文件系统传输 开发机负责编译和调试,目标板负责运行,串口和网络是沟通的桥梁

这张图里,开发机通过交叉编译生成目标板能跑的二进制,然后用JTAG/SWD烧写或调试,串口看输出,网络传文件。四个工具配合起来,开发效率能翻好几倍。

我的建议:刚开始学BSP开发,先把串口和TFTP/NFS搭好。这两个能让你快速迭代代码。调试器可以等遇到复杂bug时再深入研究。别一上来就搞OpenOCD+GDB,容易劝退。

好了,环境搭建这部分就讲到这里。下一章我们开始真正接触BSP的核心——启动流程。到时候你会看到,uboot是怎么把控制权交给内核的,中间又经历了哪些关键步骤。

可可精品资料库