Select Git revision
TemperatureController.ino
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);
}