Spring 2019

If you are a current student, please Log In for full access to the web site.

Note that this link will take you to an external site (

The microcontroller code for today, `lab07b.ino`

can be uploaded to your ESP32 as-is, and we'll spend lab adding on to it. **You can and should reset your LCD's backlight so that it is directly tied to 3.3V.**

When up and running you'll get some content on your LCD that shows some stuff, including two symbols in the upper left corner. These are basically just for show/reference since you'll need to make your own symbol (a battery one), later in this lab.

Below the symbols is a message indicating the state of your system with regards to its battery.

- Discharge Amt: Currently a dummy value which you'll implement in this lab. This should eventually report the fraction of battery discharged based on the battery voltage.
- Battery Voltage: The current battery voltage (in Volts). This is also currently a dummy value which you'll need to fill in.

Ok now let's move on.

As we saw in lecture earlier this semester, our lithium polymer battery does not provide a flat voltage which we can use directly. Instead it yields a highly variable voltage based off of the discharge amount and discharge rate and additional electronics such as regulators or power converters (boost or buck) will "clean up" this voltage up so that our sensitive electronics can run.

If starting from a fully-charged state, we go ahead and record our battery's voltage over time as we draw a constant amount of power, we can get very nice discharge curves. We ran our 1500 mAh battery and logged its voltages over several discharge cycles (with constant consumption) in order to get the data shown below. The shape and values of this curve are surprisingly consistent, so we'll use this as a starting dataset in this lab. You'll notice the x axis is time (in hours, and the battery goes through its complete cycle from charged to discharged in about 2.5 hours when we were drawing the amount of current we were. In this case all our electronics were drawing about 600 mA continuously, so we can see the 1500 mAhr rating is approximately correct. Also note that this is far higher of a current consumption than you'll ever realistically be using your battery for (the ESP32 with WiFi cranking pulls around 160 mA for example), so this data might be a little different from the discharge curve experience on your system, for example.)

Because this data was collected while under a constant load, meaning the system's components were drawing a constant amount of current and power, we can approximately convert time to the discharge amount of the battery so that we get a similarly shaped plot below except with a re-interpretted x-axis. We'll define discharge amount as a normalized value ranging from 0.0 to 1.0 where 0.0 is a fully charged battery (no amount discharged) and 1.0 is a fully discharged batter (all discharged).

While most of the time in use, we really don't need to worry about this variable battery voltage, the fact that the plot takes on the shape it does, provides a means to extract how much capacity we've got left in the battery during operation (without this we'd need some sort of timer and integration and that can be unreliable.) In fact, during normal runtime operation, we can readily take a measurement of the battery's voltage by making an analog measurement of the output of the voltage divider that is a component on our board.

The voltage we measure from the battery will be grabbed using Analog Input `A6`

which is the same as IO Pin 34 on the ESP32. When a reading is made using the call `analogRead(A6);`

, what scaling factor is needed in order to generate the value of the battery voltage in volts?

Remember the voltage divider is on the power board. That's what those two 10K resistors are that you soldered on!

Provide a scaling factor that will yield the battery's voltage (in Volts) when multiplied by a raw analog reading on pin 6

Add a line of code to `lab07b.ino`

so that the battery's current voltage shows up on the display with the proper units. Run it and make sure your voltage looks about right (what is the voltage range of the lipo battery again??)

The next goal of this lab will be for you to implement a few functions that will allow the reporting of the remaining charge left in our battery. We'll do this by:

- First coming up with a mathematical model to express
**battery voltage as a function of discharge amount**based off of the data shown above and implementing that function in code. - Generating a second function that uses the previous function to express
**discharge amount as a function of battery voltage.** - Use the discharge amount to render a human-understandable "battery-remaining" symbol

The curves shown above in Figures 2 and 3 are taken from data, but in order to use them in our code, we'd like to encapsulate the trend shown into a lower-order polynomial since that will take up significantly less space in memory. In the downloadable for this week, open up `fitter.py`

. Within this simple Python script, you'll see two large Python lists which correspond to the discharge amount and voltage values of the plot in Figure 3. We'd like to use this data to create a polynomial so we'll need to run a fitting routine on it. For this we're going to use `numpy`

and `matplotlib`

. Make sure you have `numpy`

installed in your Python distribution as well as `matplotlib`

. If you don't, you can use `pip`

or whatever to install them (or ask for help!). Once you're sure you have those installed look up how to use numpy's polynomial fitter given the data we've provided in order to calculate **a fourth-order polynomial** based off of the `levels`

vs. `voltages`

values. Store the result in the `fit_np`

variable, and run the code as well as uncomment a few lines that will allow you to compare the data to its fitted polynomial. You should get a plot that shows the raw data as well as the resulting fitted-polynomial. Is it close? On what parts is it not? Feel free to try higher and lower orders if you'd like!

The next part will be to create a C++ function that expresses that polynomial you just found! This function, `voltage_from_discharge`

should, as its name implies, take in a value of normalized discharge amount and return the voltage that corresponds to that point. Use the checker below to test your function. When it is working, replace the appropriate function in the ESP32 script for lab. In order to not lose precision, use `double`

data types as much as possible. `math.h`

is included for your benefit including its `pow`

function.

Go over your function from above and what we've covered so far with a staff member.

So we have a function which gives us voltage provided we know the discharge amount. In run-time we want the opposite though! Specifically the system can get voltage readings of the battery and we'd like it to figure out what amount of the battery has been discharged. Unfortunately our polynomial is fourth order so we can't just invert it by hand and find a non-disgusting looking closed-form solution (really anything above third-order polynomials are very hard to algebraically invert, and getting a closed-form solution to above ~fifth order is generally impossible).

What we can do, howevever, is computationally invert using our original function `voltage_from_discharge`

. The basic idea behind this is that since it is relatively easy to call `voltage_from_discharge`

we can search through its outputs (voltages), compare to the voltage we've measured, and then return the corresponding discharge amount used to generate that voltage. In essence, we're brute-forcing our way to an inverted function. Create a function `discharge_from_voltage`

that takes in two arguments:

`double voltage`

: The voltage (in Volts) we'd like to know the corresponding discharge amount for`double error`

: The error (in Volts), we'll tolerate in our answer. If an error of sufficient level cannot be found, the function should return the closest value possible.

There are many ways to do this. This is essentially a search problem. You can iterate through the entire range of possible values, you can perform a binary search through the range, or try other ways. Use the checker function below to develop your code as well as your ESP32. Note a function version of `voltage_from_discharge`

is provided for you in this checker. Again, try to use `double`

data types as much as possible.

`abs`

will work here for doubles and floats, but WILL NOT work on the ESP32 directly. To get this working on the ESP32, make sure to just use `fabs`

to ensure proper calculation of floating point and double precision floating point absolute values.

Once this second function is working, insert and integrate it (along with your earlier function) into the ESP32 code skeleton, and reupload. You should now see the Discharge amount be something reasonable based on your battery voltage, hopefully. Is your time-left approximately what it should be given your battery voltage? Charge your battery up some if not!

Now, open up the Serial Monitor and observe the number that is flying by. What is this number? Study your code. This is the time (in microseconds) that it takes your code to:

- Take an analog reading and convert it into voltage
- Determine the discharge amount from the voltage

**Without Destroying your working function,** create a new `voltage_from_discharge`

function and let's optimize it a bit. What did you have earlier? Perhaps something like this?

double voltage_from_discharge(double discharge) { return a * pow(discharge, 4) + b * pow(discharge, 3) + c * pow(discharge, 2) + d * discharge + e; }

Well this is pretty slow actually. Mainly because the `pow`

function is designed to be an efficient calculator of a wide variety of exponentials, but this generality can actually be expensive when our equation is as structured and constrained as it is. Even if you didn't use this in your solution, implement a `pow`

-based version in your code (do not delete your other version...just comment it out), and record how fast your timing is! You'll use this in your checkoff 2.

Now moving from that previous, easy-to-write solution, the next step is perhaps something like this:

double voltage_from_discharge(double dsch) { return a * dsch*dsch*dsch*dsch + b * dsch*dsch*dsch + c * dsch*dcsh + d * dsch + e; }

This will actually run faster than the `pow`

version! It isn't relying on the generic-but-good calculation for finding powers How fast does an implementation like this run? Implement it and write down the speed your system takes with this one!

Can we do better? Yes. We previously have an equation like the following where v is the battery voltage and l is discharge level of the battery:

v = al^4 + bl^3 + cl^2 + dl + e

If you were to factor it so that it instead said...

v = (((al + b)l+c)l+d)l+e

...would this be any faster than the previous equation? This is literally factoring, right? This is something ten-year-olds do to feel like big kids; obviously this can't matter? Implement a version of your `voltage_from_discharge`

function using the factored expression above, (saving your previous ones in case you need them to compare), upload it, and run it. Note down the speed of your operations now.

...

...

You should see it is faster. This is cray^{1}! How is factoring making things go faster? Factoring is something you do to make you test answers look nicer.

To gain insight into this, count the number of additions and multiplications that need to go into our first equation above (the v = al^4 + bl^3 + cl^2 + dl + e version) What do you get?

Number of Adds in first equation (unfactored)

Number of Multiplies in first equation (unfactored)

Now in our factored out expression (the v = (((al + b)l+c)l+d)l+e one), how many additions and how many multiplies do you count?

Number of Adds in first equation (factored)

Number of Multiplies in first equation (factored)

Adds are super cheap and quick in most computational devices, but multiplies can cost some clock cycles, so by cutting by literally cutting our multiplies down by more than half, we're going to run quite a bit faster. While subtle, what we're doing with the factoring here is recognizing that some operations are going to be done more than once on certain terms, and so we're in effect lumping them together. This is a very satisfying part of code optimization.

Demonstrate your working discharge estimator and nerd out with a staff member about how cool it is that you can make your code run faster by factoring/optimizing the number of operations.

We've got a battery voltage and the discharge amount plotted. But humans aren't good with numbers. If you think otherwise, you should see the answers some of you submit for the homeworks (jk. not jk). We need a symbol to show us how much life we have left in our battery. On to the next section.

A classic and important symbol of our modern lives is the battery symbol with a varying fill-amount indicative of charge. Along with hunger, this symbol guides a lot of decisions in our lives. We'd like to have that for our labkit! Complete the function `void drawBattery(TFT_eSPI* screen, uint8_t x, uint8_t y, float level, uint16_t fc, uint16_t bc)`

in the code skeleton which takes in three values:

`TFT_eSPI* screen`

: A pointer to a TFT object`uint8_t x`

: The upper x coordinate for drawing`uint8_t y`

: The upper y coordinate for drawing`float level`

: The fill amount of the battery`uint16_t fc`

: Foreground Color for drawing`uint16_t bc`

: Background Color for drawing

This function should render a battery of the dimensions shown below (but which can be translatable in x and y based on the coordinate input arguments) and fill a proportional amount of the battery based on the level provided (so if level is 0, the battery symbol will be empty/hollow and if level is 1.0, the battery symbol will be full.) Use the shape below for size reference. We'll ultimately want to draw it at coordinate (104,3), but we'd like you to keep the function as general as possible in terms of positioning (so you could move it around in x and y as needed). Remember the GFX TFT library references are your friend.

Note that the value taken in by the

`level`

variable is equivalent to `1-discharge_amt`

! This is implemented in how we call the function in the code!

Show your whole working system to a staff member. Enjoy Spring Break!

^{1}Seymour Cray, that is.

This page was last updated on Sunday March 31, 2019 at 08:27:33 AM (revision

`7103189`

).\ / /\__/\ \__=( o_O )= (__________) |_ |_ |_ |_Course Site powered by CAT-SOOP 14.0.4.dev5.

CAT-SOOP is free/libre software, available under the terms

of the GNU Affero General Public License, version 3.

(Download Source Code)

CSS/stryling from the Outboxcraft library Beauter, licensed under MIT

Copyright 2017