/* ////////////////////////////////////////////////////////////////// ARDUINO/Genuino Project "ONGMOD", a Temperature Controller https://www.changpuak.ch/electronics/Arduino-Ongmod.php Software Version 2.0 26.11.2021 by ALEXANDER SSE FRANK USES ARDUINO NANO EVERY AND TMP-101, MCP9600, ... The Serial Parser is from here : Easy_Diseqc V1.2, Monstein ETH Zurich, 20.06.2018 ////////////////////////////////////////////////////////////////// */ #include #include #include #include "Adafruit_MCP9600.h" // DISPLAY #define OLED_MOSI 6 #define OLED_CLK 5 #define OLED_DC 8 #define OLED_CS 9 #define OLED_RESET 7 // ROTARY ENCODER const int RotaryEncoder1 = 4 ; // PRESSED const int RotaryEncoder2 = 2 ; const int RotaryEncoder3 = 3 ; volatile bool LEFT = false ; volatile bool PRESS = true ; volatile bool RIGHT = false ; volatile bool READY = true ; // ANYTHING ELSE byte error = 0x00 ; char* Sensores[]={"TMP-101", "LM-35", "MCP9600K", "STS-30"} ; int SensorIndex = 2 ; int UpdateCount = 0 ; const int MaxSensorIndex = 3 ; const int smooth = 99 ; const int Update = 19 ; bool MOVEchange = false ; Adafruit_SH1106 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS); #if (SH1106_LCDHEIGHT != 64) #error("Height incorrect, please fix Adafruit_SH1106.h!"); #endif // SERIAL String buffer ; char tempbuf[80] ; // keeps the command temporary until CRLF // ///////////////////////////////////////////////////////////////////// // T E M P E R A T U R E // ///////////////////////////////////////////////////////////////////// double SetTemp = 64.0 ; double ActTemp = 0.0 ; float TempError = 0.0 ; float Hysteresis = 0.5 ; const float TempMax = 101.0 ; const float TempMin = 25.0 ; const int Heat = 11 ; const int Beep = 12 ; bool HeatIsOn = false ; void InitTempSensor() { // TMP 101, 12 Bit if(SensorIndex == 0) { Wire.beginTransmission(0x49); Wire.write(0x01); // Configuration Register Wire.write(0x60); // R0 and R1 = 1 Wire.endTransmission(); Serial.println(" >> done. Sensor is a TMP101."); } // LM35, ANALOG if(SensorIndex == 1) { Serial.println(" >> done. Sensor is a LM35."); } // MCP9600, 16 Bit if(SensorIndex == 2) { Wire.beginTransmission(0x60); Wire.write(0x00); // Configuration Register Wire.endTransmission(); Serial.println(" >> done. Sensor is a MCP9600."); } } void ReadTemp() { int a = 0x00 ; int b = 0x00 ; int c = 0x00 ; int negative = 0x00 ; double LastActTemp = ActTemp ; // TMP 101 if(SensorIndex == 0) { Wire.beginTransmission(0x49); Wire.write(0x00); error = Wire.endTransmission(); Wire.requestFrom(0x49,2); if(Wire.available()>0) { a = Wire.read(); // integer value b = Wire.read(); // fractional value negative = a & 0x80; a = a & 0x7F ; c = (( a << 8 ) | b ) >> 4 ; ActTemp = 0.0 + c * 0.0625 ; if (negative >= 1) ActTemp -= 128.0 ; } } // LM35 if(SensorIndex == 1) { a = analogRead(A0) ; ActTemp = a * (5.0 / 1023.0) ; } // MCP9600 if(SensorIndex == 2) { Wire.beginTransmission(0x60); Wire.write(0x00); error = Wire.endTransmission(); Wire.requestFrom(0x60,2); if(Wire.available()>0) { a = Wire.read(); // integer value b = Wire.read(); // fractional value negative = a & 0x80; a = a & 0x7F ; c = (( a << 8 ) | b ) ; ActTemp = 0.0 + c * 0.0625; if (negative >= 1) ActTemp -= 2048.0 ; } } if(error > 0) { Serial.print("ERROR : "); Serial.println(error,DEC); } // Average ActTemp += LastActTemp ; ActTemp /= 2.0 ; } // ///////////////////////////////////////////////////////////////////// // R E G U L A T I O N // ///////////////////////////////////////////////////////////////////// long HRS ; long MIN ; long SEC ; long TimeGoesBy = 0 ; unsigned long StartTime ; unsigned long StopTime ; const long TOGOmax = 35999 ; // 10 h const long TOGOmin = 60 ; // 1 min long TOGO = TOGOmin ; long TOGOstore = TOGO ; bool Counting = false ; // shall we count anyway ? void UpdateHHMMSS() { long aux = TOGO ; if(aux > TOGOmax) aux = TOGOmax ; HRS = aux / 3600 ; aux -= (HRS * 3600) ; MIN = aux / 60 ; aux -= (MIN * 60) ; SEC = aux ; } void DisplayHHMMSS() { if(HRS < 10) display.print("0") ; display.print(HRS,DEC) ; display.print(":") ; if(MIN < 10) display.print("0") ; display.print(MIN,DEC) ; display.print(":") ; if(SEC < 10) display.print("0") ; display.print(SEC,DEC) ; } // ///////////////////////////////////////////////////////////// // SUBROUTINES DISPLAY. // ///////////////////////////////////////////////////////////// int DisplayMode = 1 ; int CursorPos = 0 ; void SetCursor() { byte x = 0 ; byte y = 18 ; if(MOVEchange) { display.setTextSize(2) ; display.setCursor(20,23) ; if(CursorPos == 0) display.print(" ") ; if(CursorPos == 1) display.print(" ") ; if(CursorPos == 2) display.print(" ") ; if(CursorPos == 4) display.print(" ") ; if(CursorPos == 5) display.print(" ") ; if(CursorPos == 6) display.print(" ") ; if(CursorPos == 7) display.print(" ") ; if(CursorPos == 8) display.print(" ") ; if(CursorPos <= 8) display.print("_") ; if(CursorPos >= 9) display.drawTriangle(x,y,x+6,y+5,x,y+10,WHITE); } ; if(!MOVEchange) { if(CursorPos == 0) x = 32 ; if(CursorPos == 1) x = 44 ; if(CursorPos == 2) x = 68 ; if(CursorPos == 3) x = 20 ; if(CursorPos == 4) x = 32 ; if(CursorPos == 5) x = 56 ; if(CursorPos == 6) x = 68 ; if(CursorPos == 7) x = 92 ; if(CursorPos == 8) x = 104 ; y = 39 ; if(CursorPos < 9) display.fillTriangle(x,y,x+10,y,x+5,y-4,WHITE); x = 0 ; y = 18 ; if(CursorPos == 9) display.fillTriangle(x,y,x+6,y+5,x,y+10,WHITE); } } void ShowTitleBar() { display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0,0); display.print("****"); display.setCursor(46,0); display.print("ONGMOD"); display.setCursor(104,0); display.print("****"); display.drawLine(0, 11, 128, 11, WHITE); } void ShowErrorDisplay() { display.clearDisplay() ; ShowTitleBar() ; display.setTextSize(2) ; display.setCursor(0, 16) ; display.print("ERROR 404") ; display.setCursor(0, 33) ; display.print("TEMPSENSOR") ; display.setCursor(0, 50) ; display.print("NOT FOUND") ; display.display() ; } void RectangleMenue() { display.drawLine(0, 39, 128, 39, WHITE) ; display.drawLine(0, 63, 128, 63, WHITE) ; display.drawLine(0, 39, 0, 63, WHITE) ; display.drawLine(127, 39, 127, 63, WHITE) ; display.drawLine(10, 39, 10, 63, WHITE) ; display.drawLine(0, 45, 10, 45, WHITE) ; display.drawLine(0, 51, 10, 51, WHITE) ; display.drawLine(0, 57, 10, 57, WHITE) ; } void UpdateDisplay() { display.clearDisplay() ; ShowTitleBar() ; display.setTextSize(2) ; // ///////////////////////////////////////////// // SET TEMPERATURE // ///////////////////////////////////////////// if(DisplayMode == 1) { RectangleMenue() ; display.fillRect(2, 41, 7, 3, WHITE) ; display.setCursor(15, 44) ; display.setTextSize(2) ; display.print("SET TEMP") ; display.setCursor(20, 17) ; if(SetTemp < 100.0) display.print(" ") ; if(SetTemp < 10.0) display.print(" ") ; display.print(SetTemp,2) ; display.setCursor(110, 17) ; display.print("C") ; display.drawCircle(103,20,3,WHITE) ; display.drawCircle(103,20,2,WHITE) ; SetCursor() ; } // ///////////////////////////////////////////// // SET TIMER // ///////////////////////////////////////////// if(DisplayMode == 2) { RectangleMenue() ; display.fillRect(2, 47, 7, 3, WHITE) ; display.setCursor(15, 44) ; display.setTextSize(2) ; display.print("SET TIMER") ; display.setCursor(20, 17) ; UpdateHHMMSS() ; DisplayHHMMSS() ; SetCursor() ; } // ///////////////////////////////////////////// // SET SENSOR // ///////////////////////////////////////////// if(DisplayMode == 3) { RectangleMenue() ; display.fillRect(2, 53, 7, 3, WHITE) ; display.setCursor(15, 44) ; display.setTextSize(2) ; display.print("SENSOR ?") ; display.setCursor(15, 17) ; display.print(Sensores[SensorIndex]) ; SetCursor() ; } // ///////////////////////////////////////////// // RUN // ///////////////////////////////////////////// if(DisplayMode == 4) { RectangleMenue() ; display.fillRect(2, 59, 7, 3, WHITE) ; display.setCursor(15, 44) ; display.setTextSize(2) ; display.print("START") ; display.setCursor(15, 17) ; display.print("PRESS TO") ; SetCursor() ; } // ///////////////////////////////////////////// // DSIPLAY TEMPERATURE AND TIME // ///////////////////////////////////////////// if(DisplayMode == 5) { if (ActTemp >= 0.0) display.setCursor(9, 19) ; else display.setCursor(-3, 19) ; // WE ASSUME ALWAYS POSITIVE TEMPERATURES if((ActTemp < 100.0) and (ActTemp > -10)) display.print(" ") ; if(ActTemp < 10.0) display.print(" ") ; display.print(ActTemp,2) ; display.setCursor(104, 19) ; display.print("C") ; display.drawCircle(97,22,3,WHITE) ; display.drawCircle(97,22,2,WHITE) ; display.setCursor(9, 41) ; display.print(" ") ; UpdateHHMMSS() ; DisplayHHMMSS() ; display.setTextSize(1) ; display.setCursor(0, 16) ; if(HeatIsOn) display.print("H") ; } display.display() ; } // ///////////////////////////////////////////////////////////// // S E T U P // ///////////////////////////////////////////////////////////// void setup() { Wire.begin() ; Serial.begin(115200) ; // INIT OLED display.begin(SH1106_SWITCHCAPVCC); // SHOW STARTUP SCREEN display.clearDisplay() ; ShowTitleBar() ; display.setCursor(0, 21); display.println("TEMPERATUR CONTROLLER"); display.setCursor(0, 33); display.println("NOT ONLY FOR LAB USE."); display.setCursor(0, 45); display.println("(C) ETH QUANTUMOPTICS"); display.setCursor(0, 57); display.println("BUILT 26.03.2024"); display.display(); delay(999) ; pinMode(RotaryEncoder1, INPUT_PULLUP); pinMode(RotaryEncoder2, INPUT_PULLUP); pinMode(RotaryEncoder3, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(RotaryEncoder1), RotaryEncoderISR1, FALLING); attachInterrupt(digitalPinToInterrupt(RotaryEncoder2), RotaryEncoderISR2, FALLING); attachInterrupt(digitalPinToInterrupt(RotaryEncoder3), RotaryEncoderISR3, FALLING); // OUTPUTS pinMode( Heat , OUTPUT ) ; pinMode( Beep , OUTPUT ) ; digitalWrite( Beep, HIGH ) ; digitalWrite( Heat, LOW ) ; delay(999) ; digitalWrite( Beep, LOW ) ; Serial.println("ONGMOD 2.0 BY CHANGPUAK.CH") ; Serial.println("26.03.2024, (C) ETH QUANTUMOPTICS\n") ; // I2C delay(100) ; InitTempSensor() ; delay(3000) ; } // ///////////////////////////////////////////////////////////// // M A I N L O O P // ///////////////////////////////////////////////////////////// void loop() { switch(CursorPos) { // ///////////////////////////////////////////// // TEMPERATURE (SETPOINT) // ///////////////////////////////////////////// case 0: // ///////////////////////////////////////////// DisplayMode = 1 ; READY = false ; if(RIGHT) { if(MOVEchange) CursorPos = 1 ; else { SetTemp += 10.0 ; if(SetTemp > TempMax) SetTemp = TempMax ; } } if(LEFT) { if(MOVEchange) CursorPos = 10 ; else { SetTemp -= 10.0 ; if(SetTemp < TempMin) SetTemp = TempMin ; } } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// case 1: // ///////////////////////////////////////////// DisplayMode = 1 ; READY = false ; if(RIGHT) { if(MOVEchange) CursorPos = 2 ; else { SetTemp += 1.0 ; if(SetTemp > TempMax) SetTemp = TempMax ; } } if(LEFT) { if(MOVEchange) CursorPos = 0 ; else { SetTemp -= 1.0 ; if(SetTemp < TempMin) SetTemp = TempMin ; } } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// case 2: // ///////////////////////////////////////////// DisplayMode = 1 ; READY = false ; if(RIGHT) { if(MOVEchange) CursorPos = 3 ; else { SetTemp += 0.1 ; if(SetTemp > TempMax) SetTemp = TempMax ; } } if(LEFT) { if(MOVEchange) CursorPos = 1 ; else { SetTemp -= 0.1 ; if(SetTemp < TempMin) SetTemp = TempMin ; } } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// // T I M E R // ///////////////////////////////////////////// case 3: // ///////////////////////////////////////////// DisplayMode = 2 ; READY = false ; if(RIGHT) { if(MOVEchange) CursorPos = 4 ; else { TOGO += 36000 ; if(TOGO > TOGOmax) TOGO = TOGOmin ; } } if(LEFT) { if(MOVEchange) CursorPos = 2 ; else { TOGO -= 36000 ; if(TOGO < TOGOmin) TOGO = TOGOmax ; } } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// case 4: // ///////////////////////////////////////////// DisplayMode = 2 ; READY = false ; if(RIGHT) { if(MOVEchange) CursorPos = 5 ; else { TOGO += 3600 ; if(TOGO > TOGOmax) TOGO = TOGOmin ; } } if(LEFT) { if(MOVEchange) CursorPos = 3 ; else { TOGO -= 3600 ; if(TOGO < TOGOmin) TOGO = TOGOmax ; } } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// case 5: // ///////////////////////////////////////////// DisplayMode = 2 ; READY = false ; if(RIGHT) { if(MOVEchange) CursorPos = 6 ; else { TOGO += 600 ; if(TOGO > TOGOmax) TOGO = TOGOmin ; } } if(LEFT) { if(MOVEchange) CursorPos = 4 ; else { TOGO -= 600 ; if(TOGO < TOGOmin) TOGO = TOGOmax ; } } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// case 6: // ///////////////////////////////////////////// DisplayMode = 2 ; READY = false ; if(RIGHT) { if(MOVEchange) CursorPos = 7 ; else { TOGO += 60 ; if(TOGO > TOGOmax) TOGO = TOGOmin ; } } if(LEFT) { if(MOVEchange) CursorPos = 5 ; else { TOGO -= 60 ; if(TOGO < TOGOmin) TOGO = TOGOmax ; } } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// case 7: // ///////////////////////////////////////////// DisplayMode = 2 ; READY = false ; if(RIGHT) { if(MOVEchange) CursorPos = 8 ; else { TOGO += 10 ; if(TOGO > TOGOmax) TOGO = TOGOmin ; } } if(LEFT) { if(MOVEchange) CursorPos = 6 ; else { TOGO -= 10 ; if(TOGO < TOGOmin) TOGO = TOGOmax ; } } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// case 8: // ///////////////////////////////////////////// DisplayMode = 2 ; READY = false ; if(RIGHT) { if(MOVEchange) CursorPos = 9 ; else { TOGO += 1 ; if(TOGO > TOGOmax) TOGO = TOGOmin ; } } if(LEFT) { if(MOVEchange) CursorPos = 7 ; else { TOGO -= 1 ; if(TOGO < TOGOmin) TOGO = TOGOmax ; } } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// // CHOOSE SENSORTYPE // ///////////////////////////////////////////// case 9: // ///////////////////////////////////////////// DisplayMode = 3 ; READY = false ; if(RIGHT) { if(MOVEchange) CursorPos = 10 ; else { SensorIndex += 1 ; if(SensorIndex > MaxSensorIndex) SensorIndex = 0 ; InitTempSensor() ; } } if(LEFT) { if(MOVEchange) CursorPos = 8 ; else { SensorIndex -= 1 ; if(SensorIndex < 0) SensorIndex = MaxSensorIndex ; InitTempSensor() ; } } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// // R U N ? // ///////////////////////////////////////////// case 10: // ///////////////////////////////////////////// DisplayMode = 4 ; READY = false ; digitalWrite( Beep, LOW ) ; if(RIGHT) { if(MOVEchange) CursorPos = 0 ; } if(LEFT) { if(MOVEchange) CursorPos = 9 ; } RIGHT = false ; LEFT = false ; READY = true ; delay(smooth) ; break; // ///////////////////////////////////////////// case 11: // ///////////////////////////////////////////// DisplayMode = 5 ; // ////////////////////////////////// // RUNNING ... // ////////////////////////////////// TimeGoesBy = millis() % 1000 ; // FOR PWM ReadTemp() ; TempError = SetTemp - ActTemp ; if(Counting) TOGO = (StopTime - millis()) / 1000 ; if(TOGO > 0) { // ////////////////////////////////// // TWO POINT REGULATOR // ////////////////////////////////// if(TempError >= (Hysteresis/2.0)) { digitalWrite(Heat,HIGH) ; HeatIsOn = true ; } if(TempError <= (-0.5*Hysteresis)) { digitalWrite(Heat,LOW) ; HeatIsOn = false ; } // ////////////////////////////////// // END TWO POINT REGULATOR // ////////////////////////////////// delay(smooth) ; } // TIMER END ? if(TOGO <= 0) { // SWITCH OFF POWER digitalWrite( Heat, LOW ) ; HeatIsOn = false ; Counting = false ; TOGO = 0 ; digitalWrite( Beep, HIGH ) ; delay(199) ; digitalWrite( Beep, LOW ) ; } break; } // EVALUATE THE PRESS OF THE ROTARY ENCODER if(PRESS) { switch(CursorPos) { case 0: MOVEchange = !MOVEchange ; break; case 1: MOVEchange = !MOVEchange ; break; case 2: MOVEchange = !MOVEchange ; break; case 3: MOVEchange = !MOVEchange ; break; case 4: MOVEchange = !MOVEchange ; break; case 5: MOVEchange = !MOVEchange ; break; case 6: MOVEchange = !MOVEchange ; break; case 7: MOVEchange = !MOVEchange ; break; case 8: MOVEchange = !MOVEchange ; break; case 9: MOVEchange = !MOVEchange ; break; case 10: // START ONLY WHEN TOGO IS GREATER THAN MINIMUM if(TOGO >= TOGOmin) { CursorPos = 11 ; Counting = true ; DisplayMode = 5 ; StartTime = millis() ; StopTime = TOGO * 1000 + StartTime ; TOGOstore = TOGO ; } else { digitalWrite( Beep, HIGH ) ; delay(199) ; digitalWrite( Beep, LOW ) ; if(TOGO == 0) TOGO = TOGOstore ; } break; case 11: CursorPos = 10 ; Counting = false ; digitalWrite( Heat, LOW ) ; HeatIsOn = false ; DisplayMode = 4 ; break; } READY = false ; delay(smooth) ; PRESS = false ; READY = true ; } ReadTemp() ; UpdateDisplay() ; // ////////////////////////////////// // SERIAL PARSER // ////////////////////////////////// while (Serial.available() > 0) // ////////////////////////////////// { int tmp; char st[20]; char rx = Serial.read(); // read a single character buffer += rx; //add character to the string buffer if ((rx == '\n') || (rx == '\r')) { buffer.toCharArray(tempbuf, 40); if (buffer.startsWith("TEMP:")) { sscanf(tempbuf,"TEMP:%s",&st); // extract Setpoint as float SetTemp = strtod(st,NULL); if(SetTemp < TempMin) SetTemp = TempMin ; if(SetTemp > TempMax) SetTemp = TempMax ; } else if (buffer.startsWith("TEMP?")) { ReadTemp() ; Serial.println(ActTemp,3) ; } else if (buffer.startsWith("HEAT:ON")) { digitalWrite( Heat, HIGH ) ; HeatIsOn = true ; } else if (buffer.startsWith("HEAT:OFF")) { digitalWrite( Heat, LOW ) ; HeatIsOn = false ; } else if (buffer.startsWith("BEEP:ON")) { digitalWrite( Beep, HIGH ) ; } else if (buffer.startsWith("BEEP:OFF")) { digitalWrite( Beep, LOW ) ; } else if (buffer.startsWith("*IDN?")) { Serial.println("ONGMOD 2.0 BY CHANGPUAK.CH") ; Serial.println("26.03.2024, (C) ETH QUANTUMOPTICS") ; } buffer = "" ; //erase buffer for next command } } // REDUCE DATARATE UpdateCount += 1 ; if(UpdateCount >= Update) { Serial.println(ActTemp, 2) ; UpdateCount = 0 ; } delay(smooth) ; } // ///////////////////////////////////////////////////////////// // INTERRUPT SERVICE ROUTINES // ///////////////////////////////////////////////////////////// void RotaryEncoderISR1() { if(READY) { LEFT = false ; RIGHT = false ; PRESS = true ; } } void RotaryEncoderISR2() { if(READY) { LEFT = false ; RIGHT = false ; byte autre = digitalRead(RotaryEncoder3) ; if (autre > 0) RIGHT = true ; if (autre < 1) LEFT = true ; } } void RotaryEncoderISR3() { if(READY) { LEFT = false ; RIGHT = false ; byte autre = digitalRead(RotaryEncoder2) ; if (autre > 0) LEFT = true ; if (autre < 1) RIGHT = true ; } } // ///////////////////////////////////////////////////////////// // END OF FILE. // /////////////////////////////////////////////////////////////