Hardware components | ||||||
![]() |
| × | 4 | |||
![]() |
| × | 2 | |||
![]() |
| × | 1 | |||
![]() |
| × | 7 | |||
Software apps and online services | ||||||
![]() |
| |||||
What do you do if you have to expand the house because of the family expansion :) This means new roof and there are a lot of new free space under the attic. This shall be filled with a heating system which helps the already given well operating condensing boiler. The control systems was created with good old Arduino Unos and Megas! They are great because cheap, simple and proven in use!
Please note if you are not fond of system development and just want to get your house heated, then buy a standard out-of-the-box system it will do its job just fine. But if you are interested in outside of the box thinking and interested in system development just enjoy and ask me! :)
So, the heart of the system is an electric heated 300l buffer tank with a heat exchanger. The heat exchanger is connected to the heating, and also to the hot water pipes.
The buffer tank with heat exchanger:
The buffer tank connected to the heating and the hot water system with motorized 3-way zone valves. There are also check valves and air vent valves installed in the system, but they are not in the following picture. The buffer tank also has a water expansion tank to prevent the buffer tank from explosion in case of boiling water. Anyway, this is only one of several defense lines against dangerous behavior.
In the first solution the buffer tank’s electrical heating circuit was connected directly to the PV panels. For the switching on I used two elements, a high voltage (300VDC) solid state relay for DC circuits and a high performance relay with a snubber circuit. The solid state relay stopped the main current but the relay was needed because of the leakage current of the solid state relay even in off state.
It is a simple and good solution in late spring, during the summer or early fall. When another parts of the year are approaching, this simple circuit cannot get the maximum performance of the PV panels. So another solution was needed.
First I was considering doing an MPPT or PWM control in the circuit, but to do that LC or some kind of filtering circuit would be needed. It could work, but after all, if the tank is fully heated and the PV panels still provide some energy, it could be stored somewhere else. Because of that I ordered an inverter which can do MPPT and have two outputs, one for the heating one for the to be implemented charging.
So in this solution the PV panels are connected directly to the inverter, anyway it is an off-line inverter, and the inverter’s first output is connected to the buffer tank electrical heating circuit. The DC solid state relay was removed from the circuit.
FTA and System FMEAIt is obvious that too high temperature in the tank can cause serious problems so a safety concept shall be created :)
The most dangerous event if the contactor which connects the solar panels to the heating of the buffer tank remains switched on even in case of already too high temperature in the buffer tank.
The items and their possible failures are detailed in the following short System FMEA table:
The top event which is the too high temperature and pressure in the buffer tank shall be avoided.
The overall safe state for the heating is the switch off in case of any kind of failure. There is a mechanical backup in the form of an expansion tank but it is not really a good idea to leave everything to the last safety belt. The avoidance of this event shall be supported by several effective safety measures as the following picture shows:
To overcome the possible HW Contactor failure which might be the caused by eroding the contacts of the magnetic switch by electrical arching, a high performance contactor was selected. The magnetic switch was designed for 80A.
Measurement conceptBased on the FTA and System FMEA the most important event to be avoided is clear and it is also given how it can be done within the system.
The following measurement concept was created for the buffer tank. Two temperature sensors (T1 and T2) were mounted on the buffer tank, both are measuring the temperature of the water inside the tank. They are providing the same information. So, here redundancy is used to increase the availability of the temperature measurement.
The temperature measurement is close to the tank which is exposed to environmental effects, the controller is separated and placed in a metal control cabinet to provide stable temperature and EMC working conditions. Since the measurement and the controller are separated the measured temperature shall be sent to the controller. I am using Arduino libraries for the communication and not writing them from scratch. They are great and lets say proven in use but there might be also a case which was not tested and results in a fault, error and failure. So, using the same bus redundantly might be good idea but it gives a small chance for common cause failure. To exclude these I implemented two buses: CAN and RS485. The Arduinos are connected to the CAN network via MCP2515, while to the RS485 via MAX485. They are really simple and as I wrote before proven in use.
ArThe measured temperature inside the tank is sent to a controller in a diverse and redundant way. It shall be processed and the correct control decision made. Actually, there might be always a tricky situation which results in a faulty behavior. To overcome this situation lets use redundancy for error checking. There is an Arduino Mega which is executing the control algorithm along with handling of the HMI and there is another Arduino Mega which is just making the system safe. Actually the controller has also some self-diagnostic but it is much safer to make the system safe with an independent HW. The architecture is something like that:
The Arduino which is responsible for the safety of the system is also connected to the same CAN and RS485 network. It is receiving the same temperatures. In case of any failure or too high temperature, the checker will open the loop of the magnetic switch of the heating.
The Arduino HW is simple and reliable, however FMEAs shall be done to discover the possible failure modes and their effect on the system. Anyway it also an advantage that it is cheap, and if one board has a failure then it can be replaced easily. But what can be done is putting the controllers into a separated and cooled metal control cabinet. The cooling helps in case the environmental temperature is too high, while the metal cabinet is helping against EMC noises. The inputs and outputs are also shall be galvanically separated, I mean electrically decoupled against conductive EMC disturbances: Optocouplers at the inputs, relays at the outputs.
All the SW used in this project were written keeping the simplicity and defensive programming in focus. I did not used any RTOS. Actually for the Unos it is not even a good idea, because of the lack of memory. I put all the codes into functions and calling them from the loop. AI was used to count the cyclomatic complexity and in some cases for refactoring the code to reduce the complexity helping the testing.
The data received via CAN and RS485 are timestamped, all the controllers are equipped with RTC. If the RTC fails, the system goes to the safe state. The buses have no priority, the controller takes the data from the one which is available or newer. This happens based on the timestamps. If both timestamps are within a tolerance then the average is taken.
In the communication on both bus CRC, message counter and message timeout are used to get rid of integrity and availability problems. Based on this a quality of the signal is set. In any function, before doing anything this quality shall be checked and also the range, so the plausibility of the signal.
In case both of the temperature sensors are lost, the system changes back to the condensing boiler. To make the system more robust, in case both sensors have a bad QoS the controllers are trying to restart their communication.
Functional description1. The main function of the system is to keep the water inside the tank around 80°C. If it is lower than that, the controller shall switch the heating on. If the temperature is higher than 80°C then it shall switch off the heating. The heating of the tank is time dependent, since the inverter can supply the tank from the electrical network and the aim is to only heat it from the PV energy.
2. The buffer tank is used differently than the condensing boiled. If the temperature inside the buffer is higher than the lower limit (40°C), the system shall circulate the hot water in the radiators hourly. The circulation takes place in 10 minutes duration in every hour. Anyway, it is a bit complicated than that J the following picture shows how it is operating in the heating season.
3. If the temperature is not enough in the tank or there is a system failure than the heating request shall be transferred to the condensing boiler. It happens via NC contacts, so if the Arduino fails, the heating request contact can get to the condensing boiler.
#include "RTC.h"
#include <NTPClient.h>
#include "Arduino_LED_Matrix.h" // Include the LED_Matrix library
#include "WiFiS3.h"
#include <WiFiUdp.h>
#include <WDT.h>
#include "FspTimer.h"
#include "CRC.h"
#include <SPI.h>
#include <mcp2515.h>
//#define DisplaySerial SerialS
#include "Diablo_Serial_4DLib.h"
#include "Diablo_Const4D.h"
#include <SoftwareSerial.h>
//#define DisplaySerial Serial
SoftwareSerial DisplaySerial(2,3) ; // pin 2 = TX of display, pin3 = RX
#define DE 7
#define RE 6
#define RESETLINE 4
//array size for in to char array conversion
#define ARRAY_SIZE 5
#define BUFFER_SIZE 64 // maximális üzenethossz
char DataToBeProcessed[BUFFER_SIZE];
uint8_t dataIndex = 0;
Diablo_Serial_4DLib Display(&DisplaySerial);
struct can_frame RecvcanMsg;
struct can_frame SentcanMsg;
MCP2515 mcp2515(10);
char ssid[] = "KurusaNet"; // your network SSID (name)
char pass[] = "62272311"; // your network password (use for WPA, or use as key for WEP)
IPAddress ip;
int wifiStatus = WL_IDLE_STATUS;
WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
NTPClient timeClient(Udp);
RTCTime currentTime, Default_TimeStamp; // current time
int keyIndex = 0; // your network key index number (needed only for WEP)
int status = WL_IDLE_STATUS;
WiFiServer server(80);
/***********watchdog variable *************/
const long wdtInterval = 2400;
/*********** global variables *************/
unsigned long exectuion_number = 0;
/* CAN */
int first_execution = 0, previousSec = 0, act_sec = 0, CANMsgTimeout = 38, RS485MsgTimeout = 38, previousMin = 0, act_Min = 0, SerialResetErrorCounter = 1, CANResetErrorCounter = 1;
/* Display */
int ScreenNumber = 0, ActScreenNumber = 0, ScreenDrawn = 0, ScreenSelectorButtonState = 0, ScreenSelectorButtonRisingEdge = 0, ScreenSelectorButtonPin = 5;
int AutoManState = 0, AutManStateN = 9999; // 0 - Manual, 1 -Automatic
bool HeatingSeason = false;
unsigned int HeatingState = 0;
#define HeatingBefore2pm 10
#define HeatingAfter2pm 20
#define HeatingAfter10pm 30
int PVHeatingOutput = 0;
int CurrentValue = 0.0;
byte CRCcalc[8];
/* Arrays for the graphical view*/
float T1SecondAvg[60], T1Avg[60], T2SecondAvg[60], T2Avg[60];
int TSecondPtr = 0, TAvgPtr = 0;
struct Temp {
float Temperature = 40000.0;
float CAN_Temperature = 40000.0;
float RS485_Temperature = 40000.0;
unsigned int QoS = 65534; /* 1 - OK, 2 - CAN Error, 3 - RS485 Error, Any other - both wrong*/
unsigned int CAN_QoS = 65534; /* 1 - OK, Any other - CAN Error*/
unsigned int RS485_QoS = 65534; /* 1 - OK, Any other - RS485 Error*/
unsigned int CAN_timeout = 0;
unsigned int RS485_timeout = 0;
byte CAN_PrevMsgCounter = 255;
byte RS485_PrevMsgCounter = 255;
RTCTime CAN_TimeStamp = Default_TimeStamp;
RTCTime RS485_TimeStamp = Default_TimeStamp;
unsigned int CAN_ErrorState = 0; /* 0 - default, 1 - active error occurence, 2 - previous error occurence*/
RTCTime CAN_ErrorTimeStamp = Default_TimeStamp;
unsigned int RS485_ErrorState = 0; /* 0 - default, 1 - active error occurence, 2 - previous error occurence*/
RTCTime RS485_ErrorTimeStamp = Default_TimeStamp;
};
/* The N is storing the previous state. If it is different then display it*/
struct Temp T1;
struct Temp T1N;
struct Temp T2;
struct Temp T2N;
struct Temp T3;
struct Temp T3N;
struct Temp T4;
struct Temp T4N;
struct Temp T_Min;
struct Temp T_Max;
struct Flw {
float Flow = 40000.0;
float CAN_Flow = 40000.0;
float RS485_Flow = 40000.0;
unsigned int QoS = 65534; /* 1 - OK, 2 - CAN Error, 3 - RS485 Error, Any other - both wrong*/
unsigned int CAN_QoS = 65534; /* 1 - OK, Any other - CAN Error*/
unsigned int RS485_QoS = 65534; /* 1 - OK, Any other - RS485 Error*/
unsigned int CAN_timeout = 0;
unsigned int RS485_timeout = 0;
byte CAN_PrevMsgCounter = 0;
byte RS485_PrevMsgCounter = 0;
RTCTime CAN_TimeStamp = Default_TimeStamp;
RTCTime RS485_TimeStamp = Default_TimeStamp;
unsigned int CAN_ErrorState = 0; /* 0 - default, 1 - active error occurence, 2 - previous error occurence*/
RTCTime CAN_ErrorTimeStamp = Default_TimeStamp;
unsigned int RS485_ErrorState = 0; /* 0 - default, 1 - active error occurence, 2 - previous error occurence*/
RTCTime RS485_ErrorTimeStamp = Default_TimeStamp;
};
/* The N is storing the previous state. If it is different then display it*/
struct Flw F3;
struct Flw F3N;
struct Flw F4;
struct Flw F4N;
struct Valve {
int State = 2; /* 0 - closed, 1 - opened, 2 - error*/
unsigned int QoS = 65534;
unsigned int timeout = 0;
};
/* The N is storing the previous state. If it is different then display it*/
struct Valve V1;
struct Valve V1N;
struct Valve V2;
struct Valve V2N;
struct Pump {
int State = 2; /* 0 - stopped, 1 - running, 2 - error*/
unsigned int QoS = 65534;
unsigned int timeout = 0;
};
/* The N is storing the previous state. If it is different then display it*/
struct Pump P1;
struct Pump P1N;
struct Relay {
int State = 2; /* 0 - off, 1 - on, 2 - error*/
unsigned int QoS = 65534; /* 1 - OK, anything other - wrong*/
unsigned int timeout = 0;
};
struct Relay PVHeating;
struct Relay PVHeatingN;
void setup(){
pinMode(ScreenSelectorButtonPin, INPUT_PULLUP);
//pinMode(13, OUTPUT);
// Set the DE and RE pins as outputs
pinMode(DE, OUTPUT);
pinMode(RE, OUTPUT);
// Set DE and RE LOW to disable transmission mode
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
Serial.begin(115200);
Serial1.begin(115200);
Serial.println("Wifi Conn");
connectToWiFi();
Serial.println("RTC startup");
RTC.begin();
Serial.println("Timer begin");
timeClient.begin();
timeClient.update();
server.begin();
SentcanMsg.can_id = 0x060;
SentcanMsg.can_dlc = 8;
SentcanMsg.data[0] = 0x00;
SentcanMsg.data[1] = 0x00;
SentcanMsg.data[2] = 0x00;
SentcanMsg.data[3] = 0x00;
SentcanMsg.data[4] = 0x00;
SentcanMsg.data[5] = 0x00;
SentcanMsg.data[6] = 0x00;
SentcanMsg.data[7] = 0x00;
RecvcanMsg.can_id = 0x110;
RecvcanMsg.can_dlc = 8;
RecvcanMsg.data[0] = 0x00;
RecvcanMsg.data[1] = 0x00;
RecvcanMsg.data[2] = 0x00;
RecvcanMsg.data[3] = 0x00;
RecvcanMsg.data[4] = 0x00;
RecvcanMsg.data[5] = 0x00;
RecvcanMsg.data[6] = 0x00;
RecvcanMsg.data[7] = 0x00;
Serial.println("CAN Begin");
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS);
mcp2515.setNormalMode();
Serial.println("WDT setup");
WDT.begin(wdtInterval);
WDT.refresh();
Timer_update();
CANMsgSending();
Serial.println("Startup");
}
void loop(){
/*Update the RTC @ in every hour to compensate the shifting*/
if(currentTime.getMinutes() >= 59){
Serial.println("timer update");
Timer_update();
//CANMsgSending();
}
/*Reading the actual time*/
RTC.getTime(currentTime);
/*Getting the actual sec*/
act_sec = currentTime.getSeconds();
act_Min = currentTime.getMinutes();
/*checking the screen selector input pin*/
digitalInputReading(ScreenSelectorButtonPin, &ScreenSelectorButtonState, &ScreenSelectorButtonRisingEdge, first_execution);
CANMsgReceiving();
// Set DE and RE LOW to disable transmission mode
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
RS485MsgReceiving();
CAN_RS485_MsgTimeOutCheck();
SignalAvailabilityCheckAndSelection();
DisplayHandling();
ChangeUpdate();
TempAvgCounting();
Webserver();
WDT.refresh();
/*increment the execution number*/
exectuion_number++;
/*compare it with a limit and set the variable*/
if(exectuion_number > 3){
first_execution = 1;
} else {
first_execution = 0;
}
}
void DisplayHandling(){
if(ScreenSelectorButtonRisingEdge){
ScreenNumber++;
/*1since only 3 screens are implemented, the number shall be limited to 3*/
}
if(ScreenNumber > 2){
ScreenNumber = 0;
}
/*The static object shall be drawn if the act screen number is different from the actual*/
if(ScreenNumber != ActScreenNumber){
ActScreenNumber = ScreenNumber;
ScreenDrawn = 0;
}
switch(ScreenNumber){
case 0:
/*drawing the main screen*/
if(!ScreenDrawn){
DisplaySerial.begin(9600);
Display.TimeLimit4D = 5000; // 5 second timeout on all commands
Display.gfx_Cls();
Display.gfx_BGcolour(BLACK); // reset
Display.txt_BGcolour(WHITE); // reset
Display.gfx_ScreenMode(LANDSCAPE);
/*Displaying the buffer*/
Display.gfx_Rectangle(50,50, 150,200, BLUE);
Display.gfx_Line(40, 85, 60, 85, LIGHTGREY);
Display.gfx_Line(40, 155, 60, 155, LIGHTGREY);
/*Displaying the kazán :-)*/
Display.gfx_Rectangle(250, 200, 350, 250, LIGHTGREY);
/*Dispalying the radiators*/
Display.gfx_Rectangle(400, 50, 460, 150, LIGHTGREY);
/*Displaying the pipes*/
/*heating Horizontal red pinpe*/
Display.gfx_Line(150, 70, 400, 70, RED);
/*Heating Veritcal red pipe*/
Display.gfx_Line(285, 70, 285, 200, RED);
/*heating Horizontal blue pinpe*/
Display.gfx_Line(150, 140, 400, 140, BLUE);
/*Heating Veritcal blue pipe*/
Display.gfx_Line(315, 140, 315, 200, BLUE);
/*Heating Veritcal blue pipe from the valve to the pipe*/
Display.gfx_Line(240, 95, 240, 140, BLUE);
/*Hot water pipe from Buffer*/
Display.gfx_Line(100, 200, 100, 270, BLUE);
/* Hot water pipe from Boiler to valve*/
Display.gfx_Line(100, 225, 250, 225, BLUE);
ScreenDrawn = 1;
}
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
if(AutoManState != AutManStateN){
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(0,1);
if(AutoManState){
Display.putstr("AUT mode");
} else {
Display.putstr("MAN mode");
}
}
if(PVHeatingOutput){
Display.gfx_Rectangle(60,80, 90,160, GREEN);
} else {
Display.gfx_Rectangle(60,80, 90,160, WHITE);
}
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(7,3);
Display.putstr("I=");
/* Converting the received float to char array*/
char buffer1[10];
dtostrf(CurrentValue, 4, 1, buffer1);
Display.txt_MoveCursor(7,4);
Display.putstr(buffer1);
Display.txt_BGcolour(YELLOW);
Display.txt_FGcolour(BLACK);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(18,10);
if(HeatingSeason){
switch(HeatingState){
case HeatingBefore2pm: {
Display.putstr("Futes szezon: futes 2 elott");
break;
}
case HeatingAfter2pm:{
Display.putstr("Futes szezon: futes 2 utan");
break;
}
case HeatingAfter10pm:{
Display.putstr("Futes szezon: futes 10 utan");
break;
}
default:{
Display.putstr("Futes szezon: default");
}
}
} else {
Display.putstr("Nincs Futes szezon");
}
char IPAddress[3];
IPAddress[0] = ((ip[0] / 100) + 48);
IPAddress[1] = (((ip[0] % 100) / 10) + 48);
IPAddress[2] = (((ip[0] % 100) % 10) + 48);
Display.txt_BGcolour(YELLOW);
Display.txt_FGcolour(BLACK);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(19,4);
Display.putstr(IPAddress);
Display.txt_MoveCursor(19,7);
Display.putstr(".");
Display.txt_MoveCursor(19,8);
IPAddress[0] = ((ip[1] / 100) + 48);
IPAddress[1] = (((ip[1] % 100) / 10) + 48);
IPAddress[2] = (((ip[1] % 100) % 10) + 48);
Display.putstr(IPAddress);
Display.txt_MoveCursor(19,11);
Display.putstr(".");
Display.txt_MoveCursor(19,12);
IPAddress[0] = ((ip[2] / 100) + 48);
IPAddress[1] = (((ip[2] % 100) / 10) + 48);
IPAddress[2] = (((ip[2] % 100) % 10) + 48);
Display.putstr(IPAddress);
Display.txt_MoveCursor(19,15);
Display.putstr(".");
Display.txt_MoveCursor(19,16);
IPAddress[0] = ((ip[3] / 100) + 48);
IPAddress[1] = (((ip[3] % 100) / 10) + 48);
IPAddress[2] = (((ip[3] % 100) % 10) + 48);
Display.putstr(IPAddress);
if(previousSec != act_sec){
int DataToBeSent[6];
char DataInChar[30] = "";
String ToBeDisplayed = "";
DataToBeSent[0] = currentTime.getYear();
DataToBeSent[1] = Month2int(currentTime.getMonth());
DataToBeSent[2] = currentTime.getDayOfMonth();
DataToBeSent[3] = currentTime.getHour();
DataToBeSent[4] = currentTime.getMinutes();
DataToBeSent[5] = act_sec;
/*adding all data to the string to be displayed*/
for(int i = 0; i < 6; i++){
ToBeDisplayed+=String(DataToBeSent[i]);
ToBeDisplayed+=":";
}
/*The char array can be displayed*/
ToBeDisplayed.toCharArray(DataInChar, 30);
Display.txt_MoveCursor(0,20);
Display.putstr(DataInChar);
}
/* The dynaimc elements shall be checked from element to element for a change*/
if((P1.State != P1N.State)||(P1.QoS != P1N.QoS)){
/*Update the P1N to P1 values*/
/*If the QoS is wrong the background shall be red,the front color shall be white*/
if((P1.QoS != 1)||(P1.State == 2)){
Display.gfx_CircleFilled(190, 70, 20, RED);
Display.gfx_Triangle(185,85,185,55,207,70,YELLOW);
} else if(P1.State == 0){
/* If the pump is stopped*/
Display.gfx_CircleFilled(190, 70, 20, YELLOW);
Display.gfx_Triangle(185,85,185,55,207,70,BLACK);
} else if(P1.State == 1){
/* If the pump is running*/
Display.gfx_CircleFilled(190, 70, 20, GREEN);
Display.gfx_Triangle(185,85,185,55,207,70,WHITE);
}
}
if((V1.State != V1N.State)||(V1.QoS != V1N.QoS)){
/*Update the V1N to V1 values*/
/*If the QoS is wrong the background shall be red,the front color shall be white*/
if((V1.QoS != 1)||(V1.State > 2)){
/*Left*/
Display.gfx_TriangleFilled(220,85,220,55,240,70,RED);
/*Right*/
Display.gfx_TriangleFilled(260,85,260,55,240,70,RED);
/*Lower*/
Display.gfx_TriangleFilled(230,95,250,95,240,70,RED);
} else if(V1.State == 2){
/* If the valve is set to Boiler */
/*Left*/
Display.gfx_TriangleFilled(220,85,220,55,240,70,GREEN);
/*Right*/
Display.gfx_TriangleFilled(260,85,260,55,240,70,RED);
/*Lower*/
Display.gfx_TriangleFilled(230,95,250,95,240,70,GREEN);
} else if(V1.State == 1){
/* If the valve is set to Buffer */
/*Left*/
Display.gfx_TriangleFilled(220,85,220,55,240,70,GREEN);
/*Right*/
Display.gfx_TriangleFilled(260,85,260,55,240,70,GREEN);
/*Lower*/
Display.gfx_TriangleFilled(230,95,250,95,240,70,RED);
}
}
if((V2.State != V2N.State)||(V2.QoS != V2N.QoS)){
/*Update the V2N to V2 values*/
/*If the QoS is wrong the background shall be red,the front color shall be white*/
if((V2.QoS != 1)||(V2.State > 2)){
/*Upper*/
Display.gfx_TriangleFilled(85,205,115,205,100,225,RED);
/*Lower*/
Display.gfx_TriangleFilled(85,245,115,245,100,225,RED);
/*Right*/
Display.gfx_TriangleFilled(125,210,125,235,100,225,RED);
} else if(V2.State == 2){
/*Upper*/
Display.gfx_TriangleFilled(85,205,115,205,100,225,RED);
/*Lower*/
Display.gfx_TriangleFilled(85,245,115,245,100,225,GREEN);
/*Right*/
Display.gfx_TriangleFilled(125,210,125,235,100,225,GREEN);
} else if(V2.State == 1){
/*Upper*/
Display.gfx_TriangleFilled(85,205,115,205,100,225,GREEN);
/*Lower*/
Display.gfx_TriangleFilled(85,245,115,245,100,225,RED);
/*Right*/
Display.gfx_TriangleFilled(125,210,125,235,100,225,GREEN);
}
}
Serial.print("T1.QoS: ");
Serial.println(T1.QoS);
/* 1 - OK, 2 - CAN Error, 3 - RS485 Error */
if((T1.Temperature != T1N.Temperature)||(T1.QoS != T1N.QoS)){
if((T1.QoS != 1) && (T1.QoS != 2) && (T1.QoS != 3)){
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(11,5);
Display.putstr("T1=ERR");
} else if((T1.Temperature != T1N.Temperature)){
if((T1.QoS == 1) && (T1.QoS != 2) && (T1.QoS != 3)){
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
} else if((T1.QoS != 1) && (T1.QoS == 2) && (T1.QoS != 3)){
Display.txt_BGcolour(YELLOW);
Display.txt_FGcolour(BLACK);
} else if((T1.QoS != 1) && (T1.QoS != 2) && (T1.QoS == 3)){
Display.txt_BGcolour(ORANGE);
Display.txt_FGcolour(WHITE);
}
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(11,5);
Display.putstr("T1=");
/* Converting the received float to char array*/
char buffer[10];
dtostrf(T1.Temperature, 4, 1, buffer);
Display.txt_MoveCursor(11,8);
Display.putstr(buffer);
}
}
if((T2.Temperature != T2N.Temperature)||(T2.QoS != T2N.QoS)){
if((T2.QoS != 1) && (T2.QoS != 2) && (T2.QoS != 3)){
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(4,5);
Display.putstr("T2=ERR");
} else if((T2.Temperature != T2N.Temperature)){
if((T2.QoS == 1) && (T2.QoS != 2) && (T2.QoS != 3)){
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
} else if((T2.QoS != 1) && (T2.QoS == 2) && (T2.QoS != 3)){
Display.txt_BGcolour(YELLOW);
Display.txt_FGcolour(BLACK);
} else if((T2.QoS != 1) && (T2.QoS != 2) && (T2.QoS == 3)){
Display.txt_BGcolour(ORANGE);
Display.txt_FGcolour(WHITE);
}
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(4,5);
Display.putstr("T2=");
/* Converting the received float to char array*/
char buffer[10];
dtostrf(T2.Temperature, 4, 1, buffer);
Display.txt_MoveCursor(4,8);
Display.putstr(buffer);
}
}
/* if((T3.Temperature != T3N.Temperature)||(T3.QoS != T3N.QoS)){
if(T3.QoS != 1){
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(3,22);
Display.putstr("T3=ERR");
} else if((T3.Temperature != T3N.Temperature)){
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(3,22);
Display.putstr("T3=");
/* Converting the received float to char array*/
/* char buffer[10];
dtostrf(T3.Temperature, 4, 1, buffer);
Display.txt_MoveCursor(3,25);
Display.putstr(buffer);
}
}*/
/* if((T4.Temperature != T4N.Temperature)||(T4.QoS != T4N.QoS)){
if(T4.QoS != 1){
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(17,5);
Display.putstr("T4=ERR");
} else if((T4.Temperature != T4N.Temperature)){
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(17,5);
Display.putstr("T4=");
/* Converting the received float to char array*/
/* char buffer[10];
dtostrf(T4.Temperature, 4, 1, buffer);
Display.txt_MoveCursor(17,8);
Display.putstr(buffer);
}
}*/
/*Serial.print("F3.QoS: ");
Serial.println(F3.QoS);*/
if((F3.Flow != F3N.Flow)||(F3.QoS != F3N.QoS)){
if((F3.QoS != 1) && (F3.QoS != 2) && (F3.QoS != 3)){
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(2,22);
Display.putstr("F3=ERR");
} else if((F3.Flow != F3N.Flow)){
if((F3.QoS == 1) && (F3.QoS != 2) && (F3.QoS != 3)){
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
} else if((F3.QoS != 1) && (F3.QoS == 2) && (F3.QoS != 3)){
Display.txt_BGcolour(YELLOW);
Display.txt_FGcolour(BLACK);
} else if((F3.QoS != 1) && (F3.QoS != 2) && (F3.QoS == 3)){
Display.txt_BGcolour(ORANGE);
Display.txt_FGcolour(WHITE);
}
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(2,22);
Display.putstr("F3=");
/* Converting the received float to char array*/
char buffer[10];
dtostrf(F3.Flow, 4, 1, buffer);
Display.txt_MoveCursor(2,25);
Display.putstr(buffer);
}
}
/*Serial.print("F4.QoS: ");
Serial.println(F4.QoS); */
if((F4.Flow != F4N.Flow)||(F4.QoS != F4N.QoS)){
if((F4.QoS != 1) && (F4.QoS != 2) && (F4.QoS != 3)){
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(16,5);
Display.putstr("f4=ERR");
} else if((F4.Flow != F4N.Flow)){
if((F4.QoS == 1) && (F4.QoS != 2) && (F4.QoS != 3)){
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
} else if((F4.QoS != 1) && (F4.QoS == 2) && (F4.QoS != 3)){
Display.txt_BGcolour(YELLOW);
Display.txt_FGcolour(BLACK);
} else if((F4.QoS != 1) && (F4.QoS != 2) && (F4.QoS == 3)){
Display.txt_BGcolour(ORANGE);
Display.txt_FGcolour(WHITE);
}
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(16,5);
Display.putstr("F4=");
/* Converting the received float to char array*/
char buffer[10];
dtostrf(F4.Flow, 4, 1, buffer);
Display.txt_MoveCursor(16,8);
Display.putstr(buffer);
}
}
break;
case 1: /* Drawing the temperature trend graph*/
if((!ScreenDrawn) || (previousMin != act_Min)){
int TempDataPtr1 = 59, TempDataPtr2 = 58, X1 = 50;
/*Drawing the x and y axis*/
Display.gfx_Cls();
Display.gfx_Line(50, 30, 50, 270, LIGHTGREY);
Display.gfx_Line(50, 270, 450, 270, LIGHTGREY);
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(RED);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(1,10);
Display.putstr("T1");
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(YELLOW);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(1,15);
Display.putstr("T2");
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(2,0);
Display.putstr("100C");
Display.txt_MoveCursor(5,0);
Display.putstr("75C");
Display.txt_MoveCursor(9,0);
Display.putstr("50C");
Display.txt_MoveCursor(12,0);
Display.putstr("25C");
Display.txt_MoveCursor(16,0);
Display.putstr(" 0C");
Display.txt_MoveCursor(18,5);
Display.putstr("60min");
Display.txt_MoveCursor(18,20);
Display.putstr("30min");
Display.txt_MoveCursor(18,33);
Display.putstr("Act T");
/*Drawing the trends from lines*/
/*First the y value shall be calculated from the temperature value
* with the following formula: y = -2.4x + 270*/
for(int i = 0; i < 59; i++){
int T1Y1 = (int)((-2.4 * T1Avg[TempDataPtr1]) + 270);
int T1Y2 = (int)((-2.4 * T1Avg[TempDataPtr2]) + 270);
int T2Y1 = (int)((-2.4 * T2Avg[TempDataPtr1]) + 270);
int T2Y2 = (int)((-2.4 * T2Avg[TempDataPtr2]) + 270);
int X2 = X1 + 7;
Display.gfx_Line(X1, T1Y1, X2, T1Y2, RED);
Display.gfx_Line(X1, T2Y1, X2, T2Y2, YELLOW);
TempDataPtr1 = TempDataPtr2;
TempDataPtr2--;
X1 = X2;
}
ScreenDrawn = 1;
}
break;
case 2: /* Drawing the error table */
if(!ScreenDrawn){
ScreenDrawn = 1;
/* Drawing the table */
/*Cloumns*/
Display.gfx_Cls();
Display.gfx_Line(5, 5, 5, 290, LIGHTGREY);
Display.gfx_Line(40, 5, 40, 290, LIGHTGREY);
Display.gfx_Line(105, 5, 105, 290, LIGHTGREY);
Display.gfx_Line(270, 5, 270, 315, LIGHTGREY);
/*Top*/
Display.gfx_Line(5, 5, 470, 5, LIGHTGREY);
/*T1*/
/*Line next to T1*/
Display.gfx_Line(40, 29, 270, 29, LIGHTGREY);
/*Line under T1*/
Display.gfx_Line(5, 55, 270, 55, LIGHTGREY);
/*Line next to T2*/
Display.gfx_Line(40, 78, 270, 78, LIGHTGREY);
/*Line under T2*/
Display.gfx_Line(5, 100, 270, 100, LIGHTGREY);
/*Line next to T3*/
Display.gfx_Line(40, 122, 270, 122, LIGHTGREY);
/*Line under T3*/
Display.gfx_Line(5, 145, 470, 145, LIGHTGREY);
/*Line next to T4*/
Display.gfx_Line(40, 169, 270, 169, LIGHTGREY);
/*Line under T4*/
Display.gfx_Line(5, 192, 270, 192, LIGHTGREY);
/*Line next to F3*/
Display.gfx_Line(40, 218, 270, 218, LIGHTGREY);
/*Line under F3*/
Display.gfx_Line(5, 242, 270, 242, LIGHTGREY);
/*Line next to F4*/
Display.gfx_Line(40, 270, 270, 270, LIGHTGREY);
/*Line under F4*/
Display.gfx_Line(5, 290, 270, 290, LIGHTGREY);
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(2,1);
Display.putstr("T1");
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(5,1);
Display.putstr("T2");
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(8,1);
Display.putstr("T3");
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(11,1);
Display.putstr("T4");
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(13,1);
Display.putstr("F3");
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(16,1);
Display.putstr("F4");
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(2,23);
Display.putstr("Controller");
Display.txt_BGcolour(BLACK);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_4);
Display.txt_MoveCursor(11,23);
Display.putstr("Checker");
}
if(previousSec != act_sec){
int CANErrorLine = 1, RS485ErrorLine = 3;
T_CommErrors(&T1, CANErrorLine, RS485ErrorLine);
CANErrorLine+=4;
RS485ErrorLine+=4;
T_CommErrors(&T2, CANErrorLine, RS485ErrorLine);
CANErrorLine+=4;
RS485ErrorLine+=4;
// T_CommErrors(&T3, CANErrorLine, RS485ErrorLine);
CANErrorLine+=4;
RS485ErrorLine+=4;
// T_CommErrors(&T4, CANErrorLine, RS485ErrorLine);
CANErrorLine+=4;
RS485ErrorLine+=4;
F_CommErrors(&F3, CANErrorLine, RS485ErrorLine);
CANErrorLine+=4;
RS485ErrorLine+=4;
F_CommErrors(&F4, CANErrorLine, RS485ErrorLine);
/*Controller CAN*/
Display.txt_BGcolour(ORANGE);
Display.txt_FGcolour(BLACK);
Display.txt_FontID(FONT_3);
Display.txt_MoveCursor(5,35);
Display.putstr(" CAN ");
/*CAN Error timestamp*/
Display.txt_BGcolour(ORANGE);
Display.txt_FGcolour(BLACK);
Display.txt_FontID(FONT_3);
Display.txt_MoveCursor(7,35);
Display.putstr("2025:08:08 14:09:10");
/*Controller RS485*/
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_3);
Display.txt_MoveCursor(9,35);
Display.putstr(" RS485 ");
/*RS485 Error timestamp*/
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_3);
Display.txt_MoveCursor(11,35);
Display.putstr("2025:08:08 14:09:10");
/*Checker CAN*/
Display.txt_BGcolour(ORANGE);
Display.txt_FGcolour(BLACK);
Display.txt_FontID(FONT_3);
Display.txt_MoveCursor(17,35);
Display.putstr(" CAN ");
/*CAN Error timestamp*/
Display.txt_BGcolour(ORANGE);
Display.txt_FGcolour(BLACK);
Display.txt_FontID(FONT_3);
Display.txt_MoveCursor(19,35);
Display.putstr("2025:08:08 14:09:10");
/*Checker RS485*/
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_3);
Display.txt_MoveCursor(21,35);
Display.putstr(" RS485 ");
/*RS485 Error timestamp*/
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_3);
Display.txt_MoveCursor(23,35);
Display.putstr("2025:08:08 14:09:10");
}
break;
}
}
void T_CommErrors(struct Temp *T, int CAN_Line, int RS485_Line){
if(T->CAN_ErrorState == 1){
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_3);
Display.txt_MoveCursor(CAN_Line,6);
Display.putstr(" CAN ");
/*CAN Error timestamp*/
Display.txt_BGcolour(RED);
Display.txt_FGcolour(WHITE);
Display.txt_FontID(FONT_3);
PutErrorTimeStampToScreen(CAN_Line, 14, T->CAN_ErrorTimeStamp);
} else {
Display.txt_BGcolour(ORANGE);
Display.txt_FGcolour(BLACK);
Display.txt_FontID(FONT_3);
Display.txt_MoveCursor(1,6);
Display.putstr(" CAN ");
...
This file has been truncated, please download it to see its full contents.
#include <avr/wdt.h>
#include <SPI.h>
#include <mcp2515.h>
#include "CRC.h"
#include <Arduino.h>
#include <RtcDS1302.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4); // I2C address 0x27, 20 column and 4 rows
struct can_frame RecvcanMsg;
struct can_frame SentcanMsg;
MCP2515 mcp2515(53);
#define DisplaySerial Serial
#define DE 23
#define RE 22
#define BUFFER_SIZE 64 // maximális üzenethossz
char DataToBeProcessed[BUFFER_SIZE];
uint8_t dataIndex = 0, dataIndexCurrent = 0;
#define BoilerSelectedInputPinNumber 33
#define BufferSelectedInputPinNumber 35
// DS1302 RTC instance
// CONNECTIONS:
// DS1302 CLK/SCLK --> 5
// DS1302 DAT/IO --> 4
// DS1302 RST/CE --> 2
// DS1302 VCC --> 3.3v - 5v
// DS1302 GND --> GND
ThreeWire myWire(3,5,2); // IO, SCLK, CE
RtcDS1302<ThreeWire> Rtc(myWire);
/*********** global variables *************/
bool HeatingSeason = false;
unsigned int HeatingState = 0;
#define HeatingBefore2pm 10
#define HeatingAfter2pm 20
#define HeatingAfter10pm 30
unsigned long exectuion_number = 0;
/* CAN */
int first_execution = 0, previousSec = 0, actSecond = 0, CANMsgTimeout = 38, RS485MsgTimeout = 38, previousMin = 0, act_Min = 0, act_Hour = 0, act_Month = 0, SerialResetErrorCounter = 1, CANResetErrorCounter = 1;
/* Display */
int ScreenNumber = 0, ActScreenNumber = 0, ScreenDrawn = 0, ScreenSelectorButtonState = 0, ScreenSelectorButtonRisingEdge = 0, ScreenSelectorButtonPin = 36;
/* Arrays for the graphical view*/
float T1SecondAvg[60], T1Avg[60], T2SecondAvg[60], T2Avg[60];
int TSecondPtr = 0, TAvgPtr = 0;
int HeatSource = 0, HeatSource_N = 0; /* 0 - Boiler, 1 - Buffer tank*/
int HeatingRequest = 0; /* It is the signal of the thermostat. 0 - No request, 1 - heating is needed*/
float BufferTankTempLowLimit = 40.0, BufferTankTempHighLimit = 80.0, TempDifferentInsisdeBuffertank = 7.0, TemperatureDifference = 20.0, PVHeatingTemperatureMin = 50.0, ElectricalHeatingMinTemp = 40.0;
unsigned int PVHeatingMinutes = 0, PVHeatingMinutesLimit = 2;
int ValveSelection = 0; /* feedbacks the valve state: 2 - Boiler is selected, 1 - Buffer tank is selected, 3 - Circulation, any other - error*/
int ValveError = 0, ValveErrorCounter = 0, ValveTimoutErrorLimit = 55; /* Counter for counting the valve errors*/
unsigned int ValveTimeOut = 0, ValveTimeOutLimit = 15, ValveDelay = 7, ValveDelayElapsed = 0; /*These are used for the pump start condition checking*/
#define PumpNotRunning 10
#define PumpRunning 20
#define PumpStarting 30
#define PumpErrorState 40
float FlowLowLimit = 0.0;
int PumpControlState = 10, PumpCommand = 0, PumpStartRequest = 0; /* 0 - not running, 1 - running*/
int PumpError = 0, PumpErrorCounter = 0, PumpErrorLimit = 57, PumpStartupCounter = 4; /* Counter for counting the pump errors*/
/**************** Current measurement *****************/
byte CurrentStringInChar[40] = "";
uint8_t CurrentdataIndex = 0;
byte CurrentMsgCount = 0;
int CurrentMsgError = 1;
float CurrentChannel0 = 0.0, CurrentChannel1 = 0.0, CurrentChannel2 = 0.0;
/**************** Digital inputs and outputs *******************/
int HeatSourceBoilerInputPin = BoilerSelectedInputPinNumber, HeatSourceBoilerInput = 0, HeatSourceBoilerInputRisingEdge = 0, HeatSourceBufferInputPin = BufferSelectedInputPinNumber, HeatSourceBufferInput = 0, HeatSourceBufferInputRisingEdge = 0;
int HeatSourceSelectorOutputPin = 10;
int V1BufferInputPin = BufferSelectedInputPinNumber, V1BufferInput = 0, V1BufferInputRisingEdge = 0, V1BoilerInputPin = BoilerSelectedInputPinNumber, V1BoilerInput = 0, V1BoilerInputRisingEdge = 0;
int V2BufferInputPin = BufferSelectedInputPinNumber, V2BufferInput = 0, V2BufferInputRisingEdge = 0, V2BoilerInputPin = BoilerSelectedInputPinNumber, V2BoilerInput = 0, V2BoilerInputRisingEdge = 0;
int PumpStartOutputPin = 11, PumpRelayInputPin = 38, PumpRelayInput = 0, PumpRelayInputRisingEdge = 0;
int ThermostatInputPin = 32, ThermostatInput = 0, ThermostatInputRisingEdge = 0;
int BoilerCircuitOutputPin = 0, BoilerCircuitOutput = 0;
int PumpManualModeStartInputPin = 43, PumpManualModeStartInput = 0, PumpManualStartInputRisingEdge = 0;
int PVManualModeHeatingInputPin = 42, PVManualModeHeatingInput = 0, PVManualHeatingInputRisingEdge = 0;
int PVHeatingOutput = 0, PVHeatingOutputN1 = 0, PVHeatingOutputRisingEdge = 0, PVHeatingOutputFallingEdge = 0;
int PVHeatingSSROutputT_ONDelay = 2, PVHeatingSSROutputT_ONDelayCounter = 0, PVHeatingRelayOutputT_OFFDelay = 2, PVHeatingRelayOutputT_OFFDelayCounter = 0;
int PVHeatingSSROutputPin = 7, PVHeatingSSROutput = 0, PVHeatingOutputPin = 8, PVHeatingRelayOutput = 0;
int PVHeatingRelayInputPin = 41, PVHeatingRelayInput = 0, PVHeatingRelayInputRisingEdge = 0;
int AutoModeInputPin = 49, AutoModeInput = 0, ManModeInputPin = 48, ManModeInput = 0;
int ManModeBoilerSelectionInputPin = 46, ManModeBoilerSelectionInput = 0, ManModeBufferSelectionInputPin = 47, ManModeBufferSelectionInput = 0;
int AutoManState = 0, AutoManStateN = 9999; // 0 - Manual, 1 -Automatic
int AutoModeLEDOutputPin = 9, AutoModeLEDOutput = 0, ControllerWorkingOutputPin = 13, ControllerWorking = 0;
byte MsgCount = 0;
byte CRCcalc[8];
char defaultDate[10] = {"1987:03:11"};
char defaultTime[8] = {"04:30:00"};
RtcDateTime Default_TimeStamp = RtcDateTime(defaultDate, defaultTime);
/* The possible temperature change between messages and the plausibility limits*/
float TemperatureChangeLimit = 10.0, TemperaturePlausibilityMin = 0.0, TemperaturePlausibilityMax = 150.0;
struct Temp {
float Temperature = 40000.0;
float CAN_Temperature = 40000.0;
float RS485_Temperature = 40000.0;
unsigned int QoS = 65534; /* 1 - OK, 2 - CAN Error, 3 - RS485 Error, Any other - both wrong*/
unsigned int CAN_QoS = 65534; /* 1 - OK, Any other - CAN Error*/
unsigned int RS485_QoS = 65534; /* 1 - OK, Any other - RS485 Error*/
unsigned int CAN_timeout = 0;
unsigned int RS485_timeout = 0;
byte CAN_PrevMsgCounter = 255;
byte RS485_PrevMsgCounter = 255;
RtcDateTime CAN_TimeStamp = Default_TimeStamp;
RtcDateTime RS485_TimeStamp = Default_TimeStamp;
unsigned int CAN_ErrorState = 0; /* 0 - default, 1 - active error occurence, 2 - previous error occurence*/
RtcDateTime CAN_ErrorTimeStamp = Default_TimeStamp;
unsigned int RS485_ErrorState = 0; /* 0 - default, 1 - active error occurence, 2 - previous error occurence*/
RtcDateTime RS485_ErrorTimeStamp = Default_TimeStamp;
};
/* The N is storing the previous state. If it is different then display it*/
struct Temp T1;
struct Temp T1N;
struct Temp T2;
struct Temp T2N;
struct Temp T3;
struct Temp T3N;
struct Temp T4;
struct Temp T4N;
struct Temp T_Min;
struct Temp T_Max;
struct Flw {
float Flow = 40000.0;
float CAN_Flow = 40000.0;
float RS485_Flow = 40000.0;
unsigned int QoS = 65534; /* 1 - OK, 2 - CAN Error, 3 - RS485 Error, Any other - both wrong*/
unsigned int CAN_QoS = 65534; /* 1 - OK, Any other - CAN Error*/
unsigned int RS485_QoS = 65534; /* 1 - OK, Any other - RS485 Error*/
unsigned int CAN_timeout = 0;
unsigned int RS485_timeout = 0;
byte CAN_PrevMsgCounter = 0;
byte RS485_PrevMsgCounter = 0;
RtcDateTime CAN_TimeStamp = Default_TimeStamp;
RtcDateTime RS485_TimeStamp = Default_TimeStamp;
unsigned int CAN_ErrorState = 0; /* 0 - default, 1 - active error occurence, 2 - previous error occurence*/
RtcDateTime CAN_ErrorTimeStamp = Default_TimeStamp;
unsigned int RS485_ErrorState = 0; /* 0 - default, 1 - active error occurence, 2 - previous error occurence*/
RtcDateTime RS485_ErrorTimeStamp = Default_TimeStamp;
};
/* The N is storing the previous state. If it is different then display it*/
struct Flw F3;
struct Flw F3N;
struct Flw F4;
struct Flw F4N;
struct Valve {
int State = 9999; /* 1 - Boiler selected, 2 - Buffer selected, Any other - error*/
unsigned int QoS = 65534; /* 1 - OK, anything other - wrong*/
unsigned int timeout = 0;
};
/* The N is storing the previous state. If it is different then display it*/
struct Valve V1;
struct Valve V1N;
struct Valve V2;
struct Valve V2N;
struct Pump {
int State = 2; /* 0 - stopped, 1 - running, 2 - error*/
unsigned int QoS = 65534; /* 1 - OK, anything other - wrong*/
unsigned int timeout = 0;
};
/* The N is storing the previous state. If it is different then display it*/
struct Pump P1;
struct Pump P1N;
struct Relay {
int State = 2; /* 0 - off, 1 - on, 2 - error*/
unsigned int QoS = 65534; /* 1 - OK, anything other - wrong*/
unsigned int timeout = 0;
};
struct Relay PVHeating;
struct Relay PVHeatingN;
struct SwtchIn {
int State = 0;
int QoS = 0;
};
struct SwtchIn AutManStatus;
struct SwtchIn PufferHeatingSelectorStatus;
void setup() {
Serial.begin(115200);
Serial2.begin(115200);
/*********************** watchdog setup **************************************************/
wdt_disable();/* diable the watchdog and wait for more than 2 seconds */
delay(3000); /* Done so the Arduino doesnot keep resetting infinitely in case of wrong configuration*/
wdt_enable(WDTO_4S); /* Enable the watchdog with a timeout of 2 seconds */
pinMode(HeatSourceBoilerInputPin, INPUT_PULLUP);
pinMode(HeatSourceBufferInputPin, INPUT_PULLUP);
pinMode(V1BoilerInputPin, INPUT_PULLUP);
pinMode(V1BufferInputPin, INPUT_PULLUP);
pinMode(PumpRelayInputPin, INPUT_PULLUP);
pinMode(ThermostatInputPin, INPUT_PULLUP);
pinMode(PumpManualModeStartInputPin, INPUT_PULLUP);
pinMode(PVManualModeHeatingInputPin, INPUT_PULLUP);
pinMode(AutoModeInputPin, INPUT_PULLUP);
pinMode(ManModeInputPin, INPUT_PULLUP);
pinMode(ManModeBoilerSelectionInputPin, INPUT_PULLUP);
pinMode(ManModeBufferSelectionInputPin, INPUT_PULLUP);
pinMode(ScreenSelectorButtonPin, INPUT_PULLUP);
pinMode(PVHeatingRelayInputPin, INPUT_PULLUP);
pinMode(HeatSourceSelectorOutputPin, OUTPUT);
pinMode(ControllerWorkingOutputPin, OUTPUT);
pinMode(PumpStartOutputPin, OUTPUT);
pinMode(BoilerCircuitOutputPin, OUTPUT);
pinMode(PVHeatingOutputPin, OUTPUT);
pinMode(PVHeatingSSROutputPin, OUTPUT);
pinMode(AutoModeLEDOutputPin, OUTPUT);
/* digitalWrite(HeatSourceSelectorOutputPin, HIGH);
digitalWrite(ControllerWorkingOutputPin, HIGH);
digitalWrite(PumpStartOutputPin, LOW);
digitalWrite(BoilerCircuitOutputPin, HIGH);
digitalWrite(PVHeatingOutputPin, HIGH);
digitalWrite(PVHeatingSSROutputPin, HIGH);
digitalWrite(AutoModeLEDOutputPin, HIGH);
*/
// Set the DE and RE pins as outputs
pinMode(DE, OUTPUT);
pinMode(RE, OUTPUT);
// Set DE and RE LOW to disable transmission mode
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
Serial3.begin(115200);
Serial2.begin(115200);
SentcanMsg.can_id = 0x000;
SentcanMsg.can_dlc = 8;
SentcanMsg.data[0] = 0x00;
SentcanMsg.data[1] = 0x00;
SentcanMsg.data[2] = 0x00;
SentcanMsg.data[3] = 0x00;
SentcanMsg.data[4] = 0x00;
SentcanMsg.data[5] = 0x00;
SentcanMsg.data[6] = 0x00;
SentcanMsg.data[7] = 0x00;
RecvcanMsg.can_id = 0x110;
RecvcanMsg.can_dlc = 8;
RecvcanMsg.data[0] = 0x00;
RecvcanMsg.data[1] = 0x00;
RecvcanMsg.data[2] = 0x00;
RecvcanMsg.data[3] = 0x00;
RecvcanMsg.data[4] = 0x00;
RecvcanMsg.data[5] = 0x00;
RecvcanMsg.data[6] = 0x00;
RecvcanMsg.data[7] = 0x00;
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS);
mcp2515.setNormalMode();
// initialize the RTC
Serial.print("compiled: ");
Serial.print(__DATE__);
Serial.println(__TIME__);
Serial.println("RTC begin: ");
Rtc.Begin();
RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);
Serial.print("Compile time: ");
printDateTime(compiled);
Serial.println("Setting the RTC");
Rtc.SetDateTime(compiled);
if (!Rtc.IsDateTimeValid())
{
// Common Causes:
// 1) first time you ran and the device wasn't running yet
// 2) the battery on the device is low or even missing
Serial.println("RTC lost confidence in the DateTime!");
}
if (Rtc.GetIsWriteProtected())
{
Serial.println("RTC was write protected, enabling writing now");
Rtc.SetIsWriteProtected(false);
}
if (!Rtc.GetIsRunning())
{
Serial.println("RTC was not actively running, starting now");
Rtc.SetIsRunning(true);
}
lcd.init(); // initialize the lcd
lcd.backlight();
lcd.setCursor(2, 1);
lcd.print("Controller Startup");
Serial.println("Controller Startup");
}
void loop(){
//Serial.println("Input reading");
// Reading the inputs
digitalInputReading(HeatSourceBoilerInputPin, &HeatSourceBoilerInput, &HeatSourceBoilerInputRisingEdge, first_execution);
/*Serial.print("Heat source boiler input: ");
Serial.print(HeatSourceBoilerInput);*/
digitalInputReading(HeatSourceBufferInputPin, &HeatSourceBufferInput, &HeatSourceBufferInputRisingEdge, first_execution);
/*Serial.print(", Heat source buffer input: ");
Serial.println(HeatSourceBufferInput);*/
digitalInputReading(V1BufferInputPin, &V1BufferInput, &V1BufferInputRisingEdge, first_execution);
digitalInputReading(V1BoilerInputPin, &V1BoilerInput, &V1BoilerInputRisingEdge, first_execution);
/* digitalInputReading(V2BufferInputPin, &V2BufferInput, &V2BufferInputRisingEdge, first_execution);
digitalInputReading(V2BoilerInputPin, &V2BoilerInput, &V2BoilerInputRisingEdge, first_execution); */
digitalInputReading(PumpRelayInputPin, &PumpRelayInput, &PumpRelayInputRisingEdge, first_execution);
/*Serial.print("Pump start input: ");
Serial.println(PumpRelayInput);*/
digitalInputReading(ThermostatInputPin, &ThermostatInput, &ThermostatInputRisingEdge, first_execution);
HeatingRequest = !ThermostatInput;
/*Serial.print("HeatingRequest: ");
Serial.println(HeatingRequest);*/
digitalInputReading(PumpManualModeStartInputPin, &PumpManualModeStartInput, &PumpManualStartInputRisingEdge, first_execution);
digitalInputReading(PVHeatingRelayInputPin, &PVHeatingRelayInput, &PVHeatingRelayInputRisingEdge, first_execution);
/*Serial.print("Pump start input: ");
Serial.println(PumpManualModeStartInput);*/
digitalInputReading(PVManualModeHeatingInputPin, &PVManualModeHeatingInput, &PVManualHeatingInputRisingEdge, first_execution);
digitalInputReading(ScreenSelectorButtonPin, &ScreenSelectorButtonState, &ScreenSelectorButtonRisingEdge, first_execution);
/*Serial.print("Screenselector start input: ");
Serial.println(ScreenSelectorButtonState);*/
SwitchInputReading(&AutManStatus, AutoModeInputPin, ManModeInputPin);
SwitchInputReading(&PufferHeatingSelectorStatus, ManModeBoilerSelectionInputPin, ManModeBufferSelectionInputPin);
//Serial.println("RTC reading");
// get the current time
RtcDateTime now = Rtc.GetDateTime();
actSecond = now.Second();
act_Min = now.Minute();
act_Hour = now.Hour();
act_Month = now.Month();
// Set DE and RE LOW to disable transmission mode
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
RS485MsgReceiving();
CANMsgReceiving();
CAN_RS485_MsgTimeOutCheck();
SignalAvailabilityCheckAndSelection();
AutManState();
HeatingAlgorithm();
PVHeatingDelays();
ValveControl();
PumpControl();
CurrentValueReceiving();
Output_Writing(HeatSource, PumpCommand, PVHeatingRelayOutput, PVHeatingSSROutput, AutoModeLEDOutput, ControllerWorking);
/*Serial.print("Pump start command: ");
Serial.println(PumpCommand);*/
if(previousSec != actSecond){
DisplayHandling();
RS485MsgSending();
CANMsgSending();
}
/* Since there is more responsiobility on the RTC clock, it shall be checked frequently.
* If the RTC is not valid, then try to reset the controller and init the RTC*/
if((previousMin != act_Min) && (!Rtc.IsDateTimeValid())){
lcd.clear();
lcd.setCursor(5, 1);
lcd.print("RTC Error, resetting the controller");
while(1){}
}
ChangeUpdate();
TempAvgCounting();
PVHeatingCheck();
PumpStartingCheck();
wdt_reset(); /* Reset the wdt timer */
/*increment the execution number*/
exectuion_number++;
/*compare it with a limit and set the variable*/
if(exectuion_number > 3){
first_execution = 1;
} else {
first_execution = 0;
}
}
void AutManState(){
/*If the State is 2 and QoS is okay, then select auto mode*/
/*Serial.print("QoS: ");
Serial.print(AutManStatus.QoS);
Serial.print("State ");
Serial.println(AutManStatus.State); */
if((AutManStatus.QoS == 1)&&(AutManStatus.State == 2)){
AutoManState = 1;
AutoModeLEDOutput = 1;
//Serial.println("Aut Mode");
/*This else if might not be needed since if the QoS is not okay, manual mode
* is selected but for debugging it might be useful*/
} else if((AutManStatus.QoS == 1)&&(AutManStatus.State == 1)){
AutoManState = 0;
AutoModeLEDOutput = 0;
//Serial.println("Man Mode");
} else {
AutoManState = 0;
AutoModeLEDOutput = 0;
}
}
/************************ Checking the valve ***********************************************/
void ValveControl(){
/* Checking the Valve statuses one by one. */
ValveCheck(HeatSource, HeatSourceBoilerInput, HeatSourceBufferInput, V1BufferInput, V1BoilerInput, ValveTimoutErrorLimit, &V1);
ValveCheck(HeatSource, HeatSourceBoilerInput, HeatSourceBufferInput, V1BufferInput, V1BoilerInput, ValveTimoutErrorLimit, &V2);
/*Serial.print("HeatSourceBoilerInput: ");
Serial.print(HeatSourceBoilerInput);
Serial.print(", HeatSourceBufferInput: ");
Serial.print(HeatSourceBufferInput);
Serial.print(", Heat source: ");
Serial.print(HeatSource);
Serial.print(", V1 State: ");
Serial.print(V1.State);
Serial.print(", V1 QoS: ");
Serial.print(V1.QoS);*/
/*The ValveDelayElapsed shall be set to zero */
if(HeatSource != HeatSource_N){
ValveDelayElapsed = 0;
HeatSource_N = HeatSource;
}
/*Checking the valve for Boiler settings or Buffer setting*/
if(HeatSource && HeatSourceBoilerInput && !HeatSourceBufferInput && (V1.State == 1) && (V1.QoS == 1) && ValveDelayElapsed){
ValveSelection = 1;
ValveErrorCounter = 0;
ValveError = 0;
/*Serial.print("Valve Selection: ");
Serial.print(ValveSelection);
Serial.println(" , Buffer selected: ");
*/
} else if(!HeatSource && !HeatSourceBoilerInput && HeatSourceBufferInput && (V1.State == 2) && (V1.QoS == 1) && ValveDelayElapsed){
ValveSelection = 2;
ValveErrorCounter = 0;
ValveError = 0;
/*Serial.print(", Valve Selection: ");
Serial.print(ValveSelection);
Serial.println(" , Boiler selected: ");
*/
} else {
/* If the valve is not set then give some time before the error reporting*/
/*currently the same timeout is used here and for the individual valve timeout. If there is an issue they shall be separated.*/
//Serial.print(", Valve error: ");
if(previousSec != actSecond){
ValveErrorCounter++;
}
if(ValveDelay < ValveErrorCounter){
ValveDelayElapsed = 1;
}
if(ValveErrorCounter > ValveTimoutErrorLimit){
ValveSelection = 9999;
ValveError = 1;
} else {
ValveSelection = 0;
}
/*Serial.print(ValveSelection);
Serial.print(", Valve selection error: ");
Serial.println(ValveError);
Serial.print("Valve Delay: ");
Serial.print(ValveDelayElapsed);*/
}
}
/*Since the valves have different states depending on the heating source selection the valve check shall be done on Boiler and Buffer*/
void ValveCheck(int HeatSource, int HeatSourceBoilerInput, int HeatSourceBufferInput, int ValveBuffer, int ValveBoiler, int ValveTimeout, struct Valve *ValveIn){
/*The valves need approx 6s for position change. For that a delay shall be implemented and if that elapsed the valve state set.*/
if(HeatSource && ValveBoiler && !ValveBuffer){
/*If the buffer is selected then the valve shall be in that position*/
ValveIn->State = 1;
ValveIn->QoS = 1;
ValveIn->timeout = 0;
} else if(!HeatSource && !ValveBoiler && ValveBuffer){
/*If the boiler is selected then the valve shall be in that position*/
ValveIn->State = 2;
ValveIn->QoS = 1;
ValveIn->timeout = 0;
} else {
/*If none of these fulfilled, after a timeout an error shall be reported*/
if(previousSec != actSecond){
ValveIn->timeout++;
}
if(ValveTimeout < ValveIn->timeout){
ValveIn->State = 9999;
ValveIn->QoS = 0;
}
}
}
/************************ end of Checking the valve ***********************************************/
/* The pump shall be started based on a request coming from the heating control. The pump control shall check all the conditions for starting the pump.
* If the pump is started all the conditions shall be monitored for keeping it running.*/
void PumpControl(){
switch (PumpControlState){
case PumpNotRunning:
/* Starting the pump if there is a heating or circulation request. The valves can be in Buffer tank or Boiler position*/
/*Serial.print("Pump not running state");
Serial.print(", Pump start request: ");
Serial.println(PumpStartRequest);*/
if(PumpStartRequest && ((ValveSelection == 1)||(ValveSelection == 2))){
PumpCommand = 1;
PumpControlState = PumpStarting;
P1.State = 1;
P1.QoS = 1;
//Serial.println("Pump starting");
}
PumpError = 0;
break;
case PumpStarting:
/* If the pump has been started the feedback of the starting relay shall be checked. if it is not received in time, then the
* pump shall be stopped and the error state is set.*/
//Serial.print("Pump starting state");
if(previousSec != actSecond){
PumpErrorCounter++;
}
if(PumpErrorCounter > PumpErrorLimit){
PumpCommand = 0;
PumpControlState = PumpErrorState;
}
/*Serial.print("Pump error counter: ");
Serial.println(PumpErrorCounter);
Serial.print("Pump feedback: ");
Serial.println(PumpRelayInput);*/
/* The NO of the relay shall be read. If it is okay, then set the pump running state
* The pump startup limit shall be checked to allow the relay to settle(get rid of the bouncing)
* and also allow the flow to built up, The error counter is used as a second counter.*/
if(PumpRelayInput && (PumpErrorCounter > PumpStartupCounter)){
/*Serial.print("Pump error counter: ");
Serial.print(PumpErrorCounter);*/
PumpErrorCounter = 0;
PumpControlState = PumpRunning;
}
break;
case PumpRunning:
/* If any of the running conditions which are required for running: valve and the relay feedback or
* the flow is below any limit, are not fullfilled then the error state shall be set.
* If the heating request conditions is gone, then the pump shall be stopped.*/
//Serial.println("Pump running state");
if(((ValveSelection != 1) && (FlowLowLimit > F3.Flow)&&(F3.QoS == 1))||(!PumpRelayInput && PumpStartRequest)||((ValveSelection != 2)&& (FlowLowLimit > F4.Flow)&&(F4.QoS == 1))){
PumpCommand = 0;
PumpControlState = PumpErrorState;
P1.State = 2;
P1.QoS = 0;
//Serial.println("Running conditions ceased");
}
if(!PumpStartRequest){
PumpCommand = 0;
PumpControlState = PumpNotRunning;
P1.State = 0;
P1.QoS = 1;
//Serial.println("Running conditions stopped");
}
break;
case PumpErrorState:
/* If the pump is in error state, it could be reset if the pump is started and the relay feedback
* and the produced flow are okay*/
/*Serial.print("Pump error state, ");
Serial.print("Error:");
Serial.print(PumpError);
Serial.print(", Qos:");
Serial.println(P1.QoS);*/
if((PumpStartRequest && !PumpRelayInput) && ((ValveSelection == 1)||(ValveSelection == 2))){
PumpCommand = 1;
P1.State = 1;
/*Serial.println("Manual starting, cmd:");
Serial.println(PumpCommand);*/
} else if((PumpRelayInput && PumpStartRequest)){ /* Later if the flow measurement is okay, this shall be put back into the condition: ||((FlowLowLimit < F3.Flow)&&(F3.QoS == 1))*/
PumpError = 0;
P1.QoS = 1;
//Serial.println("Error state reset");
} else if(!PumpStartRequest && (PumpError == 0) && (P1.QoS == 1)){
PumpControlState = PumpNotRunning;
PumpCommand = 0;
P1.State = 0;
P1.QoS = 1;
//Serial.println("State machine to not running");
} else {
PumpCommand = 0;
//Serial.println("No pump cmd");
}
break;
}
}
void HeatingAlgorithm(){
float Local_T = 0.0;
/* Count the average of the temperature inside the tank. T2 is located at the top, T1 is at the bottom, if both are
* above the temperature then the tank is heated to the goal temperature.
* If T1 is missing/ wrong QoS then the T2 shall be considered, if T2 is missing then T1.
* If none of them is okay, then the Boiler shall be selected as a safe state*/
/*Serial.print("T1 QoS: ");
Serial.print(T1.QoS);
Serial.print(", T2 QoS: ");
Serial.print(T2.QoS);*/
if((((T1.QoS == 1)||(T1.QoS == 2)||(T1.QoS == 3))&&((T2.QoS == 1)||(T2.QoS == 2)||(T2.QoS == 3)))){
Local_T = (T1.Temperature + T2.Temperature) / 2;
//Serial.println(", T1 and T2 are okay");
} else if(((T1.QoS == 1)||(T1.QoS == 2)||(T1.QoS == 3))&&((T2.QoS != 1)||(T2.QoS != 2)||(T2.QoS != 3))){
Local_T = T1.Temperature;
//Serial.println(", T1 is okay");
} else if(((T1.QoS != 1)||(T1.QoS != 2)||(T1.QoS != 3))&&((T2.QoS = 1)||(T2.QoS = 2)||(T2.QoS = 3))){
Local_T = T2.Temperature;
//Serial.println(", T2 is okay");
} else {
HeatSource = 0;
PVHeatingOutput = 0;
PumpStartRequest = 0;
//Serial.println("T1 and T2 are wrong");
return;
}
/* If there is a heating request from the thermostat it shall be put into another variable
* and if there is a problem this variable shall be reset.*/
/* If the temp in the buffer tank is above the limit, then select it as a heat source*/
/* Switch status 1 - Manual mode, 2 - Automatic mode*/
/*Serial.print("Local_T: ");
Serial.print(Local_T);
/*Serial.print(", Aut Man: ");
Serial.println(AutoManState);*/
if(((Local_T > BufferTankTempLowLimit)&&(AutoManState))||(!AutoManState && (PufferHeatingSelectorStatus.QoS == 1) && (PufferHeatingSelectorStatus.State == 2))){
HeatSource = 1;
BoilerCircuitOutput = 1;
//Serial.println(", Buffer Selected, ");
} else {
HeatSource = 0;
BoilerCircuitOutput = 0;
//Serial.println(", Boiler Selected, ");
}
/*If it is not heating season the buffer shall be heated up to max temperature and then the heating switched off.
* There is no need for additional electrical heating.*/
if((act_Month > 4) && (act_Month < 10)){
HeatingSeason = false;
// Serial.println("Not a heating season");
} else {
/* If it is heating season the buffer tank heating is implemented in the state machine.*/
HeatingSeason = true;
// Serial.println("Heating season");
}
/*the system shall enter into auto state if it is heating season. If it is not heating season, the buffer's heating is automatic
* and if there is a heating request it will start the pump*/
if(HeatingSeason && AutoManState && HeatSource){
switch(HeatingState){
case HeatingBefore2pm: {
Serial.println("HeatingBefore2pm");
/*It is between 7 am and 2 pm. The pump shall be started for 10 mins if the tank is warm enough*/
if((act_Min >= 25) && (act_Min < 35) && (T1.Temperature > 40.0)){
Serial.println("Starting the pump");
PumpStartRequest = 1;
} else {
Serial.println("Stopping the pump");
PumpStartRequest = 0;
}
/* The PV heating shall be switched on if the temperature is lower than the limit*/
if(T1.Temperature < BufferTankTempLowLimit){
PVHeatingOutput = 1;
} else {
PVHeatingOutput = 0;
}
/* Changing the state to heating after 2pm */
if((act_Hour >= 14) && (act_Hour < 22)){
HeatingState = HeatingAfter2pm;
PVHeatingMinutes = 0;
}
break;
}
case HeatingAfter2pm: {
Serial.println("HeatingAfter2pm");
/*It is between 2 pm and 10 pm. The pump shall be started for 10 mins if the tank is warm enough T > 50.0 C,
* If the tank is around 40C then it shall change to circulating in two hours to prioritzing the heating the tank for the night showers*/
if((act_Min >= 25) && (act_Min < 35) && (T1.Temperature > 50.0)){
Serial.println("Starting the pump");
PumpStartRequest = 1;
} else if ((T1.Temperature >= 40.0) && (T1.Temperature < 50.0) && (act_Hour % 2) && (act_Min >= 25) && (act_Min < 35)){
Serial.println("Starting the pump");
PumpStartRequest = 1;
} else {
Serial.println("Stopping the pump");
PumpStartRequest = 0;
}
/* The PV heating shall be switched on if the temperature is lower than the limit and it is before 4 pm*/
if((T1.Temperature < BufferTankTempLowLimit) && (act_Hour < 16)){
lcd.setCursor(10, 1);
lcd.print("PV On");
/* If it is after 4 pm. There is a two hour time window to heat the buffer tank since in one hour with electrical heating
* the temperature can be increased with 10C.
* */
} else if((act_Hour >= 16) && ((abs(PVHeatingTemperatureMin - T1.Temperature)) <= TemperatureDifference) && (PVHeatingTemperatureMin > T1.Temperature ) && (T1.Temperature > ElectricalHeatingMinTemp) && (PVHeatingMinutesLimit > PVHeatingMinutes)){
PVHeatingOutput = 1;
/*Calculating the time limit for the PVHeating*/
PVHeatingMinutesLimit = (unsigned int)((abs(PVHeatingTemperatureMin - T1.Temperature) / 10.0) * 60.0);
if(act_Min != previousMin){
PVHeatingMinutes++;
}
} else {
PVHeatingOutput = 0;
}
/* Changing the state to heating after 10pm */
if((act_Hour < 7) || (act_Hour >= 22)){
HeatingState = HeatingAfter10pm;
PVHeatingMinutes = 0;
PVHeatingMinutesLimit = 2;
}
break;
}
case HeatingAfter10pm: {
/* In this state the circulation shall continue as long as there is enough heat in the tank.
* There is no PV heating in this state.*/
Serial.println("HeatingAfter10pm");
if((act_Min >= 25) && (act_Min < 35) && (T1.Temperature > 25.0) && (act_Hour % 2)){
Serial.println("Starting the pump");
PumpStartRequest = 1;
} else {
Serial.println("Stopping the pump");
PumpStartRequest = 0;
}
/* Changing the state to heating before 2pm */
if((act_Hour >= 7) && (act_Hour < 14)){
HeatingState = HeatingBefore2pm;
}
break;
}
/* In the default place the actual hour shall be decided*/
default:{
if((act_Hour >= 7) && (act_Hour < 14)){
HeatingState = HeatingBefore2pm;
} else if((act_Hour >= 14) && (act_Hour < 22)){
HeatingState = HeatingAfter2pm;
} else if((act_Hour < 7) || (act_Hour >= 22)){
HeatingState = HeatingAfter10pm;
}
PVHeatingMinutes = 0;
}
}
} else {
/* If it is not heating season just heating the tank to the limit, without electrical power from the network*/
/* If the buffer tank temperature is lower thean the limit, then switch on the heating. */
if(((T1.Temperature < BufferTankTempHighLimit) && (act_Hour > 7) && (act_Hour < 18) && (AutoManState)) || ((!AutoManState) && !PVManualModeHeatingInput)){
PVHeatingOutput = 1;
} else {
PVHeatingOutput = 0;
}
if(HeatingRequest && (Local_T > BufferTankTempLowLimit) && (HeatSource == 1) && (AutoManState)){
/*If in auto mode there is a heating request then the pump shall be started only if the buffer tank is selected for the heating source.*/
PumpStartRequest = 1;
//Serial.println("P1 ON: Heating req");
} else if((!AutoManState)&&!PumpManualModeStartInput){
/* In manual mode if there is a pump starting command then start the pump*/
PumpStartRequest = 1;
//Serial.println("P1 Man running");
} else {
PumpStartRequest = 0;
//Serial.println("P1 Stop");
}
}
}
/* If the temperature in the boiler rises above the safety limit, then the relay shall be opened
* If no temperature data is available then it shall be opened too.
* If the relay was opened, it shall be switched back with a hysteresis. */
void PVHeatingCheck(){
float Local_T = 0.0;
if((((T1.QoS == 1)||(T1.QoS == 2)||(T1.QoS == 3))&&((T2.QoS == 1)&&(T2.QoS == 2)||(T2.QoS == 3)))){
Local_T = (T1.Temperature + T2.Temperature) / 2;
} else if(((T1.QoS == 1)||(T1.QoS == 2)||(T1.QoS == 3))&&((T2.QoS != 1)&&(T2.QoS != 2)&&(T2.QoS != 3))){
Local_T = T1.Temperature;
} else if(((T1.QoS != 1)&&(T1.QoS != 2)&&(T1.QoS != 3))&&((T2.QoS != 1)||(T2.QoS != 2)||(T2.QoS != 3))){
Local_T = T2.Temperature;
} else {
Local_T = 0.0;
}
if(((T1.QoS == 1)||(T1.QoS == 2)||(T1.QoS == 3))||((T2.QoS == 1)||(T2.QoS == 2)||(T2.QoS == 3))){
if(Local_T > BufferTankTempHighLimit){
PVHeatingOutput = 0;
} else if(Local_T < BufferTankTempLowLimit){
PVHeatingOutput = PVHeatingOutput;
}
} else {
PVHeatingOutput = 0;
}
}
/*The relay and the solid state relay signal shall be delayed as the following:
* - at switching on the SSR shall be delayed as long as the feedback signal is provided by the PV magnetic switch to
* protect the contactor of the magnetic switch
* - at switch off the magnetic switch switch shall be delayed with 1 second, to also protect its contactors from burning
*
*/
void PVHeatingDelays(){
/*Rising edge detection*/
if(PVHeatingOutput && !PVHeatingOutputN1){
PVHeatingOutputRisingEdge = 1;
PVHeatingOutputFallingEdge = 0;
PVHeatingOutputN1 = PVHeatingOutput;
PVHeatingSSROutputT_ONDelayCounter = 0;
PVHeatingRelayOutput = 1;
}
/*Fallng edge detection*/
if(!PVHeatingOutput && PVHeatingOutputN1){
PVHeatingOutputRisingEdge = 0;
PVHeatingOutputFallingEdge = 1;
PVHeatingOutputN1 = PVHeatingOutput;
PVHeatingRelayOutputT_OFFDelayCounter = 0;
PVHeatingSSROutput = 0;
}
if(previousSec != actSecond){
if(PVHeatingOutputRisingEdge){
PVHeatingSSROutputT_ONDelayCounter++;
if(PVHeatingSSROutputT_ONDelayCounter > PVHeatingSSROutputT_ONDelay){
PVHeatingSSROutput = 1;
PVHeatingOutputRisingEdge = 0;
}
}
if(PVHeatingOutputFallingEdge){
PVHeatingRelayOutputT_OFFDelayCounter++;
if(PVHeatingRelayOutputT_OFFDelayCounter > PVHeatingRelayOutputT_OFFDelay){
PVHeatingRelayOutput = 0;
PVHeatingOutputFallingEdge = 0;
}
}
}
}
/* The pump starting shall only be available if the V1 has reached any of its end positions*/
...
This file has been truncated, please download it to see its full contents.
#include <avr/wdt.h>
#include <SPI.h>
#include <mcp2515.h>
#include "CRC.h"
#include <Arduino.h>
#include <RtcDS1302.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4); // I2C address 0x27, 20 column and 4 rows
struct can_frame RecvcanMsg;
struct can_frame SentcanMsg;
MCP2515 mcp2515(53);
#define DE 3
#define RE 2
#define BUFFER_SIZE 64 // maximális üzenethossz
char DataToBeProcessed[BUFFER_SIZE];
uint8_t dataIndex = 0;
// DS1302 RTC instance
// CONNECTIONS:
// DS1302 CLK/SCLK --> 5
// DS1302 DAT/IO --> 4
// DS1302 RST/CE --> 2
// DS1302 VCC --> 3.3v - 5v
// DS1302 GND --> GND
ThreeWire myWire(48,49,47); // IO, SCLK, CE
RtcDS1302<ThreeWire> Rtc(myWire);
/*********** global variables *************/
unsigned long exectuion_number = 0, ControllerMsg_TimeOut = 0, ControllerMsgTimeoutLimit = 4;
bool ControllerTimeoutOK = false;
/* CAN */
int first_execution = 0, previousSec = 0, actSecond = 0, previousMin = 0, actMin = 0, CANMsgTimeout = 35, RS485MsgTimeout = 38, SerialResetErrorCounter = 1, CANResetErrorCounter = 1;
float BufferTankTempLowLimit = 70.0, BufferTankTempHighLimit = 85.0, TempDifferentInsisdeBuffertank = 5.0;
int PumpCommand = 0;
int HeatSource = 0; /* 0 - Boiler, 1 - Buffer tank*/
unsigned int ValveTimeOut = 0, ValveTimeOutLimit = 15;
/**************** Digital inputs and outputs *******************/
int HeatSourceBoilerInputPin = 35, HeatSourceBoilerInput = 0, HeatSourceBoilerInputRisingEdge = 0, HeatSourceBufferInputPin = 36, HeatSourceBufferInput = 0, HeatSourceBufferInputRisingEdge = 0;
int V1BufferInputPin = 40, V1BufferInput = 0, V1BufferInputRisingEdge = 0, V1BoilerInputPin = 41, V1BoilerInput = 0, V1BoilerInputRisingEdge = 0;
int V2BufferInputPin = 40, V2BufferInput = 0, V2BufferInputRisingEdge = 0, V2BoilerInputPin = 41, V2BoilerInput = 0, V2BoilerInputRisingEdge = 0;
int PumpStartOutputPin = 10, PumpRelayInputPin = 38, PumpRelayInput = 0, PumpRelayInputRisingEdge = 0;
int ThermostatInputPin = 0, ThermostatInput = 0, ThermostatInputRisingEdge = 0;
int BoilerCircuitOutputPin = 0, BoilerCircuitOutput = 0;
int PVManualModeHeatingInputPin = 42, PVManualModeHeatingInput = 0, PVManualHeatingInputRisingEdge = 0;
int PVHeatingOutput = 0, PVHeatingOutputN1 = 0, PVHeatingOutputRisingEdge = 0, PVHeatingOutputFallingEdge = 0;
int PVHeatingSSROutputT_ONDelay = 2, PVHeatingSSROutputT_ONDelayCounter = 0, PVHeatingRelayOutputT_OFFDelay = 2, PVHeatingRelayOutputT_OFFDelayCounter = 0;
int PVHeatingSSROutputPin = 9, PVHeatingSSROutput = 0, PVHeatingOutputPin = 11, PVHeatingRelayOutput = 0;
int RedLightOutputPin = 34, RedLightOutput = 0;
int YellowLightOutputPin = 33, YellowLightOutput = 0;
int GreenLightOutputPin = 32, GreenLightOutput = 0;
byte MsgCountN = 0;
byte CRCcalc[8];
char defaultDate[10] = {"1987:03:11"};
char defaultTime[8] = {"04:30:00"};
RtcDateTime Default_TimeStamp = RtcDateTime(defaultDate, defaultTime);
float TemperatureChangeLimit = 10.0, TemperaturePlausibilityMin = 0.0, TemperaturePlausibilityMax = 150.0;
struct Temp {
float Temperature = 40000.0;
float CAN_Temperature = 40000.0;
float RS485_Temperature = 40000.0;
unsigned int QoS = 65534; /* 1 - OK, 2 - CAN Error, 3 - RS485 Error, Any other - both wrong*/
unsigned int CAN_QoS = 65534; /* 1 - OK, Any other - CAN Error*/
unsigned int RS485_QoS = 65534; /* 1 - OK, Any other - RS485 Error*/
unsigned int CAN_timeout = 0;
unsigned int RS485_timeout = 0;
byte CAN_PrevMsgCounter = 255;
byte RS485_PrevMsgCounter = 255;
RtcDateTime CAN_TimeStamp = Default_TimeStamp;
RtcDateTime RS485_TimeStamp = Default_TimeStamp;
};
/* The N is storing the previous state. If it is different then display it*/
struct Temp T1;
struct Temp T1N;
struct Temp T2;
struct Temp T2N;
struct Temp T3;
struct Temp T3N;
struct Temp T4;
struct Temp T4N;
struct Flw {
float Flow = 40000.0;
float CAN_Flow = 40000.0;
float RS485_Flow = 40000.0;
unsigned int QoS = 65534; /* 1 - OK, 2 - CAN Error, 3 - RS485 Error, Any other - both wrong*/
unsigned int CAN_QoS = 65534; /* 1 - OK, Any other - CAN Error*/
unsigned int RS485_QoS = 65534; /* 1 - OK, Any other - RS485 Error*/
unsigned int CAN_timeout = 0;
unsigned int RS485_timeout = 0;
byte CAN_PrevMsgCounter = 0;
byte RS485_PrevMsgCounter = 0;
RtcDateTime CAN_TimeStamp = Default_TimeStamp;
RtcDateTime RS485_TimeStamp = Default_TimeStamp;
};
/* The N is storing the previous state. If it is different then display it*/
struct Flw F3;
struct Flw F3_Controller;
struct Flw F4;
struct Flw F4_Controller;
struct Valve {
int State = 2; /* 1 - Boiler selected, 2 - Buffer selected, Any other - error*/
unsigned int QoS = 65534; /* 1 - OK, anything other - wrong*/
unsigned int timeout = 0;
};
/* The N is storing the previous state. If it is different then display it*/
struct Valve V1;
struct Valve V2;
struct Pump {
int State = 2; /* 0 - stopped, 1 - running, 2 - error*/
unsigned int QoS = 65534; /* 1 - OK, anything other - wrong*/
unsigned int timeout = 0;
};
/* The N is storing the previous state. If it is different then display it*/
struct Pump P1;
struct Pump P1N;
struct Relay {
int State = 2; /* 0 - off, 1 - on, 2 - error*/
unsigned int QoS = 65534; /* 1 - OK, anything other - wrong*/
unsigned int timeout = 0;
};
struct Relay PVHeating;
struct Relay PVHeatingN;
struct SwtchIn {
int State = 0;
int QoS = 0;
};
struct SwtchIn AutManStatus;
struct SwtchIn PufferHeatingSelectorStatus;
void setup() {
/*********************** watchdog setup **************************************************/
wdt_disable();/* diable the watchdog and wait for more than 2 seconds */
delay(3000); /* Done so the Arduino doesnot keep resetting infinitely in case of wrong configuration*/
wdt_enable(WDTO_4S); /* Enable the watchdog with a timeout of 2 seconds */
pinMode(HeatSourceBoilerInputPin, INPUT_PULLUP);
pinMode(HeatSourceBufferInputPin, INPUT_PULLUP);
/* pinMode(V1BufferInputPin, INPUT_PULLUP);
pinMode(V1BoilerInputPin, INPUT_PULLUP);
*/ pinMode(PumpRelayInputPin, INPUT_PULLUP);
pinMode(ThermostatInputPin, INPUT_PULLUP);
pinMode(13, OUTPUT);
pinMode(PumpStartOutputPin, OUTPUT);
pinMode(BoilerCircuitOutputPin, OUTPUT);
pinMode(PVHeatingOutputPin, OUTPUT);
pinMode(PVHeatingSSROutputPin, OUTPUT);
pinMode(RedLightOutputPin, OUTPUT);
pinMode(YellowLightOutputPin, OUTPUT);
pinMode(GreenLightOutputPin, OUTPUT);
digitalWrite(13, HIGH);
digitalWrite(PumpStartOutputPin, LOW);
digitalWrite(BoilerCircuitOutputPin, HIGH);
digitalWrite(PVHeatingOutputPin, HIGH);
digitalWrite(PVHeatingSSROutputPin, HIGH);
// Set the DE and RE pins as outputs
pinMode(DE, OUTPUT);
pinMode(RE, OUTPUT);
// Set DE and RE LOW to disable transmission mode
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
Serial.begin(115200);
Serial3.begin(115200);
SentcanMsg.can_id = 0x000;
SentcanMsg.can_dlc = 8;
SentcanMsg.data[0] = 0x00;
SentcanMsg.data[1] = 0x00;
SentcanMsg.data[2] = 0x00;
SentcanMsg.data[3] = 0x00;
SentcanMsg.data[4] = 0x00;
SentcanMsg.data[5] = 0x00;
SentcanMsg.data[6] = 0x00;
SentcanMsg.data[7] = 0x00;
RecvcanMsg.can_id = 0x110;
RecvcanMsg.can_dlc = 8;
RecvcanMsg.data[0] = 0x00;
RecvcanMsg.data[1] = 0x00;
RecvcanMsg.data[2] = 0x00;
RecvcanMsg.data[3] = 0x00;
RecvcanMsg.data[4] = 0x00;
RecvcanMsg.data[5] = 0x00;
RecvcanMsg.data[6] = 0x00;
RecvcanMsg.data[7] = 0x00;
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS);
mcp2515.setNormalMode();
// initialize the RTC
Serial.print("compiled: ");
Serial.print(__DATE__);
Serial.println(__TIME__);
Serial.println("RTC begin: ");
Rtc.Begin();
RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);
Serial.print("Compile time: ");
//printDateTime(compiled);
Serial.println("Setting the RTC");
Rtc.SetDateTime(compiled);
if (!Rtc.IsDateTimeValid())
{
// Common Causes:
// 1) first time you ran and the device wasn't running yet
// 2) the battery on the device is low or even missing
Serial.println("RTC lost confidence in the DateTime!");
}
if (Rtc.GetIsWriteProtected())
{
Serial.println("RTC was write protected, enabling writing now");
Rtc.SetIsWriteProtected(false);
}
if (!Rtc.GetIsRunning())
{
Serial.println("RTC was not actively running, starting now");
Rtc.SetIsRunning(true);
}
lcd.init(); // initialize the lcd
lcd.backlight();
Serial.println("Checker Startup");
lcd.setCursor(5, 1);
lcd.print("Checker Startup");
}
void loop() {
digitalInputReading(HeatSourceBoilerInputPin, &HeatSourceBoilerInput, &HeatSourceBoilerInputRisingEdge, first_execution);
digitalInputReading(HeatSourceBufferInputPin, &HeatSourceBufferInput, &HeatSourceBufferInputRisingEdge, first_execution);
/*digitalInputReading(V1BufferInputPin, &V1BufferInput, &V1BufferInputRisingEdge, first_execution);
digitalInputReading(V1BoilerInputPin, &V1BoilerInput, &V1BoilerInputRisingEdge, first_execution);
digitalInputReading(V2BufferInputPin, &V2BufferInput, &V2BufferInputRisingEdge, first_execution);
digitalInputReading(V2BoilerInputPin, &V2BoilerInput, &V2BoilerInputRisingEdge, first_execution);*/
digitalInputReading(PumpRelayInputPin, &PumpRelayInput, &PumpRelayInputRisingEdge, first_execution);
digitalInputReading(ThermostatInputPin, &ThermostatInput, &ThermostatInputRisingEdge, first_execution);
/* Reading the actual second */
RtcDateTime now = Rtc.GetDateTime();
actSecond = now.Second();
actMin = now.Minute();
// Set DE and RE LOW to disable transmission mode
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
RS485MsgReceiving();
CANMsgReceiving();
CAN_RS485_MsgTimeOutCheck();
SignalAvailabilityCheckAndSelection();
PVHeatingCheck();
PVHeatingDelays();
PumpStartingCheck();
WritingOutputs();
ChangeUpdate();
wdt_reset(); /* Reset the wdt timer */
if(previousSec != actSecond){
previousSec = actSecond;
GreenLightOutput =!GreenLightOutput;
LCDHandling();
}
if(previousMin != actMin){
previousMin = actMin;
SerialResetErrorCounter = 0;
CANResetErrorCounter = 0;
/* If the RTC is not valid, then try to reset the controller and init the RTC*/
if(!Rtc.IsDateTimeValid()){
lcd.clear();
lcd.setCursor(5, 1);
lcd.print("RTC Error, resetting the controller");
while(1){}
}
}
/*initialize the lcd*/
if(actMin == 57){
lcd.init();
lcd.backlight();
}
/*increment the execution number*/
exectuion_number++;
/*compare it with a limit and set the variable*/
if(exectuion_number > 3){
first_execution = 1;
} else {
first_execution = 0;
}
}
/* If the temperature in the boiuler rises above the safety limit, then the relay shall be opened
* If no temperature data is available then it shall be opened too.
* If the relay was opened, it shall be switched back with a hysteresis. */
void PVHeatingCheck(){
float Local_T = 0.0;
if((((T1.QoS == 1)||(T1.QoS == 2)||(T1.QoS == 3))&&((T2.QoS == 1)&&(T2.QoS == 2)||(T2.QoS == 3)))){
Local_T = (T1.Temperature + T2.Temperature) / 2;
} else if(((T1.QoS == 1)||(T1.QoS == 2)||(T1.QoS == 3))&&((T2.QoS != 1)&&(T2.QoS != 2)&&(T2.QoS != 3))){
Local_T = T1.Temperature;
} else if(((T1.QoS != 1)&&(T1.QoS != 2)&&(T1.QoS != 3))&&((T2.QoS != 1)||(T2.QoS != 2)||(T2.QoS != 3))){
Local_T = T2.Temperature;
} else {
Local_T = 0.0;
}
if(((T1.QoS == 1)||(T1.QoS == 2)||(T1.QoS == 3))||((T2.QoS == 1)||(T2.QoS == 2)||(T2.QoS == 3))){
if(Local_T > BufferTankTempHighLimit){
PVHeatingOutput = 0;
RedLightOutput = 0;
} else if(Local_T < BufferTankTempLowLimit){
PVHeatingOutput = 1;
RedLightOutput = 1;
} else {
/*If the temperature is between low and max limit, then no heating shall be provided*/
PVHeatingOutput = 0;
RedLightOutput = 0;
}
} else {
PVHeatingOutput = 0;
RedLightOutput = 0;
}
}
/*The relay and the solid state relay signal shall be delayed as the following:
* - at switching on the SSR shall be delayed as long as the feedback signal is provided by the PV magnetic switch to
* protect the contactor of the magnetic switch
* - at switch off the magnetic switch switch shall be delayed with 1 second, to also protect its contactors from burning
*
*/
void PVHeatingDelays(){
/*Rising edge detection*/
if(PVHeatingOutput && !PVHeatingOutputN1){
PVHeatingOutputRisingEdge = 1;
PVHeatingOutputFallingEdge = 0;
PVHeatingOutputN1 = PVHeatingOutput;
PVHeatingSSROutputT_ONDelayCounter = 0;
PVHeatingRelayOutput = 1;
/*Serial.print("PVHeating Rising edge, ");
Serial.print(" PVHeatingRelayOutput: ");
Serial.print(PVHeatingRelayOutput);
Serial.print(" PVHeatingSSROutput: ");
Serial.println(PVHeatingSSROutput); */
}
/*Fallng edge detection*/
if(!PVHeatingOutput && PVHeatingOutputN1){
PVHeatingOutputRisingEdge = 0;
PVHeatingOutputFallingEdge = 1;
PVHeatingOutputN1 = PVHeatingOutput;
PVHeatingRelayOutputT_OFFDelayCounter = 0;
PVHeatingSSROutput = 0;
/*Serial.print("PVHeating Falling edge, ");
Serial.print(" PVHeatingRelayOutput: ");
Serial.print(PVHeatingRelayOutput);
Serial.print(" PVHeatingSSROutput: ");
Serial.println(PVHeatingSSROutput);*/
}
if(previousSec != actSecond){
if(PVHeatingOutputRisingEdge){
PVHeatingSSROutputT_ONDelayCounter++;
if(PVHeatingSSROutputT_ONDelayCounter > PVHeatingSSROutputT_ONDelay){
PVHeatingSSROutput = 1;
PVHeatingOutputRisingEdge = 0;
/*Serial.print(" PVHeatingRelayOutput: ");
Serial.print(PVHeatingRelayOutput);
Serial.print(" PVHeatingSSROutput: ");
Serial.println(PVHeatingSSROutput);*/
}
}
if(PVHeatingOutputFallingEdge){
PVHeatingRelayOutputT_OFFDelayCounter++;
if(PVHeatingRelayOutputT_OFFDelayCounter > PVHeatingRelayOutputT_OFFDelay){
PVHeatingRelayOutput = 0;
PVHeatingOutputFallingEdge = 0;
/*Serial.print(" PVHeatingRelayOutput: ");
Serial.print(PVHeatingRelayOutput);
Serial.print(" PVHeatingSSROutput: ");
Serial.println(PVHeatingSSROutput);*/
}
}
}
}
/* The pump starting shall only be available if the V1 has reached any of its end positions*/
void PumpStartingCheck(){
if((!HeatSourceBoilerInput && HeatSourceBufferInput)||(HeatSourceBoilerInput && !HeatSourceBufferInput)){
PumpCommand = 1;
ValveTimeOut = 0;
YellowLightOutput = 1;
} else {
if(previousSec != actSecond){
ValveTimeOut++;
}
if(ValveTimeOut > ValveTimeOutLimit){
PumpCommand = 0;
YellowLightOutput = 0;
}
}
}
/****** Signal availability check and selection ******/
void SignalAvailabilityCheckAndSelection(){
TemperatureSignalAvailabilityCheck(&T1);
/*Serial.print("T1.QoS: ");
Serial.println(T1.QoS);*/
TemperatureSignalAvailabilityCheck(&T2);
/* TemperatureSignalAvailabilityCheck(&T3);
TemperatureSignalAvailabilityCheck(&T4);*/
FlowSignalAvailabilityCheck(&F3);
FlowSignalAvailabilityCheck(&F4);
}
void TemperatureSignalAvailabilityCheck(struct Temp *T){
int year_no_diff = 0, month_no_diff = 0, day_no_diff = 0, hour_no_diff = 0, minute_no_diff = 0, second_no_diff = 0;
/*First, the signals QoS and the msg counters shall be checked
* - If both are okay, then the timestamp shall be checked and the newest shall be used
* - If one of them is okay, then use the signal with the okay QoS
* - If none is okay, then set an error
* All the data in the timestamp shall be checked for diff, if there is no diff use the average.*/
if((T->CAN_QoS == 1) && (T->RS485_QoS == 1)){
T->QoS = 1;
/*Compare the timestamps, starting with the year*/
int year_diff = T->RS485_TimeStamp.Year() - T->CAN_TimeStamp.Year();
if(year_diff == 0){
year_no_diff = 1;
// Serial.print("No difference in year, ");
} else if(year_diff < 0){
T->Temperature = T->CAN_Temperature;
// Serial.print("CAN is actual(year), ");
} else {
T->Temperature = T->RS485_Temperature;
// Serial.print("RS485 is actual(year), ");
}
/*If there is no difference in year, then check the month*/
if(year_no_diff){
int month_diff = T->RS485_TimeStamp.Month() - T->CAN_TimeStamp.Month();
if(month_diff == 0){
month_no_diff = 1;
//Serial.print(", No difference in month, ");
} else if(month_diff < 0){
T->Temperature = T->CAN_Temperature;
//Serial.print("CAN is actual(month), ");
} else {
T->Temperature = T->RS485_Temperature;
//Serial.print("RS485 is actual(month), ");
}
}
/*If there is no difference in year and month, then check the day*/
if(year_no_diff && month_no_diff){
int day_diff = T->RS485_TimeStamp.Day() - T->CAN_TimeStamp.Day();
if(day_diff == 0){
day_no_diff = 1;
// Serial.print(", No difference in day");
} else if(day_diff < 0){
T->Temperature = T->CAN_Temperature;
// Serial.print("CAN is actual(day), ");
} else {
T->Temperature = T->RS485_Temperature;
// Serial.print("RS485 is actual(day),");
}
}
/*If there is no difference in year, month and day, then check the hour*/
if(year_no_diff && month_no_diff && day_no_diff){
int hour_diff = T->RS485_TimeStamp.Hour() - T->CAN_TimeStamp.Hour();
if(hour_diff == 0){
hour_no_diff = 1;
// Serial.print(", No difference in hour");
} else if(hour_diff < 0){
T->Temperature = T->CAN_Temperature;
// Serial.print("CAN is actual(hour), ");
} else {
T->Temperature = T->RS485_Temperature;
// Serial.print("RS485 is actual(hour), ");
}
}
/*If there is no difference in year, month and hour, then check the minute*/
if(year_no_diff && month_no_diff && day_no_diff && hour_no_diff){
int minute_diff = T->RS485_TimeStamp.Minute() - T->CAN_TimeStamp.Minute();
if(minute_diff == 0){
minute_no_diff = 1;
// Serial.print(", No difference in minute");
} else if(minute_diff < 0){
T->Temperature = T->CAN_Temperature;
// Serial.print("CAN is actual(minute), ");
} else {
T->Temperature = T->RS485_Temperature;
//Serial.print("RS485 is actual(minute), ");
}
}
/*If there is no difference in year, month and hour, minute then check the second*/
if(year_no_diff && month_no_diff && day_no_diff && hour_no_diff && minute_no_diff){
int second_diff = T->RS485_TimeStamp.Second() - T->CAN_TimeStamp.Second();
if(second_diff == 0){
second_no_diff = 1;
//Serial.print(", No difference in second");
} else if(second_diff < 0){
T->Temperature = T->CAN_Temperature;
//Serial.print("CAN is actual(second), ");
} else {
T->Temperature =T->RS485_Temperature;
//Serial.print("RS485 is actual(second), ");
}
}
/* If there is no difference in the two timestamp, then consider bothsignals okay from availability and integrity point
* maybe one of them can be used, but currently the average shall be used.*/
if(year_no_diff && month_no_diff && day_no_diff && hour_no_diff && minute_no_diff && second_no_diff){
T->Temperature = (T->RS485_Temperature + T->CAN_Temperature) / 2;
}
/* If the CAN is not okay, then signals from RS485 shall be copied*/
/*2 - CAN Error, 3 - RS485 Error */
} else if((T->CAN_QoS != 1) && (T->RS485_QoS == 1)){
//Serial.print("T QoS is bad, RS485 selected");
T->Temperature =T->RS485_Temperature;
T->QoS = 2;
/* If the RS485 is not okay, then signals from CAN shall be copied*/
} else if((T->CAN_QoS == 1) && (T->RS485_QoS != 1)){
//Serial.print("T1 QoS is bad, CAN selected");
T->Temperature =T->CAN_Temperature;
T->QoS = 3;
/*If no QoS is okay, then set the QoS to */
} else if((T->CAN_QoS != 1) && (T->RS485_QoS != 1)){
//Serial.print("T1 both QoS is bad, default value");
T->Temperature = 40000.0;
T->QoS = 0;
}
//Serial.println();
}
void FlowSignalAvailabilityCheck(struct Flw *F){
int year_no_diff = 0, month_no_diff = 0, day_no_diff = 0, hour_no_diff = 0, minute_no_diff = 0, second_no_diff = 0;
/*First, the signals QoS and the msg counters shall be checked
* - If both are okay, then the timestamp shall be checked and the newest shall be used
* - If one of them is okay, then use the signal with the okay QoS
* - If none is okay, then set an error
* All the data in the timestamp shall be checked for diff, if there is no diff use the average.*/
if((F->CAN_QoS == 1) && (F->RS485_QoS == 1)){
F->QoS = 1;
/*Compare the timestamps, starting with the year*/
int year_diff = F->RS485_TimeStamp.Year() - F->CAN_TimeStamp.Year();
if(year_diff == 0){
year_no_diff = 1;
// Serial.print("No difference in year, ");
} else if(year_diff < 0){
F->Flow = F->CAN_Flow;
// Serial.print("CAN is actual(year), ");
} else {
F->Flow = F->RS485_Flow;
// Serial.print("RS485 is actual(year), ");
}
/*If there is no difference in year, then check the month*/
if(year_no_diff){
int month_diff = F->RS485_TimeStamp.Month() - F->CAN_TimeStamp.Month();
if(month_diff == 0){
month_no_diff = 1;
// Serial.print(", No difference in month, ");
} else if(month_diff < 0){
F->Flow = F->CAN_Flow;
// Serial.print("CAN is actual(month), ");
} else {
F->Flow = F->RS485_Flow;
// Serial.print("RS485 is actual(month), ");
}
}
/*If there is no difference in year and month, then check the day*/
if(year_no_diff && month_no_diff){
int day_diff = F->RS485_TimeStamp.Day() - F->CAN_TimeStamp.Day();
if(day_diff == 0){
day_no_diff = 1;
// Serial.print(", No difference in day");
} else if(day_diff < 0){
F->Flow = F->CAN_Flow;
// Serial.print("CAN is actual(day), ");
} else {
F->Flow = F->RS485_Flow;
// Serial.print("RS485 is actual(day),");
}
}
/*If there is no difference in year, month and day, then check the hour*/
if(year_no_diff && month_no_diff && day_no_diff){
int hour_diff = F->RS485_TimeStamp.Hour() - F->CAN_TimeStamp.Hour();
if(hour_diff == 0){
hour_no_diff = 1;
// Serial.print(", No difference in hour");
} else if(hour_diff < 0){
F->Flow = F->CAN_Flow;
// Serial.print("CAN is actual(hour), ");
} else {
F->Flow = F->RS485_Flow;
// Serial.print("RS485 is actual(hour), ");
}
}
/*If there is no difference in year, month and hour, then check the minute*/
if(year_no_diff && month_no_diff && day_no_diff && hour_no_diff){
int minute_diff = F->RS485_TimeStamp.Minute() - F->CAN_TimeStamp.Minute();
if(minute_diff == 0){
minute_no_diff = 1;
// Serial.print(", No difference in minute");
} else if(minute_diff < 0){
F->Flow = F->CAN_Flow;
// Serial.print("CAN is actual(minute), ");
} else {
F->Flow = F->RS485_Flow;
// Serial.print("RS485 is actual(minute), ");
}
}
/*If there is no difference in year, month and hour, minute then check the second*/
if(year_no_diff && month_no_diff && day_no_diff && hour_no_diff && minute_no_diff){
int second_diff = F->RS485_TimeStamp.Second() - F->CAN_TimeStamp.Second();
if(second_diff == 0){
second_no_diff = 1;
// Serial.print(", No difference in second");
} else if(second_diff < 0){
F->Flow = F->CAN_Flow;
// Serial.print("CAN is actual(second), ");
} else {
F->Flow = F->RS485_Flow;
// Serial.print("RS485 is actual(second), ");
}
}
/* If there is no difference in the two timestamp, then consider bothsignals okay from availability and integrity point
* maybe one of them can be used, but currently the average shall be used.*/
if(year_no_diff && month_no_diff && day_no_diff && hour_no_diff && minute_no_diff && second_no_diff){
F->Flow = (F->RS485_Flow + F->CAN_Flow) / 2;
}
/* If the CAN is not okay, then signals from RS485 shall be copied*/
/*2 - CAN Error, 3 - RS485 Error*/
} else if((F->CAN_QoS != 1) && (F->RS485_QoS == 1)){
//Serial.print("T QoS is bad, RS485 selected");
F->Flow = F->RS485_Flow;
F->QoS = 2;
/* If the RS485 is not okay, then signals from CAN shall be copied*/
} else if((F->CAN_QoS == 1) && (F->RS485_QoS != 1)){
//Serial.print("T1 QoS is bad, CAN selected");
F->Flow = F->CAN_Flow;
F->QoS = 3;
/*If no QoS is okay, then set the QoS to */
} else if((F->CAN_QoS != 1) && (F->RS485_QoS != 1)){
//Serial.print("T1 both QoS is bad, default value");
F->Flow = 40000.0;
F->QoS = 0;
}
// Serial.println();
}
/****** end of Signal availability check and selection ******/
void RS485MsgReceiving(){
if (Serial3.available()) {
char inputByte = Serial3.read();
// ha van hely, eltároljuk
if (dataIndex < BUFFER_SIZE - 1) {
DataToBeProcessed[dataIndex++] = inputByte;
DataToBeProcessed[dataIndex] = '\0'; // mindig lezárjuk
}
// ha newline jött, akkor feldolgozás
if (inputByte == '\n') {
char localBuffer[BUFFER_SIZE];
//Serial.print("Serial data: ");
//Serial.println(DataToBeProcessed);*/
strncpy(localBuffer, DataToBeProcessed, BUFFER_SIZE);
// reseteljük a globális buffert
dataIndex = 0;
DataToBeProcessed[0] = '\0';
// --- CRC leválasztása ---
char *lastComma = strrchr(localBuffer, ',');
if (!lastComma) return; // nincs CRC mező
int CRC_int = atoi(lastComma + 1);
//Serial.print("CRC: ");
//Serial.print(CRC_int);
*lastComma = '\0'; // levágjuk a CRC-t
// --- CRC újraszámítás ---
uint8_t DataInByte[14] = {0};
//Serial.print(", localBuffer: ");
//Serial.print(localBuffer);
strncpy((char*)DataInByte, localBuffer, sizeof(DataInByte));
int crc_calculated = calcCRC8(DataInByte, sizeof(DataInByte));
//Serial.print(", Calculated CRC: ");
//Serial.print(crc_calculated);
// --- MsgCounter leválasztása ---
char *msgCounterComma = strrchr(localBuffer, ',');
if (!msgCounterComma) return;
int MsgCounter = atoi(msgCounterComma + 1);
//Serial.print(", Msg counter: ");
//Serial.print(MsgCounter);
*msgCounterComma = '\0';
// --- ID és érték leválasztása ---
char *colonPos = strrchr(localBuffer, ':');
if (!colonPos) return;
float RecvData = atof(colonPos + 1);
*colonPos = '\0';
char RecvDataID[8];
strncpy(RecvDataID, localBuffer, sizeof(RecvDataID));
//Serial.print(", Rec data ID: ");
//Serial.print(RecvDataID);
RecvDataID[sizeof(RecvDataID)-1] = '\0';
// --- CRC validálás ---
if (crc_calculated != CRC_int) return;
//Serial.print("CRC OK");
// --- ID alapján eltárolás ---
if (strcmp(RecvDataID, "T1") == 0) {
if ((MsgCounter != T1.RS485_PrevMsgCounter) && (RecvData > TemperaturePlausibilityMin) && (RecvData < TemperaturePlausibilityMax) && ((abs(RecvData - T1N.RS485_Temperature) > TemperatureChangeLimit))) {
//Serial.print("Msg counter OK");
T1.RS485_timeout = 0;
T1.RS485_Temperature = RecvData;
T1.RS485_QoS = 1;
T1.RS485_TimeStamp = Rtc.GetDateTime();
T1.RS485_PrevMsgCounter = MsgCounter;
// Serial.print("RS485 T1 received: ");
// Serial.println(RecvData);
} else {
T1.RS485_timeout = 0;
T1.RS485_Temperature = 40000.0;
T1.RS485_QoS = 0;
T1.RS485_TimeStamp = Default_TimeStamp;
T1.RS485_PrevMsgCounter = 255;
T1.RS485_TimeStamp = Default_TimeStamp;
}
}
else if (strcmp(RecvDataID, "T2") == 0) {
if ((MsgCounter != T2.RS485_PrevMsgCounter) && (RecvData > TemperaturePlausibilityMin) && (RecvData < TemperaturePlausibilityMax) && ((abs(RecvData - T2N.RS485_Temperature) > TemperatureChangeLimit))){
T2.RS485_timeout = 0;
T2.RS485_Temperature = RecvData;
T2.RS485_QoS = 1;
T2.RS485_TimeStamp = Rtc.GetDateTime();
T2.RS485_PrevMsgCounter = MsgCounter;
// Serial.print("RS485 T1 received: ");
// Serial.println(RecvData);
} else {
T2.RS485_timeout = 0;
T2.RS485_Temperature = 40000.0;
T2.RS485_QoS = 0;
T2.RS485_TimeStamp = Default_TimeStamp;
T2.RS485_PrevMsgCounter = 255;
T2.RS485_TimeStamp = Default_TimeStamp;
}
}
else if (strcmp(RecvDataID, "T3") == 0) {
// ugyanaz mint T1, csak T3-ra
}
else if (strcmp(RecvDataID, "T4") == 0) {
// ugyanaz mint T1, csak T4-re
}
else if (strcmp(RecvDataID, "F3") == 0) {
if (MsgCounter != F3.RS485_PrevMsgCounter) {
F3.RS485_timeout = 0;
F3.RS485_Flow = RecvData;
F3.RS485_QoS = 1;
F3.RS485_TimeStamp = Rtc.GetDateTime();
F3.RS485_PrevMsgCounter = MsgCounter;
// Serial.print("RS485 F3 received: ");
// Serial.println(RecvData);
} else {
F3.RS485_timeout = 0;
F3.RS485_Flow = 40000.0;
F3.RS485_QoS = 0;
F3.RS485_TimeStamp = Default_TimeStamp;
F3.RS485_PrevMsgCounter = 255;
F3.RS485_TimeStamp = Default_TimeStamp;
}
}
else if (strcmp(RecvDataID, "F4") == 0) {
if (MsgCounter != F4.RS485_PrevMsgCounter) {
F4.RS485_timeout = 0;
F4.RS485_Flow = RecvData;
F4.RS485_QoS = 1;
F4.RS485_TimeStamp = Rtc.GetDateTime();
F4.RS485_PrevMsgCounter = MsgCounter;
// Serial.print("RS485 F4 received: ");
// Serial.println(RecvData);
} else {
F4.RS485_timeout = 0;
F4.RS485_Flow = 40000.0;
F4.RS485_QoS = 0;
F4.RS485_TimeStamp = Default_TimeStamp;
F4.RS485_PrevMsgCounter = 255;
F4.RS485_TimeStamp = Default_TimeStamp;
}
}
}
// Serial.println();
}
}
void CANMsgReceiving(){
if(mcp2515.readMessage(&RecvcanMsg) == MCP2515::ERROR_OK){
//Serial.println("CAN Msg received");
/* If a message was received, then check the CRC*/
/* getting the received CRC value */
byte CRC = RecvcanMsg.data[5];
byte MsgCounter = RecvcanMsg.data[4];
/* calculating the CRC again*/
CRCcalc[0] = RecvcanMsg.data[0];
CRCcalc[1] = RecvcanMsg.data[1];
CRCcalc[2] = RecvcanMsg.data[2];
CRCcalc[3] = RecvcanMsg.data[3];
CRCcalc[4] = RecvcanMsg.data[4];
byte crc_calculated = calcCRC8(CRCcalc, 5);
/* T1 msg*/
if(RecvcanMsg.can_id == 0x020){
/* If T1 was received then first, set the timeout to 0*/
T1.CAN_timeout = 0;
float T1temp = 0.0;
/* first getting the value */
((uint8_t*)&T1temp)[0] = RecvcanMsg.data[0];
((uint8_t*)&T1temp)[1] = RecvcanMsg.data[1];
((uint8_t*)&T1temp)[2] = RecvcanMsg.data[2];
((uint8_t*)&T1temp)[3] = RecvcanMsg.data[3];
/*comparing the received and calculated CRC and modifying the QoS
* based on the comparization*/
if((crc_calculated == CRC) && (T1.CAN_PrevMsgCounter != MsgCounter) && (T1temp > TemperaturePlausibilityMin) && (T1temp < TemperaturePlausibilityMax) && ((abs(T1temp - T1N.CAN_Temperature) > TemperatureChangeLimit))){
T1.CAN_QoS = 1;
T1.CAN_Temperature = T1temp;
T1.CAN_TimeStamp = Rtc.GetDateTime();
T1.CAN_PrevMsgCounter = MsgCounter;
/*Serial.print("CAN T1 received:");
Serial.println(T1temp);
Serial.print("CAN: T1 received, temp:");
Serial.print(T1.CAN_Temperature);
Serial.print(", timestamp: Year ");
Serial.print(T1.CAN_TimeStamp.Year());
Serial.print(", Month: ");
Serial.print(T1.CAN_TimeStamp.Month());
Serial.print(", Day: ");
Serial.print(T1.CAN_TimeStamp.Day());
Serial.print(", Hour: ");
Serial.print(T1.CAN_TimeStamp.Hour());
Serial.print(", Minute: ");
Serial.print(T1.CAN_TimeStamp.Minute());
Serial.print(", Second: ");
Serial.println(T1.CAN_TimeStamp.Second());*/
} else {
T1.CAN_QoS = 0;
T1.CAN_Temperature = 999.9;
}
/* T2 msg*/
} else if (RecvcanMsg.can_id == 0x030){
/* If T2 was received then first, set the timeout to 0*/
T2.CAN_timeout = 0;
...
This file has been truncated, please download it to see its full contents.
#include <SPI.h>
#include <mcp2515.h>
#include "max6675.h"
#include <avr/wdt.h>
#include "CRC.h"
#include <Arduino.h>
#include <TM1637Display.h>
// Module connection pins (Digital Pins)
#define CLK 7
#define DIO 8
// RS485 connection pins
#define DE 3
#define RE 2
TM1637Display display(CLK, DIO);
struct can_frame canMsg1;
MCP2515 mcp2515(10);
int thermoDO1 = 4;
int thermoCS1 = 5;
int thermoCLK1 = 6;
int state_variable = 0;
int TestButtonPin = 9, TestButton = 0;
float temperature = 0.0;
String ToBeSent = "T1:";
byte StringInChar[14] = "";
byte MsgCount = 0;
MAX6675 thermocouple1(thermoCLK1, thermoCS1, thermoDO1);
byte CRCCalc[5];
uint8_t Displaydata[4] = { 0xff, 0xff, 0xff, 0xff };
const uint8_t SEG_T1[] = {
SEG_D, // _
SEG_D | SEG_E | SEG_F | SEG_G, // t
SEG_G, // -
SEG_B | SEG_C // 1
};
void setup() {
Serial.begin(115200);
Serial.println("T1 Startup");
wdt_disable();/* diable the watchdog and wait for more than 2 seconds */
delay(3000); /* Done so the Arduino doesnot keep resetting infinitely in case of wrong configuration*/
wdt_enable(WDTO_4S); /* Enable the watchdog with a timeout of 2 seconds */
canMsg1.can_id = 0x020;
canMsg1.can_dlc = 8;
canMsg1.data[0] = 0x00;
canMsg1.data[1] = 0x00;
canMsg1.data[2] = 0x00;
canMsg1.data[3] = 0x00;
canMsg1.data[4] = 0x00;
canMsg1.data[5] = 0x00;
canMsg1.data[6] = 0x00;
canMsg1.data[7] = 0x00;
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS);
mcp2515.setNormalMode();
//pinMode(13, OUTPUT);
// Set the DE and RE pins as outputs
pinMode(DE, OUTPUT);
pinMode(RE, OUTPUT);
// Set DE and RE high to enable transmission mode
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
pinMode(TestButtonPin, INPUT_PULLUP);
display.setBrightness(0x0f);
// wait for MAX chip to stabilize
delay(550);
}
void loop() {
TestButton = digitalRead(TestButtonPin);
/*Serial.print("Button State");
Serial.println(TestButton);*/
MsgCount++;
temperature = thermocouple1.readCelsius();
if(!TestButton){
temperature = 73.0;
}
ToBeSent = "T1:";
ToBeSent+=String(temperature,2);
ToBeSent+=",";
ToBeSent+=String(MsgCount);
ToBeSent.toCharArray(StringInChar, 14);
byte RS485crc_calculated = calcCRC8(StringInChar, 14);
ToBeSent+=",";
ToBeSent+=String(RS485crc_calculated);
ToBeSent+="\n";
digitalWrite(DE, HIGH);
digitalWrite(RE, HIGH);
Serial.print(ToBeSent);
Serial.flush();
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
CRCCalc[0] = ((uint8_t*)&temperature)[0];
CRCCalc[1] = ((uint8_t*)&temperature)[1];
CRCCalc[2] = ((uint8_t*)&temperature)[2];
CRCCalc[3] = ((uint8_t*)&temperature)[3];
CRCCalc[4] = MsgCount;
byte crc_calculated = calcCRC8(CRCCalc, 5);
canMsg1.data[0] = ((uint8_t*)&temperature)[0];
canMsg1.data[1] = ((uint8_t*)&temperature)[1];
canMsg1.data[2] = ((uint8_t*)&temperature)[2];
canMsg1.data[3] = ((uint8_t*)&temperature)[3];
canMsg1.data[4] = MsgCount;
canMsg1.data[5] = crc_calculated;
mcp2515.sendMessage(&canMsg1);
if(state_variable){
//digitalWrite(13, state_variable);
state_variable = 0;
display.clear();
display.showNumberDec((int)temperature, false);
} else {
//digitalWrite(13, state_variable);
state_variable = 1;
display.clear();
display.setSegments(SEG_T1);
}
wdt_reset(); /* Reset the wdt timer */
unsigned long DelayTime = random(1800, 2200);
delay(DelayTime);
/*Reset the uC after 255 execution to try to stabilize the communication*/
if(MsgCount >= 254){
while(1){}
}
}
#include <SPI.h>
#include <mcp2515.h>
#include "max6675.h"
#include <avr/wdt.h>
#include "CRC.h"
#include <Arduino.h>
#include <TM1637Display.h>
// Module connection pins (Digital Pins)
#define CLK 7
#define DIO 8
// RS485 connection pins
#define DE 3
#define RE 2
TM1637Display display(CLK, DIO);
struct can_frame canMsg1;
struct can_frame RecvcanMsg;
MCP2515 mcp2515(10);
int thermoDO1 = 4;
int thermoCS1 = 5;
int thermoCLK1 = 6;
int state_variable = 0;
int TestButtonPin = 9, TestButton = 0;
float temperature = 0.0;
String ToBeSent = "T2:";
byte StringInChar[14] = "";
byte MsgCount = 0;
MAX6675 thermocouple1(thermoCLK1, thermoCS1, thermoDO1);
byte CRCCalc[5];
uint8_t Displaydata[4] = { 0xff, 0xff, 0xff, 0xff };
const uint8_t SEG_T2[] = {
SEG_D, // _
SEG_D | SEG_E | SEG_F | SEG_G, // t
SEG_G, // -
SEG_A | SEG_B | SEG_D | SEG_E | SEG_G // 2
};
void setup() {
Serial.begin(115200);
Serial.println("T2 Startup");
wdt_disable();/* diable the watchdog and wait for more than 2 seconds */
delay(3000); /* Done so the Arduino doesnot keep resetting infinitely in case of wrong configuration*/
wdt_enable(WDTO_4S); /* Enable the watchdog with a timeout of 2 seconds */
canMsg1.can_id = 0x030;
canMsg1.can_dlc = 8;
canMsg1.data[0] = 0x00;
canMsg1.data[1] = 0x00;
canMsg1.data[2] = 0x00;
canMsg1.data[3] = 0x00;
canMsg1.data[4] = 0x00;
canMsg1.data[5] = 0x00;
canMsg1.data[6] = 0x00;
canMsg1.data[7] = 0x00;
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS);
mcp2515.setNormalMode();
pinMode(13, OUTPUT);
// Set the DE and RE pins as outputs
pinMode(DE, OUTPUT);
pinMode(RE, OUTPUT);
// Set DE and RE high to enable transmission mode
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
pinMode(TestButtonPin, INPUT_PULLUP);
display.setBrightness(0x0f);
// wait for MAX chip to stabilize
delay(750);
}
void loop() {
TestButton = digitalRead(TestButtonPin);
/*Serial.print("Button State");
Serial.println(TestButton);*/
MsgCount++;
temperature = thermocouple1.readCelsius();
if(!TestButton){
temperature = 73.0;
}
ToBeSent = "T2:";
ToBeSent+=String(temperature,2);
ToBeSent+=",";
ToBeSent+=String(MsgCount);
ToBeSent.toCharArray(StringInChar, 14);
byte RS485crc_calculated = calcCRC8(StringInChar, 14);
ToBeSent+=",";
ToBeSent+=String(RS485crc_calculated);
ToBeSent+="\n";
digitalWrite(DE, HIGH);
digitalWrite(RE, HIGH);
Serial.print(ToBeSent);
Serial.flush();
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
CRCCalc[0] = ((uint8_t*)&temperature)[0];
CRCCalc[1] = ((uint8_t*)&temperature)[1];
CRCCalc[2] = ((uint8_t*)&temperature)[2];
CRCCalc[3] = ((uint8_t*)&temperature)[3];
CRCCalc[4] = MsgCount;
byte crc_calculated = calcCRC8(CRCCalc, 5);
canMsg1.data[0] = ((uint8_t*)&temperature)[0];
canMsg1.data[1] = ((uint8_t*)&temperature)[1];
canMsg1.data[2] = ((uint8_t*)&temperature)[2];
canMsg1.data[3] = ((uint8_t*)&temperature)[3];
canMsg1.data[4] = MsgCount;
canMsg1.data[5] = crc_calculated;
mcp2515.sendMessage(&canMsg1);
if(state_variable){
digitalWrite(13, state_variable);
state_variable = 0;
display.clear();
display.showNumberDec((int)temperature, false);
} else {
digitalWrite(13, state_variable);
state_variable = 1;
display.clear();
display.setSegments(SEG_T2);
}
wdt_reset(); /* Reset the wdt timer */
unsigned long DelayTime = random(1800, 2200);
delay(DelayTime);
/*Reset the uC after 255 execution to try to stabilize the communication*/
if(MsgCount >= 254){
while(1){}
}
}


_ztBMuBhMHo.jpg?auto=compress%2Cformat&w=48&h=48&fit=fill&bg=ffffff)
_wzec989qrF.jpg?auto=compress%2Cformat&w=48&h=48&fit=fill&bg=ffffff)









Comments