class PID public: float Kp, Ki, Kd; float setpoint, input, output; float outMin, outMax;PID(float p, float i, float d, float minOut, float maxOut) Kp = p; Ki = i; Kd = d; outMin = minOut; outMax = maxOut; integral = 0.0; prevError = 0.0; prevTime = 0.0; firstRun = true; float compute(float currentInput)
private: float integral, prevError; unsigned long prevTime; bool firstRun; ;
Open Tinkercad and start a new Circuits project. Drag these components onto the breadboard:
Note: Since Tinkercad lacks a physical heater, we will simulate the plant (the heating/cooling physics) using a variable inside the Arduino code. The LED brightness will represent the "power" applied to the heater.
Add a button that, when pressed, applies a "load" by subtracting 50 from the feedback pot value (or adding a constant offset to the error). This tests how well your PID rejects disturbances.
Most PID tutorials jump straight to hardware: an Arduino Uno, a DC motor with an encoder, an H-bridge, and a pile of jumper wires. If something goes wrong (oscillations, smoke, a loose wire), debugging is a nightmare for a beginner.
Tinkercad eliminates those barriers:
In other words, Tinkercad is the ideal PID sandbox.
Open the code editor. We will write a basic PID class from scratch (no library) to understand the mechanics.
// Tinkercad PID Position Control for DC Motor double setpoint = 0; // Desired angle (0-1023 from pot) double input = 0; // Actual angle from feedback pot double output = 0; // PWM signal (-255 to 255) sent to motor double lastError = 0; double integral = 0;// PID Gains - Start with P only double Kp = 5.0; double Ki = 0.5; double Kd = 0.8;
// Timing unsigned long lastTime = 0; double dt = 0.1; // seconds
// Motor pins const int pwmPin = 9; const int dirPin = 8;
void setup() Serial.begin(9600); pinMode(pwmPin, OUTPUT); pinMode(dirPin, OUTPUT); tinkercad pid control
// Initialize setpoint from pot (we'll update in loop)
double computePID(double setp, double inp, double dt) double error = setp - inp;
// Proportional term double Pout = Kp * error;
// Integral term with anti-windup (clamp) integral += error * dt; double Iout = Ki * integral;
// Derivative term (on error, not measurement) double derivative = (error - lastError) / dt; double Dout = Kd * derivative;
// PID output double outputRaw = Pout + Iout + Dout; lastError = error; class PID public: float Kp, Ki, Kd; float
// Constrain output to -255 to 255 (PWM range) if (outputRaw > 255) outputRaw = 255; if (outputRaw < -255) outputRaw = -255;
return outputRaw;
void motorDrive(double cmd) if (cmd >= 0) digitalWrite(dirPin, HIGH); // Forward analogWrite(pwmPin, cmd); else digitalWrite(dirPin, LOW); // Reverse analogWrite(pwmPin, -cmd);
void loop() // Read setpoint (0 to 1023) setpoint = analogRead(A0);
// Read feedback position (0 to 1023 from "coupled" pot) input = analogRead(A1);
// Time delta for derivative and integral unsigned long now = millis(); double deltaTime = (now - lastTime) / 1000.0; if (deltaTime > 0.05) // Run PID every 50ms output = computePID(setpoint, input, deltaTime); motorDrive(output); lastTime = now; Open Tinkercad and start a new Circuits project
// Debug serial plotter data Serial.print(setpoint); Serial.print(" "); Serial.print(input); Serial.print(" "); Serial.println(output);