1. 引言:BMS 与嵌入式开发的结合
电池管理系统(BMS)是电动汽车、储能系统、无人机等设备的核心安全与性能保障单元。其核心任务之一是实时、精确地采集电池组中各个电芯的电压与温度,并通过可靠的总线将数据上报给主控制器。STM32 系列微控制器凭借其丰富的外设、出色的性能与完善的生态,成为实现 BMS 从机(或称“子板”、“采集板”)的理想选择。
本教程将手把手带您完成一个基于 STM32 的 BMS 数据采集与通信模块的软硬件开发全流程,重点涵盖:
- 硬件设计要点:ADC 采样电路、温度传感器电路、隔离与保护。
- 软件驱动开发:STM32CubeMX 配置、ADC 多通道扫描与 DMA 传输、温度传感器读取。
- 通信协议实现:基于 CAN 总线或 UART 的定制通信协议设计。
- 系统集成与测试:数据打包、校验、发送与上位机解析。
通过本篇教程,您将获得一个可直接用于项目原型的完整解决方案。
2. 硬件系统架构与关键电路设计
2.1 整体系统框图
一个典型的 BMS 从机模块包含以下核心部分:
[STM32 MCU] <-(SPI/I2C)-> [AFE 或 多路ADC] | V [电压采样网络] ----> [电池电芯] [温度传感器(NTC/PTC)] ----> [电芯或铜排]- MCU:STM32F103/F407 等系列,需具备足够数量的 ADC 通道和 CAN 外设。
- 模拟前端 (AFE):对于高串数电池包,常采用专用 AFE 芯片(如 TI BQ76PL455A)进行电压采集,通过 SPI 与 MCU 通信。本教程为简化,直接使用 STM32 内部 ADC。
- 采样电路:由精密电阻分压网络、滤波电容及保护器件(TVS、保险丝)组成。
- 温度传感器:通常采用 NTC 热敏电阻,贴于电芯表面或 Busbar 上。
- 通信接口:CAN 收发器(如 TJA1050)用于组网通信,或 UART 转隔离模块用于点对点通信。
2.2 ADC 电压采样电路设计
对于单节电芯(标称 3.6V),电压采样电路相对简单。关键设计在于精度、安全与抗干扰。
电路示例与计算:
假设电芯电压最高为V_cell_max = 4.2V,STM32 ADC 参考电压V_ref = 3.3V。
为将 4.2V 分压至 3.3V 以内,并留有余量,设定分压比K = R2 / (R1 + R2) = 3.0 / 4.2 ≈ 0.714。
选取R1 = 10kΩ,则R2 = R1 * K / (1 - K) ≈ 10k * 0.714 / 0.286 ≈ 24.9kΩ,可取标称值24.9kΩ或25kΩ。
实际电压计算公式:V_cell = ADC_Value * (V_ref / 4095) * ( (R1 + R2) / R2 )= ADC_Value * (3.3 / 4095) * (34.9k / 24.9k)≈ ADC_Value * 0.000805 * 1.402 ≈ ADC_Value * 0.001128
PCB 布局要点:
- 分压电阻尽量靠近 ADC 输入引脚。
- 在 ADC 输入引脚处添加一个
0.1uF的滤波电容到地,以滤除高频噪声。 - 串联一个
100Ω的小电阻,与滤波电容构成 RC 低通滤波器,并限制注入电流。 - 在采样线入口处放置 TVS 管(如 SMAJ5.0A)进行过压保护。
2.3 温度采样电路设计(NTC 热敏电阻)
NTC 的阻值随温度升高而降低。我们将其与一个精密参考电阻串联,构成分压电路,MCU 测量分压点电压。
电路示例:VCC(3.3V) — [R_ref (10kΩ)] — [ADC_IN] — [NTC] — GND
温度计算流程:
- 读取电压:
V_ntc = ADC_Value * (V_ref / 4095) - 计算 NTC 电阻:
R_ntc = R_ref * (V_ntc) / (VCC - V_ntc) - 查表或公式计算温度:通常使用Steinhart-Hart 方程或其简化版(B 值公式)将电阻值转换为温度值。
3. 软件驱动开发:STM32CubeMX 与 HAL 库
3.1 工程创建与外设配置
- 打开 STM32CubeMX,选择您的目标 MCU。
- 配置 ADC:
- 启用
ADC1(或ADC2,ADC3)。 - 工作模式选择“Scan Conversion Mode”和“Continuous Conversion Mode”。
- 在
Parameter Settings中,设置合适的采样时间(如239.5 Cycles以提高精度)。 - 在
DMA Settings中,添加 DMA 请求,模式设为“Circular”,数据宽度都为Word。 - 在
Pinout View中,将需要用到的 ADC 通道对应的 GPIO 引脚配置为Analog模式。
- 启用
- 配置 CAN(如果使用):
- 启用
CAN1。 - 模式选择
Normal。 - 配置波特率(如
500kbps)。Time Quanta和Prescaler需要根据时钟计算。 - 自动重传
Enable。
- 启用
- 配置 UART(如果使用):启用相应 USART,配置波特率(如
115200),数据位8,停止位1,无校验。 - 生成代码:指定工程路径和 IDE(Keil/IAR/STM32CubeIDE),生成初始化代码。
3.2 ADC 多通道 DMA 采集代码实现
在生成的工程中,我们需要启动 ADC 的 DMA 采集,并处理数据。
// 在 main.c 的 /* USER CODE BEGIN PV */ 区域定义变量#defineADC_CHANNEL_NUM4// 假设采集4个电芯电压uint32_tadc_buffer[ADC_CHANNEL_NUM];// DMA 搬运的目标数组// 在 /* USER CODE BEGIN 2 */ 中启动 ADCHAL_ADC_Start_DMA(&hadc1,(uint32_t*)adc_buffer,ADC_CHANNEL_NUM);// 在 /* USER CODE BEGIN 4 */ 中编写 DMA 传输完成中断回调函数voidHAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*hadc){if(hadc->Instance==ADC1){// adc_buffer 中已按配置顺序更新了所有通道的数据// 可以在此处设置一个标志位,通知主循环处理数据adc_data_ready_flag=1;}}// 主循环中处理数据if(adc_data_ready_flag){adc_data_ready_flag=0;for(inti=0;i<ADC_CHANNEL_NUM;i++){// 将原始 ADC 值转换为电压值cell_voltage[i]=adc_buffer[i]*3.3f/4095.0f*VOLTAGE_DIVIDER_RATIO;// 可以加入滤波算法,如滑动平均滤波}}3.3 温度传感器读取与计算
// 假设 NTC 接在 ADC Channel 5floatread_temperature(void){uint32_tadc_value;floatvoltage,resistance,temperature;// 1. 读取 ADC 值 (可以使用单次转换或从 DMA 缓冲区获取)HAL_ADC_Start(&hadc1);HAL_ADC_PollForConversion(&hadc1,HAL_MAX_DELAY);adc_value=HAL_ADC_GetValue(&hadc1);HAL_ADC_Stop(&hadc1);// 2. 计算电压和电阻voltage=adc_value*3.3f/4095.0f;resistance=REF_RESISTOR*voltage/(3.3f-voltage);// 10k 上拉// 3. 使用 B 值公式计算温度 (简化版,需根据具体 NTC 参数调整)// T = 1 / (1/T0 + 1/B * ln(R/R0))// T0=25°C=298.15K, R0 为 25°C 时的阻值,B 为 B 值常数floatT0=298.15;floatR0=10000.0;// 10k NTCfloatB=3950.0;temperature=1.0/(1.0/T0+log(resistance/R0)/B);temperature=temperature-273.15;// 开尔文转摄氏度returntemperature;}4. 通信协议设计与实现
4.1 基于 CAN 总线的通信协议
CAN 总线具有高可靠性、多主、抗干扰强等特点,非常适合 BMS 网络。
定义报文 ID 与数据结构:
假设我们使用标准帧(11位 ID)。
- ID 分配:高 4 位表示帧类型,低 7 位表示从机地址。
0x0X0: 心跳/状态帧 (主机 -> 从机)0x0X1: 数据请求帧 (主机 -> 从机)0x1X0: 电压温度数据帧 (从机 -> 主机)0x1X1: 报警帧 (从机 -> 主机)
- 数据帧示例(0x1X0):
Byte0-1: 电芯1电压 (uint16_t, 单位 0.1mV) Byte2-3: 电芯2电压 Byte4: 温度1 (int8_t, 单位 °C) Byte5: 温度2 Byte6: CRC8 校验 (从 Byte0 到 Byte5) Byte7: 帧尾 (0xAA)
代码实现(发送数据):
voidsend_cell_data_via_can(uint8_tslave_addr){CAN_TxHeaderTypeDef tx_header;uint8_ttx_data[8];uint32_ttx_mailbox;tx_header.StdId=0x100|slave_addr;// 组合成完整 IDtx_header.ExtId=0;tx_header.RTR=CAN_RTR_DATA;tx_header.IDE=CAN_ID_STD;tx_header.DLC=8;// 数据长度 8 字节tx_header.TransmitGlobalTime=DISABLE;// 填充数据uint16_tvol1=(uint16_t)(cell_voltage[0]*10000);// 转换为 0.1mVtx_data[0]=(vol1>>8)&0xFF;tx_data[1]=vol1&0xFF;// ... 填充其他电压温度数据tx_data[6]=calculate_crc8(tx_data,6);// 计算 CRCtx_data[7]=0xAA;if(HAL_CAN_AddTxMessage(&hcan1,&tx_header,tx_data,&tx_mailbox)!=HAL_OK){// 发送失败处理}}4.2 基于 UART 的通信协议
对于点对点或简单系统,UART 也是常用选择。需要定义简单的帧结构来保证数据的完整性。
自定义帧格式:[帧头 0xAA] [长度] [命令字] [数据...] [CRC] [帧尾 0x55]
代码实现(发送数据):
voidsend_cell_data_via_uart(void){uint8_ttx_buffer[32];uint8_tidx=0;tx_buffer[idx++]=0xAA;// 帧头uint8_tdata_len=1+2*4+1*2;// 命令字 + 4个电压(uint16) + 2个温度(int8)tx_buffer[idx++]=data_len;tx_buffer[idx++]=0x01;// 命令字:上报数据for(inti=0;i<4;i++){uint16_tvol=(uint16_t)(cell_voltage[i]*1000);// 转换为 mVtx_buffer[idx++]=(vol>>8)&0xFF;tx_buffer[idx++]=vol&0xFF;}for(inti=0;i<2;i++){tx_buffer[idx++]=(uint8_t)(temperature[i]);// 温度,假设在 -40~125°C 范围内}uint8_tcrc=calculate_crc8(&tx_buffer[1],idx-1);// 从长度开始计算tx_buffer[idx++]=crc;tx_buffer[idx++]=0x55;// 帧尾HAL_UART_Transmit(&huart1,tx_buffer,idx,100);}5. 系统集成、测试与优化
5.1 数据打包与定时发送
在主循环或定时器中断中,周期性地执行采集、处理、发送流程。
// 在 main.c 的 while(1) 循环中while(1){// 1. 检查 ADC 数据就绪标志if(adc_data_ready_flag){process_adc_data();// 处理电压数据read_temperatures();// 读取温度adc_data_ready_flag=0;}// 2. 定时发送 (例如每 100ms)if(HAL_GetTick()-last_send_tick>100){send_cell_data_via_can(SLAVE_ADDR);// 或 via UARTlast_send_tick=HAL_GetTick();}// 3. 处理接收到的命令(如休眠、校准指令)check_and_handle_rx_command();HAL_Delay(1);}5.2 上位机测试(以 CAN 为例)
可以使用USB-CAN 适配器配合PCAN-View、ZLG USBCAN配套软件或Python-can库进行测试。
- 连接硬件,上电。
- 在上位机软件中设置相同的 CAN 波特率。
- 监听 ID 为
0x1X0的报文,检查数据解析是否正确。 - 可以发送请求帧 (
0x0X1),测试从机的响应功能。
5.3 抗干扰与可靠性优化
- 软件滤波:对 ADC 采样值进行滑动平均滤波或中值滤波,消除尖峰噪声。
- 硬件看门狗:启用 STM32 的独立看门狗(IWDG),防止程序跑飞。
- 通信重发机制:在 CAN/UART 发送失败后,加入重试逻辑。
- 数据校验:务必使用CRC校验,确保数据传输的完整性。
- 隔离设计:在采样电路与 MCU 之间、通信接口处使用光耦或数字隔离器,提高系统抗共模干扰能力和安全性。
6. 总结
本教程详细介绍了从硬件选型、电路设计到软件驱动、通信协议实现的完整流程。STM32 HAL 库大大降低了开发门槛,而清晰的协议设计是系统稳定运行的关键。希望这篇教程能成为您进入 STM32 BMS 开发领域的坚实基石。在实际项目中,请务必根据具体的电池参数、安全法规和系统架构进行深入设计与充分测试。