BMS--系统架构

BQ76930():

https://blog.csdn.net/m0_74739916/article/details/154958458?sharetype=blogdetail&sharerId=154958458&sharerefer=PC&sharesource=m0_74739916&spm=1011.2480.3001.8118https://blog.csdn.net/m0_74739916/article/details/154958458?sharetype=blogdetail&sharerId=154958458&sharerefer=PC&sharesource=m0_74739916&spm=1011.2480.3001.8118

QT上位机:

https://blog.csdn.net/m0_74739916/article/details/155420906?spm=1011.2415.3001.5331https://blog.csdn.net/m0_74739916/article/details/155420906?spm=1011.2415.3001.5331

项目描述

48V 智能电池管理板核心目标是实现精准监测、多维保护、低耗节能。适配电动自行车、滑板车、便携式储能设备、工业 AGV 机器人三大核心场景,为设备提供安全可靠的电池管理解决方案。

项目实现

基于 STM32F103C8T6+TI 芯片组(BQ76940AFE/BQ34Z100-G1/BQ76200)

  • freertos 实现 7 大核心任务。故障保护 / 电芯电压与电流采样 / 电池均衡控制 / SOC 计算与电量管理 / 充放电状态识别与逻辑控制 / 外部 CAN 通信 / 其它辅助功能。
  • BQ76940 中断处理过流、短路、过温、过压 / 欠压等紧急故障。读取 13 串电压 (I2C)、总电流、NTC 温度(内置 ADC)。电池均衡控制 (内置均衡开关与外部均衡电阻)。
  • BQ34Z100-G1 (I2C) 计算 SOC (剩余电量)、循环次数、健康状态 (剩余电量),并处理容量学习。
  • BQ76200 (GPIO) 控制主回路中功率 MOS 管的通断。
  • CAN 总线数据收发,支持与外部设备(仪表盘、AGV 主控)的信息交互。管理板的对外接口。
  • 技术扩展 1。QT 上位机 (eth<lwip>\wifi<spi>\uart) 测试显示。IAP/OTA 升级。看门狗保护。
  • 技术扩展 2。多 BMS 节点 + QT 实现数字化多电池组集中监控与管理系统。
任务名称 函数名 优先级(FreeRTOS) 任务周期 函数作用
故障保护任务 void FaultProtectTask(void *pvParameters) 7(最高) 非周期性(中断触发 + 10ms 轮询兜底) 处理 BQ76940 中断上报的过流 / 短路 / 过温等故障,执行保护逻辑(如切断 MOS 管)
电芯电压与电流采样任务 void CellSampleTask(void *pvParameters) 6 250ms(匹配 BQ76940 硬件采样周期) 定时读取 BQ76940 的电芯电压、电流、温度数据,更新全局状态变量
充放电状态识别与逻辑控制任务 void ChargeDischargeCtrlTask(void *pvParameters) 5 50ms 根据采样数据和故障状态,控制 BQ76200 驱动 MOS 管,切换充放电状态
外部 CAN 通信任务 void CANCommTask(void *pvParameters) 4 100ms(主动上报)+ 中断触发(接收) 处理 CAN 总线数据收发,解析外部指令并上报电池状态
SOC 计算与电量管理任务 void SOCCalculateTask(void *pvParameters) 3 1s 基于安时积分 + 开路电压 法计算 SOC、SOH,管理电池容量数据
电池均衡控制任务 void CellBalanceTask(void *pvParameters) 2 1 分钟(或按需触发) 检测电芯压差,控制 BQ76940 均衡开关,实现电芯主动均衡
其它辅助功能任务 void AuxiliaryTask(void *pvParameters) 1(最低) 500ms~10s(依功能类型) 处理指示灯、看门狗喂狗、日志记录等非核心功能
专属打印任务(异步队列用) void PrintTask(void *pvParameters) 1(最低) 无固定周期(队列消息触发) 从打印队列读取数据,统一输出到串口,避免多任务打印冲突

CAN:

1:

https://blog.csdn.net/m0_74739916/article/details/148457004?spm=1011.2415.3001.5331https://blog.csdn.net/m0_74739916/article/details/148457004?spm=1011.2415.3001.53312:

https://blog.csdn.net/m0_74739916/article/details/148696479?spm=1011.2415.3001.5331https://blog.csdn.net/m0_74739916/article/details/148696479?spm=1011.2415.3001.5331我们板子上面的硬件电路:

数据帧+标准帧

cpp 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    can.c
  * @brief   This file provides code for the configuration
  *          of the CAN instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "can.h"

/* USER CODE BEGIN 0 */
CAN_FilterTypeDef CAN_Filter;
/* USER CODE END 0 */

CAN_HandleTypeDef hcan;

/* CAN init function */
void MX_CAN_Init(void)
{

  /* USER CODE BEGIN CAN_Init 0 */
  // 波特率 = APB1时钟 (CAN挂载的总线)/ (Prescaler * (TimeSeg1 + TimeSeg2 + 1))
  // 示例:APB1=36MHz,Prescaler=6,TimeSeg1=5,TimeSeg2=2,波特率为:
  // 36MHz / (16 * (1+1+1)) = 750kbps
  /* USER CODE END CAN_Init 0 */

  /* USER CODE BEGIN CAN_Init 1 */

  /* USER CODE END CAN_Init 1 */
  hcan.Instance = CAN1;
  hcan.Init.Prescaler = 9;
  hcan.Init.Mode = CAN_MODE_LOOPBACK;
  hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
  hcan.Init.TimeSeg1 = CAN_BS1_12TQ;
  hcan.Init.TimeSeg2 = CAN_BS2_3TQ;
  hcan.Init.TimeTriggeredMode = DISABLE;
  hcan.Init.AutoBusOff = DISABLE;
  hcan.Init.AutoWakeUp = DISABLE;
  hcan.Init.AutoRetransmission = DISABLE;
  hcan.Init.ReceiveFifoLocked = DISABLE;
  hcan.Init.TransmitFifoPriority = DISABLE;
  if (HAL_CAN_Init(&hcan) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CAN_Init 2 */
  // 32位滤波位
  CAN_Filter.FilterIdHigh=0x0000<<16;  // ID
  CAN_Filter.FilterIdLow=0x0000;
  CAN_Filter.FilterMaskIdHigh=0x0000<<16;   // 掩码
  CAN_Filter.FilterMaskIdLow=0x0000;
  CAN_Filter.FilterFIFOAssignment=CAN_FILTER_FIFO0;
  CAN_Filter.FilterBank=0;
  CAN_Filter.FilterMode=CAN_FILTERMODE_IDMASK;  // 掩码模式
  CAN_Filter.FilterScale=CAN_FILTERSCALE_32BIT;
  CAN_Filter.FilterActivation=CAN_FILTER_ENABLE;

  HAL_CAN_ConfigFilter(&hcan,&CAN_Filter);  
  HAL_CAN_Start(&hcan);
  /* USER CODE END CAN_Init 2 */

}

void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(canHandle->Instance==CAN1)
  {
  /* USER CODE BEGIN CAN1_MspInit 0 */

  /* USER CODE END CAN1_MspInit 0 */
    /* CAN1 clock enable */
    __HAL_RCC_CAN1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**CAN GPIO Configuration
    PA11     ------> CAN_RX
    PA12     ------> CAN_TX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_11;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN CAN1_MspInit 1 */

  /* USER CODE END CAN1_MspInit 1 */
  }
}

void HAL_CAN_MspDeInit(CAN_HandleTypeDef* canHandle)
{

  if(canHandle->Instance==CAN1)
  {
  /* USER CODE BEGIN CAN1_MspDeInit 0 */

  /* USER CODE END CAN1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_CAN1_CLK_DISABLE();

    /**CAN GPIO Configuration
    PA11     ------> CAN_RX
    PA12     ------> CAN_TX
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11|GPIO_PIN_12);

  /* USER CODE BEGIN CAN1_MspDeInit 1 */

  /* USER CODE END CAN1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */


/**
  * @brief  发送CAN消息
  * @param  ID: 标识符
  * @param  Length: 数据长度(1-8)
  * @param  RTR: 1-远程帧, 0-数据帧
  * @param  data: 数据指针
  * @param  IDE: 1-扩展帧, 0-标准帧
  * @retval HAL状态值
  */
HAL_StatusTypeDef CAN_AddTxMessage(uint32_t ID, uint8_t Length, uint8_t RTR, uint8_t *data, uint8_t IDE)
{
    CAN_TxHeaderTypeDef TxHeader;
    uint32_t TxMailbox;
    
    // 校验数据长度
    if (Length == 0 || Length > 8) {
        return HAL_ERROR;
    }
    
    // 配置CAN消息头
    if (IDE) {
        TxHeader.IDE = CAN_ID_EXT;       // 扩展帧
        TxHeader.ExtId = ID;
        TxHeader.StdId = 0;              // 扩展帧时标准ID无效
    } else {
        TxHeader.IDE = CAN_ID_STD;       // 标准帧
        TxHeader.StdId = ID & 0x7FF;     // 确保ID在11位范围内
        TxHeader.ExtId = 0;              // 标准帧时扩展ID无效
    }
    
    // 配置远程/数据帧
    TxHeader.RTR = (RTR == 1) ? CAN_RTR_REMOTE : CAN_RTR_DATA;
    
    // 配置数据长度
    TxHeader.DLC = Length;
    TxHeader.TransmitGlobalTime = DISABLE;
    
    // 等待发送邮箱空闲
    while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0);
    
    // 发送CAN消息并返回状态
    return HAL_CAN_AddTxMessage(&hcan, &TxHeader, data, &TxMailbox);
}
 
/**
  * @brief  接收CAN消息
  * @param  ID: 接收ID指针
  * @param  Length: 接收长度指针
  * @param  Data: 接收数据指针
  * @retval HAL状态值
  */
HAL_StatusTypeDef MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data)
{
    CAN_RxHeaderTypeDef RxHeader;
    uint8_t rxData[8] = {0};  // 初始化接收数据数组
    
    // 检查FIFO0是否有消息
    if (HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0) == 0) {
        return HAL_TIMEOUT;
    }
    
    // 接收CAN消息
    if (HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, rxData) != HAL_OK) {
        return HAL_ERROR;
    }
    
    // 提取ID
    if (RxHeader.IDE == CAN_ID_STD) {
        *ID = RxHeader.StdId;  // 标准ID
    } else {
        *ID = RxHeader.ExtId;  // 扩展ID
    }
    
    // 提取数据(仅数据帧有效)
    if (RxHeader.RTR == CAN_RTR_DATA) {
        *Length = RxHeader.DLC > 8 ? 8 : RxHeader.DLC;
        for (uint8_t i = 0; i < *Length; i++) {
            Data[i] = rxData[i];
        }
    } else {
        *Length = 0;  // 远程帧无数据
    }
    
    return HAL_OK;
}

/**
  * @brief  通过CAN发送int类型数据(4字节)
  * @param  ID: CAN标识符(标准帧/扩展帧)
  * @param  int_data: 要发送的int数据
  * @param  IDE: 1-扩展帧, 0-标准帧
  * @retval HAL状态值
  */
HAL_StatusTypeDef CAN_SendIntData(uint32_t ID, int int_data, uint8_t IDE)
{
    uint8_t tx_data[4];  // int占4字节,存放到字节数组
    
    // 将int拆分为4个字节(小端模式:低字节在前)
    tx_data[0] = (uint8_t)(int_data & 0xFF);         // 第0字节:最低8位
    tx_data[1] = (uint8_t)((int_data >> 8) & 0xFF);  // 第1字节:次低8位
    tx_data[2] = (uint8_t)((int_data >> 16) & 0xFF); // 第2字节:次高8位
    tx_data[3] = (uint8_t)((int_data >> 24) & 0xFF); // 第3字节:最高8位
    
    // 调用已有的发送函数,数据长度为4,RTR=0(数据帧)
    return CAN_AddTxMessage(ID, 4, 0, tx_data, IDE);
}

/**
  * @brief  从CAN接收数据并还原为int类型
  * @param  ID: 接收的CAN标识符(输出)
  * @param  int_data: 接收的int数据(输出)
  * @retval HAL状态值
  */
HAL_StatusTypeDef CAN_ReceiveIntData(uint32_t *ID, int *int_data)
{
    uint8_t rx_data[4];
    uint8_t length;
    HAL_StatusTypeDef status;
    
    // 调用已有的接收函数,获取4字节数据
    status = MyCAN_Receive(ID, &length, rx_data);
    if (status != HAL_OK || length != 4) {
        return HAL_ERROR;
    }
    
    // 将4个字节合并为int(小端模式)
    *int_data = (int)(
        (rx_data[3] << 24) | 
        (rx_data[2] << 16) | 
        (rx_data[1] << 8)  | 
        rx_data[0]
    );
    
    return HAL_OK;
}

/* USER CODE END 1 */
/* USER CODE BEGIN Header_CANCommTask */
/**
* @brief Function 外部 CAN 通信任务
* @param argument: 处理 CAN 总线数据收发,解析外部指令并上报电池状态
* @retval None
*/
/* USER CODE END Header_CANCommTask */
void CANCommTask(void *argument)
{
  /* USER CODE BEGIN CANCommTask */
		uint32_t preTime = xTaskGetTickCount();

 
	uint32_t TxID = 0x555;         // 发送ID (标准帧)
	uint8_t TxLength = 8;          // 发送数据长度
	uint8_t TxData[] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88};     // 发送数据
	 
	uint32_t RxID;                // 接收ID
	uint8_t RxLength;             // 接收数据长度
	uint8_t RxData[8];            // 接收数据缓冲区
	int data;
  /* Infinite loop */
  for(;;)
  {
		
		CAN_SendIntData(TxID,Battery[0],0);
		CAN_SendIntData(TxID,Battery[1],0);
		CAN_SendIntData(TxID,Battery[2],0);
		CAN_SendIntData(TxID,Battery[3],0);
		CAN_SendIntData(TxID,Battery[4],0);
		CAN_SendIntData(TxID,Battery[5],0);
		CAN_SendIntData(TxID,Battery[6],0);
		CAN_SendIntData(TxID,Battery[7],0);
		CAN_SendIntData(TxID,Battery[8],0);
		CAN_SendIntData(TxID,Battery[9],0);
		CAN_SendIntData(TxID,Battery[10],0);
//		 if ( CAN_SendIntData(TxID,Battery[0],0) != HAL_OK) {
//            u1_printf("CAN发送失败!\n");
//        } else {
//            u1_printf("发送数据: ID=0x%03X\r\n", TxID);
//						u1_printf("TXdata[]:=0x%02X  %d\r\n", Battery[0],Battery[0]);

//        }
				
				
				
//		//                // 接收CAN消息
//        if (CAN_ReceiveIntData(&RxID, &data) == HAL_OK) {
//            if (RxLength > 0) {
//							 u1_printf("接收到数据: ID=0x%03X, 长度=%d\r\n",data, data );

//            }
//					}
      
                // 接收CAN消息
        if (MyCAN_Receive(&RxID, &RxLength, RxData) == HAL_OK);

			if(RxData[0]==0xAA&&RxData[1]==0xBB&&RxData[2]==0x13&&RxData[3]==0xCC)
			{
					xEventGroupSetBits(EventGroupHandle,V_C_T_FLAG);
			}
    vTaskDelayUntil(&preTime, 1000);
		}
  /* USER CODE END CANCommTask */
}

内存:

https://zhuanlan.zhihu.com/p/1975520017564067749https://zhuanlan.zhihu.com/p/1975520017564067749

https://blog.csdn.net/m0_74739916/article/details/149198729?spm=1011.2415.3001.5331https://blog.csdn.net/m0_74739916/article/details/149198729?spm=1011.2415.3001.5331stm32f103c8t6:

ROM:64K ARM:20K

stm32f103c8t6 ARM一点少容易被撑死。

1. pflash 分区(非易失性,断电数据保留)

存放程序代码、只读数据 ,特点是只读 / 少写、断电不丢失,是程序的 "静态基础":

分区名称 作用说明 核心特性
.text(函数) 存放编译后的机器指令(程序的执行逻辑,如函数体、算法流程的二进制代码) - 程序启动后,CPU 从这里取指令执行 - 只读,运行时不可修改
.rodata 存放只读数据 (全局 const 常量、字符串字面量,如 const int a = 10; - 只读,防止程序意外篡改数据 - 直接存储在 pflash,节省 RAM 空间

2. ram 分区(易失性,断电数据丢失)

存放动态数据、运行时临时空间 ,特点是可读写、断电清空,是程序的 "动态工作区":

分区名称 作用说明 核心特性
.bss 存放未显式初始化的全局 / 静态变量 (默认值为 0,如 int global_var; - 程序加载时,系统自动清零 - 仅占内存 "地址空间",不占可执行文件体积
.data 存放显式初始化的全局 / 静态变量 (如 int global_var = 10; - 程序加载时,从可执行文件中加载初始值 - 运行时可修改,属于 "动态数据"
.Stack(栈区) 存放函数调用的临时数据(局部变量、函数参数、返回地址等) - 自动管理:函数调用时分配,返回时释放 - 空间小、速度快,遵循 "后进先出(LIFO)"
.Heap(堆区) 存放动态分配的内存 (如 C malloc、C++ new 申请的内存) - 手动管理:程序员控制分配(malloc)和释放(free) - 空间灵活,按需扩容(但需注意内存泄漏)
.Reg(寄存器区域) 处理器最核心的临时存储(存放运算中间值、指令指针、函数调用上下文) - 速度极快(比 RAM 快数倍) - 数量少(几十个通用寄存器),硬件 / 编译器自动调度

3. 程序运行流程(pflash ↔ ram 协同)

  1. 启动阶段 :程序从 pflash.text 段开始执行(CPU 读取指令),rodata 提供只读常量支持。

  2. 运行阶段

    • 全局变量(.bss/.data )、局部变量(.Stack )、动态内存(.Heap )在 ram 中动态分配 / 使用;
    • 寄存器(.Reg )作为 "高速中转站",衔接 CPU 运算与内存数据,加速程序执行。
  3. 关键设计逻辑

  • "静态存储(pflash) + 动态存储(ram)" 分离:既保证程序 / 常量的 "持久化"(断电不丢),又满足运行时数据的 "灵活性"(可读写、动态分配)。
  • 不同分区的 "管理方式" 差异.Stack 自动管理(简单但空间有限)、.Heap 手动管理(灵活但需警惕泄漏)、.bss/.data 关联全局变量生命周期。
相关推荐
数智化架构师-Aloong20 小时前
⚡️ PowerJob深度解析:Java生态下高并发分布式调度的终极选择
java·开发语言·分布式·系统架构
y***13641 天前
docker离线安装及部署各类中间件(x86系统架构)
docker·中间件·系统架构
inferno1 天前
若依框架(前后端分离版)开发环境搭建步骤
系统架构·ruoyi
软考考神2 天前
2026上半年软考高级系统架构设计师备考攻略:技术巅峰之路
系统架构·软考·软考备考
武子康2 天前
Java-179 FastDFS 高并发优化思路:max_connections、线程、目录与同步
java·开发语言·nginx·性能优化·系统架构·fastdfs·fdfs
一辉ComeOn2 天前
【大数据高并发核心场景实战】 数据持久化层 - 分表分库
java·大数据·分布式·mysql·系统架构
z_mazin2 天前
逆向Sora 的 Web 接口包装成了标准的 OpenAI API 格式-系统架构
linux·运维·前端·爬虫·系统架构
云雾J视界2 天前
当AI能写代码时,顶级工程师在做什么?大模型时代的系统架构思维重塑
人工智能·系统架构·思维重塑·能力边界·能力重构·系统定义
r***86982 天前
docker离线安装及部署各类中间件(x86系统架构)
docker·中间件·系统架构