找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3864|回复: 7
收起左侧

自制BMS监控仪通过485通讯读BMS信息并显示在LCD2004上 单片机源程序电路

  [复制链接]
ID:702386 发表于 2020-10-10 17:15 | 显示全部楼层 |阅读模式
公司是做锂电池管理系统的,主要是通信用16串锂电池用的保护板(BMS),产品有485接口,可以上传各种测量和告警信息。最近开始学习单片机,自己动手做了一个BMS监控仪,通过485与自家的BMS通讯获取状态信息并显示在LCD2004上。
程序也是自己弄了好久才排除各种bug,现在具备了基本状态信息显示、菜单、16串单体电压显示、各温度显示、BMS状态显示、基本告警信息显示。
这个小产品使用STC89C54RD+,MAX485芯,使用5V升压锂电池供电,在外壳上留出了USB充电接口。
电路原图是自己搞了一个,然后同事帮我画的PCB。制作比较废劲,尤其是壳子不好切割,还把手割伤了。。。
因为工作比较忙,从头到尾断断续续搞了一个来月终于算是完成了,和大家分享一下。

已附上原理图和单片机程序。因自己初学,程序有很多不足,比如没有按模块化编写、逻辑较乱等,希望大家帮忙指点。

制作出来的实物图如下:
1571849934.jpg 1941352020.jpg 233656511.jpg 1514261199.jpg 1633315626.jpg 1934588858.jpg

电路原理图如下:
51hei.png

单片机源程序如下:
  1. #include "STC89C54.h"
  2. #define uint unsigned int
  3. #define uchar unsigned char

  4. uchar code welcome1[]="BMS Monitor";
  5. uchar code welcome2[]="RichPower";
  6. uchar code waiting[]="CONNECTING...";
  7. uchar code menu_table1[]="CELL VOLTAGE"; //12个字符
  8. uchar code menu_table2[]="TEMPERATURE";  //11个字符
  9. uchar code menu_table3[]="BMS STATUS";        //10个字符
  10. uchar code menu_table4[]="ALARM INFO"; //10个字符
  11. uchar code BMSINFO1[20]={0x7E,0x32,0x36,0x30,0x30,0x34,0x36,0x46,0x32,0x45,0x30,0x30,0x32,0x30,0x31,0x46,0x44,0x31,0x45,0x0D};         //询遥测的命令报文
  12. uchar code BMSINFO2[20]={0x7E,0x32,0x36,0x30,0x30,0x34,0x36,0x46,0x34,0x45,0x30,0x30,0x32,0x30,0x31,0x46,0x44,0x31,0x43,0x0D};  //询遥信命令报文
  13. uchar buffer[145]={0};          //用于缓存遥测报文
  14. sbit lcdrs=P2^5;        //指令和数据寄存器选择,高电平时为数据,低电平选择命令
  15. sbit lcdrw=P2^6;        //读写选择,高电平为读,低电平为写
  16. sbit lcden=P2^7;    //使能
  17. sbit lcdbg=P2^4;        //背光,0为开
  18. sbit beep=P2^0;                //蜂鸣器,0为开
  19. sbit key1=P1^0;                //菜单或确认
  20. sbit key2=P1^1;                //上一项
  21. sbit key3=P1^2;                //下一项
  22. sbit key4=P1^3;                //返回或背光开关
  23. bit data datareceived_flag=0,displayclear=1;           //datareceived_flag为遥测报文是否接收完的标志位,current_bit为电流值符号标志位
  24. bit data current_bit,celltemp_unit,envtemp_unit,mostemp_unit;
  25. uchar data num;
  26. uchar data i=0,watchdog=0,end_position;
  27. uint single_max,single_min,totalvoltage,current,totalcap,remaincap;
  28. uint data remaincap2;               
  29. uchar digit0,digit1,digit2,digit3,digit4;                                   //LCD显示数字的万千百十个位
  30. uchar cellnumber,cellnumber_offset,address_offset;                 //串数,串数差,地址偏移量
  31. uchar cappercentage;                                                                         //SOC
  32. uint singlevoltage[15];                                                   //单体电芯电压
  33. uint temperature[5];                                                           //4个电芯温度和环境温度及功率温度
  34. uchar display_mode=0,menu_position=1,singlevoltage_page=1;                         //显示模式


  35. void delayms(uint z)         //延时子程序
  36. {
  37.         uint x,y;
  38.         for(x=z;x>0;x--)
  39.                 for(y=110;y>0;y--);
  40. }


  41. void write_com(uchar com)           //LCD1602写命令函数
  42. {
  43.         lcdrs=0;                 //rs低电平为写命令
  44.         P0=com;                        
  45.         delayms(4);
  46.         lcden=1;                 //EN先置高电平
  47.         delayms(4);
  48.         lcden=0;                 //短暂延时后EN置低电平
  49. }
  50. void write_dat(uchar dat)           //LCD1602写数据函数
  51. {
  52.         lcdrs=1;                 //rs高电平为写数据
  53.         P0=dat;
  54.         delayms(4);
  55.         lcden=1;
  56.         delayms(4);
  57.         lcden=0;
  58. }

  59. void UsartInit()           //串口初始化
  60. {
  61.         SCON=0X50;                        //设置为工作方式1
  62.         TMOD=0X20;                        //设置计数器工作方式2
  63.         PCON=0X80;                        //波特率加倍
  64.         TH1=0XFA;                                //计数器初始值设置,注意11.0592Mhz波特率是9600的
  65.         TL1=0XFA;
  66.         ES=1;                                                //打开接收中断
  67.         EA=1;                                                //打开总中断
  68.         TR1=1;                                        //打开计数器
  69. }

  70. void init()                                //LCD初始化及开机界面
  71. {
  72.         lcdrw=0;
  73.         lcden=0;
  74.         P0=0;
  75.         write_com(0x38);
  76.         write_com(0x0f);   //初始化,开显示,开光标,开光标闪烁
  77.         write_com(0x06);   //初始化,读写一个字符后地址指针自动加1
  78.         write_com(0x01);   //清屏
  79. //        write_com(0x80);   //数据地址指针从0开始
  80.         lcdbg=0;                   //开背光
  81.         write_com(0x0C);        //关光标
  82. /**********************欢迎界面**************************/
  83.         write_com(0x80+0x44);  //第2行第5个字符
  84.         for(num=0;num<11;num++)
  85.         write_dat(welcome1[num]);
  86.         write_com(0x94+0x05);         //第3行第6个字符
  87.         for(num=0;num<9;num++)
  88.         write_dat(welcome2[num]);
  89.         delayms(1000);
  90.         beep=0;
  91.         delayms(60);
  92.         beep=1;
  93. /********************************************************/
  94. }

  95. /********************************************************************************
  96. 计算报文缓存中的一个字节
  97. ********************************************************************************/
  98. uchar buffer_byte_process(uchar buffer_address)
  99. {
  100.         uchar byte_value;
  101.         if(buffer[buffer_address]<=0x39)
  102.                 buffer[buffer_address]=buffer[buffer_address]-0x30;        //若为0~9的字符,减0x30即为数值
  103.         else
  104.                 buffer[buffer_address]=buffer[buffer_address]-0x37;        //若为大于9即为A~F的字符,减0x37即为数值
  105.         if(buffer[buffer_address+1]<=0x39)
  106.                 buffer[buffer_address+1]=buffer[buffer_address+1]-0x30;
  107.         else
  108.                 buffer[buffer_address+1]=buffer[buffer_address+1]-0x37;
  109.         byte_value=(buffer[buffer_address]<<4)|buffer[buffer_address+1];               
  110.         return byte_value;
  111. }

  112. /********************************************************************************
  113. 计算报文缓存中的一个字
  114. ********************************************************************************/
  115. uint buffer_word_process(uchar buffer_address)
  116. {
  117.         uint word_value;
  118.         if(buffer[buffer_address]<=0x39)
  119.                 buffer[buffer_address]=buffer[buffer_address]-0x30;        //若为0~9的字符,减0x30即为数值
  120.         else
  121.                 buffer[buffer_address]=buffer[buffer_address]-0x37;        //若为大于9即为A~F的字符,减0x37即为数值
  122.         if(buffer[buffer_address+1]<=0x39)
  123.                 buffer[buffer_address+1]=buffer[buffer_address+1]-0x30;
  124.         else
  125.                 buffer[buffer_address+1]=buffer[buffer_address+1]-0x37;
  126.         if(buffer[buffer_address+2]<=0x39)
  127.                 buffer[buffer_address+2]=buffer[buffer_address+2]-0x30;        //若为0~9的字符,减0x30即为数值
  128.         else
  129.                 buffer[buffer_address+2]=buffer[buffer_address+2]-0x37;        //若为大于9即为A~F的字符,减0x37即为数值
  130.         if(buffer[buffer_address+3]<=0x39)
  131.                 buffer[buffer_address+3]=buffer[buffer_address+3]-0x30;
  132.         else
  133.                 buffer[buffer_address+3]=buffer[buffer_address+3]-0x37;
  134.         word_value=(buffer[buffer_address]<<12)|(buffer[buffer_address+1]<<8)|(buffer[buffer_address+2]<<4)|buffer[buffer_address+3];               
  135.         return word_value;
  136. }

  137. void buffer_value_process()
  138. {
  139.         cellnumber=buffer_byte_process(17);                                                //串数计算
  140.         cellnumber_offset=16-cellnumber;                                                //串数与16串相比的差值
  141.         address_offset=cellnumber_offset*4;                                                //报文地址偏移量        
  142.         current=buffer_word_process(109-address_offset);                //电流计算
  143.         current_bit=current>>15;                                                                //电流正负判断
  144.         if(current_bit==1)                                                                                
  145.                 current=~current+1;   //电流值为补码,负数取反加1得到原码(符号位1取反变成了0);        
  146.         totalvoltage=buffer_word_process(113-address_offset);  //总压计算
  147.         for(num=0;num<=15-cellnumber_offset;num++)                           //单体电压和最高最低电压计算
  148.         {
  149.                 singlevoltage[num]=buffer_word_process(19+4*num);  //各串单体电压
  150.                 if(num==0)
  151.                 {
  152.                         single_max=singlevoltage[0];
  153.                         single_min=single_max;
  154.                 }
  155.                 if(singlevoltage[num]>single_max)
  156.                         single_max=singlevoltage[num];                                   //最高单体电压
  157.                 if(singlevoltage[num]<single_min)
  158.                         single_min=singlevoltage[num];                                   //最低单体电压
  159.         }
  160.         remaincap=buffer_word_process(117-address_offset);           //剩余容量
  161.         remaincap2=remaincap;
  162.         totalcap=buffer_word_process(123-address_offset)/100;  //总容量
  163.         cappercentage=(remaincap/totalcap)&0xFF;                           //SOC计算
  164.         for(num=0;num<=5;num++)                                                                   //6个温度计算
  165.                 temperature[num]=buffer_word_process(85+4*num-address_offset);           //此温度需减2731并判断符号
  166. }

  167. void main_display()
  168. {
  169.         digit4=totalvoltage%10;                          //总压个位数字
  170.         digit3=totalvoltage/10%10;                  //总压十位数字
  171.         digit2=totalvoltage/100%10;                  //总压百位数字
  172.         digit1=totalvoltage/1000%10;          //总压千位数字
  173.         write_com(0x80+0x02);                   //数据地址指针从第1行第9位开始写总压值
  174.         write_dat(digit1+0x30);                           //显示千位数字的ASCII码
  175.         write_dat(digit2+0x30);
  176.         write_dat('.');
  177.         write_dat(digit3+0x30);
  178.         write_dat(digit4+0x30);
  179.         write_dat('V');        

  180.         digit4=current%10;                           //电流个位数字
  181.         digit3=current/10%10;
  182.         digit2=current/100%10;
  183.         digit1=current/1000%10;
  184.         if(current_bit==0&&digit1==0)          //如果为正电流,且千位为0
  185.         {
  186.                 write_com(0xc0);
  187.                 write_dat(' ');write_dat(' ');
  188.                 write_dat(' ');                                
  189.         }
  190.         if(current_bit==0&&digit1>0)          //如果为正电流,且千位不为0
  191.         {
  192.                 write_com(0xc0);
  193.                 write_dat(' ');write_dat(' ');
  194.                 write_dat(digit1+0x30);                                
  195.         }
  196.         if(current_bit==1&&digit1==0)          //如果为负电流,且千位为0
  197.         {
  198.                 write_com(0xc0);
  199.                 write_dat(' ');write_dat(' ');
  200.                 write_dat('-');                                
  201.         }
  202.         if(current_bit==1&&digit1>0)          //如果为负电流,且千位不为0
  203.         {
  204.                 write_com(0xc0);write_dat(' ');
  205.                 write_dat('-');
  206.                 write_dat(digit1+0x30);                                
  207.         }
  208.         write_dat(digit2+0x30);
  209.         write_dat('.');
  210.         write_dat(digit3+0x30);
  211.         write_dat(digit4+0x30);
  212.         write_dat('A');

  213.         digit4=remaincap2%10;                          //剩余容量个位数字
  214.         digit3=remaincap2/10%10;                          //十位数字
  215.         digit2=remaincap2/100%10;                  //百位数字
  216.         digit1=remaincap2/1000%10;                  //千位数字
  217.         digit0=remaincap2/10000%10;                //万位数字
  218.         write_com(0x80+0x0A);                   //数据地址指针从第1行第10位开始写剩余容量
  219.         if(digit0>0)
  220.                 write_dat(digit0+0x30);                //显示万位数字的ASCII码
  221.         else
  222.                 write_dat(' ');
  223.         if(digit0==0&&digit1==0)
  224.                 write_dat(' ');                                   //显示千位数字的ASCII码
  225.         else
  226.                 write_dat(digit1+0x30);
  227.         write_dat(digit2+0x30);
  228.         write_dat('A');
  229.         write_dat('h');

  230.         digit4=cappercentage%10;                  //SOC个位数字
  231.         digit3=cappercentage/10%10;                  //十位数字
  232.         digit2=cappercentage/100%10;          //百位数字
  233.         write_com(0x80+0x10);                   //数据地址指针从第1行第16位开始写剩余容量
  234.         if(digit2>0)
  235.                 write_dat(digit2+0x30);
  236.         else
  237.                 write_dat(' ');
  238.         if(digit2==0&&digit3==0)
  239.                 write_dat(' ');
  240.         else
  241.                 write_dat(digit3+0x30);
  242.         write_dat(digit4+0x30);
  243.         write_dat('%');

  244.         if(temperature[0]>=2731)                                         //第1节电芯温度
  245.         {
  246.                 celltemp_unit=1;   //单位为正
  247.            
  248. ……………………

  249. …………限于本文篇幅 余下代码请从51黑下载附件…………
复制代码

所有资料51hei提供下载:
BMS监控仪原理和程序.rar (19.71 KB, 下载次数: 69)
回复

使用道具 举报

ID:328014 发表于 2020-10-10 17:55 | 显示全部楼层
好东东,能分享下电路和源码吗?
回复

使用道具 举报

ID:702386 发表于 2020-10-10 20:14 | 显示全部楼层
51hei团团 发表于 2020-10-10 17:55
好东东,能分享下电路和源码吗?

已分享,欢迎给建议
回复

使用道具 举报

ID:828614 发表于 2020-10-12 10:18 | 显示全部楼层
厉害了,我们也是做电池,我最近准备做BMS蓝牙上位机
回复

使用道具 举报

ID:702386 发表于 2020-10-12 16:20 | 显示全部楼层
嗜血者123 发表于 2020-10-12 10:18
厉害了,我们也是做电池,我最近准备做BMS蓝牙上位机

那你们BMS带蓝牙模块吧。你是要开发手机app吗?
回复

使用道具 举报

ID:828614 发表于 2020-10-14 12:43 | 显示全部楼层
zsw3721 发表于 2020-10-12 16:20
那你们BMS带蓝牙模块吧。你是要开发手机app吗?

不带蓝牙  是带uart串口,自己开发个手机APP
回复

使用道具 举报

ID:876818 发表于 2021-1-12 16:37 | 显示全部楼层
厉害
谢谢,学习一下
回复

使用道具 举报

ID:373684 发表于 2021-1-14 12:48 | 显示全部楼层
嗜血者123 发表于 2020-10-12 10:18
厉害了,我们也是做电池,我最近准备做BMS蓝牙上位机

蓝牙电池保护??
回复

使用道具 举报

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

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表