📋 完整设计流程图
跳转到“📋 完整设计流程图”graph TD A[需求分析] --> B[系统设计] B --> C[硬件接口设计] C --> D[软件架构设计] D --> E[详细设计] E --> F[编码实现] F --> G[单元测试] G --> H[集成测试] H --> I[系统测试] I --> J[文档编写] J --> K[发布交付]
style A fill:#e1f5ff style D fill:#fff4e1 style F fill:#e8f5e9 style I fill:#fce4ec🎯 第一阶段:需求分析与规划
跳转到“🎯 第一阶段:需求分析与规划”步骤1:需求分析
跳转到“步骤1:需求分析”目标:明确项目要做什么
输出物:
- 需求规格说明书
- 功能需求列表
- 性能指标要求
内容包括:
- ✅ 功能需求(做什么功能)
- ✅ 性能需求(速度、精度、实时性)
- ✅ 接口需求(与外部系统的接口)
- ✅ 约束条件(成本、功耗、尺寸)
- ✅ 可靠性需求(MTBF、容错能力)
示例:
项目:智能温控系统功能需求: - 实时采集温湿度(每秒1次) - LCD显示当前温湿度 - 温度超过30℃时启动风扇 - 支持按键设置目标温度 - 数据通过UART上传到上位机
性能需求: - 温度测量精度:±0.5℃ - 响应时间:<100ms - 工作温度:-10℃~60℃
接口需求: - DHT22传感器(GPIO) - LCD1602显示屏(I2C) - 继电器控制(GPIO) - UART通信(115200bps)步骤2:可行性分析
跳转到“步骤2:可行性分析”目标:确认技术方案可行
内容:
- 芯片选型(STM32F103? STM32F407?)
- 外设资源评估(GPIO、定时器、通信接口是否够用)
- 存储资源评估(Flash、RAM是否足够)
- 功耗分析
- 成本预算
芯片选型表:
| 型号 | Flash | RAM | 主频 | 外设 | 价格 | 是否满足 |
|---|---|---|---|---|---|---|
| STM32F103C8T6 | 64KB | 20KB | 72MHz | 2xUART, I2C, SPI | ¥8 | ✅ |
| STM32F407VGT6 | 1MB | 192KB | 168MHz | 多路UART, CAN | ¥35 | ✅ 性能过剩 |
🏗️ 第二阶段:系统设计
跳转到“🏗️ 第二阶段:系统设计”步骤3:系统架构设计
跳转到“步骤3:系统架构设计”目标:设计整体架构和分层
输出物:
- 系统架构图
- 分层设计文档
- 模块划分方案
分层架构:
| 层次 | 职责 | 模块示例 |
|---|---|---|
| 应用层 | 业务逻辑 | app_main, app_temp_control, app_display |
| 中间件层 | 通用服务 | FreeRTOS, 协议解析, 数据处理算法 |
| 驱动层 | 设备驱动 | drv_dht22, drv_lcd, drv_uart, drv_relay |
| BSP层 | 板级初始化 | bsp_clock, bsp_gpio, bsp_init |
| HAL层 | 硬件抽象 | STM32 HAL库 |
步骤4:硬件接口设计
跳转到“步骤4:硬件接口设计”目标:明确软硬件接口
输出物:
- 引脚分配表
- 接口时序图
- 硬件接口说明文档
引脚分配表示例:
| 功能 | 引脚 | 配置 | 说明 |
|---|---|---|---|
| DHT22数据 | PA0 | GPIO输入/输出 | 单总线协议 |
| LCD_SDA | PB7 | I2C_SDA | I2C从设备地址0x27 |
| LCD_SCL | PB6 | I2C_SCL | I2C速率100KHz |
| 继电器控制 | PA5 | GPIO输出 | 高电平闭合 |
| UART_TX | PA9 | USART1_TX | 115200,8,N,1 |
| UART_RX | PA10 | USART1_RX | 115200,8,N,1 |
| 按键1 | PC13 | GPIO输入+上拉 | 低电平有效 |
步骤5:模块划分设计
跳转到“步骤5:模块划分设计”目标:将系统分解为独立模块
输出物:
- 模块列表
- 模块职责说明
- 模块依赖关系图
模块划分示例:
应用层模块:├── app_main.c # 主程序入口├── app_sensor.c # 传感器数据采集任务├── app_display.c # 显示任务├── app_control.c # 温控逻辑任务└── app_communication.c # 串口通信任务
驱动层模块:├── drv_dht22.c # DHT22温湿度传感器驱动├── drv_lcd1602.c # LCD1602显示驱动├── drv_uart.c # UART驱动封装└── drv_relay.c # 继电器驱动
工具模块:├── util_ringbuffer.c # 环形缓冲区├── util_crc.c # CRC校验└── util_delay.c # 延时函数模块依赖关系图:
graph TD A[app_main] --> B[app_sensor] A --> C[app_display] A --> D[app_control] A --> E[app_communication]
B --> F[drv_dht22] C --> G[drv_lcd1602] D --> H[drv_relay] E --> I[drv_uart]
F --> J[HAL_GPIO] G --> K[HAL_I2C] H --> J I --> L[HAL_UART]📐 第三阶段:详细设计
跳转到“📐 第三阶段:详细设计”步骤6:模块详细设计
跳转到“步骤6:模块详细设计”目标:设计每个模块的内部实现
输出物:
- 模块设计文档
- 接口定义(.h文件)
- 内部流程图
- 状态机图
模块设计文档模板:
DHT22驱动模块设计
跳转到“DHT22驱动模块设计”1. 模块概述
跳转到“1. 模块概述”负责DHT22温湿度传感器的数据读取
2. 依赖关系
跳转到“2. 依赖关系”- 依赖:HAL_GPIO, HAL_Delay
- 被依赖:app_sensor
3. 接口定义
跳转到“3. 接口定义”// 初始化DHT22_Status_t DHT22_Init(void);
// 读取数据DHT22_Status_t DHT22_Read(float *temp, float *humi);
// 数据结构typedef struct { float temperature; float humidity; uint32_t timestamp;} DHT22_Data_t;4. 内部流程
跳转到“4. 内部流程”[流程图]
5. 时序要求
跳转到“5. 时序要求”- 起始信号:低电平18ms
- 等待响应:80us低 + 80us高
- 数据位:50us低 + 26~70us高
内部流程图示例:
graph TD A[DHT22_Read开始] --> B[发送起始信号] B --> C[等待DHT22响应] C --> D{响应超时?} D -->|是| E[返回ERROR] D -->|否| F[读取40位数据] F --> G[校验和验证] G --> H{校验通过?} H -->|否| E H -->|是| I[解析温湿度] I --> J[返回OK]步骤7:接口设计(API设计)
跳转到“步骤7:接口设计(API设计)”目标:定义模块对外接口
设计原则:
- ✅ 接口简洁明了
- ✅ 参数校验完整
- ✅ 返回值统一(状态码)
- ✅ 使用结构体传递复杂数据
- ✅ 避免全局变量
接口设计示例:
/****************************************************************************** * 文件:drv_dht22.h * 描述:DHT22温湿度传感器驱动接口 ******************************************************************************/
#ifndef __DRV_DHT22_H#define __DRV_DHT22_H
#include <stdint.h>
/* 返回状态码 */typedef enum { DHT22_OK = 0, // 成功 DHT22_ERROR = -1, // 一般错误 DHT22_TIMEOUT = -2, // 超时 DHT22_CHECKSUM = -3, // 校验和错误 DHT22_NOT_INIT = -4 // 未初始化} DHT22_Status_t;
/* 数据结构 */typedef struct { float temperature; // 温度,单位:℃ float humidity; // 湿度,单位:%RH uint32_t timestamp; // 时间戳,单位:ms} DHT22_Data_t;
/* 配置结构 */typedef struct { GPIO_TypeDef *port; // GPIO端口 uint16_t pin; // GPIO引脚 uint32_t timeout_ms; // 超时时间} DHT22_Config_t;
/****************************************************************************** * 函数名:DHT22_Init * 描述: 初始化DHT22传感器 * 参数: config - 配置参数 * 返回: DHT22_OK - 成功 * DHT22_ERROR - 失败 ******************************************************************************/DHT22_Status_t DHT22_Init(DHT22_Config_t *config);
/****************************************************************************** * 函数名:DHT22_Read * 描述: 读取温湿度数据 * 参数: data - 输出数据指针 * 返回: DHT22_OK - 成功 * DHT22_TIMEOUT - 超时 * DHT22_CHECKSUM - 校验错误 * 注意: 两次读取间隔应大于2秒 ******************************************************************************/DHT22_Status_t DHT22_Read(DHT22_Data_t *data);
/****************************************************************************** * 函数名:DHT22_DeInit * 描述: 反初始化 * 返回: DHT22_OK - 成功 ******************************************************************************/DHT22_Status_t DHT22_DeInit(void);
#endif /* __DRV_DHT22_H */步骤8:数据结构设计
跳转到“步骤8:数据结构设计”目标:设计模块使用的数据结构
常用数据结构:
- 配置结构体
- 数据结构体
- 状态枚举
- 错误码枚举
- 回调函数类型
示例:
/* 传感器配置 */typedef struct { uint8_t sample_rate; // 采样率 Hz uint8_t filter_enable; // 滤波使能 float threshold_high; // 高温阈值 float threshold_low; // 低温阈值} SensorConfig_t;
/* 系统状态 */typedef enum { SYS_IDLE = 0, SYS_RUNNING, SYS_ERROR, SYS_SLEEP} SystemState_t;
/* 回调函数类型 */typedef void (*EventCallback_t)(void *param);💻 第四阶段:编码实现
跳转到“💻 第四阶段:编码实现”步骤9:编码规范制定
跳转到“步骤9:编码规范制定”目标:统一团队编码风格
内容:
- 命名规范
- 代码格式
- 注释规范
- 文件组织
命名规范示例:
| 类型 | 规范 | 示例 |
|---|---|---|
| 文件名 | 小写+下划线 | drv_uart.c |
| 宏定义 | 大写+下划线 | #define MAX_BUFFER_SIZE 256 |
| 全局变量 | g_前缀+小写驼峰 | g_systemStatus |
| 静态变量 | s_前缀+小写驼峰 | static uint8_t s_initialized; |
| 函数名 | 模块名_功能 | UART_Send() |
| 类型定义 | _t后缀 | typedef struct {...} SensorData_t; |
步骤10:按模块编码实现
跳转到“步骤10:按模块编码实现”编码顺序:
- 先底层后上层(HAL → 驱动 → 中间件 → 应用)
- 先稳定模块后变化模块
- 先独立模块后依赖模块
编码示例(完整模块):
/****************************************************************************** * 文件:drv_dht22.c * 描述:DHT22温湿度传感器驱动实现 * 作者:张三 * 日期:2024-12-09 * 版本:V1.0 ******************************************************************************/
#include "drv_dht22.h"#include "stm32f1xx_hal.h"
/* 私有宏定义 */#define DHT22_TIMEOUT_MS 100#define DHT22_START_SIGNAL 18 // ms#define DHT22_DATA_BITS 40
/* 私有变量 */static uint8_t s_initialized = 0;static DHT22_Config_t s_config;static uint8_t s_data_buffer[5];
/* 私有函数声明 */static DHT22_Status_t dht22_send_start(void);static DHT22_Status_t dht22_wait_response(void);static DHT22_Status_t dht22_read_byte(uint8_t *byte);static uint8_t dht22_verify_checksum(void);
/****************************************************************************** * 函数名:DHT22_Init ******************************************************************************/DHT22_Status_t DHT22_Init(DHT22_Config_t *config){ if (config == NULL) { return DHT22_ERROR; }
if (s_initialized) { return DHT22_OK; // 已初始化 }
// 保存配置 s_config = *config;
// 配置GPIO GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = s_config.pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(s_config.port, &GPIO_InitStruct);
// 初始状态拉高 HAL_GPIO_WritePin(s_config.port, s_config.pin, GPIO_PIN_SET);
s_initialized = 1; return DHT22_OK;}
/****************************************************************************** * 函数名:DHT22_Read ******************************************************************************/DHT22_Status_t DHT22_Read(DHT22_Data_t *data){ if (!s_initialized) { return DHT22_NOT_INIT; }
if (data == NULL) { return DHT22_ERROR; }
// 1. 发送起始信号 if (dht22_send_start() != DHT22_OK) { return DHT22_TIMEOUT; }
// 2. 等待DHT22响应 if (dht22_wait_response() != DHT22_OK) { return DHT22_TIMEOUT; }
// 3. 读取40位数据 for (int i = 0; i < 5; i++) { if (dht22_read_byte(&s_data_buffer[i]) != DHT22_OK) { return DHT22_TIMEOUT; } }
// 4. 校验和验证 if (!dht22_verify_checksum()) { return DHT22_CHECKSUM; }
// 5. 解析数据 uint16_t humi_raw = (s_data_buffer[0] << 8) | s_data_buffer[1]; uint16_t temp_raw = (s_data_buffer[2] << 8) | s_data_buffer[3];
data->humidity = humi_raw / 10.0f; data->temperature = temp_raw / 10.0f; data->timestamp = HAL_GetTick();
return DHT22_OK;}
/****************************************************************************** * 私有函数实现 ******************************************************************************/static DHT22_Status_t dht22_send_start(void){ // 拉低至少18ms HAL_GPIO_WritePin(s_config.port, s_config.pin, GPIO_PIN_RESET); HAL_Delay(DHT22_START_SIGNAL);
// 拉高20-40us HAL_GPIO_WritePin(s_config.port, s_config.pin, GPIO_PIN_SET); delay_us(30);
return DHT22_OK;}
static uint8_t dht22_verify_checksum(void){ uint8_t sum = s_data_buffer[0] + s_data_buffer[1] + s_data_buffer[2] + s_data_buffer[3]; return (sum == s_data_buffer[4]);}🧪 第五阶段:测试验证
跳转到“🧪 第五阶段:测试验证”步骤11:单元测试
跳转到“步骤11:单元测试”目标:测试每个模块功能
测试内容:
- 正常功能测试
- 边界条件测试
- 异常处理测试
- 参数校验测试
测试用例示例:
/* DHT22驱动单元测试 */void test_dht22_driver(void){ DHT22_Config_t config; DHT22_Data_t data; DHT22_Status_t status;
// 测试1:未初始化直接读取 status = DHT22_Read(&data); assert(status == DHT22_NOT_INIT);
// 测试2:正常初始化 config.port = GPIOA; config.pin = GPIO_PIN_0; status = DHT22_Init(&config); assert(status == DHT22_OK);
// 测试3:NULL指针测试 status = DHT22_Read(NULL); assert(status == DHT22_ERROR);
// 测试4:正常读取 status = DHT22_Read(&data); assert(status == DHT22_OK); assert(data.temperature > -40 && data.temperature < 80);
printf("DHT22 Driver Test: PASS\n");}步骤12:集成测试
跳转到“步骤12:集成测试”目标:测试模块间协作
测试内容:
- 模块间接口测试
- 数据流测试
- 时序测试
步骤13:系统测试
跳转到“步骤13:系统测试”目标:完整系统功能验证
测试内容:
- 功能测试(所有需求是否实现)
- 性能测试(响应时间、精度)
- 稳定性测试(长时间运行)
- 压力测试(极限条件)
测试报告模板:
系统测试报告
跳转到“系统测试报告”1. 测试环境
跳转到“1. 测试环境”- 硬件:STM32F103C8T6开发板
- 编译器:MDK-ARM 5.36
- 测试工具:示波器、串口调试助手
2. 功能测试
跳转到“2. 功能测试”| 测试项 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|
| 温度采集 | 精度±0.5℃ | 精度±0.3℃ | ✅ |
| LCD显示 | 刷新率1Hz | 刷新率1Hz | ✅ |
| 风扇控制 | >30℃启动 | >30℃启动 | ✅ |
3. 性能测试
跳转到“3. 性能测试”| 指标 | 要求 | 测试结果 | 是否通过 |
|---|---|---|---|
| 响应时间 | <100ms | 85ms | ✅ |
| CPU占用率 | <50% | 32% | ✅ |
| 内存占用 | <16KB | 12KB | ✅ |
4. 稳定性测试
跳转到“4. 稳定性测试”- 连续运行72小时无故障
- 1000次读写操作无错误
5. 问题记录
跳转到“5. 问题记录”无
6. 结论
跳转到“6. 结论”系统通过所有测试,满足设计要求
📝 第六阶段:文档编写
跳转到“📝 第六阶段:文档编写”步骤14:编写技术文档
跳转到“步骤14:编写技术文档”必需文档:
- 需求规格说明书 (SRS)
- 系统设计文档 (SDD)
- 详细设计文档 (DDD)
- 接口文档 (API Documentation)
- 测试报告 (Test Report)
- 用户手册 (User Manual)
- 维护手册 (Maintenance Manual)
代码注释(Doxygen格式):
/** * @file drv_dht22.c * @brief DHT22温湿度传感器驱动 * @author 张三 * @date 2024-12-09 * @version 1.0 */
/** * @brief 读取DHT22温湿度数据 * @param data 输出数据指针 * @retval DHT22_OK 成功 * @retval DHT22_TIMEOUT 超时 * @retval DHT22_CHECKSUM 校验错误 * @note 两次读取间隔应大于2秒 */DHT22_Status_t DHT22_Read(DHT22_Data_t *data);🚀 第七阶段:发布交付
跳转到“🚀 第七阶段:发布交付”步骤15:版本管理与发布
跳转到“步骤15:版本管理与发布”内容:
- 代码审查(Code Review)
- 版本打标签(Git Tag)
- 编译发布版本
- 生成Hex/Bin文件
版本号规则:
V主版本.次版本.修订版本例如:V1.2.3- 主版本:重大架构变更- 次版本:功能新增- 修订版本:Bug修复📊 完整流程总结表
跳转到“📊 完整流程总结表”| 阶段 | 步骤 | 输出物 | 工具 |
|---|---|---|---|
| 需求阶段 | 1. 需求分析 2. 可行性分析 | 需求规格书 芯片选型报告 | Word, Excel |
| 系统设计 | 3. 系统架构设计 4. 硬件接口设计 5. 模块划分 | 架构图 引脚分配表 模块列表 | Draw.io, Visio |
| 详细设计 | 6. 模块详细设计 7. 接口设计 8. 数据结构设计 | 详细设计文档 .h头文件 流程图 | Markdown, PlantUML |
| 编码实现 | 9. 编码规范 10. 模块编码 | 源代码 Makefile | Keil, IAR, VSCode |
| 测试验证 | 11. 单元测试 12. 集成测试 13. 系统测试 | 测试用例 测试报告 | CUnit, 示波器 |
| 文档发布 | 14. 文档编写 15. 版本发布 | 技术文档 Hex文件 | Doxygen, Git |
⚡ 快速开发建议(针对不同项目规模)
跳转到“⚡ 快速开发建议(针对不同项目规模)”小型项目(个人/学习项目)
跳转到“小型项目(个人/学习项目)”可简化流程:
- 需求分析(简单列表)
- 模块划分
- 接口设计
- 编码实现
- 功能测试
中型项目(产品级项目)
跳转到“中型项目(产品级项目)”标准流程:
- ✅ 需求分析
- ✅ 系统设计
- ✅ 详细设计
- ✅ 编码实现
- ✅ 测试验证
- ✅ 文档编写
大型项目(工业级项目)
跳转到“大型项目(工业级项目)”完整流程 + 额外要求:
- CMMI/ISO流程管理
- 配置管理
- 变更管理
- 风险管理
- 代码审查
- 静态分析
- 覆盖率测试
🛠️ 推荐工具链
跳转到“🛠️ 推荐工具链”| 用途 | 工具推荐 |
|---|---|
| IDE | Keil MDK, STM32CubeIDE, IAR |
| 版本控制 | Git + GitHub/Gitee |
| 文档工具 | Markdown, Doxygen, Word |
| 画图工具 | Draw.io, Visio, PlantUML, Mermaid |
| 调试工具 | ST-Link, J-Link, 逻辑分析仪 |
| 测试工具 | CUnit, Unity, CppUTest |
| 静态分析 | PC-Lint, Cppcheck |
这就是工业级STM32嵌入式软件开发的完整流程!你现在在做什么阶段的项目?我可以针对性地提供更详细的指导。
STM32F103C8T6 项目软件设计方案
跳转到“STM32F103C8T6 项目软件设计方案”根据你的需求,我来帮你做完整的软件设计分析和方案制定。
📋 第一步:需求分析与可行性评估
跳转到“📋 第一步:需求分析与可行性评估”1.1 需求整理
跳转到“1.1 需求整理”| 需求项 | 详细说明 | 硬件接口 | 优先级 |
|---|---|---|---|
| 上位机通信 | 自定义协议,USART1通信 | USART1 (PA9/PA10) | 高 |
| 电池电量读取 | 通过IIC读取电量芯片 | I2C1/I2C2 | 高 |
| 放大倍数控制 | GPIO控制放大器增益 | GPIO输出 | 高 |
| 参数保存 | 放大倍数等参数掉电保存 | I2C EEPROM | 中 |
1.2 资源评估
跳转到“1.2 资源评估”STM32F103C8T6 资源:
- Flash: 64KB
- RAM: 20KB
- 主频: 72MHz
- USART: 3路
- I2C: 2路
- GPIO: 充足
是否需要RTOS判断:
| 评估维度 | 分析 | 结论 |
|---|---|---|
| 任务复杂度 | 4个独立功能模块,相对独立 | 建议使用RTOS |
| 实时性要求 | 串口通信需要及时响应 | 建议使用RTOS |
| 并发需求 | 多个功能需要”同时”运行 | 建议使用RTOS |
| 资源占用 | FreeRTOS约占8-12KB Flash,2-4KB RAM | ✅ 资源充足 |
| 开发效率 | RTOS简化任务管理,代码更清晰 | 建议使用RTOS |
| 可维护性 | 任务独立,便于调试和扩展 | 建议使用RTOS |
结论:✅ 建议使用 FreeRTOS
跳转到“结论:✅ 建议使用 FreeRTOS”理由:
- 你的项目有多个独立功能模块,使用RTOS可以让代码结构更清晰
- 串口通信需要及时响应,RTOS的任务调度机制更合适
- STM32F103C8T6资源足够支持轻量级FreeRTOS
- 便于后期功能扩展和维护
🏗️ 第二步:系统架构设计
跳转到“🏗️ 第二步:系统架构设计”2.1 分层架构
跳转到“2.1 分层架构”┌─────────────────────────────────────┐│ 应用层 (Application) ││ ┌──────────┐ ┌──────────┐ ││ │串口通信 │ │电池管理 │ ││ │任务 │ │任务 │ ││ └──────────┘ └──────────┘ ││ ┌──────────┐ ┌──────────┐ ││ │增益控制 │ │参数管理 │ ││ │任务 │ │任务 │ ││ └──────────┘ └──────────┘ │├─────────────────────────────────────┤│ 中间件层 (Middleware) ││ ┌─────────────────────────────┐ ││ │ FreeRTOS (任务调度/队列) │ ││ └─────────────────────────────┘ ││ ┌─────────────────────────────┐ ││ │ 协议解析模块 │ ││ └─────────────────────────────┘ │├─────────────────────────────────────┤│ 驱动层 (Drivers) ││ ┌──────────┐ ┌──────────┐ ││ │UART驱动 │ │I2C驱动 │ ││ └──────────┘ └──────────┘ ││ ┌──────────┐ ┌──────────┐ ││ │GPIO驱动 │ │EEPROM驱动│ ││ └──────────┘ └──────────┘ ││ ┌──────────┐ ││ │电量芯片 │ ││ │驱动 │ ││ └──────────┘ │├─────────────────────────────────────┤│ BSP层 (Board Support) ││ 系统时钟/GPIO初始化/外设配置 │├─────────────────────────────────────┤│ HAL层 (STM32 HAL库) │└─────────────────────────────────────┘2.2 任务架构设计
跳转到“2.2 任务架构设计”graph TD A[系统启动] --> B[硬件初始化] B --> C[创建FreeRTOS任务] C --> D[启动调度器]
D --> E[串口通信任务] D --> F[电池监控任务] D --> G[增益控制任务]
E --> H[串口接收队列] E --> I[串口发送队列]
F --> J[电量数据队列]
G --> K[参数保存触发]
style E fill:#e1f5ff style F fill:#fff4e1 style G fill:#e8f5e9FreeRTOS任务规划:
| 任务名称 | 优先级 | 堆栈大小 | 运行周期 | 功能 |
|---|---|---|---|---|
| 串口通信任务 | 3 (高) | 256字节 | 事件触发 | 接收解析上位机命令,发送响应 |
| 电池监控任务 | 2 (中) | 128字节 | 1秒 | 读取电池电量,上报数据 |
| 增益控制任务 | 2 (中) | 128字节 | 事件触发 | 控制放大倍数 |
| 参数保存任务 | 1 (低) | 128字节 | 事件触发 | 保存参数到EEPROM |
任务间通信:
- 使用消息队列:串口接收队列、电量数据队列
- 使用信号量:EEPROM访问互斥、GPIO控制互斥
- 使用事件标志组:参数保存请求、电量告警
📐 第三步:硬件接口设计
跳转到“📐 第三步:硬件接口设计”3.1 引脚分配表
跳转到“3.1 引脚分配表”| 功能 | 引脚 | 配置 | 说明 |
|---|---|---|---|
| USART1通信 | |||
| USART1_TX | PA9 | 复用推挽输出 | 上位机通信发送 |
| USART1_RX | PA10 | 浮空输入 | 上位机通信接收 |
| I2C接口 | |||
| I2C1_SCL | PB6 | 复用开漏输出 | 电量芯片/EEPROM时钟 |
| I2C1_SDA | PB7 | 复用开漏输出 | 电量芯片/EEPROM数据 |
| 增益控制 | |||
| GAIN_CTRL0 | PA0 | 推挽输出 | 增益控制位0 |
| GAIN_CTRL1 | PA1 | 推挽输出 | 增益控制位1 |
| GAIN_CTRL2 | PA2 | 推挽输出 | 增益控制位2 (可选) |
| 状态指示 | |||
| LED_STATUS | PC13 | 推挽输出 | 系统状态指示 |
3.2 I2C设备地址规划
跳转到“3.2 I2C设备地址规划”| 设备 | I2C地址 | 说明 |
|---|---|---|
| 电量芯片 (如BQ27441) | 0x55 | 7位地址 |
| EEPROM (如AT24C02) | 0x50 | 7位地址 |
3.3 增益控制编码
跳转到“3.3 增益控制编码”假设使用3个GPIO控制8档增益:
| 增益倍数 | CTRL2 | CTRL1 | CTRL0 | 说明 |
|---|---|---|---|---|
| 1x | 0 | 0 | 0 | 无放大 |
| 2x | 0 | 0 | 1 | 2倍 |
| 5x | 0 | 1 | 0 | 5倍 |
| 10x | 0 | 1 | 1 | 10倍 |
| 20x | 1 | 0 | 0 | 20倍 |
| 50x | 1 | 0 | 1 | 50倍 |
| 100x | 1 | 1 | 0 | 100倍 |
| 200x | 1 | 1 | 1 | 200倍 |
🔧 第四步:模块划分设计
跳转到“🔧 第四步:模块划分设计”4.1 项目目录结构
跳转到“4.1 项目目录结构”Project/├── App/ # 应用层│ ├── app_main.c # 主任务和系统初始化│ ├── task_uart.c # 串口通信任务│ ├── task_battery.c # 电池监控任务│ ├── task_gain.c # 增益控制任务│ └── task_param.c # 参数保存任务│├── Middleware/ # 中间件层│ ├── FreeRTOS/ # FreeRTOS源码│ │ ├── Source/│ │ └── portable/│ └── Protocol/ # 协议解析│ ├── protocol.c # 自定义协议解析│ └── protocol.h│├── Drivers/ # 驱动层│ ├── BSP/ # 板级驱动│ │ ├── bsp_uart.c # UART驱动封装│ │ ├── bsp_i2c.c # I2C驱动封装│ │ ├── bsp_gpio.c # GPIO驱动封装│ │ └── bsp_led.c # LED驱动│ ├── Device/ # 设备驱动│ │ ├── drv_battery.c # 电量芯片驱动│ │ ├── drv_eeprom.c # EEPROM驱动│ │ └── drv_gain.c # 增益控制驱动│ └── HAL/ # STM32 HAL库│├── Common/ # 公共组件│ ├── ringbuffer.c # 环形缓冲区│ ├── crc.c # CRC校验│ └── utilities.c # 工具函数│├── Config/ # 配置文件│ ├── FreeRTOSConfig.h # FreeRTOS配置│ ├── system_config.h # 系统配置│ └── pin_config.h # 引脚定义│└── Inc/ # 所有头文件4.2 模块依赖关系图
跳转到“4.2 模块依赖关系图”graph TD A[task_uart] --> B[protocol] A --> C[bsp_uart]
D[task_battery] --> E[drv_battery] E --> F[bsp_i2c]
G[task_gain] --> H[drv_gain] H --> I[bsp_gpio]
J[task_param] --> K[drv_eeprom] K --> F
B --> L[CRC校验]
C --> M[HAL_UART] F --> N[HAL_I2C] I --> O[HAL_GPIO]
style A fill:#e1f5ff style D fill:#fff4e1 style G fill:#e8f5e9 style J fill:#fce4ec📡 第五步:协议设计
跳转到“📡 第五步:协议设计”5.1 自定义协议格式
跳转到“5.1 自定义协议格式”数据帧格式:
┌────────┬────────┬────────┬────────┬────────┬────────┬────────┐│ 帧头 │ 命令码 │ 数据长度│ 数据 │ 校验和 │ 帧尾 │ ││ (2字节)│ (1字节)│ (1字节) │(N字节) │ (2字节)│ (2字节)│ ││ 0xAA55│ CMD │ LEN │ DATA │ CRC16 │ 0x0D0A│ │└────────┴────────┴────────┴────────┴────────┴────────┴────────┘5.2 命令码定义
跳转到“5.2 命令码定义”| 命令码 | 命令名称 | 方向 | 数据内容 | 说明 |
|---|---|---|---|---|
| 0x01 | 读取电量 | 上→下 | 无 | 请求读取电池电量 |
| 0x81 | 电量响应 | 下→上 | 电量百分比(1字节) | 返回电量数据 |
| 0x02 | 设置增益 | 上→下 | 增益档位(1字节) | 设置放大倍数 |
| 0x82 | 增益响应 | 下→上 | 当前增益(1字节) | 返回设置结果 |
| 0x03 | 读取参数 | 上→下 | 参数ID(1字节) | 读取保存的参数 |
| 0x83 | 参数响应 | 下→上 | 参数值(4字节) | 返回参数数据 |
| 0x04 | 保存参数 | 上→下 | 参数ID+值(5字节) | 保存参数到EEPROM |
| 0x84 | 保存响应 | 下→上 | 结果(1字节) | 0=成功 1=失败 |
| 0xFF | 心跳包 | 双向 | 无 | 保持连接 |
5.3 协议示例
跳转到“5.3 协议示例”示例1:上位机请求读取电量
发送: AA 55 01 00 [CRC16] 0D 0A响应: AA 55 81 01 4B [CRC16] 0D 0A (电量75%)示例2:上位机设置增益为10x
发送: AA 55 02 01 03 [CRC16] 0D 0A (档位3=10x)响应: AA 55 82 01 03 [CRC16] 0D 0A (确认设置为档位3)💻 第六步:详细设计(核心模块)现在我为你生成一份完整的系统配置文档:最后,我为你创建一个完整的main函数和系统初始化代码:---
跳转到“💻 第六步:详细设计(核心模块)现在我为你生成一份完整的系统配置文档:最后,我为你创建一个完整的main函数和系统初始化代码:---”📊 第七步:开发流程时间表
跳转到“📊 第七步:开发流程时间表”基于你的项目,我给出一个实际的开发计划:
| 阶段 | 工作内容 | 预计时间 | 输出物 |
|---|---|---|---|
| 第1周 | 需求分析、硬件选型、原理图设计 | 3天 | 需求文档、原理图 |
| 引脚分配、接口设计 | 2天 | 引脚分配表、接口文档 | |
| 第2周 | BSP层开发(时钟、GPIO、UART、I2C) | 3天 | BSP驱动代码 |
| 驱动层开发(电量芯片、EEPROM、增益控制) | 2天 | 设备驱动代码 | |
| 第3周 | 协议设计与实现 | 2天 | 协议文档、协议代码 |
| FreeRTOS移植与配置 | 1天 | FreeRTOS工程 | |
| 串口通信任务开发 | 2天 | 串口任务代码 | |
| 第4周 | 电池监控任务开发 | 1天 | 电池任务代码 |
| 增益控制任务开发 | 1天 | 增益任务代码 | |
| 参数保存任务开发 | 1天 | 参数任务代码 | |
| 任务集成与调试 | 2天 | 完整程序 | |
| 第5周 | 单元测试 | 2天 | 测试用例 |
| 系统测试与优化 | 3天 | 测试报告 | |
| 第6周 | 文档编写 | 2天 | 技术文档 |
| 代码整理与发布 | 1天 | 发布版本 |
总计:约6周完成
🎯 关键技术点总结
跳转到“🎯 关键技术点总结”1. 为什么选择FreeRTOS?
跳转到“1. 为什么选择FreeRTOS?”| 对比项 | 裸机开发 | FreeRTOS开发 |
|---|---|---|
| 任务管理 | 需要手写状态机,复杂 | 任务独立,代码清晰 |
| 实时响应 | 需要轮询,响应慢 | 事件驱动,响应快 |
| 代码复用 | 耦合严重,难复用 | 模块独立,易复用 |
| 扩展性 | 添加功能困难 | 添加任务即可 |
| 调试难度 | 问题难定位 | 任务独立,易调试 |
| 资源占用 | 小 | 多8-12KB Flash |
你的项目适合FreeRTOS的理由:
- ✅ 有4个独立功能模块
- ✅ 串口通信需要及时响应
- ✅ 资源充足(64KB Flash,20KB RAM)
- ✅ 便于后期扩展功能
2. 任务优先级分配原则
跳转到“2. 任务优先级分配原则”优先级5 (最高) - 预留优先级4 - 预留优先级3 (高) - 串口通信任务(需要及时响应)优先级2 (中) - 电池监控、增益控制(周期性任务)优先级1 (低) - 参数保存(后台任务)优先级0 - 空闲任务(系统自动创建)3. 内存使用估算
跳转到“3. 内存使用估算”| 项目 | 大小 | 说明 |
|---|---|---|
| FreeRTOS内核 | 8-10KB | 代码段 |
| 串口任务堆栈 | 256字节 | |
| 电池任务堆栈 | 128字节 | |
| 增益任务堆栈 | 128字节 | |
| 参数任务堆栈 | 128字节 | |
| 定时器任务堆栈 | 128字节 | |
| FreeRTOS堆 | 8KB | 动态内存 |
| RAM总计 | 约10KB | 剩余10KB |
✅ 资源充足,完全可行!
📝 下一步行动建议
跳转到“📝 下一步行动建议”立即开始的工作:
跳转到“立即开始的工作:”-
第一步(本周):
- 使用STM32CubeMX生成工程框架
- 配置时钟、GPIO、UART1、I2C1
- 移植FreeRTOS(STM32CubeMX可一键添加)
- 测试LED闪烁和串口输出
-
第二步(下周):
- 实现协议解析模块(我已经给你设计好了)
- 开发串口通信任务
- 测试上位机收发数据
-
第三步:
- 根据实际电量芯片型号编写驱动(BQ27441/MAX17048/等)
- 开发电池监控任务
-
第四步:
- 实现增益控制驱动
- 开发增益控制任务
-
第五步:
- 实现EEPROM读写
- 开发参数保存任务
🛠️ 推荐开发工具
跳转到“🛠️ 推荐开发工具”| 工具 | 用途 |
|---|---|
| STM32CubeMX | 工程生成、HAL库配置 |
| Keil MDK 或 STM32CubeIDE | 编译器和IDE |
| 串口调试助手 | 测试UART通信 |
| 逻辑分析仪 | 调试I2C时序 |
| ST-Link | 下载和调试 |
总结
跳转到“总结”✅ 你的项目非常适合使用FreeRTOS
关键优势:
- 代码结构清晰,模块独立
- 便于调试和扩展
- 资源占用合理(约占50%)
- 符合工业级开发规范
我已经为你提供了:
- ✅ 完整的系统架构设计
- ✅ 协议定义和实现框架
- ✅ 增益控制驱动完整代码
- ✅ FreeRTOS配置文件
- ✅ 主程序框架
你可以直接基于这些代码开始开发。如果你需要:
- 电池芯片的具体驱动(需要知道芯片型号)
- EEPROM驱动的完整实现
- 其他任务的详细代码
STM32F103C8T6 裸机程序设计方案(不使用RTOS)
跳转到“STM32F103C8T6 裸机程序设计方案(不使用RTOS)”📋 第一步:裸机架构分析
跳转到“📋 第一步:裸机架构分析”1.1 是否需要RTOS的重新评估
跳转到“1.1 是否需要RTOS的重新评估”| 评估维度 | 分析 | 结论 |
|---|---|---|
| 任务复杂度 | 4个功能模块,逻辑相对简单 | ✅ 裸机可行 |
| 实时性要求 | 串口通信需要及时响应,但可用中断处理 | ✅ 裸机可行 |
| 并发需求 | 功能可以按优先级顺序执行 | ✅ 裸机可行 |
| 资源占用 | 裸机节省8-12KB Flash和RAM | ✅ 裸机优势 |
| 开发效率 | 项目简单,裸机代码更直观 | ✅ 裸机可行 |
| 可维护性 | 状态机设计清晰,易于维护 | ✅ 裸机可行 |
结论:✅ 裸机开发完全适合你的项目
跳转到“结论:✅ 裸机开发完全适合你的项目”裸机开发优势:
- 代码更简洁,逻辑更直观
- 节省Flash和RAM资源
- 调试更容易(没有任务调度的复杂性)
- 功耗更低(可以精确控制休眠)
- 启动速度更快
🏗️ 第二步:裸机系统架构设计
跳转到“🏗️ 第二步:裸机系统架构设计”2.1 核心设计思路
跳转到“2.1 核心设计思路”采用:主循环 + 中断 + 定时调度 + 状态机
┌─────────────────────────────────────────┐│ Main Loop (主循环) ││ ││ ┌────────────────────────────────┐ ││ │ 1. 串口数据处理 (高优先级) │ ││ └────────────────────────────────┘ ││ ┌────────────────────────────────┐ ││ │ 2. 电池电量监控 (1秒周期) │ ││ └────────────────────────────────┘ ││ ┌────────────────────────────────┐ ││ │ 3. 增益控制处理 (事件触发) │ ││ └────────────────────────────────┘ ││ ┌────────────────────────────────┐ ││ │ 4. 参数保存处理 (事件触发) │ ││ └────────────────────────────────┘ ││ ┌────────────────────────────────┐ ││ │ 5. 系统状态更新 │ ││ └────────────────────────────────┘ │└─────────────────────────────────────────┘ ↑ ↑ │ │ ┌────┴────┐ ┌────┴────┐ │UART中断 │ │定时器中断│ │接收数据 │ │提供时基 │ └─────────┘ └─────────┘2.2 系统分层架构(裸机版)
跳转到“2.2 系统分层架构(裸机版)”┌─────────────────────────────────────────┐│ 应用层 (Application) ││ ┌──────────┐ ┌──────────┐ ││ │主程序循环│ │状态机管理│ ││ └──────────┘ └──────────┘ ││ ┌──────────┐ ┌──────────┐ ││ │协议解析 │ │业务逻辑 │ ││ └──────────┘ └──────────┘ │├─────────────────────────────────────────┤│ 服务层 (Service) ││ ┌─────────────────────────────────┐ ││ │ 定时调度器 (软件定时器) │ ││ └─────────────────────────────────┘ ││ ┌─────────────────────────────────┐ ││ │ 环形缓冲区 / CRC校验 │ ││ └─────────────────────────────────┘ │├─────────────────────────────────────────┤│ 驱动层 (Drivers) ││ ┌──────────┐ ┌──────────┐ ││ │UART驱动 │ │I2C驱动 │ ││ └──────────┘ └──────────┘ ││ ┌──────────┐ ┌──────────┐ ││ │GPIO驱动 │ │EEPROM驱动│ ││ └──────────┘ └──────────┘ ││ ┌──────────┐ ││ │电量芯片 │ ││ │驱动 │ ││ └──────────┘ │├─────────────────────────────────────────┤│ BSP层 (Board Support) ││ 系统时钟/中断配置/外设初始化 │├─────────────────────────────────────────┤│ HAL层 (STM32 HAL库) │└─────────────────────────────────────────┘📐 第三步:模块划分设计
跳转到“📐 第三步:模块划分设计”3.1 项目目录结构(裸机版)
跳转到“3.1 项目目录结构(裸机版)”Project_Baremetal/├── App/ # 应用层│ ├── main.c # 主程序(主循环)│ ├── app_uart.c # 串口通信应用│ ├── app_battery.c # 电池监控应用│ ├── app_gain.c # 增益控制应用│ └── app_param.c # 参数管理应用│├── Service/ # 服务层│ ├── scheduler.c # 软件定时调度器│ ├── protocol.c # 协议解析│ ├── ringbuffer.c # 环形缓冲区│ └── crc.c # CRC校验│├── Drivers/ # 驱动层│ ├── BSP/│ │ ├── bsp_uart.c # UART驱动│ │ ├── bsp_i2c.c # I2C驱动│ │ ├── bsp_gpio.c # GPIO驱动│ │ ├── bsp_timer.c # 定时器驱动│ │ └── bsp_led.c # LED驱动│ └── Device/│ ├── drv_battery.c # 电量芯片驱动│ ├── drv_eeprom.c # EEPROM驱动│ └── drv_gain.c # 增益控制驱动│├── Config/ # 配置文件│ ├── system_config.h # 系统配置│ └── pin_config.h # 引脚定义│└── Inc/ # 头文件⏰ 第四步:定时调度器设计
跳转到“⏰ 第四步:定时调度器设计”4.1 软件定时器实现
跳转到“4.1 软件定时器实现”/** ****************************************************************************** * @file scheduler.h * @brief 软件定时调度器 - 用于裸机多任务调度 * @author Your Name * @date 2024-12-09 * @version 1.0 ****************************************************************************** */
#ifndef __SCHEDULER_H#define __SCHEDULER_H
#include <stdint.h>
/* 任务ID定义 */typedef enum { TASK_UART = 0, // 串口通信任务 TASK_BATTERY, // 电池监控任务 TASK_GAIN, // 增益控制任务 TASK_PARAM, // 参数保存任务 TASK_MAX // 任务数量} TaskID_t;
/* 任务函数指针类型 */typedef void (*TaskFunc_t)(void);
/* 任务结构体 */typedef struct { TaskFunc_t func; // 任务函数指针 uint32_t period; // 任务周期 (ms), 0=事件触发 uint32_t elapsed; // 已经过时间 uint8_t enabled; // 任务使能标志 const char *name; // 任务名称} Task_t;
/** * @brief 调度器初始化 * @retval 0=成功 -1=失败 */int Scheduler_Init(void);
/** * @brief 注册任务 * @param id 任务ID * @param func 任务函数指针 * @param period 任务周期(ms), 0=事件触发型 * @param name 任务名称 * @retval 0=成功 -1=失败 */int Scheduler_RegisterTask(TaskID_t id, TaskFunc_t func, uint32_t period, const char *name);
/** * @brief 启用任务 * @param id 任务ID */void Scheduler_EnableTask(TaskID_t id);
/** * @brief 禁用任务 * @param id 任务ID */void Scheduler_DisableTask(TaskID_t id);
/** * @brief 触发事件型任务立即执行 * @param id 任务ID */void Scheduler_TriggerTask(TaskID_t id);
/** * @brief 调度器主循环(在main函数while(1)中调用) */void Scheduler_Run(void);
/** * @brief 1ms时基更新(在定时器中断中调用) */void Scheduler_TickUpdate(void);
/** * @brief 获取系统运行时间(ms) * @retval 系统运行时间 */uint32_t Scheduler_GetTick(void);
#endif /* __SCHEDULER_H *//** ****************************************************************************** * @file scheduler.c * @brief 软件定时调度器实现 ****************************************************************************** */
#include "scheduler.h"#include <string.h>
/* 系统时基计数器 (1ms递增) */static volatile uint32_t system_tick = 0;
/* 任务列表 */static Task_t task_list[TASK_MAX];
/** * @brief 调度器初始化 */int Scheduler_Init(void){ /* 清空任务列表 */ memset(task_list, 0, sizeof(task_list));
/* 复位系统时基 */ system_tick = 0;
return 0;}
/** * @brief 注册任务 */int Scheduler_RegisterTask(TaskID_t id, TaskFunc_t func, uint32_t period, const char *name){ if (id >= TASK_MAX || func == NULL) { return -1; }
task_list[id].func = func; task_list[id].period = period; task_list[id].elapsed = 0; task_list[id].enabled = 1; // 默认使能 task_list[id].name = name;
return 0;}
/** * @brief 启用任务 */void Scheduler_EnableTask(TaskID_t id){ if (id < TASK_MAX) { task_list[id].enabled = 1; }}
/** * @brief 禁用任务 */void Scheduler_DisableTask(TaskID_t id){ if (id < TASK_MAX) { task_list[id].enabled = 0; }}
/** * @brief 触发事件型任务 */void Scheduler_TriggerTask(TaskID_t id){ if (id < TASK_MAX && task_list[id].enabled) { /* 立即执行该任务 */ if (task_list[id].func != NULL) { task_list[id].func(); } }}
/** * @brief 调度器主循环 */void Scheduler_Run(void){ for (int i = 0; i < TASK_MAX; i++) { /* 跳过未注册或未使能的任务 */ if (task_list[i].func == NULL || !task_list[i].enabled) { continue; }
/* 事件触发型任务不在这里执行 */ if (task_list[i].period == 0) { continue; }
/* 检查是否到达执行时间 */ if (task_list[i].elapsed >= task_list[i].period) { /* 执行任务 */ task_list[i].func();
/* 重置计时器 */ task_list[i].elapsed = 0; } }}
/** * @brief 1ms时基更新 (在定时器中断中调用) */void Scheduler_TickUpdate(void){ /* 系统时基递增 */ system_tick++;
/* 更新所有周期性任务的计时器 */ for (int i = 0; i < TASK_MAX; i++) { if (task_list[i].period > 0 && task_list[i].enabled) { task_list[i].elapsed++; } }}
/** * @brief 获取系统运行时间 */uint32_t Scheduler_GetTick(void){ return system_tick;}
/** * @brief 延时函数(ms) - 非阻塞其他任务 */void Scheduler_Delay(uint32_t ms){ uint32_t start = system_tick; while ((system_tick - start) < ms) { /* 在延时期间继续运行调度器 */ Scheduler_Run(); }}📡 第五步:串口通信模块(中断+缓冲区)---
跳转到“📡 第五步:串口通信模块(中断+缓冲区)---”/** ****************************************************************************** * @file app_uart.c * @brief 串口通信应用模块 (裸机版) ****************************************************************************** */
#include "app_uart.h"#include "protocol.h"#include "bsp_uart.h"#include "ringbuffer.h"#include "app_battery.h"#include "app_gain.h"#include "app_param.h"#include <string.h>
/* 接收缓冲区 */#define UART_RX_BUFFER_SIZE 256static uint8_t uart_rx_buffer[UART_RX_BUFFER_SIZE];static RingBuffer_t uart_rx_ring;
/* 协议帧解析缓冲区 */#define FRAME_BUFFER_SIZE 128static uint8_t frame_buffer[FRAME_BUFFER_SIZE];static uint16_t frame_index = 0;
/* 新数据标志 */static volatile uint8_t uart_new_data_flag = 0;
/** * @brief 串口接收中断回调 (在中断中被调用) * @param data 接收到的字节 */void UART_RxCallback(uint8_t data){ /* 将数据放入环形缓冲区 */ RingBuffer_Write(&uart_rx_ring, data);
/* 设置新数据标志 */ uart_new_data_flag = 1;}
/** * @brief 协议解析回调函数 * @param frame 解析出的完整帧 */static void protocol_parse_callback(ProtocolFrame_t *frame){ if (frame == NULL) { return; }
/* 根据命令码处理 */ switch (frame->cmd) { case CMD_READ_BATTERY: /* 读取电池电量 */ App_Battery_OnCommand(frame); break;
case CMD_SET_GAIN: /* 设置增益 */ App_Gain_OnCommand(frame); break;
case CMD_READ_PARAM: case CMD_SAVE_PARAM: /* 参数读取/保存 */ App_Param_OnCommand(frame); break;
case CMD_HEARTBEAT: /* 心跳包响应 */ Protocol_Send(CMD_HEARTBEAT, NULL, 0); break;
default: /* 未知命令 */ break; }}
/** * @brief 串口通信模块初始化 */int App_UART_Init(void){ /* 初始化环形缓冲区 */ RingBuffer_Init(&uart_rx_ring, uart_rx_buffer, UART_RX_BUFFER_SIZE);
/* 初始化协议模块 */ Protocol_Init(protocol_parse_callback);
/* 初始化UART硬件 */ BSP_UART_Init();
/* 清空帧缓冲区 */ frame_index = 0; memset(frame_buffer, 0, FRAME_BUFFER_SIZE);
return 0;}
/** * @brief 串口通信任务 (在主循环中周期调用) */void App_UART_Task(void){ uint8_t data;
/* 检查是否有新数据 */ if (!uart_new_data_flag) { return; }
/* 从环形缓冲区读取所有数据 */ while (RingBuffer_Read(&uart_rx_ring, &data) == 0) { /* 数据放入帧缓冲区 */ if (frame_index < FRAME_BUFFER_SIZE) { frame_buffer[frame_index++] = data; }
/* 检测到帧尾 0x0D 0x0A */ if (frame_index >= 2 && frame_buffer[frame_index - 2] == 0x0D && frame_buffer[frame_index - 1] == 0x0A) {
/* 协议解析 */ Protocol_Receive(frame_buffer, frame_index);
/* 清空缓冲区 */ frame_index = 0; memset(frame_buffer, 0, FRAME_BUFFER_SIZE); }
/* 缓冲区溢出保护 */ if (frame_index >= FRAME_BUFFER_SIZE) { frame_index = 0; } }
/* 清除新数据标志 */ if (RingBuffer_IsEmpty(&uart_rx_ring)) { uart_new_data_flag = 0; }}
/** * @brief 发送数据到上位机 * @param cmd 命令码 * @param data 数据指针 * @param len 数据长度 * @retval 0=成功 -1=失败 */int App_UART_Send(uint8_t cmd, uint8_t *data, uint8_t len){ return Protocol_Send(cmd, data, len);}🔋 第六步:电池监控模块---
跳转到“🔋 第六步:电池监控模块---”/** ****************************************************************************** * @file app_battery.c * @brief 电池监控应用模块 (裸机版) ****************************************************************************** */
#include "app_battery.h"#include "drv_battery.h"#include "protocol.h"#include "app_uart.h"#include "scheduler.h"
/* 电池状态 */typedef struct { uint8_t percentage; // 电量百分比 uint16_t voltage; // 电压 (mV) int16_t current; // 电流 (mA) uint8_t is_charging; // 是否充电中 uint32_t last_update_time; // 上次更新时间} BatteryStatus_t;
static BatteryStatus_t battery_status = {0};
/* 电量告警阈值 */#define BATTERY_LOW_THRESHOLD 20 // 低电量告警 20%#define BATTERY_CRITICAL 10 // 严重低电量 10%
/** * @brief 电池监控模块初始化 */int App_Battery_Init(void){ /* 初始化电池芯片驱动 */ if (Battery_Init() != BATTERY_OK) { return -1; }
/* 初始化电池状态 */ battery_status.percentage = 100; battery_status.voltage = 4200; battery_status.current = 0; battery_status.is_charging = 0; battery_status.last_update_time = 0;
return 0;}
/** * @brief 电池监控任务 (1秒周期调用) */void App_Battery_Task(void){ BatteryData_t battery_data;
/* 读取电池数据 */ if (Battery_Read(&battery_data) == BATTERY_OK) { /* 更新电池状态 */ battery_status.percentage = battery_data.percentage; battery_status.voltage = battery_data.voltage; battery_status.current = battery_data.current; battery_status.is_charging = battery_data.is_charging; battery_status.last_update_time = Scheduler_GetTick();
/* 低电量告警 */ if (battery_status.percentage <= BATTERY_CRITICAL) { /* 严重低电量 - 可以触发保护措施 */ // TODO: 进入低功耗模式或关键功能保护 } else if (battery_status.percentage <= BATTERY_LOW_THRESHOLD) { /* 低电量告警 */ // TODO: 上报告警信息 } }}
/** * @brief 处理上位机读取电量命令 * @param frame 协议帧 */void App_Battery_OnCommand(ProtocolFrame_t *frame){ uint8_t response_data[4];
if (frame->cmd == CMD_READ_BATTERY) { /* 封装响应数据 */ response_data[0] = battery_status.percentage; // 电量百分比 response_data[1] = battery_status.voltage >> 8; // 电压高字节 response_data[2] = battery_status.voltage & 0xFF; // 电压低字节 response_data[3] = battery_status.is_charging; // 充电状态
/* 发送响应 */ App_UART_Send(CMD_BATTERY_RESP, response_data, 4); }}
/** * @brief 获取当前电池电量 * @retval 电量百分比 (0-100) */uint8_t App_Battery_GetPercentage(void){ return battery_status.percentage;}
/** * @brief 获取当前电池电压 * @retval 电压 (mV) */uint16_t App_Battery_GetVoltage(void){ return battery_status.voltage;}
/** * @brief 获取是否正在充电 * @retval 1=充电中 0=未充电 */uint8_t App_Battery_IsCharging(void){ return battery_status.is_charging;}🎚️ 第七步:增益控制模块---
跳转到“🎚️ 第七步:增益控制模块---”/** ****************************************************************************** * @file app_gain.c * @brief 增益控制应用模块 (裸机版) ****************************************************************************** */
#include "app_gain.h"#include "drv_gain.h"#include "protocol.h"#include "app_uart.h"#include "app_param.h"#include "scheduler.h"
/* 增益控制状态 */typedef struct { GainLevel_t current_level; // 当前增益档位 uint8_t need_save; // 是否需要保存到EEPROM uint32_t last_change_time; // 上次修改时间} GainStatus_t;
static GainStatus_t gain_status = {0};
/* 自动保存延迟时间 (5秒无变化后保存) */#define AUTO_SAVE_DELAY_MS 5000
/** * @brief 增益控制模块初始化 */int App_Gain_Init(void){ /* 初始化增益控制驱动 */ if (Gain_Init() != GAIN_OK) { return -1; }
/* 从EEPROM读取上次保存的增益档位 */ GainLevel_t saved_gain = GAIN_1X; if (App_Param_ReadGain(&saved_gain) == 0) { /* 恢复上次的增益设置 */ Gain_SetLevel(saved_gain); gain_status.current_level = saved_gain; } else { /* 使用默认增益 */ gain_status.current_level = GAIN_1X; }
gain_status.need_save = 0; gain_status.last_change_time = 0;
return 0;}
/** * @brief 增益控制任务 (事件触发型) */void App_Gain_Task(void){ /* 检查是否需要自动保存 */ if (gain_status.need_save) { uint32_t elapsed = Scheduler_GetTick() - gain_status.last_change_time;
/* 5秒无变化后保存 */ if (elapsed >= AUTO_SAVE_DELAY_MS) { /* 保存到EEPROM */ App_Param_SaveGain(gain_status.current_level);
/* 清除保存标志 */ gain_status.need_save = 0; } }}
/** * @brief 处理上位机增益控制命令 * @param frame 协议帧 */void App_Gain_OnCommand(ProtocolFrame_t *frame){ uint8_t response_data[1];
if (frame->cmd == CMD_SET_GAIN) { /* 检查数据长度 */ if (frame->len != 1) { return; }
/* 获取增益档位 */ GainLevel_t new_level = (GainLevel_t)frame->data[0];
/* 检查档位有效性 */ if (new_level >= GAIN_MAX) { /* 无效档位 */ response_data[0] = 0xFF; // 错误码 App_UART_Send(CMD_GAIN_RESP, response_data, 1); return; }
/* 设置新增益 */ if (Gain_SetLevel(new_level) == GAIN_OK) { gain_status.current_level = new_level; gain_status.need_save = 1; gain_status.last_change_time = Scheduler_GetTick();
/* 发送成功响应 */ response_data[0] = new_level; App_UART_Send(CMD_GAIN_RESP, response_data, 1); } else { /* 设置失败 */ response_data[0] = 0xFF; App_UART_Send(CMD_GAIN_RESP, response_data, 1); } }}
/** * @brief 设置增益档位 * @param level 增益档位 * @retval 0=成功 -1=失败 */int App_Gain_SetLevel(GainLevel_t level){ if (level >= GAIN_MAX) { return -1; }
if (Gain_SetLevel(level) == GAIN_OK) { gain_status.current_level = level; gain_status.need_save = 1; gain_status.last_change_time = Scheduler_GetTick(); return 0; }
return -1;}
/** * @brief 获取当前增益档位 * @retval 增益档位 */GainLevel_t App_Gain_GetLevel(void){ return gain_status.current_level;}
/** * @brief 获取当前增益倍数 * @retval 增益倍数 */uint8_t App_Gain_GetValue(void){ return Gain_GetValue(gain_status.current_level);}💾 第八步:参数保存模块---
跳转到“💾 第八步:参数保存模块---”/** ****************************************************************************** * @file app_param.c * @brief 参数管理应用模块 (裸机版) - EEPROM参数保存 ****************************************************************************** */
#include "app_param.h"#include "drv_eeprom.h"#include "drv_gain.h"#include "protocol.h"#include "app_uart.h"#include <string.h>
/* EEPROM地址分配 */#define EEPROM_ADDR_MAGIC 0x0000 // 魔术字地址#define EEPROM_ADDR_GAIN 0x0004 // 增益档位地址#define EEPROM_ADDR_USER_PARAM1 0x0008 // 用户参数1#define EEPROM_ADDR_USER_PARAM2 0x000C // 用户参数2
/* 魔术字 - 用于判断EEPROM是否已初始化 */#define PARAM_MAGIC_NUMBER 0x12345678
/* 参数结构体 */typedef struct { uint32_t magic; // 魔术字 uint8_t gain_level; // 增益档位 uint32_t user_param1; // 用户参数1 uint32_t user_param2; // 用户参数2 uint16_t crc; // CRC校验} SystemParam_t;
static SystemParam_t system_param = {0};
/** * @brief 计算参数CRC校验 */static uint16_t param_calc_crc(SystemParam_t *param){ uint16_t crc = 0xFFFF; uint8_t *data = (uint8_t *)param;
/* 计算除CRC字段外的所有数据 */ for (int i = 0; i < sizeof(SystemParam_t) - sizeof(uint16_t); i++) { crc ^= data[i]; for (int j = 0; j < 8; j++) { if (crc & 0x0001) { crc = (crc >> 1) ^ 0xA001; } else { crc = crc >> 1; } } }
return crc;}
/** * @brief 从EEPROM加载参数 */static int param_load_from_eeprom(void){ uint8_t buffer[sizeof(SystemParam_t)];
/* 从EEPROM读取参数 */ if (EEPROM_Read(EEPROM_ADDR_MAGIC, buffer, sizeof(SystemParam_t)) != EEPROM_OK) { return -1; }
/* 复制到参数结构体 */ memcpy(&system_param, buffer, sizeof(SystemParam_t));
/* 检查魔术字 */ if (system_param.magic != PARAM_MAGIC_NUMBER) { return -1; // EEPROM未初始化 }
/* 校验CRC */ uint16_t calc_crc = param_calc_crc(&system_param); if (calc_crc != system_param.crc) { return -1; // CRC校验失败 }
return 0;}
/** * @brief 保存参数到EEPROM */static int param_save_to_eeprom(void){ uint8_t buffer[sizeof(SystemParam_t)];
/* 计算CRC */ system_param.crc = param_calc_crc(&system_param);
/* 复制到缓冲区 */ memcpy(buffer, &system_param, sizeof(SystemParam_t));
/* 写入EEPROM */ if (EEPROM_Write(EEPROM_ADDR_MAGIC, buffer, sizeof(SystemParam_t)) != EEPROM_OK) { return -1; }
return 0;}
/** * @brief 参数管理模块初始化 */int App_Param_Init(void){ /* 初始化EEPROM驱动 */ if (EEPROM_Init() != EEPROM_OK) { return -1; }
/* 从EEPROM加载参数 */ if (param_load_from_eeprom() != 0) { /* 加载失败,使用默认参数 */ system_param.magic = PARAM_MAGIC_NUMBER; system_param.gain_level = GAIN_1X; system_param.user_param1 = 0; system_param.user_param2 = 0;
/* 保存默认参数 */ param_save_to_eeprom(); }
return 0;}
/** * @brief 参数管理任务 (事件触发型) */void App_Param_Task(void){ /* 事件触发型任务,暂无周期性工作 */}
/** * @brief 处理上位机参数命令 */void App_Param_OnCommand(ProtocolFrame_t *frame){ uint8_t response_data[5];
if (frame->cmd == CMD_READ_PARAM) { /* 读取参数 */ if (frame->len != 1) { return; }
uint8_t param_id = frame->data[0];
switch (param_id) { case 0: // 增益档位 response_data[0] = param_id; response_data[1] = system_param.gain_level; App_UART_Send(CMD_PARAM_RESP, response_data, 2); break;
case 1: // 用户参数1 response_data[0] = param_id; memcpy(&response_data[1], &system_param.user_param1, 4); App_UART_Send(CMD_PARAM_RESP, response_data, 5); break;
default: break; }
} else if (frame->cmd == CMD_SAVE_PARAM) { /* 保存参数 */ if (frame->len < 2) { return; }
uint8_t param_id = frame->data[0]; uint8_t result = 0; // 0=成功 1=失败
switch (param_id) { case 0: // 增益档位 system_param.gain_level = frame->data[1]; result = (param_save_to_eeprom() == 0) ? 0 : 1; break;
case 1: // 用户参数1 if (frame->len >= 5) { memcpy(&system_param.user_param1, &frame->data[1], 4); result = (param_save_to_eeprom() == 0) ? 0 : 1; } break;
default: result = 1; break; }
/* 发送保存结果 */ response_data[0] = result; App_UART_Send(CMD_SAVE_RESP, response_data, 1); }}
/** * @brief 保存增益档位 */int App_Param_SaveGain(GainLevel_t level){ system_param.gain_level = level; return param_save_to_eeprom();}
/** * @brief 读取增益档位 */int App_Param_ReadGain(GainLevel_t *level){ if (level == NULL) { return -1; }
*level = (GainLevel_t)system_param.gain_level; return 0;}🚀 第九步:主程序实现---
跳转到“🚀 第九步:主程序实现---”/** ****************************************************************************** * @file main.c * @brief 主程序入口 (裸机版) * @author Your Name * @date 2024-12-09 * @version 1.0 ****************************************************************************** */
#include "stm32f1xx_hal.h"#include "scheduler.h"
/* 应用层模块 */#include "app_uart.h"#include "app_battery.h"#include "app_gain.h"#include "app_param.h"
/* 驱动层模块 */#include "bsp_uart.h"#include "bsp_i2c.h"#include "bsp_gpio.h"#include "bsp_timer.h"
/* 系统配置 */#include "system_config.h"
/* 系统状态LED */#define LED_PIN GPIO_PIN_13#define LED_PORT GPIOC
/** * @brief 系统时钟配置 (72MHz) */static void SystemClock_Config(void){ RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/* 使能HSE */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 8MHz * 9 = 72MHz HAL_RCC_OscConfig(&RCC_OscInitStruct);
/* 配置系统时钟 */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);}
/** * @brief 硬件初始化 */static void Hardware_Init(void){ /* HAL库初始化 */ HAL_Init();
/* 系统时钟配置 */ SystemClock_Config();
/* GPIO初始化 */ BSP_GPIO_Init();
/* 定时器初始化 (1ms时基) */ BSP_Timer_Init();
/* UART初始化 */ BSP_UART_Init();
/* I2C初始化 */ BSP_I2C_Init();}
/** * @brief 应用层初始化 */static void Application_Init(void){ /* 初始化参数管理模块 (最先初始化,其他模块依赖它) */ if (App_Param_Init() != 0) { Error_Handler(); }
/* 初始化增益控制模块 */ if (App_Gain_Init() != 0) { Error_Handler(); }
/* 初始化电池监控模块 */ if (App_Battery_Init() != 0) { Error_Handler(); }
/* 初始化串口通信模块 */ if (App_UART_Init() != 0) { Error_Handler(); }}
/** * @brief 调度器任务注册 */static void Scheduler_TaskRegister(void){ /* 注册串口通信任务 (高优先级,事件触发) */ Scheduler_RegisterTask(TASK_UART, App_UART_Task, 0, "UART");
/* 注册电池监控任务 (1秒周期) */ Scheduler_RegisterTask(TASK_BATTERY, App_Battery_Task, 1000, "Battery");
/* 注册增益控制任务 (100ms周期,用于自动保存检测) */ Scheduler_RegisterTask(TASK_GAIN, App_Gain_Task, 100, "Gain");
/* 注册参数管理任务 (事件触发) */ Scheduler_RegisterTask(TASK_PARAM, App_Param_Task, 0, "Param");}
/** * @brief 系统启动信息打印 */static void Print_SystemInfo(void){ printf("\r\n"); printf("========================================\r\n"); printf(" STM32F103C8T6 System Started\r\n"); printf(" Mode: Bare-Metal (No RTOS)\r\n"); printf(" Build: %s %s\r\n", __DATE__, __TIME__); printf("========================================\r\n"); printf("Hardware Info:\r\n"); printf(" CPU Freq: 72 MHz\r\n"); printf(" Flash Size: 64 KB\r\n"); printf(" RAM Size: 20 KB\r\n"); printf("========================================\r\n"); printf("Tasks Registered:\r\n"); printf(" 1. UART Task (Event Trigger)\r\n"); printf(" 2. Battery Task (1000 ms)\r\n"); printf(" 3. Gain Task (100 ms)\r\n"); printf(" 4. Param Task (Event Trigger)\r\n"); printf("========================================\r\n"); printf("System Running...\r\n\r\n");}
/** * @brief LED心跳闪烁 */static void LED_Heartbeat(void){ static uint32_t last_toggle = 0; uint32_t current_tick = Scheduler_GetTick();
/* 每500ms翻转一次 */ if (current_tick - last_toggle >= 500) { HAL_GPIO_TogglePin(LED_PORT, LED_PIN); last_toggle = current_tick; }}
/** * @brief 主函数 */int main(void){ /* 1. 硬件初始化 */ Hardware_Init();
/* 2. 调度器初始化 */ Scheduler_Init();
/* 3. 应用层初始化 */ Application_Init();
/* 4. 注册任务 */ Scheduler_TaskRegister();
/* 5. 打印系统信息 */ Print_SystemInfo();
/* 6. 主循环 */ while (1) { /* 串口任务 - 最高优先级,立即处理 */ App_UART_Task();
/* 调度器运行 - 处理周期性任务 */ Scheduler_Run();
/* LED心跳指示 */ LED_Heartbeat();
/* 可选:进入低功耗模式 */ // __WFI(); // 等待中断 }}
/** * @brief 1ms定时器中断回调函数 */void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ /* 更新调度器时基 */ Scheduler_TickUpdate();}
/** * @brief 错误处理函数 */void Error_Handler(void){ /* 关闭中断 */ __disable_irq();
/* LED快闪表示错误 */ while (1) { HAL_GPIO_TogglePin(LED_PORT, LED_PIN); for (volatile uint32_t i = 0; i < 100000; i++); }}
/** * @brief Assert失败处理 */void assert_failed(uint8_t *file, uint32_t line){ printf("Assert failed: file %s on line %d\r\n", file, line); Error_Handler();}📊 裸机方案总结对比
跳转到“📊 裸机方案总结对比”1. 裸机 v
跳转到“1. 裸机 v”s RTOS 对比表
| 对比项 | 裸机方案 | FreeRTOS方案 |
|---|---|---|
| 代码复杂度 | ⭐⭐⭐ 简单直观 | ⭐⭐⭐⭐ 需要理解RTOS概念 |
| Flash占用 | ~30KB | ~42KB (+12KB) |
| RAM占用 | ~5KB | ~10KB (+5KB) |
| 实时响应 | ⭐⭐⭐⭐ 中断响应快 | ⭐⭐⭐⭐⭐ 任务调度更灵活 |
| 扩展性 | ⭐⭐⭐ 需要修改主循环 | ⭐⭐⭐⭐⭐ 添加任务即可 |
| 调试难度 | ⭐⭐⭐⭐ 逻辑清晰 | ⭐⭐⭐ 任务切换需要工具 |
| 功耗 | ⭐⭐⭐⭐⭐ 更低 | ⭐⭐⭐⭐ 略高 |
| 开发效率 | ⭐⭐⭐⭐ 对简单项目快 | ⭐⭐⭐⭐⭐ 对复杂项目快 |
| 适用场景 | 简单项目、资源受限 | 中大型项目、复杂逻辑 |
2. 裸机方案的核心设计
跳转到“2. 裸机方案的核心设计”graph LR A[中断] --> B[数据缓冲] B --> C[主循环处理] C --> D[调度器] D --> E[周期任务] D --> F[事件任务] E --> G[业务逻辑] F --> G
style A fill:#ff6b6b style B fill:#ffd93d style D fill:#6bcf7f style G fill:#4d96ff关键设计点:
- 中断 + 缓冲区:串口接收使用中断+环形缓冲区,避免数据丢失
- 软件调度器:实现周期任务和事件触发任务的统一调度
- 状态机:每个模块内部使用状态机管理复杂逻辑
- 优先级处理:主循环中按优先级顺序调用各模块
3. 资源占用估算(裸机版)
跳转到“3. 资源占用估算(裸机版)”| 项目 | Flash占用 | RAM占用 | 说明 |
|---|---|---|---|
| HAL库 | ~15KB | ~1KB | STM32 HAL驱动 |
| 调度器 | ~2KB | ~200B | 软件定时调度 |
| 协议模块 | ~3KB | ~512B | 协议解析 |
| 串口应用 | ~2KB | ~512B | 包含环形缓冲区 |
| 电池应用 | ~2KB | ~128B | |
| 增益应用 | ~2KB | ~128B | |
| 参数应用 | ~3KB | ~256B | |
| 驱动层 | ~8KB | ~512B | UART/I2C/GPIO等 |
| 总计 | ~37KB | ~3KB | |
| 剩余 | ~27KB | ~17KB | 充足! |
✅ 资源占用更少,性能更优!
🎯 开发流程(裸机版)
跳转到“🎯 开发流程(裸机版)”第1周:基础框架搭建
跳转到“第1周:基础框架搭建”Day 1-2: STM32CubeMX工程生成 - 配置时钟树 (72MHz) - 配置UART1、I2C1、GPIO - 配置TIM2作为1ms时基 - 生成HAL库工程
Day 3-4: BSP层开发 - 实现bsp_uart.c (中断接收) - 实现bsp_i2c.c - 实现bsp_gpio.c - 实现bsp_timer.c (1ms中断)
Day 5: 调度器开发 - 实现scheduler.c - 测试定时任务调度第2周:驱动层开发
跳转到“第2周:驱动层开发”Day 1-2: 设备驱动 - drv_gain.c (增益控制) ✅已提供 - drv_battery.c (根据你的电量芯片型号) - drv_eeprom.c (AT24C02/AT24C08)
Day 3-4: 协议模块 - protocol.c (协议解析) ✅已提供 - ringbuffer.c (环形缓冲区) - crc.c (CRC16校验)
Day 5: 驱动测试 - 单元测试各驱动模块第3周:应用层开发
跳转到“第3周:应用层开发”Day 1: 串口通信应用 ✅已提供 - app_uart.c - 测试上位机收发
Day 2: 电池监控应用 ✅已提供 - app_battery.c - 测试电量读取
Day 3: 增益控制应用 ✅已提供 - app_gain.c - 测试增益切换
Day 4: 参数管理应用 ✅已提供 - app_param.c - 测试EEPROM读写
Day 5: 集成测试 - 主程序main.c ✅已提供 - 整体功能测试第4周:优化与测试
跳转到“第4周:优化与测试”Day 1-2: 功能测试 - 完整功能测试 - 边界条件测试
Day 3-4: 性能优化 - 代码优化 - 内存优化
Day 5: 文档编写 - 技术文档 - 用户手册📝 辅助工具代码---
跳转到“📝 辅助工具代码---”✅ 总结:裸机方案完整设计
跳转到“✅ 总结:裸机方案完整设计”我已经为你提供了一套完整的裸机开发方案,包括:
已提供的完整代码:
跳转到“已提供的完整代码:”- ✅ 调度器模块 (
scheduler.c/h) - 软件定时调度核心 - ✅ 协议模块 (
protocol.h) - 自定义通信协议 - ✅ 串口应用 (
app_uart.c) - 中断+缓冲区方案 - ✅ 电池应用 (
app_battery.c) - 周期监控 - ✅ 增益应用 (
app_gain.c) - 事件触发控制 - ✅ 参数应用 (
app_param.c) - EEPROM保存 - ✅ 增益驱动 (
drv_gain.c/h) - GPIO控制 - ✅ 环形缓冲区 (
ringbuffer.c/h) - 数据缓冲 - ✅ 主程序 (
main.c) - 完整主循环框架
核心设计特点:
跳转到“核心设计特点:”- 中断 + 主循环:串口中断接收,主循环处理
- 软件调度器:统一管理周期任务和事件任务
- 模块化设计:每个功能独立模块,低耦合
- 黑盒原则:模块内部变量static,只暴露API
- 资源占用低:Flash ~37KB,RAM ~3KB
开发建议:
跳转到“开发建议:”推荐使用裸机方案,因为:
- ✅ 你的项目功能相对简单
- ✅ 资源占用更少(省12KB Flash)
- ✅ 代码更直观,调试更容易
- ✅ 功耗更低
- ✅ 完全满足需求
下一步:
跳转到“下一步:”- 使用STM32CubeMX生成工程
- 将我提供的代码集成到工程
- 根据你的实际电量芯片型号编写
drv_battery.c - 测试验证
STM32F103C8T6 裸机开发详细设计文档
跳转到“STM32F103C8T6 裸机开发详细设计文档”📋 目录
跳转到“📋 目录”1. 系统整体架构
跳转到“1. 系统整体架构”1.1 系统分层架构图
跳转到“1.1 系统分层架构图”graph TB subgraph "应用层 Application Layer" A1[app_main.c<br/>主程序入口] A2[app_uart.c<br/>串口通信应用] A3[app_battery.c<br/>电池监控应用] A4[app_gain.c<br/>增益控制应用] A5[app_param.c<br/>参数管理应用] end
subgraph "服务层 Service Layer" S1[scheduler.c<br/>软件定时调度器] S2[protocol.c<br/>通信协议解析] S3[ringbuffer.c<br/>环形缓冲区] S4[crc.c<br/>CRC校验] end
subgraph "驱动层 Driver Layer" D1[bsp_uart.c<br/>UART驱动] D2[bsp_i2c.c<br/>I2C驱动] D3[bsp_gpio.c<br/>GPIO驱动] D4[bsp_timer.c<br/>定时器驱动] D5[drv_battery.c<br/>电量芯片驱动] D6[drv_eeprom.c<br/>EEPROM驱动] D7[drv_gain.c<br/>增益控制驱动] end
subgraph "HAL层" H1[STM32 HAL库] end
A1 --> A2 A1 --> A3 A1 --> A4 A1 --> A5
A2 --> S2 A2 --> D1 A3 --> D5 A4 --> D7 A5 --> D6
S1 --> A2 S1 --> A3 S1 --> A4 S1 --> A5
S2 --> S4 D1 --> S3
D1 --> H1 D2 --> H1 D3 --> H1 D4 --> H1 D5 --> D2 D6 --> D2 D7 --> D3
style A1 fill:#e1f5ff style S1 fill:#fff4e1 style D1 fill:#e8f5e9 style H1 fill:#fce4ec1.2 系统运行时序图
跳转到“1.2 系统运行时序图”sequenceDiagram participant Power as 上电 participant Main as main() participant HW as 硬件初始化 participant APP as 应用初始化 participant Sch as 调度器 participant ISR as 中断服务
Power->>Main: 系统启动 Main->>HW: Hardware_Init() HW->>HW: 时钟配置(72MHz) HW->>HW: GPIO初始化 HW->>HW: UART初始化 HW->>HW: I2C初始化 HW->>HW: Timer初始化(1ms)
Main->>Sch: Scheduler_Init() Sch->>Sch: 清空任务列表
Main->>APP: Application_Init() APP->>APP: 参数模块初始化 APP->>APP: 增益模块初始化 APP->>APP: 电池模块初始化 APP->>APP: 串口模块初始化
Main->>Sch: 注册任务 Sch->>Sch: 注册UART任务 Sch->>Sch: 注册电池任务(1s) Sch->>Sch: 注册增益任务(100ms)
Main->>Main: 进入主循环
loop 主循环 Main->>APP: App_UART_Task() Main->>Sch: Scheduler_Run() Sch->>APP: 调用到期任务
ISR->>Sch: 1ms定时器中断 Sch->>Sch: Tick计数++
ISR->>APP: UART接收中断 APP->>APP: 数据入环形缓冲区 end2. 模块依赖关系
跳转到“2. 模块依赖关系”2.1 模块依赖关系矩阵
跳转到“2.1 模块依赖关系矩阵”| 模块 | 依赖模块 | 被依赖模块 |
|---|---|---|
| app_main.c | scheduler, app_uart, app_battery, app_gain, app_param | 无 |
| scheduler.c | 无 | app_main, app_uart, app_battery, app_gain |
| app_uart.c | protocol, bsp_uart, ringbuffer | app_main, app_battery, app_gain, app_param |
| app_battery.c | drv_battery, scheduler | app_main |
| app_gain.c | drv_gain, app_param, scheduler | app_main |
| app_param.c | drv_eeprom | app_main, app_gain |
| protocol.c | crc | app_uart |
| bsp_uart.c | HAL_UART, ringbuffer | app_uart |
| bsp_i2c.c | HAL_I2C | drv_battery, drv_eeprom |
| drv_battery.c | bsp_i2c | app_battery |
| drv_eeprom.c | bsp_i2c | app_param |
| drv_gain.c | bsp_gpio | app_gain |
2.2 模块调用关系图
跳转到“2.2 模块调用关系图”graph TD A[main.c] --> B[scheduler.c] A --> C[app_uart.c] A --> D[app_battery.c] A --> E[app_gain.c] A --> F[app_param.c]
C --> G[protocol.c] C --> H[bsp_uart.c] C --> I[ringbuffer.c]
D --> J[drv_battery.c] D --> B
E --> K[drv_gain.c] E --> F E --> B
F --> L[drv_eeprom.c]
G --> M[crc.c] H --> I
J --> N[bsp_i2c.c] L --> N K --> O[bsp_gpio.c]
H --> P[HAL_UART] N --> Q[HAL_I2C] O --> R[HAL_GPIO]
style A fill:#ff6b6b style B fill:#ffd93d style C fill:#6bcf7f style D fill:#4d96ff style E fill:#c77dff style F fill:#ff9f1c3. 各层模块详细设计
跳转到“3. 各层模块详细设计”3.1 服务层模块
跳转到“3.1 服务层模块”模块 1: scheduler.c - 软件定时调度器
跳转到“模块 1: scheduler.c - 软件定时调度器”sequenceDiagram participant Timer as 1ms定时器中断 participant Sch as 调度器 participant Tasks as 任务列表
Note over Timer,Tasks: 时基更新流程 Timer->>Sch: Scheduler_TickUpdate() Sch->>Sch: system_tick++ loop 遍历所有任务 Sch->>Tasks: 检查任务是否使能 alt 周期任务且使能 Sch->>Tasks: elapsed++ end end
Note over Timer,Tasks: 主循环调度流程 Sch->>Sch: Scheduler_Run() loop 遍历所有任务 Sch->>Tasks: 检查任务状态 alt 未注册或未使能 Sch->>Sch: 跳过 else 事件触发型 Sch->>Sch: 跳过(由外部触发) else 到达周期时间 Sch->>Tasks: 执行任务函数 Sch->>Tasks: elapsed = 0 end end模块职责
跳转到“模块职责”- 提供1ms系统时基
- 管理周期性任务调度
- 支持事件触发型任务
- 提供系统时间戳服务
数据结构设计
跳转到“数据结构设计”/* 任务ID枚举 */typedef enum { TASK_UART = 0, // 串口通信任务 TASK_BATTERY, // 电池监控任务 TASK_GAIN, // 增益控制任务 TASK_PARAM, // 参数保存任务 TASK_MAX // 任务总数} TaskID_t;
/* 任务函数指针 */typedef void (*TaskFunc_t)(void);
/* 任务控制块 */typedef struct { TaskFunc_t func; // 任务函数指针 uint32_t period; // 任务周期(ms), 0=事件触发 uint32_t elapsed; // 已经过时间(ms) uint8_t enabled; // 任务使能标志 const char *name; // 任务名称(用于调试)} Task_t;对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int Scheduler_Init(void) | 初始化调度器 | main()初始化阶段 | 0=成功 -1=失败 |
int Scheduler_RegisterTask(TaskID_t id, TaskFunc_t func, uint32_t period, const char *name) | 注册任务 | main()初始化阶段 | 0=成功 -1=失败 |
void Scheduler_EnableTask(TaskID_t id) | 启用任务 | 任意位置 | 无 |
void Scheduler_DisableTask(TaskID_t id) | 禁用任务 | 任意位置 | 无 |
void Scheduler_TriggerTask(TaskID_t id) | 触发事件型任务立即执行 | 事件发生时 | 无 |
void Scheduler_Run(void) | 调度器主循环 | main()的while(1) | 无 |
void Scheduler_TickUpdate(void) | 1ms时基更新 | 定时器中断 | 无 |
uint32_t Scheduler_GetTick(void) | 获取系统运行时间 | 任意位置 | 运行时间(ms) |
内部实现细节
跳转到“内部实现细节”/* 私有变量 */static volatile uint32_t system_tick = 0; // 系统时基计数static Task_t task_list[TASK_MAX]; // 任务列表
/* 调度策略 */- 周期任务:时间到达时自动执行- 事件任务:通过Scheduler_TriggerTask()触发- 优先级:按任务注册顺序执行(UART任务最先注册,优先级最高)使用示例
跳转到“使用示例”/* 在main()中初始化 */Scheduler_Init();
/* 注册任务 */Scheduler_RegisterTask(TASK_UART, App_UART_Task, 0, "UART"); // 事件触发Scheduler_RegisterTask(TASK_BATTERY, App_Battery_Task, 1000, "BAT"); // 1秒周期
/* 在定时器中断中更新时基 */void TIM2_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); Scheduler_TickUpdate(); }}
/* 在main()主循环中运行 */while(1) { Scheduler_Run();}模块 2: protocol.c - 通信协议解析
跳转到“模块 2: protocol.c - 通信协议解析”flowchart TD A[开始解析] --> B{检查长度} B -->|< 最小长度| C[返回错误] B -->|>= 最小长度| D{检查帧头}
D -->|不是0xAA55| C D -->|是0xAA55| E{检查帧尾}
E -->|不是0x0D0A| C E -->|是0x0D0A| F[提取数据长度]
F --> G{验证长度} G -->|不一致| C G -->|一致| H[计算CRC16]
H --> I{校验CRC} I -->|失败| C I -->|成功| J[解析命令码]
J --> K[提取数据部分] K --> L[调用回调函数] L --> M[返回成功]
style C fill:#ff6b6b style M fill:#6bcf7f模块职责
跳转到“模块职责”- 解析上位机
- 发送的数据帧
- 封装发送给上位机的数据帧
- CRC16校验
- 协议状态管理
协议格式定义
跳转到“协议格式定义”帧格式:┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐│ 帧头 │ 命令码 │ 数据长度│ 数据 │ CRC16 │ 帧尾 ││ (2字节) │ (1字节) │ (1字节) │(N字节) │ (2字节) │ (2字节) ││ 0xAA55 │ CMD │ LEN │ DATA │ CRC │ 0x0D0A │└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
最小帧长:8字节 (无数据时)最大帧长:8 + 128 = 136字节命令码定义
跳转到“命令码定义”/* 命令码枚举 */typedef enum { CMD_READ_BATTERY = 0x01, // 读取电量 CMD_BATTERY_RESP = 0x81, // 电量响应
CMD_SET_GAIN = 0x02, // 设置增益 CMD_GAIN_RESP = 0x82, // 增益响应
CMD_READ_PARAM = 0x03, // 读取参数 CMD_PARAM_RESP = 0x83, // 参数响应
CMD_SAVE_PARAM = 0x04, // 保存参数 CMD_SAVE_RESP = 0x84, // 保存响应
CMD_HEARTBEAT = 0xFF // 心跳包} ProtocolCmd_t;数据结构设计
跳转到“数据结构设计”/* 协议帧结构 */typedef struct { uint8_t cmd; // 命令码 uint8_t len; // 数据长度 uint8_t data[128]; // 数据缓冲区 uint16_t crc; // CRC校验值} ProtocolFrame_t;
/* 协议状态 */typedef enum { PROTOCOL_OK = 0, PROTOCOL_ERROR_LENGTH, // 长度错误 PROTOCOL_ERROR_HEADER, // 帧头错误 PROTOCOL_ERROR_TAIL, // 帧尾错误 PROTOCOL_ERROR_CRC, // CRC错误} ProtocolStatus_t;
/* 回调函数类型 */typedef void (*ProtocolCallback_t)(ProtocolFrame_t *frame);对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int Protocol_Init(ProtocolCallback_t callback) | 初始化协议模块 | app_uart初始化 | 0=成功 |
ProtocolStatus_t Protocol_Receive(uint8_t *data, uint16_t len) | 接收并解析数据帧 | UART接收完整帧后 | 状态码 |
int Protocol_Send(uint8_t cmd, uint8_t *data, uint8_t len) | 封装并发送数据帧 | 需要发送数据时 | 0=成功 -1=失败 |
uint16_t Protocol_CalcCRC16(uint8_t *data, uint16_t len) | 计算CRC16 | 内部使用 | CRC值 |
协议示例
跳转到“协议示例”/* 示例1:上位机读取电量 */发送: AA 55 01 00 [CRC16] 0D 0A接收: AA 55 81 04 4B 10 68 01 [CRC16] 0D 0A // 电量75% 电压4200mV 充电中
/* 示例2:上位机设置增益为10x */发送: AA 55 02 01 03 [CRC16] 0D 0A // 档位3接收: AA 55 82 01 03 [CRC16] 0D 0A // 确认设置为档位3
/* 示例3:保存参数到EEPROM */发送: AA 55 04 05 00 00 00 00 0A [CRC16] 0D 0A // 参数ID=0, 值=10接收: AA 55 84 01 00 [CRC16] 0D 0A // 保存成功模块 3: ringbuffer.c - 环形缓冲区
跳转到“模块 3: ringbuffer.c - 环形缓冲区”模块职责
跳转到“模块职责”- 提供FIFO数据缓冲
- 用于串口接收数据缓存
- 线程安全(中断安全)
数据结构设计
跳转到“数据结构设计”/* 环形缓冲区结构 */typedef struct { uint8_t *buffer; // 缓冲区指针 uint16_t size; // 缓冲区大小 uint16_t head; // 写指针 uint16_t tail; // 读指针 uint16_t count; // 当前数据量} RingBuffer_t;对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
void RingBuffer_Init(RingBuffer_t *rb, uint8_t *buf, uint16_t size) | 初始化环形缓冲区 | 模块初始化 | 无 |
int RingBuffer_Write(RingBuffer_t *rb, uint8_t data) | 写入一个字节 | 中断中 | 0=成功 -1=缓冲区满 |
int RingBuffer_Read(RingBuffer_t *rb, uint8_t *data) | 读取一个字节 | 主循环 | 0=成功 -1=缓冲区空 |
uint16_t RingBuffer_Available(RingBuffer_t *rb) | 获取可读数据量 | 任意 | 字节数 |
uint8_t RingBuffer_IsEmpty(RingBuffer_t *rb) | 判断是否为空 | 任意 | 1=空 0=非空 |
uint8_t RingBuffer_IsFull(RingBuffer_t *rb) | 判断是否已满 | 任意 | 1=满 0=非满 |
void RingBuffer_Clear(RingBuffer_t *rb) | 清空缓冲区 | 任意 | 无 |
内部流程图
跳转到“内部流程图”flowchart LR A[中断接收数据] --> B[RingBuffer_Write] B --> C{缓冲区满?} C -->|是| D[丢弃数据] C -->|否| E[写入buffer head++]
F[主循环读取] --> G[RingBuffer_Read] G --> H{缓冲区空?} H -->|是| I[返回失败] H -->|否| J[读取buffer tail++]模块 4: crc.c - CRC16校验
跳转到“模块 4: crc.c - CRC16校验”模块职责
跳转到“模块职责”- 计算CRC16-MODBUS校验值
- 用于协议数据完整性校验
算法实现
跳转到“算法实现”/* CRC16-MODBUS算法 */多项式:0xA001初始值:0xFFFF对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
uint16_t CRC16_Calculate(uint8_t *data, uint16_t len) | 计算CRC16 | 协议模块 | CRC值 |
uint8_t CRC16_Verify(uint8_t *data, uint16_t len, uint16_t crc) | 验证CRC | 协议模块 | 1=正确 0=错误 |
3.2 驱动层模块(BSP层)
跳转到“3.2 驱动层模块(BSP层)”模块 5: bsp_uart.c - UART驱动
跳转到“模块 5: bsp_uart.c - UART驱动”sequenceDiagram participant PC as 上位机 participant UART as UART硬件 participant ISR as UART中断 participant Ring as 环形缓冲区 participant Main as 主循环
PC->>UART: 发送字节1 UART->>ISR: 触发接收中断 ISR->>Ring: RingBuffer_Write(byte1) ISR->>ISR: 设置新数据标志
PC->>UART: 发送字节2 UART->>ISR: 触发接收中断 ISR->>Ring: RingBuffer_Write(byte2)
PC->>UART: 发送字节N UART->>ISR: 触发接收中断 ISR->>Ring: RingBuffer_Write(byteN)
Note over Main: 主循环检测到新数据 Main->>Ring: RingBuffer_Read() Ring-->>Main: 返回数据 Main->>Main: 协议解析 Main->>Main: 处理命令模块职责
跳转到“模块职责”- 初始化UART1硬件(115200,8,N,1)
- 配置中断接收
- 提供发送接口
- 管理接收环形缓冲区
硬件配置
跳转到“硬件配置”/* UART1配置 */引脚:PA9(TX), PA10(RX)波特率:115200数据位:8停止位:1校验:None模式:中断接收 + 阻塞发送数据结构设计
跳转到“数据结构设计”/* UART配置结构 */typedef struct { USART_TypeDef *instance; // UART实例 uint32_t baudrate; // 波特率 IRQn_Type irq_type; // 中断类型} BSP_UART_Config_t;
/* UART句柄 */static UART_HandleTypeDef huart1;
/* 接收缓冲区 */#define UART_RX_BUFFER_SIZE 256static uint8_t uart_rx_buffer[UART_RX_BUFFER_SIZE];static RingBuffer_t uart_rx_ring;对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int BSP_UART_Init(void) | 初始化UART硬件 | main()初始化 | 0=成功 -1=失败 |
int BSP_UART_Send(uint8_t *data, uint16_t len) | 发送数据 | 协议发送时 | 0=成功 -1=失败 |
int BSP_UART_Read(uint8_t *data) | 从缓冲区读取一字节 | app_uart任务 | 0=成功 -1=无数据 |
uint16_t BSP_UART_Available(void) | 获取可读数据量 | 任意 | 字节数 |
void BSP_UART_RxCallback(uint8_t data) | 接收中断回调 | 中断内部调用 | 无 |
中断配置
跳转到“中断配置”/* 中断优先级 */NVIC_SetPriority(USART1_IRQn, 2); // 较高优先级NVIC_EnableIRQ(USART1_IRQn);
/* 中断处理函数 */void USART1_IRQHandler(void) { if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { uint8_t data = (uint8_t)(huart1.Instance->DR & 0xFF); RingBuffer_Write(&uart_rx_ring, data); }}模块 6: bsp_i2c.c - I2C驱动
跳转到“模块 6: bsp_i2c.c - I2C驱动”模块职责
跳转到“模块职责”- 初始化I2C1硬件
- 提供读写字节/多字节接口
- 支持多设备(电量芯片、EEPROM)
硬件配置
跳转到“硬件配置”/* I2C1配置 */引脚:PB6(SCL), PB7(SDA)速率:100KHz (标准模式)模式:Master模式地址模式:7位地址数据结构设计
跳转到“数据结构设计”/* I2C句柄 */static I2C_HandleTypeDef hi2c1;
/* I2C设备地址 */#define I2C_ADDR_BATTERY 0x55 // 电量芯片地址#define I2C_ADDR_EEPROM 0x50 // EEPROM地址对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int BSP_I2C_Init(void) | 初始化I2C硬件 | main()初始化 | 0=成功 -1=失败 |
int BSP_I2C_WriteByte(uint8_t dev_addr, uint8_t reg_addr, uint8_t data) | 写一个字节 | 设备驱动 | 0=成功 -1=失败 |
int BSP_I2C_ReadByte(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data) | 读一个字节 | 设备驱动 | 0=成功 -1=失败 |
int BSP_I2C_WriteBytes(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t len) | 写多字节 | 设备驱动 | 0=成功 -1=失败 |
int BSP_I2C_ReadBytes(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t len) | 读多字节 | 设备驱动 | 0=成功 -1=失败 |
时序图
跳转到“时序图”sequenceDiagram participant CPU participant I2C as I2C硬件 participant DEV as I2C设备
Note over CPU,DEV: 写操作时序 CPU->>I2C: 发送START I2C->>DEV: 发送设备地址+W DEV-->>I2C: ACK I2C->>DEV: 发送寄存器地址 DEV-->>I2C: ACK I2C->>DEV: 发送数据 DEV-->>I2C: ACK I2C->>DEV: 发送STOP
Note over CPU,DEV: 读操作时序 CPU->>I2C: 发送START I2C->>DEV: 发送设备地址+W DEV-->>I2C: ACK I2C->>DEV: 发送寄存器地址 DEV-->>I2C: ACK I2C->>DEV: 发送RESTART I2C->>DEV: 发送设备地址+R DEV-->>I2C: ACK DEV->>I2C: 发送数据 I2C-->>DEV: ACK I2C->>DEV: 发送STOP模块 7: bsp_gpio.c - GPIO驱动
跳转到“模块 7: bsp_gpio.c - GPIO驱动”模块职责
跳转到“模块职责”- 初始化所有GPIO引脚
- 提供GPIO读写接口
- 配置增益控制引脚
引脚配置表
跳转到“引脚配置表”| 引脚 | 功能 | 模式 | 初始状态 |
|---|---|---|---|
| PA0 | GAIN_CTRL0 | 推挽输出 | 低电平 |
| PA1 | GAIN_CTRL1 | 推挽输出 | 低电平 |
| PA2 | GAIN_CTRL2 | 推挽输出 | 低电平 |
| PC13 | LED_STATUS | 推挽输出 | 高电平(LED灭) |
对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int BSP_GPIO_Init(void) | 初始化所有GPIO | main()初始化 | 0=成功 |
void BSP_GPIO_Write(GPIO_TypeDef *port, uint16_t pin, uint8_t state) | 写GPIO | 任意 | 无 |
uint8_t BSP_GPIO_Read(GPIO_TypeDef *port, uint16_t pin) | 读GPIO | 任意 | 引脚状态 |
void BSP_GPIO_Toggle(GPIO_TypeDef *port, uint16_t pin) | 翻转GPIO | 任意 | 无 |
模块 8: bsp_timer.c - 定时器驱动
跳转到“模块 8: bsp_timer.c - 定时器驱动”模块职责
跳转到“模块职责”- 配置TIM2产生1ms定时中断
- 为调度器提供时基
定时器配置
跳转到“定时器配置”/* TIM2配置 */时钟源:APB1 (72MHz / 2 = 36MHz)预分频:36000 - 1 (得到1KHz)计数周期:1 - 1 (1ms中断一次)中断优先级:3 (较低)对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int BSP_Timer_Init(void) | 初始化定时器 | main()初始化 | 0=成功 |
void BSP_Timer_Start(void) | 启动定时器 | 初始化完成后 | 无 |
void BSP_Timer_Stop(void) | 停止定时器 | 需要时 | 无 |
中断处理
跳转到“中断处理”void TIM2_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
/* 调用调度器时基更新 */ Scheduler_TickUpdate(); }}3.3 驱动层模块(设备驱动)
跳转到“3.3 驱动层模块(设备驱动)”模块 9: drv_battery.c - 电量芯片驱动
跳转到“模块 9: drv_battery.c - 电量芯片驱动”模块职责
跳转到“模块职责”- 与电量芯片通信(I2C)
- 读取电量百分比、电压、电流
- 判断充电状态
支持的电量芯片
跳转到“支持的电量芯片”/* 常见电量芯片 */- BQ27441 (TI)- MAX17048/MAX17049 (Maxim)- LC709203F (ON Semiconductor)
/* 根据实际使用的芯片填写寄存器地址 */数据结构设计
跳转到“数据结构设计”/* 电量芯片状态码 */typedef enum { BATTERY_OK = 0, BATTERY_ERROR = -1, BATTERY_ERROR_COMM = -2, // 通信错误 BATTERY_ERROR_TIMEOUT = -3 // 超时} BatteryStatus_t;
/* 电池数据结构 */typedef struct { uint8_t percentage; // 电量百分比 (0-100) uint16_t voltage; // 电压 (mV) int16_t current; // 电流 (mA), 正=充电 负=放电 uint8_t is_charging; // 充电状态 (1=充电中 0=未充电) uint32_t timestamp; // 时间戳 (ms)} BatteryData_t;
/* 寄存器地址 (以BQ27441为例) */#define REG_STATE_OF_CHARGE 0x1C // 电量百分比#define REG_VOLTAGE 0x04 // 电压#define REG_AVERAGE_CURRENT 0x10 // 电流#define REG_FLAGS 0x06 // 状态标志对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
BatteryStatus_t Battery_Init(void) | 初始化电量芯片 | app_battery初始化 | 状态码 |
BatteryStatus_t Battery_Read(BatteryData_t *data) | 读取电池数据 | app_battery任务 | 状态码 |
uint8_t Battery_GetPercentage(void) | 快速读取电量百分比 | 任意 | 电量% |
uint8_t Battery_IsCharging(void) | 判断是否充电 | 任意 | 1=充电 0=未充电 |
内部流程图
跳转到“内部流程图”flowchart TD A[Battery_Read] --> B[I2C读取电量寄存器] B --> C{通信成功?} C -->|否| D[返回ERROR_COMM] C -->|是| E[I2C读取电压寄存器] E --> F{通信成功?} F -->|否| D F -->|是| G[I2C读取电流寄存器] G --> H{通信成功?} H -->|否| D H -->|是| I[I2C读取状态标志] I --> J{通信成功?} J -->|否| D J -->|是| K[解析充电状态] K --> L[填充BatteryData_t] L --> M[返回OK]
style D fill:#ff6b6b style M fill:#6bcf7f使用示例
跳转到“使用示例”/* 初始化 */if (Battery_Init() != BATTERY_OK) { printf("Battery init failed!\r\n");}
/* 读取电池数据 */BatteryData_t battery_data;if (Battery_Read(&battery_data) == BATTERY_OK) { printf("电量: %d%%\r\n", battery_data.percentage); printf("电压: %d mV\r\n", battery_data.voltage); printf("电流: %d mA\r\n", battery_data.current); printf("充电状态: %s\r\n", battery_data.is_charging ? "充电中" : "未充电");}模块 10: drv_eeprom.c - EEPROM驱动
跳转到“模块 10: drv_eeprom.c - EEPROM驱动”模块职责
跳转到“模块职责”- 与EEPROM通信(I2C)
- 提供字节/页写入
- 提供字节/连续读取
- 掉电保存参数
支持的EEPROM型号
跳转到“支持的EEPROM型号”/* 常见EEPROM芯片 */- AT24C02 (256字节)- AT24C08 (1KB)- AT24C16 (2KB)- AT24C64 (8KB)数据结构设计
跳转到“数据结构设计”/* EEPROM状态码 */typedef enum { EEPROM_OK = 0, EEPROM_ERROR = -1, EEPROM_ERROR_COMM = -2, // 通信错误 EEPROM_ERROR_TIMEOUT = -3, // 超时 EEPROM_ERROR_ADDR = -4 // 地址越界} EEPROMStatus_t;
/* EEPROM配置 */#define EEPROM_I2C_ADDR 0x50 // EEPROM设备地址#define EEPROM_SIZE 256 // EEPROM容量(字节)#define EEPROM_PAGE_SIZE 8 // 页大小(字节)对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
EEPROMStatus_t EEPROM_Init(void) | 初始化EEPROM | app_param初始化 | 状态码 |
EEPROMStatus_t EEPROM_WriteByte(uint16_t addr, uint8_t data) | 写一个字节 | 参数保存 | 状态码 |
EEPROMStatus_t EEPROM_ReadByte(uint16_t addr, uint8_t *data) | 读一个字节 | 参数读取 | 状态码 |
EEPROMStatus_t EEPROM_Write(uint16_t addr, uint8_t *data, uint16_t len) | 写多字节 | 参数保存 | 状态码 |
EEPROMStatus_t EEPROM_Read(uint16_t addr, uint8_t *data, uint16_t len) | 读多字节 | 参数读取 | 状态码 |
写入流程(页写入)
跳转到“写入流程(页写入)”flowchart TD A[EEPROM_Write] --> B{地址+长度<容量?} B -->|否| C[返回ERROR_ADDR] B -->|是| D[计算起始页] D --> E{是否跨页?} E -->|否| F[一次写入全部数据] E -->|是| G[分页写入] G --> H[写入第一页剩余空间] H --> I[写入中间完整页] I --> J[写入最后页] F --> K[等待写入完成5ms] J --> K K --> L[验证写入] L --> M{验证成功?} M -->|否| C M -->|是| N[返回OK]
style C fill:#ff6b6b style N fill:#6bcf7f模块 11: drv_gain.c - 增益控制驱动
跳转到“模块 11: drv_gain.c - 增益控制驱动”模块职责
跳转到“模块职责”- 通过GPIO控制放大器增益
- 支持多档增益切换
- 提供增益档位查询
硬件电路
跳转到“硬件电路”STM32 放大器芯片PA0 ─────> GAIN_CTRL0 ───┐PA1 ─────> GAIN_CTRL1 ───┼──> 增益控制输入PA2 ─────> GAIN_CTRL2 ───┘数据结构设计
跳转到“数据结构设计”/* 增益状态码 */typedef enum { GAIN_OK = 0, GAIN_ERROR = -1, GAIN_ERROR_INVALID_LEVEL = -2 // 无效档位} GainStatus_t;
/* 增益档位枚举 */typedef enum { GAIN_1X = 0, // 1倍 (000) GAIN_2X, // 2倍 (001) GAIN_5X, // 5倍 (010) GAIN_10X, // 10倍 (011) GAIN_20X, // 20倍 (100) GAIN_50X, // 50倍 (101) GAIN_100X, // 100倍 (110) GAIN_200X, // 200倍 (111) GAIN_MAX // 档位总数} GainLevel_t;
/* 增益配置表 */typedef struct { GainLevel_t level; // 档位 uint8_t value; // 倍数 uint8_t ctrl0; // CTRL0状态 uint8_t ctrl1; // CTRL1状态 uint8_t ctrl2; // CTRL2状态} GainConfig_t;
/* 增益配置表 */static const GainConfig_t gain_table[GAIN_MAX] = { {GAIN_1X, 1, 0, 0, 0}, {GAIN_2X, 2, 1, 0, 0}, {GAIN_5X, 5, 0, 1, 0}, {GAIN_10X, 10, 1, 1, 0}, {GAIN_20X, 20, 0, 0, 1}, {GAIN_50X, 50, 1, 0, 1}, {GAIN_100X, 100, 0, 1, 1}, {GAIN_200X, 200, 1, 1, 1}};对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
GainStatus_t Gain_Init(void) | 初始化增益控制 | app_gain初始化 | 状态码 |
GainStatus_t Gain_SetLevel(GainLevel_t level) | 设置增益档位 | app_gain | 状态码 |
GainLevel_t Gain_GetLevel(void) | 获取当前档位 | 任意 | 档位 |
uint8_t Gain_GetValue(GainLevel_t level) | 根据档位获取倍数 | 任意 | 倍数 |
内部实现
跳转到“内部实现”GainStatus_t Gain_SetLevel(GainLevel_t level) { if (level >= GAIN_MAX) { return GAIN_ERROR_INVALID_LEVEL; }
/* 从配置表读取GPIO状态 */ uint8_t ctrl0 = gain_table[level].ctrl0; uint8_t ctrl1 = gain_table[level].ctrl1; uint8_t ctrl2 = gain_table[level].ctrl2;
/* 设置GPIO */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, ctrl0 ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, ctrl1 ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, ctrl2 ? GPIO_PIN_SET : GPIO_PIN_RESET);
/* 保存当前档位 */ current_level = level;
return GAIN_OK;}3.4 应用层模块
跳转到“3.4 应用层模块”模块 12: app_uart.c - 串口通信应用#### 模块职责
跳转到“模块 12: app_uart.c - 串口通信应用#### 模块职责”- 接收上位机命令
- 解析协议帧
- 分发命令到对应模块
- 发送响应给上位机
数据结构设计
跳转到“数据结构设计”/* 帧缓冲区 */#define FRAME_BUFFER_SIZE 128static uint8_t frame_buffer[FRAME_BUFFER_SIZE];static uint16_t frame_index = 0;
/* 新数据标志 */static volatile uint8_t uart_new_data_flag = 0;对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int App_UART_Init(void) | 初始化串口应用 | main()初始化 | 0=成功 -1=失败 |
void App_UART_Task(void) | 串口通信任务 | 主循环高优先级调用 | 无 |
int App_UART_Send(uint8_t cmd, uint8_t *data, uint8_t len) | 发送数据到上位机 | 其他模块 | 0=成功 -1=失败 |
void UART_RxCallback(uint8_t data) | 接收中断回调 | 中断中调用 | 无 |
模块 13: app_battery.c - 电池监控应用
跳转到“模块 13: app_battery.c - 电池监控应用”模块职责
跳转到“模块职责”- 周期读取电池数据(1秒)
- 处理上位机电量查询命令
- 低电量告警
- 电量数据上报
数据结构设计
跳转到“数据结构设计”/* 电池状态结构 */typedef struct { uint8_t percentage; // 电量百分比 uint16_t voltage; // 电压(mV) int16_t current; // 电流(mA) uint8_t is_charging; // 充电状态 uint32_t last_update_time; // 上次更新时间} BatteryStatus_t;
static BatteryStatus_t battery_status = {0};
/* 告警阈值 */#define BATTERY_LOW_THRESHOLD 20 // 低电量告警20%#define BATTERY_CRITICAL 10 // 严重低电量10%对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int App_Battery_Init(void) | 初始化电池应用 | main()初始化 | 0=成功 -1=失败 |
void App_Battery_Task(void) | 电池监控任务(1秒周期) | 调度器 | 无 |
void App_Battery_OnCommand(ProtocolFrame_t *frame) | 处理电量命令 | 协议回调 | 无 |
uint8_t App_Battery_GetPercentage(void) | 获取当前电量 | 任意 | 电量% |
uint16_t App_Battery_GetVoltage(void) | 获取当前电压 | 任意 | 电压(mV) |
uint8_t App_Battery_IsCharging(void) | 获取充电状态 | 任意 | 1=充电 0=未充电 |
任务流程图
跳转到“任务流程图”flowchart TD A[App_Battery_Task<br/>1秒周期执行] --> B[调用Battery_Read] B --> C{读取成功?} C -->|否| D[保持上次数据] C -->|是| E[更新battery_status] E --> F{电量<10%?} F -->|是| G[严重低电量处理] F -->|否| H{电量<20%?} H -->|是| I[低电量告警] H -->|否| J[正常] G --> K[可选:进入低功耗] I --> L[可选:上报告警] J --> M[任务结束] K --> M L --> M D --> M
style G fill:#ff6b6b style I fill:#ffd93d style J fill:#6bcf7f模块 14: app_gain.c - 增益控制应用
跳转到“模块 14: app_gain.c - 增益控制应用”模块职责
跳转到“模块职责”- 处理增益设置命令
- 控制放大器增益
- 自动保存增益参数
- 提供增益查询接口
数据结构设计
跳转到“数据结构设计”/* 增益控制状态 */typedef struct { GainLevel_t current_level; // 当前增益档位 uint8_t need_save; // 是否需要保存 uint32_t last_change_time; // 上次修改时间} GainStatus_t;
static GainStatus_t gain_status = {0};
/* 自动保存延迟 */#define AUTO_SAVE_DELAY_MS 5000 // 5秒无变化后保存对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int App_Gain_Init(void) | 初始化增益应用 | main()初始化 | 0=成功 -1=失败 |
void App_Gain_Task(void) | 增益控制任务(100ms周期) | 调度器 | 无 |
void App_Gain_OnCommand(ProtocolFrame_t *frame) | 处理增益命令 | 协议回调 | 无 |
int App_Gain_SetLevel(GainLevel_t level) | 设置增益档位 | 任意 | 0=成功 -1=失败 |
GainLevel_t App_Gain_GetLevel(void) | 获取当前档位 | 任意 | 档位 |
uint8_t App_Gain_GetValue(void) | 获取当前倍数 | 任意 | 倍数 |
任务流程图
跳转到“任务流程图”flowchart TD A[App_Gain_Task<br/>100ms周期执行] --> B{需要保存?} B -->|否| C[任务结束] B -->|是| D[计算距上次修改时间] D --> E{时间>5秒?} E -->|否| C E -->|是| F[保存到EEPROM] F --> G[清除保存标志] G --> C
style C fill:#e8f5e9 style F fill:#6bcf7f模块 15: app_param.c - 参数管理应用
跳转到“模块 15: app_param.c - 参数管理应用”模块职责
跳转到“模块职责”- 管理系统参数
- EEPROM读写
- 参数CRC校验
- 处理参数读写命令
EEPROM地址分配
跳转到“EEPROM地址分配”/* EEPROM地址映射 */#define EEPROM_ADDR_MAGIC 0x0000 // 魔术字(4字节)#define EEPROM_ADDR_GAIN 0x0004 // 增益档位(1字节)#define EEPROM_ADDR_USER_PARAM1 0x0008 // 用户参数1(4字节)#define EEPROM_ADDR_USER_PARAM2 0x000C // 用户参数2(4字节)#define EEPROM_ADDR_CRC 0x0010 // CRC校验(2字节)
/* 魔术字 */#define PARAM_MAGIC_NUMBER 0x12345678数据结构设计
跳转到“数据结构设计”/* 系统参数结构 */typedef struct { uint32_t magic; // 魔术字 uint8_t gain_level; // 增益档位 uint32_t user_param1; // 用户参数1 uint32_t user_param2; // 用户参数2 uint16_t crc; // CRC校验} SystemParam_t;
static SystemParam_t system_param = {0};对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int App_Param_Init(void) | 初始化参数管理 | main()初始化(最先) | 0=成功 -1=失败 |
void App_Param_Task(void) | 参数管理任务(事件触发) | 调度器 | 无 |
void App_Param_OnCommand(ProtocolFrame_t *frame) | 处理参数命令 | 协议回调 | 无 |
int App_Param_SaveGain(GainLevel_t level) | 保存增益档位 | app_gain | 0=成功 -1=失败 |
int App_Param_ReadGain(GainLevel_t *level) | 读取增益档位 | app_gain初始化 | 0=成功 -1=失败 |
参数保存流程图
跳转到“参数保存流程图”flowchart TD A[App_Param_SaveGain] --> B[更新system_param] B --> C[计算CRC16] C --> D[写入魔术字] D --> E{写入成功?} E -->|否| F[返回失败] E -->|是| G[写入增益档位] G --> H{写入成功?} H -->|否| F H -->|是| I[写入用户参数] I --> J{写入成功?} J -->|否| F J -->|是| K[写入CRC] K --> L{写入成功?} L -->|否| F L -->|是| M[返回成功]
style F fill:#ff6b6b style M fill:#6bcf7f模块 16: app_main.c - 主程序
跳转到“模块 16: app_main.c - 主程序”模块职责
跳转到“模块职责”- 系统启动初始化
- 硬件初始化
- 应用层初始化
- 主循环调度
主函数流程图
跳转到“主函数流程图”flowchart TD A[系统上电] --> B[HAL_Init] B --> C[SystemClock_Config<br/>配置为72MHz] C --> D[Hardware_Init] D --> E[BSP_GPIO_Init] E --> F[BSP_Timer_Init] F --> G[BSP_UART_Init] G --> H[BSP_I2C_Init] H --> I[Scheduler_Init] I --> J[Application_Init] J --> K[App_Param_Init<br/>最先初始化] K --> L[App_Gain_Init<br/>从EEPROM恢复] L --> M[App_Battery_Init] M --> N[App_UART_Init] N --> O[Scheduler_TaskRegister] O --> P[打印启动信息] P --> Q[启动定时器] Q --> R[进入主循环]
R --> S[App_UART_Task<br/>高优先级] S --> T[Scheduler_Run<br/>周期任务] T --> U[LED_Heartbeat<br/>心跳指示] U --> R
style A fill:#ff6b6b style Q fill:#ffd93d style R fill:#6bcf7f对外API接口
跳转到“对外API接口”| 函数原型 | 功能描述 | 调用位置 | 返回值 |
|---|---|---|---|
int main(void) | 主函数入口 | 系统启动 | 不返回 |
void Error_Handler(void) | 错误处理 | 初始化失败时 | 不返回 |
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) | 定时器回调 | HAL库调用 | 无 |
4. 任务派发清单
跳转到“4. 任务派发清单”4.1 任务分解表
跳转到“4.1 任务分解表”| 任务编号 | 任务名称 | 模块 | 预计工时 | 优先级 | 依赖任务 |
|---|---|---|---|---|---|
| T001 | 工程创建与配置 | STM32CubeMX | 0.5天 | 高 | 无 |
| T002 | BSP_GPIO驱动 | bsp_gpio.c | 0.5天 | 高 | T001 |
| T003 | BSP_Timer驱动 | bsp_timer.c | 0.5天 | 高 | T001 |
| T004 | BSP_UART驱动 | bsp_uart.c | 1天 | 高 | T001 |
| T005 | BSP_I2C驱动 | bsp_i2c.c | 1天 | 高 | T001 |
| T006 | 调度器模块 | scheduler.c | 1天 | 高 | T003 |
| T007 | 环形缓冲区 | ringbuffer.c | 0.5天 | 中 | 无 |
| T008 | CRC16模块 | crc.c | 0.5天 | 中 | 无 |
| T009 | 协议模块 | protocol.c | 1天 | 高 | T008 |
| T010 | 增益控制驱动 | drv_gain.c | 0.5天 | 中 | T002 |
| T011 | EEPROM驱动 | drv_eeprom.c | 1天 | 中 | T005 |
| T012 | 电量芯片驱动 | drv_battery.c | 1.5天 | 中 | T005 |
| T013 | 参数管理应用 | app_param.c | 1天 | 中 | T011 |
| T014 | 增益控制应用 | app_gain.c | 1天 | 中 | T010,T013 |
| T015 | 电池监控应用 | app_battery.c | 1天 | 中 | T012 |
| T016 | 串口通信应用 | app_uart.c | 1.5天 | 高 | T004,T009 |
| T017 | 主程序集成 | app_main.c | 1天 | 高 | T013~T016 |
| T018 | 单元测试 | 各模块 | 2天 | 中 | T017 |
| T019 | 系统集成测试 | 整体 | 2天 | 高 | T018 |
| T020 | 文档编写 | 技术文档 | 2天 | 低 | T019 |
总计:约20个工作日
4.2 开发顺序建议
跳转到“4.2 开发顺序建议”第一阶段:基础框架 (5天)
跳转到“第一阶段:基础框架 (5天)”Week 1:├── Day 1: T001,T002,T003 (工程创建+GPIO+Timer)├── Day 2: T004 (UART驱动+测试)├── Day 3: T005 (I2C驱动+测试)├── Day 4: T006,T007 (调度器+环形缓冲区)└── Day 5: T008,T009 (CRC+协议模块)第二阶段:设备驱动 (4天)
跳转到“第二阶段:设备驱动 (4天)”Week 2:├── Day 1: T010 (增益控制驱动+测试)├── Day 2: T011 (EEPROM驱动+测试)├── Day 3-4: T012 (电量芯片驱动+测试)第三阶段:应用层 (5天)
跳转到“第三阶段:应用层 (5天)”Week 3:├── Day 1: T013 (参数管理应用)├── Day 2: T014 (增益控制应用)├── Day 3: T015 (电池监控应用)├── Day 4: T016 (串口通信应用)└── Day 5: T017 (主程序集成)第四阶段:测试与文档 (6天)
跳转到“第四阶段:测试与文档 (6天)”Week 4:├── Day 1-2: T018 (单元测试)├── Day 3-4: T019 (系统集成测试)└── Day 5-6: T020 (技术文档编写)4.3 详细任务卡片(示例)
跳转到“4.3 详细任务卡片(示例)”我来重新绘制详细的任务卡片,使用更清晰的格式:
📋 嵌入式开发任务派发卡片
跳转到“📋 嵌入式开发任务派发卡片”任务卡片 T001: 工程创建与配置
跳转到“任务卡片 T001: 工程创建与配置”| 项目 | 内容 |
|---|---|
| 任务编号 | T001 |
| 优先级 | 🔴 高 |
| 预计工时 | 0.5天 |
| 依赖任务 | 无 |
| 负责人 | [待分配] |
| 状态 | ⏳ 待开始 |
📝 任务描述
跳转到“📝 任务描述”使用STM32CubeMX创建STM32F103C8T6工程,配置系统时钟、外设引脚。
📦 交付物清单
跳转到“📦 交付物清单”- STM32CubeMX配置文件(.ioc)
- Keil/IAR工程文件
- 基础HAL库代码
- 引脚配置文档
🔧 技术要求
跳转到“🔧 技术要求”系统时钟配置:
┌─────────────────────────────────┐│ HSE: 8MHz 外部晶振 ││ PLL倍频: x9 ││ SYSCLK: 72MHz ││ AHB: 72MHz ││ APB1: 36MHz ││ APB2: 72MHz │└─────────────────────────────────┘外设配置:
┌──────────┬─────────────┬──────────────┬──────────┐│ 外设 │ 引脚 │ 配置 │ 说明 │├──────────┼─────────────┼──────────────┼──────────┤│ USART1 │ PA9(TX) │ 115200-8-N-1 │ 中断模式 ││ │ PA10(RX) │ │ │├──────────┼─────────────┼──────────────┼──────────┤│ I2C1 │ PB6(SCL) │ 100KHz │ 主机模式 ││ │ PB7(SDA) │ │ │├──────────┼─────────────┼──────────────┼──────────┤│ TIM2 │ - │ 1ms定时 │ 中断使能 │├──────────┼─────────────┼──────────────┼──────────┤│ GPIO │ PA0/PA1/PA2 │ 推挽输出 │ 增益控制 ││ │ PC13 │ 推挽输出 │ LED指示 │└──────────┴─────────────┴──────────────┴──────────┘✅ 验收标准
跳转到“✅ 验收标准”- 工程能正常编译通过
- 系统时钟配置为72MHz
- 所有外设时钟正确使能
- 引脚功能配置正确
- 生成的代码无编译警告
🧪 测试方法
跳转到“🧪 测试方法”// 时钟验证uint32_t sysclk = HAL_RCC_GetSysClockFreq();assert(sysclk == 72000000);
// LED闪烁验证while(1) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); HAL_Delay(500);}任务卡片 T002: BSP_GPIO驱动
跳转到“任务卡片 T002: BSP_GPIO驱动”| 项目 | 内容 |
|---|---|
| 任务编号 | T002 |
| 优先级 | 🔴 高 |
| 预计工时 | 0.5天 |
| 依赖任务 | T001 |
| 负责人 | [待分配] |
| 状态 | ⏳ 待开始 |
📝 任务描述
跳转到“📝 任务描述”开发GPIO驱动模块,封装GPIO读写接口,支持增益控制和LED指示。
📦 交付物清单
跳转到“📦 交付物清单”-
Drivers/BSP/bsp_gpio.c- GPIO驱动实现 -
Drivers/BSP/bsp_gpio.h- GPIO驱动头文件 - GPIO功能测试代码
- 测试报告文档
🔧 技术要求
跳转到“🔧 技术要求”引脚配置表:
┌────────┬──────────────┬────────────┬──────────┐│ 引脚 │ 功能 │ 模式 │ 初始状态 │├────────┼──────────────┼────────────┼──────────┤│ PA0 │ GAIN_CTRL0 │ 推挽输出 │ 低电平 ││ PA1 │ GAIN_CTRL1 │ 推挽输出 │ 低电平 ││ PA2 │ GAIN_CTRL2 │ 推挽输出 │ 低电平 ││ PC13 │ LED_STATUS │ 推挽输出 │ 高电平 │└────────┴──────────────┴────────────┴──────────┘接口定义:
/* 初始化所有GPIO */int BSP_GPIO_Init(void);
/* GPIO写操作 */void BSP_GPIO_Write(GPIO_TypeDef *port, uint16_t pin, uint8_t state);
/* GPIO读操作 */uint8_t BSP_GPIO_Read(GPIO_TypeDef *port, uint16_t pin);
/* GPIO翻转 */void BSP_GPIO_Toggle(GPIO_TypeDef *port, uint16_t pin);代码规范要求:
- 模块内部变量使用
static修饰 - 所有函数添加Doxygen注释
- 错误返回值统一为
-1 - 成功返回值统一为
0
✅ 验收标准
跳转到“✅ 验收标准”- 所有GPIO能正常初始化
- LED能够正常闪烁
- 增益控制引脚能正确输出高低电平
- 代码符合编码规范
- 无内存泄漏
- 无编译警告
🧪 测试用例
跳转到“🧪 测试用例”/** * 测试1:LED闪烁测试 */void Test_LED_Blink(void){ BSP_GPIO_Init(); for(int i=0; i<10; i++) { BSP_GPIO_Toggle(GPIOC, GPIO_PIN_13); HAL_Delay(500); }}
/** * 测试2:增益控制引脚测试 */void Test_Gain_Pins(void){ // 测试所有组合(000~111) for(uint8_t i=0; i<8; i++) { BSP_GPIO_Write(GPIOA, GPIO_PIN_0, (i & 0x01) ? 1 : 0); BSP_GPIO_Write(GPIOA, GPIO_PIN_1, (i & 0x02) ? 1 : 0); BSP_GPIO_Write(GPIOA, GPIO_PIN_2, (i & 0x04) ? 1 : 0); HAL_Delay(100); // 使用示波器验证输出电平 }}任务卡片 T003: BSP_Timer驱动
跳转到“任务卡片 T003: BSP_Timer驱动”| 项目 | 内容 |
|---|---|
| 任务编号 | T003 |
| 优先级 | 🔴 高 |
| 预计工时 | 0.5天 |
| 依赖任务 | T001 |
| 负责人 | [待分配] |
| 状态 | ⏳ 待开始 |
📝 任务描述
跳转到“📝 任务描述”配置TIM2产生精确的1ms定时中断,为调度器提供系统时基。
📦 交付物清单
跳转到“📦 交付物清单”-
Drivers/BSP/bsp_timer.c- 定时器驱动实现 -
Drivers/BSP/bsp_timer.h- 定时器驱动头文件 - 定时精度测试代码
- 精度测试报告(示波器波形图)
🔧 技术要求
跳转到“🔧 技术要求”定时器参数计算:
时钟源: APB1 Timer Clock = 72MHz目标周期: 1ms = 1000Hz
预分频器 = 72 - 1 = 71 (72MHz / 72 = 1MHz)
自动重载值 = 1000 - 1 = 999 (1MHz / 1000 = 1000Hz = 1ms)
中断优先级: 抢占优先级3, 子优先级0TIM2配置:
┌──────────────────┬─────────────┐│ 配置项 │ 值 │├──────────────────┼─────────────┤│ 定时器 │ TIM2 ││ 时钟源 │ 内部时钟 ││ 预分频器 │ 71 ││ 计数模式 │ 向上计数 ││ 自动重载值 │ 999 ││ 时钟分频 │ 1 ││ 重复计数器 │ 0 ││ 中断使能 │ UPDATE ││ 抢占优先级 │ 3 ││ 子优先级 │ 0 │└──────────────────┴─────────────┘接口定义:
/* 初始化定时器 */int BSP_Timer_Init(void);
/* 启动定时器 */void BSP_Timer_Start(void);
/* 停止定时器 */void BSP_Timer_Stop(void);
/* 定时器中断回调(弱定义) */void BSP_Timer_Callback(void);✅ 验收标准
跳转到“✅ 验收标准”- 定时器能产生精确1ms中断
- 定时精度误差 < 0.1%
- 中断响应及时(< 10us)
- 能正常启动/停止定时器
- 长时间运行稳定(测试1小时无漂移)
🧪 测试用例
跳转到“🧪 测试用例”测试1:精度测试
volatile uint32_t tick_counter = 0;
void TIM2_IRQHandler(void){ if(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); tick_counter++;
// 每1000次翻转GPIO(理论1秒) if(tick_counter % 1000 == 0) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); } }}
// 使用示波器测量PC13周期,应为2秒±2ms测试2:长时间稳定性测试
// 运行1小时,计数应为:3600000 ± 360uint32_t expected = 3600000;uint32_t tolerance = 360; // 0.01%
// 运行1小时后验证assert(tick_counter > expected - tolerance);assert(tick_counter < expected + tolerance);任务卡片 T004: BSP_UART驱动
跳转到“任务卡片 T004: BSP_UART驱动”| 项目 | 内容 |
|---|---|
| 任务编号 | T004 |
| 优先级 | 🔴 高 |
| 预计工时 | 1天 |
| 依赖任务 | T001 |
| 负责人 | [待分配] |
| 状态 | ⏳ 待开始 |
📝 任务描述
跳转到“📝 任务描述”开发UART1驱动,实现中断接收、阻塞发送,使用环形缓冲区防止数据丢失。
📦 交付物清单
跳转到“📦 交付物清单”-
Drivers/BSP/bsp_uart.c- UART驱动实现 -
Drivers/BSP/bsp_uart.h- UART驱动头文件 - 串口收发压力测试代码
- 通信测试报告
🔧 技术要求
跳转到“🔧 技术要求”UART硬件配置:
┌──────────────────┬─────────────┐│ 配置项 │ 值 │├──────────────────┼─────────────┤│ 实例 │ USART1 ││ 引脚(TX) │ PA9 ││ 引脚(RX) │ PA10 ││ 波特率 │ 115200 ││ 数据位 │ 8 ││ 停止位 │ 1 ││ 校验位 │ None ││ 模式 │ TX+RX ││ 接收模式 │ 中断 ││ 发送模式 │ 阻塞 ││ 中断优先级 │ 2/0 │└──────────────────┴─────────────┘环形缓冲区设计:
大小: 256字节类型: FIFO线程安全: 中断安全
┌───────────────────────────────┐│ ││ [空闲空间] ││ ││ Tail ──> [数据] <── Head ││ [数据] ││ [数据] ││ ││ [空闲空间] ││ │└───────────────────────────────┘接口定义:
/* 初始化UART */int BSP_UART_Init(void);
/* 发送数据(阻塞) */int BSP_UART_Send(uint8_t *data, uint16_t len);
/* 从缓冲区读取一字节 */int BSP_UART_Read(uint8_t *data);
/* 获取可读字节数 */uint16_t BSP_UART_Available(void);
/* 清空接收缓冲区 */void BSP_UART_Flush(void);
/* 接收中断回调(内部使用) */void BSP_UART_RxCallback(uint8_t data);✅ 验收标准
跳转到“✅ 验收标准”- UART能正常初始化
- 能通过串口助手正常收发数据
- 连续接收256字节不丢失数据
- 发送速率达到115200bps
- 缓冲区满时能正确处理(丢弃或覆盖)
- 代码无内存泄漏
- 支持printf重定向
🧪 测试用例
跳转到“🧪 测试用例”测试1:基础收发测试
void Test_UART_EchoTest(void){ BSP_UART_Init();
char msg[] = "Hello STM32!\r\n"; BSP_UART_Send((uint8_t*)msg, strlen(msg));
// 回环测试:发送的数据应能从上位机收到}测试2:大数据量压力测试
void Test_UART_StressTest(void){ uint8_t test_data[256]; uint8_t recv_data[256]; uint16_t recv_count = 0;
// 生成测试数据 for(int i=0; i<256; i++) { test_data[i] = i; }
// 上位机连续发送256字节 // (需要上位机配合测试)
// 等待接收 uint32_t timeout = HAL_GetTick() + 2000; while(recv_count < 256 && HAL_GetTick() < timeout) { if(BSP_UART_Available() > 0) { BSP_UART_Read(&recv_data[recv_count++]); } }
// 验证数据完整性 assert(recv_count == 256); assert(memcmp(test_data, recv_data, 256) == 0);}测试3:缓冲区溢出测试
void Test_UART_BufferOverflow(void){ // 上位机连续发送300字节(超过256字节缓冲区) // 验证前256字节正确接收 // 后续数据丢弃或覆盖}任务卡片 T005: BSP_I2C驱动
跳转到“任务卡片 T005: BSP_I2C驱动”| 项目 | 内容 |
|---|---|
| 任务编号 | T005 |
| 优先级 | 🔴 高 |
| 预计工时 | 1天 |
| 依赖任务 | T001 |
| 负责人 | [待分配] |
| 状态 | ⏳ 待开始 |
📝 任务描述
跳转到“📝 任务描述”开发I2C1驱动,支持多设备读写,支持电量芯片和EEPROM通信。
📦 交付物清单
跳转到“📦 交付物清单”-
Drivers/BSP/bsp_i2c.c- I2C驱动实现 -
Drivers/BSP/bsp_i2c.h- I2C驱动头文件 - I2C设备扫描工具代码
- I2C通信测试报告
🔧 技术要求
跳转到“🔧 技术要求”I2C硬件配置:
┌──────────────────┬─────────────┐│ 配置项 │ 值 │├──────────────────┼─────────────┤│ 实例 │ I2C1 ││ 引脚(SCL) │ PB6 ││ 引脚(SDA) │ PB7 ││ 速率 │ 100KHz ││ 模式 │ Standard ││ 主/从 │ Master ││ 地址模式 │ 7-bit ││ 占空比 │ 2 ││ 应答使能 │ Yes ││ 超时时间 │ 1000ms │└──────────────────┴─────────────┘设备地址表:
┌─────────────────┬──────────┬──────────┐│ 设备 │ 7位地址 │ 8位地址 │├─────────────────┼──────────┼──────────┤│ 电量芯片(BQ27441)│ 0x55 │ 0xAA/0xAB││ EEPROM(AT24C02) │ 0x50 │ 0xA0/0xA1││ (预留扩展) │ - │ - │└─────────────────┴──────────┴──────────┘接口定义:
/* 初始化I2C */int BSP_I2C_Init(void);
/* 写单字节 */int BSP_I2C_WriteByte(uint8_t dev_addr, uint8_t reg_addr, uint8_t data);
/* 读单字节 */int BSP_I2C_ReadByte(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data);
/* 写多字节 */int BSP_I2C_WriteBytes(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t len);
/* 读多字节 */int BSP_I2C_ReadBytes(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t len);
/* 检测设备是否存在 */int BSP_I2C_IsDeviceReady(uint8_t dev_addr);✅ 验收标准
跳转到“✅ 验收标准”- I2C能正常初始化
- 能扫描到总线上的I2C设备
- 能正确读写I2C设备
- 通信失败时返回正确错误码
- 有超时保护机制
- 支持多设备共享总线
🧪 测试用例
跳转到“🧪 测试用例”测试1:I2C设备扫描
void Test_I2C_ScanDevices(void){ BSP_I2C_Init();
printf("Scanning I2C bus...\r\n"); for(uint8_t addr = 0x01; addr < 0x7F; addr++) { if(BSP_I2C_IsDeviceReady(addr) == 0) { printf("Found device at 0x%02X\r\n", addr); } } printf("Scan complete.\r\n");}测试2:EEPROM读写测试
void Test_I2C_EEPROM(void){ uint8_t write_data = 0xAA; uint8_t read_data;
// 写入数据 BSP_I2C_WriteByte(0x50, 0x00, write_data); HAL_Delay(5); // EEPROM写入延迟
// 读取数据 BSP_I2C_ReadByte(0x50, 0x00, &read_data);
// 验证数据 assert(read_data == write_data); printf("EEPROM test: PASS\r\n");}测试3:超时测试
void Test_I2C_Timeout(void){ uint8_t data;
// 尝试访问不存在的设备(应超时返回错误) int ret = BSP_I2C_ReadByte(0x7F, 0x00, &data);
assert(ret != 0); // 应返回错误 printf("Timeout test: PASS\r\n");}任务卡片 T006: 调度器模块
跳转到“任务卡片 T006: 调度器模块”| 项目 | 内容 |
|---|---|
| 任务编号 | T006 |
| 优先级 | 🔴 高 |
| 预计工时 | 1天 |
| 依赖任务 | T003 |
| 负责人 | [待分配] |
| 状态 | ⏳ 待开始 |
📝 任务描述
跳转到“📝 任务描述”开发软件定时调度器,支持周期任务和事件触发任务,为裸机系统提供类RTOS的任务管理能力。
📦 交付物清单
跳转到“📦 交付物清单”-
Service/scheduler.c- 调度器实现 -
Service/scheduler.h- 调度器头文件 - 多任务调度演示代码
- 调度器使用手册
🔧 技术要求
跳转到“🔧 技术要求”调度器架构:
┌────────────────────────────────────┐│ Scheduler (调度器) │├────────────────────────────────────┤│ - 系统时基(1ms) ││ - 任务列表 ││ - 任务注册 ││ - 任务调度 │└────────────────────────────────────┘ ↓ ↓ ↓ ┌────────┐ ┌────────┐ ┌────────┐ │ 任务1 │ │ 任务2 │ │ 任务3 │ │ 10ms │ │ 100ms │ │ 事件 │ └────────┘ └────────┘ └────────┘任务类型:
1. 周期任务 (Periodic Task) - 按固定周期执行 - 例如:1ms, 10ms, 100ms, 1000ms
2. 事件任务 (Event-Triggered Task) - 由外部事件触发立即执行 - 例如:串口接收、按键按下数据结构设计:
/* 任务ID枚举 */typedef enum { TASK_UART = 0, // 串口通信任务 TASK_BATTERY, // 电池监控任务(1秒) TASK_GAIN, // 增益控制任务(100ms) TASK_PARAM, // 参数保存任务(事件) TASK_MAX // 任务总数} TaskID_t;
/* 任务函数指针类型 */typedef void (*TaskFunc_t)(void);
/* 任务控制块 */typedef struct { TaskFunc_t func; // 任务函数指针 uint32_t period; // 任务周期(ms), 0=事件触发 uint32_t elapsed; // 已经过时间(ms) uint8_t enabled; // 任务使能标志 const char *name; // 任务名称(调试用)} Task_t;接口定义:
/* 初始化调度器 */int Scheduler_Init(void);
/* 注册任务 */int Scheduler_RegisterTask(TaskID_t id, TaskFunc_t func, uint32_t period, const char *name);
/* 启用任务 */void Scheduler_EnableTask(TaskID_t id);
/* 禁用任务 */void Scheduler_DisableTask(TaskID_t id);
/* 触发事件型任务 */void Scheduler_TriggerTask(TaskID_t id);
/* 调度器主循环(在main的while(1)中调用) */void Scheduler_Run(void);
/* 1ms时基更新(在定时器中断中调用) */void Scheduler_TickUpdate(void);
/* 获取系统运行时间(ms) */uint32_t Scheduler_GetTick(void);✅ 验收标准
跳转到“✅ 验收标准”- 能注册和管理至少8个任务
- 周期任务误差 < 5ms
- 事件任务能立即响应(< 1ms)
- 系统时基精确(误差 < 0.1%)
- 任务使能/禁用功能正常
- 长时间运行稳定(测试24小时无崩溃)
🧪 测试用例
跳转到“🧪 测试用例”测试1:周期任务精度测试
volatile uint32_t task1_count = 0;volatile uint32_t task2_count = 0;
void task1_func(void) { task1_count++; }void task2_func(void) { task2_count++; }
void Test_Scheduler_Precision(void){ Scheduler_Init(); Scheduler_RegisterTask(TASK_UART, task1_func, 10, "Task1"); Scheduler_RegisterTask(TASK_BATTERY, task2_func, 100, "Task2");
// 运行1秒 uint32_t start = Scheduler_GetTick(); while(Scheduler_GetTick() - start < 1000) { Scheduler_Run(); }
// 验证执行次数 // Task1: 10ms周期,1秒应执行100次 // Task2: 100ms周期,1秒应执行10次 assert(task1_count >= 98 && task1_count <= 102); assert(task2_count >= 9 && task2_count <= 11);
printf("Precision test: PASS\r\n"); printf("Task1 executed: %d times\r\n", task1_count); printf("Task2 executed: %d times\r\n", task2_count);}测试2:事件触发测试
volatile uint8_t event_executed = 0;
void event_task_func(void) { event_executed = 1;}
void Test_Scheduler_EventTrigger(void){ Scheduler_Init(); Scheduler_RegisterTask(TASK_PARAM, event_task_func, 0, "Event");
event_executed = 0;
// 触发事件任务 Scheduler_TriggerTask(TASK_PARAM);
// 验证立即执行 assert(event_executed == 1);
printf("Event trigger test: PASS\r\ n");}测试3:任务使能/禁用测试
void Test_Scheduler_EnableDisable(void){ task1_count = 0;
Scheduler_Init(); Scheduler_RegisterTask(TASK_UART, task1_func, 10, "Task1");
// 禁用任务 Scheduler_DisableTask(TASK_UART);
// 运行100ms uint32_t start = Scheduler_GetTick(); while(Scheduler_GetTick() - start < 100) { Scheduler_Run(); }
// 任务被禁用,不应执行 assert(task1_count == 0);
// 启用任务 Scheduler_EnableTask(TASK_UART);
// 再运行100ms start = Scheduler_GetTick(); while(Scheduler_GetTick() - start < 100) { Scheduler_Run(); }
// 任务已启用,应执行约10次 assert(task1_count >= 9 && task1_count <= 11);
printf("Enable/Disable test: PASS\r\n");}任务统计表
跳转到“任务统计表”| 任务编号 | 任务名称 | 优先级 | 工时 | 依赖 | 状态 |
|---|---|---|---|---|---|
| T001 | 工程创建与配置 | 🔴 高 | 0.5天 | 无 | ⏳ 待开始 |
| T002 | BSP_GPIO驱动 | 🔴 高 | 0.5天 | T001 | ⏳ 待开始 |
| T003 | BSP_Timer驱动 | 🔴 高 | 0.5天 | T001 | ⏳ 待开始 |
| T004 | BSP_UART驱动 | 🔴 高 | 1天 | T001 | ⏳ 待开始 |
| T005 | BSP_I2C驱动 | 🔴 高 | 1天 | T001 | ⏳ 待开始 |
| T006 | 调度器模块 | 🔴 高 | 1天 | T003 | ⏳ 待开始 |
| T007 | 环形缓冲区 | 🟡 中 | 0.5天 | 无 | ⏳ 待开始 |
| T008 | CRC16模块 | 🟡 中 | 0.5天 | 无 | ⏳ 待开始 |
| T009 | 协议模块 | 🔴 高 | 1天 | T008 | ⏳ 待开始 |
| T010 | 增益控制驱动 | 🟡 中 | 0.5天 | T002 | ⏳ 待开始 |
| T011 | EEPROM驱动 | 🟡 中 | 1天 | T005 | ⏳ 待开始 |
| T012 | 电量芯片驱动 | 🟡 中 | 1.5天 | T005 | ⏳ 待开始 |
| T013 | 参数管理应用 | 🟡 中 | 1天 | T011 | ⏳ 待开始 |
| T014 | 增益控制应用 | 🟡 中 | 1天 | T010,T013 | ⏳ 待开始 |
| T015 | 电池监控应用 | 🟡 中 | 1天 | T012 | ⏳ 待开始 |
| T016 | 串口通信应用 | 🔴 高 | 1.5天 | T004,T009 | ⏳ 待开始 |
| T017 | 主程序集成 | 🔴 高 | 1天 | T013~T016 | ⏳ 待开始 |
| T018 | 单元测试 | 🟡 中 | 2天 | T017 | ⏳ 待开始 |
| T019 | 系统集成测试 | 🔴 高 | 2天 | T018 | ⏳ 待开始 |
| T020 | 文档编写 | 🟢 低 | 2天 | T019 | ⏳ 待开始 |
总计工时:约20个工作日
这是我为你设计的完整的STM32F103C8T6裸机开发方案。现在你有了:
✅ 完整的系统架构设计 ✅ 详细的模块设计文档 ✅ 清晰的模块依赖关系 ✅ 20个可执行的开发任务卡片 ✅ 每个任务的详细技术要求和验收标准 ✅ 完整的测试用例
你可以直接按照这个方案开始开发。如果你需要:
- 某个具体模块的完整代码实现
- 更详细的测试方案
- 调试技巧和常见问题解决方案
- 上位机测试工具的设计
来源:CSDN 原文,首次发布于 2025-12-09。