//Enumerated types
//START_UP - state entered on powerup with fans running continuously to confirm that power is supplied
//INITIAL_RUNNING_PERIOD - state entered from STARTUP when the system be jump to heating or cooling immediately when appropriate water temperatures at provided. avoids confusion during testing.
//HEATING_ACTIVE - after HEATING_STATE_NOT_DETERMINED has finished this state is only entered after a long enough period of hot water to ensure it is not a DHW cycle end hot water dump
//HEATING_NOT_ACTIVE - entered when there has not been any heating for a significant amount of time. In this state it will do cooling it cold water is detected
enum heatingStates {START_UP, INITIAL_RUNNING_PERIOD, HEATING_ACTIVE, HEATING_NOT_ACTIVE};
//Simulate flags
#define SIMULATE //Only declare this constant if in debug mode
//#define DEBUG_PROCESS_TEMPERATURES // only declare when debug output is needed from read_temperature routine
#define DEBUG_RUN_STATE_MACHINE // only declare this when debug output is needed from runStateMachine
#define DEBUG_CONSOLE // Only declare if main console output is required
//#define DEBUG_SWITCHES // Only declare if output is needed from switch
const int CONSOLE_STATUS_UPDATE_INTERVAL = 3; //Interval in seconds between updates to the console status
//Temperature constants
//Defines the 4 values for target room temperatures set using the DIP switches, NONE means there is no defined temperature, LOW, MEDIUM and HIGH have values. in 0.1 degree intervals.
const int TARGET_ROOM_TEMPERATURE_NONE = -10; //Switches 1 and 2 set to 0 and 0.
const int TARGET_ROOM_TEMPERATURE_LOW = 180; //Switches 1 and 2 set to 0 and 1 (in tenths of a degree)
const int TARGET_ROOM_TEMPERATURE_MEDIUM = 200; //Switches 1 and 2 set to 1 and 0 (in tenths of a degree)
const int TARGET_ROOM_TEMPERATURE_HIGH = 220; //Switches 1 and 2 set to 1 and 1 (in tenths of a degree)
const int MINIMUM_VALID_TEMPERATURE = 0; //A reading below this value is treated as a faulty reading on non-fitted sensor;
const int FAULTY_TEMPERATURE_READING = -1; // This is ok as an example faulty temperature
//These next two values set the hysteresis values for setting heat_requested to true or false. It is set to true when water temperature is more than WATER_TEMPERATURE_HEATING_START_OFFSET higher than
//room temperature and is set to false when it falls below WATER_TEMPERATURE_HEATING_FINISH_OFFSET above measured room temperature. Given in tenths of degrees.
const int WATER_TEMPERATURE_HEATING_START_OFFSET = 30; // in tenths of a degree
const int WATER_TEMPERATURE_HEATING_FINISH_OFFSET = 10; // in tenths of a degree
//These next two values set the hysteresis values for setting cooling_requested to true or false. It is set to true when water temperature is more than WATER_TEMPERATURE_COOLING_START_OFFSET lower than
//room temperature and is set to false when measured water temperture is less than WATER_TEMPERATURE_COOLING_FINISH_OFFSET below measured room temperature
const int WATER_TEMPERATURE_COOLING_START_OFFSET = 30; // in tenths of degrees
const int WATER_TEMPERATURE_COOLING_FINISH_OFFSET = 10; // in tenths of degrees
//Fan speed constants, defines varius fan speeds where 100 is the maximum physical fan speed and 1 is the lowest. Actual values will be determined based on output and on sound level
const int FAN_SPEED_MINIMUM = 10; //Speed must be above this value to be valid.
const int FAN_SPEED_MAXIMUM = 200; //Speed must be below this value to be valid.
const int FAN_SPEED_LOW = 40; //scored out of 100 where 1 is slowest and 250 is fan runnning full speed
const int FAN_SPEED_HIGH = 80; //scored out of 100 where 1 is slowest and 250 is fan runnning full speed
//timer constants
//Given the starting values for various timers in seconds, wnich then count down
#ifndef SIMULATE
//Setting during normal running operation, rather than when debuging or simulating
const int START_UP_TIMER_INITIAL_VALUE = 5; //number of seconds it stays in startup state after power up with the fans running (eg 5 = 5 seconds)
const int INITIAL_RUNNING_PERIOD_TIMER_INITIAL_VALUE = 12 * 60 * 60; // number of seconds unit will respond promptly to heating request or cooling request frrom shortly after startup (eg 12 * 60 * 60 = 12 hours)
const int SWITCH_TO_HEATING_STATE_INITIAL_VALUE = 30 * 60; //Time in seconds that heating needs to run for before entering heating active state (eg 30 minutes = 30 * 60).
const int SWITCH_TO_NON_HEATING_STATE_INITIAL_VALUE = 72 * 60 * 60 ; //Time in seconds that heating needs to not be run for before entering non_heating state (eg 72 hours = 72 * 30 * 60)
#endif
#ifdef SIMULATE
//Settings when running in SIMULATE / debug mode
const int START_UP_TIMER_INITIAL_VALUE = 5; //number of seconds it stays in startup state after power up with the fans running (eg 5 = 5 seconds)
const int INITIAL_RUNNING_PERIOD_TIMER_INITIAL_VALUE = 10; // number of seconds unit will respond promptly to heating request or cooling request frrom shortly after startup (eg 12 * 60 * 60 = 12 hours)
const int SWITCH_TO_HEATING_STATE_INITIAL_VALUE = 10 ; //Time in seconds that heating needs to run for before entering heating active state (eg 30 minutes = 30 * 60).
const int SWITCH_TO_NON_HEATING_STATE_INITIAL_VALUE = 10 ; //Time in seconds that heating needs to not be run for before entering non_heating state (eg 72 hours = 72 * 30 * 60)
#endif
//Arduino specific constants
const int FAN_PWM_PIN = 16; // 16 corresponds to GPIO16
const int SWITCH_1_PIN = 34; //Switches for simulation only
const int SWITCH_2_PIN = 35; //Switches for simulation only
const int SWITCH_3_PIN = 32; //Switches for simulation only
const int FAN_SPEED_SWITCH_INPUT = 1; //bit set high in position of fan speed input
const int TARGET_ROOM_TEMPERATURE_SWITCHES = 6; //bits set high in position for temperature selection inputs
const int FAN_PWM_FREQUENCY = 1000; // PWM frequency
const int FAN_PWM_RESOLUTION = 8; //The resolution of this Ardunio timer
const int WATER_TEMPERATURE_A_ADC_CHANNEL = 14;
const int WATER_TEMPERATURE_B_ADC_CHANNEL = 25;
const int ROOM_TEMPERATURE_LOCAL_ADC_CHANNEL = 26;
const int ROOM_TEMPERATURE_REMOTE_ADC_CHANNEL = 27;
const float THERMISTOR_READING_CALIBRATION = 2; // The amount to be added to the reading to adjust for poor algorithm
//Temperature variables
int waterTemperatureA = 0; //Temperature measure from the A water temperature probe after going past low pass filter
int waterTemperatureB = 0; //Temperature measure from the B water temperature probe after going past low pass filter
int roomTemperature = 0; //Measured temperature of room, using either of the possible sensors
int roomTemperatureLocal = 0; //Room temperature as measured by sensor built into the case
int roomTemperatureRemote = 0; //Room temperature as measured by sensor connected to unit by a flying lead
int higherWaterTemperature = 0; //The higher of water temperature A and B
int lowerWaterTemperature = 0; //The lower of water temperature A and B
int targetRoomTemperature = TARGET_ROOM_TEMPERATURE_NONE; //The desired room temperature in heating, or a null value if not temperature control is needed.
//Timer variables
int startupTimer = 0; // Time in seconds that the fans run regardless of water temperature after power up
int initialRunningPeriodTimer = 0; // The time in seconds the unit will stay in heating_state_not_determined before switching to 'non_heating-season'.
int switchToHeatingTimer = 0; //Time in seconds after a heating request before unit switches to heating_state.
int switchToNonHeatingTimer = 0; //Time in seconds after a cooling request before unit switched to non-heating-state.
//Boolean variables
bool heatingRequested = false;
bool coolingRequested = false;
bool heatingAvailable = false;
bool coolingAvailable = false;
bool heatingAllowed = false;
bool coolingAllowed = false;
bool runFans = false;
//Other variables
int requestedFanSpeed = 25; //range 0-100 where 100 is full speed for the fan
int fanSpeed = 0; //Current fan speed
int dipSwitchesState = 0; //Current value of input switches
//Enumerated variables
enum heatingStates heatingState;
//Arduino only variables
unsigned long localMilliseconds; //local run time in milliseconds (which wraps in 50 days)
unsigned long localSeconds; //local time in seconds (which wraps every 136 years)
unsigned long lastLocalMilliseconds; // used to avoid wrapping problems for mill
int consoleUpdateCounter = 0; // counter in seconds for how often console update is made
void setup() {
//Arduino initialisation
Serial.begin(115200);
localMilliseconds=millis();
lastLocalMilliseconds = localMilliseconds;
localSeconds = localMilliseconds / 1000;
// configure LED PWM functionalitites
ledcAttach(FAN_PWM_PIN, FAN_PWM_FREQUENCY, FAN_PWM_RESOLUTION);
//ledcAttachPin(, FAN_PWM_CHANNEL);
pinMode(SWITCH_1_PIN, INPUT);
pinMode(SWITCH_2_PIN, INPUT);
pinMode(SWITCH_3_PIN, INPUT);
//General initialisation
requestedFanSpeed = 0;
heatingState = START_UP;
startupTimer = START_UP_TIMER_INITIAL_VALUE; //Set this to time the startup period in the state machine
Serial.printf("Starting 2\n");
}
void loop(){
bool secondTick = false;
localMilliseconds = millis();
if (localMilliseconds > lastLocalMilliseconds + 1000) {
localSeconds += 1;
lastLocalMilliseconds += 1000;
secondTick = true;
}
if (secondTick) {
secondTick = false;
// Serial.println("tick");
//Serial.printf("in\n");
updateDisplay();
//Serial.printf("hi\n");
readSwitches();
//Serial.printf("hi A5\n");
processTemperatures();
//Serial.printf("hi 3\n");
runStateMachine();
//Serial.printf("hi 4\n");
// outputFanSpeed();
}
}
void readSwitches() {
int switch1State = digitalRead(SWITCH_1_PIN);
int switch2State = digitalRead(SWITCH_2_PIN);
int switch3State = digitalRead(SWITCH_3_PIN);
dipSwitchesState = switch3State * 4 + switch2State * 2 + switch1State;
//Set requestedFanSpeed based on fan speed input
if ((dipSwitchesState & FAN_SPEED_SWITCH_INPUT) == 0) {
requestedFanSpeed = FAN_SPEED_LOW;
} else {
requestedFanSpeed = FAN_SPEED_HIGH;
}
//Set targetRoomTemperatureSwitches based on position of DIP switches
int targetRoomTemperatureSwitches = (dipSwitchesState & TARGET_ROOM_TEMPERATURE_SWITCHES) >> 1;
switch (targetRoomTemperatureSwitches) {
case 0:
targetRoomTemperature = TARGET_ROOM_TEMPERATURE_NONE; //No temperature target
break;
case 1:
targetRoomTemperature = TARGET_ROOM_TEMPERATURE_LOW; //No temperature target
break;
case 2:
targetRoomTemperature = TARGET_ROOM_TEMPERATURE_MEDIUM; //No temperature target
break;
case 3:
targetRoomTemperature = TARGET_ROOM_TEMPERATURE_HIGH; //No temperature target
break;
}
#ifdef DEBUG_SWITCHES
Serial.printf("Dip switches: %2d Fan speed:%2d TargetTemp:%3d\n", dipSwitchesState, requestedFanSpeed, targetRoomTemperature);
#endif
}
void processTemperatures() {
bool temperatureReadingFault = false;
//Read temperatures with low pass filter
const int LOW_PASS_FILTER_VALUE = 4;
waterTemperatureA = waterTemperatureA * (LOW_PASS_FILTER_VALUE - 1) / LOW_PASS_FILTER_VALUE + readTemperatureInput(WATER_TEMPERATURE_A_ADC_CHANNEL) / LOW_PASS_FILTER_VALUE;
waterTemperatureB = waterTemperatureB * (LOW_PASS_FILTER_VALUE - 1) / LOW_PASS_FILTER_VALUE + readTemperatureInput(WATER_TEMPERATURE_B_ADC_CHANNEL) / LOW_PASS_FILTER_VALUE;
roomTemperatureLocal = roomTemperatureLocal * (LOW_PASS_FILTER_VALUE -1) / LOW_PASS_FILTER_VALUE + readTemperatureInput(ROOM_TEMPERATURE_LOCAL_ADC_CHANNEL) / LOW_PASS_FILTER_VALUE;
roomTemperatureRemote = roomTemperatureRemote * (LOW_PASS_FILTER_VALUE -1) / LOW_PASS_FILTER_VALUE + readTemperatureInput(ROOM_TEMPERATURE_REMOTE_ADC_CHANNEL) / LOW_PASS_FILTER_VALUE;
if ((waterTemperatureA > MINIMUM_VALID_TEMPERATURE) and (waterTemperatureB > MINIMUM_VALID_TEMPERATURE))
{
higherWaterTemperature = max (waterTemperatureA, waterTemperatureB);
lowerWaterTemperature = min (waterTemperatureA, waterTemperatureB);
}
else if (waterTemperatureA > MINIMUM_VALID_TEMPERATURE)
{
higherWaterTemperature = waterTemperatureA;
lowerWaterTemperature = waterTemperatureA;
}
else if (waterTemperatureB > MINIMUM_VALID_TEMPERATURE)
{
higherWaterTemperature = waterTemperatureB;
lowerWaterTemperature = waterTemperatureB;
}
else
{ higherWaterTemperature = FAULTY_TEMPERATURE_READING;
lowerWaterTemperature = FAULTY_TEMPERATURE_READING;
temperatureReadingFault = true;
}
//Read and update room temperatures
if (roomTemperatureRemote > MINIMUM_VALID_TEMPERATURE) {
roomTemperature = roomTemperatureRemote;}
else
roomTemperature = roomTemperatureLocal;
//Update heating requested variables if the temperatures are appropriate, otherwise leave them as they are
if (higherWaterTemperature > roomTemperature + WATER_TEMPERATURE_HEATING_START_OFFSET)
heatingAvailable = true;
if (higherWaterTemperature < roomTemperature + WATER_TEMPERATURE_HEATING_FINISH_OFFSET)
heatingAvailable = false;
if (lowerWaterTemperature < roomTemperature - WATER_TEMPERATURE_COOLING_START_OFFSET)
coolingAvailable = true;
if (lowerWaterTemperature > roomTemperature - WATER_TEMPERATURE_COOLING_FINISH_OFFSET)
coolingAvailable = false;
#ifdef DEBUG_PROCESS_TEMPERATURES
Serial.printf("WTA:%2d WTB:%2d WTH:%2d WTL:%2d RTL:%2d RTR:%2d RT:%2d HA:%1d CA:%1d\n",
waterTemperatureA, waterTemperatureB, higherWaterTemperature, lowerWaterTemperature, roomTemperatureLocal, roomTemperatureRemote, roomTemperature, heatingAvailable, coolingAvailable );
#endif
}
void runStateMachine() {
runFans = false; //default is that fans don't run,
heatingRequested = false;
coolingRequested = false;
switch (heatingState) {
case START_UP:
if (startupTimer > 0)
startupTimer--;
else {
heatingState = INITIAL_RUNNING_PERIOD;
initialRunningPeriodTimer = INITIAL_RUNNING_PERIOD_TIMER_INITIAL_VALUE;
}
break;
case INITIAL_RUNNING_PERIOD:
//requesting heating or cooling immediately if available for simplicity during initial running period, which might be during commissioning
//After a timeout period the state switches to heating if heating is available at that moment, or to non-heating if it isn't.
if (heatingAvailable)
heatingRequested = true;
else if (coolingAvailable)
coolingRequested = true;
if (initialRunningPeriodTimer > 0)
initialRunningPeriodTimer--;
else
{
if (heatingAvailable)
heatingState = HEATING_ACTIVE;
else
heatingState = HEATING_NOT_ACTIVE;
}
break;
case HEATING_NOT_ACTIVE:
// When in non_heating_season:
// respond to a cooling_request immediately (because there won't be a spurious cooling request due to a defrost cycle
// outside of the heating season). Switch to heating_active only if heating remains available to the timeout period. This is to avoid fans coming on
// at the end of hot water cycle when hot water is dumping into radiator circuit.
if (coolingAvailable) {
coolingRequested = true;
}
if (heatingAvailable) {
if (switchToHeatingTimer > 0)
switchToHeatingTimer --;
else
heatingState = HEATING_ACTIVE; //timed out so switch state.
}
else
switchToHeatingTimer = SWITCH_TO_HEATING_STATE_INITIAL_VALUE; // no heating request so restart timer
break;
case HEATING_ACTIVE:
//When heating_season:
//respond to a heating_request immediately
//ignore cooling requests, which are likely to be from a defrost cycle
//Switch to HEATING_NOT_ACTIVE after a time out with no heating
if (heatingAvailable)
{
heatingRequested = (targetRoomTemperature > roomTemperature) or (targetRoomTemperature = TARGET_ROOM_TEMPERATURE_NONE);
switchToNonHeatingTimer = SWITCH_TO_NON_HEATING_STATE_INITIAL_VALUE;
}
else {
if (switchToNonHeatingTimer > 0)
switchToNonHeatingTimer--;
else
heatingState = HEATING_NOT_ACTIVE; //Change state because resettable timer has timed out
}
break;
}
runFans = (heatingRequested or coolingRequested or heatingState == START_UP);
outputFanSpeed();
#ifdef DEBUG_RUN_STATE_MACHINE
Serial.printf("T:%003d ST:%1d HA:%1d CA:%1d HR:%1d CR:%1d Fans:%2d SNotHeat:%3d SHeat:%3d InitialT:%3d StartT:%3d\n", localSeconds, heatingState, heatingAvailable, coolingAvailable, heatingRequested, coolingRequested, fanSpeed, switchToNonHeatingTimer, switchToHeatingTimer, initialRunningPeriodTimer, startupTimer);
#endif
}
int readTemperatureInput(int ADC_channel) {
return toCentigrade(analogRead(ADC_channel)) * 10;
}
float toCentigrade(int rawValue) {
float tempInCentigrade; //Temperature in Centigrade
//value of 1300 is 60ºC value of 2800 is 19ºC
tempInCentigrade = ((float)2800 - rawValue) / (float)(2800 - 1300); // convert to 0 – 1 for 20 to 60 ºC
tempInCentigrade = 19 + tempInCentigrade * 40 + THERMISTOR_READING_CALIBRATION;
return tempInCentigrade;
}
void outputFanSpeed() {
if (runFans) {
//range speed value to between min and max pump speed values
fanSpeed = max(min(requestedFanSpeed,FAN_SPEED_MAXIMUM),FAN_SPEED_MINIMUM);
//Set the pwm signal, adjusting for the values which equate to 'fans off'
ledcWrite(FAN_PWM_PIN, requestedFanSpeed);
} else {
fanSpeed = 0;
ledcWrite(FAN_PWM_PIN, 254); //A slow PWM output signals to the pump that it should turn off
}
}
void updateDisplay() {
if (--consoleUpdateCounter <= 0) {
consoleUpdateCounter = CONSOLE_STATUS_UPDATE_INTERVAL;
#ifdef DEBUG_CONSOLE
Serial.printf("T:%2d St:%1d HA:%1d CA:%1d Fan Sp:%2d\n", localSeconds, heatingState, heatingAvailable, coolingAvailable, requestedFanSpeed);
#endif
}
}
/*
// table values stored as T +100; all values are non-negative, so permit multiplication by 4 without overflow
const unsigned int c_temp[257] = {12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000,
12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 12000, 11845, 11653, 11470, 11296, 11129, 10970, 10817,
10670, 10529, 10392, 10261, 10134, 10011, 9892, 9776, 9664, 9555, 9449, 9346, 9245, 9147, 9051, 8958, 8867, 8777,
8690, 8605, 8521, 8439, 8359, 8280, 8202, 8126, 8052, 7978, 7906, 7835, 7765, 7696, 7629, 7562, 7496, 7432, 7368,
7305, 7242, 7181, 7121, 7061, 7002, 6943, 6885, 6828, 6772, 6716, 6661, 6606, 6552, 6499, 6446, 6393, 6341, 6290,
6239, 6188, 6138, 6088, 6039, 5990, 5941, 5893, 5845, 5797, 5750, 5703, 5657, 5611, 5565, 5519, 5474, 5429, 5384,
5339, 5295, 5251, 5207, 5164, 5120, 5077, 5034, 4991, 4949, 4906, 4864, 4822, 4780, 4738, 4697, 4655, 4614, 4573,
4532, 4491, 4450, 4409, 4369, 4328, 4288, 4248, 4207, 4167, 4127, 4087, 4047, 4008, 3968, 3928, 3888, 3849, 3809,
3769, 3730, 3690, 3651, 3611, 3571, 3532, 3492, 3453, 3413, 3374, 3334, 3294, 3255, 3215, 3175, 3135, 3095, 3055,
3015, 2975, 2935, 2895, 2855, 2814, 2773, 2733, 2692, 2651, 2610, 2569, 2527, 2486, 2444, 2402, 2360, 2318, 2276,
2233, 2190, 2147, 2104, 2061, 2017, 1973, 1929, 1884, 1839, 1794, 1749, 1703, 1657, 1610, 1563, 1516, 1468, 1420,
1372, 1323, 1273, 1223, 1173, 1122, 1070, 1018, 965, 912, 858, 803, 748, 691, 634, 576, 518, 458, 397, 335, 273,
209, 143, 77, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0};
unsigned int ReadTemperature(unsigned char adc_chan) {
bool DONE = true;
int ADRESH;
int ADRESL;
// returns temperature as an integer equal to 100T
// ADCON0 = (unsigned char)(adc_chan << 2) | 0b00000001;
// initiate ADC and wait until complete
int GO = 1;
while (DONE);
// generate index for lookup table from 8 MSB, and interpolation point from 2 LSB
unsigned int eight_bit = (unsigned int)((ADRESH << 8) | ADRESL) >> 2;
unsigned int interpolate = (unsigned int)ADRESL & 3;
// generate interpolated value from lookup table
return (unsigned int)((4 - interpolate) * c_temp[eight_bit] + interpolate * c_temp[eight_bit + 1]) >> 2;
}
*/Room
temperature
Water
temperature