221|4

69

帖子

0

资源

一粒金砂(中级)

BMS 串口通讯协议

本帖最后由 xinyuanliu 于 2020-9-16 13:26 编辑

楼主是做锂电池保护板开发的,项目中会涉及到bms协议的数据收发,特此分享一下本人使用的串口协议

 

1. 使用 3 线 UART 异步通信(TX, RX, GND)采用专用命令和数据帧。
2. 主机发送命令帧,保护板回复数据帧;
3. 主机发送扫描命令时,单个数据包字节连续发送,命令数据包之间的发送周期应大 500
毫秒
4. 通讯方式:
串口 UART TTL 电平:3.3V~5V
波特率:19200
校验位:None
数据位:8
停止位:1
5. 命令数据帧格式:
起始字节 (0xEA) +
设备地址描述 (0x01 ) +
产品 ID (0xD2) +
后续数据长度 (Len) + // 包含到结束字节,不含该字节
命令 + // 1 个字节或 2 个字节
数据 +
从 Len 开始的异或校验 + // 数据长度,命令和数据的异或校验
结束字节(0xF5)
例:
命令帧: 0xEA, 0x01,0xD2, 0x04, 0xff,0x02, 0xf9, 0xf5
后续数据长度为:0x04
命令为 2 个字节:0xff, 0x02
校验码为:0xf9

 

 

数据请求命令帧定义:
0xEA, 0x01,0xD2,0x04,
0xFF, 0x02 :电池电压请求 例:0xEA, 0x01,0xD2, 0x04,0xFF, 0x02, 0xF9, 0xF5
0xFF, 0x03 :电池电流请求 例:0xEA, 0x01,0xD2, 0x04,0xFF, 0x03, 0xF8, 0xF5
0xFF, 0x04 :电池电量请求 例:0xEA, 0x01,0xD2, 0x04,0xFF,0x04, 0xFF, 0xF5

 

代码如下

 

#ifndef  COMM_H
#define  COMM_H

/********************* 协议说明 ************************

数据格式:起始字节     (0xEA) +
          设备地址描述 (0x01) +
          产品ID       (0xD0) + 
          后续数据长度 (Len)  +   //包含到结束字节,不含该字节 
                        命令  + 
                        数据  +
          从Len开始的异或校验 +
          结束字节(0xF5)

********************************************************/

#ifndef UINT8
  #define UINT8  unsigned char
#endif
#ifndef UINT16
  #define UINT16  unsigned int
#endif

#define  TXIE_EN   IEC0bits.U1TXIE=1;        // 使能串口发送中断
#define  TXIE_DIS  IEC0bits.U1TXIE=0;     // 静止串口发送中断
#define  UART_DR   U1TXREG

#define  REC_MAX   150    // 定义接收缓存区的大小
#define  SEND_MAX  150    // 定义发送缓存区的大小

 #define  SOI       0xEA      // 起始标志
 #define  ADR       0x01      // 地址描述符
 #define  CID       0xD2      // 设备描述符
 #define  EOI       0xF5      // 结束码
/*
 #define  SERIAL_TIME_ISR   5  // 定时器间隔 ms
 #define  TIME_OUT   1000      // 接收超时时间 ms
 #define  TIME_SLEEP 10000     // 休眠超时时间
*/
typedef struct 
{
  UINT8 RecBuf[REC_MAX];    // 接收缓存区
  UINT8 RecCount;           // 接收的字节数
  UINT8 SendBuf[REC_MAX];   // 发送缓存区
  UINT8 SendCount;          // 要发送的字节数
  UINT8 RecOver;            // 接收完成标志
  UINT16 RecTimer;          // 接收计时器
    UINT16 SleepTime;         // 休眠计时
    UINT8 SleepFlag;          // 通讯休眠标志
} MSG;

extern MSG MSG_SERIAL;
extern void SerialInput(UINT8 RevData);
extern void SerialStartSend(UINT8 *pData);
extern void SerialTimer(void); // 由定时器调用
extern void SerialDecode(void);// 由主函数调用
extern void SerialOutput(void);
//功能函数
// 发送命令
extern void SendCmd(UINT8 cmd1,UINT8 cmd2);
extern void SerialSendData(UINT8 cmd);
void SerialSendCurMul(UINT8 cmd1,UINT8 cmd2);
void SerialSendVlgMul(UINT8 cmd1,UINT8 cmd2); //电压倍数
void SerialSendVlg(UINT8 cmd1,UINT8 cmd2);
void SerialSendCur(UINT8 cmd1,UINT8 cmd2);
void SerialSendFuel(UINT8 cmd1,UINT8 cmd2);
void SendConfigData(void);
extern float cellVlg;

#endif
 

 

#include "global.h"
MSG MSG_SERIAL;
// 函数申明
static void SerialDecoding(UINT8 *pData);
void SendPowerNum(void);
void SerialSendFuelGuest(UINT8 cmd1);
/************************************* 基本功能函数 *********************************/
// 串口接收数据
void SerialInput(UINT8 RecData)
{  
   if(MSG_SERIAL.RecOver) return; // 停止接收?                 
   if((RecData==SOI)&&(MSG_SERIAL.RecCount==0))         
   {  MSG_SERIAL.SleepTime=0; 
      MSG_SERIAL.SleepFlag=0;    
      MSG_SERIAL.RecBuf[0]=SOI;MSG_SERIAL.RecCount=1; // 启动接收
   }  
   else if((MSG_SERIAL.RecBuf[0]==SOI)&&(MSG_SERIAL.RecCount<REC_MAX)) // 如果已经接收到起始位
   {MSG_SERIAL.RecBuf[MSG_SERIAL.RecCount++]=RecData;} // 将数据添加到缓存区
   else {MSG_SERIAL.RecCount=0;}       
   if((RecData==EOI)&&(MSG_SERIAL.RecCount==(MSG_SERIAL.RecBuf[3]+4)))
   {
     MSG_SERIAL.RecOver=1;
     MSG_SERIAL.RecTimer=0;          // 清计时器
   } //  接收完成           
}

// 校验
static UINT8 ChkSum(UINT8 *pData,UINT8 count)
{
   UINT8 chkSum=0;
   count-=1;
   while(count--)
   {
      chkSum^=(*pData++); 
   }
   return (chkSum);
}

// 接收数据校验
static UINT8 RcvChk(void)
{
  if(MSG_SERIAL.RecBuf[MSG_SERIAL.RecBuf[3]+2]==ChkSum(&MSG_SERIAL.RecBuf[3],MSG_SERIAL.RecBuf[3]))
  {return 1;}
  else 
  {return 0;}  
}

// 清接收缓存区
static void ClrRcvBuf(void)
{    
    MSG_SERIAL.RecBuf[0]=0;
    MSG_SERIAL.RecBuf[3]=0;
    MSG_SERIAL.RecCount=0; // 清接收数据计数器
    MSG_SERIAL.RecTimer=0; // 清计时器
    MSG_SERIAL.RecOver=0;  // 允许接
}
// 接收超时处理
static void SerialTimeOut(void)
{
   if(MSG_SERIAL.RecTimer>(TIME_OUT/SERIAL_TIME_ISR)) // 如果接收超时
   {
      MSG_SERIAL.RecCount=0;         // 清接收数据计数器
      MSG_SERIAL.RecTimer=0;         // 清计时器
      MSG_SERIAL.RecOver=0;          // 允许接
   }
   if(MSG_SERIAL.SleepFlag) return;
   if(MSG_SERIAL.SleepTime>(TIME_SLEEP/SERIAL_TIME_ISR))
   {
         MSG_SERIAL.SleepFlag=1;
   }
}

/**************************** 系统调用函数 *****************************/
// 由定时器调用
void SerialTimer(void)
{
    if(MSG_SERIAL.RecCount!=0)
    {
       MSG_SERIAL.RecTimer++;
    }
    if(MSG_SERIAL.RecCount==0)
    { 
       MSG_SERIAL.SleepTime++;
    }
}
// 数据解码
void SerialDecode(void)
{
   SerialTimeOut();
   if(!MSG_SERIAL.RecOver) {return;} // 接收没有完成
   if(MSG_SERIAL.RecBuf[1]!=ADR) {return;} // 如果地址不正确
   if(MSG_SERIAL.RecBuf[2]!=0xBB)    //通用设备描述符
   {
      if(MSG_SERIAL.RecBuf[2]!=CID) {return;} // 如果设备描述符不正确
   }
   if(!RcvChk()) {return;} // 如果校验错误
   SerialDecoding(&MSG_SERIAL.RecBuf[4]); //    
   ClrRcvBuf();          // 清缓存区  
}

/********************************** 移植函数 ***************************/ 

// 串口发送数据
void SerialOutput(void)
{
   static UINT8 mSendCount=0;
   if(MSG_SERIAL.SendCount)
   { 
      MSG_SERIAL.SendCount--;
      UART_DR=MSG_SERIAL.SendBuf[mSendCount++]; // 发送数据  
   }
   else{mSendCount=0;TXIE_DIS;} // 停止发送          
}

// 启动串口发送数据
void SerialStartSend(UINT8 *pData)
{
   UINT8 i;
   *(pData+*(pData+3)+3)=EOI;
   *(pData+*(pData+3)+2)=ChkSum(pData+3,*(pData+3));
   for(i=0;i<(*(pData+3)+4);i++){MSG_SERIAL.SendBuf=*(pData+i);}
   MSG_SERIAL.SendCount=*(pData+3)+4;
   IFS0bits.U1TXIF=1;   
   TXIE_EN; // 使能串口发送中断
}

// 数据提取
static void SerialDecoding(UINT8 *pData)
{
   switch(*pData)
   {
        case 0x04:  // 电池电压(0x02),电流(0x03),电量(0x04),数据请求             
             SerialSendFuelGuest(0x04);
             break;       
        case 0xff:  // 内部用命令
             switch(*(pData+1))
             {
                case 0x00:  // 读取产品序列号
                       break;
                case 0x01:  // 写入产品序列号
                       break;                     
                case 0x02:  // 电压数据电流数据  容量数据 请求
                       SerialSendVlg(0xff,0x02);                                                                                         
                       break;
                case 0x03:    //  电流数据请求                                     
                       SerialSendCur(0xff,0x03);  
                       break; 
                case 0x04:  //  电量数据请求
                       SerialSendFuel(0xff,0x04);
                       break;
                case 0x05:  //  状态数据请求
                      // SerialSendStates(0xff,0x05); // 发送状态数据
                         break;
                case 0x06:  // 写入配置数据
                       if(WriteConfigData(21,pData+2))  {SendCmd(0xff,0xff);InitCBVal(0);} // 写入成功
                       else {SendCmd(0xff,0xfe);} // 写入失败                                                                                                     
                       break;
                case 0x07:  // 配置数据请求
                       SendConfigData();
                       break;
                case 0x0f:  //  校准
                        switch(*(pData+2))
                        {
                            case 0:  // 电压数据请求
                                    SerialSendVlg(0xff,0x02);     
                                    break;
                            case 1:  // 电压校准数据请求
                                    SerialSendVlgMul(0x0f,0x01);  
                                    break;                     
                            case 2:  // 电压校准数据写入
                                    WriteVlgClb((pData+4));      
                                    break;                    
                            case 3:  // 电流常量校准 
                                    ClibCurCnt();
                                    break;
                            case 4:  // 电流放大倍数请求 
                                     SerialSendCurMul(0x0f,0x04);     
                                      break;  // 发送电流放大倍数
                            case 5:  // 电流放大倍数写入         
                                     WriteCurMul(pData+3);        
                                     break; 
                            case 6:  // 电量数据写入
                                     WriteFuel(pData+3);                               
                                     break;                                                        
                            default: break;
                }
                break; 
                default: break;
             }
             break;
        default: break;
   } 
}


/********************************** 具体功能函数 ***************************/
// 发送命令
void SendCmd(UINT8 cmd1,UINT8 cmd2)
{
    UINT8 sendData[8]={SOI,ADR,CID,0x04,0x00,0x00,0x00,0x00};
    sendData[4]=cmd1;
    sendData[5]=cmd2;
    SerialStartSend(sendData); // 启动发送
}


void SerialSendVlg(UINT8 cmd1,UINT8 cmd2)
{
    UINT16 data,i;
    UINT8 sendData[11+cell_count*3] ={SOI,ADR,CID,
                                      7+cell_count*3,   // 字节数
                                              0xff,0x02,   // 命令                                                        
                                          cell_count,   // 电池数量
                                           cell_count,   // 当前包含的电池数量  
                                                0x01,   // 电池编号
                                              0x00,0x00,   // 电压
                                           0x00,0x00};

    sendData[4]=cmd1;
    sendData[5]=cmd2;
    for(i=0;i<cell_count;i++)
    {
      data=V_CB.CellVlg*1000;
      sendData[8+i*3]=i+1;     // 电池序号
      sendData[9+i*3]=data>>8;    
        sendData[10+i*3]=data;
    }                                             
    SerialStartSend(sendData); // 启动发送
                                                                 
}

void SerialSendCur(UINT8 cmd1,UINT8 cmd2)
{
     UINT16 data;
   UINT8 sendData[25] ={SOI,ADR,CID,21,
                             0xff,0x03, // 命令 电流                                                                    
                                  0x00, // 充放电标志
                             0x00,0x00, // 充放电电流
                                  0x00, // 过压
                                  0x00, // 欠压
                                  0x00, // 过温
                                  0x00, // 过流短路
                             0x00,0x00, // 最高温度电压
                             0x00,0x00, // 最低温度                         
                             0x00,0x00};
  sendData[4]=cmd1;
  sendData[5]=cmd2;
  if(F_CB.DSG_FLAG) // 如果在放电
    {  
      data=(UINT16)(C_CB.DSGCur*100);
      sendData[6]=1;
      sendData[7]=data>>8;
      sendData[8]=data;
      
    }else if(F_CB.CHG_FLAG) // 如果在充电
    {
      data=(UINT16)(C_CB.CHGCur*100);
      sendData[6]=2;
      sendData[7]=data>>8;
      sendData[8]=data;
    }
    else{sendData[6]=0;}
    // 过压状态
    sendData[9]=((OV_CB.HNum&0x00ff)<<2)|F_CB.OV_FLAG;
    // 欠压状态
    sendData[10]=((UV_CB.LNum&0x00ff)<<2)|F_CB.UV_FLAG;
    // 过温状态
    sendData[11]=OT_CB.HVlgNum<<2|F_CB.OT_FLAG;
    // 过流短路状态
    sendData[12]=F_CB.OC_SC_FLAG;
    // 温度
    data=OT_CB.HVlg*1000;
    sendData[13]=data>>8;
    sendData[14]=data;
    data=OT_CB.LVlg*1000;
    sendData[15]=data>>8;
    sendData[16]=data;
    data=OC_MUL*SENSE_DSG*10000;
    sendData[17]=data>>8;
    sendData[18]=data;
// 平衡数据
    sendData[19]=BLA_CB.BCellData[1];
    sendData[20]=BLA_CB.BCellData[2];
    sendData[21]=BLA_CB.BCellData[3];
    sendData[22]=BLA_CB.BCellData[4];
    SerialStartSend(sendData); // 启动发送                                                                 
}
//  内部发送电量数据
void SerialSendFuel(UINT8 cmd1,UINT8 cmd2)
{
     UINT16 data;
   UINT8 sendData[31] ={SOI,ADR,CID,27,
                             0xff,0x04, // 命令 电量                                                                    
                               0x01,0x00, // 百分比
                        0x02,0x00,0x00, // 循环次数                             
                        0x03,0x00,0x00, //  电池蛮容量
                        0x04,0x00,0x00, //  电池剩余容量
                        0x05,0x00,0x00, //  放电剩余时间
                        0x06,0x00,0x00, //  充电剩余时间
                             0x00,0x00};
                                            
     sendData[4]=cmd1;
     sendData[5]=cmd2;
     sendData[6]=1;
     sendData[7]=SOC.PercentCap/2;// 
     sendData[8]=2;
     sendData[9]=SOC.CycleCount>>8;
     sendData[10]=SOC.CycleCount;
     sendData[11]=3;
     sendData[12]=SOC.RemFullCap>>8;
     sendData[13]=SOC.RemFullCap;
     sendData[14]=4;
     if(SOC.RemCap>0){data=SOC.RemCap;}
     else{data=0;}
     if(data>SOC.RemFullCap) // 防止超出满容量
     {data=SOC.RemFullCap;}
     sendData[15]=data>>8;
     sendData[16]=data;
     sendData[17]=5;
     sendData[18]=SOC_CB.DSGRemTime>>8;
     sendData[19]=SOC_CB.DSGRemTime;
     sendData[20]=6;
     sendData[21]=SOC_CB.CHGRemTime>>8;
     sendData[22]=SOC_CB.CHGRemTime;
     sendData[23]=7; 
     sendData[24]=SOC.RemFullCap>>24;
     sendData[25]=SOC.RemFullCap>>16;
     sendData[26]=8;
     if(SOC.RemCap>=0)
     { 
       sendData[27]=SOC.RemCap>>24;
       sendData[28]=SOC.RemCap>>16;
     }
     SerialStartSend(sendData); // 启动发送    
}

void SendConfigData(void)
{
    UINT8 i;
    UINT8 sendData[29]={SOI,ADR,CID,25,0xff,
                                       0x07};   // 命令
    for(i=0;i<21;i++)
    {
        sendData[6+i]=EEPROM_READ(CONFIG_ADDR+(i<<1));
    }
    SerialStartSend(sendData); // 启动发送
}


/************************* 客户区 *************************/
//  内部发送电量数据
void SerialSendFuelGuest(UINT8 cmd1)
{
   UINT16 data;
   UINT8 sendData[24] ={SOI,ADR,CID,21,
                                  0x04, // 命令 电量                                                                    
                               0x01,0x00, // 百分比
                        0x02,0x00,0x00, // 循环次数                             
                        0x03,0x00,0x00, //  电池蛮容量
                        0x04,0x00,0x00, //  电池剩余容量
                        0x05,0x00,0x00, //  放电剩余时间
                        0x06,0x00,0x00, //  充电剩余时间
                             0x00,0x00};
                                            
     sendData[5]=1;
     sendData[6]=SOC.PercentCap/2;
     sendData[7]=2;
     sendData[8]=SOC.CycleCount>>8;
     sendData[9]=SOC.CycleCount;
     sendData[10]=3;
     sendData[11]=SOC.RemFullCap>>8;
     sendData[12]=SOC.RemFullCap;
     sendData[13]=4;
     if(SOC.RemCap>0){data=SOC.RemCap;}
     else{data=0;}
     if(data>SOC.RemFullCap) // 防止超出满容量
     {data=SOC.RemFullCap;}
     sendData[14]=data>>8;
     sendData[15]=data;
     sendData[16]=5;
     sendData[17]=SOC_CB.DSGRemTime>>8;
     sendData[18]=SOC_CB.DSGRemTime;
     sendData[19]=6;
     sendData[20]=SOC_CB.CHGRemTime>>8;
     sendData[21]=SOC_CB.CHGRemTime;
     SerialStartSend(sendData); // 启动发送    
}


 

此帖出自stm32/stm8论坛

BMS串口通讯协议.pdf

73.58 KB, 阅读权限: 30, 下载次数: 1


回复

69

帖子

0

资源

一粒金砂(中级)

附件中的PDF为通讯协议,大家可以参考

回复

665

帖子

2

资源

纯净的硅(初级)

都有哪些公司支持这个协议呢?

点评

赛美达,力通威这些  详情 回复 发表于 昨天 17:51

回复

69

帖子

0

资源

一粒金砂(中级)

dwwzl 发表于 2020-9-17 16:32 都有哪些公司支持这个协议呢?

赛美达,力通威这些


回复

69

帖子

0

资源

一粒金砂(中级)

主要是抛开思路,其它串口协议都是类似,抛砖引玉


回复
您需要登录后才可以回帖 登录 | 注册

关闭
站长推荐上一条 1/5 下一条

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区知春路23号集成电路设计园量子银座1305 电话:(010)82350740 邮编:100191

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2020 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表