The graph shows that when in automode, the humidifier is switched on when the humidity goes 2 degrees (the hysteresis) under the Setpoint
On Github, Krzysztof has published a truly excellent series of articles regarding the use of an ESP8266 that reads a humidity sensor and publishes the data on a personal webserver, as well as on emoncms (an energymonitoring cloud) and allows to take action depending on the reading of the sensor, either manually or automatically and to do that via a webserver, or via MQTT or OpenHAB.
Anyone who is interested in this subject should surely read his set of articles that starts basically from scratch and keeps adding functionality.
As I am not really partial to emoncms, but more of a Thingspeak user, I decided to add a Thingspeak module to his program.
As a base I used this program.
To add Thingspeak functionality do the following:
under the section: Emoncms configuration add:
//
// Thingspeak configuration
//
const char* thingspeakServer = "api.thingspeak.com";
const char* writeAPIKey = "yourThingspeakWriteApi";
// function prototypes required by Arduino IDE 1.6.7
void sendDataToThingspeak(void);
in the main loop (thus in void loop(void) )
above (or below) sendDataToEmoncms();
add: sendDataToThingspeak();
Add a tab called “Thingspeak” and add there the following:
void sendDataToThingspeak(void)
{
// make TCP connections
WiFiClient client;//this one is probably not necessary
const int httpPort = 80;
if (!client.connect(thingspeakServer, httpPort)) {
return;
}
String url = "/update?key=";
url += writeAPIKey;
url += "&field1=";
url += String(humidity);
url += "&field2=";
url += String(humiditySetPoint );
url += "&field3=";
url += String(humidifier);
url += "&field4=";
url += String(autoMode);
url += "\r\n";
// Send request to the server
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + thingspeakServer + "\r\n" +
"Connection: close\r\n\r\n");
}
Ofcourse one still needs to setup a Thingspeak account, but that is well known how I presume. One also needs to setup an Emoncms account, buthat is well described in the series of articles, or one can decide to delete that functionality.
For MQTT I have used a public broker, but one can decide to set it up on say a Raspberry Pi
The ESP8266-01 is a great WiFi enabled microcontroller but it only has 4 I/O pins broken out. Fortunately it does support I2C protocol so in spite of the low number of pins, there still is a lot of hardware that can be added.
As the ESP8266-01 has no analog inputs at all, adding an ADC to it is something I wanted to do. After all, sensors as LDR or NTC are still analog.
The PCF8591 is such an ADC: it is a single-chip, single‑supply low‑power 8‑bit CMOS data acquisition device with four analog inputs, one analog output and a serial I²C‑bus interface. Three address pins A0, A1 and A2 are used for programming the hardware address, allowing the use of up to eight devices connected to the I²C‑bus without additional hardware. I doubt whether I need more than 8 of those chips. Lets just start with one. Although one can get the individual chip, I have chosen for a module that actually already has some sensors on it:
AIN0 – Jumper P5 – Light Dependent Resistor (LDR)
AIN1 – Jumper P4 – Thermistor
AIN2 – Not connected
AIN3 – Jumper P6 – Potentiometer
YL-40 circuit
The I2C address of the PCF8591 is determined by the pins A0-A2. As they are close to the Ground pin, let’s start with grounding them (as they are on the module). The address is 1001A2A1A0. With A2-A0 being LOW, that is 1001000=0x48.
base
A2A1A0
Hex
Dec
1001
000
48
72
1001
001
49
73
1001
010
4A
74
1001
011
4B
75
1001
100
4C
76
1001
101
4D
77
1001
110
4E
78
1001
111
4F
79
In some programs you will see the address as “0x90>>1” Which is 48 as well. The “0x90” counts the LSB of the 8 bit address, which is the R/W bit. With the Write Bit Low (=active) the full address is 10010000=0x90, but the rightshift 1 removes the LSB again, making it 0x48.
The module is hardwired to 0x48 as the three address lines are soldered to ground. So if you would want to use more than one module on the same I2C port you would need to do some de-soldering (or use bare PCF8591 chips ofcourse).
Sunfounder board (expensive)
A similar but needlessly more expensive, non configurable board without the sensors is the sunfounder board.
Mini PCF8591 AD DA Shell Module
There is a (more expensive) fully configurable module, that allows to set the I2C address with jumpers. That module is daisy chainable with other I2C modules in the same range (there is for example a PCF8547 digital I/O module with similar connections). Making a module yrself is not hard either:
PCF8591 configured as 0x48
The control byte sets the operating mode of the PCF8591 and is described in section 7.2 of the datasheet, The upper nibble of the control register is used for enabling the analog output, and for programming the analog inputs as single-ended or differential inputs.
The lower nibble selects one of the analog input channels defined by the upper nibble. If the auto-increment flag is set the channel number is incremented automatically after each A/D conversion.
If the auto-increment mode is desired in applications where the internal oscillator is used, the analog output enable flag in the control byte (bit 6) should be set. This allows the internal oscillator to run continuously, thereby preventing conversion errors resulting from oscillator start-up delay. The analog output enable flag may be reset at other times to reduce quiescent power consumption.
As it is not my intention to explain the full innerworkings of the PCF8591, but just to show it is working with the ESP8266-01, I will skip a full technical discussion. For now it is enough to know that the PCF8591 can be read byte for byte, but it can also be read in ‘burst mode’, in which we read the 4 analog values all at once. The program I present is burstmode with autoincrement of the address. The reason we read 5 bytes instead of 4 is because the first byte contains old data. As the datasheet states in paragraph 8.4: “The first byte transmitted in a read cycle contains the conversion result code of the previous read cycle.”
#include "Wire.h"
int PCF8591=0x48; // I2C bus address
byte ana0, ana1, ana2, ana3;
void setup()
{
Wire.pins(0,2);// just to make sure
Wire.begin(0,2);// the SDA and SCL
}
void loop()
{
Wire.beginTransmission(PCF8591); // wake up PCF8591
Wire.write(0x04); // control byte: reads ADC0 then auto-increment
Wire.endTransmission(); // end tranmission
Wire.requestFrom(PCF8591, 5);
ana0=Wire.read();// throw this one away
ana0=Wire.read();
ana1=Wire.read();
ana2=Wire.read();
ana3=Wire.read();
}
Obviously when you have these values read you will need to do something with them: print them out, put them on yr own webpage or upload them to e.g. Thingspeak. As printing from the ESP8266-01 is not always easy, I will show you how to upload the values to Thingspeak:
#include // ESP8266WiFi.h library
#include "Wire.h"
int PCF8591=0x48; // I2C bus address
byte ana0, ana1, ana2, ana3;
const char* ssid = "YourNetworkSSID";
const char* password = "YourPassword";
const char* host = "api.thingspeak.com";
const char* writeAPIKey = "YourWriteAPI";
void setup() {
// Initialize sensor
Wire.pins(0,2);// just to make sure
Wire.begin(0,2);// the SDA and SCL
// Connect to WiFi network
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
}
void loop() {
Wire.beginTransmission(PCF8591); // wake up PCF8591
Wire.write(0x04); // control byte: reads ADC0 then auto-increment
Wire.endTransmission(); // end tranmission
Wire.requestFrom(PCF8591, 5);
ana0=Wire.read();
ana0=Wire.read();
ana1=Wire.read();
ana2=Wire.read();
ana3=Wire.read();
// make TCP connections
WiFiClient client;
const int httpPort = 80;
if (!client.connect(host, httpPort)) {
return;
}
String url = "/update?key=";
url+=writeAPIKey;
url+="&field1=";
url+=String(ana0);
url+="&field2=";
url+=String(ana1);
url+="&field3=";
url+=String(ana2);
url+="&field3=";
url+=String(ana3);
url+="\r\n";
// Request to the server
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
delay(1000);
}
DAC
The PCF8591 does not only have 4 ADC channels but also 1 DAC channel. Writing to the DAC is as follows:
#define PCF8591 (0x48) // I2C bus address
void setup()
{
Wire.pins(0,2);// just to make sure
Wire.begin(0,2);
}
void loop()
{
for (int i=0; i<255; ++i)
{
Wire.beginTransmission(PCF8591); // wake up PCF8591
Wire.write(0x40); // turn on DAC b1000000
Wire.write(i);
Wire.endTransmission();
}
}
Calculations
A the board normally uses the 3.3v supply as the reference voltage:
The input voltage is determined with:
vIn = value * (3.3 / 255)
and the output voltage is:
vOut = (value / 255) * 3.3
or to find the value for a given voltage:
value = (vOut / 3.3) * 255
So if for instance I would write the value of 50 to the DAC. The voltage would be: 0.64V
In order to test that, I hooked up Aout to the A0 of an ESP 8266-12 and found a value of 192.
To calculate that to a voltage that is (192/1023)=0.18V. (Remember, the ESP gives readings from 0-1023, whereas 1023 being 1 Volt.) So that is not particularly close.
Oddly though when I hooked up the Aout to AIN2 or AIN1 that didn seem to give reliable readings. A value of 50 written to Aout should give a value of 50 on AIN, but oddly it didnt
In my version of the board the NTC channel only varied between 255 and 254, changing the temperatuur didnt seem to have any influence, but removing jumper P4 made the value go all over the place so i presume the channel is ok, and only the NTC might not be OK.
Differential Input
The PCF8591 is capable of more, it is for instance possible to do differential measurements.
Suppose you want to measure the difference between AIN0 and AIN1.
You do that as follows:
#include "Wire.h"
int PCF8591=0x48; // I2C bus address
int Raw = 0;
float Voltage = 0.0;
void setup()
{
Wire.pins(0,2);// just to make sure
Wire.begin(0,2);
Serial.begin(9600); //Not on an ESP8266-01
Wire.beginTransmission(PCF8591); // Wake up PCF8591
Wire.write(0x11); // control Byte for differential input mode
Wire.endTransmission(); //
}
void loop()
{
Wire.requestFrom(PCF8591, 1); // Get Data from channel 1
RawValue=Wire.read();
Voltage = (Raw * 3.3 )/ 255.0;
Serial.print("Raw ADC = ");
Serial.print(Raw);
Serial.print(" Voltage = ");
Serial.println(Voltage,3);
delay(1000);
}
If you want to hook up this chip to a Raspbery Pi, have a look here.
There are other ways of expanding the ADC capabilities of the ESP8266:
If you want more precision, use the one channel MCP3421 18 bits I2C ADC, an 8 channel 12 bits MCP3208 SPI ADC expander, or an ADS1115 16 bits 4 channel expander
The limited number (4) of GPIO pins on the ESP8266-01 may seem like an obstacle, for any serious application.
Yet if one uses the pins in a smart way it is very well possible to do a lot with only those 4 pins.
In some of my recent postings, I have shown the use of a DHT11 a DS18B20, an OLED, an RTC and a BMP180 with the ESP8266-01.
In this posting I set out to use 4 sensors and a display, while also uploading the acquired data to Thingspeak. It actually is expanding on a project of monitoring the atmosphere in and around my chicken coop. Yes, you could call this a weatherstation, but it is just to illustrate the use of the 4 pins, you could easily make something else this way
I will be using 2 pins for I2C (BMP180 and OLED)
1 pin for 2 DS18B20 sensors via the OneWire protocol
1 pin for the DHT11
Although the ESP8266-01 now has all its pins used, I can still add more sensors (or actuators) through the OneWire protocol and/or via the I2C protocol.
So, what do we need: BOM
ESP8266-01
2x DS18B20
1x DHT11
1x BMP180
OLED (optional)
and ofcourse a breadboard, a 3.3 V PSU and some breadboard wires and a Thingspeak acount
Just some remarks regarding the BOM:
ESP8266-01
Obviously the project is about utilizing the limited pins of the ESP8266-01, but if you still need to buy one, you could consider an ESP8266-12 that has more pins
DHT11
A cheap all purpose humidity and temperature sensor. It is not hugely accurate but it will do. If you still need to buy one, you could opt for the DHT22 that is supposedly more accurate, but you could also opt for the AM2321. That is a sort of DHT22 that is suitable for I2C, thus freeing another pin
BMP180
measures temperature and Airpressure. It is the successor of the BMP085, but it also now has some successors itself. There is the (cheaper) BMP280, but you could also opt for the BME280 that measures temperature, airpresure AND humidity. That way you can save on the DHT/AMS sensor
OLED
I just used that so I quickly could see whether the sensors were read, but you could just as well check it on Thingspeak. The OLED is too small anyway to print all the read values
The circuit
The 4 pins of the ESP8266 are not indicated as such on the PCB, and most images only clearly state GPIO0 and GPIO2.
However the ESP826-01 has a a GPIO1 pin (the Tx pin) and a GPIO3 pin (the Rx pin).
i will be using those pins as follows
GPIO0 -> SDA pin of the I2C port
GPIO1 ->DHT11
GPIO2-> SCL pin of the I2C port
GPIO3-> OneWire Bus
As my I2C modules already have pull up resistors, I will not add any I2C pullup resistors there. The DS18B20 still needs a pull up resistor for which I used a 4k7, but it is really not that critical, a 10k is also good. The DHT11 supposedly also needs a pull-up resistor but I found it to work without one as well. adding a 4k7 resistor didnt change any of the readings, so I left it out. Many of the 3 pin DHT11 modules, already have a 10 k soldered onto the module.
I just realized that I didnt draw the connections for the OLED. That is because I only hooked it up for a quick check, but should you want to add it, it is just a matter of connecting SDA to SDA and SCL to SCL… and ofcourse the ground and Vcc pins to their counterparts
The program is quite straightforward. First it sets up the libraries and the sensors.
It attaches the DHT11 to pin 1 (Tx) and the OnWire bus for the DS18B20 to pin 3 (Rx). In order to use more than 1 DS18B20 sensor on the OneWire bus, you need to know their ‘unique adress’. If you do not have that then you need a program to read those addresses. Do that on an arduino for ease.
In the program you still have to provide your WiFi credentials as well as the write API for your Thingspeak Channel
/*
Field 1 temp roost (DHT11)
Field 2 humidity roost (DHT11)
field 3 Coop temperature (DS18B20)
field 4 soil temperature (DS18B20)
field 5 Airpressure (bmp180)
field 6 Outside temperature (bmp180)
* */
#include <DHT.h>
#include <OneWire.h>// http://www.pjrc.com/teensy/td_libs_OneWire.html
#include <DallasTemperature.h> // http://milesburton.com/Main_Page?title=Dallas_Tem...
#include <Adafruit_BMP085.h>
#include <ESP8266WiFi.h>
#include "SSD1306.h"
SSD1306 display(0x3c, 0, 2);
#define DHTPIN 1 //GPIO1 (Tx)
#define DHTTYPE DHT11
#define ONE_WIRE_BUS 3 // GPIO3=Rx
const char* ssid = "YourSSID";
const char* password = "YourPassword";
const char* host = "api.thingspeak.com";
const char* writeAPIKey = "W367812985"; //use YOUR writeApi
//DHT11 stuff
float temperature_buiten;
float temperature_buiten2;
DHT dht(DHTPIN, DHTTYPE, 15);
//DS18b20 stuff
OneWire oneWire(ONE_WIRE_BUS); //oneWire instance to communicate with any OneWire devices
DallasTemperature sensors(&oneWire);// Pass address of our oneWire instance to Dallas Temperature.
DeviceAddress Probe01 = { 0x28, 0x0F, 0x2A, 0x28, 0x00, 0x00, 0x80, 0x9F};
DeviceAddress Probe02 = { 0x28, 0x10, 0xA4, 0x57, 0x04, 0x00, 0x00, 0xA9};
// bmp180 stuff
Adafruit_BMP085 bmp;
void setup() {
//I2C stuff
Wire.pins(0, 2);
Wire.begin(0, 2);
// Initialize sensors
//dht 11 stuff
dht.begin();
//ds18b20 stuff
sensors.begin();//ds18b20
// set the resolution to 10 bit (Can be 9 to 12 bits .. lower is faster)
sensors.setResolution(Probe01, 10);
sensors.setResolution(Probe02, 10);
//bmp180 stuff
if (!bmp.begin()) {
// Serial.println("No BMP180 / BMP085");
// while (1) {}
}
//OLED stuff
display.init();
display.flipScreenVertically();
display.setFont(ArialMT_Plain_10);
delay(1000);
// Connect to WiFi network
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
}
void loop() {
//ds18b20stuff-------------------
sensors.requestTemperatures(); // Send the command to get temperatures
temperature_buiten = sensors.getTempC(Probe01);//
temperature_buiten2 = sensors.getTempC(Probe02);//
//dht11 stuff--------------------
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
if (isnan(humidity) || isnan(temperature)) {
return;
}
//bmp stuff-------------------------
String t= String(bmp.readTemperature());
String p=String(bmp.readPressure());
//OLED stuff--------------------------
display.clear();
display.drawString(0,10,p);//bmp pressure
display.drawString(0,24,String(temperature_buiten));//ds18b20
display.drawString(0,38,String(humidity));//dht11
display.display();
// make TCP connections
WiFiClient client;
const int httpPort = 80;
if (!client.connect(host, httpPort)) {
return;
}
String url = "/update?key=";
url += writeAPIKey;
url += "&field1=";
url += String(temperature);// roost (DHT1)
url += "&field2=";
url += String(humidity);// roost (DHT11)
url += "&field3=";
url += String(temperature_buiten);//coop temperature (DS18B20 nr 1)
url += "&field4=";
url += String(temperature_buiten2); //soil temperature (DS18B29 nr 2)
url +="&field5=";
url +=String(bmp.readTemperature());//Outside temperature (BMP180)
url +="&field6=";
url +=String(bmp.readPressure());// Airpressure (BMP180)
url += "\r\n";
// Send request to the server
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
delay(1000);
}
Currently this program only monitors, but what is to stop you from adding a BH1750 I2C light sensor to measure if it is evening or morning or an RTC to know the time of day and to open and close the door of the coop automatically with aid of a PCF8574 I2C I/O expansion card, or as it is already in the garden, add a PCF8591 or ADS1115 AD converter to measure soil humidity and activate a pump when necessary. Or maybe switching on the water basin heater when the temperature falls below zero
if there is an I2 C chip for it, the ESP8266 can probably use it.
NOTE: the Adafruit DHT library contains an error that may show up in bigger programs on an 8266. If the majority of readings result in “failed to read”, it is time to comment out two erroneous lines in the DHT.cpp file as in the picture below:
Note: The Rx and Tx pins sometimes can be a bit unruly when used as GPIO. If you want to use them e.g. as an output, you may want to check here.
After I added an RTC and an OLED to the ESP8266-01 through I2C, I presumed it should not be too difficult to add a BMP180 as well, in spite of coming across some postings on Internet of people not succeeding.
My BMP180 module -from my Arduino days- was a 5 Volt module, which made me think I may need a level shifter, which would be a pity as the BMP180 is in fact a 3.3V chip.
The circuit of my BMP180 module shows that the I2C pull up resistors (4k7) are in fact connected to the 3.3 V line that is provided by a 662k voltage regulator (as was to be expected). This meant that even if I fed the module with 5 Volt, I would not need a level shifter. And as the 662K is a low drop regulator and the BMP 180 also works with voltages lower than 3.3 (1.8-3.6 in fact), I presumed I didn’t need 5 Volt whatsoever.
The connection of the BMP180 is simple Vcc to Vcc, Ground to ground, SDA to SDA and SCL to SCL. On the ESP8266 I am using GPIO0 as SDA and GPIO2 as SCL
ESP8266-01: If you still have to buy this, consider an ESP8266-12. it isn’t much more expensive and has more pins. Some modules like Wemos D1 even have an USB added
BMP180: If you still need to buy this, consider a BMP280 that is more precise and cheaper or even a BME280 that can also measure humidity. The BME280 is slightly more expensive. I have not been able to test it, but the BMP280 needs a different library and the BME280 needs a different library.
Adafruit BMP_085 Library: This is the ‘Old’ library. Adafruit has a newer one that I found less pleasant to work with and I seem to remember it also needed the Adafruit ‘unified sensor’ library. There is a Sparkfun library as well
The female and male headers and the piece of veroboard are used to make a breadboard friendly adapter for the ESP8266-01
If you dont have a 3.3 Volt USB-TTL adapter yet and are only planning to work with the ESP8266-01, consider this handy device.
The OLED is just used to display the values. Ofcourse you may use an LCD as well, or forget about the display and send it off to a website or Thingspeak
The connections couldn’t be simpler:
I used GPIO0 as SDA and GPIO2 as SCL. The BMP180 and OLED have the pin nominations clearly stamped on them so you only need to connect as follows All Vcc-s together All Grounds together BMP180 and OLED SDA pins to GPIO0 BMP180 and OLED SCL pins to GPIO2 Finally connect the CH_PD pin with Vcc Make sure you identify the proper pins. Your modules may have a pin sequence that differs from mine. As my BMP180 module already had pull up resistors, I didnt need to add those. If your module does not, add 4k7 resistors as pull up on the SDA and SCL lines.
I presume you do know how to program the ESP8266. In short:
Connect Tx<->Rx (meaning the Tx of your ESP to the Rx of your USB-TTL converter)
Connect Rx<->Tx
Connect CH_PD<->Vcc
Connect GPIO0 <->Grnd
Connect Vcc <-> Vcc (Only if you have a 3.3 volt Vcc)
Connect Grnd <-> Grnd
#include <Wire.h>
#include <Adafruit_BMP085.h>
#include "SSD1306.h"
Adafruit_BMP085 bmp;
SSD1306 display(0x3c, 0, 2);
void setup() {
Serial.begin(9600);
Wire.pins(0, 2);
Wire.begin(0, 2);
if (!bmp.begin()) {
// Serial.println("No BMP180 / BMP085");// we dont wait for this
// while (1) {}
}
display.init();
display.flipScreenVertically();// I turn the screen coz easier in my setup
display.setFont(ArialMT_Plain_10);
}
void loop() {
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_16);
String t = "T=" + String(bmp.readTemperature()) + " *C";
String p = "P=" + String(bmp.readPressure()) + " Pa";
String a = "A=" + String(bmp.readAltitude(103000)) + " m";// insert pressure at sea level
String s= "S="+ String(bmp.readSealevelpressure(30))+" Pa";// or insert your atltitude
display.drawString(0, 10, t);
display.drawString(0, 24, p);
display.drawString(0, 38, a);
//display.drawString(0, 38, s);// if you want to calculate sealevelpressure
// write the buffer to the display
display.display();
delay(2000);
}
As you can see, one of the parameters is the Altitude. This value is only correct if you supply the program with the pressure at sea level for your location. As that pressure can change, it is not of real value. On the other hand, as the altitude or elevation is much less likely to change (unless you are mobile), you could provide the known elevation for your location and then back calculate the pressure at sea level. Or… given the fact you have a WiFi processor available, read the current pressure at sea level from a web site and then calculate your altitude.
The temperature readings of the BMP180, though precise, still can be off (too high) if the sensor is mounted too close to the ESP8266. Whether this is a result of the HF radio waves or just direct heat of the processor is still being argued. I think it is the latter