Gitee项目源码:BMS: 基于STM32f103开发的BMS电池管理系统项目 (gitee.com)

关于BMS电池管理系统前言

为什么需要BMS(电池管理系统)

BMS全称是BatteryManagementSystem,顾名思义它就是用来专门管理电池的系统。

11488597d2a9490eb86103519ae342ad.jpeg

随着新能源汽车(纯电、油电混合)、储能设备、机器人、UPS、电瓶车、移动式电动工具等对电池的需求越来越多。如何解决电池安全性、延长电池寿命、性能优化等已经成了至关重要的问题。而且不同类型电池之间他们的特性及电参数是不一样的(如常见的动力锂电池18650,BYD的刀片电池,宁德时代的钠离子电池等),不过目前市面上绝大多数都是锂电池(三元锂,磷酸铁锂)为主,主要都是根据锂电池的化学特性来管理,所以我这次主要是用18650来学习。

总的来说BMS就是一套用来解决专门根据电池的特性管理多节电池充放电 、安全保护、信息监控(如电池容量百分比 、电池的温度、电池循环充电等)的软硬结合的系统,就是一套嵌入式系统。

BMS的架构设计种类

(1)单节或少量几节电池时。一般不需BMS,只需要简单的充放电保护即可,如TP4056。

e6563e8e076949879a8d7d7e29695336.jpeg

(2)几节、几十节电池时。一般使用一体式BMS设计,所有功能单元在一块PCBA中实现。(本项目的开发板就是这种)

52e3afbb88e64c0ea29800c7ad9fb168.jpeg

(3)几百几千上万节电池时。一般使用分布式BMS设计;由1个BMU和N个BCU/BAU组成。

ef3d47d4e59547edb5fd4ace0e728317.png

BMS系统硬件解读

整体的工作原理

从硬件角度上来说因为本项目主要还是通过STM32f103c8t6和BQ7692来交互获取参数,所以结构构图上的其他硬件就不还太多,详细的后面原理图有介绍。

cb509e914f19466d8000472379fe4a0f.jpeg

BMS上电后先是主控(BCU)跟前端模拟芯片(BMU)建立通讯,并对前端模拟芯片(BMU)温度采集、电池参数等相关数据做初始化。然后BCU就会进入全局的监控状态。前端模拟芯片(BMU)会对采集到的数据先做一个ADC处理, 后面主控(BCU)会先从前端模拟芯片(BMU)获取采样数据做二次处理,主控(BCU)的应用层就会根据二次处理数据后就会响应相关的GPIO、CAN等外设相对充放电管理,电池保护等的业务逻辑的处理。

BMS中的术语:

  1. BCU通常负责监控和控制整个电池组的工作状态。它可以收集来自各个BMU的数据,并进行综合分析,从而做出决策,如调整充放电策略、监测电池健康状况等。

  2. BMU主要负责监控单个电池模块的状态,包括电压、电流、温度等参数。BMU还可能具备均衡电池模块的功能,以确保各个电池单元的一致性和延长电池寿命。

  3. BAU通常指的是辅助电池管理系统的一些额外功能部件,如外部接口、通信模块、显示模块等。BAU的作用是增强BMS的功能,提供额外的支持和服务。

BMS电池仓

10d88a3aa21547eda81727dc0cad67e9.png

从原理图上我们可以分析,电池结构整体上是一组串联电池(U1 U2 U3)、开关(S1)及其两个正负极端子(P3/P1)组成。其中每一节电池的正负极又引出一条采样点(CELLx),一共有6条。这6条主要是给BQ7692(BMU单元)用来获取电池信息采样的电路。以下是BMS电池仓的实物图。

d081303e682a4ac0bbb2985d7e02bfe4.png

非充电部分

主控部分跟大部分的STM32最小开发板一样,电源、STM32芯片、 复位电路、8M晶振、SWD、BOOT选着这一些。不过最小系统部分的电源来源有两个就是原理图上通过接口的3.3v和GND来供电,另外一的电源则通过电池仓,就是使用电池仓上的CELLx的采样线通过保护板上的DCDC模块进行降压后或取电源。

通讯接口方面有CAN、RS485以及UART。CAN使用的是NXP的TJA1050芯片,RS485使用的是美信的MAX3485芯片,不过目前RS485上是空贴。

原理图及实物图如下:

f4d32ac9963740d99f3cc1d46a400a88.png

b1d02f6e714148ce9bb57cc2f3c59998.png

充放电回路

开发板充电和放电使用的同一条回路,也就是说充电的时候不能放电,放电的时候不能充电。因为大多数的BMS都没有同时充放电的需求,所以这里的开发版使用了充电和放电同一条回路的设计。同时充电回路有3道开关:2个MOS、1个机械开关及其4个PACK+/PCAK-,BAT+/BAT。

实现原理是利用了2个MOS管(Q6、Q1,华羿微HY3210B)进行反接,通过BQ7693芯片对DSG和CHG这两个引脚输出电平信号从控制MOS管的门极,使其实现充电还是放电的功能。

充电回路: 充电器+ -> PACK+ -> BAT+ -> BAT- -> Q1 -> Q6 -> BAT -> PACK- -> 充电器-

2403eeba0b974865978228141a457382.png

放电回路:BAT+ -> PACK+ -> 负载+ -> 负载- -> PACK ->Q6 -> Q1 -> BAT-

b12d377c198947bda2cbe8adcd2776aa.png

采样电路

用于采集回路的电流状态,实现使用1个0.005R的精准电阻,电阻通过两头的电路(SRP、SRN)100R的电阻放大后再传回去给BQ7692。

9b7889cba6dc42b2aa5119d019b31dde.png

BQ7692芯片解读及其外围电路

BQ7692003PWR是前端模拟芯片,一个单片机的外部扩展 ,主控STM32(BCU)只管后端的业务逻辑,不管模拟功能 ,主控通过BQ7692003PWR通信进行管理电池。

a0c93e0115a64fc698228fa755a9ed8e.png

各功能引脚的介绍:

  1. BAT&VSS:芯片供电,这里的来源于电池。

  2. REGOUT&REGSRC:BQ1692需要外接电容,这两个是用来外接电源输入和输出。

  3. TS1:BQ7692这颗芯片有休眠功能,当给这个引脚写入信号相当唤醒这个颗芯片。这颗芯片有3路唤醒源KEY1、WakeUP(PB15)、热敏电阻。

  4. CHG&DSG:这两个是控制MOS管(上面充放电路板已解释)。

  5. ALERT:是报警电路。

  6. SRP&SRN:这两个充放电回回路采样(上面充放电路板已解释)。

  7. VC0~VC5:对应的是5节电池各个采样点CELL0~CELL5。

  8. NC:这里设计上就没有使用到。

被动均衡电路

这里的被动均衡电路思路是:这里我们直接拿CELL0和CELL1的电池(以下是称电BAT1)和CELL1和CELL2的电池(以下是称BAT2)这两路采样点举例子。如原理图所示,这里所有的电池都是串联在一起,如果BAT1已经充满(100%),而BAT2还没充满(随机定个80%),那么BAT1则要需要继续充电才能BAT2充电,但又产生了一个问题,因为电池之间是串联电路,如果BAT1已经充满电了还要继续充电那么就会“过充”,最后就会导致BAT1这一节电池报废。为了解决这一系列的问题,这里的电路设计就引入了一套被动均衡的设计。

09df037565df45fba7127cc987a0e9af.png

被动均衡的实现思路是BAT1就可以通过被动均衡的R7电阻进行做功消耗电能、压差误差在0.1。让BAT1从充满的状态变成未充满的状态(100%降到80%)。BAT2暂停充电,当采样电路检测到BAT1和BAT2处于同一水平(都在80%左右,在误差范围内 )未满状态,那么BAT1和BAT2就可以继续充电 。如果BAT1和BAT2之间还是有电量差距,再次重复以上的方法操作,根据这套策略最终2节电池都充满且不"过冲"。其他组电池也是同样的处理。

这里不用主动均衡的原因(毕竟是开发板,只是为了学习)是:1.不考效率。2.电路电力设计过于复杂。

BMS系统工作、充放电和过压过流演示

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

做充放电前一定要理清充放电工作原理及流程,用万用表测每个节点的状态,再三检查才上电工作。

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

e9260966ad984167a24341c8213ba95b.jpeg

因为本项目的是用RT-Rtread开发及Shell组件开发的BMS,所以上电开机进入上位机(115200bps)后按回车输入help就会进入脚本命令项。主要使用到命令都做了标注,如下图。

​
 \ | /
- RT -     Thread Operating System
 / | \     3.1.5 build Sep 11 2024
 2006 - 2020 Copyright by rt-thread team
msh >[I/bq76920] BQ769X0 Initialize successful!
[I/monitor] Entry Standby Mode
​
msh >help
RT-Thread shell commands:
BMS_CmdInfo      - Open Info                            //打印电池信息
BMS_CmdOpenDSG   - Open DSG                             //打开放电MOS
BMS_CmdCloseDSG  - Close DSG                            //关闭放电MOS
BMS_CmdOpenCHG   - Open CHG                             //开启充电MOS
BMS_CmdCloseCHG  - Close CHG                            //关闭充电MOS
BMS_CmdOpenBalan - Open Balance                         //开启均衡
BMS_CmdCloseBala - Close Balance                        //关闭均衡
BMS_CmdLoadDetec - Load Detect                          //在未开启充放电MOS情况下运行此命令可检测是否接了电子负载仪
BMS_CmdOpenInfo  - Open Info Printf
BMS_CmdCloseInfo - Close Info Printf
version          - show RT-Thread version information
list_thread      - list thread
list_sem         - list semaphore in system
list_event       - list event in system
list_msgqueue    - list message queue in system
list_timer       - list timer in system
help             - RT-Thread shell help.
ps               - List threads in the system.
free             - Show the memory usage in the system.
​
msh >
输入BMS_CmdInfo命令按回车后,就会打印当前状态的信息:
  1. Battery Real Capacity:表示电池实际容量值(额定容量值是出厂给的,SOH电池健康度计算也可以用实际值/额定值)

  2. Battery Remain Capacity:表示电池剩余容量值

  3. Battery SOC:电池剩余容量百分比(剩余值/实际值)

  4. Cell Max Voltage:最大电芯电压值

  5. Cell Min Voltage:最小电芯电压值

  6. Cell Max Voltage Difference:最高和最低电芯电压差值

  7. Cell Average Voltage:电芯平均电压

  8. Battery Real Power:电池实时功率,电流*总电压

  9. Battery Voltage:整串电池总电压

  10. Battery Current:电池实时电流

  11. Cellx Voltage:电芯电压,cell1对应电池板上的第一串电芯电压。

充/放电操作过程

6a74ceea2e58466d991529aef55d3863.png

充电

一定要按照操作步骤来,先将电池板上的充放电开关一定要处于断开状态(字母O一边按下是断开,字母I一边按下是接通),然后夹上充电器的电源夹子到BMS控制板的PACK-、PACK+两端,然后给充电器插上电源(如果使用自己的充电器一定要注意不要大于21V),然后拨动电池板充放电开关至开启状态,在串口终端输入 BMS_CmdOpenCHG 命令开启充电MOS开关,由于均衡策略的原因如果电芯正在均衡是不会立即开启充电MOS开关,需要输入BMS_CmdCloseBalance 关闭均衡命令后才会开启充电MOS然后电池正常充电(充电器绿灯变红灯),电流采样的数据(充电器的充电电流会随着电池电压变化而变化,电池电压越高充电电流越小,反之越大,不会超过充电器额定电流值)

放电:放电跟充电操作差不多操作,唯一不相同地方无需关闭均衡开关。

充/放电过压欠压

  1. 这里仅演示充电过压功能,放电欠压和充电过呀操作都差不多

  2. 接线顺序:先把电池板充放电开关拨动处于关闭状态,然后将BMS控制板和电池板的所有线连接上,接着继续接上充电器的正负极到PACK端,然后插上充电器电源,拨动电池板的充放电开关至开启状态。

  3. 为了尽快演示充电过压保护,我们先修改一下充电过压的值为3.92(因为我的电池板最高那节电芯电压静态值为3.7x),过压恢复的值为3.90,如下图,然后输入开启充电MOS开关命令,此时充电器正常充电,当最高节电芯达到过压保护值3.92V时,会触发过压保护,此时系统会断开充电MOS开关,等到最高那节电芯电压降为3.90V以下然后恢复充电MOS开关继续充电,如下图。

313ee5a5b3074aefbcd0913c80cf8802.png

74886cb1ecbc47309746e8c62b43e734.png

充/放电过流放电短路

  1. 放电过流演示需要用到电子负载仪,接线方式还是按照充电演示顺序,先断开电池板的充放电开关,等所有线接上后,先给负载仪上电,再开启电池板上的充放电开关。

  2. 普通的18650电池不能超过1C放电速率,我推荐购买的电池额定容量是2000mA/H的,所以软件默认设置的保护电流为2A电流,BMS控制板的放电过流配置参数在 drv_softi2c_bq769x0.c文件,这个参数的详细计算方法我也写了注释。我们将这个放电过流保护参数设置为1.6A如下图(当然也可以不用改直接将放电负载仪的放电电流设置为2.5A,启动放电负载的瞬间系统会直接关闭放电MOS开关,我是为了安全才这么操作的),再修改bms_config.h文件中的 INIT_OCD_RELIEVE 参数,该参数为触发放电保护过后恢复解除延时时间,在这里为了更快的见到演示的效果我们设置为5秒。然后设置放电负载仪的放电电流为2A,接着输入开启放电MOS命令 BMS_CmdOpenDSG,再接下来按下放电负载仪的开关开启放电。

de07d1dba7244951800dbddce9dac4dc.jpeg

b79d31c5f36d4f0c8aaae6e8de5fac97.jpeg

此时的现象为开启放电负载仪放电后检测到超过放电过流预设值,会等待 INIT_OCD_DELAY 时间后触发放电过流保护,接着5秒过后恢复放电MOS。如下图

4d6e5b78497d491cb950d784b8c7ec7b.jpeg

2ed9c21e0018469cbf33fe6297dab5ba.jpeg

充电过流操作也就不演示了,唯一的区别就是充电过流BQ芯片不接管,只接管放电过流,在BMS软件中充电过流的保护由软件层接管了。放电短路操作也不演示了,会产生火花,流程跟放电过流,最好不要自己演示,短路会产生很大电流。

BMS业务源码解析及软件方面整体的框架

整体框架

整体框架(框里的内容有些笔误)分了应用层、中间层、驱动层、硬件层4层,其中应用层又分为“业务”和“全局变量”2块。

eead55f5b2044ce6ad4447843a961aa0.jpeg

硬件层:硬件层负责给驱动层提供硬件的直接访问,如通讯协议的起始位、读写时序、停止位、I/O读取等等的封装,源码中类似这些“I2C_Start”、“SCL_H”的函数,基本都是硬件层的封装。

451ba36457da4a14b36e616b2d71eeb2.png

驱动层:驱动层负责给应用层或者给中间件提供统一的操作接口。隐藏了硬件的物理细节,并处理硬件可能发生的错误,并向系统报告这些错误。源码中类似这些“I2C_TransferMessages”、“BQ769X0_WriteRegisterByteWithCRC”的函数,基本都是驱动层的封装。不过像BQ开头的文件都是TI官方提供的驱动代码,拿来改了做适配。

a691fedbd0654e32a5bbe0cadf4905f3.png

中间件:中间件是实现具体某一些方面功能的操作逻辑的封装,方便给应用层的业务逻辑的调用和项目移植。当移植或者或者新增的业务需要实现某些功能时,就不在需要从头“造轮子”直接调用中间层API即可。源码中类似这些“Bms_HalMonitorCellVoltage”的这些都是做了二次封装的函数,基本都是中间件的封装。

5e0c4a5c58ca4e099b185967e006d214.png

应用层:应用层是实现需求和业务逻辑与用户之间交互、状态监控等的层级,是所有层级最直观的一层。业务需求与业务与业务之间的交互通过全局变量的实时参数进行实现,后面会具体说明每一块业务的分工。例如要实现一个充放电的保护业务,那么业务模块就会调用中间层、者驱动层相关的函数已经一些全局变量的参数来实现需求。同时对业务块进行(RT-Thread)系统编程,并内存管理、抢占优先级进行分配,避免业务之间在高并发的时候死锁、饥饿、竞争等等问题。源码中类似这些“BMS_MonitorInit”函数都是应用层的函数。

d3d6ce3108f54af18b023abda10180c3.png

业务块源码解析

从main函数进来之后先是做了主控(STM32)上的初始化,然后就进入了到了 "BMS_SysInitialize();"函数里面去,BMS_SysInitialize();函数里面是所有BMS的业务逻辑。

int main(void)
{
  /*.........*/
  HAL_Init();
  /*.........*/
  SystemClock_Config();
  /*.........*/
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  MX_CAN_Init();
  /*.........*/
  BMS_SysInitialize()       //BMS的所有业务逻辑
  {
     /**********/
     // 硬件初始化
    I2C_BusInitialize();
    BQ769X0_Initialize(&InitData);
     
     // 软件初始化
    BMS_MonitorInit();      // 电池监控初始化
    BMS_ProtectInit();      // 电池保护初始化
    BMS_AnalysisInit();     // 电池分析初始化
    BMS_EnergyInit();       // 能量管理初始化
    BMS_InfoInit();         // 信息管理初始化
    //BMS_CommInit();       // 通信管理初始化
    /**********/
  }
  /*.........*/
  while (1)
  {
    //CAN_SendTest();
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    //rt_kprintf("Hello RT-Thread!\r\n");
    rt_thread_mdelay(1000);
  }
}

进入“BMS_SysInitialize();”函数后,先是跟BQ7692(IIC协议)建立通讯,然后获取到数据对BQ7692并对初始化。后面便是到了各个业务逻辑上的内容了。

电池监控

电池监控业务负责对采样数据的更新以及数据的处理:

  • 电池监控:负责从BMU获取采样数据,并对数据进行处理,进行实时更新处理。

  • 系统监控模式:负责从充放电回流电流判断的BMS当前的状态,主要目的是唤醒BQ7692以及一些调试信息。

    BMS_MonitorInit()   // 电池监控业务   从BQ芯片获取原始数据
    {
        rt_thread_t thread; 
        /*......*/
        BMS_MonitorTaskEntry()              //电池监控线程    
        {
            BMS_MonitorBattery()                    //电池监视任务(上层业务),下面是底层的调用
            {
                //整体做了HAL(中间)封装,方便移植。
                Bms_HalMonitorCellVoltage();            // 单体电芯电压
                Bms_HalMonitorBatteryVoltage();         // 电池组电压
                Bms_HalMonitorCellTemperature();        // 电池温度
                Bms_HalMonitorBatteryCurrent();         // 电流采样由软件触发
                Bms_HalMonitorBatteryCurrent();         // 电流采样由硬件中断触发,太麻烦了,每次烧写都得重新给BQ重新上电
            }
            BMS_MonitorSysMode()    //系统模式监控换转换
            {
                /*
                通过充放电回流电流判断的BMS当前的状态,主要目的是唤醒BQ7692以及一些调试信息。
               */
            }
        }
        rt_thread_mdelay(MONITOR_TASK_PERIOD)   //每2s执行一次   
    }

电池监视功能块实现的过程,这里以单体电芯电压为例子,其他子功能功能模块也是大致思路和框架。也是通过一层层的函数往下调,最后通过IIC从前端芯片BMU(BQ7692)获取数据,最后做一些数据处理、排序把值返回给全局变量里面去。这块主要是做数据的更新。

        BMS_MonitorBattery()    //电池监视任务(上层业务),下面是底层的调用
        {
            Bms_HalMonitorCellVoltage() // 单体电芯电压
            { 
                BQ769X0_UpdateCellVolt()     //从BQ7692上获取电池采样数据
                {
                    BQ769X0_ReadBlockwithCRC()          //上层驱动,BQ7692驱动,用HAL设计思路做了一层封装。
                    {
                        I2C_TransferMessages()          //下层驱动,IIC驱动
                        {
                            I2C_Restart()
                            {
                                SDA_H()
                                {
                                    HAL_GPIO_WritePin() //HAL库
                                }
                            }
                        }
                    }
                }
                BubbleSort();                   // 最后进行冒泡排序把值返回给全局变量里面去
            }

系统模式在通过充放电回流电流判断的BMS当前的状态,主要目的是唤醒BQ7692以及一些调试信息,这一块不太复杂不作太多介绍。

            BMS_MonitorSysMode()    //系统模式监控换转换
            {
                /*
                通过充放电回流电流判断的BMS当前的状态,主要目的是唤醒BQ7692以及一些调试信息。
               */
            }

电池保护

电池保护业务负责对电池充放电、温度进行实时保护:

  • 电池保护监控(软件) :判断充电、放电、待机、睡眠模式下的不同状态做软件保护处理。

  • 保护解除监控:判断电压、电流等这块等待时间是否结束,如果时间到了且还是超出安全

    值,那就继续等待如果低于安全值,就解除保护,继续充放电。

  • 电池保护监控(硬件):这些电池保护处理是由硬件触发,当当前采样数据超过安全值就会

    触发保护处理

 BMS_ProtectInit()   // 电池保护业务   保护电池过充过放 做了软保及硬保
    {
        rt_thread_t thread; //创建了电池保护控线程
        /*......*/
        BMS_ProtectTaskEntry()                  //电池保护任务(上层业务),下面是底层的调用
        {
            BMS_ProtectTiggerMonitor()          // 保护触发监控(这是软件触发,有一些保护是由硬件中断触发)
            {
                       /***********************************************
                              判断当前是什么系统是处于哪一个状态
                       ***********************************************/
                switch(BMS_GlobalParam.SysMode)
                {
                      BMS_MODE_CHARGE:      BMS_ChargeMonitor();        //充电监控
                      BMS_MODE_DISCHARGE:    BMS_DischargeMonitor();      //放电监控
                      BMS_MODE_STANDBY:      BMS_StandbyMonitor();        //待机监控
                      BMS_MODE_SLEEP:        // 睡眠暂时没什么可监控的      //睡眠监控
                }     
            }
            BMS_ProtectRelieveMonitor()         //保护解除监控
            {
                /*************************************************************
                     判断电压、电流等这块等待时间是否结束,如果时间到了且还
                     是超出安全值,那就继续等待。如果低于安全值,就解除保护,
                     继续充放电。
                *************************************************************/
            }
            rt_thread_mdelay(PROTECT_TASK_PERIOD);  //每2s执行一次
        }
        
        /*
            后面的函数都是硬件保护触发
        */
        
        // 放电过流(OCD)硬件触发
        void BMS_ProtectHwOCD(void)
        // 放电短路(SCD)硬件触发
        void BMS_ProtectHwSCD(void)
        // 充电过压(OV)硬件触发
        void BMS_ProtectHwOV(void)
        // 放欠过压(UV)硬件触发
        void BMS_ProtectHwUV(void)    
    }

电池分析

进入函数后电池分析会使用“BMS_AnalysisCapAndSocInit()”先进行初始化,里面会对soc计算、剩余容量、实际容量进行初始化。

这块都是做一些计算,主要分成三块,都根据全局变量的实时采样做一些最大电压差、平均电压、实时功率等,温度校准等一些的计算。最后“BMS_AnalysisSocCheck();”这个函数是做锂电的Soc算法的计算。

    BMS_AnalysisInit(); // 电池分析业务   做电池的电压、压差计算容量校准及Soc估量算法
    {
        rt_thread_t thread; //创建了电池分析线程
        /*......*/
        BMS_AnalysisTaskEntry()                 //电池分析任务(上层业务),下面是底层的调用
        {
            BMS_AnalysisCapAndSocInit()     // 容量和SOC上电初始化
            {
                 /***************************************************************
                    soc计算,实际容量后面再完善,涉及到完整充放电流计算、老化损耗、
                    温度特性曲线、信息存储模块,剩余容量的初始化。
                 ***************************************************************/
            }
            
            BMS_AnalysisEasy()              // 简单分析,通过数据直接进行计算就能得到的
            {
                // 最大电压差、平均电压、实时功率、最大和最小电压的一些计算
            }
            BMS_AnalysisCalCap()            // 实时校准容量涉及因素:温度、完整充放电、老化等等
            {
​
            }
            BMS_AnalysisSocCheck();         // soc检查
            {
                /***************************************************************
                    温度校准,锂电池充放电时温度的变化会影响充放电时电压与时间的
                    关系,进而影响电池实时容量这里只做温度的校准,校准的是实际容量。
                ***************************************************************/
                BMS_AnalysisOcvSocCalculate()       // 开路电压法soc计算
                {
                        /*****************************************************************
                            开路电压法的基本原理是通过测量电池在没有电流流动(即开路状态)
                            下的电压来估算SOC。这是因为电池的OCV与其SOC之间存在一定的关系,
                            这种关系可以通过实验数据建立起来。当电池处于静置状态一段时间后,
                            其端电压会稳定在一个值,这个值就是OCV。通过查找预先标定好的OCV-
                            SOC曲线或表格,可以得到近似的SOC值。
                        *****************************************************************/
                }
                BMS_AnalysisAHSocCalculate()        // soc = 实时积分的容量 / 电池包实际容量
                {
                        /*****************************************************************
                            基于对电池充放电过程中电流的累积测量来确定电池的剩余电量。需
                            要一个已知的初始SOC值,使用充电回路上高精度的电流电阻来连续
                            测量电池的电流,将测得的电流与相应的时间段相加,得到累积的电
                            荷量,根据累积的电荷量来更新 SOC 值。
                        *****************************************************************/
                }
            }
​
            rt_thread_mdelay(PROTECT_TASK_PERIOD);  //每2s执行一次
        }
    }

能量管理

这块的业务整体分为2块,当前系统状态(充电、模式、待机)选择判断,如果有的电池的电量没满,有些已满,那么调用硬件上的R39电阻进行对已满电的电池操做定时的放电处理,直到所有的电池的电量到达了一个允许的误差均值为止

    BMS_EnergyInit();   // 能量管理业务   做电池的充放电管理及能量均衡
    {
        rt_thread_t thread; //创建了能量管理线程
        /*......*/
        BMS_EnergyTaskEntry()               //能量管理任务(上层业务),下面是底层的调用
        {
            BMS_EnergyBalanceFilter()       // 充放电管理
            {
                switch(BMS_GlobalParam.SysMode)
                {
                    /**********************************************************************
                      这里对当前做一些对当前系统状态(充电、模式、待机)选择判断,是否进行充放电
                    **********************************************************************/
                }
            }
            BMS_EnergyBalanceStart()        // 均衡管理
            {
                 /**********************************************************************
                      接受上面的返回值对电池进行定时的均衡操作,这里是实际调用硬件操作。
                 **********************************************************************/
            }
        }
        rt_thread_startup(thread);
         /*......*/
        BMS_BalanceTimerEntry()             // 定时器能量管理均衡
        {
             /**********************************************************************
                                这里主要都是做一些以均衡策略的定时
             **********************************************************************/
        }
            
        rt_thread_mdelay(PROTECT_TASK_PERIOD);  //每2s执行一次
        }
    }

信息管理

这里主要做一些信息打印管理以及电池仓的总容量的指示灯控制

    BMS_InfoInit();     // 信息管理业务   电池健康、电池健康充放状态等的打印信息
    {
        rt_thread_t thread; //创建了信息管理线程
        /*......*/
        BMS_InfoTaskEntry()                 //信息管理任务(上层业务),下面是底层的调用
        {
            if (FlagInfoPrintf == true)
            {   
                BMS_InfoPrintf()            //做信息打印
                {
                    /***********************************************
                        打印一些电池包实时容量、电池包剩余容量等一些
                        实时信息
                    ***********************************************/
                }
            }
            BMS_InfoBatCapacityIndicator()   // 电池容量指示灯
            {
                 /***********************************************
                    根据实时采样电池总容量来打开当前的电池容量指示灯
                 ***********************************************/
            }
            rt_thread_mdelay(INFO_TASK_PERIOD);     
        }
    }

通信管理

电池的网络通讯、上位机、GUI、CAN、RS485等功能。

    BMS_CommInit(); // 通信管理业务   电池的网络通讯、上位机、GUI、CAN、RS485等功能
    {
        rt_thread_t thread; //创建了通信管理线程
        /*......*/
        BMS_CommTaskEntry()                 //通信管理任务(上层业务),下面是底层的调用
        {
            /* 目前未支持,待后续支持 */
            rt_thread_mdelay(PROTECT_TASK_PERIOD);  //每2s执行一次
        }
    }

全局变量源码

全局变量要结合业务板块来思考和理解

/*****************************************************************************************************************/
BMS_MonitorDataTypedef BMS_MonitorData              //监控数据
typedef struct
{
    float CellTemp[BMS_TEMP_MAX];                    // 采样温度,温度数据会从小到大排序,几路温度
    float BatteryVoltage;                           // 电池总电压
    float BatteryCurrent;                           // 电池组电流
    BMS_CellDataTypedef CellData[BMS_CELL_MAX];       // 电芯数据,电压数据会从小到大排序
    float CellVoltage[BMS_CELL_MAX];                 // 电芯电压,未排序的
    uint32_t CellTempEffectiveNumber;                // 有效值的温度数量
}BMS_MonitorDataTypedef;
typedef struct
{
    float CellVoltage;      // 电芯电压
    uint32_t CellNumber;    // 电芯的编号
}BMS_CellDataTypedef;
​
/*****************************************************************************************************************/
B0769X_SampleDataTypedef B0769X0_SampleData             //采样数据
typedef struct
{
    float CellVoltage[BQ769X0_CELL_MAX];        // 单节电芯电压
    float TsxTemperature[BQ769X0_TMEP_MAX];     // 热敏电阻温度
    float BatteryCurrent;                   // 电池包总电流
    float BatteryVoltage;                   // 电池包总电压
    float DieTemperature;                   // ic温度,目前还未测试成功
}BQ769X0_SampleDataTypedef;
​
/*****************************************************************************************************************/
BMS_ProtectParamTypedef BMS_ProtectParam
typedef struct              // 跟保护相关的参数结构体
{
    BMS_ProtectParamTypedef param;
    BMS_ProtectAlertTypedef alert;
}BMS_ProtectTypedef;   
// 跟保护相关的参数结构体
typedef struct
{
    float ShoutdownVoltage; // 关机电压(V)
    
    float OVProtect;        // 充电过压保护电压(V)                  由硬件完成
    float OVRelieve;        // 充电过压恢复电压(V)
    float UVProtect;        // 放电欠压保护电压(V)                  由硬件完成
    float UVRelieve;        // 放电欠压恢复电压(V)
    
    float OCCProtect;               // 充电过流阈值(A)
    float OCDProtect;               // 放电过流阈值(A)            由硬件完成
    
    BMS_OVDelayTypedef OVDelay;     // 充电过压保护延时             由硬件完成
    BMS_UVDelayTypedef UVDelay;     // 放电欠压保护延时             由硬件完成
    BMS_OCDDelayTypedef OCDDelay;   // 放电过流保护延时             由硬件完成
    BMS_SCDDelayTypedef SCDDelay;   // 放电短路保护延时             由硬件完成
    
    uint8_t OCDRelieve;     // 放电过流恢复(S)
    uint8_t SCDRelieve;     // 放电短路恢复(S)
    
    uint8_t OCCDelay;       // 充电过流延时(S)
    uint8_t OCCRelieve;     // 充电过流恢复(S)
    
    float OTCProtect;       // 充电过温保护(℃)
    float OTCRelieve;       // 充电过温解除(℃)
    float OTDProtect;       // 放电过温保护(℃)
    float OTDRelieve;       // 放电过温解除(℃)
    
    float LTCProtect;       // 充电低温保护(℃)
    float LTCRelieve;       // 充电低温解除(℃)
    float LTDProtect;       // 放电低温解除(℃)
    float LTDRelieve;       // 放电低温解除(℃)
}BMS_ProtectParamTypedef;    
// 报警枚举体
typedef enum
{
    FlAG_ALERT_NO   = 0x0000,       // 无报警触发
    
    FlAG_ALERT_OV   = 0X0001,       // 充电过压保护触发位                硬件触发
    FlAG_ALERT_OCC  = 0X0002,       // 充电过流保护触发位                软件触发
    FlAG_ALERT_OTC  = 0X0004,       // 充电过温保护触发位                软件触发
    FlAG_ALERT_LTC  = 0X0008,       // 充电低温保护触发位                软件触发
​
    FLAG_ALERT_CHG_MASK = 0x000F,   // 充电报警掩码
    
    FlAG_ALERT_UV   = 0X0010,       // 放电欠压保护触发位                硬件触发
    FlAG_ALERT_OCD  = 0X0020,       // 放电过流保护触发位                硬件触发
    FlAG_ALERT_SCD  = 0X0040,       // 放电短路保护触发位                硬件触发
    FlAG_ALERT_OTD  = 0X0080,       // 放电过温保护触发位                软件触发
    FlAG_ALERT_LTD  = 0X0100,       // 放电低温保护触发位                软件触发
​
    FLAG_ALERT_DSG_MASK = 0x01F0,   // 放电报警掩码
}BMS_ProtectAlertTypedef;
​
/*****************************************************************************************************************/
BMS_AnalysisDataTypedef BMS_AnalysisData            //分析数据
typedef struct
{
    // 这三个值目前不用管
    uint8_t SOH;    // 电池包SOH值          实际容量/额定容量
    uint8_t SOP;    // 电池包SOP值  
    uint8_t SOE;    // 电池包SOE值
​
​
    // 目前这两个值还未做,等后面再实现了
    uint32_t LoopCount;         // 电池包循环次数(完整的一个充放过程+1)
    float CapacityLoop;         // 电池包循环容量(A/H)
    
​
    // 下面的值已经实现了
    float SOC;                  // 电池包SOC值(剩余电量百分比)
​
    float AverageVoltage;       // 单体平均电压值(V)
    float MaxVoltageDifference; // 单体电芯最大电压差(V)
    float PowerReal;            // 电池包实时功率(W)
    float CellVoltMax;          // 单体电芯最大电压
    float CellVoltMin;          // 单体电芯最小电压
    
    float CapacityRated;        // 电池包额定容量(A/H)
    float CapacityReal;         // 电池包实时容量(A/H)         计算方法得进行一次完整的充放电计算
    float CapacityRemain;       // 电池包剩余容量(A/H)
}BMS_AnalysisDataTypedef;
​
/*****************************************************************************************************************/
BMS_EnergyDataTypedef BMS_EnergyData        //能量全局参数
typedef struct
{
    float SocStopChg;           // 停止充电SOC值
    float SocStartChg;          // 启动充电SOC值
    float SocStopDsg;           // 停止放电SOC值
    float SocStartDsg;          // 启动放电SOC值
    
    float BalanceStartVoltage;  // 均衡起始电压(V)
    float BalanceDiffeVoltage;  // 均衡差异电压(V)
    uint32_t BalanceCycleTime;  // 均衡周期时间(s)
    BMS_CellIndexTypedef BalanceRecord; // 均衡记录,正在均衡的会被位与上
    bool BalanceReleaseFlag;            // 表示均衡释放,false:表示已不满足均衡条件,true:满足均衡条件
}BMS_EnergyDataTypedef;    
​
/*****************************************************************************************************************/
BMS_GlobalParamTypedef BMS_GlobalParam          //全局参数
typedef struct
{
    BMS_SysModeTypedef SysMode; // 当前系统处于什么模式
    BMS_StateTypedef Charge;    // 充电状态
    BMS_StateTypedef Discharge; // 放电状态
    BMS_StateTypedef Balance;   // 均衡状态
    
    uint8_t Cell_Real_Number;   // 电芯实时数量
    uint8_t Temp_Real_Number;   // 温度实时数量
}BMS_GlobalParamTypedef;

FreeRTOS移植及移植中的问题

移植思路

既然已经从宏观了解项目的架构,那么很清晰移植思路就是把以前用RT-Thread做了编程的代码现在改成用FreeRTOS的代码做替代,没有的的就要做设配。

移植的实现与移植中的问题

既然是用FreeRTOS做移植,第一步肯定先把FreeRTOS的组件先添加进来。我的做法是先通过CudeMX把RT-Thread的组件先移除,然后就把FreeRTOS的组件添加进来。

如果也想用CudeMX移除但是没有RT-Thread这个选项,你可以参考以下博客添加进来在移除(当然你也可以手动删除)。

参考博客:基于 STM32CubeMX 添加 RT-Thread 操作系统组件(一)- 详细介绍操作步骤_cubemx rtthread-CSDN博客

b063547f1a434d099be3777ec97a33c5.png

在Keil中先保留RT-Thread的头文件,然后开始对RT-Thread组件先移除,最后编译时没报错只剩下一些业务相关的在编译过程中没有每有链接源文件(组件移除)就OK了。

e310b480f9a8479dbb960fac7d0cc9dc.png

接下来就是要把原有的的业务及一些以前用到RT-Thread做编程的地方一步一步地做替代,但是并不是什么东西都可以做平替。

f154046ef4af4bc688088f87ad84fffd.png

例如这种rt_kprintf函数是RT-Thread独有的打印函数,而且FreeRTOS上就没有,这时就要做适配。这里我就做了printf_usart1来平替。

e61518a78b6943ea9c0629ff2cb6e222.png

还有RT-Thread移植时没做CMSIS适配,我在移植过程中参考RT-Thread的底层来给FreeRTOS配参,例如rt_thread_create这个API。

afa88b29c596422fb2e92f81280063a5.png

因为RT-Thread这里没有做CMSIS的适配,这里我们osThreadNew在配参时只能从底层再做一次适配。

c4c9d1ce20fc4dd8b5387d35778a0537.png

还有RT-Thread支持Shell,有相关的的组件。FreeRTOS没有我们要是想使用Shell脚本使用脚本命令,那就要从第三方资料做个Shell组件来平替,我这里就没有打算做了,后面做二次开发的时候做一个上位机时,再做适配。要是想做的。这里也有个参考。

参考博客:STM32+FreeRtos 移植letter-shell工具_freertos shell-CSDN博客

当把之前所有用RT-Thread的代码都平替完后,无错误、无害警那就可以上电烧录代码。最后能充放电,过流过压等等都做了一系列测试都达标后,整个移植过程就算完成了。

项目总结

整体上项目只是做了BMS上的一些很基础的的框架,并没有做实际的的产品的应用,所以很难体现到这个BMS的“价值”所在。后面打算做一个UPS,超大号的充电宝。毕竟有出门在外旅行或者露营的习惯,有时候人多或者高功率的放电时,充电宝往往不能满足我的需求,还有在电池业务中的SOX算法做的比较粗糙,还得结合其他算SOH(电池健康)、SOE(剩余能量)等等算法了来完善SOX建模。通讯模块在本项目中没有一直使用到,到时时候做一个上位机软件,这里就可以启用到相关的通讯配置,后面有空在更新。

6cdc8979d06c4932a2a6061a11ff8aee.png

 

 

Logo

获取更多汽车电子技术干货

更多推荐