0
\$\begingroup\$

I am trying to use a (discrete) I-PD controller to regulate the temperature of a pan I will be using to homebrew. However I would like to test my controller calculations and code, without having to fire up my heater and finding out 5 times in a row that I did something wrong. Is there a simulation I can use to test my code?

Code for reference

#include <Arduino.h>

#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 2
#define RELAY 3
#define PERIODMS 2000
#define PWMVALUE 50


#define RESET 0

// Start value = 20 degrees
// Endvalue = 93.5 degrees
// DeltaTemp (DT) = 93.5 - 20 = 73.5
// T1 = time at .735*DT = (0.283*73.5) = time at DT == 20+20.80 => T = 20+20.80 = 40.80
// T1 = 691.690550
// T2 = time at (0.632*73.5) = tijd time at DT == 20+46.45 => T = 20+46.45=66.45
// T2 = 1426.1424958
// Tsys = 1.5(T2-T1) = 1101.6 s
// Theta = T2-Tsys = 324.5 s
// Ksys = 93.5/50 = 1.87 degC/%
#define Tsys    1101.6
#define Theta   324.5
#define Ksys    1.87

// Cohen-coon
#define Kc ((Tsys/(Ksys*Theta))*((Theta/(4.0*Tsys)) +(4.0/3.0)))
#define Ti (Theta*(32.0*Tsys+6.0*Theta)/(13.0*Tsys+8.0*Theta))
#define Td (Theta*(4.0*Tsys)/(2*Theta+11*Tsys))

#define Ts (0.1875*Theta)

// #define q0 (Kc * ( 1 + (Ts/(2 * Ti)) + (Td/Ts)))
// #define q1 (Kc * (-1 + (Ts/(2 * Ti)) - (2*Td/Ts)))
// #define q2 (Kc * (Td / Ts))

OneWire oneWire(ONE_WIRE_BUS);

DallasTemperature sensors(&oneWire);

uint64_t startTime;
uint16_t flipTime;
uint8_t setPoint;

void setPWM(uint8_t pwm) { // in %
    if (pwm > 100) {
        pwm = 100;
    }
    flipTime = PERIODMS*pwm/100;
}

void setSetpoint(uint8_t setpoint) {
    if (setpoint > 95) {
        setpoint = 95;
    } 
    setPoint = setpoint;
}

float getSensorTemp(bool report) {
    static float currentTemp = 20, lastTemp = 20;
    sensors.requestTemperatures(); 
    currentTemp = sensors.getTempCByIndex(0);
    if(currentTemp == -127)
        currentTemp = lastTemp;
    if(currentTemp != lastTemp){
        lastTemp = currentTemp;
        uint32_t secondsPassed = (millis() - startTime)/1000;
        Serial.print(secondsPassed);
        Serial.print('.');
        Serial.print(uint32_t(millis()-startTime-secondsPassed));
        Serial.print("; ");
        Serial.println(currentTemp);
    }
    return currentTemp;
}

uint16_t control(float e0, float y0) {
    // u0 = output(t-0), u1 = output(t-1)
    // e = error, y = process output (degrees)
    static int32_t u0, u1;
    static float e1, e2, y1, y2;

    u0 = u1 + Kc * (-y0 + y1 + (Ts/Ti) * e0 + (Td/Ts) * (-y0 + 2 * y1 - y2));
    if(u0 > 100) {
        u0 = 100;
    } else if(u0 < 0) {
        u0 = 0;
    }
    
    u1 = u0;
    e2 = e1;
    e1 = e0;

    return (uint16_t)u0;
}

void setup(void) {
    pinMode(RELAY, OUTPUT);
    digitalWrite(RELAY, LOW);
    while(RESET);
    
    Serial.begin(9600);
    sensors.setWaitForConversion(0);
    sensors.begin();
    delay(1000);
    
    // Start heating 
    digitalWrite(RELAY, HIGH);
    startTime = millis();

    setSetpoint(80);
    setPWM(100);
}

void loop(void) {
    float currentTemp;
    static uint64_t lastPeriod = 0, lastControl = 0;
    static bool pinState = digitalRead(RELAY);
    

    do {
        currentTemp = getSensorTemp(true);

        // Control function if passed sampletime
        if(millis() >= lastControl + Ts) {
            control(setPoint - currentTemp, currentTemp);
            lastControl = millis(); 
        }

        // PWM Calculations 
        if((millis() >= lastPeriod + flipTime) && pinState) {
            digitalWrite(RELAY, LOW);
            pinState = LOW;
        } 
        if(millis() >= lastPeriod + PERIODMS) { 
            lastPeriod = millis();
            digitalWrite(RELAY, HIGH);
            pinState = HIGH;
        }

    } while (currentTemp < 95);
    digitalWrite(RELAY, LOW);
    while(1)
        getSensorTemp(true);
}
```
\$\endgroup\$
4
  • \$\begingroup\$ You can write your own! Most of this code will run on your computer just as well. But you'll need to change the parts that talk to the sensors and the pins. And make fake time - put millis++ in each loop or something. Or copy your algorithm over to your favourite other computer language like Python \$\endgroup\$
    – user253751
    Sep 21 '20 at 18:40
  • \$\begingroup\$ Realistically you're going to spend more time trying to accurately simulate the system than it would take to just make the real one safe to test. One of the things you'll probably want to do is get good temperature logging going ultimately to a graph, so you can tell how it is performing; then do things like add a bunch of cold water to the pot (or whatever is being heated) \$\endgroup\$ Sep 21 '20 at 19:04
  • 1
    \$\begingroup\$ connect a lightbulb instead of the heater \$\endgroup\$
    – jsotola
    Sep 21 '20 at 19:27
  • \$\begingroup\$ @jsotola great idea, thanks! Although the time constant of a lightbulb is too short for my sensor (200ms) reducing the time constant from 20 minutes down to a minute really solves my problem. I’d mark this as the solution \$\endgroup\$
    – Ananas_hoi
    Sep 21 '20 at 19:31
3
\$\begingroup\$

Use a lightbulb instead of a heater.

A lightbulb gives you visual feedback.

It can also be used as a heatsource for a small beaker of water for accomodating the thermal sensor.

Hot water and ice cubes can be added to the beaker to reduce the time span of the heating/cooling cycles.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.