ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
pid_controller.cpp
Go to the documentation of this file.
1#include "pid_controller.h"
2
3namespace esphome::pid {
4
5float PIDController::update(float setpoint, float process_value) {
6 // e(t) ... error at timestamp t
7 // r(t) ... setpoint
8 // y(t) ... process value (sensor reading)
9 // u(t) ... output value
10
11 dt_ = calculate_relative_time_();
12
13 // e(t) := r(t) - y(t)
14 error_ = setpoint - process_value;
15
16 calculate_proportional_term_();
17 calculate_integral_term_();
18 calculate_derivative_term_(setpoint);
19
20 // u(t) := p(t) + i(t) + d(t)
21 float output = proportional_term_ + integral_term_ + derivative_term_;
22
23 // smooth/sample the output using shared buffer with mode-appropriate sample count
24 int samples = in_deadband() ? deadband_output_samples_ : output_samples_;
25 return ring_buffer_average_(output_window_, output, samples);
26}
27
29 // return (fabs(error) < deadband_threshold);
30 float err = -error_;
31 return (threshold_low_ < err && err < threshold_high_);
32}
33
34void PIDController::calculate_proportional_term_() {
35 // p(t) := K_p * e(t)
36 proportional_term_ = kp_ * error_;
37
38 // set dead-zone to -X to +X
39 if (in_deadband()) {
40 // shallow the proportional_term in the deadband by the pdm
41 proportional_term_ *= kp_multiplier_;
42
43 } else {
44 // pdm_offset prevents a jump when leaving the deadband
45 float threshold = (error_ < 0) ? threshold_high_ : threshold_low_;
46 float pdm_offset = (threshold - (kp_multiplier_ * threshold)) * kp_;
47 proportional_term_ += pdm_offset;
48 }
49}
50
51void PIDController::calculate_integral_term_() {
52 // i(t) := K_i * \int_{0}^{t} e(t) dt
53 float new_integral = error_ * dt_ * ki_;
54
55 if (in_deadband()) {
56 // shallow the integral when in the deadband
57 accumulated_integral_ += new_integral * ki_multiplier_;
58 } else {
59 accumulated_integral_ += new_integral;
60 }
61
62 // constrain accumulated integral value
63 if (!std::isnan(min_integral_) && accumulated_integral_ < min_integral_)
64 accumulated_integral_ = min_integral_;
65 if (!std::isnan(max_integral_) && accumulated_integral_ > max_integral_)
66 accumulated_integral_ = max_integral_;
67
68 integral_term_ = accumulated_integral_;
69}
70
71void PIDController::calculate_derivative_term_(float setpoint) {
72 // derivative_term_
73 // d(t) := K_d * de(t)/dt
74 float derivative = 0.0f;
75 if (dt_ != 0.0f) {
76 // remove changes to setpoint from error
77 if (!std::isnan(previous_setpoint_) && previous_setpoint_ != setpoint)
78 previous_error_ -= previous_setpoint_ - setpoint;
79 derivative = (error_ - previous_error_) / dt_;
80 }
81 previous_error_ = error_;
82 previous_setpoint_ = setpoint;
83
84 // smooth the derivative samples
85 derivative = ring_buffer_average_(derivative_window_, derivative, derivative_samples_);
86
87 derivative_term_ = kd_ * derivative;
88
89 if (in_deadband()) {
90 // shallow the derivative when in the deadband
91 derivative_term_ *= kd_multiplier_;
92 }
93}
94
95float PIDController::ring_buffer_average_(FixedRingBuffer<float> &buf, float new_value, int max_samples) {
96 // if only 1 sample needed (or invalid), clear the buffer and return
97 if (max_samples <= 1) {
98 buf.clear();
99 return new_value;
100 }
101
102 // Trim oldest entries to make room (handles mode-switching where buffer
103 // may have more entries than the current mode needs)
104 while (buf.size() >= static_cast<size_t>(max_samples))
105 buf.pop();
106 buf.push(new_value);
107
108 float sum = 0;
109 for (auto val : buf)
110 sum += val;
111 return sum / buf.size();
112}
113
114float PIDController::calculate_relative_time_() {
115 uint32_t now = millis();
116 uint32_t dt = now - this->last_time_;
117 if (last_time_ == 0) {
118 last_time_ = now;
119 return 0.0f;
120 }
121 last_time_ = now;
122 return dt / 1000.0f;
123}
124
125} // namespace esphome::pid
mopeka_std_values val[3]
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
static void uint32_t
float update(float setpoint, float process_value)