DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
control.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file control.cpp
6 * @brief PID control law (pure single-precision float; FPU-accelerated on ESP32 / ESP32-S3).
7 */
8
10
11#if DETWS_ENABLE_CONTROL
12
13#include <string.h>
14
15// clamp helper is control_clamp() from control.h (inline) - reused here, not re-declared.
16
17void pid_init(Pid *p, float kp, float ki, float kd)
18{
19 if (!p)
20 return;
21 p->kp = kp;
22 p->ki = ki;
23 p->kd = kd;
24 p->kff = 0.0f;
25 p->out_min = -CONTROL_UNBOUNDED;
26 p->out_max = CONTROL_UNBOUNDED;
27 p->integ_min = -CONTROL_UNBOUNDED;
28 p->integ_max = CONTROL_UNBOUNDED;
29 p->d_alpha = 0.0f;
30 p->dt = 0.0f;
31 p->inv_dt = 0.0f;
32 pid_reset(p);
33}
34
35void pid_set_rate(Pid *p, float dt)
36{
37 if (p && dt > 0.0f)
38 {
39 p->dt = dt;
40 p->inv_dt = 1.0f / dt;
41 }
42}
43
44void pid_set_output_limits(Pid *p, float lo, float hi)
45{
46 if (p)
47 {
48 p->out_min = lo;
49 p->out_max = hi;
50 }
51}
52
53void pid_set_integral_limits(Pid *p, float lo, float hi)
54{
55 if (p)
56 {
57 p->integ_min = lo;
58 p->integ_max = hi;
59 }
60}
61
62void pid_set_derivative_filter(Pid *p, float alpha)
63{
64 if (p)
65 p->d_alpha = alpha;
66}
67
68void pid_set_feedforward(Pid *p, float kff)
69{
70 if (p)
71 p->kff = kff;
72}
73
74void pid_reset(Pid *p)
75{
76 if (!p)
77 return;
78 p->integ = 0.0f;
79 p->prev_meas = 0.0f;
80 p->d_filt = 0.0f;
81 p->primed = false;
82}
83
84// pid_update() is defined inline in control.h (zero call overhead); this TU just uses it below.
85
86void pid_update_n(Pid *p, const float *setpoint, const float *measurement, float dt, float *out, uint8_t n)
87{
88 if (!p || !setpoint || !measurement || !out)
89 return;
90 for (uint8_t i = 0; i < n; i++)
91 out[i] = pid_update(&p[i], setpoint[i], measurement[i], dt);
92}
93
94// --- dense-binary log packers (little-endian; see the PID_LOG_* format in control.h) ---
95
96static size_t put_u16le(uint8_t *p, uint16_t v)
97{
98 p[0] = (uint8_t)(v & 0xFF);
99 p[1] = (uint8_t)(v >> 8);
100 return 2;
101}
102
103static size_t put_u32le(uint8_t *p, uint32_t v)
104{
105 p[0] = (uint8_t)(v & 0xFF);
106 p[1] = (uint8_t)((v >> 8) & 0xFF);
107 p[2] = (uint8_t)((v >> 16) & 0xFF);
108 p[3] = (uint8_t)((v >> 24) & 0xFF);
109 return 4;
110}
111
112static size_t put_f32le(uint8_t *p, float v)
113{
114 uint32_t u;
115 memcpy(&u, &v, 4); // reinterpret the IEEE-754 bits, then emit little-endian
116 return put_u32le(p, u);
117}
118
119size_t pid_log_header(uint8_t *buf, size_t cap, const Pid *p, float dt)
120{
121 if (!buf || !p || cap < PID_LOG_HEADER_LEN)
122 return 0;
123 size_t o = 0;
124 memcpy(buf, PID_LOG_MAGIC, 4);
125 o += 4;
126 buf[o++] = PID_LOG_VERSION;
127 buf[o++] = 0; // flags (reserved)
128 o += put_u16le(buf + o, 0);
129 o += put_f32le(buf + o, dt);
130 o += put_f32le(buf + o, p->kp);
131 o += put_f32le(buf + o, p->ki);
132 o += put_f32le(buf + o, p->kd);
133 o += put_f32le(buf + o, p->kff);
134 o += put_f32le(buf + o, p->out_min);
135 o += put_f32le(buf + o, p->out_max);
136 return o; // == PID_LOG_HEADER_LEN
137}
138
139size_t pid_log_record(uint8_t *buf, size_t cap, float setpoint, float measurement, float output, bool saturated)
140{
141 if (!buf || cap < PID_LOG_RECORD_LEN)
142 return 0;
143 size_t o = 0;
144 o += put_f32le(buf + o, setpoint);
145 o += put_f32le(buf + o, measurement);
146 o += put_f32le(buf + o, output);
147 o += put_u32le(buf + o, saturated ? PID_LOG_STATUS_SATURATED : 0u);
148 return o; // == PID_LOG_RECORD_LEN
149}
150
151#endif // DETWS_ENABLE_CONTROL
Closed-loop control law (DETWS_ENABLE_CONTROL) - a zero-heap PID controller plus a handful of inline ...