[post] 中大型嵌入式项目开发与管理:构建系统

📅 2025-03-29

🔄 2025-03-29

⌚ Reading time: 3 min


大型嵌入式项目的开发与管理:构建系统

在嵌入式系统开发中,项目的规模和复杂性随着需求的增加而不断扩大。对于小型项目,开发者通常能够直接操作硬件,利用简洁的编译和开发环境进行开发。这类项目通常是裸机开发,例如使用 C51 或 STM32 等微控制器,通过集成开发环境(IDE)来进行开发。开发者通常专注于核心业务功能的实现,而不关心过多的底层细节,如编译过程、链接器配置或系统架构。这种方式的优点是快速实现和简单的开发流程,但在面对更复杂的需求时往往显得力不从心。

例如,使用 C51 的经典项目通常包括简单的应用,如控制一个 LED 的闪烁、读取传感器数据并输出,或者其他单一功能的嵌入式系统。这些项目通常不涉及操作系统或复杂的硬件抽象层,开发者只需要专注于代码的实现和调试。

同样,使用 STM32 的开发者通常通过 STM32CubeIDE 或 Keil MDK 等集成开发环境进行开发。这些工具提供了完整的硬件支持库和简化的项目设置,开发者能够快速构建和测试程序,只需关注核心业务逻辑,而不必过多处理与硬件平台和构建系统相关的细节。

然而,随着项目规模的扩大,特别是当项目需要支持多个硬件平台、复杂的功能模块以及持续集成等需求时,裸机开发的方式就暴露出其局限性。此时,构建系统就显得尤为重要。

什么是构建系统?

构建系统是自动化处理代码构建过程的工具,通常包括以下功能:

  • 源代码编译:将源代码(如 .c、.cpp 文件)编译为目标文件(.o、.obj 文件)。
  • 模块依赖管理:在大型项目中,代码模块之间通常有复杂的依赖关系。构建系统能够自动识别依赖,并按正确顺序进行编译和链接。
  • 配置管理:根据项目需求或目标平台,配置哪些模块需要启用,哪些功能需要裁剪。
  • 跨平台支持:支持不同的硬件平台、操作系统或编译器工具链,以便生成适配不同目标平台的可执行文件或固件。

在嵌入式开发中,编译通常并非在目标硬件平台上进行,而是通过交叉编译来生成目标平台的代码。交叉编译工具链(如 Keil、IAR 和 GCC)是构建系统的核心组成部分,负责将开发环境中的源代码编译成目标平台能够执行的二进制文件。在构建系统中,交叉编译工具链的选择和配置直接影响到项目的编译速度、生成代码的效率以及对目标平台的支持能力。构建系统需要能够灵活地集成不同的交叉编译工具链,并根据目标硬件架构和操作系统配置相应的编译参数。通过这一过程,开发者能够确保代码能够在不同的硬件平台上正确运行。

本篇文章以 RT-Thread 为代表,结合 Linux Kernel、U-Boot、PX4-Autopilot 等开源项目,系统性探讨大型嵌入式项目的开发管理思路、构建系统对比及实际工程中可复用的最佳实践。

1 中大型嵌入式项目开发特征,使用构建系统的必要性

1.1 特征一:多模块与组件化架构,依赖管理,功能可裁剪与配置能力

大型项目功能全面,但是项目中的嵌入式设备是高度定制化的,并不需要用到全部功能, 由此带来的,依赖管理。如传感器软件包依赖通信接口的驱动 功能裁剪与配置,功能本身的裁剪,功能的配置,如引脚等

中大型嵌入式项目往往采用多模块、组件化架构来组织系统。每个模块对应一类功能或外设,可单独开发、调试和裁剪,便于项目的可维护性和可扩展性。

以 RT-Thread 中的 OLED 显示模块为例:

OLED 模块是一个可裁剪模块,用户可以根据项目需求选择是否启用该功能。

OLED 显示屏的驱动接口可配置,支持使用 IIC 或 SPI 接口与主控通信。

若选择 IIC 接口,系统会依赖一个 IIC 驱动模块。此驱动本身也具有多种实现方式,比如:

使用硬件 IIC 控制器;

使用模拟 IIC(Soft IIC) 实现。软 IIC 驱动中,GPIO 引脚是可配置的,需根据硬件实际连接进行适配。

OLED 驱动模块对外只暴露一组标准接口(如初始化、写数据、更新显示等),而具体使用哪种底层通信方式则由构建系统根据配置决定。

这一系列的裁剪与配置动作,都可以通过 RT-Thread 的 menuconfig 系统完成,底层由 Kconfig 文件和构建系统配合解析,并由 SCons 构建工具根据配置决定最终构建哪些代码。

这体现了模块之间的依赖(OLED 依赖 IIC/SPI,IIC 又依赖 GPIO)、功能可裁剪(启用或关闭 OLED)、配置灵活性(选择接口和引脚等),以及构建系统对这些特性的有力支持。

由此带来的软件上的一些设计:使用自动著恶策和初始化流程,使得不需要做源码级别上的修改实现对功能的初始化

不是所有的功能都在所有设备中都启用,裁剪机制变得非常关键:

  • RT-Thread 和 Linux 使用 Kconfig/menuconfig 来做配置裁剪
  • PX4 使用 cmake + config.cmake 组合管理功能启用
  • Zephyr 使用 Kconfig + west 统一配置与依赖管理

通过良好的配置机制,不仅可以精简目标固件大小,还可以减少编译依赖、加快构建速度。

1.2 特征二:多平台、多架构支持

三个层次:架构的不同,芯片的不同,单板级硬件的不同

大型项目往往需要支持多种硬件平台,这种支持分为三个层次:

一、架构层级差异 处理器架构的不同直接影响到指令集、寄存器模型、启动流程、异常管理等底层行为。比如:

ARM Cortex-M 系列就包含 Cortex-M3/M4/M7,用于中低端嵌入式设备;

Cortex-A 系列如 Cortex-A7/A53,则多用于高性能嵌入式设备;

新兴架构如 RISC-V,因其开源特性在近年来快速发展,受到越来越多项目支持。

构建系统需要根据架构选择相应的编译器、链接脚本和启动代码。

二、芯片层级差异 即使在相同架构下,不同厂商设计的芯片或同一厂商的不同系列芯片在外设资源、时钟系统、中断配置等方面都存在差异。例如:

ST 公司推出了 STM32F1、F4、H7 等系列,虽然都使用 ARM Cortex-M 架构,但在 DMA、Flash、USB 等方面的支持差异很大;

同一个 ARM Cortex-M4 内核,可能被 ST、NXP、TI 等多个公司用在不同产品线上,构建系统需要能够识别和适配这些差异。

构建系统通过选择 BSP(Board Support Package)来应对这一层的变化,统一芯片初始化流程。

三、单板层级差异

芯片作为核心器件,会被设计成各种开发板或实际产品板,而不同板子的引脚配置、电源架构、外设连接方式可能不同。

举例来说,同一块 STM32F407 芯片可以被用于:

一款开发板上,OLED 接在 SPI1;

另一款工业控制板上,OLED 接在 IIC2;

这类差异通常由 board-level 配置文件决定,构建系统需根据目标板选择对应的初始化代码、引脚映射和驱动配置。

大型项目往往不是“为一个板子服务”,而是需要支持多个硬件平台,比如:

  • 不同系列的 STM32(F1、F4、H7)
  • NXP i.MX RT、LPC 系列
  • RISC-V 架构设备(如 RT1052、BL808 等)

为了支持这些平台,项目需要具备以下能力:

  • 支持多个交叉编译器(如 arm-none-eabi-gcc, riscv64-unknown-elf-gcc
  • 根据平台差异编译不同的启动文件、驱动文件
  • 通常会将 BSP(Board Support Package)分离成子项目或子模块进行管理

1.3 特征三:软件项目管理的必要性

随着项目复杂度上升,开发者不仅需要处理技术细节,也要面对诸如团队协作、质量控制、持续交付等工程问题。构建系统在这其中起到了桥梁作用,是支撑项目高效运作的核心工具之一:

在中大型项目中,项目管理的复杂性远超小型项目。在这种环境下,传统的开发方式已经不能满足需求,开发团队需要依赖构建系统来高效管理整个项目。以下是构建系统在项目管理中的几个关键功能:

依赖管理:在大型项目中,模块间的依赖关系变得极其复杂。构建系统能够自动识别模块之间的依赖,确保构建顺序正确,并减少模块的冗余编译。

自动化测试与验证:构建系统可以集成测试工具,自动执行单元测试和集成测试,确保代码在集成时的正确性。

版本控制与管理:构建系统与版本控制系统(如 Git)集成,能够方便地管理不同版本的构建,支持分支管理和版本发布。

团队协作:构建系统通过统一的构建流程和环境,使得开发团队能够协同工作,避免因环境不同引发的构建错误。

持续集成(CI):构建系统能够集成持续集成工具(如 Jenkins、GitLab CI),自动化构建、测试和部署过程,确保软件质量和开发进度。

例如,RT-Thread 项目通过集成 Git 和 Jenkins 等工具,能够实现持续集成,自动化构建、测试和验证,大大提高了团队的开发效率和代码质量。

  • 提交 PR → 自动触发构建 → 自动单元测试 + 代码规范检查 → 报告结果 → 决定是否合并

构建系统的职责不只是“把代码编译过”。它还承载:

  • 多模块的依赖关系管理
  • 编译选项和优化级别的控制
  • 目标平台的选择与工具链配置
  • 构建过程与初始化流程的组织(如链接脚本、汇编文件等)

优秀的构建系统可以显著提升开发效率,而差的构建系统会严重阻碍开发节奏。

2 构建系统原理与对比分析(Makefile、SCons、CMake)

嵌入式系统的构建系统是工程管理的“骨架”,它不仅决定了如何编译、链接和裁剪代码,更影响了项目的模块化、平台适配、CI 自动化等重要特性。

本章将介绍三种主流构建系统工具的基本原理及其适用场景,并通过 RT-Thread、Linux Kernel、PX4-Autopilot 三个实际项目对比其使用方式。

在嵌入式项目中,一个完整的构建系统应具备以下职责:

  • 源码编译:将 .c/.cpp/.S 文件编译为 .o 对象文件
  • 链接生成:根据链接脚本将 .o 链接为最终固件 .elf/.bin
  • 模块依赖:控制模块启用、禁用、依赖顺序
  • 配置管理:根据 Kconfig/CMake 配置功能开关
  • 平台适配:支持不同 MCU 架构与工具链
  • 资源处理:处理头文件、启动代码、汇编、链接脚本等

2.1 makefile:linux kernel / u-boot

原理: 基于规则的依赖构建工具,使用特定语法描述“目标 <- 依赖 + 命令”,执行顺序由文件时间戳决定。

特点:

稳定可靠,广泛应用于嵌入式和系统软件

语法灵活但易出错,可读性和可维护性较差

需要手动维护子目录编译逻辑

Linux Kernel 构建系统本质上是 Makefile 套 Makefile,配合 Kconfig 实现模块配置。

2.2 rt-thread: Scons

原理: 基于 Python 脚本的声明式构建系统,用 Python 语言描述源文件、目标文件、依赖关系与构建规则。

特点:

使用真实的 Python 语言,语义灵活,可扩展性强

支持跨平台构建、自动模块扫描、条件编译

社区相对小众,生态不如 CMake 广泛

SConstruct 示例:

import os
import rtconfig

# 注册顶层组件和路径
DefaultEnvironment(tools=[])
Export('RTT_ROOT', 'BSP_ROOT')
SConscript(os.path.join(BSP_ROOT, 'SConscript'))

SConscript 示例(模块注册):

from building import *
cwd = GetCurrentDir()
src = ['my_module.c']
group = DefineGroup('MyModule', src, LOCAL_CPPPATH=[cwd])

ROS / px4-autopilot:cmake

原理: 使用 DSL 语言(CMakeLists.txt)描述构建目标,生成平台相关 Makefile 或 Ninja 文件。

特点:

跨平台支持强,主流 IDE 支持好(如 CLion、VS Code)

target 概念强,模块管理清晰

难以调试,语法不直观,初学者学习曲线陡峭

示例(PX4 的子模块):

add_library(mavlink INTERFACE)
target_include_directories(mavlink INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

示例(板卡配置):

px4_add_board(
  PLATFORM nuttx
  VENDOR holybro
  MODEL pixhawk4
  # ...
)

3 rt-thread 的构建系统

RT-Thread 是一个国产开源嵌入式实时操作系统,其构建系统基于 Python 的 SCons,相比传统 Makefile 方式具有更强的可读性、灵活性与模块化管理能力。

本章将系统性分析 RT-Thread 构建系统的架构和工作流程,讲解 SConstruct 和 SConscript 的角色、组件注册机制、Kconfig 配置系统如何配合构建过程,以及如何支持多平台和自动化构建。

构建系统结构概览

RT-Thread 项目的典型目录结构如下(以 stm32f407-atk-explorer 为例):

rt-thread/
├── bsp/
   └── stm32/
       └── stm32f407-atk-explorer/
           ├── SConscript          <-- 当前 BSP 的构建脚本
           ├── board.c / startup.s
├── components/
   └── finsh/
       └── SConscript              <-- 组件模块构建脚本
├── libcpu/
├── SConstruct                      <-- 顶层构建入口脚本
├── Kconfig                         <-- 全局配置
└── tools/

常见的构建工具

makefile scons cmake

与具体的 keil IAR 的区别

系统配置,模块裁剪

Kconfig menuconfig

跨平台与多架构支持