Files
2025-06-17 16:04:24 +08:00

849 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📦 第八层:项目实战与工具链
## ✅ 工程管理
### 🔹 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)打标签:
```bash
# 创建标签
git tag v1.0.0
# 推送标签到远程
git push origin v1.0.0
# 查看所有标签
git tag -l
```
### 🔹 Makefile、CMake 构建工具
#### 1. **Makefile 基础**
- **简单示例**:
```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
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 配置**
- **编译与测试工作流**:
```yaml
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 集成**
- **构建脚本示例**:
```groovy
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 实现示例**
```c
// 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驱动示例**
```c
// 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状态机**
```c
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. **检查更新**:
```c
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. **下载固件**:
```c
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. **验证与应用**:
```c
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
**官网链接**:
- [VS Code](https://code.visualstudio.com/)
- [PlatformIO](https://platformio.org/)
**安装步骤**:
1. 下载并安装 [VS Code](https://code.visualstudio.com/Download)
2. 打开VS Code,点击左侧扩展图标(或按 `Ctrl+Shift+X`)
3. 搜索并安装 **PlatformIO IDE** 扩展
4. 安装完成后,重启VS Code
5. PlatformIO会自动安装所需的工具链和依赖
**验证安装**:
打开VS Code,点击左下角的 **PlatformIO Home** 图标,若能正常打开则安装成功。
### STM32CubeIDE
**官网链接**:
- [STM32CubeIDE](https://www.st.com/en/development-tools/stm32cubeide.html)
**安装步骤**:
1. 访问官网,点击 **Get Software** 下载对应操作系统的安装包
2. 运行安装程序,按照向导完成安装
3. 安装过程中会自动下载并配置STM32CubeMX
**验证安装**:
启动STM32CubeIDE,创建一个新的STM32项目,若能正常编译则安装成功。
### CLion
**官网链接**:
- [CLion](https://www.jetbrains.com/clion/)
**安装步骤**:
1. 下载并安装 [CLion](https://www.jetbrains.com/clion/download/)
2. 安装CMake和MinGW(Windows用户需要):
- CMake:从 [官网](https://cmake.org/download/) 下载并安装
- MinGW:推荐使用 [MSYS2](https://www.msys2.org/) 安装
**验证安装**:
启动CLion,创建一个新的C/C++项目,选择CMake工具链,若能正常编译则安装成功。
## 2. **调试工具**
### OpenOCD
**官网链接**:
- [OpenOCD](http://openocd.org/)
**安装步骤**:
- **Windows**:
1. 从 [GNU MCU Eclipse](https://github.com/gnu-mcu-eclipse/openocd/releases) 下载预编译二进制包
2. 解压到指定目录(如 `C:\openocd`)
3. 将 `bin` 目录添加到系统环境变量
- **Linux**:
```bash
sudo apt-get install openocd # Ubuntu/Debian
sudo yum install openocd # CentOS/RHEL
```
- **macOS**:
```bash
brew install open-ocd
```
**验证安装**:
在终端中运行 `openocd --version`,若显示版本信息则安装成功。
### GDB
**官网链接**:
- [GDB](https://www.gnu.org/software/gdb/)
- [ARM GCC Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm)
**安装步骤**:
1. 下载并安装 [ARM GCC Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads)
2. 将 `bin` 目录添加到系统环境变量
**验证安装**:
在终端中运行 `arm-none-eabi-gdb --version`,若显示版本信息则安装成功。
### ST-Link/V2
**官网链接**:
- [ST-Link](https://www.st.com/en/development-tools/st-link-v2.html)
**安装步骤**:
- **Windows**:
1. 从 [ST官网](https://www.st.com/en/development-tools/stsw-link004.html) 下载并安装ST-Link驱动
2. 安装完成后,将ST-Link/V2调试器连接到电脑
- **Linux**:
```bash
sudo apt-get install stlink-tools # Ubuntu/Debian
```
**验证安装**:
在终端中运行 `st-info --version`,若显示版本信息则安装成功。
## 3. **静态代码分析**
### CppCheck
**官网链接**:
- [CppCheck](https://cppcheck.sourceforge.io/)
**安装步骤**:
- **Windows**:
1. 从 [官网](https://cppcheck.sourceforge.io/) 下载安装包
2. 运行安装程序,按照向导完成安装
- **Linux**:
```bash
sudo apt-get install cppcheck # Ubuntu/Debian
sudo yum install cppcheck # CentOS/RHEL
```
- **macOS**:
```bash
brew install cppcheck
```
**验证安装**:
在终端中运行 `cppcheck --version`,若显示版本信息则安装成功。
### Clang-Tidy
**官网链接**:
- [Clang-Tidy](https://clang.llvm.org/extra/clang-tidy/)
**安装步骤**:
- **Windows**:
1. 安装 [LLVM](https://releases.llvm.org/download.html)
2. Clang-Tidy会随LLVM一起安装
- **Linux**:
```bash
sudo apt-get install clang-tidy # Ubuntu/Debian
```
- **macOS**:
```bash
brew install llvm
```
**验证安装**:
在终端中运行 `clang-tidy --version`,若显示版本信息则安装成功。
### SonarQube
**官网链接**:
- [SonarQube](https://www.sonarqube.org/)
**安装步骤**:
1. 下载并安装 [Docker](https://www.docker.com/get-started)
2. 运行SonarQube容器:
```bash
docker run -d --name sonarqube -p 9000:9000 sonarqube
```
3. 访问 [http://localhost:9000](http://localhost:9000),使用默认账号(admin/admin)登录
**验证安装**:
在浏览器中打开 [http://localhost:9000](http://localhost:9000),若能看到SonarQube界面则安装成功。
## 4. **单元测试**
### Unity
**官网链接**:
- [Unity](https://github.com/ThrowTheSwitch/Unity)
**安装步骤**:
1. 从GitHub下载Unity源码:
```bash
git clone https://github.com/ThrowTheSwitch/Unity.git
```
2. 将 `src` 目录添加到项目的头文件搜索路径
**验证安装**:
创建一个简单的测试文件,包含Unity头文件,若能正常编译则安装成功。
### CMock
**官网链接**:
- [CMock](https://github.com/ThrowTheSwitch/CMock)
**安装步骤**:
1. 从GitHub下载CMock源码:
```bash
git clone https://github.com/ThrowTheSwitch/CMock.git
```
2. 将 `src` 目录添加到项目的头文件搜索路径
**验证安装**:
创建一个简单的测试文件,包含CMock头文件,若能正常编译则安装成功。
### Google Test
**官网链接**:
- [Google Test](https://github.com/google/googletest)
**安装步骤**:
1. 从GitHub下载Google Test源码:
```bash
git clone https://github.com/google/googletest.git
```
2. 使用CMake构建并安装:
```bash
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构建并安装 |