#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>
#include <EasyButton.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define LCD_ROWS 4
#define LCD_COLS 20
LiquidCrystal_I2C lcd(0x27, LCD_COLS, LCD_ROWS);
#define ONE_WIRE_BUS 12 // Pin Arduino a cui colleghiamo il pin DQ del sensore
OneWire oneWire(ONE_WIRE_BUS); // Imposta la connessione OneWire
DallasTemperature sensor(&oneWire); // Dichiarazione dell'oggetto sensor
#define PREV 10
#define ENTER 9
#define NEXT 8
#define MIN_LEVEL 7 // Galleggiante??
#define RESERVE_PUMP 6
#define RECIRC_PUMP 5
#define HEATER_OUT 4
#define RELE_ON LOW
#define RELE_OFF HIGH
#define RUN_LED 13
// Evitiamo lo attacca/stacca della pompe in prossimità del setpoint
// mantenendole attive per un tempo minimo;
#define MIN_PUMP_TIME 3000
bool runJob = true;
bool setupMode = false;
float setpoint1 = 45.0, setpoint2 = 40.0;
float waterTemp;
uint16_t outputHeat;
/*
* Per arrivare più rapidamente al setpoint, mantiene l'uscita al 100%
* fintanto che la temperatura letta è < kOn * Setpoint
* Analogamente imposta l'uscita a 0% se > kOff * Setpoint
*/
float kOn = 0.85;
float kOff = 0.01;
#include "lcdMenu.h"
#include "buttons.h"
/* Per la regolazione della temperatura viene usato
un algoritmo PI con approccio "Time Proportional Control"
https://www.controleng.com/articles/time-proportional-control-more-from-an-on-off-switch/
*/
uint32_t windowSize = 5000;
float Kp = 600 ; float Ki = 2 ;
////////////////////////////////// PI (PID) /////////////////////////////////
uint16_t Incremental_PI( float actual, float target ) {
static float last_error, output;
float error = actual - target; //Calculate the deviation
output += Kp * (error - last_error) + Ki * error; //Incremental PI
last_error = error; //Save as the last deviation
output = constrain (output, 500, windowSize);
return (uint16_t) windowSize - output;
}
void setup() {
digitalWrite(RECIRC_PUMP, RELE_OFF);
digitalWrite(RESERVE_PUMP, RELE_OFF);
digitalWrite(HEATER_OUT, RELE_OFF);
pinMode(RECIRC_PUMP, OUTPUT);
pinMode(RESERVE_PUMP, OUTPUT);
pinMode(HEATER_OUT, OUTPUT);
pinMode(RUN_LED, OUTPUT);
pinMode(MIN_LEVEL, INPUT_PULLUP);
Serial.begin(115200);
Serial.println();
lcd.init();
lcd.backlight();
lcd.clear();
getSavedValues();
setuMenuPages();
setupButtons();
delay(2000);
if (runJob) {
lcdMenu.setCurrentPage(P_RUN_JOB);
}
}
void loop() {
// Update buttons state
readButtons();
// Set mode (long press ENTER button)
if (btnEnter.pressedFor(2000)) {
setupMode = !setupMode;
if (setupMode) {
getSavedValues();
lcd.clear();
lcdMenu.setCurrentPage(P_SETUP);
}
else {
if (runJob) {
lcd.clear();
lcdMenu.setCurrentPage(P_RUN_JOB);
}
else
lcdMenu.setCurrentPage(P_MAIN);
}
delay(1000);
}
// Start heating
if (btnNext.pressedFor(1000)) {
runJob = true;
lcdMenu.setCurrentPage(P_RUN_JOB);
EEPROM.put(0, runJob);
delay(500);
}
// Force STOP heating
if (btnPrev.pressedFor(1000)) {
runJob = false;
lcdMenu.setCurrentPage(P_STOP_JOB);
digitalWrite(HEATER_OUT, RELE_OFF);
digitalWrite(RECIRC_PUMP, RELE_OFF);
EEPROM.put(0, runJob);
delay(500);
}
switch (lcdMenu.getCurrentPageID()) {
case P_STOP_JOB:
case P_RUN_JOB:
update_run();
break;
case P_SETUP:
if (btnPrev.wasPressed()) {
lcdMenu.setCurrentPage(P_MAIN);
}
if (btnNext.wasPressed()) {
lcdMenu.nextPage();
}
break;
case P_SET1:
if (updateVar(setpoint1, 0.5))
lcdMenu.display();
break;
case P_SET2:
if (updateVar(setpoint2, 0.5))
lcdMenu.display();
break;
case P_START_JOB:
if (btnNext.wasPressed()) {
Serial.println(F("Start JOB"));
lcdMenu.setCurrentPage(P_RUN_JOB);
saveValues();
runJob = true;
}
break;
}
// Leggo la temperaura dell'acuqa
getTemperature();
// Se il sistema è attivo, regolo la temperatura dell'acqua
if (runJob) {
setOutputs();
}
digitalWrite(RUN_LED, runJob);
}
void getSavedValues() {
int eeAdr = 0;
EEPROM.get(eeAdr, runJob);
eeAdr += sizeof(float);
// Check if value was stored (otherwise it will be equal to 0.0)
float val;
EEPROM.get(eeAdr, val);
// Load EEPROM stored values
if (val >= 0.1) {
EEPROM.get(eeAdr, setpoint1);
eeAdr += sizeof(float);
EEPROM.get(eeAdr, setpoint2);
}
}
void saveValues() {
int eeAdr = 0;
EEPROM.put(eeAdr, runJob);
eeAdr += sizeof(float);
EEPROM.put(eeAdr, setpoint1);
eeAdr += sizeof(float);
EEPROM.put(eeAdr, setpoint2);
}
// Leggo la temperaura dell'acuqa (una volta al secondo)
void getTemperature() {
static uint32_t readTime;
if (millis() - readTime > 1000 ) {
readTime = millis();
// richiesta lettura temperatura
sensor.requestTemperatures();
waterTemp = sensor.getTempCByIndex(0);
// Aggiorno l'algoritmo PI con il nuovo valore
if (waterTemp < (setpoint1 * kOn))
outputHeat = windowSize;
else if (waterTemp > (setpoint1 + (setpoint1 * kOff)))
outputHeat = 0;
else
outputHeat = Incremental_PI(waterTemp, setpoint1);
Serial.print("Temp: ");
Serial.print(waterTemp);
if (runJob) {
Serial.print(", out: ");
Serial.print(outputHeat);
}
Serial.println();
}
}
void setOutputs() {
static uint32_t recirculatorTime;
static uint32_t reserveTime;
// Reset della finestra temporale dell'algoritmo "Time Proportional Control"
static uint32_t windowStart = millis();
if (millis() - windowStart > windowSize)
windowStart = millis();
// Aggiorno l'uscita della resistenza in funzione del valore PI
// (attivo il riscaldatore solo se c'è livello minimo)
if (digitalRead(MIN_LEVEL))
digitalWrite(HEATER_OUT, outputHeat < (millis() - windowStart));
else
digitalWrite(HEATER_OUT, RELE_OFF);
// Se la temperaura letta supera il setpoint attivo la pompa di ricircolo
if (millis() - recirculatorTime > MIN_PUMP_TIME ) {
recirculatorTime = millis();
digitalWrite(RECIRC_PUMP, waterTemp < setpoint2);
}
// Se il galleggiante si scopre, attiva la pompa
if (millis() - reserveTime > MIN_PUMP_TIME ) {
reserveTime = millis();
digitalWrite(RESERVE_PUMP, digitalRead(MIN_LEVEL));
}
digitalWrite(RUN_LED, runJob);
}