ESP32 BMS master

Because of shortage of chips i couldnt play with Teensy or DUE anymore. However I noticed increasing popularity of ESP32 family of chips.

ESP32 is a low-cost, low-power system on a chip microcontrollers with integrated Wi-Fi and dual-mode Bluetooth. It has Tensilica Xtensa LX6 microprocessor with two cores. I barely schratched the surface with one core control, but while reading about it it seems it has really robust watchdog process which makes it a good candidate for VCU chip.

I am not so sure about ADC pins. I read about them not being really accurate but rather adequate.

It only has a single CAN controler. It will be ok for testing. Later on for BMS and VCU functionality it could be sufficient. However to use full integration i may build a dual CAN bus version with second controler on SPI.

Also SPI consumes a lot of pins therefore i would need to use a digital port expander such as MCP23S17. But i will get to this later if i need it at all.

I have made a board to fit 24pin casing from Aliexpress https://www.aliexpress.com/item/33048065281.html?gatewayAdapt=4itemAdapt

It is simple enough casing sealed against weather and robust enough to fit in the trunk or under the hood.

I designed it to be fed from 12V supply. Integrated is a 5V regulator with additional 3V3 LDO regulator to provide stable logic voltage for ESP32. 5V is there simply because it is easier to transition from 12V to 5V and then to 3V3 than directly to 3V3. Also there is 5V layer onboard which can be used as supply for throttle or other analog sensors.

Onboard there are 3 powerfull switches NCV8401A which can clamp heavy relays or 12V moderate loads directly.

4x opto coupled input circuit.

ULN2003 darlington array for signaling or triggering low loads and driving PWM.

Small buzzer for sounding alarm state.

EVSE Cp protocol sensing circuit

CAN bus transciever

Innitialy i had soome problems with positioning I/Os. ESP32 has some specific limitations which I/Os to use when and i had to adapt the first circuit. I consulted this source: https://randomnerdtutorials.com/esp32-pinout-reference-gpios/

First working version of circuit is therefore V1.

I have a set of 6 Hankzor active balancers that are not quite BMS, but they gather data, balance and can output data on CAN bus. What is revolutionary for me is that those balancers can ACTIVELY balance cells. This means capacitively adding charge to the lowest cell whilst taking from higher charged cells in a pack.

JK-B1A24S


I need to program a master unit for them to listen to. Hardware is Arduino DUE with CAN bus transciever.
CAN protocol for modules is found in this document: https://openinverter.org/forum/download/file.php?id=13605

Communication works at 250kbps at 11bit ID.
Each unit needs to have its own ID which is set by the DIP switches on the side of the module. Here is the translation table for different switch positions.

DIP Position / Fixed address no.1234
0OFFOFFOFFOFF
1ONOFFOFFOFF
2OFFONOFFOFF
3ONONOFFOFF
4OFFOFFONOFF
5ONOFFONOFF
6OFFONONOFF
7ONONONOFF
8OFFOFFOFFON

I mounted each 16S module with its own balancer module. Iach comes with one thermistor and CAN bus line. Also i could set addressing on the sides of the modules. I decided i began with no. 1 module at the first +HV connection. The last module is no. 6 at the rear with -HV connection.


CAN bus is dormant until master requests data from particular module. When that is accomplished that module dumps several lines of data on the CAN bus. ID is still the same for every line, but data is classified by the first byte of the line.
For example 01 in byte1 is reserved for cell temperature values and 04 is actual cell values…

Master request:
ID 01 DATA FF

Slave response:
ID 01 DATA 01 00 11 24 F9 0F 68 18
ID 01 DATA 02 10 03 02 00 2C 04 5A
ID 01 DATA 03 00 05 03 E8 01 18
ID 01 DATA 04 00 0F 64 0F 61 0F 61
ID 01 DATA 04 03 0F 60 0F 60 0F 67
ID 01 DATA 04 06 0F 61 0F 61 0F 62
ID 01 DATA 04 09 0F 73 0F 6B 0F 61
ID 01 DATA 04 0C 0F 69 0F 73 0F 71
ID 01 DATA 04 0F 0F 6B 0F 8C 0F 64
ID 01 DATA 04 12 0F 65 0F 62 0F 65
ID 01 DATA 04 15 0F 65 0F 6F 0F 60

ID: 01 cell voltage report explained

Response data: 01 00 15 1E D3 0F 69 14;

Temperature 0x0015 * 1 ℃ = 21 ℃

Total voltage 0x1ED3 * 10mV = 7891 * 10mV = 78.910V

Number of identification units: 0x14 in HEX = 20 cells

I could use this particular report to determin number of places in array!

Response data: 04 00 0F 69 0F 69 0F 67;

Starting measure No 0x00 = 0 means beginning of array!

measure 0 Voltage 0xoF69 * 1mV = 3945mV = 3.945V

measure 1 voltage 0x0F69 * 1mV = 3945mV = 3.945V

measure 2 voltage 0x0F67 * 1mV = 3943mV = 3.943V

Response data: 04 03 0F 69 0F 68 0F 67;

Starting voltage No 0x03 = 0

measure 3 Voltage 0x0F69 * 1mV = 3945mV = 3.945V

measure 4 voltage 0x0F68 * 1mV = 3944mV = 3.944V

measure 5 voltage 0x0F67 * 1mV = 3943mV = 3.943V

Response data: 04 06 0F 68 0F 68 0F 6C;

Starting voltage No 0x06 = 0

measure 6 Voltage 0x0F68 * 1mV = 3944mV = 3.944V

measure 7 voltage 0x0F68 * 1mV = 3944mV = 3.944V

measure 8 voltage 0x0F6C * 1mV = 3948mV = 3.948V

I have built the code to run master command at 500ms intervals thus supplying VCU with stream of cell data.

void sendBMSquerry_1 () // send CAN frame for BMS command 
{
 CAN_FRAME txFrame;
    txFrame.rtr = 0;
    txFrame.id = 0x01; // Rotate ID for each report
    txFrame.extended = false;
    txFrame.length = 1; // Data payload 1 byte
    txFrame.data.uint8[0] = 0xFF;
    CAN0.sendFrame(txFrame);
}

Commands need to be send from 1st module to the last, so i made the function revolve.

void requestDATA () 
{
      frameRotate++;
      frameRotate %= 5; // 5 cases 
      switch (frameRotate)
      {
        case 0:
          sendBMSquerry_1 ();
          break;
        case 1:
          sendBMSquerry_2 ();
          break;
        case 2:
          sendBMSquerry_3 ();
          break;
        case 3:
          sendBMSquerry_4 (); 
          break;
        case 4:
          sendBMSquerry_5 (); 
          break;                     
      }
}

Likewise i setup a command to run balancing when conditions were right

void SendBMSbalancingON_1 () // Turn ON/OFF balancing when cells are within 3.9V
{
 CAN_FRAME txFrame;
    txFrame.rtr = 0;
    txFrame.id = 0x01; // Rotating frame max no. of modules 
    txFrame.extended = false;
    txFrame.length = 2; // Data payload 2 bytes
    txFrame.data.uint8[0] = 0xF6;
    txFrame.data.uint8[1] = 0x01; // Use BMS balancing flag to switch on/off
    CAN0.sendFrame(txFrame);
}

And i setup a command to turm off balancing

void SendBMSbalancingOFF_1() // Turn ON/OFF balancing when cells are within 3.9V
{
 CAN_FRAME txFrame;
    txFrame.rtr = 0;
    txFrame.id = 0x01; // Rotating frame max no. of modules 
    txFrame.extended = false;
    txFrame.length = 2; // Data payload 2 bytes
    txFrame.data.uint8[0] = 0xF6;
    txFrame.data.uint8[1] = 0x00; // Use BMS balancing flag to switch on/off
    CAN0.sendFrame(txFrame);
}

I decided to make the BMS turn ON balancing only when charger is connected. At the time cells are at rest and balancing can be done efficiently.

Also i made BMS turn OFF balancing when car is driving. Reasoning is cell voltage swing while driving is too great to allow for efficient balancing.

if(digitalRead(PP_pin) == LOW) { // if PP_pin senses EVSE
  if(millis()-last > transmitime)  //Nominally set for 100ms - do stuff on 100 ms non-interrupt clock
    {   
     last=millis();        //Zero our timer
       requestBMS_ON ();
Serial.println("Put BMS in charge mode");
    } }      

if(digitalRead(Enable_pin) == HIGH) { // if PP_pin senses EVSE
  if(millis()-last > transmitime)  //Nominally set for 100ms - do stuff on 100 ms non-interrupt clock
    {   
     last=millis();        //Zero our timer
       requestBMS_OFF ();
Serial.println("Put BMS in drive mode"); 
 }}  

Additionaly i will add several alarm states such as:

  • cell Over Temperature Alarm…..OOOOOL
  • cell Under Temperature Alarm…OOOOOOL
  • cell Over Voltage Alarm………….OOOL
  • cell Under Voltage Alarm………..OOL
  • cell Voltage Difference Alarm….OOOOL
  • BMS change of balancing state…OL

Alarm beep consists of short beeps and a single long beep to signify classification of alarm. For example a single short beep and long beep will mean VCU veaking up from reset.

I will program alarms by severity and similarity

Some alarms would only warn user by a series of beeps. Others would activate BMS output pin which will disable charging or driving.

Each alarm state will require change of state to be reset. Either transition from charging to driving or from drive to charge…

Use of buzzer

https://robocraze.com/blogs/post/how-to-use-buzzer-with-arduino

2 thoughts on “ESP32 BMS master

  1. EV-FAN.HU April 17, 2023 / 3:01 pm

    Hello Arber, is there a forum, or other “place”, where more info can be found about your projects? I’m a 406 Coupe enthusiast, and (I used to be) a DIY EV builder, so I was really excited when saw your electrified Coupe 🙂 last year. Recently bought a “donor” Coupe, and I definitely want to convert it to electric. Your experiences could save years for me… can we please consult about it?

    Like

    • arber333 April 17, 2023 / 7:04 pm

      Hi

      Are you from Hungary? I am from Slovenia, so very close. You could come visit :).

      There is a lot of information online here:
      https://openinverter.org/forum/
      And Wiki as well

      I am working with Lebowski inverter because it is so smooth at speed. But if you would be satisfied with 80kW you could just use Leaf motor and inverter together. I am making a control VCU to drive that via CAN bus together with AC compressor and heater.

      Like

Leave a comment