/********************************************************
PID Basic simulated heater Example
Reading analog input 0 to control analog PWM output 3
********************************************************/
// This simulates a 20W heater block driven by the PID
// Vary the setpoint with the Pot, and watch the heater drive the temperature up
//
// Simulation at https://wokwi.com/projects/358122536159671297
//
// Based on
// Wokwi https://wokwi.com/projects/357374218559137793
// Wokwi https://wokwi.com/projects/356437164264235009
#include "PID_v1.h" // https://github.com/br3ttb/Arduino-PID-Library
#include <LiquidCrystal_I2C.h>
#define I2C_ADDR 0x27
#define LCD_COLUMNS 20
#define LCD_LINES 4
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
//Define Variables we'll be connecting to
double Setpoint, Output;
double Input = 25.0; // water initial temp
//Specify the links and initial tuning parameters
double Kp = 10, Ki = 0, Kd = 0; // works reasonably with sim heater block
//double Kp = 255, Ki = .0, Kd = 0; // works reasonably with sim heater block
//double Kp = 2, Ki = 5, Kd = 1; // commonly used defaults
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, P_ON_M, DIRECT);
const int PWM_PIN = 3; // UNO PWM pin
const int INPUT_PIN = -1; // Analog pin for Input (set <0 for simulation)
const int SETPOINT_PIN = A1; // Analog pin for Setpoint Potentiometer
const int SETPOINT_INDICATOR = 6; // PWM pin for indicating setpoint
const int INPUT_INDICATOR = 5; // PWM pin for indicating Input
void setup()
{
lcd.init();
lcd.backlight();
Serial.begin(115200);
Serial.println(__FILE__);
myPID.SetOutputLimits(0, 255);
if (SETPOINT_INDICATOR >= 0) pinMode(SETPOINT_INDICATOR, OUTPUT);
if (INPUT_INDICATOR >= 0) pinMode(INPUT_INDICATOR, OUTPUT);
Setpoint = 0;
//turn the PID on
myPID.SetMode(AUTOMATIC);
if (INPUT_PIN>0) {
Input = analogRead(INPUT_PIN);
} else {
Input = simPlant(Input, 0.0);
}
Serial.println("Setpoint Input Output Watts");
}
void loop()
{
// gather Input from INPUT_PIN or simulated block
float heaterWatts = (int) Output * 2500.0 / 255; // 20W heater
if (INPUT_PIN > 0 ) {
Input = analogRead(INPUT_PIN);
} else {
float blockTemp = simPlant(Input, heaterWatts); // simulate heating
Input = blockTemp; // read input from simulated heater block
}
if (myPID.Compute())
{
analogWrite(PWM_PIN, (int)Output);
Setpoint = 110 * (float)analogRead(SETPOINT_PIN) / 1023; // Read setpoint from potentiometer
if (INPUT_INDICATOR >= 0) analogWrite(INPUT_INDICATOR, Input);
if (SETPOINT_INDICATOR >= 0) analogWrite(SETPOINT_INDICATOR, Setpoint);
}
report(heaterWatts);
}
void report(float heaterWatts)
{
static uint32_t last = 0;
const int interval = 500;
if (millis() - last > interval) {
last += interval;
// Serial.print(millis()/1000.0);
Serial.print("SP:");
Serial.print(Setpoint);
Serial.print(" PV:");
Serial.print(Input);
Serial.print(" CV:");
Serial.print(100.0 * Output / 255.0);
// Serial.print(" Int:");
// Serial.print(myPID.GetIntegral());
// Serial.print(" watts:");
// Serial.print(heaterWatts);
Serial.print(' ');
Serial.println();
// Print something
lcd.setCursor(0, 0);
lcd.print("SP:");
lcd.setCursor(3, 0);
lcd.print(Setpoint, 1);
lcd.setCursor(0, 1);
lcd.print("PV:");
lcd.setCursor(3, 1);
lcd.print(Input, 1);
lcd.setCursor(10, 0);
lcd.print("H%:");
lcd.setCursor(13, 0);
float heatPercent = 100.0 * Output / 255.0;
String outputCV = String((int) heatPercent);
outputCV = outputCV + "% ";
lcd.print(outputCV);
// if ((int) outputCV < 100) {
// lcd.setCursor(15, 0);
// lcd.print(" ");
// }
}
}
float simPlant(float T, float Q) {
// heat input in W (or J/s)
float h = 200; // W/m2K thermal conduction of 2mm of stainless steel
float Cps = 4.19; // J/g°C -> thermal capacity of water
float area = 0.05; // m2 area for convection
float mass = 300; // g
float Tamb = 25; // °C
// static float T = Tamb; // °C
static uint32_t last = 0;
float interval = 500.0; // ms
float newT = T;
if (millis() - last >= interval) {
// Serial.print("T:");
// Serial.print(T);
// Serial.print(" interval/1000:");
// Serial.print(interval / 1000);
// Serial.print(" gain:");
// Serial.print(Q * (interval / 1000) / mass / Cps);
// Serial.print(" loss:");
// Serial.print((T - Tamb) * area * h);
// Serial.println();
last += interval;
// 0-dimensional heat transfer
double heatLoss = (T - Tamb) * area * h; // W
double totalHeatTransfer = Q - heatLoss;
newT = T + totalHeatTransfer * (interval / 1000) / (mass * Cps);
}
return newT;
}