Files
EmbeddedSoftwareLearn1/08-项目实战与工具链/README.md
T
2025-06-17 16:04:24 +08:00

23 KiB
Raw Blame History

📦 第八层:项目实战与工具链

✅ 工程管理

🔹 Git 版本控制

1. Git 分支策略

  • 主干分支(main/master):
    永远代表可发布的稳定版本,仅接受通过CI/CD验证的代码。
  • 开发分支(develop):
    集成所有新功能的开发,是日常开发的基础分支。
  • 特性分支(feature/*):
    从develop分支创建,用于开发单个新功能或修复问题,完成后合并回develop。
  • 发布分支(release/*):
    从develop分支创建,用于准备发布版本,进行最后的测试和Bug修复。
  • 热修复分支(hotfix/*):
    从main分支创建,用于紧急修复生产环境问题,修复后合并回main和develop。

2. 提交规范

采用Conventional Commits规范:

<类型>[可选范围]: <描述>

[可选正文]

[可选脚注]
  • 常见类型:
    • feat:新功能
    • fix:修复Bug
    • docs:文档更新
    • style:代码格式调整(不影响功能)
    • refactor:代码重构
    • test:添加或修改测试
    • chore:构建或辅助工具的变动

3. 标签管理

使用语义化版本(SemVer)打标签:

# 创建标签
git tag v1.0.0

# 推送标签到远程
git push origin v1.0.0

# 查看所有标签
git tag -l

🔹 Makefile、CMake 构建工具

1. Makefile 基础

  • 简单示例:
    CC = arm-none-eabi-gcc
    CFLAGS = -Wall -O2 -mcpu=cortex-m4 -mthumb
    LDFLAGS = -Tstm32f4.ld
    
    SRCS = $(wildcard *.c)
    OBJS = $(SRCS:.c=.o)
    TARGET = firmware.elf
    
    all: $(TARGET)
    
    $(TARGET): $(OBJS)
        $(CC) $(LDFLAGS) $(OBJS) -o $@
    
    %.o: %.c
        $(CC) $(CFLAGS) -c $< -o $@
    
    clean:
        rm -f $(OBJS) $(TARGET)
    

2. CMake 高级应用

  • 跨平台配置:
    cmake_minimum_required(VERSION 3.10)
    project(EmbeddedProject C)
    
    # 设置交叉编译工具链
    set(CMAKE_SYSTEM_NAME Generic)
    set(CMAKE_C_COMPILER arm-none-eabi-gcc)
    set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
    set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
    set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
    
    # 添加编译选项
    add_compile_options(
        -mcpu=cortex-m4
        -mthumb
        -mfloat-abi=hard
        -mfpu=fpv4-sp-d16
        -Wall
        -Wextra
        -Os
    )
    
    # 添加链接选项
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -T${CMAKE_SOURCE_DIR}/STM32F407VGTx_FLASH.ld")
    
    # 添加源文件
    file(GLOB_RECURSE SOURCES "src/*.c" "drivers/*.c")
    
    # 添加可执行文件
    add_executable(${PROJECT_NAME}.elf ${SOURCES})
    
    # 添加目标文件
    add_custom_target(${PROJECT_NAME}.bin
        COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
        DEPENDS ${PROJECT_NAME}.elf
    )
    

🔹 Jenkins/GitHub Actions CI 流水线

1. GitHub Actions 配置

  • 编译与测试工作流:
    name: Build and Test
    
    on:
      push:
        branches: [ main, develop ]
      pull_request:
        branches: [ main, develop ]
    
    jobs:
      build:
        runs-on: ubuntu-latest
    
        steps:
        - name: Checkout code
          uses: actions/checkout@v3
    
        - name: Set up Python
          uses: actions/setup-python@v4
          with:
            python-version: 3.9
    
        - name: Install dependencies
          run: |
            sudo apt-get update
            sudo apt-get install -y gcc-arm-none-eabi cmake ninja-build
    
        - name: Configure CMake
          run: cmake -B build -G Ninja
    
        - name: Build
          run: cmake --build build
    
        - name: Run tests
          run: |
            cd build
            ctest --output-on-failure
    

2. Jenkins 集成

  • 构建脚本示例:
    pipeline {
        agent any
    
        stages {
            stage('Checkout') {
                steps {
                    checkout scm
                }
            }
    
            stage('Build') {
                steps {
                    sh 'make clean all'
                }
            }
    
            stage('Test') {
                steps {
                    sh 'make test'
                }
            }
    
            stage('Code Coverage') {
                steps {
                    sh 'make coverage'
                }
                post {
                    always {
                        junit 'build/test-results/*.xml'
                        publishCoverage adapters: [coberturaAdapter('build/coverage/coverage.xml')]
                    }
                }
            }
    
            stage('Deploy') {
                when {
                    branch 'main'
                }
                steps {
                    sh 'make deploy'
                }
            }
        }
    }
    

✅ 项目实践

🔹 嵌入式应用框架设计

1. 分层架构

+----------------------+
|     应用层           |
|  (业务逻辑、算法)    |
+----------------------+
|     服务层           |
|  (任务管理、事件)    |
+----------------------+
|     驱动层           |
|  (硬件抽象、BSP)     |
+----------------------+
|     硬件层           |
|  (MCU、外设)         |
+----------------------+

2. 组件化设计

  • 核心组件:
    • 任务管理器:负责任务创建、调度和通信。
    • 事件系统:处理异步事件和回调。
    • 配置管理:加载和保存系统配置。
    • 日志系统:分级日志记录和输出。

3. 代码结构示例

project/
├── app/            # 应用层
│   ├── main.c      # 主程序入口
│   ├── modules/    # 功能模块
│   │   ├── sensor/ # 传感器处理
│   │   ├── comm/   # 通信处理
│   │   └── control/ # 控制逻辑
│   └── config/     # 配置文件
├── services/       # 服务层
│   ├── task_mgr/   # 任务管理器
│   ├── event/      # 事件系统
│   └── utils/      # 工具函数
├── drivers/        # 驱动层
│   ├── bsp/        # 板级支持包
│   ├── hal/        # 硬件抽象层
│   └── periph/     # 外设驱动
└── build/          # 构建系统
    ├── cmake/      # CMake配置
    └── Makefile    # Makefile

🔹 通用 BSP 构建

1. 设计原则

  • 硬件无关性:上层代码不直接访问硬件寄存器。
  • 可移植性:相同功能代码可在不同硬件平台复用。
  • 配置化:通过配置文件而非修改代码适配不同硬件。

2. BSP 实现示例

// bsp_led.h
#ifndef BSP_LED_H
#define BSP_LED_H

#include <stdint.h>

typedef enum {
    LED_RED,
    LED_GREEN,
    LED_BLUE
} led_t;

typedef enum {
    LED_OFF,
    LED_ON,
    LED_TOGGLE
} led_state_t;

// 初始化LED
void bsp_led_init(void);

// 设置LED状态
void bsp_led_set(led_t led, led_state_t state);

#endif

// bsp_led.c (STM32实现)
#include "bsp_led.h"
#include "stm32f4xx_hal.h"

// LED GPIO定义
#define LED_RED_PIN    GPIO_PIN_14
#define LED_RED_PORT   GPIOG
#define LED_GREEN_PIN  GPIO_PIN_13
#define LED_GREEN_PORT GPIOG
#define LED_BLUE_PIN   GPIO_PIN_15
#define LED_BLUE_PORT  GPIOG

void bsp_led_init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 使能GPIO时钟
    __HAL_RCC_GPIOG_CLK_ENABLE();
    
    // 配置GPIO引脚
    GPIO_InitStruct.Pin = LED_RED_PIN | LED_GREEN_PIN | LED_BLUE_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
    
    // 默认关闭所有LED
    HAL_GPIO_WritePin(LED_RED_PORT, LED_RED_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED_GREEN_PORT, LED_GREEN_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED_BLUE_PORT, LED_BLUE_PIN, GPIO_PIN_RESET);
}

void bsp_led_set(led_t led, led_state_t state) {
    GPIO_TypeDef *port;
    uint16_t pin;
    
    // 根据LED类型选择GPIO
    switch (led) {
        case LED_RED:
            port = LED_RED_PORT;
            pin = LED_RED_PIN;
            break;
        case LED_GREEN:
            port = LED_GREEN_PORT;
            pin = LED_GREEN_PIN;
            break;
        case LED_BLUE:
            port = LED_BLUE_PORT;
            pin = LED_BLUE_PIN;
            break;
        default:
            return;
    }
    
    // 设置LED状态
    switch (state) {
        case LED_OFF:
            HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);
            break;
        case LED_ON:
            HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET);
            break;
        case LED_TOGGLE:
            HAL_GPIO_TogglePin(port, pin);
            break;
    }
}

🔹 模块化驱动结构

1. 驱动分层

  • 硬件层:直接操作寄存器的低级驱动。
  • 抽象层:提供统一接口的高级驱动。
  • 适配层:连接抽象层和硬件层的中间层。

2. SPI驱动示例

// spi_interface.h (抽象接口)
#ifndef SPI_INTERFACE_H
#define SPI_INTERFACE_H

#include <stdint.h>

typedef struct {
    // 初始化SPI
    void (*init)(uint32_t baudrate);
    
    // 发送数据
    void (*send)(const uint8_t *data, uint32_t length);
    
    // 接收数据
    void (*receive)(uint8_t *data, uint32_t length);
    
    // 发送并接收数据
    void (*transfer)(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length);
} spi_interface_t;

// 获取SPI接口实例
const spi_interface_t* spi_get_interface(void);

#endif

// spi_stm32.c (STM32实现)
#include "spi_interface.h"
#include "stm32f4xx_hal.h"

static SPI_HandleTypeDef hspi1;

static void spi_init(uint32_t baudrate) {
    // 配置SPI参数
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    
    // 根据波特率计算分频系数
    uint32_t prescaler = SPI_BAUDRATEPRESCALER_2;
    if (baudrate < 1000000) prescaler = SPI_BAUDRATEPRESCALER_128;
    else if (baudrate < 2000000) prescaler = SPI_BAUDRATEPRESCALER_64;
    else if (baudrate < 4000000) prescaler = SPI_BAUDRATEPRESCALER_32;
    else if (baudrate < 8000000) prescaler = SPI_BAUDRATEPRESCALER_16;
    else if (baudrate < 16000000) prescaler = SPI_BAUDRATEPRESCALER_8;
    else if (baudrate < 32000000) prescaler = SPI_BAUDRATEPRESCALER_4;
    
    hspi1.Init.BaudRatePrescaler = prescaler;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    hspi1.Init.CRCPolynomial = 10;
    
    // 初始化SPI
    HAL_SPI_Init(&hspi1);
}

static void spi_send(const uint8_t *data, uint32_t length) {
    HAL_SPI_Transmit(&hspi1, (uint8_t*)data, length, 1000);
}

static void spi_receive(uint8_t *data, uint32_t length) {
    HAL_SPI_Receive(&hspi1, data, length, 1000);
}

static void spi_transfer(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length) {
    HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)tx_data, rx_data, length, 1000);
}

// SPI接口实现
static const spi_interface_t spi_impl = {
    .init = spi_init,
    .send = spi_send,
    .receive = spi_receive,
    .transfer = spi_transfer
};

// 获取SPI接口实例
const spi_interface_t* spi_get_interface(void) {
    return &spi_impl;
}

🔹 OTA 升级方案设计

1. 双分区架构

Flash布局:
+-------------------+ 0x08000000
| Bootloader        | (80KB)
+-------------------+ 0x08014000
| Application A     | (448KB)
+-------------------+ 0x08084000
| Application B     | (448KB)
+-------------------+ 0x08104000
| Configuration     | (16KB)
+-------------------+

2. OTA状态机

typedef enum {
    OTA_IDLE,            // 空闲状态
    OTA_CHECKING,        // 检查更新
    OTA_DOWNLOADING,     // 下载中
    OTA_DOWNLOAD_PAUSED, // 下载暂停
    OTA_VERIFYING,       // 校验中
    OTA_READY,           // 准备重启
    OTA_UPGRADING,       // 升级中
    OTA_FAILED           // 升级失败
} ota_state_t;

typedef struct {
    ota_state_t state;
    uint32_t total_size;
    uint32_t downloaded_size;
    uint8_t progress;
    char error_msg[64];
    uint8_t firmware_hash[32];
} ota_context_t;

3. OTA流程

  1. 检查更新:

    bool ota_check_update(void) {
        // 从服务器获取版本信息
        http_response_t response = http_get(UPDATE_SERVER_URL "/version");
        if (response.status != 200) {
            return false;
        }
    
        // 解析服务器版本
        uint32_t server_version = parse_version(response.body);
        uint32_t current_version = get_current_version();
    
        // 比较版本
        return (server_version > current_version);
    }
    
  2. 下载固件:

    void ota_download_firmware(void) {
        // 打开固件下载URL
        http_client_t client = http_open(UPDATE_SERVER_URL "/firmware.bin");
        if (!client) {
            ota_set_state(OTA_FAILED, "Failed to open URL");
            return;
        }
    
        // 获取文件大小
        uint32_t file_size = http_get_content_length(client);
        ota_set_total_size(file_size);
    
        // 开始下载
        uint8_t buffer[512];
        uint32_t bytes_received = 0;
        uint32_t bytes_written = 0;
    
        while ((bytes_received = http_read(client, buffer, 512)) > 0) {
            // 写入到备份区
            if (!flash_write(APPLICATION_B_ADDRESS + bytes_written, buffer, bytes_received)) {
                ota_set_state(OTA_FAILED, "Flash write failed");
                http_close(client);
                return;
            }
    
            bytes_written += bytes_received;
            ota_update_progress(bytes_written * 100 / file_size);
    
            // 检查是否需要暂停
            if (ota_should_pause()) {
                http_close(client);
                ota_set_state(OTA_DOWNLOAD_PAUSED, "Download paused");
                return;
            }
        }
    
        http_close(client);
        ota_set_state(OTA_VERIFYING, "Verifying firmware");
    }
    
  3. 验证与应用:

    bool ota_verify_firmware(void) {
        // 计算下载固件的哈希值
        uint8_t calculated_hash[32];
        calculate_firmware_hash(APPLICATION_B_ADDRESS, APPLICATION_SIZE, calculated_hash);
    
        // 与服务器提供的哈希值比较
        if (memcmp(calculated_hash, ota_get_expected_hash(), 32) != 0) {
            return false;
        }
    
        // 验证向量表
        uint32_t *vector_table = (uint32_t*)APPLICATION_B_ADDRESS;
        if (vector_table[0] == 0 || vector_table[1] == 0) {
            return false;
        }
    
        return true;
    }
    
    void ota_apply_update(void) {
        // 设置升级标志
        set_update_flag(1);
    
        // 保存新固件版本
        save_new_version(get_server_version());
    
        // 重启系统
        NVIC_SystemReset();
    }
    

开发工具链安装指南

1. IDE推荐

VS Code + PlatformIO

官网链接:

安装步骤:

  1. 下载并安装 VS Code
  2. 打开VS Code,点击左侧扩展图标(或按 Ctrl+Shift+X)
  3. 搜索并安装 PlatformIO IDE 扩展
  4. 安装完成后,重启VS Code
  5. PlatformIO会自动安装所需的工具链和依赖

验证安装:
打开VS Code,点击左下角的 PlatformIO Home 图标,若能正常打开则安装成功。

STM32CubeIDE

官网链接:

安装步骤:

  1. 访问官网,点击 Get Software 下载对应操作系统的安装包
  2. 运行安装程序,按照向导完成安装
  3. 安装过程中会自动下载并配置STM32CubeMX

验证安装:
启动STM32CubeIDE,创建一个新的STM32项目,若能正常编译则安装成功。

CLion

官网链接:

安装步骤:

  1. 下载并安装 CLion
  2. 安装CMake和MinGW(Windows用户需要):
    • CMake:从 官网 下载并安装
    • MinGW:推荐使用 MSYS2 安装

验证安装:
启动CLion,创建一个新的C/C++项目,选择CMake工具链,若能正常编译则安装成功。

2. 调试工具

OpenOCD

官网链接:

安装步骤:

  • Windows:

    1. 从 GNU MCU Eclipse 下载预编译二进制包
    2. 解压到指定目录(如 C:\openocd)
    3. 将 bin 目录添加到系统环境变量
  • Linux:

    sudo apt-get install openocd  # Ubuntu/Debian
    sudo yum install openocd      # CentOS/RHEL
    
  • macOS:

    brew install open-ocd
    

验证安装:
在终端中运行 openocd --version,若显示版本信息则安装成功。

GDB

官网链接:

安装步骤:

  1. 下载并安装 ARM GCC Toolchain
  2. 将 bin 目录添加到系统环境变量

验证安装:
在终端中运行 arm-none-eabi-gdb --version,若显示版本信息则安装成功。

ST-Link/V2

官网链接:

安装步骤:

  • Windows:

    1. 从 ST官网 下载并安装ST-Link驱动
    2. 安装完成后,将ST-Link/V2调试器连接到电脑
  • Linux:

    sudo apt-get install stlink-tools  # Ubuntu/Debian
    

验证安装:
在终端中运行 st-info --version,若显示版本信息则安装成功。

3. 静态代码分析

CppCheck

官网链接:

安装步骤:

  • Windows:

    1. 从 官网 下载安装包
    2. 运行安装程序,按照向导完成安装
  • Linux:

    sudo apt-get install cppcheck  # Ubuntu/Debian
    sudo yum install cppcheck      # CentOS/RHEL
    
  • macOS:

    brew install cppcheck
    

验证安装:
在终端中运行 cppcheck --version,若显示版本信息则安装成功。

Clang-Tidy

官网链接:

安装步骤:

  • Windows:

    1. 安装 LLVM
    2. Clang-Tidy会随LLVM一起安装
  • Linux:

    sudo apt-get install clang-tidy  # Ubuntu/Debian
    
  • macOS:

    brew install llvm
    

验证安装:
在终端中运行 clang-tidy --version,若显示版本信息则安装成功。

SonarQube

官网链接:

安装步骤:

  1. 下载并安装 Docker
  2. 运行SonarQube容器:
    docker run -d --name sonarqube -p 9000:9000 sonarqube
    
  3. 访问 http://localhost:9000,使用默认账号(admin/admin)登录

验证安装:
在浏览器中打开 http://localhost:9000,若能看到SonarQube界面则安装成功。

4. 单元测试

Unity

官网链接:

安装步骤:

  1. 从GitHub下载Unity源码:
    git clone https://github.com/ThrowTheSwitch/Unity.git
    
  2. 将 src 目录添加到项目的头文件搜索路径

验证安装:
创建一个简单的测试文件,包含Unity头文件,若能正常编译则安装成功。

CMock

官网链接:

安装步骤:

  1. 从GitHub下载CMock源码:
    git clone https://github.com/ThrowTheSwitch/CMock.git
    
  2. 将 src 目录添加到项目的头文件搜索路径

验证安装:
创建一个简单的测试文件,包含CMock头文件,若能正常编译则安装成功。

Google Test

官网链接:

安装步骤:

  1. 从GitHub下载Google Test源码:
    git clone https://github.com/google/googletest.git
    
  2. 使用CMake构建并安装:
    cd googletest
    mkdir build
    cd build
    cmake ..
    make
    sudo make install
    

验证安装:
创建一个简单的测试文件,包含Google Test头文件,若能正常编译则安装成功。

📚 资源汇总

工具 官网链接 安装指南
VS Code https://code.visualstudio.com/ 直接下载安装包
PlatformIO https://platformio.org/ VS Code扩展市场安装
STM32CubeIDE https://www.st.com/en/development-tools/stm32cubeide.html 官网下载安装包
CLion https://www.jetbrains.com/clion/ 官网下载安装包
OpenOCD http://openocd.org/ 包管理器或预编译二进制包
GDB https://www.gnu.org/software/gdb/ 随ARM GCC Toolchain安装
ST-Link/V2 https://www.st.com/en/development-tools/st-link-v2.html 官网下载驱动
CppCheck https://cppcheck.sourceforge.io/ 包管理器或安装包
Clang-Tidy https://clang.llvm.org/extra/clang-tidy/ 随LLVM安装
SonarQube https://www.sonarqube.org/ Docker容器或独立安装
Unity https://github.com/ThrowTheSwitch/Unity 从GitHub下载源码
CMock https://github.com/ThrowTheSwitch/CMock 从GitHub下载源码
Google Test https://github.com/google/googletest CMake构建并安装