How to measure and display battery level on micro-controller Arduino projects
Many micro-controller projects use lithium ion (li-ion) batteries and we needs a simple way to display the state of charge (SoC) which is how much battery we have left. This is typically displayed on the screen as a partially filled battery icon and optionally a percentage indicator next to it.
There are however several challenges we need to address to be able to display reasonably accurate information on the screen. We will focus on the three primary items below which does the job with very little effort.
- How o we measure the battery level?
- How does the batteries characteristics taken into account?
- How do we quickly draw this visually without having to worry about bitmaps that can be slow and harder to work with?
Measuring Battery Level
Measuring of the battery level can be accomplished by by using one of the micro-controllers input ports set up as an analog to digital converter (ADC). The main thing is to account for max voltage on the input pins compared to your batteries max voltage. For example, most li-ion batteries produce around 4.2 volts when they are fully charged so connecting them to a 3.3 volt operated micro-controller input pin will either burn out the pin or the input will read it the same level so you will not be able to see the difference between 4.2V and 3.3V.
The solution is to use two resistor as voltage dividers to reduce the voltage slightly so the maximum voltage to the pin is 0.75X of the micro-controllers rating. So say we we have a Li-on battery at 4.2V and running a ESP32 micro-controller at 3.3V, we want the maximum voltage to the Analog Input pin to be: 3.3V x 0.75 = 2.5 Volts
Using ohms law and this example calculator, we find out the values we need for the two resistors we need as shown:
So all we need to do is connect our Li-Ion battery to the 2 resistors and feed the divided voltage to the input pin of our micro-controller and we will be within safe specs of the ESP32 and we will get much more accurate readings, especially on the higher end.
For the lower end, we need to know approximately the minimum voltage the micro-controller will still operate before it just gives up and shuts down. Looking at the ESP32s data sheet for operating range it says it can operate with minimum source voltage of 2.3 volts.
At this low end right before they system gives up, what is the voltage applied to our input pin through our divider? Using the same formula above we get 1.37 volts.
So what will the ESP 32 input pin measure when it sees 2.5V on the high end and 1.37V on the low end? This can vary depending on how many bits the ADC of the micro-controller has. The ESP32 has 12-bit ADCs which means it can report 4096 different levels on the ADC pin. If you assume 4096 = 3.3V and 0 is 0V, this means our range of 1.37 to 2.5v should give us values between 1700 and 3100. In practice, its not 1:1 like this as the ADC performs differently in higher and lower ends and amount of load on battery can effect it, so you may need to measure the full charge level for your project. For now we can use these and I will show you how to adjust for load later in the article. This brings the next challenge as follows.
Li-ion batteries do not discharge linearly!
We need to understand how a Li-Ion batteries discharge over time because if we simply take the voltage we measure and show it as a percentage between low and high range, we will not get correct results because the discharge curve for Li-Ion batteries are not linear. Instead the they typically look something like below.
Between full charge (V full) say 4.2V and (V exp) ~3.3v, the discharge rate is rapid. For majority of the operating time, the discharge rate is very low. this is the mid section of the graph above. The higher the battery capacity, the longer this middle cycle is. Finally, when the voltage reaches (Vnom), the discharge rate picks up quickly. If we do not account for this you will see some ugly results on your display.
For example, lets say you have a 500mah battery that lasts about 4 hours in your project and our battery reading range was 3100 at full and 1700 right before it would shut down as described previously:
- First 30 minutes you would see the battery go down from 100% to 80% (readings of 3100 to 2820 )
- Next 3 hours you would see it go from 80% to 60% (reading of 2820 to 2540)
- Then, you would see it drop from from 60% to 0 in last 30 minutes! (reading of 2540 to 1700)
How did I go from 60% to 0 so fast? Sounds familiar
We can give users a much better experience and account for this in our project by treating each of these 3 phases differently. We can normalize these phases by
- Mapping the first phase which was originally 100 to 80%, to be instead 100 to 90%.
- Mapping the second phase which was originally 80 to 60%, to be instead 90%-10%.
- Mapping the last phase which was originally 60 to 0%, to be instead 10%-0%.
This is how it would look like in code:
value = analogRead(GPIO_NUM_36);
if (value >= 3100) bat_percent = 100;
else if (value >= 2820) // 90-100% range
bat_percent = map(value, 2820, 3100, 90, 100);
else if (value >= 2540) // 10-90% range
bat_percent = map(value, 2540, 2820, 10, 90);
else if (value >= 1700) // 0-10% range
bat_percent = map(value, 1700, 2540, 0, 10);
else bat_percent = 0;
So I tested this in a test project where I used a small screen with an ESP32 connected to a 500mah Li-Ion battery.
Remember I mentioned earlier that load of projects can effect the range I used above which was 3100-1700. So I fully charged the battery and connected to the ESP32 to capture battery levels and saw the high was almost 2700 and they lows before my battery died was 1700 . I decided to graph my batteries discharge curve by checking voltage every second and sending the values via Bluetooth communication to my PC where I could graph the discharge curve using raw levels and the mapped percentages based on adjustments I made. Here are the results:
You can see the true discharge curve in blue and how the modified code below transformed it to the orange much linear percentage over time.
value = analogRead(GPIO_NUM_36);
if (value >= 2650) bat_percent = 100;
else if (value >= 2500) // 85-100% range
bat_percent = map(value, 2500, 2650, 85, 100);
else if (value >= 2100) // 10-85% range
bat_percent = map(value, 2100, 2500, 10, 85);
else if (value >= 1700) // 0-10% range
bat_percent = map(value, 1700, 2100, 0, 10);
else bat_percent = 0;
Drawing the Battery Indicator
Instead of messing with loading bitmaps which can we a bid difficult to deal with, I found it much easier to just draw 3 of rectangles as follows:
You can then easily update the last filled rectangle with the current percentage adjusted for size of the rectangle. For example if the rectangle is 20 in width, you would just divide your battery percentage by 5 to get the width of the filled rectangle. This is what it looks like on my sample project. You can tell I missed the thickness of my first rectangle
Good luck and make sure to leave comments and questions!
Atari Retro Joystick USB adapter – Make or Buy – 2 and 4 ports.
I recently built a Raspberry Pi 4 unit with Retro Pi and Emulation Station to check out its performance for retro gaming and when I got the Atari 800 emulation to work, it brought back many childhood memories of my first real PC. Games like Star Raiders and M.U.L.E. were well ahead of their time and to get the true experience, they have to be played with real Atari joysticks!
Star Raiders and M.U.L.E. on Atari 800
For past few months, I spent a good amount of time programming micro-controllers for IoT devices for my work projects and I thought about the possibility of using a micro-controllers to adapt Atari Joysticks to a PC so they can be seen as regular PC Gamepads.
I looked online and found some classic joysticks that came with the Atari Game Console Flashback unit for $15 on eBay and decided to give it a shot.
I checked out the Pin Diagrams for the joysticks and it seemed simple enough as they are basically 5 switches (1 for each direction and 1 fire button). Since I wanted at least 2 Joysticks connected for multiplayer games, I needed at least 10 IO ports to connect the 5 switches of each joystick.
I decided to use an Arduino Pro Micro controller since it was only $4, had plenty of IO ports, and most importantly it had a USB port that can communicate with PCs as a HID device.
I created a circuit design on EasyEDA so I could later order a PC baord but initially I created a prototype using PCS Prototype Boards you can get from Amazon or Aliexpress for pennies.
I ordered a few DB9 male connectors where the I could plug the joysticks into and put the parts on the board and soldered the connections.
I wrote some code in Arduino and after several attempts and figuring out custom board files for naming, finally had success. You can get the Dual joystick code from here.
If you are wondering how the animation below shows 5 button working, its not related to directions.. keep reading as there are multi button Atari joysticks.
Next. I decided to make an enclosure for it using my 3D printer! I drew it up in Fusion 360 and printed the top and bottom and made quite a tight fit.
I was finished with first working prototype. Next I refined it to a smaller version with a professionally made circuit board so its more reliable, no jumper wires, and less soldering. So its basically done.
Download STL files here.
But wait, M.U.L.E. is best with 4 players and I had to find a way to get it done so I could play with my nephews. One way was just to build another one taking up 2 USB ports and double the parts, and another was to challenge myself to do a 4 player design with a single USB port. So I did both
For the 4 port unit, I needed more ports so I decided to use a I2C I/O extender that can extend 2 GPIO pins to 16 using serial communication. I also wanted to support the modified Joysticks that have the extra Start, Select, Menu, and a few more button. Here is the circuit diagram and the outcome.
It worked great and I can tell you we had an amazing time playing 4 player M.U.L.E. ! So give it a shot and make one. You can get the code here.
If you rather just buy one from me, you can get it below. All net proceeds go to charity.
- Sale!Product On Sale
document.getElementById in asp.net with Master Pages
Using JavaScript to locate controls in ASP.net pages that are linked to master pages can be tricky. This is because the ASP.net reassigns control IDs dynamically and the names change.
A simple solution is to use asp.net inline Tags.
For Example:
Instead for using:
document.getElementById(“Label1″)
try using:
document.getElementById(“<%=Label1.ClientID%>”)
C# function for a Bezier Curve
The Bezier Curve formula below can be used to define smooth curves between points in space using line handlers (line P0 to P1 and line P2 to P3).
P(t) = (1-t)^3P0 + 3(1-t)^2tP1 + 3(1-t)t^2P2 + t^3P3
At t=0 you will be at p0, and at t=1 you will be at p3.
The function below is a C# implementation of the formula return the X and Y coordinates of a position on the curve for a given Time (t) and the 4 points that define the Bezier Curve.
You can easily extend this to connect multiple curves to each other (multi-segmented path). You can do this simply by making p0 of subsequent segments equal p3 of the prior segment.
Include this function in your c# projects to create smooth curves.
C# Code Sample:
private Vector2 GetPoint(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3)
{
float cx = 3 * (p1.X - p0.X);
float cy = 3 * (p1.Y - p0.Y);
float bx = 3 * (p2.X - p1.X) - cx;
float by = 3 * (p2.Y - p1.Y) - cy;
float ax = p3.X - p0.X - cx - bx;
float ay = p3.Y - p0.Y - cy - by;
float Cube = t * t * t;
float Square = t * t;
float resX = (ax * Cube) + (bx * Square) + (cx * t) + p0.X;
float resY = (ay * Cube) + (by * Square) + (cy * t) + p0.Y;
return new Vector2(resX, resY);
}
To use the function, simply pass in the 4 point and a time (between 0 and 1). To draw the curve, try calling this function from a for loop that looks something like this:
// preset your p0,p1,p2,p3
Vector2 PlotPoint;
for (float t = 0; t <= 1.0f; t += 0.01f)
{
PlotPoint = GetPoint(t, p0, p1, p2, p3);
// now call some function to plot the PlotPoint
YourDrawFunction(PlotPoint);
}
This basically starts you at p0 when time t is zero and at each increment of time +0.01 you will slowly move on the curve towards p3.
So to give you an idea of how this was used by my son in one of his games, watch the video below
- Note these are circular Paths each withe 3 segments. Each segment is a 4 point Bezier calculation.
- p3 of the each segment is common to p0 of next each segment.
- To make the circular, p3 of last segment must be same as p0 of 1st segment.
- p2 and p3 of each segment must be on a strait line with p0 and p1 or the next segment. (this give you the double handles for each point)
So this means:
- If a path point was moved by user, you must also move left and right control handler points by same deltas
- If left handler moved, must also move corresponding path point so it intersects line to other handler at mid point
- If right handler moved, must also move corresponding path point so it intersects line to other handler at mid point
Good luck