# Build Your Own Battery Monitor

Written by

## Counting Coulombs

### If you think of a battery charge as “fuel” for your electronic system, “coulombs” are the “gas.” In this article, Jeff discusses the math, history and science behind Coulombs law. He then shares the details of his project to build a battery monitor based on ADI’s LTC4150 “coulomb counter” device.

• How to build a o build a battery monitor based on ADI’s LTC4150 “coulomb counter” device

• What is Coulombs law?

• How to select battery cells

• How to count coulombs

• How to use and program the Trinket from Adafruit

• How to develop the software using Circuit Python

• Analog Devices’ LTC4150

• Microchip Technology ATSAMD21E18 MCU

• Circuit Python

Eventually, the pendulum swung back and we saw gas (and heating oil) prices below \$2 a gallon. While they are now rising slightly, 20 years ago I thought I’d never see those prices ever again. It’s hard to guess the real reason for such swings—politics, reserves, auto industry and the like. In the near future, I foresee an environmental tax to offset the damage we’ve done, and to help pay for what is necessary to reverse it.

“Nuff said” on that (thanks, Stan Lee). I do, however, want you to think about the gas tank in your car, because it does relate to the “coulomb.” First, like many terms, the coulomb is named after its discoverer, French military engineer and physicist Charles-Augustin de Coulomb. During his decades of military service, he was continuously involved in engineering fields, including structural and soil mechanics. His later work in physics led to the discovery of the inverse relationship of the force between electric charges (and magnetic) and the square of their distance. This was named after him as Coulomb’s law. Here’s the equation:

Or, in words:

The two basic parts of an atom are protons and electrons. Each has an electrical charge. This elementary charge (e or sometimes qe) is considered positive for protons, and negative for electrons. The coulomb (C) is a unit of electric charge and is equal to a number of elemental charges (or proton/electron charges).

When 1V is placed across a 1Ω resistor, we get 1A of current passing through it. If we could see into this resistor, every second we would see 6,241,509,629,152,650,000 electrons pass through the resistor. That’s 1C of electricity—the amount of charge conveyed in 1 second by a current of 1A.

##### STORAGE CELLS

When you choose a battery to power your portable equipment, you will most likely make a compromise on the size of the battery. The volume/weight is inversely proportional to how long it will power your equipment before it must be changed (or recharged). The chemistry and size of the battery determine the energy it can store. Batteries usually have ratings tattooed on their skins describing their capacity—numbers such as “1.2V NiMH 2,500mAhour” or “3.7V Li-ion 5,800mAh.”

When multiple cells of the same type are put in series, we get twice the voltage or potential to push electrons through a circuit. When multiple cells of the same type are put in parallel, we get twice the capacity. It is every designer’s dream to never have to change or recharge batteries, but with the technology of today’s batteries, that just isn’t possible. We must therefore know the amount of current our circuit needs, to be able to calculate how long the chosen batteries will last, based on their advertised capacity.

Going back to our 1Ω resistor example above, this is pretty straightforward, because the load does not change. This “heater” will have a constant current of 1A per second as long as the voltage remains the same. The nickel metal hydroxide battery above has a capacity of 2,500mAhour or 2.5Ahour. If our circuit drains current at a rate of 1A per hour, then we can expect to continue at this rate for 2.5 hours. If we dropped the load down to 0.4Ω, the same battery would be drawing 2.5A, and the battery would last 1 hour (that’s its rating). We would get more heat (power across the load), but less running time.

While that seems simple enough, since the battery voltage starts to drop as its charge content is used up, the calculation will constantly be changing, until the point where the voltage is too low to force any more current through the load. Now, suppose your circuit had other components in it. The load might actually be changing constantly. Indicators might be lighting up, relays energized, sensors taking a reading, display’s changing or wireless modules periodically transmitting or receiving. While you could take a current reading at some point, you can’t assume an accurate approximation of current usage unless you continually measure and recalculate. Enter the coulomb counter.

##### COUNTING ON COULOMBS

The total charge over time is the integral or sum of the currents at those times. The integral is a calculus term that provides a way to calculate the sum of infinitesimal samples. It can be used to calculate the area under a curve. Without getting too deep into the mathematics, we can look at the area as a rectangle, during which time electrons flow through our resistor for 10 seconds (Figure 1). During that time, the current remains at 1A.

The area of the rectangle with a height of 1A and the length of 10seconds = 1A x 10s or 10Asecond. We could just as easily have summed ten 1Asecond samples to get the 10Aseconds. It’s apparent that even if the current changes over time, we can get the sum of the area by breaking it down into the sum of multiple samples. You can see that the higher the sample rate, the more accurate the sum will be.

So, to count coulombs we need to sample current and to sum the samples. The faster the better. Thinking along these lines, this could be accomplished using a fast ADC (analog-to-digital converter). An alternate method is used by the Analog Devices (ADI) (formerly Linear Technology) LTC4150, a popular Coulomb Counter/Battery Gas Gauge [1]. This device uses an analog integrator, an op amp with a capacitor in its feedback loop, to automatically sum the inputs that come from a low-value sensing resistor between the battery and the load. Current through the sense resistor produces a voltage across it. When this input voltage has charged up this capacitor such that it approaches the supply voltage, it remains clipped and integrating (summing) ceases.

As seen in the block diagram in Figure 2, control logic is used to recognize that a positive or negative voltage level has been reached and resets the integrator (shorts it out), so that integration can again continue. Each time this reset happens, a counter is incremented when the integration is positive and decremented when the integration is negative. Depending on the polarity of the sense resistor, the input to the integrator will be either positive or negative. This allows the device to monitor both charging currents and discharging currents, and produce one count for each integrator reset. A separate output indicates the polarity (charge vs. discharge), so you know if you need to add or subtract the current pulse.

Today, you will often find modules that allow you to use a particular device immediately. That is, a small PCB that holds the reference IC and the supporting parts necessary for the IC to operate in the real world. Often times this will have an SPI or I2C interface, which makes for quick connections, especially if you have a dedicated Arduino or other such board set up to read and write using these protocols. While neither of these protocols is available here, it still only requires minimum connections to a microcontroller (MCU) with some available I/O.

I like using any MCU that is supported by the Arduino IDE. The Trinket from Adafruit uses a Microchip Technology ATSAMD21E18 32-bit Cortex M0+ MCU on a tiny PCB. Although the Trinket has only five I/Os, it has an onboard USB connector that is used for programming and console serial without compromising any of the I/O. If you are familiar with these MCUs, you know they are used a bit differently than the standard USB port that is attached to a processor’s dedicated serial. The MCU can repurpose the on-board USB port to look like other USB devices.

The Trinket makes use of this with a special bootloader. The Trinket comes preloaded with Circuit Python. Circuit Python is a derivative of MicroPython that, if you are familiar with Python, allows you to get up and running quickly with low-cost MCUs. Circuit Python is a .UF2 file that the bootloader loads and executes. At this point, Circuit Python takes over and can interpret your Python program. Because all of this is taking up extra space, the user’s available RAM and flash space is reduced.

When the Trinket USB is attached to a system, it is recognized as a virtual drive. Your program and any libraries you may need are files that you store on this virtual drive. Upon reset, Circuit Python looks at the drive, and if it finds code.txtcode.pymain.txt or main.py in that order, it then runs the first one it finds. Since the drive contents are stored on the Trinket itself, it runs code whenever reset. It can then repurpose the USB connection as a console serial port for use with a terminal program.

I was a bit excited about presenting a project using Python, and got started right away. Since I wanted this project to have some feedback to the user, I needed a display. In the process of adding the support libraries for the 128×64-pixel SSD1315 OLED and building the text messaging display routines, I ran into some error messages that I was running low on resources. At this point, I had a long way to go, and knew that I probably should change gears sooner rather than later.

##### TRINKET’S NEW PERSONALITY

Instead of replacing the Trinket with a different MCU, I decided to step back and use the Arduino IDE and forgo Circuit Python. This was easy, because the Arduino IDE can get your program into the Trinket via the .UF2 process replacing Circuit Python with your application, and frees up the resources it was consuming. I won’t go into the .UF2 file format here, but if you’d like to know more, you can google it or ask me to do a column on it. This was a good choice, because I had already wired up a prototype project (Figure 3), and could now delve into getting the interface operational. Let’s do a little math first to see how the value of series resistor affects the LTC4150’s pulse output.

The integrating capacitor (100pF) is fixed and internal to the LTC4150. The only variable here is the series RSENSE resistor. The integrator runs off a single supply, and since the integration can be positive or negative, the op amp must be biased up off ground. With an operational VDD minimum of 2.7V, the full signal excursions must be kept well below that. You can see from the REFHI and REFLO that this is set at about ±0.375V around the bias. With a specified input voltage maximum of 50mV and the fixed integration capacitor, the integration time will be 600µs. Therefore, the control logic will receive a pulse every 600µs when the input voltage is 50mV.

This 50mV is the key to determining what RSENSE value to use. While the module I am using has a 0.05Ω resistor for RSENSE, the surface mount part can be removed, and a different value resistor can be substituted. Holes are provided for a through-hole component as an alternative to the SMT part. To get 50mV across a 0.05Ω resistor, we would need to supply I=E/R or 0.05V/0.05Ω = 1A. Note: This is the maximum current we expect to have either as load current or charging current.

So why is this important? Let’s say you know your circuit has a maximum draw of 50mA, which is 0.05A/1A = 1/20 of the maximum, which integrates in 600µs. This means the integration rate will be 600µs × 20 = 12ms. This doesn’t sound like a big number, right? But, refer back to the block diagram (Figure 2), and you will find there is a counter receiving pulses from the integrator. It increases the interrupt period (at 1A) from 600µs to around 0.6 seconds. Now the 12ms (at 50mA) has become 12.2 seconds. That’s the time between pulses (interrupts) to your circuitry. Should the circuit draw less than that maximum of 1A, then this time will be even longer. You could improve the feedback from the LTC4150 by increasing RSENSE by a factor of 10 to 0.5Ω. You would get 10 times the pulses in the same time frame, but you will need to limit charging currents by 1/10 as well, or you will be over-stressing the LTC4150. Life’s a compromise.

Since any calculations you will make are based on time between pulses, you must wait for an output pulse to occur to update any calculations. For the maximum designed current of 1A (RSENSE = 0.05Ω) we can expect a pulse every half second. For low operating currents, this could take a while. Independent of our RSENSE value, but based on the 50mV maximum voltage across it, the output pulse frequency will be:

Or

… or a pulse every:

and each pulse is equal to:

Or

with 1Ahour equal to 3,600C, a pulse is also equal to:

Or

So, for each 1Ahour that’s 1Ahour/0.171mAhour/pulse or 5,848 pulses (based on 50mV across 0.05Ω). If you have a smaller maximum current—say 0.1A—then you would substitute RSENSE = 0.5Ω and a pulse would equal 0.017mAhour. With a larger maximum current—10A for example, RSENSE becomes 0.005Ω, and a pulse would equal 1712mAhour.

##### WHAT CAN WE FIGURE OUT?

One thing to note here is that this module does not connect the LTC4150’s VDD to VIO. We could choose to power this (and the rest of our project) from the battery under test. If we did this, we would be seeing the project as an additional load to the battery under test. I’m powering the project separately, so as not to influence the circuit under test.

From the schematic (Figure 3) you can see that two of the Trinket’s five I/Os are dedicated to the I2C display. Two of the I/Os are inputs from the LTC4150, and the remaining I/O is used as an analog input to measure the voltage of the battery under test. Note that the voltage divider on the analog input puts a small load on the battery under test.

From these inputs, we can glean two pieces of information—the actual battery voltage, and the current over the last interval. Initially this current will be zero until the LTC4150 has seen over 6×1023 electrons (1C of charge based on our RSENSE = 0.05Ω). This could be from 0.6 seconds to infinity (current=zero).

Elapsed time will be important here, since we will need the time between output pulses to determine the rate of discharge (or charge). The Arduino’s millisecond timer will be most adequate for this. While not absolutely necessary for live activity, knowing the battery type will allow us to calculate the amount of time the battery should last, based on the data input. I allow the data to be input through the USB port of the Trinket. A short menu is sent out this port to a serial terminal application on your PC. This will allow you to identify the battery type along with its nominal charged voltage and its capacity. Here is the menu:

User Presets
1 – Type: Li-ion
2 – Capacity: 5800 mAhour
3 – Battery Maximum: 3.70V
4 – Battery Minimum: 2.00V
Type item number to change

This data is stored in flash until changed by the user. So, the first page of information to display is the user data. All calculations will be based on this information, so it should be accurate to achieve the best results (Figure 4). The second page of information includes the live measurements. Here the actual battery voltage and last current measurement are presented, along with a status line, which will read “Waiting for battery” until you’ve connected the test article (Figure 5). The last page includes the cumulative (calculated), coulombs used, Ahours used, percent charge remaining and estimated time until empty (Figure 6). Prior to any output pulses, before any calculations can be made, a status line will read “Waiting for LTC4150.”

##### INTERRUPTION

The pulse output of the LTC4150 is connected to a Trinket I/O input that is used as an interrupt source, and this is where most of the calculations occur. An interrupt is set up using the `attachinterrupt `function.

`attachInterrupt(digitalPinTo Interrupt(int_Pin), Count, FALLING);`

When the falling edge of the output pulse occurs the interrupt routine `Count()` is called. The total count tics will increment or decrement depending on the LTC4150’s polarity output. The time of the interrupt` ti` is stored. The duration or time since the previous interrupt,` lastti` (initially set to zero) is stored. A flag `t `is set to indicate an interrupt has occurred. This flag can be tested in the main `loop()` to see if an interrupt has happened.

Ordinarily you want the interrupt routine to be as short as possible to avoid holding up any other functions, but no other important timing is taking place, so I can do a few calculations as well. The coulombs can be calculated from `tics `and the constant `cpi `(coulombs per interrupt) and the mAhour current from `tics `and the constant `api `(amperes per interrupt). The present average current (over the last duration) is also calculated based on the pulse time of 0.6411 seconds/1A (Listing 1).

``````LISTING 1 - Code for the interrupt routine Count()

//------------------------------------
// Interrupt on 0.614439 seconds / Ampere
//------------------------------------
void Count()
{
if(polarity)
{
tics--;      // charging
}
else
tics++;           // discharging
}
ti=millis();
duration = ti - lastti;
lastti = ti;
t = true;
coulombs = tics * cpi;
mAhr = tics * api;
mA = pTimeAt1A * 1000000 / duration;
interruptCount++;
}``````

The main `loop()` has very little to do. Based on reading the battery voltage, we know if a battery has been attached, and its present voltage. With this information, the String `message `can be set based on this information. If there is no battery, `message `= “Waiting for battery.” If flag t has not been set, `message` = “Waiting for LTC4150.” If battery voltage < BatMin, `message` = “Start charging now.” If battery voltage > BatMax, `message `= “Stop charging now.” Otherwise, `message `= “Battery in use.”

Even with a display capable of 8 lines of 20 characters each, it was clear that I would need multiple screens to show everything. Every 5 seconds, the display routine updates one of the three pages of information in round-robin fashion (as shown in Figures 4, 5 and 6). With the ability to draw text and graphics at specific pixel locations, I was able to draw some simple frames around lines of text. I had to waste one text line to format the text with borders, but I think it was worth it. Although you do need a terminal to change the battery information, once it is set, a project can be run stand-alone, powered by a 5V power supply, its own Li-Ion battery or the battery under test.

##### CIRCUIT UNDER TEST

As part of a previous column—”Direct Line Connection” (Circuit Cellar 248, March 2011) [2]—I described a simulated lighthouse beacon that used 16 LEDs mounted around the edge of a small circular PCB (Figure 7). 5V powered a PIC to fade the LEDs ON and OFF in succession. Since the LED output will fall to NIL as the input voltage goes down, I’ll use a small DC boost module to create a 5V output for the beacon. The boost allows input from a single cell battery to power the 5V. This means that as the battery voltage goes down, the converter will require proportionally more current to allow the beacon to run continuously at maximum output (assuming efficiency stays the same), and our coulomb counter will accurately keep track of the total coulombs (ampere hours) used. While I like the thought of counting actual electrons, the numbers are so large that they dwarf the national deficit of \$25 trillion. The coulomb makes this a bit more manageable.

Many 18650 Li-ion batteries have a circuit to prevent over discharging; this should trip to prevent further discharge. If you’ve experienced cells that no longer work after being discharged, you may have a faulty cell or more likely a fake, especially if rated beyond 3,000mAhour. If you leave them connected to your load you may find them discharged below the protection voltage and dead to many chargers. Most chargers will not provide a charge to cells that have discharged to less than 2.0V. You can bring some of these back to life by slowly trickle charging them with a small current until their voltage comes up to greater than 2.0V. Just beware, you usually get what you pay for. Check out this site for some good info at [3].

Knowing how long our battery will last, before the internal low voltage cut-off circuitry attempts to save the cell by removing it from battery terminals, is only half the battle. Note: The battery under test, the 18650, has this safety circuitry built in. Be careful, because not all cells have this, and if you discharge them past the safety level, usually 2.0V, then it can be permanently damaged!

For projects that run continuously on internal battery power, you will either have to periodically exchange or recharge the batteries externally, or provide some alternate charging source. Solar cells are the usual go-to source for doing this. But how big of a cell do you need? What kind of output can you expect—and here’s the key—for your climate? Depending on where the equipment is used, a solar cell can provide dramatically different results. I intend to use the coulomb counter to record some good data on what I can expect my latitude to provide. Connecticut is not in the sun belt. That just means requiring more cells to get the same output as in, say, Arizona. So much to do, so little time…

RESOURCES

References:
[1] LTC4150 datasheet https://www.analog.com/media/en/technical-documentation/data-sheets/4150fc.pdf
[2] “Direct Line Connection” (Circuit Cellar 248, March 2011)
[3] http://danyk.cz/test18650_en.html

Analog Devices | www.analog.com
Microchip Technology | www.microchip.com

PUBLISHED IN CIRCUIT CELLAR MAGAZINE • JULY 2021 #372 – Get a PDF of the issue

 Keep up-to-date with our FREE Weekly Newsletter! Don't miss out on upcoming issues of Circuit Cellar. Subscribe to Circuit Cellar Magazine Note: We’ve made the May 2020 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine. Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.