Full Arduino Code for Poultry Egg Incubator with Humidity Sensor

We are frequently asked for the source code used on the Arduino-based products which we design and build. Unfortunately we cannot do that for commercial reasons. However, more than 80 percent of the requests we receive are for the code used in our poultry egg incubation controllers, with almost 100 percent of those requests coming from the developing world.

Therefore, as a one-off, we are releasing the full Arduino source code for our most popular (and easiest to build with commonly available components) device of this type, our Poultry Egg Incubator with Humidity Sensor.

egg incubator with humidity sensor, fan, heater, and humidifier

egg incubator controller status summary display

This device makes use of an always on motor which turns the eggs six full turns every 24 hours. Many of our other incubators have motors which have to be set up to run for a certain length of time a certain number of times per day, but the code for these is either specific to a particular motor or far more complicated (but of course more flexible) if the end-user of the incubator is to set these timings.

This device makes use of a DS18b20 digital temperature sensor, a 1602 LCD display module, a DHT11 humidity sensor, and is based around an Arduino Pro Mini, together with some relays, resistors, buttons, terminals, and other components available everywhere – easily salvaged from old and broken electronics even.

In time we will add a circuit diagram together with a suggested parts list, but the design is very flexible in terms of the components which can be used to put it together (with exception of the sensors and display for which the code provided is specific…and which together with an Arduino Pro Mini clone can be purchased for well under US$10 total including delivery.

Click here for details on how to connect and use a DHT11 Humidity Sensor with Arduino, and here for information on using DS18B20 sensors and 1602 LCD modules with Arduino.

Here is the full Arduino sketch (source code) for the egg incubator.

// (C) 2017-2020 REUK.CO.UK - Free for non-commercial use.
// Egg incubation temperature and humidity controller with display.
// Connection to an always on motor - e.g. 6 rotations per day.
// Fan for cooling, heater for heating, humidifier to humidify.

// For the temperature sensor.
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 3

// For the non-volatile storage.
#include <EEPROM.h> //EEPROM.write(addr, val); val = EEPROM.read(address);

// For the DHT11 humidity sensor.
#include <dht.h>
dht DHT;
#define DHT11_PIN 10

//for the LCD
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#define I2C_ADDR 0x27 // This i2c address depends on the display.
#define BACKLIGHT_PIN 3
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

// Setup a oneWire instance to communicate with the OneWire 
// device (Dallas temperature IC)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensor1(&oneWire);

// Set up the input and output pins used on the Arduino.
const int button1 = 2;
const int button2 = 8;
const int heater = 9;
const int fan = 4;
const int humidifier = 5;

// Define variables to store the humidity and 
// temperature readings froms sensors.
float sensorOneTemperature;
float humidity;

// Define variables for the upper and lower 
// thresholds (read in from EEPROM).
float heaterOnThreshold;
float heaterOffThreshold;
float fanOnThreshold;
float fanOffThreshold;
float humidifierOnThreshold;
float humidifierOffThreshold;

// Define the memory locations of the thresholds in EEPROM.
int heaterOnEEPROM = 100;
int heaterOffEEPROM = 120;
int fanOnEEPROM = 140;
int fanOffEEPROM = 160;
int humidifierOnEEPROM = 180;
int humidifierOffEEPROM = 200;

// Set some default values for the thresholds.
const float heaterOnDefault = 37.0;
const float heaterOffDefault = 39.0;
const float fanOnDefault = 40.0;
const float fanOffDefault = 37.0;
const float humidifierOnDefault = 45.0;
const float humidifierOffDefault = 70.0;

// Define variables to keep track of the status of 
// the system (0=off,1=on)
int heaterStatus;
int fanStatus;
int humidifierStatus;

void setup(void)
{
 // Start up the temperature sensor library.
 sensor1.begin();
 // Set the temperature measurement resolution to 
 // 12 bit ADC (0.0625°C resolution)
 sensor1.setResolution(12);

 // Read in the threshold values from EEPROM.
 heaterOnThreshold = EEPROM_readFloat(heaterOnEEPROM);
 heaterOffThreshold = EEPROM_readFloat(heaterOffEEPROM);
 fanOnThreshold = EEPROM_readFloat(fanOnEEPROM);
 fanOffThreshold = EEPROM_readFloat(fanOffEEPROM);
 humidifierOnThreshold = EEPROM_readFloat(humidifierOnEEPROM);
 humidifierOffThreshold = EEPROM_readFloat(humidifierOffEEPROM);

 // Make sure that we at least have read in values, or read in defaults.
 if(isnan(heaterOnThreshold)) heaterOnThreshold = heaterOnDefault;
 if(isnan(heaterOffThreshold)) heaterOffThreshold = heaterOffDefault;
 if(isnan(fanOnThreshold)) fanOnThreshold = fanOnDefault;
 if(isnan(fanOffThreshold)) fanOffThreshold = fanOffDefault;
 if(isnan(humidifierOnThreshold)) humidifierOnThreshold = humidifierOnDefault;
 if(isnan(humidifierOffThreshold)) humidifierOffThreshold = humidifierOffDefault;
 
 // Set up the inputs and outputs on the Arduino IO
 pinMode(button1, INPUT);
 digitalWrite(button1, HIGH); //turn on the pull up resistor
 pinMode(button2, INPUT);
 digitalWrite(button2, HIGH); //turn on the pull up resistor
 pinMode(heater, OUTPUT);
 digitalWrite(heater, LOW);
 pinMode(fan, OUTPUT);
 digitalWrite(fan, LOW);
 pinMode(humidifier, OUTPUT);
 digitalWrite(humidifier, LOW);
 
 // Need to keep track of the status of the heater, fan, and humidifier
 // so we know whether they are on or off.
 heaterStatus = 0; // Start with the heating element off (=0)
 fanStatus = 0; // Start with the fan off
 humidifierStatus = 0; // Start with humidifier turned off
 
 // Set up the LCD
 lcd.begin (16,2);
 // Switch on the backlight.
 lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
 lcd.setBacklight(HIGH);
 // Clear the display.
 lcd.clear();
 delay(200);
}

void loop(void)
{ 
 // Read in the temperature and humidity and display across 
 // the top line of the display.
 readInAndDisplayTemperature();
 readInAndDisplayHumidity();
 // Display the current system status on the bottom line of the display.
 displaySystemStatus();

 // If heater is currently off, and temperature is low, turn on the heater.
 if(heaterStatus == 0 and sensorOneTemperature < heaterOnThreshold){
 digitalWrite(heater, HIGH);
 heaterStatus = 1;
 }
 
 // If heater is currently on, and temperature is high, turn off the heater.
 if(heaterStatus == 1 and sensorOneTemperature > heaterOffThreshold){
 digitalWrite(heater, LOW);
 heaterStatus = 0;
 }

 // If fan is currently off, and temperature is high, turn on the fan.
 if(fanStatus == 0 and sensorOneTemperature > fanOnThreshold){
 digitalWrite(fan, HIGH);
 fanStatus = 1;
 }
 
 // If fan is currently on, and temperature is low, turn off the fan.
 if(fanStatus == 1 and sensorOneTemperature < fanOffThreshold){
 digitalWrite(fan, LOW);
 fanStatus = 0;
 }

 // If humidifier is currently off, and humidity is low, turn on the humidifier.
 if(humidifierStatus == 0 and humidity < humidifierOnThreshold){
 digitalWrite(humidifier, HIGH);
 humidifierStatus = 1;
 }
 
 // If humidifier is currently on, and humidity is high, turn off the humidifier.
 if(humidifierStatus == 1 and humidity > humidifierOffThreshold){
 digitalWrite(humidifier, LOW);
 humidifierStatus = 0;
 }

 // Press and hold button1 for half a second to briefly show 
 // the thresholds currently set.
 if(digitalRead(button1)==LOW){
 int i;
 for(i = 0; i < 5; i++){
 delay(100);
 if(digitalRead(button1) == HIGH) i = 20;
 }
 // If button was held for at least 1/2 second, display thresholds...
 if(i < 11){
 displayThresholds();
 }
 }
 
 // Press and hold button2 for half a second to enter programming mode.
 if(digitalRead(button2)==LOW){
 int i;
 for(i = 0; i < 5; i++){
 delay(100);
 if(digitalRead(button2) == HIGH) i = 20;
 }
 // If button was held for at least 1/2 second we go into programming mode.
 if(i < 11){
 programmingMode();
 }
 }
}

void programmingMode(){
 // We arrive in here with the button2 currently being pressed 
 // and entering programming mode.
 lcd.clear();
 lcd.setCursor(0,0);
 // Make sure that the user has released the programming button
 while (digitalRead(button1) == LOW) {
 delay(100);
 }
 lcd.print("PROGRAMMING MODE");

 // Wait for button2 to be released.
 do{
 delay(50);
 }while(digitalRead(button2) == LOW);

 // FIRST of all we are going to programme in the new value 
 // for heaterOnThreshold
 lcd.setCursor(0,0);
 lcd.print("set Heater ON T ");
 displayANumberDuringProgramming(heaterOnThreshold);
 for(int i = 0; i < 50; i++){
 delay(100);
 if(digitalRead(button1) == LOW){ // go down
 heaterOnThreshold = heaterOnThreshold - 0.2;
 displayANumberDuringProgramming(heaterOnThreshold);
 i = 0; 
 waitForButton1Release();
 }
 if(digitalRead(button2) == LOW){ // go up
 heaterOnThreshold = heaterOnThreshold + 0.2;
 displayANumberDuringProgramming(heaterOnThreshold);
 i = 0;
 waitForButton2Release();
 }
 }
 EEPROM_writeFloat(heaterOnEEPROM, heaterOnThreshold);

 // SECOND we set the new value for heaterOffThreshold.
 lcd.setCursor(0,0);
 lcd.print("set Heater OFF T ");
 displayANumberDuringProgramming(heaterOffThreshold);
 for(int i = 0; i < 50; i++){
 delay(100);
 if(digitalRead(button1) == LOW){ // go down
 heaterOffThreshold = heaterOffThreshold - 0.2;
 displayANumberDuringProgramming(heaterOffThreshold);
 i = 0; 
 waitForButton1Release();
 }
 if(digitalRead(button2) == LOW){ // go up
 heaterOffThreshold = heaterOffThreshold + 0.2;
 displayANumberDuringProgramming(heaterOffThreshold);
 i = 0;
 waitForButton2Release();
 }
 }
 EEPROM_writeFloat(heaterOffEEPROM, heaterOffThreshold);

 // THIRD we are going to programme in the new value 
 // for fanOnThreshold.
 lcd.setCursor(0,0);
 lcd.print("set fan ON T ");
 displayANumberDuringProgramming(fanOnThreshold);
 for(int i = 0; i < 50; i++){
 delay(100);
 if(digitalRead(button1) == LOW){ // go down
 fanOnThreshold = fanOnThreshold - 0.2;
 displayANumberDuringProgramming(fanOnThreshold);
 i = 0; 
 waitForButton1Release();
 }
 if(digitalRead(button2) == LOW){ // go up
 fanOnThreshold = fanOnThreshold + 0.2;
 displayANumberDuringProgramming(fanOnThreshold);
 i = 0;
 waitForButton2Release();
 }
 }
 EEPROM_writeFloat(fanOnEEPROM, fanOnThreshold);

 // FOURTH we are going to programme in the new value
 // for fanOnThreshold.
 lcd.setCursor(0,0);
 lcd.print("set fan OFF T ");
 displayANumberDuringProgramming(fanOffThreshold);
 for(int i = 0; i < 50; i++){
 delay(100);
 if(digitalRead(button1) == LOW){ // go down
 fanOffThreshold = fanOffThreshold - 0.2;
 displayANumberDuringProgramming(fanOffThreshold);
 i = 0; 
 waitForButton1Release();
 }
 if(digitalRead(button2) == LOW){ // go up
 fanOffThreshold = fanOffThreshold + 0.2;
 displayANumberDuringProgramming(fanOffThreshold);
 i = 0;
 waitForButton2Release();
 }
 }
 EEPROM_writeFloat(fanOffEEPROM, fanOffThreshold);

 // FIFTH we are going to programme in the new value
 // for humidifierOnThreshold
 lcd.setCursor(0,0);
 lcd.print("set Humidi ON % ");
 displayANumberDuringProgramming(humidifierOnThreshold);
 for(int i = 0; i < 50; i++){
 delay(100);
 if(digitalRead(button1) == LOW){ // go down
 humidifierOnThreshold = humidifierOnThreshold - 2;
 displayANumberDuringProgramming(humidifierOnThreshold);
 i = 0; 
 waitForButton1Release();
 }
 if(digitalRead(button2) == LOW){ // go up
 humidifierOnThreshold = humidifierOnThreshold + 2;
 displayANumberDuringProgramming(humidifierOnThreshold);
 i = 0;
 waitForButton2Release();
 }
 }
 EEPROM_writeFloat(humidifierOnEEPROM, humidifierOnThreshold);

 // SIXTH we are going to programme in the new value
 // for humidifierOffThreshold
 lcd.setCursor(0,0);
 lcd.print("set Humidi OFF % ");
 displayANumberDuringProgramming(humidifierOffThreshold);
 for(int i = 0; i < 50; i++){
 delay(100);
 if(digitalRead(button1) == LOW){ // go down
 humidifierOffThreshold = humidifierOffThreshold - 2;
 displayANumberDuringProgramming(humidifierOffThreshold);
 i = 0; 
 waitForButton1Release();
 }
 if(digitalRead(button2) == LOW){ // go up
 humidifierOffThreshold = humidifierOffThreshold + 2;
 displayANumberDuringProgramming(humidifierOffThreshold);
 i = 0;
 waitForButton2Release();
 }
 }
 EEPROM_writeFloat(humidifierOffEEPROM, humidifierOffThreshold);
}

void waitForButton1Release(){
 do{
 delay(50);
 }while(digitalRead(button1) == LOW);
}

void waitForButton2Release(){
 do{
 delay(50);
 }while(digitalRead(button2) == LOW);
}

void displayANumberDuringProgramming(float theNumber){
 lcd.setCursor(0,1);
 lcd.print(" ");
 lcd.print(theNumber,1);
 lcd.print(" ");
}

void readInAndDisplayTemperature(){
 readTemperatureSensor();
 displayTemperature(); 
}
void readTemperatureSensor(){
 sensor1.requestTemperatures();
 sensorOneTemperature = sensor1.getTempCByIndex(0); 
}
void displayTemperature(){
 // Need to deal with below zero temperatures and also 
 // with above 100 degree temperatures. Adjust number of
 // decimal places to result in similar number of overall 
 // digits used.
 int dp; //number of decimal places to show
 
 // Show the temperature on the screen
 lcd.setCursor(0,0);
 dp = 2; //by default
 if (sensorOneTemperature > 99.9) dp = 0; 
 if (sensorOneTemperature < 0) dp = 0;
 lcd.print("T=");
 lcd.print(sensorOneTemperature,dp);
 lcd.print((char)223);
 lcd.print("C");
 lcd.print(" ");
}

void readInAndDisplayHumidity(){
 readInHumidity();
 displayHumidity(); 
}

void readInHumidity(){
 // Read in the humidity from the sensor and set it 
 // to the global variable 'humidity'.
 int chk = DHT.read11(DHT11_PIN);
 humidity = DHT.humidity;
}

void displayHumidity(){
 // Note - always call this function immediately
 // after displaying the temperature.
 lcd.print("H=");
 lcd.print(humidity,0);
 lcd.print("% ");
}

void displaySystemStatus(){
 lcd.setCursor(0,1);
 // We need to display the status of the heater, fan,
 // and humidifier which can be ON or OFF. If status is
 // ON, fill in character in front of device name.
 if(heaterStatus == 1) 
 lcd.write(255);
 else lcd.print(" ");
 lcd.print("Heat ");
 if(fanStatus == 1)
 lcd.write(255);
 else lcd.print(" ");
 lcd.print("Fan ");
 if(humidifierStatus == 1)
 lcd.write(255);
 else lcd.print(" ");
 lcd.print("Hum ");
}

void displayThresholds(){
 // Arrive here with button1 currently depressed.
 // Show the six programmed thresholds on the screen
 // for three seconds each.

 // First of all show the heater thresholds.
 lcd.clear();
 lcd.print("Heater ");
 lcd.setCursor(0,1);
 lcd.print(heaterOnThreshold,1);
 lcd.print((char)223);
 lcd.print("C to ");
 lcd.print(heaterOffThreshold,1);
 lcd.print((char)223);
 lcd.print("C ");
 delay(3000);

 // Now show the fan thresholds.
 lcd.setCursor(0,0);
 lcd.print("Fan ");
 lcd.setCursor(0,1);
 lcd.print(fanOnThreshold,1);
 lcd.print((char)223);
 lcd.print("C to ");
 lcd.print(fanOffThreshold,1);
 lcd.print((char)223);
 lcd.print("C ");
 delay(3000);

 // Now show the humidifier thresholds.
 lcd.setCursor(0,0);
 lcd.print("Humidifier ");
 lcd.setCursor(0,1);
 lcd.print(humidifierOnThreshold,0);
 lcd.print("% to ");
 lcd.print(humidifierOffThreshold,0);
 lcd.print("% ");
 delay(3000);
}

//---------------------------------------------------------
// Utility functions to write float values to EEPROM and to 
// read them back in again.
void EEPROM_writeFloat(int ee, float value)
{
 byte* p = (byte*)(void*)&value;
 for (int i = 0; i < sizeof(value); i++)
 EEPROM.write(ee++, *p++);
}

float EEPROM_readFloat(int ee)
{
 float value = 0;
 byte* p = (byte*)(void*)&value;
 for (int i = 0; i < sizeof(value); i++)
 *p++ = EEPROM.read(ee++);
 return value;
}

Arduino Two Channel Thermometer with Display (Full Code)

Pictured below is a two-channel thermometer we recently built for a customer. This device takes inputs from two ds18b20 temperature sensors and displays their measured temperatures on a 1602 LCD display module. The thermometer is built around an Arduino Pro Mini.

Arduino double thermometer with lcd display - full code provided

Below is the full Arduino sketch (code) for our device.

See this guide to Connecting an I2C Display to Arduino for the LCD connections. We have added the necessary 3k3 pull up resistors between pin A4 and 5V, and pin A5 and 5V – click here to read about I2C Pull Up Resistors. We have also used an external 5V regulator rather than relying on the 5V regulator built into the Arduino Pro Mini, and added reverse polarity protection on the input with a 1N4001 diode.

// © reuk.co.uk - 2018
// Double Thermometer with Display.

// For the DS18B20 temperature sensors.
#include <OneWire.h> // (https://github.com/PaulStoffregen/OneWire)
#include <DallasTemperature.h> // (https://github.com/milesburton/Arduino-Temperature-Control-Library)

// Data wires are plugged into pins 2 and 3 on the arduino.
#define ONE_WIRE_BUS 2
#define SECOND_BUS 3

// For the 1602 LCD module.
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#define I2C_ADDR 0x27 // Note that some modules have address 0x3F

#define BACKLIGHT_PIN 3
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

// Setup a oneWire instances to communicate with OneWire devices.
OneWire oneWire(ONE_WIRE_BUS);
OneWire secondWire(SECOND_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensor1(&oneWire);
DallasTemperature sensor2(&secondWire);

float sensorOneTemperature = 0.0;
float sensorTwoTemperature = 0.0;

void setup(void)
{
 // Start up the temperature sensor library.
 sensor1.begin();
 sensor2.begin();
 
 // Set the temperature sensor resolutions to 11 bit
 // ADC (12 bit is much slower but higher resolution).
 sensor1.setResolution(11);
 sensor2.setResolution(11);
 
 // Initialise the LCD.
 lcd.begin (16,2); // For a 16x2 character LCD
 // Switch on the LCD backlight.
 lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
 lcd.setBacklight(HIGH);
 // Clear the LCD screen.
 lcd.clear();
}

void loop(void)
{ 
 // Read in the temperatures of the two sensors.
 sensor1.requestTemperatures(); // Read temperature of sensor1
 sensorOneTemperature = sensor1.getTempCByIndex(0);
 sensor2.requestTemperatures(); // Read temperature of sensor2
 sensorTwoTemperature = sensor2.getTempCByIndex(0);

 // Display the temperatures of the sensors on the LCD.
 displayTemperatures();
}

void displayTemperatures(){
 // Display sensor1's temperature.
 lcd.setCursor(0,0);
 lcd.print(" T1: ");
 lcd.print(sensorOneTemperature,2);
 lcd.print((char)223);
 lcd.print("C ");
 
 // Display sensor2's temperature.
 lcd.setCursor(0,1);
 lcd.print(" T2: ");
 lcd.print(sensorTwoTemperature,2);
 lcd.print((char)223);
 lcd.print("C ");
}

If you need any kind of bespoke thermometer or thermostat, please email neil@reuk.co.uk with details of your specific requirements.

Dawn Dusk Henhouse Door Controller Instructions

Pictured below is the connection diagram for the Arduino based standard REUK Dawn/Dusk Henhouse Door Controller. This device is sold as shown including the light detector (non-waterproof), but NOT including the rollers switches and motor which you must source yourself. We recommend the following:

Connection Diagram

connection diagram for Arduino based dawn dusk hen house door opener

Choosing a Motor

The choice of motor is particularly important. We favour the high torque low RPM type of motors linked to above because they are very strong, slow moving, and they do not draw a lot of current. Most importantly, if they are prevented from turning, they still draw well under 1 Amp. Other motors may draw only a couple of Amps in normal operation, but if the door becomes jammed (debris, snow, ice, motor or mechanism seizing, etc) then they can draw very high currents which exceed the 10 Amp rating of the components used on this controller. We suggest that a 5 Amp fuse is fitted in the positive line between the 12V battery (or power supply) and the positive Power Input of the controller.
You will have to double check which way the motor turns to open/close the door, and reverse the connections to it from the controller if the motor is turning the wrong way – i.e. trying to open the door at night, or close the door in the morning.

Roller Switches

For the roller switches, pretty much any will do the job electrically, so you just need to ensure that the ones you choose are sturdy enough to operate reliably with the door mechanism you have put together. The switches need to be wired to the controller such that the terminals are shorted out (closed) when the switch is closed, and are open when the switch is open. Most roller switches have three terminals – a COM (common), an NO (normally open), and an NC (normally closed). You need to use the COM (usually found in the centre), and the NO.

Setting the Light Level Threshold

You have one thing to set up – that is the light level threshold at which you consider it to be the point between day and dusk (and therefore the point between night and dawn). This calibration option is only available between connecting the power to the controller and it detecting dusk – i.e. during what it considers to be day time.
To calibrate the light level threshold, press and hold the Light Level Calibration Button. The red and green LEDs will both turn on. When they turn on (after around one second), release the button. Whatever is the measured light level by the light detector at this time will be stored in memory as the light level ‘threshold’. The red and green LEDs will rapidly flash for 10 seconds to let you know that calibration has been successfully completed. From now on, whenever the light level is brighter than this threshold it is ‘day’, and whenever the light level is darker than this threshold it is ‘night’. Obviously you need to go through this process at dusk when the ambient light level is the same as you want it to be when the door is to close. You need to have the light detector in the actual location and orientation it will have in operation.
The light detector is not waterproof and must therefore be protected from rain and also condensation. Ideally face it in a Southerly direction – if it is facing East or West then then ‘dawn’ will be detected late or early respectively.

Using the Door Controller

The controller starts off assuming that it is day time, so you want to start off with the door open. If the light level falls below the threshold you have set, the red LED will turn on. If the light level remains below the threshold continuously for 10 seconds, then the controller will assume that it is now dusk and will run the motor to close the door until the lower roller switch closes. The controller will then sleep for 2 hours to avoid a false dawn detection if the sun brightens up as it gets close to the horizon (as it often seems to do). During this time the red and green LEDs will blink so you know what is happening.
When the controller finishes sleeping, the ambient light level will be much lower (since it will be 2 hours further into the evening). The light detector will then wait until dawn. When the measured light level exceeds the threshold, the green LED will turn on. If the measured light level remains higher than the threshold continuously for 10 seconds, the controller will assume it is now dawn and run the motor to open the door until the upper roller switch closes. Again the controller will sleep for 2 hours to avoid false dusk detections.
This process will repeat every day.

Further Information

If you have any questions about the connection, setup, and use of this controller, email neil@reuk.co.uk.

Arduino Thermostat with Full Code

We are often requested to build simple thermostats – devices to turn something on or off depending on a measured temperature. Pictured below is an example of one such thermostat we made recently using an Arduino Pro Mini.

arduino thermostat

This particular thermostat was designed to keep a 12V 2A output turned on unless the temperature measured on a waterproof DS18B20 digital temperature sensor reaches above 80°C. The output must then remain off until the measured temperature has fallen to below 70°C.

The thermostat was designed to control a low voltage pump, turning it off if the water in a hot water tank fed by a solar water heating panel gets to be too hot. The 10°C temperature difference between the turn off and turn back on temperatures is hysteresis to prevent the pump being turned on and off rapidly multiple times.

In the image above a MOSFET is used to switch the small pump on and off, but typically a relay would be used in most pump controlling thermostats so that high currents and high voltages can be switched by the low voltage powered thermostat. Therefore, in the Arduino sketch, all references are to a relay. The relay will need to be connected to the relevant Arduino pin via an NPN transistor.

Here is a poorly drawn and badly photographed circuit diagram of the thermostatic relay controller. Click on the image to view it in larger size.

circuit diagram for arduino thermostat relay controller

We used an Arduino Pro Mini because of its small size and price, but any Arduino board could be used for this type of controller.

While the Arduino Pro Mini has an on board 5V regulator, we prefer to use an external low drop 5V regulator (lp2950cz-5.0) because they are much more robust and will cope with higher input voltages – for example 12V batteries while they are being heavily charged.

The red LED shown on the circuit board at the top of this page is not used in this project.

Here is the full sketch (source code) for this thermostat

/*
 * REUK.co.uk - 2017
 * This is a thermostat which will keep a relay closed
 * until the temperature measured reaches 80 degrees C, and then will then
 * then re-close the relay only when the temperature falls to 70 degres or below.
 */

// For the temperature sensors
#include <OneWire.h>
#include <DallasTemperature.h>
// Data wire plugged into pin 3 on the arduino
#define ONE_WIRE_BUS 3

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensor1(&oneWire);

const int relay = 5;

// Fixed temperature thresholds - turn off output at >80C, and turn back on again when <70C
const float onTemp = 70.0;
const float offTemp = 80.0;

// Keep track of whether the relay is energised/closed (1) or open (0). Start with closed (1).
int relayStatus = 1;

void setup(void)
{ 
 // Start up the library
 sensor1.begin();
 //set the resolution to 10 bit ADC
 sensor1.setResolution(10);
 
 // Set up the relay output for the arduino, and start with it high
 // since the thermostat only turns off at >offTemp degrees.
 pinMode(relay, OUTPUT);
 digitalWrite(relay, HIGH);
}

void loop(void)
{ 
 // Read in the temperature of the sensor
 sensor1.requestTemperatures(); // Read temperature of sensor1
 float sensorOneTemperature = sensor1.getTempCByIndex(0);

if(relayStatus == 1 and sensorOneTemperature >= offTemp-0.00001){
 // Relay is currently closed, but the temperature exceeds offTemp - therefore open the relay
 digitalWrite(relay, LOW);
 // Remember the new status
 relayStatus = 0;
 }

if(relayStatus == 0 and sensorOneTemperature <= onTemp+0.00001){
 // Relay is currently open from a previous high temperature event, but now the temperature 
 // is below the threshold so close it again
 digitalWrite(relay, HIGH);
 // Remember the new status
 relayStatus = 1;
 }

// Wait 1/10th of a second before we measure the temperature again.
 delay(100);
}

Testing Arduino Low Power Library with Pro Mini

In general when using an Arduino Pro Mini in one of our projects or products, we use an external LP2940CZ-5.0 voltage regulator instead of the on board regulator. This is because most things we make are for 12V battery systems, and the voltage from a 12V battery can get to well over 12V which is the specified upper input voltage for a Pro Mini. We have measured that one of these regulators with a 10uF capacitor across its 5.0V output, draws a quiescent current of only 0.079mA.

We have found that an Arduino Pro Mini, whether powered as described above, or with the on board regulator draws around 20mA @ 12.0V. This is very high for an always on battery powered device – it will use 500mAh (0.5Ah) of battery charge per day. Therefore, we are always interested in testing ways to minimise power consumption.

breadboard test of low power library for arduino pro mini

We set up the above test circuit with a 12V input, and our usual LM2940CT-5.0 regulator connected to an Arduino Pro Mini (16MHz / 5V). With a sketch containing just delay(8000); in the loop() function – i.e. the Arduino will wait 8 seconds, then wait another 8 seconds, then wait another 8 seconds, etc – we measured a current draw of 19.793mA @ 12.0V input voltage.

We downloaded and installed the following Lightweight low power library for Arduino – LowPower.h, and modified our test sketch as shown below to power down the microcontroller for 8 seconds within the loop.

arduino-pro-mini-low-power-testing

This time we measured the current draw to be just 6.265mA @ 12.0V input voltage – a huge reduction of around 70% power consumption obtainable just by replacing the delay function with the powerDown function from the LowPower library.

We make a lot of dataloggers and monitoring devices which spend most of their time doing nothing – just waiting to take the next measurement. Therefore this low power library is a quick and easy way to reduce power consumption.

(Note that 8 seconds is the maximum power down duration that can be set with this library, but by using loops of multiple 8 second intervals in your sketches, you can create a low power consumption delay of as long as you want.)

A standalone arduino in a low power consumption circuitIf you use a Standalone Arduino on a breadboard directly powered by a battery pack of the correct voltage (i.e. no voltage regulation required), it is possible to run your Arduino off less than 50uA @5V (<1000th the power consumption of our tests above) and therefore power something for years with a AA cells or smaller. See here for an excellent article How to Run An Arduino For Years on a Battery from the Open Home Automation website where they use the JeeLib low power library with a standalone Arduino.

Picaxe Based Dual One Shot Timer Relay with Code

Pictured below is a simple timer relay circuit we recently made which we will detail here together with the source code for the microcontroller since we have had many requests for example code for timers of this type.

Two option one shot timer relay circuit - PICAXEWe received a request for a timer with two buttons. Pressing the first button was to cause a relay to close for 10 minutes, and pressing the second button was to cause the relay to close for 30 minutes. The relay was to be used to switch a mains powered appliance.

In our article Make a PICAXE Repeating Timer, we show how to make a repeating on/off timer using a PICAXE microcontroller. The timer pictured above differs in that it has button inputs to deal with and also a one-shot instead of repeating timer.

The red LED is used to show which timer is running – off, but flickering on briefly once per second is the 10 minute timer; on, but flickering off briefly once per second is the 30 minute timer. The green LED is connected across the coil of the relay (with a current limiting resistor) to show when the relay is closed.

The PICAXE code below could be greatly reduced in length but to keep it simple to read through, understand, and adapt, we have left it with separate functions for the 10 minute and the 30 minute timers (instead of making one general function which could run for any duration in response to any button press).

symbol button1 = pinC.1
symbol button2 = pinC.2
symbol led = C.0
symbol relay = C.4

' Start with the relay open and the red LED turned off.
low relay
low led

main:
   if button1 = 1 then goto run10minutes
   if button2 = 1 then goto run30minutes
   pause 100
   goto main

run10minutes:
   'make sure button is held a little before closing the relay,
   high led
   for b0 = 1 to 5
      delay 50
      if button1 = 0 then 
         low led
         goto main
      endif
   next b0

   'Close the the relay
   high relay 

   'wait for the button to be released.
   do
      pause 50
   loop while button1 = 1

   low led

   for b0 = 1 to 10 'minutes
      for b1 = 1 to 60 'seconds
         high led
         pause 100
         low led
         pause 900
      next b1
   next b0

   'Open the relay.
   low relay

   goto main

run30minutes:
   'make sure button is held a little before closing the relay,
   high led
   for b0 = 1 to 5
      delay 50
      if button2 = 0 then 
         low led
         goto main
      endif
   next b0

   'Close the the relay
   high relay 

   'wait for the button to be released.
   do
      pause 50
   loop while button2 = 1

   low led

   for b0 = 1 to 30 'minutes
      for b1 = 1 to 60 'seconds
         high led
         pause 900
         low led
         pause 100
      next b1
   next b0

   'Open the relay.
   low relay

   goto main

 

Saving Arduino Collected Data to Text File on Windows

We are often asked how to log data from an Arduino to a text file saved on a Windows PC. This is very simple with Linux and Mac OS, but it can be also be achieved on Windows with minimal effort.

We make a lot of dataloggers, the majority of which either store data internally and then output a summary to an LCD display, or dump all collected data to an SD card for later processing and analysis on a PC. However, it is relatively simple to collect data from any number of sensors connected to an Arduino board and send that data over a serial connection directly to a text file on a PC.

There are many software options available, but we typically use CoolTerm (available free of charge here: download CoolTerm) which is a serial monitor which will also capture transmitted data to a text file and automatically add time stamps to each line of data which are essential for a good datalogger.

As an example we slightly modified the code for our 2016 solar water heating pump controller so that every time data is taken from the two connected digital temperature sensors, those measurements and also the system status (pump ON or OFF) are output through the serial port to a connected PC. (Full details on generating Serial output from an Arduino are available here: Arduino Serial from the official Arduino Reference site.)

Download CoolTerm from the link already provided above. You will end up with an approximately 10MB zip file which needs to be extracted. When that is done, go into the folder created, and double click on the CoolTerm application.

Launch CoolTerm applicationClick on Connection > Options and then in Serial Port Options select the Port you would like to use. If you are using the Arduino IDE, in the bottom right hand corner of the window will be shown the type of board you are using followed by COM# where # is the number of the port your Arduino is currently set up to use and is also the port you should select within CoolTerm).

Selecting the serial COM port to use with CoolTerm with ArduinoAt the same time set the baudrate to 9600 (making sure that in the sketch you have uploaded to your Arduino, you have also included Serial.begin(9600); in the setup() function.

CoolTerm connection options

Assuming that you would like all data to be timestamped (adding the date and time to every line of data sent), do Connection > Options > Receive and check the ‘Add timestamps to received data’ box.

timestamp serial data from Arduino and store in text file on PC

Then to have any serial data from your Arduino automatically stored in a text file on the PC, do Connection > Capture to Text File and then click on Start. You then just have to set the name for the file that you would like your data to be stored in, and your datalogger is complete.

arduino coolterm serial monitor showing arduino collected data

To stop collecting data, you can either click on the large Disconnect icon, or if you want to stay connected to the Arduino board, do Connection > Capture to Text File > Stop.

Once you have either disconnected the Arduino board or Stopped the capture, you cannot then restart and append data to the same file – you can only overwrite the original file or start a new one. If you want to pause capture and then restart it to append to the same file, do Connection > Capture to Text File > Pause to pause, and then Connection > Capture to Text File > Resume to resume it at a later time.

Data collected from solar pump controller from Arduino via Serial and CoolTerm to PC

When you have finished capturing data, you will end up with a text file of everything captured which can be processed and visualised using Excel or a similar application.

Testing Accuracy of micro:bit Internal Clock

At REUK.co.uk we make a lot of timers for a wide range of different applications. Most projects only call for a timer to be accurate to within around 10 seconds per hour. For those that require greater accuracy, we sometimes use a real time clock such as the DS3231, and other times (when the microcontroller we are using is inaccurate but CONSISTENTLY inaccurate) we just calibrate the timer against a previously tested and known accurate timer.

While having a first play around with MicroPython on the micro:bit, we ran a few one minute tests of the accuracy of the internal clock. When hand timing with a stopwatch we consistently found that one micro:bit minute was actually around 59.85 seconds. That is of course very close to the 60.00 seconds of a true minute and the error could well have been due to human error with the stopwatch, but it did appear that our micro:bit was running a little fast (since its ‘minute’ was finishing quicker than a true minute). Being 0.15 seconds fast every minute may not sound a lot, but it equates to the internal clock being minutes fast per day. Had the timing appeared to be a tiny bit slow, it could just have been that there was a delay due to the time taken for the micro:bit to do internal processing etc, but as the timings appeared to be fast, they must actually be fast.

In order to test this properly, we connected a relay via a transistor to one of the digital output pins of the micro:bit. The NO and COM terminals of this relay were in turn connected in parallel across the contacts of the start/stop button on a previously cannibalised stopwatch. We then wrote a small MicroPython script to briefly set the output pin to digital HIGH to start the stop watch, and then after 10 minutes sleep(600000) – i.e. sleep for 600,000 milliseconds, 600 seconds, 10 minutes – again briefly set the pin high to stop the stopwatch.

testing the micro:bit internal clock accuracy

Running this test ten times and averaging the results we found that the micro:bit ten minutes was actually 9 minutes, 58.51 seconds (598.51 seconds instead of 600.00). The timings were very consistent with the shortest and longest measured times both being just 0.03 seconds away from the average – a tight range of 9m58.48s to 9m58.54s measured over the ten ten minute periods.

Looking at the average time, 9 minutes 58.51 seconds is 0.248% faster than 10 minutes. Being 0.248% fast is not a big deal when timing events lasting seconds or a couple of minutes, but over the course of a 24 hour period, our micro:bit would gain 215 seconds, or over 3.5 minutes which is not insignificant.

The consistency we had found in the inaccuracy was a very good sign. It made manual calibration of our micro:bit a possibility so that we would forever know (at least if timing accuracy is not significantly affected by changes in ambient temperature or input voltage) that this particular micro:bit’s clock could be trusted without the need for an external real time clock (RTC).

The Python command we used to get the micro:bit to sleep for one second was sleep(1000). Apparently with our micro:bit this resulted in it sleeping for a little less than one true second, therefore we needed to increase the sleep time by the 0.248% that the internal clock was now known to be fast. It would be nice to use the command sleep(1002.4833), but unfortunately it is only possible to use a whole number (integer) value – i.e. sleep(1002) or sleep(1003). Either of these approximations would make times measured by our micro:bit 5 times more accurate, but that is still around 45 seconds too fast per 24 hours – not good enough.

Therefore, when we want to time long intervals – hours and days – we need to sleep for longer intervals such as sleep(601489.98) corresponding to 10 minutes to increase the accuracy possible. Luckily enough 601489.98 is almost exactly 601490 micro:bit measured milliseconds. So, in any projects which require us to measure long time periods with our micro:bit, we will count the number of our pre-calibrated 10 minute intervals have passed measured with sleep(601490). Now calibrated, our micro:bit should certainly be accurate enough for a wide range of future long duration projects.

The timing accuracy we found for our micro:bit before calibration was 25 ppm (parts per million) which compares favourably with the typical 50 ppm found with Arduino boards, and we found the consistency in timing inaccuracy to be better with micro:bit than we have previously found with Arduino. However, we could have been lucky or unlucky with our particular micro:bit board. We will need to repeat our experimentation with multiple micro:bits to find the typical timing accuracy of micro:bits in general in the future.

micro:bit Battery Capacity

In our recent blog post: micro:bit Power Consumption, we look at how much power the micro:bit draws at different input voltages while idle, illuminating the full array of 25 LEDs, or with the processor operating at 100% load carrying out calculations. In this post, we will look at how this power consumption relates to battery life.

The micro:bit is supplied with a 2xAAA battery holder. Assuming that the majority of users will put a pair of good quality alkaline cells into this battery holder for powering their micro:bit projects, we will use 3 Volts as the input voltage in the calculations to follow. Good alkaline cells start off at just over 1.5 Volts and maintain this voltage until it begins to drop off only when the cells are almost depleted of charge. (The calculations would be quite different if rechargeable NiMH cells were to be used for example since with an input voltage of 2.4-2.5V, the power consumption is notably lower.)

Referring to the table of power consumption results (available in the post linked to at the top of this post), with 3 Volts on the input we saw the following:

microbit power consumption with a 3V input voltagei.e. at idle (minimal processing and no LEDs on), power consumption is just 5.83mW, but with the processor working at 100% and all of the LEDs on, power consumption is over 43mW.

Good quality AAA cells typical hold a charge of somewhere from 800-1000mAh, AA cells from 1800-2400mAh. There are higher capacity cells available, but these are usually optimised for use with digital cameras and similar supplying high current briefly each time a picture is taken rather than the steady state constant low current typical for a micro:bit project. They also tend to be much more expensive.

battery life testing with micro:bitThe table above takes the power consumption data previously measured with a 3 Volt input voltage and shows for how many days the battery pack will last for a wide selection of different capacities. For example, if you had a processor intensive project which made use of all or nearly all of the LEDs at once, and a pair of 1000mAh Duracell Plus AAA cells, you could expect to get approximately 3 days of battery life. If on the other hand you had a project which was not processor intensive, did not make use of the LEDs, and used a pair of 2400mAh AA cells, you could expect around 50 days of battery life.

There are many factors above which will affect the real world battery life including the ambient temperature, the specific features of the project to be built, and of course any additional components, sensors, or devices to be powered at the same time as the micro:bit. However, it will hopefully give you a pretty accurate idea of what to expect and to inform your choices when you are choosing how to power your project.

Bookmark microbit.me.uk to view all our previous and future micro:bit related articles.

micro:bit Power Consumption

Having finally managed to obtain a micro:bit, the first thing we wanted to test was the power consumption at different input voltages.

The micro:bit is supplied as a kit together with a 2xAAA battery holder which plugs into the micro:bit, two cheap generic AAA cells, and a USB cable for connecting the micro:bit to a PC to upload your code to it (and which can also be used to power the micro:bit via the onboard voltage regulator).

The input voltage for micro:bit is labelled as 3 Volts. Alkaline AAA cells such as those offered by Duracell supply around 1.5-1.7 Volts each when fully charged. Rechargeable NiMH cells, something around 1.25 Volts. Therefore, since the micro:bit is designed to be battery powered by TWO AAA cells, it must be able to operate reliably from a wide range of input voltage – e.g. just 2V from a pair of depleted rechargeable NiMH cells, up to at least 3.4V to cope with a pair of brand new out of the box Duracell cells.

In order to test power consumption, we used the Microsoft Block Editor (available for use at the official micro:bit website) to build a simple project as shown below. Click on the image to see it in full size.

Power testing the micro:bit

This will first turn off all of the LEDs in the 25 LED array so the micro:bit is not doing anything apart from background processing. If the A button on the micro:bit is then pressed, all LEDs will light up and remain on for five seconds. If the B button is pressed, the processor of the micro:bit will be worked hard by generating 233,000 random numbers. (The reason that 233,000 was chosen was that it took the micro:bit 10 seconds to complete that many iterations of the loop, and that gave the time necessary to reliably measure the minimum and maximum current drawn by the micro:bit while under this heavy load).

Powering the micro:bit from a variable DC power supply starting down at 1.5V, the voltage was increased until it was sufficient to have the LEDs turn on. We found this voltage to be at around 1.80V. We then tested different input voltages from around 1.8V up to just over 4.0V in 0.2V steps with the micro:bit either resting, with all the LEDs on, and then with it doing repeated random number generation.

We found that the current draw fluctuated constantly (by around 1mA), probably due to the internal workings of the micro:bit which we have not yet studied in detail. We found that in all three tests for all input voltages, there was a minimum current, a maximum current, but the typical current draw was found to be around one-third of the way between the minimum and maximum. We called this the mean (average) for this rough and ready set of experiments and the final table of results is given below.

results of micro:bit power consumption experimentsEach row in the table shows a different input voltage. The Resting (Idle) Mean, LEDs Mean, and Working Mean have been described in the previous paragraph. P(Resting), P(LEDs), and P(Working) are calculated using Ohm’s Law that Power equals Current multiplied by Voltage.

It is immediately apparent the massive effect that input voltage has on total power consumption. At 1.83V input voltage with all the LEDs on, only 3.76mW is consumed, but with an input voltage of 4V, 78.7mW is consumed – 21x as much power. Even with input voltages of 3V and 2.4V (typical alkaline and NiMH rechargeable voltages respectively) the power consumption is 31mW compared to 14mW – more than twice as much. Therefore, unless you need the LEDs to be very bright, it is better (if power consumption is important to you) to use a low input voltage, or to use the built in PWM to reduce the brightness of the LEDs and therefore their power consumption.

For projects not requiring the LEDs, the power consumption is not so affected by input voltage. At idle, approximately twice as much power is consumed at 4V as it is at 1.8V. Under heavy load, more than three times as much power is consumed at 4V compared to 1.8V. Therefore, if the LEDs are not required, it is more power efficient to use a good voltage regulator to bring the input battery voltage down to around 2V than it is to just run the micro:bit at the battery voltage. Important things to consider if you intend to power your micro:bit by solar power or some other limited power source.

Finally we noted that the 10 seconds it took the micro:bit to generate 233,000 random numbers in our experimentation was completely unaffected by changes to the input voltage – i.e. it took 10.0 seconds with an input voltage of 1.83V and still took 10.0 seconds with 4.05V. Again this shows that where the LEDs are not being used, a low input voltage is the way to go if you need to minimise power consumption.

Bookmark microbit.me.uk to view all our previous and future micro:bit related articles.