📅 2025-03-29
🔄 2025-03-29
⌚ Reading time: 3 min
在嵌入式系统开发中,项目的规模和复杂性随着需求的增加而不断扩大。对于小型项目,开发者通常能够直接操作硬件,利用简洁的编译和开发环境进行开发。这类项目通常是裸机开发,例如使用 C51 或 STM32 等微控制器,通过集成开发环境(IDE)来进行开发。开发者通常专注于核心业务功能的实现,而不关心过多的底层细节,如编译过程、链接器配置或系统架构。这种方式的优点是快速实现和简单的开发流程,但在面对更复杂的需求时往往显得力不从心。
例如,使用 C51 的经典项目通常包括简单的应用,如控制一个 LED 的闪烁、读取传感器数据并输出,或者其他单一功能的嵌入式系统。这些项目通常不涉及操作系统或复杂的硬件抽象层,开发者只需要专注于代码的实现和调试。
同样,使用 STM32 的开发者通常通过 STM32CubeIDE 或 Keil MDK 等集成开发环境进行开发。这些工具提供了完整的硬件支持库和简化的项目设置,开发者能够快速构建和测试程序,只需关注核心业务逻辑,而不必过多处理与硬件平台和构建系统相关的细节。
然而,随着项目规模的扩大,特别是当项目需要支持多个硬件平台、复杂的功能模块以及持续集成等需求时,裸机开发的方式就暴露出其局限性。此时,构建系统就显得尤为重要。
什么是构建系统?
构建系统是自动化处理代码构建过程的工具,通常包括以下功能:
在嵌入式开发中,编译通常并非在目标硬件平台上进行,而是通过交叉编译来生成目标平台的代码。交叉编译工具链(如 Keil、IAR 和 GCC)是构建系统的核心组成部分,负责将开发环境中的源代码编译成目标平台能够执行的二进制文件。在构建系统中,交叉编译工具链的选择和配置直接影响到项目的编译速度、生成代码的效率以及对目标平台的支持能力。构建系统需要能够灵活地集成不同的交叉编译工具链,并根据目标硬件架构和操作系统配置相应的编译参数。通过这一过程,开发者能够确保代码能够在不同的硬件平台上正确运行。
本篇文章以 RT-Thread 为代表,结合 Linux Kernel、U-Boot、PX4-Autopilot 等开源项目,系统性探讨大型嵌入式项目的开发管理思路、构建系统对比及实际工程中可复用的最佳实践。
大型项目功能全面,但是项目中的嵌入式设备是高度定制化的,并不需要用到全部功能, 由此带来的,依赖管理。如传感器软件包依赖通信接口的驱动 功能裁剪与配置,功能本身的裁剪,功能的配置,如引脚等
中大型嵌入式项目往往采用多模块、组件化架构来组织系统。每个模块对应一类功能或外设,可单独开发、调试和裁剪,便于项目的可维护性和可扩展性。
以 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)、配置灵活性(选择接口和引脚等),以及构建系统对这些特性的有力支持。
由此带来的软件上的一些设计:使用自动著恶策和初始化流程,使得不需要做源码级别上的修改实现对功能的初始化
不是所有的功能都在所有设备中都启用,裁剪机制变得非常关键:
Kconfig/menuconfig
来做配置裁剪cmake + config.cmake
组合管理功能启用Kconfig
+ west
统一配置与依赖管理通过良好的配置机制,不仅可以精简目标固件大小,还可以减少编译依赖、加快构建速度。
三个层次:架构的不同,芯片的不同,单板级硬件的不同
大型项目往往需要支持多种硬件平台,这种支持分为三个层次:
一、架构层级差异 处理器架构的不同直接影响到指令集、寄存器模型、启动流程、异常管理等底层行为。比如:
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 配置文件决定,构建系统需根据目标板选择对应的初始化代码、引脚映射和驱动配置。
大型项目往往不是“为一个板子服务”,而是需要支持多个硬件平台,比如:
为了支持这些平台,项目需要具备以下能力:
arm-none-eabi-gcc
, riscv64-unknown-elf-gcc
)随着项目复杂度上升,开发者不仅需要处理技术细节,也要面对诸如团队协作、质量控制、持续交付等工程问题。构建系统在这其中起到了桥梁作用,是支撑项目高效运作的核心工具之一:
在中大型项目中,项目管理的复杂性远超小型项目。在这种环境下,传统的开发方式已经不能满足需求,开发团队需要依赖构建系统来高效管理整个项目。以下是构建系统在项目管理中的几个关键功能:
依赖管理:在大型项目中,模块间的依赖关系变得极其复杂。构建系统能够自动识别模块之间的依赖,确保构建顺序正确,并减少模块的冗余编译。
自动化测试与验证:构建系统可以集成测试工具,自动执行单元测试和集成测试,确保代码在集成时的正确性。
版本控制与管理:构建系统与版本控制系统(如 Git)集成,能够方便地管理不同版本的构建,支持分支管理和版本发布。
团队协作:构建系统通过统一的构建流程和环境,使得开发团队能够协同工作,避免因环境不同引发的构建错误。
持续集成(CI):构建系统能够集成持续集成工具(如 Jenkins、GitLab CI),自动化构建、测试和部署过程,确保软件质量和开发进度。
例如,RT-Thread 项目通过集成 Git 和 Jenkins 等工具,能够实现持续集成,自动化构建、测试和验证,大大提高了团队的开发效率和代码质量。
构建系统的职责不只是“把代码编译过”。它还承载:
优秀的构建系统可以显著提升开发效率,而差的构建系统会严重阻碍开发节奏。
嵌入式系统的构建系统是工程管理的“骨架”,它不仅决定了如何编译、链接和裁剪代码,更影响了项目的模块化、平台适配、CI 自动化等重要特性。
本章将介绍三种主流构建系统工具的基本原理及其适用场景,并通过 RT-Thread、Linux Kernel、PX4-Autopilot 三个实际项目对比其使用方式。
在嵌入式项目中,一个完整的构建系统应具备以下职责:
原理: 基于规则的依赖构建工具,使用特定语法描述“目标 <- 依赖 + 命令”,执行顺序由文件时间戳决定。
特点:
稳定可靠,广泛应用于嵌入式和系统软件
语法灵活但易出错,可读性和可维护性较差
需要手动维护子目录编译逻辑
Linux Kernel 构建系统本质上是 Makefile 套 Makefile,配合 Kconfig 实现模块配置。
原理: 基于 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])
原理: 使用 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
# ...
)
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