2、开发环境搭建:交叉编译工具链安装、Makefile基础、CMake基础、版本控制(Git)入门

说实话,搭建开发环境这事儿,看着简单,但坑是真不少。我见过太多人卡在第一步——工具链装不上,或者装上了路径不对,编译出来跑不了。咱们今天就把这事儿捋清楚。

2.1 交叉编译工具链安装

什么叫交叉编译?说白了,就是在你的电脑上编译出能在另一个设备上跑的程序。比如你在 x86 的笔记本上编译 ARM 的固件。嗯,这里要注意,工具链的选择直接决定了你后面能不能顺利干活。

2.1.1 工具链的选择

我个人习惯用 Linaro 提供的工具链,稳定且社区活跃。当然,如果你用的是芯片厂商的 SDK,那直接用他们配好的也行。

  • ARM 架构:推荐 gcc-arm-none-eabi(裸机)或 gcc-arm-linux-gnueabihf(带 Linux 系统)
  • RISC-V 架构:推荐 riscv64-unknown-elf-gcc
  • MIPS 架构:推荐 mips-linux-gnu-gcc
注意:我曾经因为用了版本太新的工具链,结果编译出来的固件在旧版内核上跑不起来。建议先查一下目标系统的 glibc 版本,再选对应的工具链。

2.1.2 安装步骤(以 ARM 为例)

下载解压,然后配置环境变量。就这么两步,但细节决定成败。

# 下载工具链(以 gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz 为例)
wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.07/gcc-arm-none-eabi-10.3-2021.07-x86_64-linux.tar.bz2

# 解压到 /opt 目录
sudo tar -xjf gcc-arm-none-eabi-10.3-2021.07-x86_64-linux.tar.bz2 -C /opt/

# 配置环境变量(写入 ~/.bashrc 或 ~/.zshrc)
export PATH=$PATH:/opt/gcc-arm-none-eabi-10.3-2021.07/bin
export CROSS_COMPILE=arm-none-eabi-
export ARCH=arm

# 使配置生效
source ~/.bashrc

# 验证安装
arm-none-eabi-gcc --version
小技巧:我习惯把工具链路径单独写到一个脚本文件里,比如 setenv.sh,每次开新终端时 source 一下。这样不同项目可以用不同版本的工具链,互不干扰。

2.2 Makefile 基础

Makefile 这东西,说白了就是告诉编译器怎么干活。你想想看,一个项目几十个源文件,总不能每次都手动敲 gcc 命令吧?

2.2.1 最简单的 Makefile

# 目标文件: 依赖文件
main: main.o utils.o
    gcc -o main main.o utils.o

main.o: main.c
    gcc -c main.c

utils.o: utils.c
    gcc -c utils.c

clean:
    rm -f *.o main

嗯,这里要注意,Makefile 里的缩进必须用 Tab 键,不能用空格。我刚开始学的时候被这个坑过,编译报错半天找不到原因。

2.2.2 变量与自动推导

实际项目中,我们不会这么傻写。用变量和自动推导规则,代码会清爽很多。

CC = gcc
CFLAGS = -Wall -O2
TARGET = main
OBJS = main.o utils.o

$(TARGET): $(OBJS)
    $(CC) $(CFLAGS) -o $@ $^

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

clean:
    rm -f $(OBJS) $(TARGET)

.PHONY: clean
关键点$@ 表示目标文件,$^ 表示所有依赖文件,$< 表示第一个依赖文件。这三个自动变量,你写 Makefile 时天天都会用到。

2.3 CMake 基础

Makefile 虽然好用,但跨平台时就很头疼。CMake 的出现就是为了解决这个问题——你写一份 CMakeLists.txt,它能自动生成对应平台的 Makefile 或项目文件。

2.3.1 一个简单的 CMake 项目

# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyProject)

set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11)

# 添加可执行文件
add_executable(main main.c utils.c)

# 添加头文件路径
target_include_directories(main PRIVATE include)

编译方式也很简单:

mkdir build
cd build
cmake ..
make
我的习惯:我一般会在 build 目录里编译,这样源码目录保持干净。想清理时,直接 rm -rf build 就行,不用一个个删 .o 文件。

2.3.2 交叉编译的 CMake 配置

做嵌入式开发时,CMake 的交叉编译配置是必学的。我曾经在给一个 ARM 项目配 CMake 时,忘了设置 CMAKE_SYSROOT,结果链接器找不到标准库,折腾了一下午。

# toolchain.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

使用时加上 -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake 参数即可。

2.4 版本控制(Git)入门

Git 这东西,说实话,不会用的话项目根本没法管。我见过有人用「复制文件夹+改日期」来管理版本,结果出了 bug 根本不知道改了什么。

2.4.1 基本工作流

# 初始化仓库
git init

# 添加文件到暂存区
git add main.c utils.c

# 提交到本地仓库
git commit -m "初始提交:添加主程序与工具函数"

# 查看状态
git status

# 查看提交历史
git log --oneline

2.4.2 分支管理

我个人习惯用 main 分支作为稳定版本,dev 分支做开发。每次修 bug 或加功能,都从 dev 拉一个新分支,改完再合并回去。

# 创建并切换到 dev 分支
git checkout -b dev

# 在 dev 上创建功能分支
git checkout -b feature/add-uart-driver

# 开发完成后合并回 dev
git checkout dev
git merge feature/add-uart-driver

# 删除功能分支
git branch -d feature/add-uart-driver
避坑指南:我曾经在合并分支时,因为没先 pull 远程仓库的最新代码,结果冲突一大堆。建议合并前先 git pull --rebase 一下,保持提交历史的整洁。

2.4.3 .gitignore 文件

编译生成的 .o 文件、build 目录、IDE 的配置文件,这些都不应该提交到仓库里。写一个 .gitignore 文件,省心很多。

# .gitignore
build/
*.o
*.elf
*.bin
*.hex
.vscode/
.idea/
*.swp
总结一下:工具链是基础,Makefile 和 CMake 是构建工具,Git 是版本管理。这三样东西,你花一周时间熟练掌握,后面写代码的效率能翻倍。我当年就是吃了「工具不熟」的亏,现在想想,真没必要。