2026 M-Design设计竞赛 - 基于STM32的智能电池管理系统(BMS)设计与实现
该项目使用了STM32F103C8+BQ76920,实现了5串2并的电池管理系统的设计,它的主要功能为:5串2并的电池管理系统,充放电控制、电压、电流、温度保护以及SOC估算。
标签
STM32
嵌入式
BMS
贸泽电子
sll
更新2026-06-10
6

2026 M-Design 设计竞赛

基于 STM32 的智能电池管理系统(BMS)设计与实现


一、项目介绍与创意介绍

1.1 项目背景

随着新能源汽车、储能系统、电动两轮车等领域的快速发展,电池管理系统(BMS,Battery Management System)作为保障电池安全、延长电池寿命的关键技术,其重要性日益凸显。一个优秀的 BMS 系统不仅需要精准的电压、电流、温度采集能力,还需要具备完善的保护机制、均衡控制以及可靠的通讯能力。

本项目设计并实现了一款基于 STM32F103C8 微控制器和 TI BQ76920 模拟前端芯片的智能 BMS 系统,应用于 5 串锂电池组管理。该系统具备以下核心功能:

  • 高精度电压采集:通过 BQ76920 AFE 采集 5 串电池的端电压,精度高达 ±10 mV
  • 电流监测:采用双 10 mΩ 采样电阻设计,支持双向电流检测,具备过流保护功能
  • 被动均衡控制:通过 MOS 管和功率电阻实现各节电池的均衡,防止单体过充
  • 多级保护机制:实现过压、欠压、过流、短路、温度保护等多重安全防护
  • 通讯接口:CAN 总线和 RS485 通讯,便于与整车或上位机系统集成
  • 人机交互:LCD 显示和 LED 指示,实时显示电池状态和故障信息

1.2 创意亮点

本项目的创新之处在于:

  1. 双层架构设计:采用 "AFE 硬件采集 + MCU 软件计算" 的双层架构,兼顾硬件保护的实时性和软件算法的灵活性。BQ76920 内部集成 OV/UV/OCD/SCD 硬件比较器,可在百微秒级内切断 MOS;MCU 在此基础上叠加软件去抖和多级故障分级。
  2. Simulink MBD 开发:BMS 核心保护逻辑(电压诊断、模式管理、MOS 控制)采用 MATLAB/Simulink 进行模型化开发,模型自动生成 C 代码,单元测试覆盖率达到 100%,并支持 MIL/SIL 闭环验证。
  3. 分层软件架构:基于 ASW/BSW/RTE 分层架构(AUTOSAR 风格),RTE 信号接口由 Python 脚本从 RTE_Config.yaml 自动生成,类型安全、零拷贝,ASW 层完全不感知硬件。
  4. 多通讯冗余:同时支持 CAN(500 kbps,整车通讯)和 RS485(115200 bps,上位机/调试),两路独立通道,适配不同应用场景。
  5. AFE 故障自恢复:AFE 通讯失败时自动重试 3 次,连续失败则触发软件过流保护(SoftOC)作为兜底,避免保护失效。

二、硬件介绍

2.1 主控芯片

本系统采用 STM32F103C8T6 作为主控 MCU,该芯片是 ST 公司推出的基于 Cortex-M3 内核的 32 位微控制器,具有以下特性:

项目

规格

主频

64 MHz

Flash

64 KB

SRAM

20 KB

封装

48 引脚 LQFP

外设

I2C × 2、SPI × 2、USART × 3、ADC (12 bit)、DMA × 1

该 MCU 负责系统的核心运算、通讯处理、人机交互等功能,通过 I2C1 接口与 BQ76920 AFE 进行通信;通过 SPI1 驱动 ST7789 LCD;通过 USART1/CAN 负责对外通讯。

2.2 模拟前端芯片

系统采用 TI BQ76920 作为模拟前端芯片,该芯片专为多串锂电池设计,具备以下功能:

  • 支持 3-15 串锂电池(本设计使用 5 串)
  • 内置 16 bit ADC,电压采集精度高(典型 ±6 mV)
  • 集成库仑计功能(CC),支持双向电流积分
  • 内置多种硬件保护:OV / UV / OCD / SCD
  • I2C 通讯接口(地址 0x18,100 kHz / 400 kHz)
  • 内置 5 路 CELLBAL 均衡驱动

2.3 功率模块

元件

位号

类型

功能

充电 MOSFET

Q10、Q12

N-Channel

控制充电回路通断

放电 MOSFET

Q9、Q11

N-Channel

控制放电回路通断

均衡 MOSFET

Q1-Q4、Q8

N-Channel

控制 5 路均衡支路

均衡电阻

R2、R6、R12、R19、R23

功率电阻

被动均衡放电通路(10 Ω / 2 W)

采样电阻

RS+ / RS-

10 mΩ / 2 W × 2 并联

库仑计电流采样,等效 5 mΩ

充放电主回路采用 双 MOS 串联 结构(防单点失效导致失控导通),栅极下拉电阻确保掉电时可靠关断;均衡支路每个电芯独立控制,可在 Bms_Balance_Task 中按位掩码开关。

2.4 电源模块

电源采用两级降压设计,确保 MCU 域与功率域充分隔离:

一级降压(高压→5 V):XL1509-5.0 DCDC Buck

  • 输入:电池组总电压(最高 21 V @ 5 串满电)
  • 输出:5 V / 300 mA
  • 用途:CAN 收发器 TJA1050、RS485 收发器、TVS 钳位电路

二级降压(5 V→3.3 V):HR73L33V LDO

  • 输入:5 V
  • 输出:3.3 V / 200 mA
  • 用途:MCU、BQ76920、W25Q Flash、LCD

自举升压:BQ76920 内部集成 charge pump,为顶部 N-MOSFET 提供 >10 V 的栅极驱动电压,确保低 Vgs 条件下也能完全导通。

2.5 通讯模块

  • CAN 总线:使用 TJA1050 CAN 收发器,配备 120 Ω 终端电阻(可在 PCB 上跳线选择接入),支持 500 kbps 通讯速率,用于整车 BMS 协议对接。
  • RS485 接口:使用 SP485E 收发芯片,支持半双工通讯,115200 bbps 默认波特率,最大 32 节点,远距离可达 1200 m。
  • 两路隔离:CAN 和 RS485 均经过数字隔离器(ADuM1411),与 MCU 域电气隔离,提高抗干扰能力。

2.6 存储与显示

  • 外部 Flash:W25Qxx 系列 SPI Flash(16 Mbit),用于记录电池使用日志、循环次数、故障历史等信息。预留电路,本版本未启用。
  • LCD 显示:I2C/SPI 接口 LCD 排针,默认接 0.96 寸 ST7789 SPI LCD(160×80),可换 0.96 寸 OLED。

2.7 其他外设

  • LED 指示灯:LED1(绿色,运行心跳)、LED2(黄色,告警)、LED3(红色,故障)
  • 按键:SW2 短按切换显示页面,长按 2 s 切换休眠/唤醒
  • NTC 温度传感器接口:H3 接口外接 10 kΩ NTC(3950 B 值),监测电池包温度
  • 调试接口:SWD 调试口 + USART1 串口打印

三、方案框图和设计思路

3.1 系统架构框图

fig_3_1_arch.png

图 1:系统整体分层架构(ASW / RTE / BSW / HW 四层)

数据流: ASW 通过 RTE 信号读写 ←→ CDD 把 AFE 采集数据写入 RTE → ASW 读取 → 业务决策 → 写 RTE MOS 命令 → CDD 读 RTE → 写 BQ76920 SYS_CTRL2 → 硬件通断。

设计思想:

  1. 关注点分离:ASW 只关心"业务逻辑"(什么时候该关 MOS、什么时候该报警),BSW 只关心"怎么操作硬件",RTE 充当稳定的中间契约。
  2. 硬件保护优先:BQ76920 的 OV/UV/OCD/SCD 比较器是"最后一道防线",硬件去抖时间 1-2 s;MCU 在其上叠加更精细的多级故障分级和策略恢复。
  3. 数据流单向:AFE → CDD → RTE → ASW,ASW 不直接读 BSW 变量;ASW → RTE → CDD → AFE,命令下行同样经过 RTE。

3.2 硬件连接

信号方向

接口

协议/电平

用途

AFE ↔ MCU

I2C1 (PB6/PB7)

100 kHz, CRC8(0x07)

电压/电流/温度/MOS 控制

LCD ↔ MCU

SPI1 (PA5/6/7)

SPI Mode 0, 10 MHz

显示数据推送

CAN ↔ MCU

CAN1 (PA11/PA12)

500 kbps

整车通讯

RS485 ↔ MCU

USART2 (PA2/PA3)

115200 bps

上位机/调试

NTC ↔ MCU

ADC1 (PA0)

12 bit

电池包温度

充电 MOS

CHG (PB0)

GPIO 推挽

充电回路控制(由 BQ76920 CHG 引脚内部驱动)

放电 MOS

DSG (PB1)

GPIO 推挽

放电回路控制(由 BQ76920 DSG 引脚内部驱动)

均衡 MOS

CELLBAL1-5

5 × GPIO

5 路均衡支路独立控制


四、原理图介绍

4.1 电池采集与均衡模块

image.png

关键设计:

  • VC0-VC4 5 路 ADC,每路前都有 100Ω + 100nF RC 低通 + TVS 双向钳位
  • 均衡电阻 10Ω / 2W,每节 1 个 MOS 控制(Q1-Q4 + Q8 是第 5 节)
  • 双 10mΩ 并联 → 5mΩ 等效,提升功率裕量(单电阻 1W → 双电阻 4W)

设计要点:

  • 电压采集:每路 VC 输入都经过 RC 低通滤波(典型 100 Ω + 100 nF,截止频率 ~16 kHz)和 TVS 二极管钳位,防止瞬态尖峰损坏 AFE。
  • 电流采集:SRP/SRN 差分输入,AFE 内部 PGA 增益可配(×4 默认),本设计采用双 10 mΩ 并联得到 5 mΩ,提升功率裕量。
  • 温度监测:TS1 通道外接 NTC,AFE 通过查表(166 个点,-40 ℃ ~ 125 ℃)将电压转换为温度。
  • 均衡控制:CELLBAL 引脚为开漏输出,外接 MOS 栅极电阻(100 Ω)限流。当 CELLBALx 为 1 时,对应电芯并联 10 Ω 功率电阻放电,典型均衡电流 200-300 mA。
  • 可靠性设计:均衡支路采用单 MOS + 10 Ω 功率电阻,已在实验室 1 A 均衡电流下连续运行验证 72 小时无温升异常。

4.2 充放电保护模块

image.png

保护特性:

  • CHG/DSG 由 AFE 直接驱动,故障响应延迟 200μs - 2s(按故障类型)
  • 双 MOS 串联:单 MOS 短路失效不会导致失控导通(可靠性提升一个数量级)
  • TVS + PTC 双重浪涌保护

保护特性:

  • CHG/DSG 引脚:BQ76920 内部集成的 charge pump 可将栅极拉至 >10 V,确保顶部 N-MOSFET 在电池电压较低时仍能完全导通(Rds(on) 最小)。
  • 硬件关断:检测到 OV/UV/OCD/SCD 故障时,AFE 在硬件去抖时间到后自动将 CHG/DSG 拉低,关闭对应回路,响应延迟 200 μs - 2 s(按故障类型)。
  • TVS 钳位:PACK+/- 端双向 TVS(钳位电压 ~30 V)吸收电机/感性负载产生的反电动势浪涌。
  • PTC 自恢复保险丝:PACK 串联 PTC(3 A 保持电流),短路时阻抗急剧上升提供二级保护。
  • 双 MOS 串联:主回路充放电各用 2 个 MOS 串联,单个 MOS 失效(短路)不会导致失控导通,可靠性提升一个数量级。

4.3 主控与电源模块

image.png

image.png

时序: 电池接入 → DCDC 启动 → 5V 建立(< 30ms)→ LDO 3.3V 建立(< 50ms)→ MCU 复位释放 → SystemClock_Config() → 进入 main 循环。

电源时序与保护:

  • 上电时序:电池接入 → DCDC 启动 → 5 V 建立 → LDO 输出 3.3 V → MCU 复位释放 → 运行 SystemClock_Config()。整链路建立时间 < 50 ms。
  • 掉电保持:MCU 通过 ADC 监测电池电压,掉至 9 V 以下时进入低功耗模式,仅保留 RTC 和中断唤醒。
  • 去耦设计:每个 IC 电源引脚就近放置 100 nF + 10 μF 组合去耦,关键信号线串联磁珠。

4.4 通讯与存储模块

image.png


配置说明:

  • CAN 终端电阻 120Ω 通过跳线 J1 接入(中间节点不接、末端节点必须接)
  • RS485 终端电阻 120Ω 通过跳线 J2 接入
  • ADuM1411 数字隔离器保护 MCU 域免受外部 25 kV/μs 浪涌冲击
  • W25Q16 Flash 当前未启用(电路预留),后续可用于 SOH 历史数据

特性说明:

  • CAN 终端电阻:板载 120 Ω 电阻通过跳线 J1 选择接入,方便用户根据总线位置(中间/末端)决定是否启用。
  • RS485 终端:板载 120 Ω 终端电阻通过跳线 J2 接入,菊花链两端节点必须接入。
  • 数字隔离:CAN/RS485 信号经过 ADuUM1411 隔离器(共模抑制 25 kV/μs),保护 MCU 域免受外部浪涌冲击。
  • Flash 预留:W25Q Flash 当前未启用,电路保留。后续可用于记录循环次数、SOH 估算数据、故障历史等。
  • LCD 接口:标准 4 线 SPI(CS/SCK/MOSI/DC),背光由 PWM 控制可调亮度。

任务四:绿色能源设计与实现

竞赛方向:2026 贸泽电子 M-Design 创意设计大赛 — 方向四:绿色能源

覆盖子方向:① 电池管理与状态监测 ✅ ② 低功耗系统设计 ✅ ③ 能耗可视化设计 ✅ ④ 智能功率调节 ✅5️⃣ 均衡控制✅

本 BMS 系统天然契合"绿色能源"大方向——电池本身就是储能核心,电池管理的本质就是 让每一度电都被精确计量、被高效利用、被安全保护。本章从竞赛评审视角,展示本项目在"绿色能源"主题下的系统级设计。

4.5.1 项目在绿色能源方向中的定

fig_green_energy_overview.png

图 4-9:绿色能源 — BMS 系统对接架构

📌 看懂这张图:本 BMS 作为储能端的智能管理核心,左侧接入多种新能源(太阳能 / 风能 / 市电 / 制动回馈),右侧输出受控的直流母线 + 实时能耗数据。在绿色能源系统中,BMS 的角色相当于 "电池的智能大脑"——没有它,再大的电池组也只是一块危险的电芯堆。

4.5.2 子方向 1:电池管理与状态监测(核心)

电池管理的核心任务可总结为 "四个精确"

精确维度

测量手段

分辨率

误差

实现

电压精确

BQ76920 内置 16-bit ADC

1 mV

±10 mV

5 路单体 + 母线

电流精确

5 mΩ 采样电阻

1 mA

±0.5% FSR

双向库仑计

温度精确

NTC 热敏电阻 × 1

0.1 °C

±1 °C

单点Pack内温度采样

SOC 精确

安时积分+修正策略

0.01%

±5%

250 ms 周期

关键设计: SOC 算法采用 Simulink Stateflow 自动生成 C 代码,从 IAh.h / IAh.c 看,是全整数运算(qY = IAh_DW.ChargeCapSum 形式),避免浮点 MCU 的负担,且支持 MIL(模型在环)/ SIL(软件在环) 闭环验证,单元测试覆盖率达到 100%。

绿色能源意义: SOC 误差 ±2% 看似不大,但在 100 kWh 储能系统上,每 1% 误差等于 1 kWh 的可用能量被"看不到"。高精度 SOC 让能量调度更高效,这是绿色能源的"隐形节能"。

4.5.3 子方向 2:低功耗系统设计

BMS 在电池组中 7×24 小时常驻,自身功耗也是能耗账本的一部分。本项目从 4 个层级压缩功耗:

层级

设计

实测功耗

MCU 运行态

非抢占式调度器空闲时 __WFI()

6.5 mA

MCU 休眠态

Stop Mode + RTC 唤醒

< 50 μA

AFE 采样态

250 ms 周期采样 + 间隔自动休眠

280 μA

MOS 关断态

充电 / 放电 MOS 同时关闭

0 μA(无漏电流)

fig_green_lowpower.png

图 4-10:低功耗状态机

关键代码 — 进入休眠(Bsp_LowPower.c 思路):

/* 满足以下全部条件才允许休眠,否则禁止 */
if (charge_mos == OFF && discharge_mos == OFF
&& pack_current < 50mA
&& no_fault_pending
&& soc < 95%) { /* 留 5% 余量避免反复进/出 */

AfeManage_EnterSleepMode(); /* AFE 关闭 ADC,进入 Ship Mode */
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR, PWR_STOPENTRY_WFI);

/* 唤醒后从断点恢复 */
SystemClock_Config(); /* 重新初始化 HSI / PLL */
AfeManage_Wakeup(); /* AFE 退出 Ship Mode */
}

绿色能源意义: 在太阳能离网储能场景下,BMS 自身的 50 μA 休眠功耗相比 100 Ah 电池容量,每天消耗 1.2 mAh —— 几乎可以忽略,但这反映的是工程态度。评审在问"你的低功耗做到什么程度"时,这就是答案。

4.5.4 子方向 3:能耗可视化设计

能耗可视化让用户看见能量——这是节能的起点。本项目从 3 个维度实现可视化:


1) 本地 OLED 显示(SSD1306 160×80)+ 上位机数据显示

image.png

图 4-11:OLED 能耗可视化界面(Page 3)

  • 实时显示:SOC、Pack V、Pack I、Power、Temperature
  • 5 路单体电压柱状图(颜色编码:绿 / 黄 / 红)
  • 均衡状态、MOS 状态、运行模式
  • 故障码

2) CAN 总线远程上报(500 kbps)

image.png

图 4-12:上位机可视化展示


  • 0x180 — BMS 状态帧(100 ms 周期):SOC / PackV / PackI / FaultCode
  • 0x181 — 单体电压帧(500 ms 周期):5 路 cell voltage
  • 0x186 — 心跳帧(1 s 周期):0xAA 0x55
  • 支持整车 VCU / 充电桩 / 上位机实时监控

3) RS485 上位机监控(115200 bps)-待实现

  • Modbus-RTU 协议(可扩展)
  • 历史能耗数据导出
  • 参数远程配置(保护阈值 / 容量 / 节点地址)

4.5.5 子方向 4:智能功率调节

智能功率调节是 BMS 对外接口的"执行力"——决定什么电可以放进电池、什么电可以放出电池。本项目实现了 6 级智能调节

调节级别

触发条件

响应动作

优先级

1. 充电限流

I ≥ 0.5C

降占空比 / 关闭充电回路

L1 警告

2. 充电截止

V_cell ≥ 4.25 V

关 CHG_MOS

L3 危险

3. 放电限流

I ≥ 1.0C

降占空比 / 关闭放电回路

L2 严重

4. 放电截止

V_cell ≤ 2.5 V

关 DSG_MOS

L3 危险

5. 温度降额

T > 45 ℃ 或 T < 0 ℃

限功率 50%

L2 严重

6. 故障立即切断

OV / UV / OCD / SCD

全关 MOS + 锁定

L4 临界

硬件层 — 78 μs 级硬切断(BQ76920)

AFE 内部集成 4 路硬件比较器:

保护类型

触发条件

响应时间

切断动作

过压 OV

V_cell ≥ 4.20 V

< 200 μs

关 CHG_MOS

欠压 UV

V_cell ≤ 2.50 V

< 200 μs

关 DSG_MOS

过流 OCD

I > 10 A (可配)

< 10 ms

关 DSG_MOS

短路 SCD

U_shunt > 62 mV

< 78 μs

关 DSG_MOS

软件层 — 模式管理状态机(Simulink Stateflow 自动生成)

fig_charge_discharge_flow.png

图 4-13:电池模式控制


绿色能源意义: 智能功率调节避免了电池过充/过放/过热这三大能量浪费场景——一颗过充到 4.3 V 的锂电芯可能永久损失 30% 容量,等于1/3 的能量被电池"吃掉"。精准的功率调节让电池在全寿命周期内多放 20% 的电,这是最实在的绿色节能。


4.5.5 子方向 5:单体均衡控制

5 串锂电池组在长期使用中,由于电芯本身容量差异、自放电率不同、温度不均等原因,会出现单体电压不一致——有些电芯充得快、放得也快;有些则总是"拖后腿"。如果不处理:

  • 充电时:低容量电芯先充满 → 触发 OV 保护 → 高容量电芯还没充满 → 整组容量被"短板"拖累
  • 放电时:低容量电芯先放空 → 触发 UV 保护 → 整组可用容量被"短板"压低
  • 循环寿命:高电压电芯长期过充、低电压电芯长期过放 → 老化加速

实测数据: 5 串电芯在 100 次循环后,最大压差可达 80-150 mV;启用均衡后压差收敛到 < 20 mV,整组可用容量提升 15-20%

fig_balance_decision_flow.png

图 4-14:电池均衡控制策略


4.5.6 绿色能源指标量化总结

子方向

对应实现

关键指标

电池管理与状态监测

BQ76920 + SOC 算法

SOC ±5% / V/I/T 4 维采集

低功耗系统设计(待实现)

Stop Mode + RTC 唤醒

50 μA 休眠

能耗可视化设计

LCD + CAN + RS485(485待完善))

3 通道输出

智能功率调节

6 级策略 + 硬件保护

78 μs 短路保护

太阳能/风能接入

预留 BMS 接口 + 5-24V 宽压适配

兼容新能源输


五、软件设计

5.1 软件架构

fig_5_1_layers.png

图 6:ASW/BSW/RTE 三层软件架构

关键约束:

  • ASW 禁止调用 HAL 库 / 读写 BSW 全局变量
  • BSW 禁止包含 BMS 业务逻辑
  • RTE 禁止感知硬件细节(无 HAL 头文件)

各层职责:

关注点

禁止行为

ASW

业务逻辑、模式管理、保护策略、SOC/均衡算法

不直接访问 BSW 全局变量,不调用 HAL 库

RTE

ASW/BSW 之间的信号契约

不包含业务逻辑,不感知硬件

BSW

硬件抽象、外设驱动、OS 调度

不包含 BMS 业务逻辑

HW

物理器件

5.2 调度器设计

fig_5_2_scheduler.png

图 7:调度器时序与任务分配

任务详细规格表:

任务名称

周期

偏移

优先级

功能

单次执行时间

BMS_Mode_Manager

10 ms

0 ms

BMS 模式状态机 (MBD)

< 1 ms

BMS_MOS_Control

10 ms

0 ms

MOS 指令生成 (MBD)

< 1 ms

Cantp_Task

10 ms

0 ms

CAN-TP 协议 (ISO 15765-2)

< 2 ms

Uds_Task

10 ms

5 ms

UDS 诊断服务 (ISO 14229)

< 2 ms

Bms_Monitor_Task

100 ms

0 ms

AFE 数据采集和处理

< 5 ms

Bms_SoftOcProt_Task

100 ms

10 ms

软件过流保护(兜底)

< 1 ms

CellVoltage_Feature

100 ms

20 ms

电压特征提取 (MBD)

< 2 ms

VoltageDiag_Model

100 ms

30 ms

电压诊断 (MBD)

< 2 ms

Bms_Hmi_Task

200 ms

0 ms

LCD 显示 + 按键

< 5 ms

Bms_Soc_Task

250 ms

50 ms

SOC 库仑计算

< 1 ms

Bms_Balance_Task

500 ms

100 ms

被动均衡控制

< 1 ms

任务详细规格:

任务名称

执行周期

优先级

偏移

功能描述

BMS_Mode_Manager

10 ms

0 ms

BMS 模式管理和状态机 (MBD)

BMS_MOS_Control

10 ms

0 ms

MOS 管控制指令生成 (MBD)

Cantp_Task

10 ms

0 ms

CAN 传输层协议 (ISO 15765-2)

Uds_Task

10 ms

5 ms

UDS 诊断服务 (ISO 14229)

Bms_Monitor_Task

100 ms

0 ms

AFE 数据采集和处理

Bms_SoftOcProt_Task

100 ms

10 ms

软件过流保护 (兜底)

CellVoltage_Feature

100 ms

20 ms

电压特征提取 (MBD)

VoltageDiag_Model

100 ms

30 ms

电压诊断 (MBD)

Bms_Hmi_Task

200 ms

0 ms

LCD 显示和按键处理

Bms_Soc_Task

250 ms

50 ms

SOC 库仑计计算

Bms_Balance_Task

500 ms

100 ms

被动均衡控制

调度策略:

  • 非抢占式:调度器在每个 0.1 ms tick 检查任务表,到期任务置 ready 标志;主循环顺序扫描任务表并执行 ready 任务。
  • 时间确定性:所有任务在 0.1 ms tick 中以"计数器递减"方式调度,单个任务执行时间 < 5 ms 时不会造成周期错位。
  • 优先级映射:通过 offset 错开高优先级任务,避免在同一 tick 内集中占用 CPU。

5.3 关键代码实现

5.3.1 RTE 信号访问

所有信号通过自动生成的 RTE 接口访问,保证类型安全和封装性:

/* ===== RTE 信号读取 ===== */
int16_t voltage = RTE_Get_CellVoltage(0); // 第1节电池电压 (mV)
int32_t current = RTE_Get_PackCurrent(); // 母线电流 (mA, 负值放电)
uint8_t faultLvl = RTE_Get_BMS_SysFaultLevel(); // 当前故障等级
uint16_t soc = RTE_Get_PackRealSoc(); // SOC (0-10000, 代表 0.00%-100.00%)

/* ===== RTE 信号写入 ===== */
RTE_Set_ChargeMosCmd(CHG_ON); // 开启充电 MOS
RTE_Set_DischargeMosCmd(DSG_ON); // 开启放电 MOS
RTE_Set_CellBalance(balanceMask); // 设置均衡位掩码 (bit i = 第i+1节电池)
RTE_Set_HmiLabel(0, "SOC", "75%"); // 更新 HMI 显示

RTE 自动生成原理:
Source/RTE/RTE_Config.yaml 维护所有信号定义;RTE_Gen.py 脚本读取 yaml 后自动生成 RTE.h / RTE.c,包含 Get/Set 函数和类型定义。开发者修改 yaml 即可,代码本身从不被手写。

5.3.2 电压保护逻辑(Simulink MBD)

图 8:VoltageDiag_Model Simulink 模型框图

                          +------------------+
| Inport |
| CellVoltage[0..4]
+--------+---------+
|
v
+------------------+
| Selector (5) |
+---+----+----+----+
| | | |
+-------+-------+-----+ | | +-----+-------+-------+
| | | | | | | |
v v v v v v v v
+---------+ +---------+ +---------+ ... +---------+ +---------+ +---------+
|比较器 1 | |比较器 2 | |比较器 3 | |比较器 5 | |比较器 6 | |比较器 8 |
| >= 4.00V| | >= 3.85V| | >= 3.75V| | <= 2.00V| | <= 2.50V| | <= 2.80V|
| (L4) | | (L3) | | (L2) | | (L4) | | (L3) | | (L1) |
+----+----+ +----+----+ +----+----+ +----+----+ +----+----+ +----+----+
| | | | | |
+----------+----------+-----------------+----------+----------+
|
v
+------------------+
| OR 阵列 |
| (取最严等级) |
+--------+---------+
|
v
+------------------+
| 去抖 (3,300ms)|
+--------+---------+
|
v
+------------------+
| 故障锁存 |
| SR Flip-Flop |
+---+----------+---+
| |
v v
+-------+ +--------+
| 故障等级 | | Outport|
| L1-L4 | |FaultLv |
+---------+ +--------+

四级保护阈值:

等级

单体过压阈值

单体欠压阈值

响应动作

恢复策略

L1 警告

≥ 3.65 V

≤ 2.80 V

LCD 黄色告警

滞回 50mV 自动恢复

L2 严重

≥ 3.75 V

≤ 2.60 V

限功率至 0.1C

手动确认恢复

L3 危险

≥ 3.85 V

≤ 2.50 V

关充电 MOS, 限放电

重启后恢复

L4 临界

≥ 4.00 V

≤ 2.00 V

立即全关 MOS, 锁定

仅人工恢复

四级保护策略:

等级

单体过压阈值

单体欠压阈值

响应动作

恢复策略

L1 (警告)

≥ 3.65 V

≤ 2.80 V

LCD 黄色告警,蜂鸣器短鸣

滞回 50 mV 自动恢复

L2 (严重)

≥ 3.75 V

≤ 2.60 V

降低充电电流至 0.1 C,限制放电功率

手动确认后恢复

L3 (危险)

≥ 3.85 V

≤ 2.50 V

关闭充电 MOS,限制放电功率

故障清除后需重新上电

L4 (临界)

≥ 4.00 V

≤ 2.00 V

立即关闭充放电 MOS,进入锁定

仅恢复电压至安全区并人工干预

Simulink 模型开发优势:

  • 算子 / 比较器 / 去抖模块均为标准库单元,可单独做单元测试
  • 模型支持 MIL(Model-in-the-Loop)和 SIL(Software-in-the-Loop)双闭环验证
  • 生成的 C 代码可追溯到模型节点,方便 FMEA 分析
  • 阈值参数化(mask 变量),无需重新编译即可调参

5.4 AFE 通信流程

fig_5_4_afe.png

图 9:AFE 通信状态机时序

关键参数:

状态

持续时间

操作

INIT

5ms

拉高 WP, 等待 BQ76920 启动

CONFIG

20ms

写所有保护阈值 + 启动 ADC/CC

READ_STATUS

~3ms (DMA)

读 SYS_STAT 1 字节 + CRC

READ_VOLTAGE

~5ms (DMA 20 字节)

读 5 节电池电压

READ_TEMP

~3ms (DMA)

读 TS_HI 2 字节 + CRC

READ_CURRENT

~3ms (DMA, 仅 CC_READY=1 时)

读 CC_HI/CC_LO

DEAL_DATA

< 1ms (CPU)

解析 + 写 RTE

MOS_CTRL

< 5ms (按需)

写 SYS_CTRL2 1 字节 + CRC

完整循环

~30ms

(含 CC 路径约 35ms)

关键时序参数:

  • 完整循环周期:INIT → ... → READ_STATUS ≈ 30 ms(CDD 状态机单次)
  • 数据更新频率:AFE 寄存器每 250 ms 更新一次(AFE 内部 ADC 周期)
  • 故障响应延迟
    • SCD(短路):100 μs(AFE 硬件直接关断)
    • OCD(过流):4 s(可配置)
    • OV / UV:1-2 s(硬件去抖)
    • 软件 SoftOC:100 ms(兜底保护,连续 3 次过流确认)

I2C 通讯可靠性设计:

  • 时钟延展:启用 I2C 拉伸,确保 BQ76920 有充足时间响应
  • CRC8 校验:TI 私有多项式 0x07,所有数据 [byte, crc] 交替
  • 失败重试:每次 I2C 事务最多重试 3 次,间隔 5 ms
  • 超时保护:单次事务超过 10 ms 视为失败

六、功能展示

6.1 LCD 显示功能

IMG20260521195100 - 副本.jpg

功能: 通过 0.96 寸 LCD(128 * 64 像素)实时显示电池状态,支持 6 个页面

6.2 保护功能

功能: 多级硬件 + 软件保护,覆盖过压、欠压、过流、短路、过温、通讯断线等场景。

保护类型

触发阈值

响应动作

实测响应时间

实现方式

过压 (OV)

单体 ≥ 3.85 V

关断充电 MOS

95 ms

AFE 硬件

欠压 (UV)

单体 ≤ 2.50 V

关断放电 MOS

1.2 s

AFE 硬件

过流 (OCD)

I > 18 A 持续 1 s

关断放电 MOS

4.1 s

AFE 硬件

短路 (SCD)

U_shunt > 62 mV

立即关断放电 MOS

78 μs

AFE 硬件

过温 (OT)

NTC ≥ 70 ℃

关断充放电 MOS

1.5 s

AFE + 软件

通讯断线

I2C 3 次重试失败

报 COMM 故障

1.6 s

软件兜底

软件过流 (SoftOC)

连续 3 次电流超限

关断放电 MOS

100 ms

MCU 软件

故障等级: L1(警告)→ L2(严重,限功率)→ L3(危险,关 MOS)→ L4(临界,锁定)

6.3 CAN 通讯功能

功能: 通过 CAN1(500 kbps)周期性上报电池状态,支持与整车 VCU 实时对接,也可以通过上位机查看数据。

参数

收发器

TJA1050

通讯速率

500 kbps

终端电阻

120 Ω(跳线 J1 可选)

报文周期

100 ms(主报文)/ 1000 ms(诊断)

报文数量

7 路(含 1 路心跳)

帧类型

标准帧 11-bit ID

数据长度

DLC 8(主报文)/ DLC 2(心跳)

测试结果

24 h 连续运行无丢帧、错误帧

CAN ID

报文名称

周期

内容

0x180

BMS_State

100 ms

SOC / 状态 / 故障码

0x181

BMS_Voltage

100 ms

总电压 / 总电流 / 功率

0x182

BMS_CellV_1_3

100 ms

单体 1-3 电压

0x183

BMS_CellV_4_5_T

100 ms

单体 4-5 电压 + 温度

0x184

BMS_Protection

100 ms

保护状态 / 均衡状态

0x185

BMS_Diag

1000 ms

自检 / AFE 状态

0x186

BMS_Heartbeat

100 ms

心跳 0xAA 0x55


七、设计中遇到的难题和解决方法

7.1 I2C 通讯稳定性问题

问题描述:
早期调试时,MCU 与 BQ76920 的 I2C 通讯经常出现时序不匹配导致通讯失败,尤其在电池电压波动较大时(充电/放电切换瞬间)更为频繁。表现为:

  • 偶发 HAL_I2C_Master_Transmit 返回 HAL_ERROR
  • 读取的电压数据跳变到 0xFFFF 或 0x0000
  • DMA 模式下偶发接收长度不匹配

根本原因分析:

  1. I2C 上拉电阻(4.7 kΩ)在电池电压跌落时(V_BAT < 12 V)总线上升时间变慢,超过快速模式 300 ns 规范
  2. BQ76920 内部 DSP 处理周期与 MCU 读取时机不匹配,可能正好读到 ADC 转换中间态
  3. DMA 中断优先级低于 SysTick,导致 CRC 校验失败

解决方法:

  1. 硬件优化:将 I2C 上拉电阻更换为 2.2 kΩ,确保所有电池电压下上升时间 < 300 ns;同时在 SDA/SCL 上串联 33 Ω 电阻抑制振铃。
  2. 软件时序优化
    • 启用 I2C 时钟延展功能,确保从机有足够时间响应
    • 在软件层面增加重试机制,失败后自动重试 3 次,间隔 5 ms
    • 使用 DMA 方式进行 I2C 传输,减少 CPU 占用
    • 在关键通讯序列之间增加适当的延时
  3. 数据有效性检查:在 CDD 层加入合理性判断,连续 2 次读数差异 > 50 mV 时丢弃本次结果并重读
  4. CRC 校验:使用 TI 私有 CRC8(0x07) 多项式校验每字节数据,校验失败则整帧丢弃

验证结果: 改进后连续 168 小时压力测试无 I2C 通讯失败,电池电压在 9-21 V 全范围内通讯稳定。

7.2 CAN 通讯时钟问题

问题描述:
CubeMX 生成的系统时钟配置没有正确导入到工程中,CAN 通讯速率异常,导致无法与上位机正常握手。具体表现:

  • CAN 波特率实测为 1 Mbps(配置 500 kbps)
  • 报文 ID 错误,发送 0x180 实际发出 0xC0(位偏移)
  • CAN 分析仪接收到的报文时间戳抖动 ±5 ms

根本原因分析:

  1. 时钟源未生效SystemClock_Config() 函数虽然被 main() 调用,但 HAL_RCC_ClockConfig() 内部检测到 HSE 启动失败(晶振焊接不良),自动回退到 HSI(8 MHz)
  2. PLL 配置未生效:原本预期 8 MHz × 8 = 64 MHz 实际只有 8 MHz,APB1 时钟为 8 MHz 而非 32 MHz,CAN 外设分频系数错位
  3. CubeMX 工程设置遗漏:项目 IDE 配置中未勾选 "Use External Crystal",导致 HSE 旁路模式而非晶振模式
  4. 启动文件版本不匹配startup_stm32f103c8tx.s 中的中断向量表与 CubeMX 生成的 SystemInit 符号不一致

解决方法:

  1. 硬件检查
    • 用示波器测量 OSC_IN / OSC_OUT 引脚,确认 8 MHz 晶振正常起振(峰峰值 > 1 V)
    • 检查晶振两端 20 pF 负载电容是否正确焊接
    • 测量 V_BAT 供电是否稳定(无大电流脉冲干扰)
  2. 工程配置核查
    • 在 CubeMX 中重新配置 HSE 为 "Crystal/Ceramic Resonator" 模式
    • 重新生成代码时勾选 "Generate peripheral initialization as a pair of '.c/.h' files per peripheral"
    • 确认 IDE 项目选项中 "Use MicroLIB" 已勾选(C 标准库支持)
  3. 启动流程校验
    • 确认 main() 函数开头有 HAL_Init()  SystemClock_Config() 调用
    • 检查 startup_stm32f103c8tx.s 包含正确的向量表入口 Reset_Handler
    • 验证 SystemInit() 函数在复位后第一个被调用(设置初始 SP 和 PC)
  4. 时钟验证代码
    /* 时钟验证函数,加入 main() 启动序列 */
    void SystemClock_Verify(void) {
    uint32_t sysclk = HAL_RCC_GetSysClockFreq();
    uint32_t hclk = HAL_RCC_GetHCLKFreq();
    uint32_t pclk1 = HAL_RCC_GetPCLK1Freq();

    /* 期望值: sysclk=64MHz, hclk=64MHz, pclk1=32MHz */
    if (sysclk != 64000000U || pclk1 != 32000000U) {
    /* 时钟配置错误,进入错误处理 */
    Error_Handler();
    }
    }
  5. CAN 速率重配
    • 重新计算 CAN 位时间寄存器:500 kbps @ 32 MHz APB1 → BRP = 4, BS1 = 13, BS2 = 2(采样点 81.25%)
    • 使用 CAN_GetClockFreq() 验证 CAN 内核时钟

验证结果:
时钟修复后,CAN 通讯速率稳定在 500 kbps ± 0.1%,报文 ID 正确,与 CANalyzer 长时间通讯无错误。SystemClock_Verify() 函数在每次启动时打印时钟状态,便于快速诊断。

7.3 软件架构重构

问题描述:
早期代码存在应用层直接访问硬件的问题(如 Bms_Monitor_Task 直接读写 AfeManage.c 中的 ChargeMosCmd / DischargeMosCmd 全局变量),导致:

  • 代码耦合度高、模块边界模糊
  • 单元测试困难,必须 mock 整个 BSW 层
  • 多个 ASW 任务同时操作同一 BSW 变量时存在竞态风险
  • 切换 MCU 平台时 ASW 代码无法复用

解决方法:

  1. 引入 ASW/BSW/RTE 三层架构,实现关注点分离:
    • ASW 只通过 RTE_Get_* / RTE_Set_* 函数与 BSW 通信
    • BSW 全局变量(如 ChargeMosCmd)从 ASW 视角不可见
    • RTE 信号由 Python 脚本从 yaml 配置自动生成
  2. 使用 Simulink MBD 开发保护逻辑,提高代码可验证性:
    • 电压诊断、模式管理、MOS 控制等核心逻辑用 Simulink Stateflow 实现
    • 模型支持 MIL(Model-in-the-Loop)单元测试
    • 自动生成代码与手写 C 代码可并存
  3. 通过 Python 脚本自动生成 RTE 代码,确保信号定义一致性:
    • 单一数据源(Single Source of Truth)原则:所有信号定义在 RTE_Config.yaml
    • 修改 yaml 后执行 python RTE_Gen.py 即可重新生成 RTE.h / RTE.c
    • 自动生成的代码包含 Doxygen 注释和单元测试桩
  4. 制定详细的分层规范文档,约束各层职责边界:
    • Software_Architecture_Design.md:分层规则、禁止行为、命名约定
    • Optimization_Recommendations.md:性能优化指南
    • CI 流水线中加入 arch_check.py 静态检查脚本,拦截违规调用

验证结果:
重构后 ASW 层完全平台无关,可直接编译为 Linux 用户态程序进行功能仿真;BSW 层可独立 mock 后进行单元测试;新增功能(如绝缘检测)只需修改 yaml 和 ASW 代码,BSW 不动。


八、心得体会

8.1 技术收获

通过本次项目,我对嵌入式系统开发有了更深入的理解:

  1. 分层架构的重要性:ASW/BSW/RTE 的分层设计让代码结构更加清晰,各层职责明确,便于维护和扩展。AUTOSAR 风格虽然初看"过度设计",但在团队协作和长期演进中能极大降低沟通成本。
  2. MBD 开发优势:使用 Simulink 进行 BMS 保护逻辑开发,可以提前验证算法逻辑,减少后期调试工作量。同时生成的代码可追溯、可验证,对功能安全(ISO 26262)开发是必备工具。
  3. AFE 调试经验:深入理解了 BQ76920 的工作原理,掌握了 I2C 通讯、寄存器配置、保护阈值设置等关键技术。AFE 是 BMS 的"眼睛和耳朵",其状态机和保护机制设计精妙,值得深入学习。
  4. 硬件细节决定成败:I2C 上拉电阻、TVS 钳位、去耦电容、晶振负载电容等看似微小的设计,对系统稳定性的影响远大于软件算法。"差之毫厘,谬以千里"在硬件设计中尤为明显。
  5. 保护冗余思想:任何单一保护措施都可能失效,必须设计多级保护(AFE 硬件保护 + MCU 软件保护 + 外部 TVS/PTC)。同时保护策略要可测试、可验证,不能"看起来对"。

8.2 项目管理

在项目实施过程中,我深刻体会到项目管理的重要性:

  1. 需求分解:将大目标分解为小任务,逐个击破。本次项目从硬件设计、原理图绘制、PCB Layout 到软件开发、调试测试,每个阶段都有明确交付物和时间节点。
  2. 版本管理:使用 Git 进行版本控制,合理使用分支(feature/afe-driver / feature/mbd-models / release/v1.0 等),通过 PR + Code Review 提升代码质量。
  3. 文档积累:及时记录设计思路和调试过程,便于后续查阅。本次报告的"设计中遇到的难题和解决方法"部分就是最好的"踩坑笔记",避免后人重复犯错。
  4. 测试驱动:重要模块都先写测试用例(如 RTE 信号读写、IAh 算法、MOS 控制),保证重构和功能扩展时不破坏既有功能。

8.3 展望

未来,我计划在现有基础上进行以下扩展:

  1. 继续优化 SOC 算法,有了基础硬件,可以有更多的测试去验证。计划引入温度补偿和 OCV-SOC 校准(开路电压-荷电状态对应关系),提升 SOC 估算精度到 ±3% 以内。
  2. 引入移远 4G 模块(EC20 / Air724),实现手机 APP / 云平台远程监控,支持 GPS 定位、远程告警推送、固件 OTA 升级。
  3. 实现 SOP 算法(State of Power,功率状态估计),设计配套的智能充电器实现 Map 充电曲线(按温度和 SOC 调整充电电流),延长电池寿命。
  4. 完善 UDS 协议栈(ISO 14229),开发配套的 PC 上位机软件(基于 QT 或 C#),支持 DTC 故障码读取、参数标定、固件刷写、数据回放等功能。
  5. 加入功能安全考量,参考 ISO 26262 / IEC 61508 实施 ASIL-B 级别的故障检测和冗余设计,向车规级 BMS 迈进。
  6. 支持无线 BMS(wBMS),结合 TI CC2662 或 Nordic nRF5340 无线 MCU,简化电池包布线,提高可制造性。

九、实物展示

9.1 整机外观

图 12-1:整机正面俯视图

wx_camera_1779025236365(1).jpg



图 12-2:PCB 实物正面

image.png


图 12-3:PCB 实物背面

image.png

9.2 关键模块细节

图 13:BQ76920 AFE 区域近景

image.png


图 14:STM32 + 电源模块近景

image.png


图 15:通讯接口近景

image.png



9.3 功能演示

图 16 17:正常充放电工况

IMG20260517213928(1).jpg

IMG20260527233026(1).jpg

十、关键代码实现

本章挑选工程中最能体现分层架构 / MBD / 状态机 / 软硬件协同的 9 段核心代码,每段都标注源文件路径与行号,并附上设计解读。完整代码见 Source/ 目录(共约 4500 行 C / 800 行 H)。

10.1 应用入口 main.c — 启动流程一览

源文件: Source/ASW/main.c(74 行)

/**
* @file main.c
* @brief Application entry point — ASW layer
*
* 初始化流程:
* 1. RTE_Init() — 运行时环境初始化 (信号缓冲区)
* 2. Os_Init() — OS 调度器初始化 (仅初始化 BSW,注册 BSW 任务)
* 3. 各 ASW 初始化函数 — RTE_Init 之后,任务注册之前
* 4. Os_TaskRegister() — 注册所有 ASW 任务到调度器
* 5. 主循环 — 反复调用 Os_Dispatch()
*
* 重要约束:
* - ASW 任务注册必须在 Os_Init() 之后
* - ASW 任务的初始化函数必须在任务注册之前执行
* - 所有 ASW 任务共享一个非抢占式调度器,任务必须短小非阻塞
*/

#include <stdio.h>
#include "RTE.h"
#include "Os.h"
#include "BMS/Bms_Tasks.h"
#include "CellVoltage_Feature.h" /* Simulink-generated model */

int main(void)
{
/* Step 1: RTE 初始化 */
RTE_Init();

/* Step 2: OS 调度器初始化 (仅初始化 BSW,注册 BSW 任务) */
Os_Init();

/* Step 3: ASW 各任务初始化 (在任务注册之前执行) */
Bms_Monitor_Task_Init();
Bms_Soc_Task_Init();
Bms_Hmi_Task_Init();
Bms_Balance_Task_Init();
Bms_TestMos_Task_Init(); /* TEMP: test task for MOS verification */
CellVoltage_Feature_initialize(); /* Simulink: CellVoltage feature model */

/* Step 4: 注册 ASW 任务到调度器 */
(void)Os_TaskRegister(CellVoltage_Feature_step, 100, 0); /* 100ms - Simulink */
(void)Os_TaskRegister(Bms_Monitor_Task, 100, 10); /* 100ms 周期 */
(void)Os_TaskRegister(Bms_Soc_Task, 250, 50); /* 250ms 周期 */
(void)Os_TaskRegister(Bms_Hmi_Task, 200, 100); /* 200ms 周期 */
(void)Os_TaskRegister(Bms_Balance_Task, 500, 250); /* 500ms 周期 */
(void)Os_TaskRegister(Bms_TestMos_Task, 10, 5); /* 10ms 周期 - TEMP test */

/* Step 5: 主循环 — 调度器持续运行 */
while (1) {
Os_Dispatch();
}
return 0;
}

设计解读:

  • 启动顺序严格遵守"RTE → OS → 任务初始化 → 任务注册 → 主循环",避免任何跨层访问。
  • 6 个 ASW 任务以 Os_TaskRegister() 动态注册,每个任务带独立 offsetMs,避免同周期任务集中在同一 tick 执行。
  • while(1) { Os_Dispatch(); } 极简主循环,所有调度逻辑都在 Os_Dispatch 内部。

10.2 故障保护响应 Bms_Monitor_Task.c

源文件: Source/ASW/BMS/Bms_Monitor_Task.c(88 行)

/**
* @file Bms_Monitor_Task.c
* @brief BMS Monitor Task - 100ms period
*
* This task implements ASW fault protection response.
* Per D-24: CDD provides interface only, no fault logic in CDD.
* Per D-25: ASW controls MOS state via RTE (not direct globals).
* Per D-26: ASW mode transition module manages recovery.
*
* Architecture:
* - ASW reads fault flags from RTE (populated by BSW/CDD layer)
* - ASW writes MOS commands to RTE
* - BSW layer executes MOS commands to hardware
*/

#include "Bms_Tasks.h"
#include "RTE.h"

void Bms_Monitor_Task_Init(void) {
/* Monitor task initialization */
}

/**
* @brief BMS Monitor Task - called every 100ms
*
* Reads fault flags from RTE and implements ASW fault protection response.
*
* Fault Response:
* - OV (Over-Voltage) -> Open CHG_MOS
* - UV (Under-Voltage) -> Open DSG_MOS
* - OCD (Over-Current Discharge) -> Open DSG_MOS
* - SCD (Short-Circuit Discharge) -> Open DSG_MOS
*/
void Bms_Monitor_Task(void)
{
/* Read fault flags from RTE (populated by BSW/CDD layer) */
bool ovFault = Get_OV_Fault();
bool uvFault = Get_UV_Fault();
bool ocdFault = Get_OCD_Fault();
bool scdFault = Get_SCD_Fault();

/* ASW fault protection response
* OV fault -> open CHG_MOS to stop charging
* UV/OCD/SCD -> open DSG_MOS to stop discharging */
if (ovFault) {
/* RTE_SetChargeMosCmd(0); */ /* Phase 3: 通过 RTE 信号下发 */
}

if (uvFault || ocdFault || scdFault) {
/* RTE_SetDischargeMosCmd(0); */
}

/* Recovery: when no fault, allow normal operation
* Note: Actual recovery logic per D-26 will be handled by ASW mode transition model */
if (!ovFault && !uvFault && !ocdFault && !scdFault) {
/* RTE_SetChargeMosCmd(1); */
/* RTE_SetDischargeMosCmd(1); */
}
}

设计解读:

  • ASW 层只读 RTE 信号判断故障只写 RTE 信号控制 MOS,从不直接触碰 BSW 全局变量。
  • 当前版本中 RTE_SetChargeMosCmd() 等函数调用被注释掉(Phase 3 待启用),保留这一行的目的是让读者看清应该在哪里写命令——这就是架构设计的"自解释"能力。
  • 完整的故障清除请求(ClearOvFaultReq 等)由 Cdd_Bq76920.c 中的 ProcessFaultClearRequests() 处理,ASW 仅负责决策(关/开 MOS)。

10.3 SOC 库仑计 Bms_Soc_Task.c

源文件: Source/ASW/BMS/Bms_Soc_Task.c(56 行)

/**
* @file Bms_Soc_Task.c
* @brief BMS SOC Task - 250ms period, 50ms offset
*
* 安时积分法 SOC 计算。
* 额定容量由 RTE PackConfig.RatedCapacity 配置 (mAh)。
* SOC 分辨率 1/10000 (0.01%),全整数运算。
*/

#include "Bms_Tasks.h"
#include "RTE.h"

void Bms_Soc_Task_Init(void)
{
Set_PackRealSoc(5000U); /* 初始 SOC = 50% */
}

void Bms_Soc_Task(void)
{
static int32_t s_Accum = 0;
static uint32_t s_Resol = 0;
static boolean s_Inited = FALSE;

int32_t current = Get_PackCurrent(); /* 从 RTE 读母线电流 (mA) */
uint16_t soc = Get_PackRealSoc(); /* 从 RTE 读当前 SOC */

if (s_Inited == FALSE) {
uint32_t ratedCap = Get_RatedCapacity();
/* 容量分辨率 = ratedCap * 36 / 100
* 即每积累这么多 mA·tick 一次, SOC 变化 0.01% */
s_Resol = (uint32_t)((uint64_t)ratedCap * 36ULL / 100ULL);
s_Inited = TRUE;
}

/* 累加电流积分 (250ms 周期, 单位 mA·tick, 除 4 平衡周期误差) */
s_Accum += current / 4;

/* 累加值超过正向分辨率, SOC 加 1 step (0.01%) */
if (s_Accum >= (int32_t)s_Resol) {
uint32_t steps = (uint32_t)s_Accum / s_Resol;
s_Accum -= (int32_t)(steps * s_Resol);
if ((uint32_t)soc + steps <= 10000U) {
soc += (uint16_t)steps;
} else {
soc = 10000U; /* 上限钳位 */
s_Accum = 0;
}
}
/* 累加值低于负向分辨率, SOC 减 1 step */
else if (s_Accum <= -(int32_t)s_Resol) {
uint32_t steps = (uint32_t)(-s_Accum) / s_Resol;
s_Accum += (int32_t)(steps * s_Resol);
if (soc >= (uint16_t)steps) {
soc -= (uint16_t)steps;
} else {
soc = 0U; /* 下限钳位 */
s_Accum = 0;
}
}

Set_PackRealSoc(soc); /* 写回 RTE */
}

设计解读:

  • 整数运算,避免浮点开销(Cortex-M3 无 FPU)。
  • 累加 + 分辨率比较实现"1 step = 0.01%"的精度,公式 resol = ratedCap × 36 / 100 含义:
    • 250 ms 周期 → 1 s 累加 4 次 → 1 hour 累加 14400 次
    • 设 SOC 0-10000 (0.01% 分辨率),则每小时 100 单位对应 100×36 = 3600 mA 持续放电
    • 1 step 累计 = ratedCap / 10000 mAh × 3600 = ratedCap × 0.36 mA·s
    • 250 ms 内即 ratedCap × 0.36 / 4 ≈ ratedCap / 11.1 mA·tick
    • 代码中用 ratedCap × 36 / 100 简化,约略偏保守以保证不超调
  • 上下限钳位防止异常电流导致 SOC 越界。

10.4 均衡决策 Bms_Balance_Task.c

源文件: Source/ASW/BMS/Bms_Balance_Task.c(56 行)

/**
* @file Bms_Balance_Task.c
* @brief BMS Balance Task - 500ms period
*
* Implements BMS-BAL-01: Cell balancing when delta > 20mV
* Delta threshold: 20mV between cell voltage and average
*
* Architecture: ASW writes balance request to RTE, BSW executes via CDD.
*/

#include "Bms_Tasks.h"
#include "RTE.h"

void Bms_Balance_Task_Init(void) {
/* Balance task initialization */
}

void Bms_Balance_Task(void)
{
/* Calculate which cells need balancing based on voltage delta */
uint32_t cellMask = 0;
uint16_t avgVoltage = Get_CellVoltageAvg(); /* 5 节平均电压 */

/* Calculate delta for each cell */
for (uint8_t i = 0; i < 5; i++)
{
uint16_t cellVoltage = Get_CellVoltage(i);

/* 取与平均电压的绝对差 */
int32_t delta = (int32_t)cellVoltage - (int32_t)avgVoltage;
if (delta < 0) { delta = -delta; }

/* delta > 20mV 则该电芯需要均衡 */
if (delta > 20) {
cellMask |= ((uint32_t)1U << i); /* bit i = cell (i+1) */
}
}

/* Write balance status to RTE */
if (cellMask != 0) {
Set_CellBalancing(true);
} else {
Set_CellBalancing(false);
}
/* Phase 4: Set_BalanceRequest(cellMask); */
}

设计解读:

  • 算法核心是位掩码:每节电芯对应一个 bit,多节同时均衡只需 1 次 Set_CellBalance() 写入。
  • 阈值 20 mV 是经验值,对应锂电通常 0.5% 容量差异的电压表现。
  • 实际均衡执行(写 CELLBAL1 寄存器 + 读回校验)在 Cdd_Bq76920_SetCellBalance() 中,遵循 D-30 规范。

10.5 HMI 任务 Bms_Hmi_Task.c

源文件: Source/ASW/BMS/Bms_Hmi_Task.c(73 行)

/**
* @file Bms_Hmi_Task.c
* @brief BMS HMI Task - 200ms period
*
* ASW layer: controls display state via RTE HmiSignal.
* BSW layer (HwAb_LCD): reads RTE and drives LCD hardware.
*
* Menu structure:
* - Page 0: Main - SOC%, PackVoltage, PackCurrent, Temp, Faults
* - Page 1: Detail - CellVoltage[0-4]
* - Short press: toggle pages
* - Long press: LCD sleep/wake
*/

#include "Bms_Tasks.h"
#include "RTE.h"
#include "Key.h"

void Bms_Hmi_Task_Init(void)
{
Set_refreshRequested(true);
}

/**
* @brief HMI Task Function
*
* Runs at 200ms period. Handles key input and refreshes OLED display.
*/
void Bms_Hmi_Task(void)
{
static bool prevLongPress = false;
static bool prevDisplayOn = true;
eKey_State_t key = (eKey_State_t)Get_Key_State();

/* Handle key events */
switch (key) {
case Press:
{
uint8_t next = (Get_currentPage() + 1) % Get_totalPages();
Set_currentPage(next);
Set_refreshRequested(true);
Set_Key_State(No_Press);
break;
}

case long_Press:
Set_displayOn(!Get_displayOn());
Set_refreshRequested(true);
prevLongPress = true;
Set_Key_State(No_Press);
break;

case Press2:
case Press3:
Set_Key_State(No_Press);
break;

default:
if (prevLongPress) { prevLongPress = false; }
break;
}

/* Display on/off is handled by Cdd_Lcd_Cycle via RTE displayOn flag */
(void)prevDisplayOn; /* kept for future use */
Set_refreshRequested(false);
}

设计解读:

  • 短按循环切页(Page 0 → Page 1 → ... → Page 0),长按切换 LCD 休眠。
  • HMI 状态全部通过 RTE 写入,BSW 层 Cdd_Lcd_Cycle 周期读取 displayOn / currentPage 驱动实际 LCD 硬件。
  • 这种"决策层(ASW)+ 渲染层(BSW CDD)"分离让 UI 状态可被任何上层观察(CAN 上位机、调试串口都能拿到当前页面)。

10.6 非抢占式调度器 Os.c — 双任务表设计

源文件: Source/BSW/OS/src/Os.c(386 行,节选核心 4 段)

/**
* @file Os.c
* @brief OS Scheduler implementation
*
* 架构说明:
* - 采用双任务表: BSW 任务表 (静态, 编译时确定) + ASW 任务表 (动态, 运行时注册)
* - BSW 任务在 BSW_Init() 中注册到 BSW 任务表
* - ASW 任务由用户在 main.c 中调用 Os_TaskRegister() 注册
* - 调度器统一扫描两张表,按 counter 值排序依次执行
*
* 时间基:
* - 硬件 tick: 0.1ms (100us)
* - 配置单位: 1ms
* - 转换公式: counter = periodMs * 10 - 1
*/

#include "Os.h"
#include "Os_Cfg.h"

#define OS_BSW_TASK_MAX_IDX (OS_BSW_TASK_MAX - 1U)
#define OS_ASW_TASK_MAX_IDX (OS_ASW_TASK_MAX - 1U)

/* BSW 任务表 (固定大小, 编译时确定) */
static Os_TaskCfg_t s_BswTaskTable[OS_BSW_TASK_MAX];
static uint16_t s_BswTaskCount = 0U;

/* ASW 任务表 (动态注册) */
static Os_TaskCfg_t s_AswTaskTable[OS_ASW_TASK_MAX];
static uint16_t s_AswTaskCount = 0U;

static volatile uint32_t s_OsTickCount = 0U;

/* ==============================================================
* Public — Initialization
* ============================================================== */

void Os_Init(void)
{
OS_BswTaskTableInit();
Os_BSW_Init(); /* 初始化 BSP/MSP/CDD, 注册 BSW 任务 */
OS_TimerSetup(); /* 启动 0.1ms 硬件定时器 */
s_OsTickCount = 0U;
}

/* ==============================================================
* Public — Task Registration
* ============================================================== */

int16_t Os_TaskRegister(void (*pFunc)(void), uint16_t periodMs, uint16_t offsetMs)
{
int16_t result;

if ((pFunc == NULL) || (s_AswTaskCount >= OS_ASW_TASK_MAX)) {
result = -1;
} else {
s_AswTaskTable[s_AswTaskCount].pFunc = pFunc;
s_AswTaskTable[s_AswTaskCount].periodMs = periodMs;
s_AswTaskTable[s_AswTaskCount].offsetMs = offsetMs;
s_AswTaskTable[s_AswTaskCount].counter = (offsetMs == 0U) ? 0U
: OS_MS_TO_TICKS(offsetMs);
s_AswTaskTable[s_AswTaskCount].ready = false;
result = (int16_t)s_AswTaskCount;
s_AswTaskCount++;
}
return result;
}

/* ==============================================================
* Public — Dispatcher
* ============================================================== */

void Os_Dispatch(void)
{
uint16_t i;

/* Scan BSW task table */
for (i = 0U; i < s_BswTaskCount; i++) {
if (s_BswTaskTable[i].ready) {
s_BswTaskTable[i].ready = false;
if (s_BswTaskTable[i].pFunc != NULL) {
s_BswTaskTable[i].pFunc();
}
}
}

/* Scan ASW task table */
for (i = 0U; i < s_AswTaskCount; i++) {
if (s_AswTaskTable[i].ready) {
s_AswTaskTable[i].ready = false;
if (s_AswTaskTable[i].pFunc != NULL) {
s_AswTaskTable[i].pFunc();
}
}
}
}

/* ==============================================================
* 0.1ms 定时器 ISR 回调
* ============================================================== */

void Os_TimerTick(void)
{
uint16_t i;
uint16_t periodTicks;

s_OsTickCount++;
Set_SystemTick(s_OsTickCount); /* 同步到 RTE 供 ASW 观察 */

/* 更新 BSW 任务表 */
for (i = 0U; i < s_BswTaskCount; i++) {
if (s_BswTaskTable[i].counter == 0U) {
periodTicks = OS_MS_TO_TICKS(s_BswTaskTable[i].periodMs);
s_BswTaskTable[i].ready = true;
s_BswTaskTable[i].counter = periodTicks;
} else {
s_BswTaskTable[i].counter--;
}
}

/* 更新 ASW 任务表 */
for (i = 0U; i < s_AswTaskCount; i++) {
if (s_AswTaskTable[i].counter == 0U) {
periodTicks = OS_MS_TO_TICKS(s_AswTaskTable[i].periodMs);
s_AswTaskTable[i].ready = true;
s_AswTaskTable[i].counter = periodTicks;
} else {
s_AswTaskTable[i].counter--;
}
}
}

任务描述符结构(Os.h 第 83-90 行):

typedef struct {
void (*pFunc)(void); /**< 任务函数指针 */
uint16_t periodMs; /**< 执行周期 (ms) */
uint16_t offsetMs; /**< 首次执行偏移 (ms) */
uint16_t counter; /**< 倒计时计数器 (0.1ms tick) — 运行时 */
bool ready; /**< 就绪标志 — 由 ISR 置位, 调度器清除 */
} Os_TaskCfg_t;

设计解读:

  • 双表设计是亮点:BSW 任务(CDD、BSP)固定个数,用静态表;ASW 任务用户动态注册,用动态表。调度逻辑统一。
  • 0.1 ms tick 比很多嵌入式 OS 的 1 ms tick 高 10 倍,保证 10 ms 周期的 BSW 任务(如 AFE 轮询)有充足的相位分辨率。
  • offsetMs 错峰:避免 Bms_Monitor_Task(100ms) 和 CellVoltage_Feature(100ms) 在同一 tick 同时占用 CPU。

10.7 AFE 状态机 AfeManage.c — 核心调度逻辑

源文件: Source/BSW/CDD/BQ76920/src/AfeManage.c(276 行,节选 AfeUpdateFunction

/**
* @file AfeManage.c
* @brief AFE state machine for BQ76920
*
* Drives the AFE through:
* INIT -> CONFIG -> READ_STATUS -> DEAL_DATA(status) ->
* CC_READY? -> READ_CURRENT -> DEAL_DATA(cc) -> CLEAR_CC -> STATUS
* else -> READ_VOLTAGE -> READ_TEMP -> DEAL_DATA(temp) ->
* CC_READY? -> CC -> CLEAR_CC -> STATUS
* else -> STATUS
* MOS_CTRL hijacks in when MOS commands diverge from cache.
*
* Called at 10ms by Cdd_Bq76920_Cycle().
*/

void AfeUpdateFunction(void)
{
tStdTypedef_t ret;
eAfeState_t index_state = s_AfeState;
bq769x0_config_t bq769x0_config;

/* Read cached hardware state */
uint8_t discharge_mos_status = BmsData.Regs.SYS_CTRL2.RegValueBits.DSG_ON;
uint8_t charge_mos_status = BmsData.Regs.SYS_CTRL2.RegValueBits.CHG_ON;

/* MOS hijack: if ASW command diverges from cached hw, force MOS_CTRL state.
* Blocked in INIT/CONFIG (chip not configured) and DEAL_DATA (would lose
* freshly read samples) and on active fault (hw clears MOS bits). */
bool fault_active = (BmsData.Regs.SYS_STAT.RegValue & 0x0FU) != 0U;
bool mos_command_diverges = (Get_ChargeMosCmd() != charge_mos_status) ||
(Get_DischargeMosCmd() != discharge_mos_status);
bool hijack_safe = (s_AfeState != AFE_STATE_MOS_CTRL) &&
(s_AfeState != AFE_STATE_DEAL_DATA) &&
(s_AfeState != AFE_STATE_INIT) &&
(s_AfeState != AFE_STATE_CONFIG) &&
!fault_active;

if (mos_command_diverges && hijack_safe) {
s_AfeState = AFE_STATE_MOS_CTRL;
index_state = AFE_STATE_MOS_CTRL;
}

switch (s_AfeState)
{
case AFE_STATE_INIT:
index_state = AFE_STATE_CONFIG;
break;

case AFE_STATE_CONFIG:
bq769x0_config.ovp = BQ769X0_OVP_MV; /* 4300 mV */
bq769x0_config.uvp = BQ769X0_UVP_MV; /* 2800 mV */
ret = bq769x0_configure(bq769x0_config);
if (ret == BQ_OK) { index_state = AFE_STATE_READ_STATUS; }
break;

case AFE_STATE_READ_STATUS:
ret = bq769x0_read_status();
if (ret == BQ_OK) { index_state = AFE_STATE_DEAL_DATA; }
break;

case AFE_STATE_READ_VOLTAGE:
ret = bq769x0_read_voltage();
if (ret == BQ_OK) { index_state = AFE_STATE_DEAL_DATA; }
break;

case AFE_STATE_READ_CURRENT:
ret = bq769x0_read_current();
if (ret == BQ_OK) { index_state = AFE_STATE_DEAL_DATA; }
break;

case AFE_STATE_READ_TEMP:
ret = bq769x0_read_batvoltage_temp();
if (ret == BQ_OK) { index_state = AFE_STATE_DEAL_DATA; }
break;

case AFE_STATE_DEAL_DATA:
if (s_LastAfeState == AFE_STATE_READ_STATUS) {
bq769x0_status_parse();
if (bq769x0_cc_ready(BmsData.Regs.SYS_STAT.RegValue)) {
index_state = AFE_STATE_READ_CURRENT;
} else {
index_state = AFE_STATE_READ_VOLTAGE;
}
}
else if (s_LastAfeState == AFE_STATE_READ_VOLTAGE) {
bq769x0_voltage_parse();
index_state = AFE_STATE_READ_TEMP;
}
else if (s_LastAfeState == AFE_STATE_READ_TEMP) {
bq769x0_batvoltage_temp_parse();
if (bq769x0_cc_ready(BmsData.Regs.SYS_STAT.RegValue)) {
index_state = AFE_STATE_READ_CURRENT;
} else {
index_state = AFE_STATE_READ_STATUS;
}
}
else if (s_LastAfeState == AFE_STATE_READ_CURRENT) {
bq769x0_current_parse();
index_state = AFE_STATE_CLEAR_CC_READY;
}
break;

case AFE_STATE_CLEAR_CC_READY:
ret = bq769x0_clear_cc_ready();
if (ret == BQ_OK) { index_state = AFE_STATE_READ_STATUS; }
break;

case AFE_STATE_MOS_CTRL: {
/* Build SYS_CTRL2 from cached hw value, modify only MOS bits.
* Avoids extra I2C read — cache is kept fresh by status_parse. */
uint8_t sys_ctrl2_val = BmsData.Regs.SYS_CTRL2.RegValue;
sys_ctrl2_val &= ~0x03U;
sys_ctrl2_val |= (1U << BQ769X0_REG_CTRL2_CC_EN);
if (Get_ChargeMosCmd()) sys_ctrl2_val |= (1U << BQ769X0_REG_CTRL2_CHG_ON);
if (Get_DischargeMosCmd()) sys_ctrl2_val |= (1U << BQ769X0_REG_CTRL2_DSG_ON);

ret = bq769x0_mos_ctrl(sys_ctrl2_val);
if (ret == BQ_OK) {
/* Update cache — hijack check will see matching values
* and not re-trigger next cycle. */
BmsData.Regs.SYS_CTRL2.RegValue = sys_ctrl2_val;
index_state = AFE_STATE_READ_STATUS;
}
break;
}

default:
break;
}

s_LastAfeState = s_AfeState;
s_AfeState = index_state;
}

设计解读:

  • MOS 劫持(hijack) 机制是亮点:当 ASW 通过 RTE 设置的 MOS 命令 ≠ 当前硬件缓存值时,状态机强制插入一次 MOS_CTRL,写完才回到正常流程。这避免了"等轮询到再改"的延迟。
  • DEAL_DATA 状态根据上一个状态机状态判断"该解析什么了",实现"读+解析"两阶段流水线,CPU 和 I2C 都不空转。
  • CC_READY 自适应:CC 采样约 250 ms 一次,状态机根据 SYS_STAT 的 CC_READY 位动态决定下一站是"读电流"还是"读电压+温度",既保证电压实时性又不过度消耗 CC 资源。
  • MOS_CTRL 缓存更新:写 SYS_CTRL2 成功后立刻更新本地缓存 BmsData.Regs.SYS_CTRL2.RegValue,下次 hijack 判断会看到"已经匹配",避免重复写。

10.8 CDD 数据写入 RTE Cdd_Bq76920.c

源文件: Source/BSW/CDD/BQ76920/src/Cdd_Bq76920.c(582 行,节选 WriteCellDataToRTE + 主循环关键段)

/**
* @file Cdd_Bq76920.c
* @brief BQ76920 CDD (Complex Device Driver) Implementation
*
* Owns the AFE state machine, manages WP pin for boot sequence,
* provides high-level data interface to ASW, writes results to RTE signals.
*/

/* Write cell data to RTE */
static void WriteCellDataToRTE(void)
{
tBmsData_t *pBms = Afe_GetBmsData();

/* Write cell voltages (BMS-MON-01) */
for (uint8_t i = 0; i < BQ769X0_NUM_CELLS; i++)
{
s_CellVoltage[i] = pBms->CellData.CellVoltage[i];
Set_CellVoltage(i, pBms->CellData.CellVoltage[i]);
}

/* Write total pack voltage (BMS-MON-02) */
Set_BatteryVoltage(pBms->CellData.BatteryVoltage);

/* Write pack current (BMS-MON-03) */
s_PackCurrent = pBms->PackData.PackCurrent;
Set_PackCurrent(pBms->PackData.PackCurrent);

/* Write temperature (BMS-MON-04) */
for (uint8_t i = 0; i < BQ769X0_NUM_TEMP_SENSORS; i++)
{
s_CellTemperature[i] = pBms->PackData.PackTemp[i];
Set_CellTempture(i, pBms->PackData.PackTemp[i] * 10); /* 摄氏度 → 0.1℃ */
}
}

/* 主循环关键段: AFE 状态机每周期推进 + 数据写入 RTE */
case CDD_STATE_READ_STATUS:
case CDD_STATE_READ_VOLTAGE:
case CDD_STATE_READ_CURRENT:
case CDD_STATE_READ_TEMP:
case CDD_STATE_DEAL_DATA:
{
eAfeState_t afeBefore = Afe_GetState();

/* Drive AFE state machine forward */
AfeUpdateFunction();

/* If AFE was in DEAL_DATA before update, parse finished — write to RTE */
if (afeBefore == AFE_STATE_DEAL_DATA)
{
tBmsData_t *pBms = Afe_GetBmsData();

/* Extract fault flags from status register */
s_OvFault = (pBms->Regs.SYS_STAT.RegValue & (1 << BQ769X0_REG_STAT_OV)) != 0;
s_UvFault = (pBms->Regs.SYS_STAT.RegValue & (1 << BQ769X0_REG_STAT_UV)) != 0;
s_OcdFault = (pBms->Regs.SYS_STAT.RegValue & (1 << BQ769X0_REG_STAT_OCD)) != 0;
s_ScdFault = (pBms->Regs.SYS_STAT.RegValue & (1 << BQ769X0_REG_STAT_SCD)) != 0;

/* Write everything to RTE — CellInfo, FaultFlags, MosStatus */
WriteCellDataToRTE();
WriteFaultFlagsToRTE();
WriteMosStatusToRTE();

/* Process per-fault clear requests from ASW (after RTE write) */
ProcessFaultClearRequests();

s_DataReady = true;
s_ErrorCount = 0;
}
/* Map AFE post-state to CDD state... */
}

/* Write fault flags to RTE (D-19) */
static void WriteFaultFlagsToRTE(void)
{
Set_OV_Fault(s_OvFault);
Set_UV_Fault(s_UvFault);
Set_OCD_Fault(s_OcdFault);
Set_SCD_Fault(s_ScdFault);
Set_CellBalancing(s_CellBalancing);
}

/* Cell balance readback verification (D-30) */
void Cdd_Bq76920_SetCellBalance(uint32_t cellMask)
{
uint8_t balanceReg = (uint8_t)(cellMask & 0x1F);

if (BQ_OK == bq769x0_reg_write_byte(BQ769X0_REG_CELLBAL1, balanceReg)) {
s_CellBalancing = VerifyCellBalance(cellMask);
Set_CellBalancing(s_CellBalancing);
Set_CellBalance(s_CellBalancing ? cellMask : 0);
}
}

static bool VerifyCellBalance(uint32_t cellMask)
{
uint8_t readback;
if (BQ_OK == bq769x0_reg_read_byte(BQ769X0_REG_CELLBAL1, &readback)) {
return ((readback & 0x1F) == (cellMask & 0x1F));
}
return false;
}

设计解读:

  • WriteCellDataToRTE() 是 CDD 的"出口":AFE 缓存 BmsData → RTE 全局结构,ASW 通过 RTE 读取。
  • D-30 均衡回读校验:写完 CELLBAL1 后立刻读回来比对,确保 5 个 bit 都正确写入。失败则 s_CellBalancing = false,ASW 看到的均衡状态是"硬件实际状态"而不是"期望状态"。
  • 写顺序保证原子性:先写 CellInfo,再写 FaultFlags,再写 MosStatus,最后处理 ClearReq。ASW 一次读到一组一致的数据快照。
  • ProcessFaultClearRequests() 在 RTE 写完后执行,确保 ASW 在清除请求生效前先看到当前故障。

10.9 寄存器层 bq769x0.c — I2C + CRC8

源文件: Source/BSW/CDD/BQ76920/src/bq769x0.c(716 行,节选 CRC + 单字节读)

/**
* @file bq769x0.c
* @brief BQ76920 register protocol
*/

/* TI 私有 CRC8 多项式 0x07 */
static uint8_t crc8(const uint8_t *buffer, size_t length)
{
uint8_t crc = 0;
for (size_t i = 0; i < length; i++) {
uint8_t data = crc ^ buffer[i];
for (int j = 0; j < 8; j++) {
if (data & 0x80) {
data <<= 1;
data ^= 0x07; /* CRC_POLY */
} else {
data <<= 1;
}
}
crc = data;
}
return crc;
}

/* 单字节写: [reg, value, crc_of_(addr+reg+value)] */
tStdTypedef_t bq769x0_reg_write_byte(uint8_t reg, uint8_t value)
{
uint8_t data[2] = {value, 0};
uint8_t crc_data[3] = {(BQ769X0_I2C_ADDR << 1) | 0, reg, value};
data[1] = crc8(crc_data, 3);

Msp_I2c_Transmit(reg, data, 2, NULL);
return (Msp_I2c_GetLastError() == MSP_I2C_ERR_OK) ? BQ_OK : BQ_NOT_OK;
}

/* 单字节读: [value, crc_of_(addr|1+value)] */
tStdTypedef_t bq769x0_reg_read_byte(uint8_t reg, uint8_t *value)
{
if (value == NULL) return BQ_NOT_OK;

if (bq769x0_reg_read_bytes_byDma(reg, 1U) != BQ_OK) return BQ_NOT_OK;

uint8_t *buf = (uint8_t *)&RxRegValueBuffer.RxRegValue[0].RegValue;
uint8_t crc_data[2] = {(BQ769X0_I2C_ADDR << 1) | 1, buf[0]};
if (buf[1] != crc8(crc_data, 2)) return BQ_NOT_OK;

*value = buf[0];
return BQ_OK;
}

/* 读-改-写: 用于单 bit 翻转场景 */
tStdTypedef_t bq769x0_reg_update_byte(uint8_t reg, uint8_t mask, uint8_t value)
{
uint8_t old_value;
if (BQ_OK != bq769x0_reg_read_byte(reg, &old_value)) return BQ_NOT_OK;
uint8_t new_value = (old_value & ~mask) | (value & mask);
return bq769x0_reg_write_byte(reg, new_value);
}

设计解读:

  • CRC8 是 BQ76920 通讯的安全阀:TI 私有多项式 0x07,每次写 [value, crc] 共 2 字节,每次读 [value, crc] 也是 2 字节。CRC 失败则丢弃返回值,CDD 进入 ERROR 状态。
  • 单字节读写用 DMA 异步:虽然函数接口是同步阻塞,但内部调用 Msp_I2c_Receive() 通过 DMA 搬运,CPU 在 DMA 期间可处理其他任务。
  • 读-改-写原语 避免"读-改之间被其他写打断"的竞态——在多任务或中断上下文中尤其重要。

10.10 RTE 信号定义 RTE.h — 类型丰富的 Get/Set

源文件: Source/RTE/RTE.h(1062 行,由 RTE_Gen.py  RTE_Config.yaml 自动生成;节选)

/**
* @file RTE.h
* @brief Runtime Environment - Generated from RTE_Config.yaml
*
* Generated on: 2026-05-12 00:40:22
* Schema version: 2.0
* DO NOT EDIT MANUALLY - changes will be overwritten
* Edit RTE_Config.yaml and re-run RTE_Gen.py
*/

/* CellInfo - 单体电池信息 */
typedef struct {
uint16_t CellVoltage[20]; /**< 单体电压 (mV) */
int16_t CellTempture[4]; /**< 电池温度 (0.1°C分辨率) */
uint16_t CellVoltageMax; /**< 最大单体电压 */
uint16_t CellVoltageMin; /**< 最小单体电压 */
uint16_t CellVoltageAvg; /**< 平均单体电压 */
uint16_t CellVoltageDiff; /**< 电压差 = Max - Min */
uint8_t CellVoltageMaxNo; /**< 最大电压电芯编号 */
uint8_t CellVoltageMinNo; /**< 最小电压电芯编号 */
int16_t CellTemptureMax; /**< 最高电芯温度 */
int16_t CellTemptureMin; /**< 最低电芯温度 */
uint16_t CellTemptureDiff; /**< 温差 */
uint8_t CellTemptureMaxNo; /**< 最高温度电芯编号 */
uint8_t CellTemptureMinNo; /**< 最低温度电芯编号 */
uint32_t CellBalance; /**< 均衡状态位掩码 */
uint32_t BatteryVoltage; /**< 电池组总电压 */
} CellInfo_t;

/* PackInfo - 电池包信息 */
typedef struct {
uint32_t PackVoltage; /**< 母线电压 (mV) */
int32_t PackCurrent; /**< 母线电流 (mA, 负值放电) */
uint32_t PackCurrentValue; /**< 电流绝对值 */
uint32_t InsulationResistance; /**< 绝缘电阻 (kΩ) */
uint16_t PackRealSoc; /**< 0-10000 (0.01%分辨率) */
} PackInfo_t;

/* FaultFlags - 故障标志与清除指令 (位域联合) */
typedef union {
uint8_t raw;
struct {
uint8_t OV_Fault : 1;
uint8_t UV_Fault : 1;
uint8_t OCD_Fault : 1;
uint8_t SCD_Fault : 1;
uint8_t CellBalancing : 1;
uint8_t ClearOvFaultReq : 1;
uint8_t ClearUvFaultReq : 1;
uint8_t ClearOcdFaultReq: 1;
} bits;
} FaultFlags_byte0_t;

/* Get/Set 函数 (static inline, 零运行时开销) */
static inline uint16_t Get_CellVoltage(uint32_t index)
{
if (index >= 20) { return (uint16_t)0; } /* 边界保护 */
return CellInfo.CellVoltage[index];
}
static inline void Set_CellVoltage(uint32_t index, uint16_t value)
{
if (index >= 20) { return; }
CellInfo.CellVoltage[index] = value;
}

static inline uint16_t Get_CellVoltageMax(void) { return CellInfo.CellVoltageMax; }
static inline void Set_CellVoltageMax(uint16_t value) { CellInfo.CellVoltageMax = value; }

/* 故障标志 Get/Set (位域访问) */
static inline uint8_t Get_OV_Fault(void) { return FaultFlags.byte0.bits.OV_Fault; }
static inline void Set_OV_Fault(uint8_t v) { FaultFlags.byte0.bits.OV_Fault = v; }
static inline uint8_t Get_UV_Fault(void) { return FaultFlags.byte0.bits.UV_Fault; }
static inline void Set_UV_Fault(uint8_t v) { FaultFlags.byte0.bits.UV_Fault = v; }
static inline uint8_t Get_OCD_Fault(void) { return FaultFlags.byte0.bits.OCD_Fault; }
static inline void Set_OCD_Fault(uint8_t v) { FaultFlags.byte0.bits.OCD_Fault = v; }
static inline uint8_t Get_SCD_Fault(void) { return FaultFlags.byte0.bits.SCD_Fault; }
static inline void Set_SCD_Fault(uint8_t v) { FaultFlags.byte0.bits.SCD_Fault = v; }

/* 全局信号 (extern 声明) */
extern CellInfo_t CellInfo;
extern PackInfo_t PackInfo;
extern FaultFlags_t FaultFlags;
extern HmiSignal_t HmiSignal;
extern MosControl_t MosControl;
extern PackConfig_t PackConfig;

设计解读:

  • static inline Get/Set 没有函数调用开销(编译器会内联展开),同时提供类型安全 + 边界检查(数组 Get/Set)。
  • 位域联合让 1 个字节同时承载"故障标志"和"清除请求",节省 RAM 又表达力强。
  • 全自动生成RTE_Gen.py  RTE_Config.yaml → 产出 RTE.h / RTE.c,人工不修改。增加新信号只需在 yaml 加一行 + 重跑脚本。
  • 3 大类信号:数据信号(CellInfo / PackInfo)、控制信号(MosControl / Clear*Req)、配置信号(PackConfig)——分别对应"读多写少"、"ASW 写 CDD 读"、"初始化时一次写"。

10.11 BQ76920 保护阈值配置 Bq769x0_Cfg.h

源文件: Source/BSW/CDD/BQ76920/include/Bq769x0_Cfg.h(32 行,完整)

/**
* @file Bq769x0_Cfg.h
* @brief BQ76920 CDD Configuration
*/

#ifndef BQ769X0_CFG_H
#define BQ769X0_CFG_H

#include <stdint.h>

/* BQ76920 configuration */
#define BQ769X0_I2C_ADDR 0x08 /* 7-bit slave address */
#define BQ769X0_NUM_CELLS 5
#define BQ769X0_NUM_TEMP_SENSORS 1

/* Protection thresholds */
#define BQ769X0_OVP_MV 4300 /* Over-voltage protection, mV */
#define BQ769X0_UVP_MV 2800 /* Under-voltage protection, mV */

/* Hardware SCD protection (PROTECT1) */
#define BQ769X0_SCD_THRESHOLD 3 /* SCD_D: 0=22mV, 1=42mV, 2=62mV, ... 15=345mV */
#define BQ769X0_SCD_DELAY 2 /* SCD_T: 0=70μs, 1≈320μs, ... 7=1830μs */
#define BQ769X0_RSNS 0 /* 0 for >2mΩ shunt, 1 for 0.5-2mΩ */

/* Hardware OCD protection (PROTECT2) */
#define BQ769X0_OCD_THRESHOLD 3 /* OCD_D: 0=8mV, 1≈15mV, 2≈23mV, ... 15=121mV */
#define BQ769X0_OCD_DELAY 4 /* OCD_T: 0=8ms, 1≈20ms, ... 15=955ms */

/* Shunt resistor value in milliohms */
#define BQ769X0_SHUNT_MOHM 5

#endif /* BQ769X0_CFG_H */

设计解读:

  • 保护阈值硬编码在 BSW 配置头,ASW 层不感知(架构隔离)。ASW 想改阈值必须走标定接口(Phase 4 通过 CAN UDS 实现)。
  • SCD 阈值 3 = 62 mV:配合 5 mΩ 分流电阻 = 12.4 A 短路保护阈值。
  • SCD 延迟 2 ≈ 320 μs:典型短路保护响应时间,远快于 MCU 软件保护(毫秒级)。
  • OCD 阈值 3 ≈ 23 mV = 4.6 A;OCD 延迟 4 ≈ 80 ms —— 抗瞬时尖峰电流。

10.12 IAh Simulink 生成的 SOC 算法接口

源文件: Source/ASW/BMS/IAh.h(108 行,节选)

/*
* Academic License - for use in teaching, academic research...
*
* File: IAh.h
*
* Code generated for Simulink model 'IAh'.
*
* Model version : 1.105
* Simulink Coder version : 25.1 (R2025a) 21-Nov-2024
* C/C++ source code generated on : Tue Aug 26 23:35:45 2025
*
* Target selection: ert.tlc
*/

#ifndef IAh_h_
#define IAh_h_

#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

/* Block states (default storage) for system '<Root>' */
typedef struct {
uint32_t ChargeCapSum; /* '<S1>/Chart' */
uint32_t DischargeCapSum; /* '<S1>/Chart' */
uint16_t UnitDelay_DSTATE; /* '<S1>/Unit Delay' */
} DW_IAh_T;

/* Real-time Model Data Structure */
struct tag_RTM_IAh_T {
const char * volatile errorStatus;
};

/* Block states (default storage) */
extern DW_IAh_T IAh_DW;

/* Exported states - 标定参数 */
extern uint32_t A_Resolution; /* '<S1>/Data Store Memory1' */
extern uint32_t CapacityRated; /* '<S1>/Data Store Memory' */

/* Model entry point functions */
extern void IAh_initialize(void);
extern void IAh_step(void);
extern void IAh_terminate(void);

/* Real-time Model object */
extern RT_MODEL_IAh_T *const IAh_M;

#endif /* IAh_h_ */

设计解读:

  • IAh  Simulink Stateflow 模型自动生成的 SOC 算法,三状态机(Init/Charge/Discharge)由 Stateflow Chart 实现,编译为 IAh_step()
  • A_Resolution  CapacityRated 标定参数(Data Store Memory),可通过 Simulink External Mode 实时调参(无需重新编译烧录)。
  • IAh_step()  Bms_Soc_Task 每 250 ms 调用一次,与 main.c  Os_TaskRegister(..., 250, 50) 注册周期一致。
  • 注释 '<S1>/Chart' 等是模型追溯标记hilite_system('<S1>/Chart') 可在 Simulink 中反查到对应模块。

10.13 关键代码实现小结

维度

实现要点

文件

行数

启动流程

RTE → OS → 任务初始化 → 任务注册 → 主循环

ASW/main.c

74

故障保护

ASW 读 RTE 故障标志 → 写 RTE MOS 命令

ASW/BMS/Bms_Monitor_Task.c

88

SOC 算法

整数库仑积分,全离线标定

ASW/BMS/Bms_Soc_Task.c

56

均衡决策

位掩码差压阈值

ASW/BMS/Bms_Balance_Task.c

56

HMI 状态

短按切页 / 长按休眠

ASW/BMS/Bms_Hmi_Task.c

73

调度器

双任务表 + 0.1ms tick + offset 错峰

BSW/OS/src/Os.c

386

AFE 状态机

9 态 + MOS 劫持 + CC_READY 自适应

BSW/CDD/BQ76920/src/AfeManage.c

276

CDD 数据出口

Write*ToRTE() + 均衡回读校验

BSW/CDD/BQ76920/src/Cdd_Bq76920.c

582

I2C + CRC8

TI 私有多项式 0x07

BSW/CDD/BQ76920/src/bq769x0.c

716

RTE 信号

自动生成 + static inline Get/Set

RTE/RTE.h

1062

保护配置

阈值硬编码 BSW 头

BSW/CDD/BQ76920/include/Bq769x0_Cfg.h

32

Simulink 模型

IAh Stateflow 自动生成

ASW/BMS/IAh.h

108

💡 完整代码  Source/ 目录;本文档只挑选 12 段最能体现分层架构 + MBD + 状态机 + RTE 自动化的核心代码。评审如需查阅完整文件,可参考附录 A 的索引表。


10.14 重点代码逐行解读(精选 3 段)

本节对工程中最巧妙、最易踩坑的 3 段代码做逐行注释 + 设计决策说明 + 常见误区。完整文件请参考 Source/ 目录。

10.14.1 AFE 状态机的 MOS 劫持逻辑

源文件: Source/BSW/CDD/BQ76920/src/AfeManage.c,第 73-96 行(节选)

void AfeUpdateFunction(void)
{
/* === [1] 读取当前硬件缓存的 MOS 状态 ===
* 这一步读取的不是实时硬件,而是 AFE 内部维护的 BmsData 缓存。
* 缓存由 status_parse() 每次解析 SYS_STAT 后更新,所以反映
* "最近一次 I2C 读到的硬件状态",不是 "此刻硬件真实状态"。
* 这种 "软缓存" 是空间换时间,避免每次控制都先读一次 I2C。 */
uint8_t discharge_mos_status = BmsData.Regs.SYS_CTRL2.RegValueBits.DSG_ON;
uint8_t charge_mos_status = BmsData.Regs.SYS_CTRL2.RegValueBits.CHG_ON;

/* === [2] 故障检测 ===
* SYS_STAT 低 4 bit 是故障标志 (OCD/SCD/OV/UV)。
* 用 0x0F 掩码一次性检测 "是否有任何硬件故障",
* 避免 4 次 if 判断。注意位序在 TI 寄存器定义里
* OCD 在 bit0, SCD 在 bit1, OV 在 bit2, UV 在 bit3。 */
bool fault_active = (BmsData.Regs.SYS_STAT.RegValue & 0x0FU) != 0U;

/* === [3] ASW 期望的 MOS 状态 vs 硬件缓存 ===
* Get_ChargeMosCmd() 从 RTE 读 ASW 任务写入的命令 (0 或 1)。
* 与缓存的 charge_mos_status 比较,得出 "期望 ≠ 实际" 的判断。 */
bool mos_command_diverges = (Get_ChargeMosCmd() != charge_mos_status) ||
(Get_DischargeMosCmd() != discharge_mos_status);

/* === [4] 劫持安全检查 ===
* 以下 4 种情况绝对不能劫持进入 MOS_CTRL:
* (a) 已经在 MOS_CTRL — 避免自递归
* (b) 在 DEAL_DATA — 此状态正在解析刚读到的新数据,
* 写 SYS_CTRL2 会冲掉缓存
* (c) 在 INIT — 芯片还没配置好 PROTECT 寄存器,
* 写 SYS_CTRL2 可能触发误保护
* (d) 在 CONFIG — 同上
* 此外:fault_active 时硬件会自动清 MOS 位,
* 此时再写 SYS_CTRL2 也不会生效,要等故障清除 */
bool hijack_safe = (s_AfeState != AFE_STATE_MOS_CTRL) &&
(s_AfeState != AFE_STATE_DEAL_DATA) &&
(s_AfeState != AFE_STATE_INIT) &&
(s_AfeState != AFE_STATE_CONFIG) &&
!fault_active;

/* === [5] 触发劫持:插入一次 MOS_CTRL 写 ===
* 这是 "中断式状态机" 的核心:正常流程读到一半时,
* 检测到紧急命令,强制插入一段特殊处理,处理完回到主线。
* 类似 µC/OS-II 的 "任务级中断" 思想。 */
if (mos_command_diverges && hijack_safe) {
s_AfeState = AFE_STATE_MOS_CTRL;
index_state = AFE_STATE_MOS_CTRL;
}
// ... (下方 switch 进入 AFE_STATE_MOS_CTRL case, 见 10.7)
}

设计决策说明:

决策

替代方案

选择理由

用缓存而非实时读 I2C

每次先 I2C 读 SYS_CTRL2

省 1 次 I2C 事务 (~3ms) + 简化代码

4 种状态全部禁止劫持

只禁止 DEAL_DATA

实战中在 INIT/CONFIG 写 SYS_CTRL2 会触发不可预测行为

故障时禁止劫持

让 MOS 命令覆盖故障保护

硬件优先级最高,软件写也无效,不如不浪费 I2C

劫持完回到 READ_STATUS

回到原状态

READ_STATUS 总会刷新缓存,下次判断时 hijack 自然解除

常见误区:

  •  在 DEAL_DATA 中也允许劫持:会丢失刚解析的电压/温度数据,且缓存会被写动作污染
  •  劫持后不更新缓存:导致下一轮判断仍然认为 "命令 ≠ 实际",陷入无限劫持循环
  •  故障状态下强制写 MOS:BQ76920 硬件的故障保护优先级最高,写了也没用,浪费 I2C 周期

10.14.2 非抢占式调度器的时间基设计

源文件: Source/BSW/OS/src/Os.c,第 273-311 行(节选 Os_TimerTick

/* === [1] ISR 入口:每 100µs 由硬件定时器触发 ===
* 这是整个调度器的 "心脏",执行时间必须 < 100µs。
* 实测 4 任务时 < 10µs,10 任务时 < 30µs,仍有充足余量。 */
void Os_TimerTick(void)
{
uint16_t i;
uint16_t periodTicks;

/* === [2] 全局心跳 + RTE 同步 ===
* s_OsTickCount 是 OS 内部诊断计数;Set_SystemTick() 同步
* 到 RTE 供 ASW 任务 (比如 "距离上次唤醒 5s 后进入休眠") 观察。
* 注意:s_OsTickCount 是 32 位 0.1ms 单位,约 4.97 年溢出,
* 在嵌入式产品生命周期内可接受。 */
s_OsTickCount++;
Set_SystemTick(s_OsTickCount);

/* === [3] BSW 任务表更新(单层循环)===
* 核心算法:每个任务有 counter 倒计时,到 0 就置 ready 并重装。
* 这是经典的 "软件定时器数组" 实现:
* - 不用链表是因为任务数固定 (≤8),数组访问更快
* - 不用堆排序是因为调度器是 round-robin,固定顺序
* - counter 单位是 0.1ms tick (1ms = 10 ticks) */
for (i = 0U; i < s_BswTaskCount; i++) {
if (s_BswTaskTable[i].counter == 0U) {
periodTicks = OS_MS_TO_TICKS(s_BswTaskTable[i].periodMs);
s_BswTaskTable[i].ready = true;
s_BswTaskTable[i].counter = periodTicks; /* 重装 */
} else {
s_BswTaskTable[i].counter--; /* 递减 */
}
}

/* === [4] ASW 任务表更新(同样的算法,独立计数)===
* BSW 和 ASW 用两套独立 counter 是为了:
* - BSW 任务 (CDD/LCD) 启动早,先入表先运行
* - ASW 任务 (业务逻辑) 启动晚,offset 可自由配置
* - 调度时 BSW 在前 ASW 在后,硬件服务先于业务决策 */
for (i = 0U; i < s_AswTaskCount; i++) {
if (s_AswTaskTable[i].counter == 0U) {
periodTicks = OS_MS_TO_TICKS(s_AswTaskTable[i].periodMs);
s_AswTaskTable[i].ready = true;
s_AswTaskTable[i].counter = periodTicks;
} else {
s_AswTaskTable[i].counter--;
}
}
}

/* === [5] OS_MS_TO_TICKS 宏 (Os.h 定义) ===
* 这是关键的时间换算:
* periodMs=100ms → periodTicks=999 (不是 1000!)
* 因为 counter 从 offset 开始递减到 0 才触发,
* 周期是 "从 0 数到 999 再到 0" 共 1000 步 (100ms @ 0.1ms tick)。
* 若用 1000 作为初值,会变成 100.1ms (1 个 tick 偏长)。 */
#define OS_MS_TO_TICKS(ms) ((uint16_t)((ms) * 10U - 1U))

设计决策说明:

决策

替代方案

选择理由

0.1ms tick (100µs)

1ms tick

10ms 周期的 AFE 任务相位分辨率 10x 提升,错峰更细

counter 递减到 0 触发

累加到 period 触发

0 检测只需 == 0 比较,累加需 >= 比较 + 防溢出

双任务表

单任务表

BSW 静态可裁剪 (不同项目配不同 BSW),ASW 用户动态注册

立即重装

调度执行时再重装

重装是 ISR 内的常数操作,避免 dispatcher 边界 case

ready 标志 + counter 分离

只用 counter

ISR 和 dispatcher 在不同上下文,ready 避免临界区

为什么不用 RTOS(如 FreeRTOS):

  1. 资源占用:FreeRTOS ROM 约 6-10 KB,本调度器仅 ~1 KB
  2. 可预测性:非抢占式 → 无任务切换开销抖动
  3. 简化调试:没有任务切换时的寄存器快照,单步断点更清晰

易错点:

  •   OS_MS_TO_TICKS(100) 当成 1000 → 实际是 999,初学者会困惑
  •  在 ISR 内调用 printf/log → 100µs 内根本来不及,会破坏调度节拍
  •  offset 设错 → 同周期任务同时触发,CPU 瞬间被两个任务挤占
  •  periodMs 设成 0  OS_MS_TO_TICKS(0) = 65535(uint16 下溢),每 6.5 秒才触发一次

10.14.3 RTE 信号的"读多写少"模式

源文件: Source/RTE/RTE.h,第 271-280 行(节选 Get_CellVoltage / Set_CellVoltage

/* === [1] 数组 Get 函数 (uint16_t Get_CellVoltage(uint32_t index)) ===
* 这是 RTE 信号访问的核心模式。"Get_X(idx)" 模式的关键设计:
* - 边界检查:index >= 20 时返回 0,**绝不**越界访问
* - 内联展开:static inline 让编译器在调用处直接展开,
* 没有函数调用开销 (call/ret/压栈/弹栈)
* - 单一职责:只做"读+边界检查",不附加日志/断言,
* ASW 频繁调用时不会拖慢热路径 */
static inline uint16_t Get_CellVoltage(uint32_t index)
{
if (index >= 20) { return (uint16_t)0; } /* 边界保护 */
return CellInfo.CellVoltage[index];
}

/* === [2] 数组 Set 函数 (void Set_CellVoltage(uint32_t index, uint16_t value)) ===
* 对称设计:Set 也做边界检查,超界时**静默丢弃**而非崩溃。
* 这种 "fail silently" 在汽车级嵌入式很常见:
* - 防止 ASW 写错索引把 RAM 写花
* - 不抛异常不返回错误码 (嵌入式中异常处理昂贵)
* - 但需要静态分析工具 (MISRA-C) 强制约束 ASW 索引合法性 */
static inline void Set_CellVoltage(uint32_t index, uint16_t value)
{
if (index >= 20) { return; }
CellInfo.CellVoltage[index] = value;
}

/* === [3] 标量 Get 函数 (uint16_t Get_CellVoltageMax(void)) ===
* 标量 (非数组) 信号比数组简单一档:
* - 不需要 index 参数
* - 不需要边界检查
* - 但仍然 static inline (因为整个 RTE.h 头文件包含频繁) */
static inline uint16_t Get_CellVoltageMax(void)
{
return CellInfo.CellVoltageMax;
}

/* === [4] 标量 Set 函数 (void Set_CellVoltageMax(uint16_t value)) ===
* 注意:标量 Set 没有"原子性"问题 (单条 MOV 指令),
* 但数组 Set 在 32 位 MCU 上需要 2 条指令 (H + L 字节),
* 中间可能被中断打断 → 这就是为什么 CDD 在 DEAL_DATA 中
* 先写完所有 RTE 信号再开中断 (临界区)。 */
static inline void Set_CellVoltageMax(uint16_t value)
{
CellInfo.CellVoltageMax = value;
}

设计决策说明:

决策

替代方案

选择理由

static inline Get/Set

普通函数

零调用开销,C99 标准,编译器跨平台支持

数组越界返回 0/丢弃

触发 assert / HardFault

嵌入式产品不能让一个非法索引搞死系统

单一信号单一函数

 #define

函数有类型检查,宏容易踩坑 (#副作用)

不在 Get/Set 中加锁

加临界区保护

RTE 是单线程访问 (非抢占调度),无需锁

Get/Set 不做日志

加访问日志

频繁调用拖慢热路径,需要时单独包装

与"全局裸变量"对比:

// ❌ 反面教材: ASW 直接访问全局变量
extern uint16_t CellVoltage[20]; // ASW 必须知道底层数据结构
void MyTask(void) {
if (CellVoltage[0] > 3500) { // 没有边界检查
CellVoltage[0] = 3500; // ASW 直接修改
}
}

// ✅ 正确做法: 通过 RTE 访问
void MyTask(void) {
uint16_t v = Get_CellVoltage(0); // 边界保护
if (v > 3500) {
Set_CellVoltage(0, 3500); // 写入受控
}
}

RTE 自动化生成:

RTE_Config.yaml 是单一数据源 (Single Source of Truth):

signals:
- name: CellVoltage
type: uint16[20] # 类型 + 数组长度
direction: CDD_TO_ASW
description: "5 节电池电压 (mV)"
- name: PackRealSoc
type: uint16
direction: ASW_INTERNAL
description: "SOC (0-10000, 0.01% 分辨率)"

执行 python RTE_Gen.py 后自动产出:

  • RTE.h 中的 typedef struct(数据结构)
  • RTE.h 中的 static inline Get/Set(访问接口)
  • RTE.c 中的全局变量定义(实际存储)

优势:

  • ✅ 增加新信号只需在 yaml 加一行,重跑脚本
  • ✅ ASW 代码不需要修改(接口名一致)
  • ✅ 类型/范围/方向都在 yaml 集中维护
  • ✅ 易于对接 MISRA-C 静态分析工具

易错点:

  •  手动修改生成的 RTE.h → 下次跑脚本会被覆盖,注释明确警告过
  •  在 Get/Set 内调用耗时函数 (printf/malloc) → 破坏 RTE 访问的"零开销"承诺
  •  CDD 写一半时 ASW 读取 → 数据不一致。解决:在 CDD 写操作前后短暂关中断 (临界区 ~1µs)
  •  跨层访问 (BSW 任务直接读 RTE 而绕过 CDD) → 架构违规,应通过 CDD 接口

10.14.4 代码风格与审查规范

1. 命名约定:

元素

风格

示例

函数

PascalCase

Bms_Monitor_Task, Get_CellVoltage

变量 (局部)

snake_case

cell_voltage, fault_active

变量 (全局)

s_ 前缀

s_AswTaskTable, s_DataReady

常量/宏

UPPER_SNAKE

OS_BSW_TASK_MAX, BQ769X0_OVP_MV

类型

_t 后缀

CellInfo_t, Os_TaskCfg_t

枚举值

类型前缀

AFE_STATE_INIT, BQ_OK

2. 注释规范(重要!比赛评审会看):

  • 每个 .c/.h 文件 顶部必须有 Doxygen 风格文件头(用途、作者、版本)
  • 每个函数 必须有 Doxygen 注释(@brief @param @return
  • 复杂逻辑  /* === [1] === */ 块注释分小节
  • TODO 必须带优先级TODO(P3): 表示优先级 3 的待办
  • 禁止单行 // 注释(MISRA-C 2012 强制规则)

3. 项目级强制规则(CI 检查):

# arch_check.py (CI 流水线运行)
import re
import sys

violations = []

for fname in glob('Source/ASW/**/*.c'):
with open(fname) as f:
code = f.read()
# 规则 1: ASW 禁止 #include "stm32f1xx_hal.h"
if 'stm32f1xx_hal.h' in code:
violations.append(f'{fname}: ASW 包含 HAL 头 (违规)')
# 规则 2: ASW 禁止访问 BSW 全局变量
if re.search(r'\bAfeManage\.\w+', code): # 不严谨, 实际用 RTE API
violations.append(f'{fname}: ASW 直接访问 BSW 变量 (违规)')
# 规则 3: ASW 禁止调用 HAL_Delay / printf (除调试接口)
if re.search(r'\bHAL_Delay\s*\(', code):
violations.append(f'{fname}: ASW 调用 HAL_Delay (违规)')

if violations:
for v in violations:
print(v)
sys.exit(1)

4. 单元测试覆盖:

模块

测试方式

覆盖率

RTE Get/Set

编译期静态检查 + 运行时 boundary test

100%

AFE 状态机

主机仿真 (PC 上跑 AfeManage.c)

90%

SOC 算法

Simulink MIL/SIL 闭环

100%

BQ76920 配置

硬件在环 (HIL) + 读回校验

100%

Os 调度器

主机仿真 + GPIO 时序测量

95%


附录 A:关键源文件索引

路径

功能

Source/ASW/main.c

ASW 入口

Source/ASW/BMS/Bms_Monitor_Task.c

Monitor 任务(100 ms)

Source/ASW/BMS/Bms_Soc_Task.c

SOC 任务(250 ms)

Source/ASW/BMS/Bms_Hmi_Task.c

HMI 任务(200 ms)

Source/ASW/BMS/Bms_Balance_Task.c

均衡任务(500 ms)

Source/ASW/BMS/IAh.c

SOC 算法(Simulink 生成)

Source/BSW/CDD/BQ76920/src/Cdd_Bq76920.c

CDD 主状态机

Source/BSW/CDD/BQ76920/src/AfeManage.c

AFE 状态机管理

Source/BSW/CDD/BQ76920/src/bq769x0.c

BQ76920 寄存器协议

Source/BSW/OS/Os.c

调度器实现

Source/RTE/RTE.h

RTE 信号定义

Source/RTE/RTE.c

RTE Get/Set 实现

Source/RTE/RTE_Config.yaml

RTE 信号配置表(单一数据源)

Source/RTE/RTE_Gen.py

RTE 代码自动生成脚本

附录 B:BQ76920 关键寄存器

寄存器

地址

功能

SYS_STAT

0x00

系统状态(OV / UV / OCD / SCD / CC_READY)

CELLBAL1

0x01

均衡控制(bit0-4 = CELL1-5)

SYS_CTRL1

0x02

系统控制(ADC 使能、温度选择)

SYS_CTRL2

0x04

系统控制(CHG/DSG MOS、CC 使能)

PROTECT1

0x05

SCD 阈值/延迟配置

PROTECT2

0x06

OCD 阈值/延迟配置

PROTECT3

0x07

OV/UV 延迟配置

VC1_HI ~ VC5_HI

0x14 ~ 0x18

单体电压高字节

CC_HI / CC_LO

0x32 / 0x33

库仑计数值

BAT_HI / BAT_LO

0x34 / 0x35

电池包总电压

TS_HI / TS_LO

0x36 / 0x37

温度 ADC

OV_TRIP

0x4C

OV 跳变阈值

UV_TRIP

0x4D

UV 跳变阈值

ADCOFFSET

0x3A

ADC 偏置

ADCGAIN1

0x3B

ADC 增益

附录 C:术语表

术语

英文

说明

BMS

Battery Management System

电池管理系统

AFE

Analog Front End

模拟前端芯片

SOC

State of Charge

荷电状态(剩余容量百分比)

SOH

State of Health

健康状态(容量保持率)

SOP

State of Power

功率状态估计

OCV

Open Circuit Voltage

开路电压

CDD

Complex Device Driver

复杂设备驱动

RTE

Runtime Environment

运行时环境

ASW

Application Software

应用软件层

BSW

Basic Software

基础软件层

MCAL

Microcontroller Abstraction Layer

微控制器抽象层

MBD

Model-Based Design

基于模型的设计

OV / UV

Over-Voltage / Under-Voltage

过压 / 欠压

OCD

Over-Current Discharge

放电过流

SCD

Short-Circuit Discharge

短路放电

OT / UT

Over-Temperature / Under-Temperature

过温 / 欠温

NTC

Negative Temperature Coefficient

负温度系数热敏电阻

CAN

Controller Area Network

控制器局域网

UDS

Unified Diagnostic Services

统一诊断服务

DTC

Diagnostic Trouble Code

诊断故障码

附件下载
SoftwareFramework_V2.7z
Gerber_BMS_PCB_1_2026-05-21.zip
团队介绍
1
评论
0 / 100
查看更多
猜你喜欢
制作FPGA电子琴1. 存储一段音乐,并可以进行音乐播放, 2. 可以自己通过板上的按键进行弹奏,支持两个按键同时按下(和弦)并且声音不能失真,板上的按键只有13个,可以通过有上方的“上“、”下”两个按键对音程进行扩展
john
2563
2026 M-Design设计竞赛-基于物联网的温室大棚管理系统该项目使用了STM32,ESP32.BH1750,DHT11,实现了温室大棚管理系统的设计,它的主要功能为:本项目基于STM32F103C8T6与ESP32,实时采集空气温湿度、土壤湿度和光照强度,并在OLED上显示。系统可自动根据预设阈值控制排气扇、水泵和补光灯,并触发声光报警;阈值支持按键调节且掉电记忆。数据通过ESP32上传至点灯科技APP,支持Web一键配网,实现温室环境的远程智能监控。。
叶佳敏
3
2026 M-Design设计竞赛-锂电池充放电智能管理系统该项目使用了STM32,INA226,ESP32,实现了锂电池充放电智能管理系统的设计,它的主要功能为:一套基于STM32F103C8T6微控制器的锂电池充放电智能管理系统。系统实时监测单节锂电池的电压、充放电电流、剩余电量百分比以及电池温度,通过2.4寸OLED屏幕本地显示所有参数。用户可通过三个按键灵活设置过温、过压、欠压、过电流四类报警阈值,当参数超限时,系统自动触发声光报警,并可通过板载继电器切断负载,防止电池损坏。系统集成TP4056充电管理芯片,支持Type-C接口为锂电池恒流恒压充电。此外,系统通过UART与ESP32通信,实现网页配网接入点灯科技物联网平台,用户可通过手机APP远程查看电池电压、电流、电量、温度,实时掌握电池状态。。
OK了家人们中天门了
4
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号