#include <Wire.h>
#include <Adafruit_ADS1015.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <WiFiClient.h>
#include <WiFiServer.h>
#include <WiFiUdp.h>
#include "SimpleTimer.h" // multiple, polled timer events without using a hardware (AVR) timer, but (much) less precision wrt the interval
#include "MCP320x.h"
#define DebugSerial Serial
#define DEBUG // enable BLYNK_PRINT and a corresponding serial output to capture the stream
Adafruit_ADS1015 ads; /* Use thi for the 12-bit version */
/*
probe and related thermistor defines
Probe 1 must be connected to A0 and then sequentially from there
*/
#define PROBE1_TEMP V0 // THIS GROUP MUST BE SEQUENTIAL
#define PROBE2_TEMP V1
#define PROBE3_TEMP V2
#define PROBE4_TEMP V3
#define PROBE1_SETPT V4 // THIS GROUP MUST BE SEQUENTIAL
#define PROBE2_SETPT V5
#define PROBE3_SETPT V6
#define PROBE4_SETPT V7
#define PROBE1_LED V8
#define PROBE2_LED V9
#define PROBE3_LED V10
#define PROBE4_LED V11
#define CONNECTED_LED V12
#define P1_CONNECTED V13
#define P2_CONNECTED V14
#define P3_CONNECTED V15
#define P4_CONNECTED V16
#define CLONE_P1 V17
/* ================================= SPI A to D converter MPC3204 ====================== */
#define ADC_ZERO_THRESHOLD 10 // threshold value for a zero reading (capacitance?)
#define ADC_SAMPLES 5 // number of sames to taken when reading analog ports for temp values
MCP320x MCP(SS); // see pins_arduino.h for ESP8266/Huzzah - using only 1 parameter forces SPI MODE (uses SPI library)
/* ================================= sketch ======================== */
/*
Steinhart-Hart coefficients obtained empirically from the calibration utility for the Maverik ET-73 200k ohm probe
See https://en.wikipedia.org/wiki/Steinhart-Hart for reference and readTemp() below
*/
#define A 0.001433954477
#define B 0.000076877069
#define C 0.000000537597
/*
other defines
*/
#define PROBE_COUNT 4 // number of temperature probes
#define ADC_RESOLUTION 4095 // ADC resolution: depends on the platform. Mega/Uno is 10 bits, so (2^10)-1 = 1023. This is for the 12-bit MPC3204 SPI ADC
/*
data associated with each temperature probe
*/
typedef struct {
uint16_t Setpoint; // setpoint
uint16_t SavedSetpoint; // copy to save for a disconnected probe
uint16_t CurrentTemp; // current temperature.
float dCurrentTemp; // current temperature.
uint8_t SetpointReached :1; // if set temperature has been reached
uint8_t Present :1; // true if probe is connected
uint8_t TempChanged :1; // true if temp has changed - throttles updates to Blynk dashboard
} ProbeType;
ProbeType Probe[PROBE_COUNT];
uint32_t seriesResistor[PROBE_COUNT] = { 197900, 200500, 197800, 198800 }; // Measured series resistor value in ohms. Must be the SAME as the nominal 25deg C resistance of the probe
/* ================================================== Timer functions =========================================================================== */
// stagger the intervals to allow time for the server messages to complete. Also, use primes in case that helps.
#define SETPT_DELAY 1609 // how often setpoints are checked in msec
#define PROBE_DELAY 1009 // how often probe temperatures are read in msec
#define TEMP_DELAY 1289 // how often dashboard temperature display values are pushed
#define LCD_DELAY 3049 // how often physical LCD is updated (slow enough to minimize flicker)
#define PUSH_DELAY 300000 // how often push notifications for setpoint reached are done in msec
SimpleTimer timer;
/* ========================================================== GENERAL ================================================================================ */
bool redrawScreen = false; // set when a screen redraw is needed
bool firstConnect = true; // true after we have connected once
// ThingSpeak Settings
char thingSpeakAddress[] = "api.thingspeak.com";
String writeAPIKey = "yourkeyhere";
const int updateThingSpeakInterval = 30 * 1000; // Time interval in milliseconds to update ThingSpeak (number of seconds * 1000 = interval)
void setup ( void ) {
#if defined(DEBUG)
DebugSerial.begin(9600);
while ( !DebugSerial );
#endif // DEBUG
MCP.setMCPConfig(MCP_SINGLE, MCP_ALL_PORTS); // single mode for all MPC3204 pins
/*
Program flow:
Event Handler Dashboard updated
----------------------------- ------------------- ----------------
Setpoint set in dashboard BLYNK_WRITE() YES
Probe temperature change updateProbes()
Probe removed updateProbes() (YES)
Probe re-inserted updateProbes() (YES)
Dashboard Temp display update updateTemps() YES
Setpoint reached updateSetpoints()
Dashboard setpt LED updated updateSetpoints() YES
Setpoint push notices sendPushNotices()
LCD updated updateLCD()
*/
timer.setInterval(SETPT_DELAY, updateSetpoints);
timer.setInterval(PROBE_DELAY, updateProbes);
timer.setInterval(LCD_DELAY, updateLCD);
timer.setInterval(PUSH_DELAY, updateThingspeak);
WiFi.mode(WIFI_STA);
ads.begin();
}
/*
Check current values to see if probe setpoints have been reached
Note that for the setpoint flag to be set, it must be present. Thus, we don't need to check this in other functions
Also updates Blynk dashboard setpoint LEDs
*/
void updateSetpoints ( void ) {
for ( uint8_t i = 0; i < PROBE_COUNT; i++ ) {
if ( Probe[i].Present ) {
if ( (Probe[i].Setpoint > 0) && (Probe[i].CurrentTemp >= Probe[i].Setpoint) ) {
Probe[i].SetpointReached = true;
} else {
Probe[i].SetpointReached = false;
}
}
}
}
/*
Derive the resistance of the probe to use in the Steinhart-Hart eqution.
We can do this given the known value of the series resistor in the fixed part of the voltage divider
and the fact that since V = I*R the voltage drop on each resistor is directly proportional to voltage.
Thus, we can use a ratio to determine the resistance of the probe without using the reference voltage which can vary considerably from 5V.
Also, this allows us to use the same code on 5V or 3.3V platforms :-) as well as just working with the digitized reading without any voltage conversions.
NOTE: both float and double on Arduino are 32-bit values. No difference between the two.
*/
float calcResistance ( const uint8_t pin ) {
float reading = 0;
for ( uint8_t i = 0; i < ADC_SAMPLES; i++ ) {
reading += MCP.readChannel(pin);
}
if ( reading >= ((ADC_RESOLUTION * ADC_SAMPLES) - 20)) {
// probe disconnected
return 0;
} else {
reading /= (float)ADC_SAMPLES;
reading = (ADC_RESOLUTION / reading) - 1;
return (seriesResistor[pin] / reading);
}
}
/*
get the normalized raw value of the probe on the given pin for debug
note that a disconnected probe will return 0
*/
uint16_t probeRaw ( const uint8_t pin ) {
uint16_t reading = 0;
for ( uint8_t i = 0; i < ADC_SAMPLES; i++ ) {
reading += MCP.readChannel(pin);
}
reading /= ADC_SAMPLES;
return reading;
}
/*
read the temperature from the given analog pin and return temperature in degrees F
uses the Steinhart-Hart equation
Note: when using constants in float calculations, make sure there is a decimal point
*/
float readTemp ( const uint8_t pin ) {
float R, kelvin;
R = calcResistance(pin);
if ( R == 0 ) {
// probe disconnected
return 0;
} else {
kelvin = 1.0/(A + B*log(R) + C*pow(log(R), 3.0));
return ((kelvin * (9.0/5.0)) - 459.67); // K to F conversion
}
}
/*
Get current probe temps
Check for probes that have been disconnected or reconnected
- update Blynk dashboard if probe status has changed
*/
void updateProbes ( void ) {
for ( uint8_t i = 0; i < PROBE_COUNT; i++ ) {
uint16_t temp = (uint16_t)readTemp(i);
Probe[i].dCurrentTemp = readTemp(i);
// get probe temps & handle disconnects/reconnects
if ( temp != Probe[i].CurrentTemp ) {
Probe[i].CurrentTemp = temp;
Probe[i].TempChanged = true;
}
if ( (Probe[i].CurrentTemp == 0) && Probe[i].Present ) {
/*
probe disconnected:
push dashboard setpoint slider value
temperature will be updated with the next BLYNK_READ of the probe temp
*/
Probe[i].Present = false;
Probe[i].Setpoint = 0;
Probe[i].SetpointReached = false;
redrawScreen = true;
} else if ( (Probe[i].CurrentTemp > 0) && !Probe[i].Present ) {
// reinserted - reset to previous setpoint
Probe[i].Present = true;
Probe[i].Setpoint = Probe[i].SavedSetpoint;
redrawScreen = true;
}
}
}
/*
Update the physical LCD display
This happens even when Blynk is offline
Periodically display an offline message when Blynk is offline
*/
void updateLCD ( void ) {
static uint8_t offlineCount = 0;
if ( redrawScreen ) {
redrawScreen = false;
}
for(int i = 0;i < PROBE_COUNT; i++ )
{
DebugSerial.print("field");
DebugSerial.print(i+5);
DebugSerial.print("=");
DebugSerial.print(ads.readADC_SingleEnded(i) * 0.0255);
DebugSerial.println("&");
}
}
int status = WL_IDLE_STATUS;
void updateThingspeak ( void ) {
if ( status != WL_CONNECTED) {
DebugSerial.println("Couldn't get a wifi connection");
WiFi.printDiag(Serial);
status = WiFi.begin("SSID", "mypw");
}
else {
WiFiClient client;
while(!client.connect("api.thingspeak.com", 80)) {
DebugSerial.print(".");
}
String temps = readTemps();
// Make a HTTP request:
client.print("POST /update HTTP/1.1\n");
client.print("Host: api.thingspeak.com\n");
client.print("Connection: close\n");
client.print("X-THINGSPEAKAPIKEY: "+writeAPIKey+"\n");
client.print("Content-Type: application/x-www-form-urlencoded\n");
client.print("Content-Length: ");
client.print(temps.length());
client.print("\n\n");
client.print(temps);
client.stop();
client.flush();
DebugSerial.println(temps);
}
}
String readTemps()
{
String temp;
for(int i = 0;i < PROBE_COUNT; i++ )
{
temp += "field";
temp += (i+1);
temp += "=";
temp += Probe[i].dCurrentTemp;
temp += "&";
}
for(int i = 0;i < PROBE_COUNT; i++ )
{
temp += "field";
temp += (i+5);
temp += "=";
temp += ads.readADC_SingleEnded(i) * 0.0255;
temp += "&";
}
return temp;
}
/*
LOOP
*/
void loop ( void ) {
yield();
timer.run();
}