Solar Powered Arduino With 433MHz Radio

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 with 433MHz radio

The 433MHz radios get a lot of bad press, but they aren't all bad. This project uses a pretty standard transmitter with this superheterodyne receiver. The "superhet" receiver is better able to distinguish signal from noise, and it shows in the results. It gets closer to 100% than any other 433MHz receiver I've tried.

The transmitter is driven off of the higher (~8V) battery voltage, rather than the 3.3V Vcc, and that gives it a much higher output. A higher output makes it easier for the receiver to pull the signal from the background noise.

Finally, there is the firmware that drives the transmitter and receiver. I chose the RFTransmitter and RFReceiver by Andreas Rohner and it worked great. It works at a long distance, and with other 433MHz things going on around it.

Power Requirements

The measured current in the Solar Arduino Pro Mini, modified to use the 433MHz transmitter, was 0.26mA when not transmitting, and 20.1mA when transmitting. It takes less than 5 seconds to transmit a 38 character message.

0.26mA + ((5s รท 60s) x (20.1 - 0.26)) = 1.91mA

It takes 1.91 x 24h = 45.92mAH per day to run the circuit. To get the highest transmitter power it would be best to run the transmitter off of the highest voltage, which would be 8.2V from the series batteries. That is how the transmitter was connected when the current was measured.

Availability of Sunlight

To charge the batteries we need to know the sunlight on the shortest day of the year, which is around 9.5 hours from sunrise to sunset on December 21st in St Louis, MO (Daylight hours vs. latitude). Another is the NOAA Solar Calculator. You'll have to subtract sunrise from sunset to get the length of the day. The sunlight is not 100% useful, though, so look at the link below, too.

We also need the % of sunlight that we can use in our city. Only 57% of the sunlight in St. Louis is useful, due to clouds, angle of sun, etc., which cuts our sun time to 9.5 x 0.57 = 5.415 hours per day. European cities are in there, too, but provide sunny days per month. That divided by the number of days in the month gives you a number you can work with.

Solar Panel

 

Since the circuit needs 45.92mAH per day, and we have only 5.42 hours to produce that, we need to generate 8.47mA during those 5.42 hours. I don't have any panels that small, so this 12V @ 125mA panel was used. You can see that although it is rated at 1.5W, it falls a little short. The voltage is good, though, and the current is more than enough for the job.

Batteries

I have the choice of either 2 x AAA 600mAH batteries for 8.2V @ 600mAH, or 2 x AA 1200mAH batteries for 8.2V @ 1200mAH. I went with 1200mAH batteries, since I have more of them. That provides several days of operation from a single charge. You could easily choose AAA batteries for a smaller package and correspondingly fewer days. I ran the circuit for two days with no solar panel and the battery discharged to 8.11V. It would still have a reasonably full charge at 7.4V.

Battery Charger

This is a bit repetitive, but the charger is the same one used on the Solar Uno and the Solar WiFi projects.

 

The charger is an LM317T-ADJ regulator adjusted to 8.8V output, and run through a diode to the batteries. The diode prevents the batteries from draining through the voltage regulator when the sun goes down or the Arduino shuts off the charger. That's what the transister does. When the voltage on V_BATTERY reaches the max value we want, the Arduino turns on that transistor and the regulator output drops to around 1.25V, stopping any charge from getting to the batteries. When the V_BATTERY drops to below the minimum we want, the Arduino turns the transistor off and charging resumes.

The sunlight has not been kind to my breadboard. I've ordered a new one.

The Arduino and RTC are in the upper left of the picture, the charger in the upper right. In between are the three dividers. The transmitter is in the lower left, and the batteries in the lower right. All of this circuit, with the exception of the 433MHz transmitter, are common to all of the Solar Arduino Pro Mini projects.

Transmitter

The 433MHz transmitter is connected Vcc pin to the battery, DA pin to Arduino pin 11, G pin to ground, and a piece of solid copper wire 25cm (9.75") long is connected to the AN pin. The antenna wire is straight, not coiled.

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

#define NODE_ID          1
#define OUTPUT_PIN       11

#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.16

// 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

// Increase to reduce errors, and slow down communication.
// Make receiver match this value!
#define BIT_TIME 250

int ticker = 0;
char buf[40];
char tmpBuf[8];
double v_panel, v_charger, v_battery;
DateTime now;

// Send on digital pin 11 and identify as node 1
RFTransmitter transmitter(OUTPUT_PIN, NODE_ID, BIT_TIME);

/*****************************************************************************
 * 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() {

  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);
  
  transmitter.send((byte *)buf, strlen(buf) + 1);
}

/*****************************************************************************
 * 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);
     _delay_ms(100);
     
      // See if battery is charged.
      if (getBatteryVoltage() < MAX_BATTERY) {
        
        // Not charged - don't shut it down.
        digitalWrite(SHUTDOWN, SHUTDOWN_NO);
      }
    } else {
      // Don't keep the transistor on if 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(OUTPUT_PIN, OUTPUT);
  digitalWrite(OUTPUT_PIN, LOW);
  
  // 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 > 57) {
      digitalWrite(INDICATOR, INDICATOR_ON);
      now = rtc.now();
      if (now.second() == 0) {
        
        // Send the voltage readings to the radio.
        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() {;}
            

Receiver

For the receiver I used a Freaduino Uno board. I could have used any Arduino Uno clone. The receiver wiring really depends on the receiver you have. Mine looks like the picture at the top of this page, and is wired:

  • DER not connected
  • GND to ground
  • 5V to 5V on Arduino
  • Data to pin 2 on Arduino
  • Ant to a 25cm piece of solid copper wire

There are multiple ground and 5V pins. Make sure you get them all hooked up. Also, make sure you run it on 5V and not 3.3V. I kept missing every minute that ended in "2" and "7". It only happened when the transmitter was outside. A look at the image below shows the reason. In fact a look at the image below is how I found the reason. The power switch is in the 3.3V position.

433MHz receiver breadboard
#include <PinChangeInterruptHandler.h>
#include <RFReceiver.h>

// Increase to reduce errors, and slow down communication.
// Make transmitter match this value!
#define BIT_TIME 250

RFReceiver receiver(2, BIT_TIME);

void setup() {
  Serial.begin(115200);
  receiver.begin();

}

void loop() {
  
  char msg[MAX_PACKAGE_SIZE];
  byte senderId = 0;
  byte packageId = 0;
  byte len = receiver.recvPackage((byte *)msg, &senderId, &packageId);

  Serial.print(msg);
}            

Helper Code

This is the python 3 code I use to save the data to disk for making pretty graphs with later. You'll want to change the port to match yours. "/dev/ttyxx' on Linux. "COM1" or similar for windows.

import os.path
import serial

filename = "solar-433.csv"

if not os.path.isfile(filename):
    with open(filename, "w") as of:
        of.write("time,solar,charger,battery\n")
        of.close()

with serial.Serial('/dev/cu.usbserial-A9WFN915', 9600, timeout=125) as ser:
    while True:
        res = ser.readline().decode("utf-8")
        print(res.rstrip())
        of = open(filename, "a")
        of.write(res)
        of.close()
            
433MHz Transmitter Library
433MHz Receiver Library
PinChangeInterruptHandler Library
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.