// Load libraries #include #include #include #include #include #include #include #include // General definitions #define ON 0 #define OFF 1 // Pin setup // Arduino physical pins // Temperature probe pins const int pin_probe_1_CS = 52; const int pin_probe_1_SDI = 53; const int pin_probe_1_SDO = 50; const int pin_probe_1_CLK = 51; // Heating element SSR pins const int pin_ssr_element_1 = 30; // Relay board (element safety, pump, other) const int pin_relay_element_1 = 42; const int pin_relay_pump_1 = 38; // EEPROM value addresses // Temperature control PID values const int ER_address_PID_1_mode_1_kp = 10; const int ER_address_PID_1_mode_1_ki = 20; const int ER_address_PID_1_mode_1_kd = 30; // Tmperature control offsets const int ER_address_temp_probe_1_offset = 110; // Variable declaration and initialization // Blynk and network authentication char ssid[] = "INSERT SSID HERE"; char pass[] = "INSERT WIFI PASSWORD HERE"; char auth[] = "INSERT AUTH CODE HERE"; char IPaddress[] = "192.168.1.125"; #define EspSerial Serial1 #define ESP8266_BAUD 115200 ESP8266 wifi(&EspSerial); // Temperature control and PID initialization and objects // PID time window int PID_window_size = 2000L; unsigned long PID_window_start_time; byte element_state = 3; // RTD board initialization Adafruit_MAX31865 temp_probe_1 = Adafruit_MAX31865(pin_probe_1_CS,pin_probe_1_SDI,pin_probe_1_SDO,pin_probe_1_CLK); // Temp current, target, and tolerance for switching phases double temp_target=0; double temp_target_holding = 0; double temp_probe_1_current; double temp_target_tolerance=0.5; // Temperature probe 1 offset double temp_probe_1_offset; double temp_probe_1_offset_level_holding; // Element power interval level double element_PID_interval=0; // PID settings and intialization double PID_1_mode_1_kp; double PID_1_mode_1_ki; double PID_1_mode_1_kd; double PID_1_kp_holding; double PID_1_ki_holding; double PID_1_kd_holding; boolean PID_autotune_running=false; PID main_PID(&temp_probe_1_current, &element_PID_interval, &temp_target,0,0,0, DIRECT); PID_ATune aTune(&temp_probe_1_current, &element_PID_interval); double aTuneStep=500; double aTuneNoise=0.2; long aTuneStartValue=500; unsigned int aTuneLookBack=20; // Other // Blynk monitoring objects and strings // Set update timer for updating monitored values BlynkTimer update_mon_temp_timer; long update_mon_temp_interval = 2000; // Initialize Blynk monitors WidgetLED LED_element_1(V101); WidgetLED LED_pump_1(V103); WidgetRTC rtc; // Setup loop void setup() { // Start serial console Serial.begin(9600); delay(10); // Start blynk EspSerial.begin(ESP8266_BAUD); delay(10); Blynk.begin(auth, wifi, ssid, pass); // Pull current time and date from the RCT widget rtc.begin(); setSyncInterval(10 * 60); // SSRs digitalWrite(pin_ssr_element_1,OFF); pinMode(pin_ssr_element_1, OUTPUT); // Relay items (pumps, element safety, etc.) // Ensure relay board starts in OFF position digitalWrite(pin_relay_element_1,OFF); digitalWrite(pin_relay_pump_1,OFF); // Set pins as in/out pinMode(pin_relay_element_1,OUTPUT); pinMode(pin_relay_pump_1,OUTPUT); // Temperature probe // Start temp probe object temp_probe_1.begin(MAX31865_3WIRE); // Read EEPROM values EEPROM.get(ER_address_PID_1_mode_1_kp,PID_1_mode_1_kp); EEPROM.get(ER_address_PID_1_mode_1_ki,PID_1_mode_1_ki); EEPROM.get(ER_address_PID_1_mode_1_kd,PID_1_mode_1_kd); EEPROM.get(ER_address_temp_probe_1_offset,temp_probe_1_offset); EEPROM.get(ER_address_PID_1_mode_1_kp,PID_1_mode_1_kp); EEPROM.get(ER_address_PID_1_mode_1_ki,PID_1_mode_1_ki); EEPROM.get(ER_address_PID_1_mode_1_kd,PID_1_mode_1_kd); // Initialize PID settings PID_window_start_time = millis(); main_PID.SetOutputLimits(0, PID_window_size); main_PID.SetMode(AUTOMATIC); main_PID.SetTunings(PID_1_mode_1_kp, PID_1_mode_1_ki, PID_1_mode_1_kd); // Set default values for all inputs and holding values // General change_temp_target(0); // Manual page Blynk.virtualWrite(V1,int(temp_target)); pump_1_off(); // Config page Blynk.virtualWrite(V40,0); Blynk.virtualWrite(V45,0); Blynk.virtualWrite(V46,int(PID_1_mode_1_kp)); Blynk.virtualWrite(V47,int(PID_1_mode_1_ki)); Blynk.virtualWrite(V48,int(PID_1_mode_1_kd)); // Start monitoring timers update_mon_temp_timer.setInterval(update_mon_temp_interval, update_mon_temp); // Notify that Blynk is ready Blynk.notify("Brewing app ready"); } // Main Loop void loop() { // Update (read) current temperature update_probe_1_temp(); // Set PID temperature control or autotune mode; temp_control_update(); // Timers for pushing monitoring data to arduino update_mon_temp_timer.run(); // Update/run Blynk Blynk.run(); // Output for debugging //Serial.println(""); } // Controls temperature and autotune void temp_control_update(){ // Turns on elements based on how close they are to the target and if state change needed if ((temp_probe_1_current >= temp_target + 2)){ element_element_1_disable(); } else if ((temp_probe_1_current <= temp_target + 2) && (temp_target>0)) { element_element_1_enable(); } // Check if AutoTune is running if(PID_autotune_running) { // Check if tuning is complete byte val = (aTune.Runtime()); if (val!=0) { PID_autotune_running = false; } if(!PID_autotune_running) { // Get tuning parameters PID_1_kp_holding = aTune.GetKp(); PID_1_ki_holding = aTune.GetKi(); PID_1_kd_holding = aTune.GetKd(); // Send tuning values to app Blynk.virtualWrite(V46, PID_1_kp_holding); Blynk.virtualWrite(V47, PID_1_ki_holding); Blynk.virtualWrite(V48, PID_1_kd_holding); // Turn off autotune button Blynk.virtualWrite(V45, 0); // Send notification of autotune complete Blynk.notify("Autotune complete"); } } else { unsigned long now = millis(); if (now - PID_window_start_time > PID_window_size) { PID_window_start_time += PID_window_size;} if (element_PID_interval > now - PID_window_start_time) { digitalWrite(pin_ssr_element_1, HIGH); } else { digitalWrite(pin_ssr_element_1, LOW); } } main_PID.Compute(); } // Start/Stop Autotune void PID_auto_tune_start(){ element_PID_interval=aTuneStartValue; aTune.SetNoiseBand(aTuneNoise); aTune.SetOutputStep(aTuneStep); aTune.SetLookbackSec((int)aTuneLookBack); aTune.SetControlType(1); main_PID.SetMode(MANUAL); PID_autotune_running = true; } void PID_auto_tune_cancel() { aTune.Cancel(); PID_autotune_running = false; main_PID.SetMode(AUTOMATIC); } void change_temp_target(double new_temp) { temp_target = new_temp; Blynk.virtualWrite(V111, temp_target); Blynk.virtualWrite(V113, temp_target); Blynk.virtualWrite(V1, temp_target); } // Update monitors void update_mon_temp() {Blynk.virtualWrite(V110, temp_probe_1_current);} void update_mon_temp_target() { Blynk.virtualWrite(V111, temp_target); Blynk.virtualWrite(V113, temp_target); } // Read temp probe 1 (including conversion to F and offset) void update_probe_1_temp() { temp_probe_1_current = temp_probe_1.temperature(100.0, 430.0)*9/5 + 32 + temp_probe_1_offset; } // Enable/disable relay board items void element_element_1_enable(){ digitalWrite(pin_relay_element_1,ON); LED_element_1.on(); } void element_element_1_disable() { digitalWrite(pin_relay_element_1,OFF); LED_element_1.off(); } void pump_1_on(){ digitalWrite(pin_relay_pump_1,ON); LED_pump_1.on(); Blynk.virtualWrite(V30, 1); } void pump_1_off() { digitalWrite(pin_relay_pump_1,OFF); LED_pump_1.off(); Blynk.virtualWrite(V30, 0); } // Configure/update probe 1 offset void update_probe_1_offset(){ // Determine difference between calibrated temp and current readout temp temp_probe_1_offset = temp_probe_1_offset_level_holding - (temp_probe_1.temperature(100.0, 430.0)*9/5 + 32); // Write new value to EEPROM EEPROM.put(ER_address_temp_probe_1_offset,temp_probe_1_offset); } // Configure/update PID tunings void update_PID_tunings(){ // Update current values PID_1_mode_1_kp = PID_1_kp_holding; PID_1_mode_1_ki = PID_1_ki_holding; PID_1_mode_1_kd = PID_1_kd_holding; main_PID.SetTunings(PID_1_mode_1_kp, PID_1_mode_1_ki, PID_1_mode_1_kd); // Write new values to EEPROM EEPROM.put(ER_address_PID_1_mode_1_kp,PID_1_mode_1_kp); EEPROM.put(ER_address_PID_1_mode_1_ki,PID_1_mode_1_ki); EEPROM.put(ER_address_PID_1_mode_1_kd,PID_1_mode_1_kd); } void emergency_stop(){ // Stops everything ASAP in two phases to stop everythign immediately and get the UI reset // Phase 1) Directly orders all commands to stop everything immediately as fast as possible to avoid slow Arduino/Blynk response time for UI // Relay board items digitalWrite(pin_relay_pump_1,OFF); digitalWrite(pin_relay_element_1,OFF); // Phase 2) Repeat the above using the "correct" commands to reset the UI/Blynk properties correctly // Relay board items pump_1_off(); element_element_1_disable(); // General mode and notifications change_temp_target(0); Blynk.notify("Emergency stop"); } // Blynk buttons, inputs, and settings // Manual control inputs // Emergency stop buttons BLYNK_WRITE(V11) { emergency_stop(); } // Temperature input BLYNK_WRITE(V1) { temp_target_holding = param.asDouble(); } // Set temp from temp input using button BLYNK_WRITE(V2) { change_temp_target(temp_target_holding); update_mon_temp_target(); main_PID.SetMode(AUTOMATIC); } // Pump button BLYNK_WRITE(V30) { int y = param.asInt(); if (y==1){ pump_1_on(); } if (y==0){ pump_1_off(); } } // Config inputs // Temperature offset input BLYNK_WRITE(V40) { temp_probe_1_offset_level_holding = param.asDouble(); } // Temperature offset set button BLYNK_WRITE(V41) { update_probe_1_offset(); } // Autotune run/cancel button BLYNK_WRITE(V45) { int y = param.asInt(); if (y==1){ PID_auto_tune_start(); } else { PID_auto_tune_cancel(); } } // Set PID tunings button BLYNK_WRITE(V44) { update_PID_tunings(); } BLYNK_WRITE(V46) { PID_1_kp_holding = param.asDouble(); } BLYNK_WRITE(V47) { PID_1_ki_holding = param.asDouble(); } BLYNK_WRITE(V48) { PID_1_kd_holding = param.asDouble(); }