I want to build a solar powered sensor that uses supercapacitors instead of batteries and can run for years without any intervention.
The full story is a little more complex:
Some years ago I stumbled accross the ESP8266 and ordered a couple of them and had quite fun playing around. I mainly explored the software side and the WLAN capabilities. At some point I ordered ESP32 microcontrollers and a couple of sensors and created a indoor temperature and humidity sensor with a small webserver that could display a reasonable amount of historic data.
Writing the software was “easy”. Things became hard when I wanted to put the sensor into a room with too few power socket. My development board has an USB connector and so I just connected it to a power bank, which quickly ran out of juice. I started looking into the power saving capabilities of the ESP32 and quickly hit a dead end: All power banks I own shut down shortly after the microcontroller goes into deep sleep mode. They all require a minimum load to stay on. I needed another way to power the sensor.
I started looking into various options:
My ESP32 boards have a battery connector. I could have just bought a battery, use the deep sleep mode for power saving and a simple circuit to measure the battery voltage and report it together with the other sensor reading. It would have lasted a couple of months and I would than have to replace the batteries. I did not like the idea of recharging batteries.
Although small solar cells would be able to charge the battery, I would have to replace the battery every few years. I did not try it but I did not like the idea either.
I really liked the idea to power everything with a combination of small solar panels and supercapacitors. Supercapacitors can store way less energy than a battery of the same size, but that’s ok because I only plan to send a couple of updates per day. I have some ideas to save energy that I haven’t seen implemented yet:
If not properly powered a microcontroller like the ESP32 can be unstable. Sending data using WLAN requires a lot of power and can result in power fluctuations which cause the ESP32 to crash. For me this problem even occured when using a USB power supply.
The solution to the problem is to add one or more reasonable sized capacitors close to the pins supplying power to the ESP32. I ended up soldering two 470 µF capacitors directly accross 3.3V and GND.
When researching this problem I found the excellent YouTube Channel of Andreas Spiess. In Video #0911 he investigates the problem and also presents the solution.
When you charge a capacitor using a solar panel there are a number of problems that need to be addressed:
If you connect a capacitor directly to a solar panel the capacitor will be charged when there is light, but when it becomes dark the opposite will happen and the capacitor will be discharged into the solarpanel. To prevent this you can add a diode. The diode will allow the capacitor to charge at the cost of a voltage drop but prevent it to discharge into the solar panel.
Capacitors are rated for a specific voltage. Most supercapacitors are rated for 2.7 V. Charging them higher will damage them. With a TL431, which is a programmeble reference, there is a cheap way to protect the capacitor from overcharging.
In Video #1392 of his YouTube channel, Andreas Spiess points out this cheap solution.
If you buy cheap supercapacitors you might want to test them to assure that they are of good quality. For me this means:
Normal capacitors could easily be tested with a multimeter or a cheap parts tester. This however does not work for supercapacitors with an capacity of 5, 10 or 20 Fahrad. I wanted an cheap solution to test my capacitors and found one:
Wikipedia states:
The RC time constant, also called tau (𝜏), the time constant (in seconds) of an RC circuit, is equal to the product of the circuit resistance (in ohms) and the circuit capacitance (in farads), i.e.
𝜏 = RC
It is the time required to charge the capacitor, through the resistor, from an initial charge voltage of zero to approximately 63.2% of the value of an applied DC voltage
If we know the resistor and measure the time to charge the capacitor to 63.2 % of the applied voltage we can calculate the capacitance using: C = 𝜏 / R
// Pins
#define analogPin 36 // analog pin for measuring capacitor voltage - connected to the anode (+) of the capacitor
#define chargePin 32 // pin to charge the capacitor - connected to the charging resistor
// Resistor
#define resistorValue 199.9F // resistor value in Ohm; F formatter tells the compliler it's a floating point value
// Voltage
float analogReading = 0;
float chargedVoltage = 0;
// Times
unsigned long progressUptdateInterval = 10; // Output progress every x seconds
unsigned long lossTestDuration = 60 * 60; // Test time for loss is seconds
unsigned long startTime;
unsigned long capacityStartTime;
unsigned long lossStartTime;
unsigned long lastProgressUptdateTime;
unsigned long elapsedTime;
// Capacitor
float capacity = 0; // floating point variable to preserve precision, make calculations
float output; // floating point variable for output after calculations
float loss = 0;
void setup()
{
// Initialize serial transmission for debugging
Serial.begin(115200);
Serial.println("");
// Start Timer
startTime = millis();
// Set ADC settings
// https://www.youtube.com/watch?v=RlKMJknsNpo&t=145s
analogReadResolution(12); // 12 bits
analogSetAttenuation(ADC_11db); // for all pins (11db = 0-3.3v)
analogSetCycles(255); // default is 8, range 1-255
analogSetSamples(1); // default is 1
analogSetClockDiv(1); // default is 1, range 1-255
// Discharge the capacitor
discharge();
// Test capacitor
Serial.println("=== CAPACITY TEST==========");
// Prepare Pins
pinMode(chargePin, OUTPUT); // set chargePin to output
digitalWrite(chargePin, HIGH); // set chargePin HIGH and capacitor charging
// Start Timer
capacityStartTime = millis();
// Measure charging
analogReading = measure();
while (analogReading < 2588)
{ // 2588 is 63.2% of 4095, which corresponds to full-scale voltage
analogReading = measure();
if (lastProgressUptdateTime + progressUptdateInterval * 1000 < millis())
{
Serial.print("Capacity test\t");
Serial.print(analogReading * (100.0 / 2588.0), 1);
Serial.println(" %");
lastProgressUptdateTime = millis();
}
}
// stop charging
pinMode(chargePin, INPUT);
// Calculate elapsed time
elapsedTime = millis() - capacityStartTime; // milliseconds
// Calculate Capacity
capacity = ((double)elapsedTime / resistorValue) * (1000000); // in nF
delay(2000);
// Test loss
Serial.println("=== LOSS TEST =============");
lossStartTime = millis();
chargedVoltage = measure();
while (millis() < lossStartTime + 1000 * lossTestDuration)
{
if (lastProgressUptdateTime + progressUptdateInterval * 1000 < millis())
{
analogReading = measure();
loss = (1 - (analogReading / chargedVoltage)) * 100;
Serial.print("Loss test\t");
Serial.print(((millis() - lossStartTime) / (float)(1000 * lossTestDuration)) * 100, 1);
//Serial.print(analogReading * (100.0 / 4095.0), 2);
Serial.println(" %");
lastProgressUptdateTime = millis();
}
}
discharge();
// Calculate elapsed time
elapsedTime = millis() - startTime; // milliseconds
// Print results
showResults(capacity, loss, lossTestDuration, elapsedTime);
}
// Do nothing - we only want to measure once
void loop()
{
}
// Function to discharge capacitor
void discharge()
{
Serial.println("=== DISCHARGING ===========");
pinMode(chargePin, OUTPUT); // set discharge pin to OUTPUT
digitalWrite(chargePin, LOW); // set discharge pin LOW
analogReading = measure();
while (analogReading > 0)
{ // wait until capacitor is completely discharged
delay(progressUptdateInterval * 1000);
analogReading = measure();
Serial.print("Discharging\t");
Serial.print(100 - (analogReading * (100.0 / 4095.0)), 1);
Serial.println("%");
}
}
void showResults(float capacity, float loss, float lossTestDuration, float elapsedTime)
{
Serial.println("=== RESULTS ===============");
// Output Capacity
Serial.print("Capacity:\t");
if (capacity / (double)(1000000000) >= 1)
{
Serial.print(capacity / (double)(1000000000), 1);
Serial.println(" F");
}
else if (capacity / (double)(1000000) >= 1)
{
Serial.print(capacity / (double)(1000000), 1);
Serial.println(" mF");
}
else if (capacity / (double)(1000) >= 1)
{
Serial.print(capacity / (double)(1000), 1);
Serial.println(" μF");
}
else
{
Serial.print((double)capacity, 1);
Serial.println(" nF");
}
// Output Loss
Serial.print("Loss:\t");
Serial.print(loss, 1);
Serial.println(" %");
// Loss Test duration
Serial.print("Loss test duration:\t");
Serial.print((float)(lossTestDuration), 0);
Serial.println(" s");
// Output elapsed time
Serial.print("Total time:\t");
Serial.print((float)(elapsedTime / 1000), 0);
Serial.println(" s");
Serial.println("===========================");
}
double measure(void)
{
// ADC readings are non linear and need correction
// https://youtu.be/RlKMJknsNpo?t=337
double reading = analogRead(analogPin);
if (reading < 1 || reading > 4095)
{
return 0;
}
else
{
return (-0.000000000000016 * pow(reading, 4) + 0.000000000118171 * pow(reading, 3) - 0.000000301211691 * pow(reading, 2) + 0.001109019271794 * reading + 0.034143524634089) / 3.3 * 4095;
}
}
Note: Normally super capacitors are measured the other way around. They are usually first charged to a voltage and then discharged with a constant current. It found out about this after I implemented the solution above. I found detailed documentation about measuring procedures on various manufacturer websites e.g. Maxwell[^maxwell-test-procedures].
Let me know if you adapted my code for this.
[^maxwell-test-procedures].: https://maxwell.com/wp-content/uploads/2021/08/1007239_EN_test_procedures_technote_2.pdf
If you have charged supercapacitors you can just connect the microcontroller and everything works fine. If you start with empty capacitors you will run into the following problem:
This will continue forever.
To allow the chip to be powered from empty supercapacitors we need a voltage supervisor circuit that cuts the power at about 3 V so there is enough voltage to boot up and go to deep sleep. That way the supercapacitors could continue charging to 3.3 V.
I tried building the following voltage supervisor circuit to solve this problem but it did not work:
The Mosfet is required because the TL431 is only rated for up to 100 mA and the ESP32 needs higher currents when using WLAN. My circuit works, but draws 0.67 mA which is way higher than the deep sleep current. I got the idea for this circuit from a Video3 of the Tech Ideas YouTube Channel.
My next try will be to use a dedicated voltage supervisor. A voltage supervisor that pulls the enable pin of the ESP32 low should disable the ESP32 until the reference voltage of the supervisor circuit is reached.
Once again in a video4 of his YouTube channel, Andreas Spiess explains how they work.
I ordered the one from Texas Instruments and will test it once it arrives.
The voltage output of solar panels varies a lot depending on the lighting conditions. With a single 5 V solar panel the voltage output is often lower than the 3.3 V required to power the ESP32. I looked at various power harvesting boards to boost small voltages and store the energy in capacitors but I found none that fullfilled all requirements:
Drop me an E-Mail if you have some suggestions.
I have have a circuit that uses an energy harvester to charge two supercapacitors. Four more supercapacitors store more energy at a higher voltage level before the buck/boost converter of the energy harvester.
This is a really bad concept design on breadboard because the capacitors will never be fully charged up to 2.7 V and it also has the problems with undervoltage.
Currently my best idea to solve my problems is to adapt a design5 I found on OpenHardware.io. The design uses a MIC5365-2.7YC5-TR6 to charge capacitors to 2.7 V so I would not have any half charged capacitors. In addition it uses an TPS610986DSET7 (Power Management IC, Current Synchronous Boost, Integrated LDO/Load Switch, 0.7V - 4.5V), which sadly has no undervoltage cutoff for the microcontroller. Therefore my idea is to replace it with a TPS630318 that has the ability to disconnect the load.
So overall: Work in progress. I will update this post when there is some process. If you are willing to help me, please let me know.
Andreas Spiess - Youtube Video - #091 How to properly power the ESP8266 modules ↩︎
Andreas Spiess - Youtube Video - #139 Is there a simple and cheap way to protect your super caps? How? ↩︎
Tech Ideas - Youtube Video - Battery low voltage cutoff circuit | Battery protection circuit ↩︎
Andreas Spiess - Youtube Video - How to use Voltage Supervisors to protect ESP32, Raspberry Pi, and Batteries ↩︎
OpenHardware.io - Super capacitor power supply for wireless sensors with charger ↩︎