Files
hot-water-control/HotWaterController/HotWaterController.ino
T
2022-09-24 09:45:20 -07:00

261 lines
6.0 KiB
Arduino

// https://gist.github.com/jdneo/43be30d85080b175cb5aed3500d3f989
// Timer Interrupt example for Adafruit Feather M0.
// Callback function can be written in TC3_Handler().
// better comments with TC5
// https://gist.github.com/nonsintetic/ad13e70f164801325f5f552f84306d6f
// Handlers declared here
// https://github.com/arduino/ArduinoCore-samd/blob/master/cores/arduino/cortex_handlers.c
// (void*) TC3_Handler, /* 18 Basic Timer Counter 0 */
// void TC3_Handler (void) __attribute__ ((weak, alias("Dummy_Handler")));
// to be defined by user
// VUSB -> Hdr-1
// D12 -> Hdr-2
// Hdr-3
// D11 <- Hdr-4
// Hdr-5
// GND -> Hdr-6
#define DEBUG false
#define DEBUGSTATE(s) {\
Serial.println(); \
Serial.print("S"); \
Serial.println(s); \
Serial.println(); \
}
#define LED_PIN 13
#define PUMP_PIN 12
#define PUMP_ON digitalWrite(PUMP_PIN,LOW)
#define PUMP_OFF digitalWrite(PUMP_PIN,HIGH)
#define FLOW_PIN 11 // this is INT0
#define FLOWING digitalRead(FLOW_PIN)==LOW
#define NOTFLOWING digitalRead(FLOW_PIN)==HIGH
// 2/3 = 66% duty cycle
#define PUMP_RUN_TIME 1200 // ticks. run for 2min = 120sec after flowstarted
#define PUMP_OFF_TIME 600 // ticks. keep off 1min = 60sec before re-enabling
#define FLOW_THLD 20 // ticks @ RATE 2 seconds
#define RATE 10 // Hz == 100mS ticks
// declare Timer1_ISR functions
void startTimer(int frequencyHz);
void setTimerFrequency(int frequencyHz);
void TC3_Handler();
bool isLEDOn = false;
bool notRun = true;
/*
* ticks are each 100mS so uint is 6553.5 seconds = 109.2 min
* ticktime 32 bit 0.1sec rollover is 13 years
* 16 bits would be 109 minutes
* make all timers unsigned long to avoid comparison wrap around
*/
unsigned long totalflow = 0;
unsigned long flowstarted = 0;
unsigned long flowstopped = 0;
unsigned long pumpstarted = 0;
unsigned long pumpstopped = 0;
boolean flow = false;
boolean pumpOn = false;
boolean pumpRan = false;
boolean pumpEnabled = true;
// FSM
enum FSM {S0,S1,S2,S3};
FSM state=S0;
volatile boolean intflag1 = false; // external interrupt (event)
volatile unsigned long ticktime = 0;
volatile unsigned int ticknum = 0;
// external pin interrupt handler
void flowstate() {
if (!intflag1) {
intflag1 = true;
}
}
// called from TC3 ISR...
void Tick(void) {
ticknum++;
ticktime++;
} // end Tick
void toggleLED(){
digitalWrite(LED_PIN, isLEDOn);
isLEDOn = !isLEDOn;
}
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(PUMP_PIN, OUTPUT);
pinMode(FLOW_PIN, INPUT_PULLUP);
attachInterrupt(FLOW_PIN, flowstate, FALLING);
startTimer(RATE);
if (DEBUG) {
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
}
} // end setup()
/*
* S0: wait for flow threshold before turning pump on
* S1: wait for pump off
* S2: wait for flow to stop
* S3: wait for hysteresis time before cycling again
*
* When the pump stops and there is still flow (demand), don't restart the pump until flow stops + offtime
* This avoids cycling the pump during a shower.
*/
// loop() is called repeatedly
void loop () {
switch (state){
case S0:
if (flow && pumpEnabled) {
state = S1;
if (DEBUG) DEBUGSTATE(state)
if (DEBUG) Serial.print("pump start @ ");
if (DEBUG) Serial.println(ticktime);
pumpOn = true;
pumpEnabled = false;
pumpstarted = ticktime;
}
break;
case S1:
if (!pumpOn) {
state = S2;
if (DEBUG) DEBUGSTATE(state)
}
break;
case S2:
if (!flow) { // wait for flow to stop; don't need to keep pumping!
state = S3;
if (DEBUG) DEBUGSTATE(state)
//totalflow = Tflow;
}
break;
case S3:
// post totalflow via MQTT
if (pumpEnabled) { // limit duty cycle
state = S0;
if (DEBUG) DEBUGSTATE(state)
}
break;
} // FSM
// record flowstart time
if (intflag1 && (flowstarted == 0)) {
flowstarted = ticktime; // only set flowstarted time once!
}
// process interrupt (after setting flowstart time)
if (intflag1) {
if (ticktime > flowstarted + FLOW_THLD) { // delay to avoid bounces generating more interrupts
if (FLOWING) {
flow = true; // set flow state 2000mS after interrupt IF still flowing
} else { // flow didn't last longer than the threshold!
flow = false;
flowstarted = 0; // reset
}
intflag1 = false; // clear interrupt.
if (DEBUG) Serial.println("flow interrupt");
}
}
// record flowstop time
if ((flow == true) && (NOTFLOWING) && (flowstopped == 0)) {
flowstopped = ticktime; // only set flowstopped time once!
if (DEBUG) Serial.println("flow stop");
}
// flow stop
if ((flow == true) && (flowstopped > 0)){
if (ticktime > flowstopped + 20){ // flow state changes 2 sec after flowstopped
flow = false;
flowstopped = 0;
}
}
// pump stop
if (pumpOn){ // pump run time
if (ticktime > (pumpstarted + PUMP_RUN_TIME)) {
if (DEBUG) Serial.print("pump stop @ ");
if (DEBUG) Serial.println(ticktime);
if (DEBUG) Serial.println(pumpstarted);
pumpOn = false;
flowstarted = 0;
pumpstarted = 0;
pumpstopped = ticktime;
}
}
// pump enable
if (pumpstopped > 0){
if (ticktime > (pumpstopped + PUMP_OFF_TIME)) {
pumpEnabled = true;
pumpstopped = 0;
}
}
// motor control
if (pumpOn) {
PUMP_ON;
} else {
PUMP_OFF;
}
// 1 Hz debug events
if (ticknum >= 10) { // 1 sec
ticknum = 0;
toggleLED();
//Serial.println(ticktime);
if (DEBUG) {
Serial.print(state);
Serial.print(" ");
Serial.print(intflag1);
Serial.print(flow);
Serial.print("\t");
Serial.print(flowstarted);
Serial.print("\t");
Serial.print(flowstopped);
Serial.print("\t");
Serial.print(pumpEnabled);
Serial.print(pumpOn);
Serial.println();
}
}
} // end loop()