Skip to content
Snippets Groups Projects
Select Git revision
  • c54a39d6a3014a36d95f21172e5492946e3960f1
  • master default protected
  • ShutterRework
  • Shutterschalter-alt
4 results

TemperatureController.ino

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    TemperatureController.ino 13.24 KiB
    
    /*
     ***************************
     * Read sensor values and calculate a PID value from sensor 0.
     * Depending on this PID value, a control (relay, valve etc.) is switched.
     *
     * Error is setpoint minus current value.
     * For temperature: higher temperature is lower value. Positive P/I increases with rising temperatures.
     *
     * An additional channel can deactivate the controller as an emergency shut off.
     ***************************
    */
    
    //**        CONSTANTS PARAMETERS  change for your application     **//
    const String version = "2.0.0"; // Version of this script. Filename is added before, date of compilation afterwards.
    
    const unsigned long cyclePeriod = 1000; //program cycle period in ms
    const int controlPin = 11; //Pin by which the control unit is controlled (11 or 12)
    const int secondaryControlPin = -1;  // Secondary Pin to control output (or -1)
    
    const int emergencyPin = 5; // Analog channel to which the emergency sensor is connected. Set to -1 to deactivate
    const int emergencyThreshold = 341; // Threshold value, when the control should be deactivated due to emergency activated
    
    
    //**         CONSTANTS OF CODE  do not change              **//
    
    // I/O-pins
    const int sensor0Pin = 0; //Channel to which the sensor 0 is connected
    const int sensor1Pin = 1; //Channel to which the sensor 1 is connected
    const int sensor2Pin = 2; //Channel to which the sensor 2 is connected
    const int sensor3Pin = 3; //Channel to which the sensor 2 is connected
    const int sensor4Pin = 4; //Channel to which the sensor 2 is connected
    const int sensor5Pin = 5; //Channel to which the sensor 2 is connected
    
    const int powerPin = 13;  //Pin for indicating device is on; also used to indicate errors
    const int indicatorPin = 3;  //Pin for a LED indicating, whether the control is enabled (typically green)
    
    const int blueLedPin = 2;  //Pin for a blue LED
    const int redLedPin = 4;  //Pin for a red LED
    const int errorPin = 7;  //Pin for a bright red warning LED
    
    // ERROR indicator
    const int errorCyclePeriod = 2000;  // Interval between two error indicators in ms
    const int errorOnTime = 100;  // How long the output is on  (in ms) for a single blink
    const int errorInterval = 200;  // Interval for the error blinks
    
    // MISC
    const unsigned long cycle_period_us = cyclePeriod * 1000;  // for the µs counter
    const unsigned long max_ulong = -1;  // maximum value of unsigned long needed for rollover detection
    const float duty_cycle_upper_limit = 1.05;  // what the duty cycle should be at most. Should be more than 1 for continuous control
    const unsigned long rolloverThreshold = max_ulong - cycle_period_us * duty_cycle_upper_limit * 1.5;  // when the rollover protection should kick in.
    // ********************************************************************************
    
    
    // ***********************************VARIABLES************************************
    #include <EEPROM.h>
    const int floatsize = sizeof(float);
    
    // sensor parameters
    long measurements = 100; //How many times should the sensor be read for a single readout?
    const int averaging = 50;  // Number of times for the running average
    int precision = 5; // Number of digits to print.
    
    // Sensor values.
    float sensor0Value = 0; //Define sensor value variable
    float sensor1Value = 0; //Define sensor value variable
    float sensor2Value = 0; //Define sensor value variable
    float sensor3Value = 0; //Define sensor value variable
    float sensor4Value = 0; //Define sensor value variable
    float sensor5Value = 0; //Define sensor value variable
    
    #include "RunningAverage.h"
    
    RunningAverage average0(averaging); //generate running average variable
    RunningAverage average1(averaging); //generate running average variable
    RunningAverage average2(averaging); //generate running average variable
    RunningAverage average3(averaging); //generate running average variable
    RunningAverage average4(averaging); //generate running average variable
    RunningAverage average5(averaging); //generate running average variable
    
    // As we only want to do one measurement per second, we need to know, when we last ran.
    unsigned long nextCycle = 3000; //set starting values for an initial delay of some seconds to prevent racing conditions on start
    unsigned long nextOff = 0; // When to disable the control for the next time
    unsigned long nextError = 3000;  // when to show the next error
    
    
    //******************************PID-CONTROL*****************************************
    // Werte werden aus EEPROM gelesen
    float setpoint; // setpoint for the temperature = 36°C
    float PIDKp; //Wert für die PID Regelung (Proportional-Teil)
    float PIDKi; //Wert für die PID Regelung (Integral-Teil)
    float integral; //Wert für die PID Integralteil
    float duty_cycle; //Current duty cycle as fraction of cycle period
    
    int controller = 1;  // controller mode: 0 = off, 1 = on
    int error = 0;  // current error code
    //******************************************************************************
    
    
    void setup()  {
      //Setup the serial connection and the pins
      Serial.begin(9600);
    
      pinMode(blueLedPin,OUTPUT);
      pinMode(redLedPin,OUTPUT);
      pinMode(indicatorPin, OUTPUT);
      pinMode(controlPin, OUTPUT);
      pinMode(powerPin, OUTPUT);
      pinMode(errorPin, OUTPUT);
    
      pinMode(2, OUTPUT);
      digitalWrite(2, HIGH);
    
      digitalWrite(powerPin, HIGH);
    
      // Read PID values
      EEPROM.get(0 * floatsize, setpoint);
      if ( isnan(setpoint)){
        setpoint = 400;
      }
      EEPROM.get(1 * floatsize, PIDKp);
      if ( isnan(PIDKp)){
        PIDKp = 1;
      }
      EEPROM.get(2 * floatsize, PIDKi);
      if ( isnan(PIDKi)){
        PIDKi = 0.001;
      }
      EEPROM.get(3 * floatsize, integral);
      if ( isnan(integral)  || integral < 0 || integral > 1 ){
        integral = 0.1;
      }
    }
    
    
    void loop(){
      // Read the sensor many times and calculate the average
      sensor0Value = 0;
      sensor1Value = 0;
      sensor2Value = 0;
      sensor3Value = 0;
      sensor4Value = 0;
      sensor5Value = 0;
      for(int x=1; x <= measurements; x++){
        sensor0Value += analogRead(sensor0Pin);
        sensor1Value += analogRead(sensor1Pin);
        sensor2Value += analogRead(sensor2Pin);
        sensor3Value += analogRead(sensor3Pin);
        sensor4Value += analogRead(sensor4Pin);
        sensor5Value += analogRead(sensor5Pin);
      }
    
      sensor0Value /= measurements;
      sensor1Value /= measurements;
      sensor2Value /= measurements;
      sensor3Value /= measurements;
      sensor4Value /= measurements;
      sensor5Value /= measurements;
      average0.addValue(sensor0Value);
      average1.addValue(sensor1Value);
      average2.addValue(sensor2Value);
      average3.addValue(sensor3Value);
      average4.addValue(sensor4Value);
      average5.addValue(sensor5Value);
    
      int emergencyValue = analogRead(emergencyPin);
      if (emergencyPin >= 0 and emergencyValue > emergencyThreshold){
        controller = 0;
        error = 1;
        enable_control(false);
        digitalWrite(redLedPin, HIGH);
      }
    
      //Serial communication
      if ( Serial.available() ){
        char input[20];
        int len;
        len = Serial.readBytesUntil('\n', input, 20);
        input[len] = '\0';  // Null terminating the array to interpret it as a string
        switch (input[0]){
          case 'p':  // Ping
            Serial.println("Pong");
            break;
          case 'r':  // Send read average sensor data
            sendSensorDataAverage();
            break;
          case 'l':  // Send sensor data
            sendSensorDataLast();
            break;
          case 'm':  //Set number of measurements
            measurements = atoi(&input[1]);
            break;
          case 'f': //Set the precision
            precision = atoi(&input[1]);
            break;
          case 'c':  // Configure the controller
            controller = input[1]-'0';
            Serial.println("ACK-controller");
            break;
          case 'k'://Einstellen der PID Parameter
            if ( input[1] == 'p'){
              PIDKp=atof(&input[2]);
              Serial.println("ACK-Kp");
            }
            else if ( input[1] == 'i'){
              PIDKi=atof(&input[2]);
              Serial.println("ACK-Ki");
            }
            else if ( input[1] == 'x'){
              integral=atof(&input[2]);
              Serial.println("ACK-integral");
            }
            else if ( input[1] == 's'){
              setpoint = atof(&input[2]);
            Serial.println("ACK-setpoint");
            }
            break;
          case 'o': // direct output control
            if ( input[1] <= '0' ){
              enable_control( false );
              Serial.println("ACK disable control");
            }
            else {
              enable_control( true );
              Serial.println("ACK enable control");
            }
            break;
          case 'd': // debug
            Serial.print(integral);
            Serial.print(", p:");
            Serial.print(PIDKp);
            Serial.print(", i:");
            Serial.print(PIDKi);
            Serial.print(", setpoint: ");
            Serial.print(setpoint);
            Serial.print(", duty cycle: ");
            Serial.print(duty_cycle);
            Serial.println();
            break;
          case 'v':  // Send Version
            Serial.print(__FILE__);  // file name at compilation
            Serial.print(" ");
            Serial.print(version);
            Serial.print(" ");
            Serial.print(__DATE__);  // compilation date
            Serial.println();
            break;
          case 'e':  // Store values in EEPROM
            EEPROM.put(0 * floatsize, setpoint);
            EEPROM.put(1 * floatsize, PIDKp);
            EEPROM.put(2 * floatsize, PIDKi);
            EEPROM.put(3 * floatsize, integral);
            Serial.println("ACK EEPROM saved.");
            break;
          case 'x':  // Parameters, name TBD
            Serial.print("cyclePeriod: ");
            Serial.print(cyclePeriod);
            Serial.print("ms, ");
            Serial.print("controlPin: ");
            Serial.print(controlPin);
            Serial.print(", ");
            Serial.print("emergencyPin: ");
            Serial.print(emergencyPin);
            Serial.print(", ");
            Serial.print("emergencyThreshold: ");
            Serial.print(emergencyThreshold);
            Serial.println();
            break;
          case 's':  // show the current (error) state
            if ( input[1] == '?'){
              Serial.print("Errorcode: ");
              Serial.print(error);
              Serial.println();
            }
            else if ( input[1] == '0'){
              //reset state
              error = 0;
              Serial.println("ACK error state reset.");
            }
            break;
        }
      }
    
      // Start new pulse, calculating the duty cycle and pulse length
      if ( micros() >= nextCycle ){
        if (controller > 0){
          calculatePID();
        }
        nextCycle += cycle_period_us;
      }
    
      // stop the pulse, if pulse length elapsed
      if ( micros() >= nextOff && nextOff > 0){
        enable_control(false);
      }
    
      // show the error, if any
      if ( error > 0 && millis() >= nextError){
        show_error(error);
        nextError = millis() + errorCyclePeriod;
      }
    
      //if we are within cycle_period of rollover of counter suspend normal operation  
      if (micros() > rolloverThreshold){
        while (micros() > rolloverThreshold){};    //wait for rollover to happen
        nextCycle = 0;  // continue normally after rollover
      };  
    }
    
    
    void sendSensorDataLast(){
        // Send the last sensor values.
        Serial.print(sensor0Value, precision);
        Serial.print("\t");
        Serial.print(sensor1Value, precision);
        Serial.print("\t");
        Serial.print(sensor2Value, precision);
        Serial.print("\t");
        Serial.print(sensor3Value, precision);
        Serial.print("\t");
        Serial.print(sensor4Value, precision);
        Serial.print("\t");
        Serial.print(sensor5Value, precision);
        Serial.print("\t");
        Serial.print(setpoint, precision);
        Serial.println();
    }
    
    
    void sendSensorDataAverage(){
        // Send the running average sensor values.
        Serial.print(average0.getAverage(), precision);
        Serial.print("\t");
        Serial.print(average1.getAverage(), precision);
        Serial.print("\t");
        Serial.print(average2.getAverage(), precision);
        Serial.print("\t");
        Serial.print(average3.getAverage(), precision);
        Serial.print("\t");
        Serial.print(average4.getAverage(), precision);
        Serial.print("\t");
        Serial.print(average5.getAverage(), precision);
        Serial.println();
    }
    
    
    void calculatePID(){
      // Calculate the PID signal and duty cycle, and start a new pulse
      float error = setpoint - average0.getAverage();
    
      // calculate integral part, clamp it between 0 and 1
      integral += PIDKi * error;
      if (integral > 1){
        integral = 1;
      }
      else if (integral < 0){
        integral = 0;
      }
    
      // calculate the duty cycle, clamp it between 0 and 1
      duty_cycle = PIDKp * error + integral;
      // limit duty cycle against overflow
      if (duty_cycle > duty_cycle_upper_limit){
        duty_cycle = duty_cycle_upper_limit;
      }
      if (duty_cycle < 0){
        nextOff = 0;
        duty_cycle = 0;
        enable_control( false );
      }
      else {
        // start a new pulse and set the pulse length
        nextOff = micros() + cycle_period_us * duty_cycle;
        enable_control( true );
      }
    }
    
    void enable_control(const boolean enabled){
      //Enable or disable the control (relay, valve...)
      if (digitalRead(controlPin) != enabled){
        digitalWrite(controlPin, enabled);
        digitalWrite(indicatorPin, enabled);
        if (secondaryControlPin >= 0){
          digitalWrite(secondaryControlPin, enabled);
        }
      }
    }
    
    void show_error(int error){
      // Show an error via blinking
    //const int errorCyclePeriod = 3000;  // Interval between two error indicators
    //const int errorOnTime = 100;  // How long the output is on  (in ms) for a single blink
    //const int errorInterval = 200;  // Interval for the error blinks
      blink_once();
      for (int i = 1; i < error; i++) {
        delay(errorInterval - errorOnTime);
        blink_once();
      }
    }
    
    void blink_once(){
      digitalWrite(errorPin, HIGH);
      delay(errorOnTime);
      digitalWrite(errorPin, LOW);
    }