Skip to content

Commit fca339c

Browse files
author
camilo
committed
initial changes on pid to handle 2nd rls estimation
1 parent 08b301d commit fca339c

File tree

6 files changed

+282
-124
lines changed

6 files changed

+282
-124
lines changed

doc/qpid.dox

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -268,40 +268,63 @@
268268
*
269269
* @section qpid_autotune Autotuning
270270
*
271-
* Autotuning can eliminate much of the trial and error of a manual tuning
272-
* approach, especially if you do not have a lot of loop tuning experience.
273-
* Performing the autotuning procedure will get the tuning parameters close to
274-
* their optimal values, but additional manual tuning may be required to get
275-
* the tuning parameters to their optimal values.
271+
* Autotuning eliminates much of the trial-and-error required by manual PID tuning,
272+
* especially when loop dynamics are complex or the user lacks tuning experience.
273+
* The procedure provides controller gains that are close to optimal, but a fine
274+
* adjustment step may still be necessary to achieve the best performance.
276275
*
277-
* The Autotune feature for the controller will only run for a limited amount of time
278-
* after it gets enabled. In other words, autotuning does not run continuously
279-
* during operation. Whenever there is a substantial change in the process
280-
* dynamics, the tuning process will need to be repeated in order to derive new
281-
* gains required for optimal control.
276+
* The Autotune feature is executed only for a limited time after activation;
277+
* it is not a continuous background process. If the plant dynamics change
278+
* significantly (e.g., due to load variation or environmental conditions),
279+
* the autotuning sequence must be repeated to update the control gains.
282280
*
283-
* Autotuning is performed by using the following recursive algorithm:
281+
* The underlying method is based on a Recursive Least Squares (RLS) identification
282+
* of the process, followed by an analytical mapping to PID parameters. The RLS
283+
* algorithm estimates a first-order process model, from which the controller
284+
* computes suitable gains. The recursive update makes the method efficient for
285+
* real-time embedded systems, as it avoids repeated excitation tests or large
286+
* datasets.
287+
*
288+
* @note
289+
* The estimation assumes a first-order stable process model. Therefore,
290+
* this autotuning method is only reliable when applied to inherently
291+
* stable processes. For unstable or integrating processes, the RLS
292+
* identification may diverge and the computed PID gains may not be valid.
293+
*
294+
* The algorithm proceeds as follows:
295+
*
296+
* Compute the correction gain \f$ L(t) \f$
284297
*
285-
* <center>
286298
* \f$ L(t) = \frac{ P(t-1)\phi }{ \lambda + \phi^{T}P(t-1)\phi } \f$
287299
*
300+
* Perform correction
301+
*
288302
* \f$\theta(t) = \theta(t-1) + L[ y(t) - \phi^{T}\theta(t) ] \f$
289303
*
290-
* \f$ P(t) = \lambda^{-1}[ I - L(t)+\phi^{T}]P(t-1) \f$
304+
* where,
291305
*
292306
* \f$ \theta(t) = \begin{bmatrix} \theta_{1}(t) & \theta_{2}(t) \end{bmatrix}^T \f$
293307
* and \f$ \phi(t) = \begin{bmatrix} -y(t-1) & u(t-1) \end{bmatrix}^T \f$
294308
*
309+
* Update covariance
310+
*
311+
* \f$ P(t) = \lambda^{-1}[ I - L(t)+\phi^{T}]P(t-1) \f$
312+
*
313+
* Compute the system gain \f$ g(t) \f$
314+
*
295315
* \f$ g(t) = \frac{ (1 - \mu)\theta_{2}(t) }{ 1 + \theta_{1} } + \mu g(t-1) \f$
296316
*
317+
* Compute the system time-constant \f$ \tau(t) \f$
318+
*
297319
* \f$ \tau(t) = \frac{ ( \mu - 1 )dt }{ ln( |\theta_{1}| ) } + \mu \tau(t-1) \f$
298320
*
321+
* Compute the PID gains
322+
*
299323
* \f$ K_{c}(t) = \alpha \frac{ 1.35 }{ g(t) } \left ( \frac{ \tau(t) }{ T_d } + 0.185 \right ) \f$
300324
*
301325
* \f$ K_{i}(t) = K_{c}(t) \frac {\tau(t) + 0.611 T_d}{ 2.5 T_d( \tau(t) + 0.185 T_d)} \f$
302326
*
303327
* \f$ K_{d}(t) = \frac{ 0.37 K_{c}(t) T_d \tau(t) }{ \tau(t) + 0.185 T_d} \f$
304-
* </center>
305328
*
306329
*
307330
* and the remaining parameters \f$\mu\f$, \f$\alpha\f$, \f$\lambda\f$ are internally

doc/qrms.dox

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
* specifications, either by configuring a timer interrupt or by delegating
1818
* responsibility to a real-time task.
1919
*
20-
* @section qrms_ex1 Example : By using a timer interrupt
20+
* @section qrms_ex1 Example: Using a Timer Interrupt
2121
*
22-
* rms_compute.c
22+
* The following example demonstrates how to configure a timer to periodically update the RMS
23+
* estimator at a 1 ms interval.
24+
*
25+
* @b File: @c rms_compute.c
2326
* @code{.c}
2427
* #include "bsp.h"
2528
* #include "hal_timer.h"
@@ -53,7 +56,8 @@
5356
*
5457
*
5558
* @endcode
56-
* rms_compute.h
59+
*
60+
* @b File: @c rms_compute.h
5761
* @code{.c}
5862
*
5963
* #ifndef RMS_COMPUTE_H

doc/qssmoother.dox

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,10 @@
4444
*
4545
* @section qssmoother_lpf2 Second Order Low Pass Filter
4646
*
47-
* \ref qlibs::smootherLPF2 is a filter with similar properties to the 1st order low pass filter. The main
48-
* difference is that the stop band roll-off will be twice the 1st order filters
49-
* at 40dB/decade (12dB/octave) as the operating frequency increases above the
50-
* cut-off frequency \f$w\f$.
47+
* \ref qlibs::smootherLPF2 extends the first-order filter with steeper attenuation.
48+
* Its stop-band roll-off is twice as sharp (≈40 dB/decade or 12 dB/octave).
5149
*
52-
* The difference-equation of the output of this filter is given by:
50+
* The difference equation is:
5351
*
5452
* <center> \f$y(t)=kx(t)+b_{1}x(t-1)+kx(t-2)-a_{1}y(t-1)-a_{2}y(t-2)\f$ </center>
5553
*

doc/qtdl.dox

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
11
/*! @page qtdl_desc Tapped Delay Line in O(1)
2-
* The class \ref qtdl is an implementation of the Tapped Delay Line (TDL) structure.
3-
* A TDL is a discrete element in digital filter theory, that allows a signal to
4-
* be delayed by a number of samples and provides an output signal for each delay.
5-
* Here, a TDL is implemented as a circular buffer. This means
6-
* that the complexity of the enqueue and dequeue operations is constant @c O(1), so
7-
* integer delays can be computed very efficiently.
8-
*
9-
* The delay by one sample is notated \f$z^{-1}\f$ and delays of \f$N\f$ samples
10-
* is notated as \f$z^{-N}\f$ motivated by the role the z-transform plays in
11-
* describing digital filter structures.
12-
*
13-
* To create a TDL, you just need to define an instance of type \ref qlibs::tdl and
14-
* then, configure it by using the constructor or \ref qlibs::tdl::setup(),
15-
* where you can define the number of lines to be delayed and the initial values
16-
* for all of them. Then, you can start operating over this structure by inserting
17-
* samples of the input signal by using \ref qlibs::tdl::insertSample().
18-
* You can also get any specific delay from it by using:
19-
*
20-
* - \ref qlibs::tdl::getOldest(), to get the oldest delay at tap \f$z^{-N}\f$
21-
* - \ref qlibs::tdl::getAtIndex(), to get the delay at tap \f$z^{-i}\f$.
22-
* You can also use the index using an unsigned value as follows: @c "delay[ i ]"
23-
* - Index operator
24-
*
25-
* Given the applications of this structure, the \ref qlibs::tdl class is used as the
26-
* base component of some aspects of \ref qlibs::smoother and \ref qlibs::ltisys.
27-
*
28-
* @section qtdl_ex1 Example : Code snippet to instantiate a TDL to hold up to 256 delays.
2+
* The class \ref qtdl implements a Tapped Delay Line (TDL), a fundamental structure
3+
* in digital filter theory. A TDL delays an input signal by a number of samples and
4+
* exposes each delayed version as an output
5+
*
6+
* In this implementation, the TDL is backed by a circular buffer. As a result,
7+
* both enqueue and dequeue operations run in constant time @c O(1), making integer
8+
* delays highly efficient.
9+
*
10+
* A delay of one sample is denoted \f$z^{-1}\f$ , while a delay of \f$N\f$ samples
11+
* is denoted \f$z^{-N}\f$ consistent with z-transform notation in digital signal
12+
* processing.
13+
*
14+
* To use a TDL, create an instance of \ref qlibs::tdl and configure it using either
15+
* the constructor or \ref qlibs::tdl::setup(). During setup you specify both the
16+
* number of delay taps and their initial values. Once configured, samples of the
17+
* input signal can be inserted using \ref qlibs::tdl::insertSample() (or the
18+
* function-call operator).
19+
*
20+
* You can access specific delayed samples via:
21+
* - \ref qlibs::tdl::getOldest() - retrieves the oldest sample (tap \f$z^{-N}\f$)
22+
* - \ref qlibs::tdl::getAtIndex() — retrieves the sample at tap \f$z^{-i}\f$
23+
* - The index operator @c delay[i] — equivalent to @c getAtIndex(i)
24+
*
25+
* Because of its versatility, the \ref qlibs::tdl class is a core component in
26+
* higher-level modules such as \ref qlibs::smoother and \ref qlibs::ltisys.
27+
*
28+
* @section qtdl_ex1 Example : Instantiating a TDL with 256 delay taps
2929
*
3030
* @code{.c}
3131
* real_t storage[ 256 ] = { 0.0f };
3232
* tdl delay( storage );
3333
* @endcode
3434
*
35+
* Insert new samples:
3536
* @code{.c}
3637
* delay.insertSample( 2.5 );
3738
* delay.insertSample( -2.3 );
3839
* delay( 4.8 ); // same as delay.insertSample( 4.8 )
3940
* @endcode
4041
*
42+
* Retrieve delayed samples:
4143
* @code{.c}
4244
* auto d3 = delay[ 3 ]; // get delay at t-3, same as delay.getAtIndex( 3 )
4345
* auto dOldest = delay.getOldest(); // get the oldest sample at t-N

src/include/pid.hpp

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,33 @@ namespace qlibs {
8787
}
8888
/*! @endcond */
8989

90+
9091
/**
9192
* @brief A PID Auto-tuning object
9293
* @details The instance should be bound to a configured PID controller by
9394
* using the pidController::bindAutoTuning() method
9495
*/
9596
class pidAutoTuning {
97+
/*! @cond */
98+
struct systemParams {
99+
real_t gain;
100+
real_t tau1;
101+
real_t tau2;
102+
bool stable;
103+
};
104+
105+
struct estimationParams {
106+
real_t theta[4]; /*parameter estimations*/
107+
real_t P[4][4]; /*covariance matrix*/
108+
real_t lambda; /*forgetting factor [ 0.9 < l < 1 ]*/
109+
real_t y1, y2; /*past values of y(t)*/
110+
real_t u1, u2; /*past values of u(t)*/
111+
};
112+
113+
using EstimationStepFn = void(*)(estimationParams&, real_t);
114+
using ComputeParamsFn = void(*)(systemParams&,const estimationParams&, const real_t);
115+
116+
/*! @endcond */
96117
friend class pidController;
97118
public:
98119
/**
@@ -101,20 +122,30 @@ namespace qlibs {
101122
static const uint32_t UNDEFINED;
102123
protected:
103124
/*! @cond */
104-
real_t p00{ 1.0_re }; /*covariance value*/
105-
real_t p01{ 0.0_re }; /*covariance value*/
106-
real_t p10{ 0.0_re }; /*covariance value*/
107-
real_t p11{ 1.0_re }; /*covariance value*/
108-
real_t b1{ 0.1_re }; /*estimation value*/
109-
real_t a1{ 0.9_re }; /*estimation value*/
110-
real_t uk{ 0.0_re }; /*process input*/
111-
real_t yk{ 0.0_re }; /*process output*/
112-
real_t l{ 0.9898_re }; /*memory factor [ 0.9 < l < 1 ]*/
113-
real_t k{ 1.0_re }; /*process static gain*/
114-
real_t tao{ 1.0_re }; /*process time constant*/
125+
EstimationStepFn estimationStep{ &pidAutoTuning::estimationStepN1 };
126+
ComputeParamsFn computeParameters{ &pidAutoTuning::computeParamsN1 };
127+
estimationParams estParams;
128+
systemParams sysParams;
129+
115130
real_t mu{ 0.95_re }; /*variation attenuation*/
116131
real_t speed{ 0.25_re }; /*final controller speed*/
117132
uint32_t it{ UNDEFINED }; /*enable time*/
133+
134+
static bool getTimeConstant( real_t& tau,
135+
const real_t abs_z,
136+
const real_t dt );
137+
static void computeParamsN1( systemParams& p,
138+
const estimationParams& e,
139+
const real_t dt );
140+
static void computeParamsN2( systemParams& p,
141+
const estimationParams& e,
142+
const real_t dt );
143+
144+
static void estimationStepN1( estimationParams &p,
145+
const real_t y );
146+
static void estimationStepN2( estimationParams &p,
147+
const real_t y );
148+
118149
static bool isValidValue( const real_t x ) noexcept;
119150
pidType type{ pidType::PID_TYPE_PI };
120151
void initialize( const pidGains current,
@@ -129,7 +160,7 @@ namespace qlibs {
129160
}
130161
inline void setMemoryFactor( const real_t lambda ) noexcept
131162
{
132-
l = lambda;
163+
estParams.lambda = lambda;
133164
}
134165
inline void setMomentum( const real_t Mu ) noexcept
135166
{

0 commit comments

Comments
 (0)