72 PID Implementation and Labs
72.1 Learning Objectives
By the end of this chapter, you will be able to:
- Implement PID Controllers: Write a discrete-time PID controller in Arduino/ESP32 C++ with proper sampling intervals
- Simulate PID Systems: Use Python to model and tune PID controllers before hardware deployment
- Apply Anti-Windup Techniques: Implement clamping and back-calculation to prevent integral saturation
- Tune PID Parameters Systematically: Apply Ziegler-Nichols and manual methods to optimize controller performance
- Evaluate PID Performance Metrics: Measure and compare settling time, overshoot, and steady-state error across configurations
For Beginners: PID Implementation and Labs
Process control in IoT is about automatically adjusting systems to maintain desired conditions. Think of cruise control in a car: it continuously measures your speed, compares it to your target, and adjusts the throttle to keep you on track. IoT systems use similar feedback loops to control everything from room temperature to industrial manufacturing processes.
72.2 Hands-On Lab: PID Control Simulation
In this lab, you’ll experiment with a simulated PID controller to understand how each parameter affects system behavior.
72.2.1 Lab Objective
Tune P, I, and D gains to achieve optimal control of a simulated temperature system, observing the effects of each parameter.
72.2.2 Simulated System
Consider a smart thermostat controlling room temperature: - Process: Room heating/cooling - Set Point: 22C - Initial Temperature: 18C - Disturbance: External temperature changes
72.2.3 Lab Tasks
72.3 Arduino PID Implementation
Putting Numbers to It
Discrete-time PID with 100ms sampling:
The continuous PID formula \(u(t) = K_p e(t) + K_i \int e(\tau) d\tau + K_d \frac{de}{dt}\) becomes discrete:
\[u[k] = K_p e[k] + K_i \sum_{j=0}^{k} e[j] \Delta t + K_d \frac{e[k] - e[k-1]}{\Delta t}\]
Example with Kp=2.0, Ki=0.5, Kd=1.0, \(\Delta t = 0.1\) seconds:
At time step k=10, current error e[10] = 2.5°C, previous error e[9] = 2.8°C, accumulated error sum = 35°C·s:
- P-term: \(2.0 \times 2.5 = 5.0\)
- I-term: \(0.5 \times 35 = 17.5\)
- D-term: \(1.0 \times \frac{2.5 - 2.8}{0.1} = 1.0 \times (-3) = -3.0\)
- Total output: \(u[10] = 5.0 + 17.5 - 3.0 = 19.5\)
The negative D-term provides “braking” since error is decreasing (temperature approaching setpoint).
72.4 Interactive PID Output Calculator
Explore how changing PID gains and error values affects controller output. Adjust the sliders to see real-time results.
For real hardware implementation with ESP32:
// ESP32 PID Temperature Controller
#include <Arduino.h>
class PIDController {
float kp, ki, kd, setpoint;
float integral = 0, previous_error = 0;
unsigned long last_time = 0;
public:
PIDController(float Kp, float Ki, float Kd, float sp)
: kp(Kp), ki(Ki), kd(Kd), setpoint(sp) {}
float update(float measured) {
unsigned long now = millis();
float dt = last_time ? (now - last_time) / 1000.0 : 0;
float error = setpoint - measured;
float P = kp * error;
integral = constrain(integral + error * dt, -100, 100); // Anti-windup
float I = ki * integral;
float D = dt > 0 ? kd * (error - previous_error) / dt : 0;
previous_error = error;
last_time = now;
return P + I + D;
}
void setSetpoint(float sp) { setpoint = sp; }
void reset() { integral = 0; previous_error = 0; last_time = 0; }
};The main program reads a TMP36 temperature sensor and drives a heater via PWM:
#define TEMP_SENSOR_PIN 34
#define HEATER_PWM_PIN 25
PIDController pid(2.0, 0.5, 1.0, 22.0); // Kp, Ki, Kd, setpoint
float readTemperature() {
float voltage = analogRead(TEMP_SENSOR_PIN) * (3.3 / 4095.0);
return (voltage - 0.5) * 100.0; // TMP36: 10mV/C, 500mV at 0C
}
void setup() {
Serial.begin(115200);
ledcSetup(0, 5000, 8); // Channel 0, 5kHz, 8-bit
ledcAttachPin(HEATER_PWM_PIN, 0);
}
void loop() {
float temp = readTemperature();
int pwm = constrain((int)pid.update(temp), 0, 255);
ledcWrite(0, pwm);
Serial.printf("%.1fs: setpoint=22.0, temp=%.1f, pwm=%d\n",
millis()/1000.0, temp, pwm);
delay(100);
}72.5 Interactive Quiz
Test your understanding of processes, systems, and PID control:
72.6 Python PID Implementation
72.6.1 PID Controller for IoT Systems
Key Features:
- Complete PID Implementation: Proportional, Integral, and Derivative terms
- Anti-Windup: Prevents integral term saturation
- Derivative Filtering: Reduces high-frequency noise
- Plant Simulation: First-order system with time delay
- Performance Metrics: Steady-state error, overshoot, rise time
Example Output:
=== PID Controller Simulation ===
--- P-only (Kp=2) ---
Steady-State Error: 16.67
Overshoot: 0.0%
Final Output: 33.33
--- PI (Kp=2, Ki=0.5) ---
Steady-State Error: 0.42
Overshoot: 12.3%
Final Output: 49.58
--- PID (Kp=2, Ki=0.5, Kd=0.1) ---
Steady-State Error: 0.38
Overshoot: 8.1%
Final Output: 49.62
=== IoT Temperature Control ===
Time(s) Temp(C) Heater(%)
------------------------------
0.0 18.00 83.4
10.0 20.15 67.2
20.0 21.52 42.1
30.0 21.89 28.3
60.0 22.01 21.7
Performance:
Steady-State Error: 0.15C
Overshoot: 2.3%
72.7 Production Framework: IoT Control Systems
This section provides a comprehensive, production-ready Python framework for IoT control systems, implementing PID controllers, open-loop and closed-loop architectures, system simulation, and auto-tuning capabilities.
This production framework provides comprehensive IoT control system capabilities including:
- PID Controller: Full PID implementation with anti-windup, output limiting, and configurable modes (P, PI, PD, PID)
- Plant Models: First-order, thermal, and motor dynamics with realistic physics
- System Simulation: Closed-loop and open-loop simulation with disturbance injection
- Auto-Tuning: Ziegler-Nichols and Cohen-Coon tuning methods
- Performance Analysis: 7 metrics including rise time, settling time, overshoot, and integral errors
The framework demonstrates production-ready patterns for IoT control systems with realistic plant models, comprehensive performance analysis, and auto-tuning capabilities.
For Kids: Meet the Sensor Squad!
Building a PID controller is like teaching a robot to be a perfect chef!
72.7.1 The Sensor Squad Adventure: The Robot Chef
Max the Microcontroller wanted to build a robot that could heat soup to EXACTLY 60 degrees – not too hot (ouch!), not too cold (yuck!).
“First, we need eyes!” said Sammy the Sensor. So they added a temperature sensor to the soup pot. Now the robot could CHECK the temperature every second.
“Next, we need a brain!” said Max, writing code on his ESP32 board. He programmed three rules:
- Rule P: “If the soup is cold, turn the heat up. If it’s almost ready, turn it down.”
- Rule I: “If the soup has been a teensy bit too cold for a while, slowly add more heat.”
- Rule D: “If the temperature is shooting up fast, ease off the heat NOW before it gets too hot!”
“What about me?” asked Bella the Battery. “I keep the robot running! Without power, the robot chef falls asleep and the soup goes cold!”
“And I flash green when the soup is ready!” said Lila the LED proudly.
They tested it: The robot heated the soup from room temperature to exactly 60 degrees with NO burning and NO waiting too long.
“We just built a REAL PID controller!” cheered Max. “And it only took a tiny computer, a temperature sensor, and some clever code!”
72.7.2 Key Words for Kids
| Word | What It Means |
|---|---|
| Code | Instructions that tell a computer what to do |
| ESP32 | A tiny, cheap computer perfect for IoT projects |
| Anti-windup | A safety rule that stops the robot from overreacting |
| PWM | A way to control how much power goes to the heater (like dimming a light) |
72.7.3 Try This at Home!
Be a human PID controller! Fill a sink with water and try to keep the temperature at “warm” by adjusting the hot and cold taps. Notice how you react to the current temperature (P), remember if it has been too cold for a while (I), and ease off when it is getting hot fast (D). You are running PID in your brain!
Common Mistake: Incorrect Sampling Rate in PID Controllers
The Mistake: A student implements PID temperature control for a greenhouse heater using an ESP32 and DHT22 sensor. They set the control loop to run every 100 ms (10 Hz) because “faster is better.” After deployment, the heater oscillates wildly between 50% and 100% power every few seconds, and temperature never stabilizes.
Why It Fails:
DHT22 sensor limitation: The DHT22 has a 2-second minimum sampling period. Reading it every 100 ms returns stale data 19 times out of 20. The controller sees 19 identical readings, then a sudden jump – the derivative term interprets this as a massive rate of change and overreacts.
Thermal time constant mismatch: A greenhouse heating system has a time constant of 5-10 minutes (the time to reach 63% of setpoint after a step input). Sampling every 100 ms is 3,000x faster than the process dynamics. The Nyquist criterion requires sampling at least 2x the highest frequency of interest, but sampling far beyond process dynamics amplifies noise without improving control.
Integral windup acceleration: With 10 Hz sampling, the integral term accumulates error 600 times per minute instead of 30 times. A 1°C error for 10 seconds adds 60°C-sec to the integral (instead of 5°C-sec at 0.5 Hz). Integral windup happens 12x faster, causing severe overshoot.
The Correct Approach:
Rule of thumb: Control loop frequency = 10x faster than process time constant, but never faster than sensor update rate.
For greenhouse temperature control:
- Process time constant: 5 minutes
- Sensor update rate: 2 seconds (DHT22 limit)
- Required loop frequency: 5 min / 10 = 30 seconds
- Chosen loop frequency: 10 seconds (2 sec sensor rate is fine, 30 sec provides margin)
Fixed implementation:
const unsigned long SAMPLE_INTERVAL = 10000; // 10 seconds in ms
void loop() {
static unsigned long last_time = 0;
unsigned long current_time = millis();
// Only run control loop every 10 seconds
if (current_time - last_time >= SAMPLE_INTERVAL) {
float temperature = readDHT22();
float pid_output = pid.update(temperature);
applyHeaterControl(pid_output);
last_time = current_time;
}
// Other non-control tasks can run every loop iteration
}Results before vs after:
| Metric | 100 ms Loop (Wrong) | 10 sec Loop (Correct) | Improvement |
|---|---|---|---|
| Temperature stability | ± 3.5°C oscillation | ± 0.4°C steady | 8.75x better |
| Heater cycling | 180 on/off per hour | 6 on/off per hour | 30x less wear |
| Power consumption | 2.8 kWh/hour (oscillating) | 1.9 kWh/hour (smooth) | 32% reduction |
| Control output variability | 0-100% every 3 seconds | 40-60% gradual | Stable operation |
Key lesson: Sampling too fast is as bad as sampling too slow. Match your control loop frequency to the process time constant (10x faster rule), and never exceed sensor update rate. For thermal systems (HVAC, ovens, refrigerators), 5-30 second sampling is typical. For motor control, 10-100 ms. For pressure control, 1-10 seconds.
Key Takeaway
Implementing PID on microcontrollers like ESP32 requires converting continuous-time PID math into discrete-time code with fixed sampling intervals (typically 10-100ms for fast processes, 5-30 seconds for thermal systems). Three critical implementation details separate working controllers from failing ones: anti-windup clamping prevents integral saturation during startup, derivative filtering avoids amplifying sensor noise, and proper output limiting keeps actuator commands within safe bounds. Start with lab simulation (P-only, then PI, then PID) to build tuning intuition before deploying on real hardware.
Key Concepts
- Discrete PID: The digital implementation of PID using numerical approximations of integration (accumulation) and differentiation (finite difference) updated at fixed sampling intervals, required for microcontroller execution
- Anti-Windup: A mechanism preventing the integral term from accumulating during actuator saturation — implemented by clamping the integral, back-calculating to reset it, or disabling integration when output is saturated
- Sample Time: The fixed interval (Ts) at which the PID algorithm executes on a microcontroller — must be 5–20× shorter than the dominant process time constant and consistent enough that Ki and Kd gains have predictable effect
- Output Clamping: Limiting the PID output to the physical range of the actuator (0–100% for a valve, 0–255 for PWM duty cycle) to prevent physically impossible commands from being sent to the actuator
- Bumpless Transfer: The technique of initializing the integral term to the current output when switching from manual to automatic control mode, preventing a step change (bump) in actuator output at the mode transition
- Fixed-Point Arithmetic: Integer-only computation scaling PID terms by a constant factor to avoid floating-point operations, required for microcontrollers without FPUs — must account for integer overflow in intermediate calculations
- Interrupt-Driven Control Loop: Executing the PID algorithm from a hardware timer interrupt at fixed intervals, ensuring consistent sample timing independent of other firmware tasks — critical for Kd and Ki accuracy
Common Pitfalls
1. Using Variable Sample Time Without Correcting Gains
Executing PID in an Arduino loop() function without fixed timing — if other code adds variable delay, Ki and Kd become incorrect because they depend on a fixed Ts. Use hardware timer interrupts for control loops requiring Ki or Kd terms.
2. Accumulating Integer Overflow in Fixed-Point PID
Storing the integral accumulator as a 16-bit integer when the accumulated value can exceed 32,767 over multiple seconds of error. Integer overflow causes the integral to silently wrap to a large negative value, causing the controller to slam the actuator in the wrong direction.
3. Not Implementing Output Clamping Before Anti-Windup
Implementing anti-windup without first clamping the output to actuator limits. If the PID output is not clamped, the anti-windup logic cannot detect saturation and the integral continues to wind up. Always clamp output first, then use the saturation state to trigger anti-windup.
4. Testing PID Only in Simulation
Tuning PID gains in a MATLAB/Simulink simulation then flashing to hardware and expecting the same behavior. Real hardware has sensor noise, actuator nonlinearity, communication delays, and timing jitter absent from simulation. Always validate and fine-tune on actual hardware with realistic loads.
72.8 Summary
This chapter provided hands-on implementation guidance for PID control systems.
Key Takeaways:
ESP32 Implementation: Complete PID controller class with anti-windup, suitable for real hardware deployment
Lab Exercises: Systematic exploration of P, PI, and PID configurations to build intuition for tuning
Performance Metrics: Steady-state error, overshoot, settling time are key indicators of control quality
Production Framework: Auto-tuning (Ziegler-Nichols, Cohen-Coon), plant models, and comprehensive simulation capabilities
Distributed Control: System-level feedback can span cloud and edge, but local control loops minimize latency
Edge Processing: Feature extraction and analytics at the edge reduces bandwidth 100-1000x for high-frequency sensor data
72.9 Further Reading
Control Theory Fundamentals:
- Ogata, K. (2010). Modern Control Engineering. Prentice Hall.
- Astrom, K. J., & Murray, R. M. (2021). Feedback Systems: An Introduction for Scientists and Engineers. Princeton University Press.
PID Tuning Methods:
- Ziegler-Nichols tuning method
- Cohen-Coon method
- Software auto-tuning algorithms
IoT-Specific Resources:
- ESP32 PID library: Arduino PID Library
- Industrial IoT control systems
- Edge computing for distributed control
Online Simulators:
- PID simulator: https://pidtuner.com
- Control system design tools in MATLAB/Simulink
72.10 References
Perera, C., et al. (2014). “Context Aware Computing for The Internet of Things: A Survey.” IEEE Communications Surveys & Tutorials.
Franklin, G. F., Powell, J. D., & Emami-Naeini, A. (2019). Feedback Control of Dynamic Systems. Pearson.
Ang, K. H., Chong, G., & Li, Y. (2005). “PID control system analysis, design, and technology.” IEEE Transactions on Control Systems Technology.
IoT Architecture Working Group. (2016). “IoT Reference Architecture.” IoT-A Project Deliverable.
Chapter Summary
This chapter established the practical implementation of PID control systems for IoT applications.
System Fundamentals: We explored how PID controllers are implemented in both Arduino/ESP32 C++ and Python, with anti-windup mechanisms and proper gain tuning.
Feedback and Control: The labs demonstrated how P-only, PI, and PID configurations behave differently, building intuition for when to use each approach.
Distributed Architecture: We examined how feedback loops can span edge and cloud, with local control loops handling time-critical responses and cloud coordination optimizing overall system performance.
These implementation skills prepare you for deploying real PID controllers in IoT applications from temperature control to motor speed regulation.
72.11 What’s Next?
Having mastered PID control theory and implementation, we now examine multi-hop ad-hoc networks. The next chapter builds upon these control foundations to explore network architectures and routing.
Continue to Multi Hop Ad Hoc Networks
| Previous | Current | Next |
|---|---|---|
| Integral and Derivative Control | PID Implementation and Labs | Multi-Hop Fundamentals |