DC Smart Panel 01

Here is a new project I’m working on and hoping to get to a point where I can share the files and source code.

This is a replacement for a passive DC fuse box that you typically buy for low power solar projects.

This will allow you to remotely control 6 independent circuits each with a dedicated 25A max blade fuse. It uses an esp32 pico and each circuit has a 31A max current sensor. With the esp32, data logging can be incorporated and sent to influxDB server.

I’m very excited and hopefully I can get this project done so you can enjoy it as well

8S Arduino Cell Monitor

Introduction

Like all my other projects, I’ve decided to create my own custom version of a cell monitor. Here we have an 8S arduino based voltage cell monitor that I designed to allow the user have control on how they monitor there batteries and how its displayed.

Its a very basic and not a lot of bells and whistles but still powerful enough to allow you to monitor cell voltages with accuracy and precision. This project is open source and so you will have the ability to add features or change them.

Specifications

  1. Cell count: 8
  2. Battery type: Li-ion, LiFePO4, NiMH
  3. ADC resolution: 16-bit
  4. Voltage measurement resolution: 1mV accuracy
  5. Fault detection: Over-voltage, Under-voltage
  6. I/O: 1-CH Digital output

The main purpose this is designed for is to measure an 8S Lithium Iron Phosphate pack up to 1mV resolution.

Hardware

Now here is were the fun part starts with the hardware used to create this DIY cell monitor. The main sections we will talk about are:

  1. ADC
  2. Power Supply
  3. MCU
  4. Menu Button Controls

ADC

Here we talk about the most important part of the project and that’s the ADC section. I’ve decided to go with two external 16-Bit adcs instead of the internal adcs of the arduino.

Since we need to step down the voltage from 30V max and need the best resolution possible. If we go with the internal adcs of the arduino, with a 10-Bit ADC we will only have resolution of 17mV/Bit. Now with 16-bit adcs, we have a resolution of 0.9mV/bit which is a huge increase.

I’ve decided to go with the ADS1119IPWR which is a 16-bit I2C adc. See below for a snip-it of the schematic:

ADS1119IPWR 16-Bit ADC x2

Power Supply

In the power supply section, we have a small profile buck converter that can handle up to 36V input. For this version I did not add a series fuse for the input of the buck converter but will add in second version. One this that was added was a tvs diode to protect the buck converter.

One thing I noticed is that when you plug anything into a high capacity battery bank, you need to protect your circuit from inrush current that causes a quick spike in current voltage that could damage your circuit before you even start. Adding a TVS diode will clamp this voltage and protect your circuit.

MCU

As the title implies, we are using the Atmega328P running the minicore bootloader https://github.com/MCUdude/MiniCore .

One thing I did change from the standard arduino board is use a cmos oscillator instead of the typical crystal oscillator. Honestly this is mostly a preference but it can be modified to use the typical crystal.

Menu Button Controls

Here is the schematic section for the control buttons. Its a little more complex than the typical push button design but this helps a lot with debounce.

We have a two stage design used to eliminate electrical debounce. First we have a passive RC low pass filter and then an inverting logic gate which adds some buffer to further reduce any false triggers.

Below is the full caption for all 4 input buttons:

Software

Normally I would breakdown every section of the code but its too large to fully go through the code. But I will go over the important sections:

  1. Menu controls
  2. ADC calibration

Menu Controls

In the control menu you are able to set the UVP value for cells and pack as well as the OVP value for cells and pack. In order to get into the menu screen you need to press the menu button and hold for about a second or two.

Once you are in the menu screen, you can navigate using the up and down buttons. Now even though I have the debounce circuit and a small software delay, the movement is still not smooth but it sufficient to operate.

In order to change the values, navigate for example to the OVP value with the arrow and then click the enter button to enter the OVP value screen. Using the up/down buttons you can increase or decrease the value by 0.05 increments. Once you are satisfied with your value then you can save the value by pressing the enter button and this will save to EEPROM so that its store permanently even after the power is removed.

ADC Calibration

Now before you can get accurate measured values, you need to calibration each ADC input to fix the offset from each IC.

You can calibrate using the serial monitor with an FTDI board connected to the UART port of the cell monitor.

The simplest way of calibrating each value is to measure each cell with a multimeter and write down the values.

Next, in the serial monitor, you need to enter the following:

1002, (cell 1 value), (cell 2 value) , (cell 3 value), (cell 4 value), (cell 5 value), (cell 6 value), (cell 7 value), (cell 8 value)

example: 1002,3.111,3.154,2.967,3.546,2.567,3.222,3.234,3.555

Make sure not to include spaces as this will not calibrate if you do.

After the calibrate is complete, calibrated values are stored in EEPROM so that after power is removed then the board is still calibrated.

Conclusion

Even though you can buy an 8S lithium battery monitor with probably more features, this one gives you more flexibility and also the confidence that its doing what it is suppose to do.

If you have any comments, suggests, or feedback let me know in the comments below. Thank you.

Here is the link to the schematics and code on my github:

https://github.com/simple-ee/CellMonitor

Arduino 4S BMS: UPDATE (Version 5)

UPDATE:

This is an updated post regarding my 4S arduino based lithium ion balance management system (BMS).

If you click here, you will see the original post with details on the project.

IMPORTANT CHANGES:

  1. Replaced external voltage reference to a more stable one. Upgraded to a REF3012AIDBZT from Texas instrument.  This does not require an output capacitor for stability.
  2. Modified Buck converter RT6208GE.  Grounded pin 4 to set the max peak current to 50mA which is an average current of 25mA.  This was done to eliminate audible oscillation from the buck converter
  3. Increased copper traces for ADC pins A0-A3
  4. Better layout to increase continuous copper plane on internal layer

Minicore bootloader

updated 01/13/2019: For the new arduino version, load bootloader with 8MHz clock. I did more testing and this was more stable and accurate on the adc then the 1MHz clock setting.

I will be doing testing on these new versions and see how they perform.  I will post results as soon as I can.  Also below I will attach all files needed (schematics, gerber, bom) if you wish to make your own. 

Arduino Code Download

updated 01/13/2019: Download arduino code version A15.

Schematic and Gerber Download

ARDUINO BMS

 

CHECK UPDATED VERSION 5 HERE

CHECK UPDATED VERSION 7 HERE

INTRODUCTION

Now this might seem like it was a complete waste of time to try and reinvent the wheel with creating my owm custom BMS solution. You can buy them on ebay or alibaba or banggood for super cheap and it seems like a plug and play solution.

One problem I see with this, at least for me, is that I dont completely trust those cheap BMS system. I like to buy something that is reasonable as well as trusting that it will safely protect my cells from unbalancing. Another this is that I like to have flexibility with how I balance my cells or anything else.

Now this is where the custom arduino BMS came into play. I’ve designed this bms to handle a 4S battery pack so upto 16.8V fully charged and 14.8V nominal. The balance discharge current can be adjusted by simply replacing the discharge resistor with any value you wish. Just to add this does not have over current or over voltage protection..yet 🙂 I’m currently working on an add on for this so stay tuned for that future update.

Lets get started with all the cool details for this arduino bms.

3D RENDERING

Here i just wanted to show you the 3D rendering images of the final board. The rendering was created using kicad and its pretty nice actually for an EE cad software.

The heart of this system is the atmega328P running the minicore arduino bootloader[1]. From here you have the 4 balancing passive resistors to the right of the board. The best part of this project is that you can replace these resistors to any value you want to customize the balance current.

The one thing that this does not have is overvoltage and overcurrent protection but that will be a separate circuit later on ;).

Now we can talk about the main features below in the schematic section.

SCHEMATICS

Below are images of the full schematics for this project:

The first page of the schematic files is a hierarchy block level of the system to help understand how a lot of these subcircuits are linked together. Below I’ll breakdown all the pages and what exactly they do.

1. Hierarchy Block:

  • This is the overview block level that links the subcircuits on the separate pages. I normally like to design my projects this way if I have more than 2 subcircuits because its easier to see how things are working together

2. Power Supply

  • This section is just the power supply section to power the arduino. This is a high differential buck converter. The input voltage can go up to 36V and output down to 1.8V with an active quiescent current of only 190uA. Now the most important part is the quiescent current because we are trying to eliminate any wasted current since this is for a battery operated system and we do not want to drain the batteries faster than needed.

3. MCU

  • The MCU page has the Atmega328P and all the corresponding IO related to it. Im using A0-A3 to measure the batteries via a resistor divider
  • This page also includes the external reference voltage used instead of the built in 1.1V from the atmega328p. Its a 1.25V external voltage reference that has a better tolerance at a range of temperatures.

4. Balancing Circuit

  • This page has the balacing circuit which is incharge of discharging the battery cell that has a higher potentional. The heart of this is a bidirectional N-channel mosfet that allows current to flow in both directional either during discharge or during charging.

5. Connectors

  • This page has the power pin connectors and the BMS pads that connectors to the lithium cells.

Now that we’ve gone through the schematic lets go into the second most important part and that is the arduino code.

ARDUINO CODE

Now probably the second most important part is the arduino code that will control the mosfets triggering the discharge when a preset voltage difference is hit.


#include "Adafruit_SSD1306.h"

/* Revision: A9

* Written by: Steven Guzman

* Date: 7/10/2018

* Description: This is a customizable BMS system for a 4

* cell lithium ion pack.

*/

#include "Wire.h"

#include "avr/wdt.h"

#define OLED_RESET 4

Adafruit_SSD1306 display(OLED_RESET);

#define NUMFLAKES 10

#define XPOS 0

#define YPOS 1

#define DELTAY 2

//////////////////////////////////////////////

// This variable is used for checking on

// whether the OLED screen is attached or not

//////////////////////////////////////////////

int check = LOW;

//////////////////////////////////////////////

// Sampling number for analog read values.

// Will use this sample size to calculate a

// better estimation of the analog value

//////////////////////////////////////////////

int samples = 100;

//////////////////////////////////////////////

// This is the max difference that the cells

// can achieve in respect to each other

//////////////////////////////////////////////

float tol = 0.03;

//////////////////////////////////////////////

// To temporaly store analog values from the

// cell voltages to average out later with

// the number of samples taken

//////////////////////////////////////////////

float temp_cell[4];

//////////////////////////////////////////////

// Float array to store unconverted cell values

//////////////////////////////////////////////

float cell[4];

//////////////////////////////////////////////

// Variable array to store converted voltage

// values for cell measurement

//////////////////////////////////////////////

float B[4];

//////////////////////////////////////////////

// Variable array to store voltage differences

// between cells

//////////////////////////////////////////////

float diff[4];

/////////////////////////////////////////////////

// Variable used for digital output

// signals

/////////////////////////////////////////////////

int BAT[4];

/////////////////////////////////////////////////

// Variable used for storing the cell pack

/////////////////////////////////////////////////

float PACK = 0.000;

/////////////////////////////////////////////////

// Scaling factor used for converting

// the scaled down analogread values to there

// actual values from the voltage divider

/////////////////////////////////////////////////

float scale0 = 0.25; // 10k/(30k + 10k)

float scale1 = 0.125; // 10k/(69.8k + 10k)

float scale2 = 0.083; // 10k/(110k + 10K)

float scale3 = 0.0625; // 10k/(150k + 10k)

#if (SSD1306_LCDHEIGHT != 32)

#error("Height incorrect, please fix Adafruit_SSD1306.h!");

#endif

void setup()

{

Serial.begin(9600);

analogReference(EXTERNAL); // Configure reference voltage for

// external 1.25V

//////////////////////////////////////////////

// After switching to external reference you

// need to read a value a couple times before

// the reference has stabalized.

//////////////////////////////////////////////

analogRead(A0);

analogRead(A0);

analogRead(A0);

delay(20);

// Setup up watchdog timer to reset

// after 4 seconds

wdt_enable(WDTO_4S);

///////////////////////////////////////////////

// Variables to set the digital output

// pins 1-4

///////////////////////////////////////////////

BAT[0] = 5;

BAT[1] = 6;

BAT[2] = 7;

BAT[3] = 8;

///////////////////////////////////////////////

// Setting digital pins to output configuration

///////////////////////////////////////////////

pinMode(BAT[0], OUTPUT);

pinMode(BAT[1], OUTPUT);

pinMode(BAT[2], OUTPUT);

pinMode(BAT[3], OUTPUT);

///////////////////////////////////////////////

// Sets the digital outputs to an initial low

// state

///////////////////////////////////////////////

digitalWrite(BAT[0],LOW);

digitalWrite(BAT[1],LOW);

digitalWrite(BAT[2],LOW);

digitalWrite(BAT[3],LOW);

}

void loop()

{

Wire.requestFrom(0x3C, 1); // Pings OLED screen

///////////////////////////////////////////////

// If OLED pings back and check statement

// equals 1, then initiate the OLED screen

// and reset the check value to 0 so it will

// not continue to re-initiate. It needs to

// initiate another request one last time

///////////////////////////////////////////////

if (Wire.available() && check == HIGH )

{

display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

check = 0;

Wire.requestFrom(0x3C, 1);

}

if ( Wire.available() && check == LOW)

{

// Leave blank

}

else if (!Wire.available())

{

check = 1;

}

///////////////////////////////////////////////

// Sets the temp variable array back to zero

///////////////////////////////////////////////

for (int x = 0; x <4; x++)

{

temp_cell[x] = 0.00;

diff[x] = 0.00;

cell[x] = 0.00;

}

///////////////////////////////////////////////

// For loop used to record battery cell voltage

// information and used later to average out

// the readings

///////////////////////////////////////////////

delay(100);

////////////////////////////////////////////////

// Using true RMS calculation for computing a

// better average estimate for multiple samples.

////////////////////////////////////////////////

for (int i = 0; i < samples; i++)

{

temp_cell[0] = temp_cell[0] + sq((analogRead(A0)/scale0)* (1.249 / 1024));

delay(1);

temp_cell[1] = temp_cell[1] + sq((analogRead(A1)/scale1) * (1.249 / 1024));

delay(1);

temp_cell[2] = temp_cell[2] + sq((analogRead(A2)/scale2) * (1.249 / 1024));

delay(1);

temp_cell[3] = temp_cell[3] + sq((analogRead(A3)/scale3) * (1.249 / 1024));

delay(1);

}

cell[0] = sqrt(temp_cell[0] / samples);

cell[1] = sqrt(temp_cell[1] / samples);

cell[2] = sqrt(temp_cell[2] / samples);

cell[3] = sqrt(temp_cell[3] / samples);

////////////////////////////////////////////////

// Scale up the cell values and then convert to

// to voltage values

////////////////////////////////////////////////

B[0] = (cell[0]);

B[1] = ((cell[1] - cell[0]));

B[2] = ((cell[2] - cell[1] ));

B[3] = ((cell[3] - cell[2] ));

PACK = B[3] + B[2] + B[1] + B[0];

///////////////////////////////////////////////////////////////////

// Compares each cell voltage to each other and then finds the

// lowest cell value. Then it stores the difference between the

// lowest cell and the remaining cells for comparison

///////////////////////////////////////////////////////////////////

if ( (B[0] <= B[1]) && (B[0] <= B[2]) && (B[0] <= B[3]))

{

diff[1] = B[1] - B[0];

diff[2] = B[2] - B[0];

diff[3] = B[3] - B[0];

}

else if ((B[1] <= B[0]) && (B[1] <= B[2]) && (B[1] <= B[3]))

{

diff[0] = B[0] - B[1];

diff[2] = B[2] - B[1];

diff[3] = B[3] - B[1];

}

else if ((B[2] <= B[0]) && (B[2] <= B[1]) && (B[2] <= B[3]))

{

diff[0] = B[0] - B[2];

diff[1] = B[1] - B[2];

diff[3] = B[3] - B[2];

}

else

{

diff[0] = B[0] - B[3];

diff[1] = B[1] - B[3];

diff[2] = B[2] - B[3];

}

//////////////////////////////////////////////////////////////////

// It enables the balancing protocol for a given cell that has a

// greater voltage difference from the lowest cell and the set

// tolerance.

//////////////////////////////////////////////////////////////////

delay(1);

for ( int x = 0; x tol )

{

digitalWrite(BAT[x], HIGH);

delay(1);

}

else

{

digitalWrite(BAT[x], LOW);

delay(1);

}

delay(1);

}

// text display tests

display.setTextSize(1);

display.setTextColor(WHITE);

display.setCursor(0,0);

display.print("V1:");

display.print(B[0]);

display.println("V");

display.print("V2:");

display.print(B[1]);

display.println("V");

display.print("V3:");

display.print(B[2]);

display.println("V");

display.print("V4:");

display.print(B[3]);

display.println("V");

display.setCursor(70,0);

display.print("VP:");

display.print(PACK);

display.print("V");

display.setCursor(70,8);

display.print("Config:");

display.print("4S");

display.display();

delay(1);

display.clearDisplay();

wdt_reset();

}

Lets break down this code in order to understand my thought process behind this.

arduino_code_sampling_section
  1. The samples variable is used to create an average sampling of the battery cells to eliminate any noise issues. The averaging technique I used in this project is a little different than most but I will explain later on
  2. The next variable is the tol variable. This variable is the maximum voltage different between the lowest cell and the rest of the cells in the 4S pack. Currently its set to a maximum of 30mV difference but this can be set to a minimum of 10mV because of the 8-bit adc on the arduino.
arduino_code_voltage_divider_section
  1. Here is the scaling factor for the adc from the cell measurements. We have different values because in order to get the best accuracy measuring in reference to ground, different values were needed for measuring cell 1 which has a max of 4.2V and cell 4 which measures a max of 16.8V.
arduino_code_oled_section
  1. This portion of the code is mainly for the OLED screen. It allows for the screen to be removed and re attached without having to reset the arduino. Before you had to initialize it in the setup but with this loop it is initialized in the main loop when it detects the screen.
arduino_code_average_section
  1. Now this section is averaging the adc values from all 4 cells to get a best measurement of the cell voltage values. Instead of using the traditional sample 25 and take the average, I’m using a true RMS calculation that will give you a slightly better voltage measurement that just taking the average. It will be at a closer measurement to your multimeter than using average sample.

CONCLUSION / ISSUES

Here is the final PCB:

I’m pretty excited to do more testing on this and I will post my technical review later on that includes current consumption, maybe a mini manual on how to use it or change values for your own build.

Again this does not include an over voltage or over current protection circuit but I decided to make that a separate board with its own controller in order to make the board as small as I could and isolate the two circuits as a redundancy. I’m still making some tweaks and might make another revision but all in all I’m very happy about it.

Some current issues that I know off.

  1. You might have to calibrate with a known good multimeter. The arduinos ADC has some weird issues with analogPin A2 measuring higher than the rest. I added a calibration value in the code to help with that.

DOWNLOAD ORIGINAL FILES

Arduino Code Download

Schematic and Gerber Download

For the Bill of Materials, I’ve embedded the datasheets for some of the major components in case you wondered why the file is larger than it should.

Footnotes

1. Minicore bootloader