Project showcase
HVAC Controller Retrofit

HVAC Controller Retrofit © GPL3+

An electric furnace-air handler, arduino-based, controller.

  • 2,025 views
  • 13 comments
  • 18 respects

Components and supplies

Apps and online services

About this project

I recently bought an old house which came with a central duct system, an electric furnace and a heat pump.

The electric furnace, from what I gathered, had been installed in the early 80's and the heat pump was added in 2000.

I first changed the thermostat and installed an Ecobee 4, which was a nice upgrade from the old one :

but I was still unsatisfied by the way the whole system was wired :

  • There was only one heat stage (W1), while I had 4x 5kW heat strips installed in the furnace (only 3 connected)
  • The motor in the air handler was multi-speed (4 speeds) but only the high speed was connected
  • There was no obvious upgrade path, the whole thing was controlled by big discrete relays (no PCB) and frankly the installation was a mess :

So I decided to go ahead with the design of an Arduino-based controller that would offer the kind of features I was looking for :

  • Measurement of air temperature in the the supply and return plenums
  • Multi-speed control of the air handler motor based on the measured temperature difference between the two plenums
  • Addition of a W2 (2nd heating stage)
  • Addition of a W3 (3rd heating stage, provision)
  • Electrical current monitoring (4x heat strips + air handler fan)
  • Real-time data reporting (when a computer is connected)
  • Fault modes management and integration of a limp mode in case of partial failure of the system
  • Visual indication of the system status (bi-color LED)

Because of the amount of input/output I needed:

8 digital ouptuts / 4 digital inputs / 7 analog inputs

I decided to base my design on the LEONARDO board (I like the fact that's its a bit more modern than the UNO).

HVAC systems use 24VAC signaling, I did not find any shield on the market that would be close to help me interface with that so I decided to design my own. I went ahead with a simple software (Fritzing) and designed everything from scratch :

- I kept the original 24VAC transformer that send supply voltage to the upstair thermostat, which send back 24VAC on either Y O/B, G, W1, W2 command lines which interface with the controller I designed.

I added two 10k flanged, thermistors to the supply and return plenums.

I ordered 5 Seeedstudio current transformers (4x 30A for the heat strips which are rated at 20A, and 1x 5A for the air handler fan).

  • A bi-color LED (green / red) is operated by a single PWM output digital line and is visible from the side of the furnace for a quick assessment of the system status.
  • 8 relays (7 mechanical, 1 solid-state) are controlled by transistors, mounted directly on the board, control the high current flow

The end-result is this shield (PCB):

Which, once assembled look like this :

The Arduino board goes under (you can see the outline on the bottom left side). The 4 relays for the heat strips (240V, 20A each) have quick-connect connectors on top, consequently no current for these circulate through the board.

The 3 NO/NC relays for the fan manage the speed selection (4 available speeds) and a solid-sate relay (not in the picture) disconnect / reconnect line voltage in between speed changes as to minimize the wear on the mechanical relays (motor current inrush).

Everything has been mounted on an aluminum plate with stand-offs, all main components of the system are fused on both side :

The software has been tested and debugged, using the the "UnoArduSim" simulator, which I highly recommend !

Before installing the system I fully tested its functionality with "dummy loads" and test jigs for emulating the control signals.

I waited for a warm day, as winter is already here in eastern Canada. Final installation had to work the first time around !

I took my time, removed the old, installed the new, and voila !

You can see the heat strips in the back (which comes with a thermal protection). You can see the current transformer as well.

I wish the cabling would have been a bit more neat, but with all the fuses, and the low flexibility of the wires (AWG 10), that's how it ended up.

Switched back the breaker ON and everything was fully operational !

I'm currently in the process of tinkering the software parameters, like the temperature thresholds for the fan speed on actual data from the system.

I decided to connect the heat pump control (Y line) on W3 (which could not be controlled by the ecobee anyway) in order to by-pass the temperature-based fan control in case the heat pump is active (it's better for efficiency). The implementation of the fault modes management and limp mode is not complete yet, but basically what I want to detect is failure of the fan or a heat strip (through the current monitoring).

If a heat strip fails, I want to fall back on a limp mode and use the other functionning heat strips.

If the blower fails, everything has to stop.

Here is a run-down of the main features of the software :

- Cyclical operation of the heat strips for "equal wear"

- Temperature-based fan speed control with hysteresis, overridden when the heat pump is activated (for efficiency).

- Running average RMS current evaluation by measuring 4 samples per 60 Hz cycle. continuous timing adjustment in the main loop for precise samples spacing

- All parameters are outputted every 2 sec on the serial port if a connection is detected

- All actions are timer-based, monitoring continuously run, and multiple processes can be concomitant

- Parameters evaluation (thermistor voltage to temperature conversion, and measured voltage from the current transformer to actual RMS current) is look-up table based to minimize floating points operations

- W1 activates two heat strips (2x5 kW), W2 activates the two other heat strips. A 5 sec delay between activation of individual heat strip has been implemented.

- Proper management of the millis() rollover after 50 days

Here is an example of the monitored data :

What I manage to evaluate from the data :

My blower capacity at max speed is ~ 1250-1300 cfm

My heat pump has a pretty good efficiency : COP = 2.8 @ -1.5 ºC

but its capacity is a bit low (5.5 kW @ -1.5ºC), while it should be closer to 8 kW (3 tons derated for the lower temperature). I'm possibly a bit low on refrigerant.

----

Want to purchase the PCB ?

http://rover.ebay.com/rover/1/706-53473-19255-0/1?icep_ff3=2&pub=5575378759&campid=5338273189&customid=&icep_item=273978414391&ipn=psmain&icep_vectorid=229529&kwid=902099&mtid=824&kw=lg&toolid=11111

Code

HVAC Ctrl 1.0 by LD design (revision 2.5)Arduino
You need a leonardo board and my shield for it to actually do something
// HVAC Ctrl 1.0, by LD design
// revision 2.5, 2019-02-10

// Copyright 2018, Louis Desbiens, All rights reserved.


#include <avr/pgmspace.h>




//
//
//
//
// beginning of global variables and constant declarations
//
//
//
//






// digital addresses

// outputs

const byte LED_ctrl = 13;      // LED control pin address
const byte H1_ctrl = 12;       // Heat strip #1, relay pin address
const byte H2_ctrl = 11;       // Heat strip #2, relay  pin address
const byte H3_ctrl = 10;       // Heat strip #3, relay pin address
const byte H4_ctrl = 9;        // Heat strip #4, relay pin address
const byte BLO_ctrl = 8;       // Blower low speed, relay pin address
const byte BMED_ctrl = 7;      // Blower medium speed, relay pin address
const byte BHI_ctrl = 6;       // Blower high speed, relay pin address
const byte BSSR_ctrl = 5;      // Blower circuit, solid-state relay pin address

// inputs

const byte G = 3;              // Thermostat G line status address (blower)
const byte W1 = 2;             // Thermostat W1 line status address (heat stage 1)
const byte W2 = 1;             // Thermostat W2 line status address (heat stage 2)
const byte W3 = 0;             // Thermostat W3 line status address (heat stage 3), connected to heat pump Y in the current implementation to override the fan speed algorithm


// analog input signal, addresses

const byte TH1 = 0;           // Thermistor 1 address
const byte TH2 = 6;           // Thermistor 2 address
const byte CSFAN = 1;         // Fan current sense address
const byte CSH1 = 5;          // Heat strip #1 current sense address
const byte CSH2 = 4;          // Heat strip #2 current sense address
const byte CSH3 = 3;          // Heat strip #3 current sense address
const byte CSH4 = 2;          // Heat strip #4 current sense address


// constant values

const byte nb_of_heating_elements = 4;                           // nb of heating elements
const byte CS_vector_size = 144;                                 // current sense vector size
const int delta_H = 1000;                                        // minimal delay (ms) between two subsequent activation / dactivation of heat strip relays
const int delta_G0 = 5000;                                       // minimal delay (ms), after a blower power-up at the lowest speed, before engaging the speed adjustment algorithm
const int delta_G = 1000;                                        // minimal delay (ms), between two subsequent blower speed change
const int delta_W1_level_1 = 5;                                  // minimal delay (s) before engaging the second heat strip on a W1 heat request, (2 x 5 kW capacity)
const int delta_W2_level_1 = 5;                                  // minimal delay (s) before engaging the second heat strip on a W2 heat request, (2 x 5 kW capacity)
const int delai_SSR = 10;                                        // minimal delay (ms) before adjusting the blower speed selection relays after opening the circuit with the SSR relay
const int delai_relay = 15;                                      // minimal delay (ms) after adjustment the blower speed selection relays before closing the circuit with the SSR relay
const int delta_T_LO_up = 1000;                                  // minimum temp for going from LO to MED in 1/100 of C (hysteresis)
const int delta_T_MED_up = 1500;                                 // minimum temp for going from MED to HI in 1/100 of C (hysteresis)
const int delta_T_HI_up = 2000;                                  // minimum temp for going from HI to VHI in 1/100 of C (hysteresis)
const int delta_T_MED_down = 900;                                // minimum temp for going from MED to LO in 1/100 of C (hysteresis)
const int delta_T_HI_down = 1400;                                // minimum temp for going from HI to MED in 1/100 of C (hysteresis)
const int delta_T_VHI_down = 1900;                               // minimum temp for going from VHIGH to HI in 1/100 of C (hysteresis)
const int H_current_conversion_1 = 35;                           // current sense conversion constant (30A model, heat strip)
const int B_current_conversion_1 = 210;                          // current sense conversion constant (5A model, blower)
const int HB_current_conversion_2 = 10000;                       // current sense conversion constant
const byte nb_pts_period = 4;                                    // nb of points per 60Hz cylce for the current sense evaluation
const int current_FAN_min[] = {0, 1300, 1400, 1600, 1900};       // blower current sense minimal threshold (mA) for OFF, LO, MED, HIGH, VHIGH states (PRELIMINARY)
const int current_FAN_max[] = {500, 2200, 2400, 2700, 3200};     // blower current sense maximal threshold (mA) for OFF, LO, MED, HIGH, VHIGH states (PRELIMINARY)
const int current_H_min[] = {0, 15000};                          // heat strip current sense minimal threshold (mA) for OFF, ON states (PRELIMINARY)
const int current_H_max[] = {1000, 25000};                       // heat strip current sense maximal threshold (mA) for OFF, ON states (PRELIMINARY)
const int USB_comm_period = 2000;                                // periodic delay (ms) between checks whether a USB connection is present


// constants stored in the flash (instead of SRAM)

const PROGMEM int16_t thermistor_conversion[] = {32767, 32767, 29765, 26554, 24485, 22984, 21820, 20876, 20087, 19411, 18822, 18301, 17836, 17415, 17032, 16681, 16358, 16058, 15779, 15518, 15273, 15042, 14824, 14618, 14422, 14235, 14058, 13888, 13726, 13570, 13421, 13277, 13139, 13006, 12877, 12753, 12633, 12517, 12404, 12295, 12189, 12086, 11987, 11889, 11795, 11703, 11613, 11525, 11440, 11357, 11275, 11196, 11118, 11042, 10967, 10895, 10823, 10753, 10685, 10618, 10552, 10487, 10424, 10362, 10300, 10240, 10181, 10123, 10066, 10010, 9955, 9901, 9847, 9795, 9743, 9692, 9642, 9592, 9543, 9495, 9448, 9401, 9355, 9309, 9265, 9220, 9177, 9133, 9091, 9049, 9007, 8966, 8926, 8885, 8846, 8807, 8768, 8730, 8692, 8655, 8618, 8581, 8545, 8509, 8474, 8439, 8404, 8370, 8336, 8302, 8269, 8236, 8203, 8171, 8139, 8107, 8076, 8044, 8014, 7983, 7953, 7923, 7893, 7863, 7834, 7805, 7776, 7748, 7720, 7691, 7664, 7636, 7609, 7582, 7555, 7528, 7501, 7475, 7449, 7423, 7397, 7372, 7346, 7321, 7296, 7272, 7247, 7223, 7198, 7174, 7150, 7126, 7103, 7079, 7056, 7033, 7010, 6987, 6965, 6942, 6920, 6897, 6875, 6853, 6832, 6810, 6788, 6767, 6746, 6724, 6703, 6682, 6662, 6641, 6620, 6600, 6580, 6560, 6539, 6519, 6500, 6480, 6460, 6441, 6421, 6402, 6383, 6364, 6345, 6326, 6307, 6288, 6269, 6251, 6232, 6214, 6196, 6178, 6160, 6142, 6124, 6106, 6088, 6071, 6053, 6036, 6018, 6001, 5984, 5966, 5949, 5932, 5915, 5899, 5882, 5865, 5849, 5832, 5815, 5799, 5783, 5766, 5750, 5734, 5718, 5702, 5686, 5670, 5655, 5639, 5623, 5608, 5592, 5577, 5561, 5546, 5530, 5515, 5500, 5485, 5470, 5455, 5440, 5425, 5410, 5395, 5381, 5366, 5351, 5337, 5322, 5308, 5293, 5279, 5265, 5250, 5236, 5222, 5208, 5194, 5180, 5166, 5152, 5138, 5124, 5111, 5097, 5083, 5069, 5056, 5042, 5029, 5015, 5002, 4989, 4975, 4962, 4949, 4935, 4922, 4909, 4896, 4883, 4870, 4857, 4844, 4831, 4818, 4805, 4793, 4780, 4767, 4754, 4742, 4729, 4717, 4704, 4692, 4679, 4667, 4654, 4642, 4630, 4617, 4605, 4593, 4581, 4568, 4556, 4544, 4532, 4520, 4508, 4496, 4484, 4472, 4460, 4448, 4437, 4425, 4413, 4401, 4389, 4378, 4366, 4354, 4343, 4331, 4320, 4308, 4297, 4285, 4274, 4262, 4251, 4240, 4228, 4217, 4206, 4194, 4183, 4172, 4161, 4150, 4138, 4127, 4116, 4105, 4094, 4083, 4072, 4061, 4050, 4039, 4028, 4017, 4006, 3996, 3985, 3974, 3963, 3952, 3942, 3931, 3920, 3910, 3899, 3888, 3878, 3867, 3857, 3846, 3835, 3825, 3814, 3804, 3793, 3783, 3773, 3762, 3752, 3741, 3731, 3721, 3710, 3700, 3690, 3680, 3669, 3659, 3649, 3639, 3628, 3618, 3608, 3598, 3588, 3578, 3568, 3558, 3548, 3538, 3527, 3517, 3507, 3498, 3488, 3478, 3468, 3458, 3448, 3438, 3428, 3418, 3408, 3399, 3389, 3379, 3369, 3359, 3350, 3340, 3330, 3320, 3311, 3301, 3291, 3282, 3272, 3262, 3253, 3243, 3233, 3224, 3214, 3205, 3195, 3186, 3176, 3166, 3157, 3147, 3138, 3128, 3119, 3109, 3100, 3091, 3081, 3072, 3062, 3053, 3044, 3034, 3025, 3015, 3006, 2997, 2987, 2978, 2969, 2959, 2950, 2941, 2932, 2922, 2913, 2904, 2895, 2885, 2876, 2867, 2858, 2849, 2839, 2830, 2821, 2812, 2803, 2793, 2784, 2775, 2766, 2757, 2748, 2739, 2730, 2721, 2712, 2702, 2693, 2684, 2675, 2666, 2657, 2648, 2639, 2630, 2621, 2612, 2603, 2594, 2585, 2576, 2567, 2558, 2549, 2540, 2531, 2522, 2513, 2504, 2496, 2487, 2478, 2469, 2460, 2451, 2442, 2433, 2424, 2415, 2407, 2398, 2389, 2380, 2371, 2362, 2353, 2344, 2336, 2327, 2318, 2309, 2300, 2291, 2283, 2274, 2265, 2256, 2247, 2239, 2230, 2221, 2212, 2203, 2195, 2186, 2177, 2168, 2159, 2151, 2142, 2133, 2124, 2116, 2107, 2098, 2089, 2081, 2072, 2063, 2054, 2046, 2037, 2028, 2019, 2011, 2002, 1993, 1984, 1976, 1967, 1958, 1949, 1941, 1932, 1923, 1914, 1906, 1897, 1888, 1880, 1871, 1862, 1853, 1845, 1836, 1827, 1818, 1810, 1801, 1792, 1784, 1775, 1766, 1757, 1749, 1740, 1731, 1722, 1714, 1705, 1696, 1687, 1679, 1670, 1661, 1653, 1644, 1635, 1626, 1618, 1609, 1600, 1591, 1583, 1574, 1565, 1556, 1548, 1539, 1530, 1521, 1512, 1504, 1495, 1486, 1477, 1469, 1460, 1451, 1442, 1433, 1425, 1416, 1407, 1398, 1389, 1381, 1372, 1363, 1354, 1345, 1336, 1328, 1319, 1310, 1301, 1292, 1283, 1274, 1266, 1257, 1248, 1239, 1230, 1221, 1212, 1203, 1194, 1185, 1176, 1168, 1159, 1150, 1141, 1132, 1123, 1114, 1105, 1096, 1087, 1078, 1069, 1060, 1051, 1042, 1033, 1024, 1015, 1006, 997, 988, 979, 969, 960, 951, 942, 933, 924, 915, 906, 897, 887, 878, 869, 860, 851, 842, 832, 823, 814, 805, 795, 786, 777, 768, 758, 749, 740, 730, 721, 712, 702, 693, 684, 674, 665, 655, 646, 637, 627, 618, 608, 599, 589, 580, 570, 561, 551, 542, 532, 523, 513, 503, 494, 484, 475, 465, 455, 445, 436, 426, 416, 407, 397, 387, 377, 367, 358, 348, 338, 328, 318, 308, 298, 288, 278, 268, 259, 249, 238, 228, 218, 208, 198, 188, 178, 168, 158, 147, 137, 127, 117, 106, 96, 86, 76, 65, 55, 44, 34, 24, 13, 3, -8, -18, -29, -40, -50, -61, -71, -82, -93, -104, -114, -125, -136, -147, -158, -168, -179, -190, -201, -212, -223, -234, -245, -257, -268, -279, -290, -301, -312, -324, -335, -346, -358, -369, -381, -392, -404, -415, -427, -438, -450, -462, -473, -485, -497, -509, -521, -532, -544, -556, -568, -580, -592, -605, -617, -629, -641, -654, -666, -678, -691, -703, -716, -728, -741, -753, -766, -779, -792, -804, -817, -830, -843, -856, -869, -883, -896, -909, -922, -936, -949, -963, -976, -990, -1003, -1017, -1031, -1045, -1058, -1072, -1086, -1100, -1115, -1129, -1143, -1157, -1172, -1186, -1201, -1216, -1230, -1245, -1260, -1275, -1290, -1305, -1320, -1335, -1351, -1366, -1381, -1397, -1413, -1428, -1444, -1460, -1476, -1492, -1509, -1525, -1541, -1558, -1575, -1591, -1608, -1625, -1642, -1659, -1677, -1694, -1711, -1729, -1747, -1765, -1783, -1801, -1819, -1838, -1856, -1875, -1894, -1913, -1932, -1951, -1971, -1990, -2010, -2030, -2050, -2070, -2091, -2111, -2132, -2153, -2174, -2196, -2217, -2239, -2261, -2283, -2306, -2328, -2351, -2375, -2398, -2422, -2446, -2470, -2494, -2519, -2544, -2569, -2595, -2621, -2647, -2674, -2701, -2728, -2756, -2784, -2812, -2841, -2870, -2900, -2930, -2961, -2992, -3024, -3056, -3089, -3122, -3156, -3190, -3225, -3261, -3297, -3334, -3372, -3411, -3451, -3491, -3533, -3575, -3618, -3663, -3708, -3755, -3803, -3853, -3904, -3956, -4010, -4066, -4124, -4184, -4247, -4311, -4379, -4449, -4523, -4600, -4682, -4768, -4859, -4956, -5059, -5171, -5291, -5423, -5568, -5731, -5915, -6128, -6384, -6704, -7138, -7839, -32768}; // use this form
const PROGMEM uint16_t current_sensing_conversion[] = {64532, 64280, 64029, 63778, 63527, 63277, 63027, 62778, 62530, 62281, 62034, 61787, 61540, 61294, 61048, 60803, 60558, 60314, 60070, 59827, 59585, 59342, 59101, 58859, 58619, 58378, 58139, 57899, 57661, 57422, 57185, 56947, 56711, 56474, 56238, 56003, 55768, 55534, 55300, 55067, 54834, 54602, 54370, 54138, 53908, 53677, 53447, 53218, 52989, 52761, 52533, 52305, 52078, 51852, 51626, 51401, 51176, 50951, 50727, 50504, 50281, 50058, 49836, 49615, 49394, 49173, 48953, 48734, 48515, 48296, 48078, 47861, 47644, 47427, 47211, 46995, 46780, 46566, 46352, 46138, 45925, 45712, 45500, 45289, 45077, 44867, 44657, 44447, 44238, 44029, 43821, 43613, 43406, 43199, 42993, 42787, 42582, 42378, 42173, 41970, 41766, 41564, 41361, 41160, 40958, 40758, 40557, 40357, 40158, 39959, 39761, 39563, 39366, 39169, 38973, 38777, 38582, 38387, 38192, 37998, 37805, 37612, 37420, 37228, 37036, 36846, 36655, 36465, 36276, 36087, 35898, 35710, 35523, 35336, 35150, 34964, 34778, 34593, 34409, 34225, 34041, 33858, 33676, 33494, 33312, 33131, 32950, 32770, 32591, 32412, 32233, 32055, 31877, 31700, 31524, 31348, 31172, 30997, 30822, 30648, 30475, 30301, 30129, 29957, 29785, 29614, 29443, 29273, 29103, 28934, 28765, 28597, 28429, 28262, 28095, 27929, 27763, 27598, 27433, 27269, 27105, 26942, 26779, 26617, 26455, 26294, 26133, 25973, 25813, 25653, 25495, 25336, 25178, 25021, 24864, 24708, 24552, 24397, 24242, 24087, 23933, 23780, 23627, 23475, 23323, 23171, 23020, 22870, 22720, 22570, 22421, 22273, 22125, 21977, 21830, 21684, 21538, 21392, 21247, 21103, 20959, 20815, 20672, 20529, 20387, 20246, 20105, 19964, 19824, 19684, 19545, 19407, 19269, 19131, 18994, 18857, 18721, 18585, 18450, 18316, 18181, 18048, 17914, 17782, 17650, 17518, 17387, 17256, 17126, 16996, 16867, 16738, 16610, 16482, 16355, 16228, 16102, 15976, 15850, 15726, 15601, 15478, 15354, 15231, 15109, 14987, 14866, 14745, 14625, 14505, 14385, 14266, 14148, 14030, 13913, 13796, 13679, 13564, 13448, 13333, 13219, 13105, 12991, 12878, 12766, 12654, 12542, 12431, 12321, 12211, 12101, 11992, 11884, 11776, 11668, 11561, 11455, 11349, 11243, 11138, 11033, 10929, 10826, 10723, 10620, 10518, 10416, 10315, 10214, 10114, 10015, 9916, 9817, 9719, 9621, 9524, 9427, 9331, 9235, 9140, 9045, 8951, 8857, 8764, 8671, 8579, 8487, 8396, 8305, 8215, 8125, 8036, 7947, 7859, 7771, 7684, 7597, 7511, 7425, 7339, 7255, 7170, 7086, 7003, 6920, 6838, 6756, 6674, 6594, 6513, 6433, 6354, 6275, 6196, 6119, 6041, 5964, 5888, 5812, 5736, 5661, 5587, 5513, 5439, 5366, 5294, 5222, 5150, 5079, 5009, 4939, 4869, 4800, 4731, 4663, 4596, 4529, 4462, 4396, 4330, 4265, 4201, 4136, 4073, 4010, 3947, 3885, 3823, 3762, 3701, 3641, 3581, 3522, 3464, 3405, 3348, 3290, 3234, 3177, 3122, 3066, 3012, 2957, 2904, 2850, 2798, 2745, 2694, 2642, 2591, 2541, 2491, 2442, 2393, 2345, 2297, 2250, 2203, 2156, 2110, 2065, 2020, 1976, 1932, 1888, 1846, 1803, 1761, 1720, 1679, 1638, 1598, 1559, 1520, 1481, 1443, 1406, 1369, 1332, 1296, 1261, 1226, 1191, 1157, 1124, 1091, 1058, 1026, 995, 963, 933, 903, 873, 844, 815, 787, 760, 733, 706, 680, 654, 629, 604, 580, 557, 533, 511, 488, 467, 446, 425, 405, 385, 366, 347, 329, 311, 294, 277, 261, 245, 229, 215, 200, 187, 173, 160, 148, 136, 125, 114, 104, 94, 84, 76, 67, 59, 52, 45, 39, 33, 27, 22, 18, 14, 10, 7, 5, 3, 2, 1, 0, 0, 1, 2, 3, 5, 7, 10, 14, 18, 22, 27, 33, 39, 45, 52, 59, 67, 76, 84, 94, 104, 114, 125, 136, 148, 160, 173, 187, 200, 215, 229, 245, 261, 277, 294, 311, 329, 347, 366, 385, 405, 425, 446, 467, 488, 511, 533, 557, 580, 604, 629, 654, 680, 706, 733, 760, 787, 815, 844, 873, 903, 933, 963, 995, 1026, 1058, 1091, 1124, 1157, 1191, 1226, 1261, 1296, 1332, 1369, 1406, 1443, 1481, 1520, 1559, 1598, 1638, 1679, 1720, 1761, 1803, 1846, 1888, 1932, 1976, 2020, 2065, 2110, 2156, 2203, 2250, 2297, 2345, 2393, 2442, 2491, 2541, 2591, 2642, 2694, 2745, 2798, 2850, 2904, 2957, 3012, 3066, 3122, 3177, 3234, 3290, 3348, 3405, 3464, 3522, 3581, 3641, 3701, 3762, 3823, 3885, 3947, 4010, 4073, 4136, 4201, 4265, 4330, 4396, 4462, 4529, 4596, 4663, 4731, 4800, 4869, 4939, 5009, 5079, 5150, 5222, 5294, 5366, 5439, 5513, 5587, 5661, 5736, 5812, 5888, 5964, 6041, 6119, 6196, 6275, 6354, 6433, 6513, 6594, 6674, 6756, 6838, 6920, 7003, 7086, 7170, 7255, 7339, 7425, 7511, 7597, 7684, 7771, 7859, 7947, 8036, 8125, 8215, 8305, 8396, 8487, 8579, 8671, 8764, 8857, 8951, 9045, 9140, 9235, 9331, 9427, 9524, 9621, 9719, 9817, 9916, 10015, 10114, 10214, 10315, 10416, 10518, 10620, 10723, 10826, 10929, 11033, 11138, 11243, 11349, 11455, 11561, 11668, 11776, 11884, 11992, 12101, 12211, 12321, 12431, 12542, 12654, 12766, 12878, 12991, 13105, 13219, 13333, 13448, 13564, 13679, 13796, 13913, 14030, 14148, 14266, 14385, 14505, 14625, 14745, 14866, 14987, 15109, 15231, 15354, 15478, 15601, 15726, 15850, 15976, 16102, 16228, 16355, 16482, 16610, 16738, 16867, 16996, 17126, 17256, 17387, 17518, 17650, 17782, 17914, 18048, 18181, 18316, 18450, 18585, 18721, 18857, 18994, 19131, 19269, 19407, 19545, 19684, 19824, 19964, 20105, 20246, 20387, 20529, 20672, 20815, 20959, 21103, 21247, 21392, 21538, 21684, 21830, 21977, 22125, 22273, 22421, 22570, 22720, 22870, 23020, 23171, 23323, 23475, 23627, 23780, 23933, 24087, 24242, 24397, 24552, 24708, 24864, 25021, 25178, 25336, 25495, 25653, 25813, 25973, 26133, 26294, 26455, 26617, 26779, 26942, 27105, 27269, 27433, 27598, 27763, 27929, 28095, 28262, 28429, 28597, 28765, 28934, 29103, 29273, 29443, 29614, 29785, 29957, 30129, 30301, 30475, 30648, 30822, 30997, 31172, 31348, 31524, 31700, 31877, 32055, 32233, 32412, 32591, 32770, 32950, 33131, 33312, 33494, 33676, 33858, 34041, 34225, 34409, 34593, 34778, 34964, 35150, 35336, 35523, 35710, 35898, 36087, 36276, 36465, 36655, 36846, 37036, 37228, 37420, 37612, 37805, 37998, 38192, 38387, 38582, 38777, 38973, 39169, 39366, 39563, 39761, 39959, 40158, 40357, 40557, 40758, 40958, 41160, 41361, 41564, 41766, 41970, 42173, 42378, 42582, 42787, 42993, 43199, 43406, 43613, 43821, 44029, 44238, 44447, 44657, 44867, 45077, 45289, 45500, 45712, 45925, 46138, 46352, 46566, 46780, 46995, 47211, 47427, 47644, 47861, 48078, 48296, 48515, 48734, 48953, 49173, 49394, 49615, 49836, 50058, 50281, 50504, 50727, 50951, 51176, 51401, 51626, 51852, 52078, 52305, 52533, 52761, 52989, 53218, 53447, 53677, 53908, 54138, 54370, 54602, 54834, 55067, 55300, 55534, 55768, 56003, 56238, 56474, 56711, 56947, 57185, 57422, 57661, 57899, 58139, 58378, 58619, 58859, 59101, 59342, 59585, 59827, 60070, 60314, 60558, 60803, 61048, 61294, 61540, 61787, 62034, 62281, 62530, 62778, 63027, 63277, 63527, 63778, 64029, 64280, 64532};
const PROGMEM byte LED1[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2};
const PROGMEM byte LED2[] = {0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2};
const PROGMEM byte LED3[] = {0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2};

// thermistor conversion :  the "analogread" value is used as an index, what is stored in the flash are the correspoding 1024 temperatures (in 1/100 C)
// current sense conversion, the instanteous current (mA) is given by :   sqrt(current_sensing_conversion)/H_current_conversion_1*HB_current_conversion_2 for the heat strips and  sqrt(current_sensing_conversion)/B_current_conversion_1*HB_current_conversion_2 for the blower



// working variables

byte current_speed = 0;                                                    // 0 min speed, 1,2,3 max speed (blower, current state)
byte new_speed = 0;                                                        // 0 min speed, 1,2,3 max speed (blower, new state)
byte G_current_status = 0;                                                 // current status of the G line (0=OFF, 1=ON)
byte G_new_status = 0;                                                     // new status of the G line (0=OFF, 1=ON)
byte W1_current_status = 0;                                                // current status of the W1 line (0=OFF, 1=ON)
byte W1_new_status = 0;                                                    // new status of the W1 line (0=OFF, 1=ON)
byte W2_current_status = 0;                                                // current status of the W2 line (0=OFF, 1=ON)
byte W2_new_status = 0;                                                    // new status of the W2 line (0=OFF, 1=ON)
byte W3_current_status = 0;                                                // current status of the W3 line (0=OFF, 1=ON)
byte W3_new_status = 0;                                                    // new status of the W3 line (0=OFF, 1=ON)
int TH1_value = 0;                                                         // Temperature value (1/100 C), thermistor 1
int TH2_value = 0;                                                         // Temperature value (1/100 C), thermistor 2
byte active_elements_W1_W2[5] = {H4_ctrl, H1_ctrl, H2_ctrl, H3_ctrl, H4_ctrl}; // indexes 0 and 1 correspond to W1, indexes 2 and 3 to W2, the addresses of the heat strips are cycled through in the vector each time a new activation of W1 occcurs
byte W1_heating_state = 0;                                                 // heating states associated with W1  (0: no heat strips activates, 1: first heat strip is activated, 2: both heat strips are activated)
byte W2_heating_state = 0;                                                 // heating states associated with W2  (0: no heat strips activates, 1: first heat strip is activated, 2: both heat strips are activated)
byte speed_change_stage = 3;                                               // blower speed change states (0: beginning of speed change sequence, 1: the circuit is open, 2: The speed adjustment has been made, 3: the circuit is closed)
byte fan_activation_state = 0;                                             // blower fan state sequence for shutdown (=0 fan is ready for first step of startup or shutdown sequence, =1 shutdown sequence intermediate step, after SSR has openend the circuit before fan speed goes back to minimal value, =2 fan is off and speed is at low)
byte pointer_CS = 0;                                                       // current sense consist of measuring multiplie instantaneous current value samples (4 per 60 Hz cycle, 36 consecutives cycles, 144 samples in total representing 600 ms) and calculating the RMS value, pointer_CS is the index in the vector current
long correction_us = 0;                                                    // To ensure the current measurement are performed every 4.16 ms, a delay is adjusted each cycle to compensate for the small variations, correction_us is the correction to the fized latenccy in us, reevaluated at each cycle
byte system_status_LED[] = {0, 0, 0, 0, 0, 0, 0, 0};                       //  LED1 LED2 LED3 color   0 =green  1 = yellow  2 = red,   FAN status  H1 H2 H3 H4   0 = OK(green)   1 = UC(yellow)  2 = OC(red)   PRELIMINARY
byte FAN_status = 0;                                                       // FAN_status (0= OFF, 1= LOW, 2= MED, 3= HIGH, 4= VHIGH) to be used as an indicator in the reporting system (USB) and eventually in the LED control PRELIMINARY
byte HEATER_status = 0;                                                    // HEATER_status (0= OFF, 1= W1 1st heat strip, 2= W2 both heat strip, 3= W2 1st heat strip, 4= W2 both heat strip) to be used as an indicator in the reporting system (USB) and eventually in the LED control PRELIMINARY
byte system_status_index = 0;                                              // global value indicating the status in which the system is in, 27 possible states PRELIMINARY
byte USB_connection_status = 0;                                            // indication whether a USB connection is present or not
unsigned int H1_current_vector[CS_vector_size];                            // current sense vector of 144 samples for heat strip #1
unsigned int H2_current_vector[CS_vector_size];                            // current sense vector of 144 samples for heat strip #2
unsigned int H3_current_vector[CS_vector_size];                            // current sense vector of 144 samples for heat strip #3
unsigned int H4_current_vector[CS_vector_size];                            // current sense vector of 144 samples for heat strip #4
unsigned int FAN_current_vector[CS_vector_size];                           // current sense vector of 144 samples for the blower
unsigned long H1_CS_sum = 0;                                               // current sense sum for heat strip #1
unsigned long H2_CS_sum = 0;                                               // current sense sum for heat strip #2
unsigned long H3_CS_sum = 0;                                               // current sense sum for heat strip #3
unsigned long H4_CS_sum = 0;                                               // current sense sum for heat strip #4
unsigned long FAN_CS_sum = 0;                                              // current sense sum for the blower
unsigned int current_sense_H_vector[nb_of_heating_elements];               // actual current value in mA for the heat strips
unsigned int current_sense_FAN = 0;                                        // actual current value in mA for the blower




// time measurements

unsigned long time_H0 = 0;                  // absolute time of the last relay activation
unsigned long time_W1 = 0;                  // absolute time of the last W1 activation
unsigned long time_W2 = 0;                  // absolute time of the last W2 activation
unsigned long time_G0 = 0;                  // absolute time of the last blower activation
unsigned long time_G = 0;                   // absolute time of the last speed change
unsigned long time_SC = 0;                  // absolute time used for counting during the intermediate steps associated with a speed change
unsigned long time_AF = 0;                  // absolute time used for couting during the intermediate steps associated with the activation of the fan
unsigned long time_USB = 0;                 // absolute time used for the delays in the USB connection presence check
unsigned long current_time = 0;             // absolute time to verify, at each cycle whether the time overflow has occured (~ every ~ 50 days)
unsigned long old_current_time = 0;         // previous value of the absolute time to verify whether the time overflow has occured
unsigned long time_old_CS = 0;              // absolute time used in current sense algorithm to synchronize the measurements on a 60 Hz cycle (previous value)
unsigned long time_new_CS = 0;              // absolute time used in current sense algorithm to synchronize the measurements on a 60 Hz cycle (new value)
unsigned int fixed_latency_old = 0;         // latency to be waited at each cycle to stay synchronized with the 60 Hz electrical supply (previous value)
unsigned int fixed_latency_new = 0;         // latency to be waited at each cycle to stay synchronized with the 60 Hz electrical supply (new value)


//
//
//
//
// end of global variables and constant declarations
//
//
//
//


/////////////////////////////////////////////////////////////////////////////
// routine for evaluating whether a speed change of the blower is required
/////////////////////////////////////////////////////////////////////////////

void new_speed_evaluation() {


  // The verification is performed only if we're are not currently in the process of changing the speed
  if (speed_change_stage == 3) {


    // If the heat pump is active (W3) we force the blower to high speed for maximum efficiency
    if (W3_current_status == 1) {
      new_speed = 3;
    }
    else {



      // validation of the current speed and check whether the temperature difference is over or under the thresholds we have defined
      switch (current_speed) {
        case 0:   // LO speed
          if (abs(TH1_value - TH2_value) >= delta_T_LO_up) {
            new_speed = 1;
          }
          break;

        case 1:   // MED speed
          if (abs(TH1_value - TH2_value) >= delta_T_MED_up) {
            new_speed = 2;
          }
          if (abs(TH1_value - TH2_value) < delta_T_MED_down) {
            new_speed = 0;
          }
          break;

        case 2:   // HI speed
          if (abs(TH1_value - TH2_value) >= delta_T_HI_up) {
            new_speed = 3;
          }
          if (abs(TH1_value - TH2_value) < delta_T_HI_down) {
            new_speed = 1;
          }
          break;

        case 3:   // VHI speed
          if (abs(TH1_value - TH2_value) < delta_T_VHI_down) {
            new_speed = 2;
          }
          break;
      }

    }
  }
}

/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// routine for changing the fan speed
/////////////////////////////////////////////////////////////////////////////

void speed_change() {

  if (new_speed != current_speed) {

    // if the speed_change_stage is equal to 3, its a new speed change sequence (and not an ongoing sequence), therefore we start at the beginning (speed_stage_stage=0)
    if (speed_change_stage == 3) {
      speed_change_stage = 0;
    }

  }



  switch (speed_change_stage) {

    // The electrical circuit is openend with the SSR relay
    case 0:

      digitalWrite(BSSR_ctrl, LOW);
      speed_change_stage = 1;
      time_SC = millis();

      break;


    // if the delay after opening the circuit is over the 10 ms minimum, we select the new speed
    case 1:

      if ((long)(current_time - time_SC) >= delai_SSR) {

        switch (new_speed) {
          case 0:   // LOWEST SPEED

            digitalWrite(BHI_ctrl, LOW);
            digitalWrite(BMED_ctrl, LOW);
            digitalWrite(BLO_ctrl, LOW);

            break;
          case 1:  // SECOND LOWEST
            digitalWrite(BHI_ctrl, LOW);
            digitalWrite(BMED_ctrl, LOW);
            digitalWrite(BLO_ctrl, HIGH);
            break;
          case 2:  // SECOND HIGHEST
            digitalWrite(BHI_ctrl, LOW);
            digitalWrite(BMED_ctrl, HIGH);
            digitalWrite(BLO_ctrl, HIGH);
            break;
          case 3:  // HIGHEST
            digitalWrite(BHI_ctrl, HIGH);
            digitalWrite(BMED_ctrl, HIGH);
            digitalWrite(BLO_ctrl, HIGH);
            break;
        }
        speed_change_stage = 2;
        time_SC = millis();


      }
      break;



    // if the delay after selecting the new speed is over the 15 ms minimum (mechanical relay boucing), we close the circuit again with the SSR
    case 2:

      if ((long)(current_time - time_SC) >= delai_relay) {
        digitalWrite(BSSR_ctrl, HIGH);
        current_speed = new_speed;
        time_G = millis();
        speed_change_stage = 3;
        break;


      }
  }
}

/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// temperature reading routine
/////////////////////////////////////////////////////////////////////////////

int read_temp(int pin_adress) {


  int TH_raw_cnt;
  int read_temp_value;

  TH_raw_cnt = analogRead(pin_adress);

  read_temp_value = pgm_read_word(&thermistor_conversion[TH_raw_cnt]);

  return read_temp_value;
}












/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// routine for the activation of the relays associated withe the W1 heat command
/////////////////////////////////////////////////////////////////////////////

void activate_W1() {

  switch (W1_new_status) {

    // W1 heat strips deactivation
    case 0:

      // Validation of which heating state we're in (state 0 should not be an option)
      switch (W1_heating_state) {

        // deactivation of heat strip #1 associated with W1
        case 1:
          digitalWrite(active_elements_W1_W2[0], W1_new_status);
          W1_current_status = W1_new_status;
          W1_heating_state = 0;
          break;

        // deactivation of heat strip #2 associated with W1
        case 2:
          digitalWrite(active_elements_W1_W2[1], W1_new_status);
          W1_heating_state = 1;
          break;
      }
      break;


    // W1 heat strips activation
    case 1:

      // Validation of which heating state we're in (state 2 should not be an option)
      switch (W1_heating_state) {

        // activation of heat strip #1 associated with W1
        case 0:


          // rotation of the heat strips associated with W1 and W2 (for even wear)
          // before performing the rotation, we validate that W2 is not active, it should not as thermostat usually don't activate W2 without activating W1
          if (W2_current_status == 0) {


            int i;
            for (i = 0; i < nb_of_heating_elements; i++) {

              active_elements_W1_W2[i] = active_elements_W1_W2[i + 1];
            }

            active_elements_W1_W2[nb_of_heating_elements] = active_elements_W1_W2[0];

          }


          // activation of heat strip #1
          digitalWrite(active_elements_W1_W2[0], W1_new_status);
          time_W1 = millis();
          W1_current_status = W1_new_status;
          W1_heating_state = 1;
          break;

        // activation of heat strip #2 assocviated with W1
        case 1:
          digitalWrite(active_elements_W1_W2[1], W1_new_status);
          W1_heating_state = 2;
          break;

      }
      break;

  }

  time_H0 = millis();

}


/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// routine for the activation of the relays associated withe the W2 heat command
/////////////////////////////////////////////////////////////////////////////

void activate_W2() {

  switch (W2_new_status) {

    // W2 heat strips deactivation
    case 0:

      // Validation of which heating state we're in (state 0 should not be an option)
      switch (W2_heating_state) {

        // deactivation of heat strip #1 associated with W2
        case 1:
          digitalWrite(active_elements_W1_W2[2], W2_new_status);
          W2_current_status = W2_new_status;
          W2_heating_state = 0;
          break;

        // deactivation of heat strip #2 associated with W2
        case 2:
          digitalWrite(active_elements_W1_W2[3], W2_new_status);
          W2_heating_state = 1;
          break;
      }
      break;


    // W2 heat strips activation
    case 1:

      // Validation of which heating state we're in (state 2 should not be an option)
      switch (W2_heating_state) {

        // activation of heat strip #1 associated with W2
        case 0:
          digitalWrite(active_elements_W1_W2[2], W2_new_status);
          time_W2 = millis();
          W2_current_status = W2_new_status; // on met  jour le status W2
          W2_heating_state = 1;
          break;

        // activation of heat strip #2 associated with W2
        case 1:
          digitalWrite(active_elements_W1_W2[3], W2_new_status);
          W2_heating_state = 2;
          break;

      }
      break;

  }
  time_H0 = millis();

}



/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// routine for the activation of the blower
/////////////////////////////////////////////////////////////////////////////


void activate_G() {


  // check whether the fan was in a steady state (OFF or ON)
  // if the answer is positive, put the fan state at value for a new sequence (ON or OFF)

  if (fan_activation_state == 2) {
    fan_activation_state = 0;

  }



  switch (G_new_status) {


    // blower deactivation, by default we set the speed to the lowest setting for the next power-up
    case 0:

      switch (fan_activation_state) {

        // The electrical circuit is openend with the SSR relay
        case 0:
          digitalWrite(BSSR_ctrl, LOW);
          fan_activation_state = 1;
          time_AF = millis();
          break;

        // if the delay after opening the circuit is over the 10 ms minimum, we select the new speed (lowest value for next power-up)
        case 1:

          if ((long)(current_time - time_AF) >= delai_SSR) {

            digitalWrite(BHI_ctrl, LOW);
            digitalWrite(BMED_ctrl, LOW);
            digitalWrite(BLO_ctrl, LOW);
            current_speed = 0;
            new_speed = 0;
            fan_activation_state = 2;
            G_current_status = G_new_status;

          }
          break;
      }
      break;


    // We close the circuit with the SSR for the blower power-up
    case 1:

      digitalWrite(BSSR_ctrl, HIGH);

      time_G0 = millis();
      time_G = time_G0;
      G_current_status = G_new_status;
      break;
  }

}


/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// routine for the evaluation of the current sense values
/////////////////////////////////////////////////////////////////////////////


void current_update() {

  int CS_raw_cnt;
  float temp_FLOAT_var;


  // we substract the raw instantaneous current value at the current pointer position

  H1_CS_sum = H1_CS_sum - H1_current_vector[pointer_CS];
  H2_CS_sum = H2_CS_sum - H2_current_vector[pointer_CS];
  H3_CS_sum = H3_CS_sum - H3_current_vector[pointer_CS];
  H4_CS_sum = H4_CS_sum - H4_current_vector[pointer_CS];
  FAN_CS_sum = FAN_CS_sum - FAN_current_vector[pointer_CS];


  // we update in the raw instanteous current vectors the value at the current pointer position

  CS_raw_cnt = analogRead(CSH1);
  H1_current_vector[pointer_CS] = pgm_read_word(&current_sensing_conversion[CS_raw_cnt]);

  CS_raw_cnt = analogRead(CSH2);
  H2_current_vector[pointer_CS] = pgm_read_word(&current_sensing_conversion[CS_raw_cnt]);

  CS_raw_cnt = analogRead(CSH3);
  H3_current_vector[pointer_CS] = pgm_read_word(&current_sensing_conversion[CS_raw_cnt]);

  CS_raw_cnt = analogRead(CSH4);
  H4_current_vector[pointer_CS] = pgm_read_word(&current_sensing_conversion[CS_raw_cnt]);

  CS_raw_cnt = analogRead(CSFAN);
  FAN_current_vector[pointer_CS] = pgm_read_word(&current_sensing_conversion[CS_raw_cnt]);

  // we update the raw sum

  H1_CS_sum = H1_CS_sum + H1_current_vector[pointer_CS];
  H2_CS_sum = H2_CS_sum + H2_current_vector[pointer_CS];
  H3_CS_sum = H3_CS_sum + H3_current_vector[pointer_CS];
  H4_CS_sum = H4_CS_sum + H4_current_vector[pointer_CS];
  FAN_CS_sum = FAN_CS_sum + FAN_current_vector[pointer_CS];

  // check whether the pointer has reached the end of the vector and update of the latency value for synchronizing the current evaluation with the 60 Hz electrical cycle

  if (pointer_CS == CS_vector_size - 1) {
    pointer_CS = 0;
    time_new_CS = millis();
    correction_us = (long) 100000 / 6 / nb_pts_period - (time_new_CS - time_old_CS) * 1000 / CS_vector_size;
    time_old_CS = time_new_CS;


    fixed_latency_new = fixed_latency_old + correction_us;

    fixed_latency_old = fixed_latency_new;


  }
  else {
    pointer_CS++;
  }

  // conversion of the current value from raw to physical value in mA

  temp_FLOAT_var = (float) sqrt(H1_CS_sum / CS_vector_size) / H_current_conversion_1 * HB_current_conversion_2;
  current_sense_H_vector[0] = temp_FLOAT_var;

  temp_FLOAT_var = (float) sqrt(H2_CS_sum / CS_vector_size) / H_current_conversion_1 * HB_current_conversion_2;
  current_sense_H_vector[1] = temp_FLOAT_var;

  temp_FLOAT_var = (float) sqrt(H3_CS_sum / CS_vector_size) / H_current_conversion_1 * HB_current_conversion_2;
  current_sense_H_vector[2] = temp_FLOAT_var;

  temp_FLOAT_var = (float) sqrt(H4_CS_sum / CS_vector_size) / H_current_conversion_1 * HB_current_conversion_2;
  current_sense_H_vector[3] = temp_FLOAT_var;

  temp_FLOAT_var = (float) sqrt(FAN_CS_sum / CS_vector_size) / B_current_conversion_1 * HB_current_conversion_2;
  current_sense_FAN = temp_FLOAT_var;
}


/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// routine for comparing the current sense values to the thresholds for error management     PRELIMINARY
/////////////////////////////////////////////////////////////////////////////

void validation_current_vs_status() {

  // blower current management
  switch (G_current_status) {

    case 0:
      if (current_sense_FAN < current_FAN_min[0]) {
        system_status_LED[3] = 1;
      }
      if (current_sense_FAN > current_FAN_max[0]) {
        system_status_LED[3] = 1;
      }

      break;
    case 1:

      if (current_sense_FAN < current_FAN_min[current_speed + 1]) {
        system_status_LED[3] = 1;
      }
      if (current_sense_FAN > current_FAN_max[current_speed + 1]) {
        system_status_LED[3] = 1;
      }
      break;
  }





}


/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// routine for sending the monitored values on the virtual COM port if a USB connection is detected
/////////////////////////////////////////////////////////////////////////////


void USB_status_verbose() {

  //Serial.print("Current time = ");
  Serial.println(current_time);
  //Serial.print("system status index = ");
  Serial.println(system_status_index);
  //Serial.print("FAN status  = ");
  Serial.println(FAN_status);
  //Serial.print("HEATER status  = ");
  Serial.println(HEATER_status);
  //Serial.print("FAN UC / OC status = ");
  Serial.println(system_status_LED[3]);
  //Serial.print("H1 UC / OC status = ");
  Serial.println(system_status_LED[4]);
  //Serial.print("H2 UC / OC status = ");
  Serial.println(system_status_LED[5]);
  //Serial.print("H3 UC / OC status = ");
  Serial.println(system_status_LED[6]);
  //Serial.print("H4 UC / OC status = ");
  Serial.println(system_status_LED[7]);
  //Serial.print("G status  = ");
  Serial.println(G_current_status);
  //Serial.print("W1 status  = ");
  Serial.println(W1_current_status);
  //Serial.print("W2 status  = ");
  Serial.println(W2_current_status);
  //Serial.print("W3 status  = ");
  Serial.println(W3_current_status);
  //Serial.print("Thermistor 1 value (C) = ");
  Serial.println(TH1_value);
  //Serial.print("Thermistor 2 value (C) = ");
  Serial.println(TH2_value);
  //Serial.print("FAN RMS current (mA) = ");
  Serial.println(current_sense_FAN);
  //Serial.print("H1 RMS current (mA) = ");
  Serial.println(current_sense_H_vector[0]);
  //Serial.print("H2 RMS current (mA) = ");
  Serial.println(current_sense_H_vector[1]);
  //Serial.print("H3 RMS current (mA) = ");
  Serial.println(current_sense_H_vector[2]);
  //Serial.print("H4 RMS current (mA) = ");
  Serial.println(current_sense_H_vector[3]);
  //Serial.print("loop delay = ");
  Serial.println(fixed_latency_new);

  time_USB = millis();
}


/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// error management and system status update    PRELIMINARY
/////////////////////////////////////////////////////////////////////////////

void system_status_update() {

  FAN_status = 0;

  if (G_current_status == 1) {
    FAN_status = current_speed + 1;
  }

  HEATER_status = 0;

  if (W1_current_status == 1) {
    HEATER_status = HEATER_status + W1_heating_state;

    if (W2_current_status == 1) {
      HEATER_status = HEATER_status + W2_heating_state;
    }
  }

  system_status_index = FAN_status * 5 + HEATER_status;

  // Check whether we are in error mode

  if (system_status_LED[3] > 0) {


    // blower issue, major error, heat strips cannot be operated (not implemented yet)

    system_status_index = 26;

  }
  else
  {


    // Limp mode management, blower is working but at least 1 heat strip is not functionnal

    if (system_status_LED[4] + system_status_LED[5] + system_status_LED[6] + system_status_LED[7] > 0) {

      if ((system_status_LED[4] == 0) || (system_status_LED[5] == 0) || (system_status_LED[6] == 0) || (system_status_LED[7] == 0)) {


        // At least one heat strip is not functionnal, we are in limp mode

        system_status_index = 25;
      }
      else
      {

        // Every heat strips are not functionnal, major error
        system_status_index = 26;
      }
    }
  }

  system_status_LED[0] = pgm_read_word(&LED1[system_status_index]);
  system_status_LED[1] = pgm_read_word(&LED2[system_status_index]);
  system_status_LED[2] = pgm_read_word(&LED3[system_status_index]);

}

/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// main loop initialization
/////////////////////////////////////////////////////////////////////////////


void setup() {

  Serial.begin(9600);

  // initialisation

  pinMode(LED_ctrl, OUTPUT);
  pinMode(H1_ctrl, OUTPUT);
  pinMode(H2_ctrl, OUTPUT);
  pinMode(H3_ctrl, OUTPUT);
  pinMode(H4_ctrl, OUTPUT);
  pinMode(BLO_ctrl, OUTPUT);
  pinMode(BMED_ctrl, OUTPUT);
  pinMode(BHI_ctrl, OUTPUT);
  pinMode(BSSR_ctrl, OUTPUT);
  pinMode(G, INPUT);
  pinMode(W1, INPUT);
  pinMode(W2, INPUT);
  pinMode(W3, INPUT);

  // analog inputs initialization

  analogRead(TH1);
  analogRead(TH2);
  analogRead(CSH1);
  analogRead(CSH2);
  analogRead(CSH3);
  analogRead(CSH4);
  analogRead(CSFAN);

  // LED light initialization

  digitalWrite(LED_ctrl, LOW);   // HIGH = RED,   LOW = GREEN

  // initialization of the heat strips relays
  digitalWrite(H1_ctrl, LOW);
  digitalWrite(H2_ctrl, LOW);
  digitalWrite(H3_ctrl, LOW);
  digitalWrite(H4_ctrl, LOW);

  // initialization of blower relays
  digitalWrite(BSSR_ctrl, LOW);
  digitalWrite(BHI_ctrl, LOW);
  digitalWrite(BMED_ctrl, LOW);
  digitalWrite(BLO_ctrl, LOW);


  // initialization of the vectors
  memset(H1_current_vector, 0, CS_vector_size);
  memset(H2_current_vector, 0, CS_vector_size);
  memset(H3_current_vector, 0, CS_vector_size);
  memset(H4_current_vector, 0, CS_vector_size);
  memset(FAN_current_vector, 0, CS_vector_size);
  memset(current_sense_H_vector, 0, nb_of_heating_elements);


}


/////////////////////////////////////////////////////////////////////////////






/////////////////////////////////////////////////////////////////////////////
// main loop
/////////////////////////////////////////////////////////////////////////////



void loop() {


  // reading of the thermostat signals


  G_new_status = (digitalRead(G) == LOW);   //   inverse logic, G_new_status is equal to 1 (fan activation) when the actual physical signal on the board is LOW
  W1_new_status = (digitalRead(W1) == LOW); //   inverse logic, W1_new_status is equal to 1 (heat command active) when the actual physical signal on the board is LOW
  W2_new_status = (digitalRead(W2) == LOW); //   inverse logic, W2_new_status is equal to 1 (heat command active) when the actual physical signal on the board is LOW
  W3_new_status = (digitalRead(W3) == LOW); //   inverse logic, W3_new_status is equal to 1 (heat pump is active) when the actual physical signal on the board is LOW

  // current time is updated once per cycle in the main loop
  current_time = millis();


  // rollover just happenend, reseeting of all the time stamps, the usual delays between the different steps will be longer just this one time, once every 50 days
  if (old_current_time > current_time) {

    time_H0 = current_time;
    time_W1 = current_time;
    time_W2 = current_time;
    time_G0 = current_time;
    time_G = current_time;
    time_old_CS = current_time;
    time_new_CS = current_time;
    time_SC = current_time;
    time_AF = current_time;
    time_USB = current_time;
  }

  old_current_time = current_time;


  // Update of the thermistor temperature at every loop (mostly for monitoring purposes as it could be performed only when the blower is active)
  TH1_value = read_temp(TH1);
  TH2_value = read_temp(TH2);

  // security check, if the heat commands (W1 or W2) or the heat pump (W3) are active, we make sure to operate the blower
  if ((W1_current_status == 1 || W2_current_status == 1 || W3_current_status == 1) && (G_current_status == 0 || G_new_status == 0 )) {

    G_new_status = 1;

  }



  // a change has been detected on the blower G command status
  if (G_new_status != G_current_status) {

    activate_G();
  }


  // optimization of the blower speed and change of its speed are performed only after a fixed delay (after power-up and after a previous speed change)
  if (G_current_status == 1) {

    if ((long)(current_time - time_G0) >= delta_G0) {

      if ((long)(current_time - time_G) >= delta_G) {

        new_speed_evaluation();
        speed_change();
      }
    }
  }

  // a change has been detected on the W1 heat command status
  if (W1_new_status != W1_current_status && (long)(current_time - time_H0) >= delta_H)  {
    activate_W1();
  }



  // a change has been detected on the W2 heat command status
  if (W2_new_status != W2_current_status && (long)(current_time - time_H0) >= delta_H) {
    activate_W2();
  }


  // delay validation before activating the second heat strip of W1
  if (W1_current_status == 1 && W1_new_status == 1) {
    if ((long)(current_time - time_W1) / 1000.0 >= delta_W1_level_1 && W1_heating_state == 1 && (long)(current_time - time_H0) >= delta_H) {
      activate_W1();
    }
  }


  // delay validation before activating the second heat strip of W2
  if (W2_current_status == 1 && W2_new_status == 1) {
    if ((long)(current_time - time_W2) / 1000.0 >= delta_W2_level_1 && W2_heating_state == 1 && (long)(current_time - time_H0) >= delta_H) {
      activate_W2();
    }

  }

  // a change has been detected on the W3 heat command status (heat pump)
  if (W3_new_status != W3_current_status) {
    W3_current_status = W3_new_status;
  }



  // current sense value update
  current_update();


  // error management PRELIMINARY
  validation_current_vs_status();

  // system status update PRELIMINARY
  system_status_update();



  // Verification whether a USB connection is present

  if (Serial && USB_connection_status == 0) {
    USB_connection_status = 1;
  }

  if (!Serial && USB_connection_status == 1) {
    USB_connection_status = 0;
  }


  // Delay validation and communication with the PC if the USB connection is active
  if ((long)(current_time - time_USB) >= USB_comm_period && USB_connection_status == 1) {

    USB_status_verbose();
  }




  // fixed latency for perfect synchonization with the 60 Hz electrical AC for precise current sense evaluation
  delayMicroseconds(fixed_latency_new);

}

Schematics

schematic
Schematic publish 8u6tlpwkgb
24VAC interfacing
how to interface 24VAC to Arduino
24vac interfacing cstviblgvn

Comments

Similar projects you might like

Arduino Ethernet Controller

Project showcase by Team TATCO Inc

  • 4,552 views
  • 1 comment
  • 22 respects

Arduino Yun Controller

Project showcase by Team TATCO Inc

  • 2,476 views
  • 3 comments
  • 11 respects

Multi-Zone Heating Controller

Project tutorial by erkr

  • 4,001 views
  • 4 comments
  • 13 respects

Using Android Smart Phone to Remote Controller

Project tutorial by Makewith

  • 2,153 views
  • 1 comment
  • 6 respects

Portable - Non-Invasive Light Controller & More

Project tutorial by Emmanuel Garuz

  • 428 views
  • 0 comments
  • 2 respects

Reef Controller

Project showcase by Shawn Leclair

  • 13,537 views
  • 0 comments
  • 19 respects
Add projectSign up / Login