ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_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 {
21 return;
22 }
23 p->kp = kp;
24 p->ki = ki;
25 p->kd = kd;
26 p->kff = 0.0f;
27 p->out_min = -CONTROL_UNBOUNDED;
28 p->out_max = CONTROL_UNBOUNDED;
29 p->integ_min = -CONTROL_UNBOUNDED;
30 p->integ_max = CONTROL_UNBOUNDED;
31 p->d_alpha = 0.0f;
32 p->dt = 0.0f;
33 p->inv_dt = 0.0f;
34 pid_reset(p);
35}
36
37void pid_set_rate(Pid *p, float dt)
38{
39 if (p && dt > 0.0f)
40 {
41 p->dt = dt;
42 p->inv_dt = 1.0f / dt;
43 }
44}
45
46void pid_set_output_limits(Pid *p, float lo, float hi)
47{
48 if (p)
49 {
50 p->out_min = lo;
51 p->out_max = hi;
52 }
53}
54
55void pid_set_integral_limits(Pid *p, float lo, float hi)
56{
57 if (p)
58 {
59 p->integ_min = lo;
60 p->integ_max = hi;
61 }
62}
63
64void pid_set_derivative_filter(Pid *p, float alpha)
65{
66 if (p)
67 {
68 p->d_alpha = alpha;
69 }
70}
71
72void pid_set_feedforward(Pid *p, float kff)
73{
74 if (p)
75 {
76 p->kff = kff;
77 }
78}
79
80void pid_reset(Pid *p)
81{
82 if (!p)
83 {
84 return;
85 }
86 p->integ = 0.0f;
87 p->prev_meas = 0.0f;
88 p->d_filt = 0.0f;
89 p->primed = false;
90}
91
92// pid_update() is defined inline in control.h (zero call overhead); this TU just uses it below.
93
94void pid_update_n(Pid *p, const float *setpoint, const float *measurement, float dt, float *out, uint8_t n)
95{
96 if (!p || !setpoint || !measurement || !out)
97 {
98 return;
99 }
100 for (uint8_t i = 0; i < n; i++)
101 {
102 out[i] = pid_update(&p[i], setpoint[i], measurement[i], dt);
103 }
104}
105
106// --- dense-binary log packers (little-endian; see the PID_LOG_* format in control.h) ---
107
108static size_t put_u16le(uint8_t *p, uint16_t v)
109{
110 p[0] = (uint8_t)(v & 0xFF);
111 p[1] = (uint8_t)(v >> 8);
112 return 2;
113}
114
115static size_t put_u32le(uint8_t *p, uint32_t v)
116{
117 p[0] = (uint8_t)(v & 0xFF);
118 p[1] = (uint8_t)((v >> 8) & 0xFF);
119 p[2] = (uint8_t)((v >> 16) & 0xFF);
120 p[3] = (uint8_t)((v >> 24) & 0xFF);
121 return 4;
122}
123
124static size_t put_f32le(uint8_t *p, float v)
125{
126 uint32_t u;
127 memcpy(&u, &v, 4); // reinterpret the IEEE-754 bits, then emit little-endian
128 return put_u32le(p, u);
129}
130
131size_t pid_log_header(uint8_t *buf, size_t cap, const Pid *p, float dt)
132{
133 if (!buf || !p || cap < PID_LOG_HEADER_LEN)
134 {
135 return 0;
136 }
137 size_t o = 0;
138 memcpy(buf, PID_LOG_MAGIC, 4);
139 o += 4;
140 buf[o++] = PID_LOG_VERSION;
141 buf[o++] = 0; // flags (reserved)
142 o += put_u16le(buf + o, 0);
143 o += put_f32le(buf + o, dt);
144 o += put_f32le(buf + o, p->kp);
145 o += put_f32le(buf + o, p->ki);
146 o += put_f32le(buf + o, p->kd);
147 o += put_f32le(buf + o, p->kff);
148 o += put_f32le(buf + o, p->out_min);
149 o += put_f32le(buf + o, p->out_max);
150 return o; // == PID_LOG_HEADER_LEN
151}
152
153size_t pid_log_record(uint8_t *buf, size_t cap, float setpoint, float measurement, float output, bool saturated)
154{
155 if (!buf || cap < PID_LOG_RECORD_LEN)
156 {
157 return 0;
158 }
159 size_t o = 0;
160 o += put_f32le(buf + o, setpoint);
161 o += put_f32le(buf + o, measurement);
162 o += put_f32le(buf + o, output);
163 o += put_u32le(buf + o, saturated ? PID_LOG_STATUS_SATURATED : 0u);
164 return o; // == PID_LOG_RECORD_LEN
165}
166
167#endif // PC_ENABLE_CONTROL
Closed-loop control law (PC_ENABLE_CONTROL) - a zero-heap PID controller plus a handful of inline con...