ubuntu下在Vscode中搭建基于libopencm3开源库的stm32 c/c++项目开发环境

放弃stm32专用集成开发平台进行项目开发,从零开始自己配置环境,也不失为一次了解底层硬件知识的好机会。本项目以简单的LED闪烁程序作为例子,本文章主要聚焦于如何在Ubuntu系统中,使用VSCode搭建基于libopencm3开源库的STM32开发环境,以及搭建项目的框架。
后面可能还会发几篇关于这个项目的文章,写好了就贴在这里。

工具选择

  1. 工具链:gcc-arm-none-eabi 、gdb-multiarch(调试用)
  2. 标准库:newlib、libstdc++
  3. 工具:git、make、cmake、ninja、matplotlib(可视化输出)
  4. 硬件抽象库:libopencm3
  5. 下载和调试工具:J-Link
  6. vsCode插件:CMke Tools、Cortex-Debug、以及基础的c++语法插件

开始之前,请确保除了硬件库之外的所有上述工具都已正确配置。



一、环境准备

1. 安装必要工具链和工具

# 安装ARM交叉编译工具链
sudo apt install gcc-arm-none-eabi gdb-multiarch

# 安装构建工具和依赖
sudo apt install git make cmake ninja-build python3-matplotlib

# 安装J-Link驱动 (需从官网下载)
# https://www.segger.com/downloads/jlink/
# 下载后执行: sudo dpkg -i JLink_Linux_*.deb

2. 安装VSCode插件

在VSCode扩展商店中安装:

  • CMake Tools
  • Cortex-Debug


二、基础项目架构

stm32-project/
├── cmake
│   └── arm-gcc-toolchain.cmake  # 交叉编译配置
├── include
│   └── led.h                    # LED驱动接口
├── src
│   ├── main.cpp                 # 主程序,包含:
│   │   - 时钟系统配置
│   │   - Systick定时器初始化
│   │   - LED控制逻辑
│   ├── led.cpp                  # LED驱动实现
│   └── syscalls.c               # 系统调用适配
├── scripts
│   ├── flash.sh                 # 一键烧录脚本
│   └── size_report.py           # 固件内存分析
├── lib
│   └── libopencm3               # 硬件抽象层
├── build                        # 构建输出目录
├── CMakeLists.txt               # 主构建配置
├── CMakePresets.json            # CMake预设配置(可以不要)
└── stm32f103cbtx_flash.ld       # 链接器脚本


三、项目搭建

第一步: 获取libopencm3硬件库

  我们先理解“硬件抽象库”是做什么用的。硬件抽象库(如libopencm3)是一组针对特定微控制器(如STM32)的底层驱动代码,它封装了对芯片寄存器的直接操作。通过调用硬件库提供的接口,我们得以操作底层的硬件设备。
  拿到硬件抽象库之后,我们还需要根据自己具体的芯片型号对硬件抽象库经行编译,得到专用的静态库。静态库(.a文件)本质上是预编译好的机器码集合。在业务代码中,我们主要通过调用静态库提供的接口来实现对硬件操作。
  编译后会得到如下的库项目结构:

libopencm3/
├── include/         # 头文件目录
│   └── libopencm3/  # 核心头文件
│       ├── stm32/
│       │   └── f1/  # STM32F1专用头文件
│       └── gpio.h   # GPIO操作API(GPIO:芯片的通用I/O管脚)
└── lib/
    └── libopencm3_stm32f1.a  # 编译好的静态库

  接下来,让我们正式为项目接入硬件抽象库。
1. 克隆libopencm3仓库,部署到本地项目的lib目录下

cd ./lib
git clone https://github.com/libopencm3/libopencm3

2. 编译STM32F1硬件抽象库
在官方的README文件中,我们可以找到编译指南。我们需要根据芯片型号,选择对应的版本。注意109行。
在这里插入图片描述
我们在这里选择stm/f1型号进行库编译,在终端运行:

cd libopencm3
make TARGETS=stm32/f1

第二步:创建CMake相关文件

首先,我们先了解核心工具CMake。

在传统开发中,我们通常需要手动编写 Makefile 文件并使用 make 指令进行编译。这种方式存在几个显著问题:

  1. 平台依赖性: 不同操作系统(如 Linux, Windows, macOS)需要编写和维护完全不同的构建脚本。
  2. 维护复杂度: 开发者必须手动跟踪并维护所有源文件(.c/.cpp)和头文件(.h/.hpp)之间的依赖关系,极易出错且繁琐。
  3. 扩展困难: 随着项目规模增大、文件增多、依赖关系复杂化,手动维护的 Makefile 会变得异常臃肿且难以维护。

而CMake 通过其核心设计——构建系统生成器——完美解决了上述痛点。CMake 本身不直接编译代码,它的核心作用是:

  1. 读取配置: 你编写一个名为 CMakeLists.txt 的配置文件,在其中声明:
    • 项目包含哪些源文件
    • 需要链接哪些库
    • 指定编译器(可选)
    • 定义编译选项、目标(可执行文件或库)等
  2. 生成构建系统: 运行 cmake 命令,它会根据 CMakeLists.txt 中的配置和当前平台环境(操作系统、编译器、工具链),自动生成适合该平台的底层构建脚本(如 MakefileNinja 文件、Visual Studio 解决方案等)。
  3. 执行编译: 使用生成的构建脚本(如运行 makeninja 或打开 VS 解决方案)进行实际的编译和链接操作。
CMake vs. 传统 Make 流程对比:
cmake -Bbuild
make -C build
make
CMakeLists.txt
Makefile
可执行文件
手工编写Makefile
Makefile

下面,让我们来具体编写项目文件,将libopencm3接入我们的项目。总的来说,我们需要创建如下两个文件:

  • CMakeLists.txt
    • CMake 的核心配置文件,定义项目构建规则(如源文件、目标可执行文件、依赖库等)。在嵌入式开发中会指定芯片架构、链接脚本等关键参数。
    • 需要include arm-gcc-toolchain.cmake,相当于我们把工具链配置拆除去单独放一样
  • arm-gcc-toolchain.cmake
    • 工具链配置文件,位于 cmake/ 目录。用于定义 ARM 交叉编译工具链的路径和参数(如编译器 arm-none-eabi-gcc、链接器等),使 CMake 能生成针对 ARM 芯片的 Makefile

  1. CMakeLists.txt (项目根目录)
# ====================== 项目全局配置 ======================
# 设置CMake最低版本要求
cmake_minimum_required(VERSION 3.20)

# 定义项目基本信息
# - stm32_led_blink: 项目名称
# - VERSION 1.0.0: 项目版本号
# - LANGUAGES C CXX ASM: 支持C/C++/汇编语言
# - DESCRIPTION: 项目描述
project(stm32_led_blink
    VERSION 1.0.0
    LANGUAGES C CXX ASM
    DESCRIPTION "STM32F103CBT6 LED闪烁项目"
)

# ====================== 编译选项配置 ======================
# 全局配置选项
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)  # 生成compile_commands.json供IDE索引
set(CMAKE_C_STANDARD 11)               # 使用C11标准
set(CMAKE_CXX_STANDARD 20)             # 使用C++20标准
set(CMAKE_CXX_EXTENSIONS OFF)          # 禁用编译器扩展
# 设置默认构建类型为Debug(可通过命令行覆盖)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "构建类型 (Debug/Release)")

# 确保构建类型已设置
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE "Debug")
endif()

# ====================== 工具链配置 ======================
# 包含ARM交叉编译工具链配置
include(cmake/arm-gcc-toolchain.cmake)

# 根据构建类型设置优化选项
if(CMAKE_BUILD_TYPE STREQUAL "Release")
  add_compile_options(-O3)  # 发布模式:最高优化级别
else()
  add_compile_options(-O0 -g)  # 调试模式:无优化,包含调试信息
endif()
# ====================== 可执行文件配置 ======================
# 创建可执行文件(ELF格式)
add_executable(${PROJECT_NAME}.elf
    src/main.cpp      # 主程序入口
    src/led.cpp       # LED驱动实现
    src/syscalls.c    # 标准库系统调用实现
)

# 设置头文件包含路径
target_include_directories(${PROJECT_NAME}.elf PRIVATE
    include                 # 项目头文件目录
    lib/libopencm3/include  # libopencm3库头文件
)

# 添加全局宏定义(重要)
target_compile_definitions(${PROJECT_NAME}.elf PRIVATE
    STM32F1        # 指定STM32F1系列
    STM32F103xB    # 指定具体芯片型号
)

# ====================== 链接配置 ======================
# 链接libopencm3静态库
target_link_libraries(${PROJECT_NAME}.elf
    ${CMAKE_SOURCE_DIR}/lib/libopencm3/lib/libopencm3_stm32f1.a
)

# 设置链接脚本(指定内存布局)
set(CMAKE_EXE_LINKER_FLAGS
    "${CMAKE_EXE_LINKER_FLAGS} -T${CMAKE_SOURCE_DIR}/stm32f103cbtx_flash.ld"
)

# 添加编译选项(针对嵌入式系统优化)
target_compile_options(${PROJECT_NAME}.elf PRIVATE
    # C++特有选项
    $<$<COMPILE_LANGUAGE:CXX>:
        -fno-exceptions               # 禁用异常处理
        -fno-unwind-tables            # 禁用异常展开表
        -fno-asynchronous-unwind-tables # 禁用异步异常表
        -fno-use-cxa-atexit           # 禁用C++全局析构函数
        -fno-threadsafe-statics       # 禁用线程安全静态变量
    >
    # 公共选项(C和C++共享)
    -ffunction-sections           # 函数分段(链接优化)
    -fdata-sections               # 数据分段(链接优化)
)

# 设置链接器选项
set(CMAKE_EXE_LINKER_FLAGS
    "${CMAKE_EXE_LINKER_FLAGS} -static -Wl,--gc-sections -Wl,--no-eh-frame-hdr -nostartfiles -lc -lm -lstdc++"
)

# ====================== 后构建步骤 ======================
# 定义构建完成后自动执行的命令
add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD
    # 生成内存使用报告
    COMMAND arm-none-eabi-size ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.elf
    # 运行Python脚本生成详细内存报告
    COMMAND python3 ${CMAKE_SOURCE_DIR}/scripts/size_report.py ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.elf
    # 生成.bin格式固件
    COMMAND arm-none-eabi-objcopy -O binary ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.elf ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.bin
    # 生成.hex格式固件
    COMMAND arm-none-eabi-objcopy -O ihex ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.elf ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.hex
    COMMENT "后构建步骤:生成内存报告和固件文件"
)
  1. arm-gcc-toolchain.cmake (cmake目录下)
# ====================== ARM交叉编译工具链配置 ======================
# 本文件定义ARM嵌入式开发的交叉编译环境
# 适用于STM32F1系列Cortex-M3处理器

# 设置通用系统类型(嵌入式系统)
set(CMAKE_SYSTEM_NAME Generic)

# 指定目标处理器架构
set(CMAKE_SYSTEM_PROCESSOR arm)

# ====================== 编译测试配置 ======================
# 设置尝试编译的目标类型为静态库(跳过可执行文件链接测试)
# 嵌入式环境中链接需要特定启动文件和链接脚本,跳过可执行文件测试
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

# ====================== 工具链路径配置 ======================
# 定义可能的工具链安装路径(解决不同系统的路径问题)
set(TOOLCHAIN_PATH /usr/bin /opt/gcc-arm-none-eabi/bin)

# 查找ARM GCC编译器(确保工具链存在)
find_program(TOOLCHAIN_EXISTS arm-none-eabi-gcc 
    PATHS ${TOOLCHAIN_PATH} 
    REQUIRED) # 必须找到否则报错

# 获取工具链所在目录
get_filename_component(TOOLCHAIN_DIR ${TOOLCHAIN_EXISTS} DIRECTORY)

# ====================== 工具链命令前缀 ======================
# 设置工具链命令前缀(arm-none-eabi-)
set(TOOLCHAIN_PREFIX arm-none-eabi-)

# ====================== 编译器配置 ======================
# 设置C编译器
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}gcc)

# 设置C++编译器
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}g++)

# 设置汇编器(使用GCC处理汇编文件)
set(CMAKE_ASM_COMPILER ${TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}gcc)

# 设置二进制工具
set(CMAKE_OBJCOPY ${TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}objcopy) # 用于生成bin/hex文件
set(CMAKE_OBJDUMP ${TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}objdump) # 用于反汇编
set(CMAKE_SIZE ${TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}size)       # 用于查看内存占用

# ====================== 编译器标志 ======================
# 公共编译器标志(C/C++/ASM共享)
# 注意:必须作为单个字符串传递,不能是列表
set(COMMON_FLAGS "-mcpu=cortex-m3 -mthumb -fdata-sections -ffunction-sections")

# C编译器标志(内部缓存)
set(CMAKE_C_FLAGS "${COMMON_FLAGS}" CACHE INTERNAL "C编译器标志")

# C++编译器标志(额外禁用异常和RTTI)
set(CMAKE_CXX_FLAGS "${COMMON_FLAGS} -fno-exceptions -fno-rtti" CACHE INTERNAL "C++编译器标志")

# 汇编器标志(额外指定语言类型)
set(CMAKE_ASM_FLAGS "${COMMON_FLAGS} -x assembler-with-cpp" CACHE INTERNAL "汇编器标志")

# 注意:链接器选项在CMakeLists.txt中设置,避免此处冲突

# ====================== 搜索路径策略 ======================
# 设置各种查找策略(针对交叉编译环境)
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)   # 只在目标系统中查找包



四、连接器脚本文件(stm32f103cbtx_flash.ld)

比较重要,后面会单开一篇文章讲解。写好了会把连接贴到这里



五、VSCode开发环境配置

隐藏文件夹.vscode下就放置了我们vsCode的四个配置文件。有了它们,再加上插件,我们就可以在vsCode中进行可视化调试了。这里先介绍比较重要的部分。

1. .vscode/settings.json (CMake配置)

{
    "cmake.configureOnOpen": true,
    "cmake.buildDirectory": "${workspaceFolder}/build",
    "cmake.generator": "Ninja",
    "cmake.parallelJobs": 4,
    "cortex-debug.armToolchainPath": "/opt/gcc-arm-none-eabi/bin"
}

2. .vscode/launch.json (调试配置)

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Cortex Debug",
            "cwd": "${workspaceRoot}",
            "executable": "${workspaceRoot}/build/stm32_led_blink.elf",
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "jlink",
            "device": "STM32F103CB",
            "interface": "swd",
            "svdFile": "${env:HOME}/.vscode/extensions/marus25.cortex-debug-1.10.0/svd/STMicro/STM32F103.svd"
        }
    ]
}

六、程序代码实现

也是后面单独开一片文章讲解,这里先占位。



七、编译烧录流程

  1. 编译项目
# 创建构建目录
mkdir -p build && cd build

# 配置项目(使用预设工具链)
cmake --preset=debug ..

# 编译固件
cmake --build .
  1. 烧录固件 (使用J-Link):
./scripts/flash.sh

烧录脚本内容:

#!/bin/bash
openocd -f /usr/share/openocd/scripts/interface/jlink.cfg \
        -c "transport select swd" \
        -f /usr/share/openocd/scripts/target/stm32f1x.cfg \
        -c "program build/stm32_led_blink.elf verify" \
        -c "reset run" \
        -c "exit"

调试与验证

  1. 在VSCode中按F5启动调试会话
  2. 使用Cortex-Debug插件查看外设寄存器
  3. 添加断点观察LED状态变化
  4. 查看内存使用报告(build/stm32_led_blink_memory_report.png)

总结

通过以上步骤,我们成功在Ubuntu系统中使用VSCode搭建了基于libopencm3的STM32开发环境。这套环境具有以下优势:

  1. 开源免费:全部使用开源工具链和库
  2. 高效开发:CMake+Ninja构建系统提供快速编译
  3. 强大调试:VSCode+Cortex-Debug提供可视化调试体验
  4. 跨平台:可在Linux、Windows和macOS上使用

此环境框架也可轻松扩展到其他STM32系列芯片,只需修改相应的MCU型号和链接脚本即可。

提示:实际开发中请根据具体硬件修改LED引脚定义和时钟配置。完整项目示例可在GitHub仓库获取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值