Solar Powered WiFi and Pro Mini

This site has been great fun, but it has run its course, and it is time to move on. Please get what you want from the site before January 1, 2020. Some pages will move to another site but most will go away.

Solar Powered Arduino Pro Mini with WiFi

As power hungry as the ESP8266 is it can still be run from solar power. It draws less than 35mA when idle, going up to as high as 350mA when transmitting. This is total current for the ESP-12, Arduino Pro Mini, RTC, and µSD card. I started out with the Solar Powered Arduino Pro Mini, then added some power supply enhancements to handle the additional load.

I've always heard the ESP8266 uses too much power to work well with solar and batteries. I'm not sure that I have proven this wrong, but it works, and it isn't ridiculously difficult or expensive. It is the most power hungry thing I've used on solar power - it uses more power than the Solar Uno project.

The problem is that it is so cheap and useful that it is difficult to pass up. No additional equipment is needed inside to receive the data from the WiFi outside. I just run a simple python app on my PC and let it run in the background.

Power Requirements

The transmit power on the ESP8266, at least the ESP-12 I used, apparently doesn't change with RSSI, so it is always on full-tilt-boogey, using the battery as it transmits. It only transmits when it has something to transmit, though. With this project, we only send one 38 character line every minute. The transmitter, according to the datasheet you find, is using anywhere from 170mA to 350mA, but only for a few hundred µS at a time. It isn't as bad as it seemed at first. So average 20mA to 35mA idle, 50mA to 75mA when listening, and 75mA with a few 500µS spikes to 350mA when it transmits. Again, this is for the entire circuit. In the Solar Powered Arduino Pro Mini article the current was found to be 450µA for all of the circuitry, so the rest is the WiFi module.

To figure out the current requirements, start with the background current - 35mA. Add to that the periodic listen current, which is inclusive of the background current.

(75mA - 35mA) x (8mS ÷ 400mS) = 40mA x 0.02 = 0.8mA

Add to that the "getting ready to transmit" listen current, which is very unpredictable at around 100mS every three seconds.

40mA x (0.1 ÷ 3) = 1.33mA

Add to that the transmit current, which is very, very unpredictable at around 500µS 7 times every 20 seconds.

(350mA - 75mA) x ((0.0005 x 7) ÷ 20) = 48µA

Now these can be added together to get a ballpark number of mA.

35mA + 0.8mA + 1.33mA + 0.048mA = 37.178mA

So that's it. At the end of any given minute, the average usage will be 37.178mA. That is about 32% higher than the Solar Arduino Uno average. During that same period the peak current may be 350mA.

Power Availability

The solar panels I'm using are from the Solar Arduino Uno project. They are 12V 3W each, and I'm using two of them. I didn't show the calculations here, but you can see how it is done over at the Solar Uno page. That solar circuit was built to be a little more powerful than needed because panel availability dictates what panels need to be used. It happens that they are powerful enough to drive this circuit as well.

The batteries are six AA-size Lithium-ion 3.7V to 4.2V batteries 2 in series and 3 of those in parallel. Unlike the Solar Uno project, this one runs a 3.3V Arduino Pro Mini, so the batteries don't need to maintain such a high charge voltage. I let them back down a bit from 8.35V max to 8.25V max, but they still need a good charge.

Charger

The charger circuit is the same as the Solar Uno project. It is an LM317T-ADJ regulator tuned to 8.25V and having a transistor that can shut it down on command from the Arduino. Adjustment involves hooking up the panels or a 12V power supply in their place, and adjusting the output at the V_BATTERY point to 8.25V with no batteries or Arduino circuit present. Then hook up charged batteries and adjust it again. Finally, hook up the rest of the circuit and measure again. The voltage should stay at or near 8.2V, although some droop under load is acceptable. If the batteries are not fully charged, the voltage will drop to match them. Make sure all batteries are the same type, brand, age, etc.

The battery output from the charger hooks to the battery and to the "RAW" input on the Arduino. The rest of the circuit is driven from the 3.3V output, "VCC" on the Arduino, which is good for 150mA. I used a 1µF ceramic capacitor at the Vcc pin of the ESP8266 module to help store a little power.

ESP8266

The ESP8266 needs to have the esp-link firmware flashed on it. There is an excellent article on the JEELABS github readme. What you do is set it up as an access point, then get into the web manager at the address given and set it to access your router by giving it the SSID and the password. From then on, when it powers up, it logs in to the router and listens on port 23. You need to watch your router then in order to see which IP address it is using. You'll need it for the software below. You should also assign that address in your router to that MAC address so it never changes.

Software

Having a WiFi interface one must use some kind of socket client or telnet to read the data. Telnet is fine if you just want to watch it scroll away into oblivion, but I wanted to save the data in a file so I could make the graphs. To that end I put together a tiny Python 3 script that does just that. If the ESP8266 goes down, this script will not reconnect, so watch for that. Of course there is the µSD backup on the Arduino side.

Keep in mind that there is an inactivity timeout. If you are idle for 5 minutes, the connection is closed by the ESP8266, but that is taken care of in this application by the transmit once each minute.


import socket


sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('192.168.1.12', 23)
print('connecting to %s port %s' % server_address)
sock.connect(server_address)

try:
    while(True):
        amount_received = 0
        amount_expected = 38
        while amount_received < amount_expected:
            data = sock.recv(amount_expected - amount_received)
            amount_received += len(data)
            strdata = data.decode("utf-8")
            print(strdata.rstrip())
            file = open("solardata.csv", "a")
            if file:
                file.write(strdata)
                file.close()
finally:
    print('closing socket')
    sock.close()
            

Firmware

The firmware is from the Solar Arduino Pro Mini project. The voltages at the top of the file are changed due to the battery arrangement. The maximum voltage has changed from 4.1V to 8.25V and the minimum from 3.85V to 8.16V. The reason for the tighter range is so the charger will kick in more often during the day and have a better chance of topping off the battery near the end of the daylight hours.


// The timer0 interrupt is disabled, so the delay()
// functions will not work. Use _delay_ms() instead.
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <LowPower.h>
#include "RTClib.h"

#define DS_3231
//#define DS_1307

#ifdef DS_3231
RTC_DS3231 rtc;
#else
RTC_DS1307 rtc;
#endif

#define V_PANEL A0
#define V_CHARGER A1
#define V_BATTERY A2
#define MIN_SOLAR (double)8.9
#define MAX_BATTERY (double)8.25
#define MIN_BATTERY (double)8.15

// Blips for one second every minute.
#define INDICATOR 3
#define INDICATOR_ON 1
#define INDICATOR_OFF 0

// Shuts down the charging circuit.
#define SHUTDOWN  4
#define SHUTDOWN_YES 1
#define SHUTDOWN_NO 0

#define SQUAREWAVE 2

// This is the mV at the battery/panel for one ADU:
// (10v / 1.1v) * (1.1v / 1024)
// 13.636363 * 0.00107421875
#define ADC_FACTOR (double)0.0146484375

// Change the chipSelect to match the CS you choose on
// your SD card module.
#define chipSelect 10

int ticker = 0;
char buf[40];
char tmpBuf[8];
double v_panel, v_charger, v_battery;
DateTime now;
char filename[] = {"solar.csv"};

/*****************************************************************************
 * isShutdown
 *
 * Reports on the state of the SHUTDOWN bit.
 *****************************************************************************/

bool isShutdown() {
  return digitalRead(SHUTDOWN)?true:false;
}

/*****************************************************************************
 * getSolarVoltage
 *
 * Returns the voltage on the solar panel.
 *****************************************************************************/

double getSolarVoltage() {
  return analogRead(V_PANEL) * ADC_FACTOR;
}

/*****************************************************************************
 * getChargerVoltage
 *
 * Returns the voltage on the charger output.
 *****************************************************************************/

double getChargerVoltage() {
  return analogRead(V_CHARGER) * ADC_FACTOR;
}

/*****************************************************************************
 * getBatteryVoltage
 *
 * Returns the voltage on the battery bus.
 *****************************************************************************/

double getBatteryVoltage() {
  return analogRead(V_BATTERY) * ADC_FACTOR;
}

/*****************************************************************************
 * saveReadings
 *
 * Reads the voltages off the analog pins and writes
 * them as a line in a CSV file on the SD card.
 *****************************************************************************/

void saveReadings() {

//  if (!SD.begin(chipSelect)) {
//    Serial.println("SD Card failure");
//  }
  memset(buf, 0, sizeof(buf));
  sprintf(buf, "%4d-%02d-%02d %02d:%02d,",
    now.year(),
    now.month(),
    now.day(),
    now.hour(),
    now.minute()
  );
  // Read the panel voltage.
  v_panel = getSolarVoltage();

  // Read the battery voltage.
  v_battery = getBatteryVoltage();

  // Read the charger voltage.
  v_charger = getChargerVoltage();

  // Some icky string formatting.
  memset(tmpBuf, 0, sizeof(tmpBuf));
  dtostrf(v_panel, 6, 3, tmpBuf);
  strcat(buf, tmpBuf);
  strcat(buf, ",");

  memset(tmpBuf, 0, sizeof(tmpBuf));
  dtostrf(v_charger, 6, 3, tmpBuf);
  strcat(buf, tmpBuf);
  strcat(buf, ",");

  memset(tmpBuf, 0, sizeof(tmpBuf));
  dtostrf(v_battery, 6, 3, tmpBuf);
  strcat(buf, tmpBuf);
  strcat(buf, "\n");
  Serial.print(buf);
  _delay_ms(7);

  File dataFile = SD.open(filename, FILE_WRITE);

  if (dataFile) {
    dataFile.print(buf);
    dataFile.close();
  } else {
    Serial.println("error opening " + (const)filename);
  }
}

/*****************************************************************************
 * setCharger
 *
 * Determines the correct state of the charging switch
 * based on the solar, charger, and battery voltages.
 *****************************************************************************/

void setCharger() {

  // Power supply maintenance.
  if (isShutdown()) {
    // The charger is shutdown. Just check the voltage and see
    // if it is lower than we want it to be.

    // Only charge if the sun is up.
    if (getSolarVoltage() > MIN_SOLAR) {
      if (v_battery < MIN_BATTERY) {
        // Turn the charger back on.
        digitalWrite(SHUTDOWN, SHUTDOWN_NO);
      }
    } else {
      // Save a milliamp and turn off the shutdown transistor.
      digitalWrite(SHUTDOWN, SHUTDOWN_NO);
    }
  } else {

    // If the sun is up...
    if (getSolarVoltage() > MIN_SOLAR) {

      // Shutdown the charger.
      digitalWrite(SHUTDOWN, SHUTDOWN_YES);

      // See if the battery is charged.
      if (getBatteryVoltage() < MAX_BATTERY) {

        // Not charged - don't shut it down.
        digitalWrite(SHUTDOWN, SHUTDOWN_NO);
      }
    } else {
      // Don't drive the transistor if there is no sun.
      digitalWrite(SHUTDOWN, SHUTDOWN_NO);
    }
  }
}

/*****************************************************************************
 * setup
 *
 * Initializes everything.
 *****************************************************************************/

void setup() {
  // Open serial port
  Serial.begin(115200);

  pinMode(SQUAREWAVE, INPUT);
  digitalWrite(SQUAREWAVE, 1);

  pinMode(INDICATOR, OUTPUT);
  digitalWrite(INDICATOR, INDICATOR_OFF);

  pinMode(SHUTDOWN, OUTPUT);
  digitalWrite(SHUTDOWN, SHUTDOWN_NO);

  pinMode(chipSelect, OUTPUT);

  // See if the SD card is present
  Serial.println("Check SD card");
  if (!SD.begin(chipSelect)) {
    Serial.println("SD Card failed, or not present");
    while (1);
  }
  Serial.println("SD Ok");
  if (!SD.exists(filename)) {

    Serial.println("Creating file " + (const)filename);
    File myFile = SD.open(filename, FILE_WRITE);
    myFile.println("time,solar,charger,battery");
    myFile.close();
  }

  // See if the RTC is present
  Serial.println("Check RTC");
  if (! rtc.begin()) {
    Serial.println("No response from RTC");
    while (1);
  }
  Serial.println("RTC Ok");

#ifdef DS_3231
  if (rtc.lostPower()) {
#else
  if (!rtc.isrunning()) {
#endif
    Serial.println("Set RTC time");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
#ifdef DS_3231
  rtc.writeSqwPinMode(DS3231_SquareWave1Hz);
#else
  rtc.writeSqwPinMode(SquareWave1HZ);
#endif

  now = rtc.now();
  ticker = now.second();
  Serial.print("Second: ");
  Serial.println(ticker);

  // Disable timer0
  TIMSK0 &= ~_BV(TOIE0);

  // Enable the interrupt as a falling edge.
  attachInterrupt(digitalPinToInterrupt(2), Int0, FALLING);

  // Change the reference to the 1.1V internal.
    analogReference(INTERNAL);

  // Clear out one read for the ADC reference change.
  analogRead(V_PANEL);

  // _delay_ms() to allow chrs to xmit before
  // the CPU goes to sleep.
  _delay_ms(10);
}

/*****************************************************************************
 * loop
 *
 * Main program loop
 *****************************************************************************/

void loop() {

  while(1) {
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);

    // Just skip 58 seconds, but after that, read
    // the RTC to get the real second.
    if (++ticker > 58) {
      digitalWrite(INDICATOR, INDICATOR_ON);
      now = rtc.now();
      if (now.second() == 0) {
        ticker = 0;
        // Save the voltage readings to SD card.
        saveReadings();

        // Set the charging state.
        setCharger();
        digitalWrite(INDICATOR, INDICATOR_OFF);

        // Reset the ticker in case we ran over one second.
        now = rtc.now();
        ticker = now.second();
      }
    }
  }
}

// The Int0() just needs to be here - it doesn't have to do anything.
void Int0() {;}
            

Performance

The performance is great for both WiFi range and for power supply stamina. The breadboard is sitting on the ground, about 50 feet from my 2nd floor window, tirelessly pushing out one message every minute. The data, as in most of these solar experiments, is the solar panel voltage, charger output voltage, and battery voltage. The WiFi hasn't missed a single message in days of operation. I have interfered with it by changing firmware, etc., during that time, but the WiFi has sent and received every message it was asked to send.

I thought there would be a problem with the Arduino Pro Mini 3.3V output driving the ESP-12, but since the average current is so low there doesn't seem to be an issue. The capacitor on the ESP Vcc pin helps with that as well.

As is usual, the charging picks up as soon as the sun is up, but really gets going after the sun hits the panel. It looks like it took 2.5 hours to get the batteries charged (30 minutes longer than the Arduino Uno). After that, the battery bounced three times between minimum and maximum voltage before the sun went down.

Finally

It is quite practical to make a WiFi enabled Arduino Pro Mini powered by the sun. It takes panels that are big enough to charge the batteries in a reasonable amount of time, and batteries big enough to power it overnight.

See Also: Solar Arduino Projects

As an Amazon Associate I earn from qualifying purchases.
Arduino Board Logo

 

Arduino-Board is the go-to source for information on many available Arduino and Arduino-like boards, tutorials and projects.

Help and Support

Arduino-Board

Stay updated

Sign up if you would like to receive our once monthly newsletter.