Subversion Repositories HomeAutomation

Rev

Details | Last modification | View Log | SVN | RSS feed

Rev Author Line No. Line
2274 linlun 1
#include "PID_AutoTune.h"
2
#include <avr/pgmspace.h>
3
#include <math.h>
4
#include <drivers/timer/timer.h>
5
#include <stdbool.h>
6
// source of Tyreus-Luyben and Ciancone-Marlin rules:
7
// "Autotuning of PID Controllers: A Relay Feedback Approach",
8
//  by Cheng-Ching Yu, 2nd Edition, p.18
9
// Tyreus-Luyben is more conservative than Ziegler-Nichols
10
// and is preferred for lag dominated processes
11
// Ciancone-Marlin is preferred for delay dominated processes
12
// Ziegler-Nichols is intended for best disturbance rejection
13
// can lack robustness especially for lag dominated processes
14
 
15
// source for Pessen Integral, Some Overshoot, and No Overshoot rules:
16
// "Rule-Based Autotuning Based on Frequency Domain Identification" 
17
// by Anthony S. McCormack and Keith R. Godfrey
18
// IEEE Transactions on Control Systems Technology, vol 6 no 1, January 1998.
19
// as reported on http://www.mstarlabs.com/control/znrule.html
20
 
21
// irrational constants
22
  //static const float CONST_PI          = 3.14159265358979323846;
23
  //static const float CONST_SQRT2_DIV_2 = 0.70710678118654752440;
24
  #define CONST_SQRT2_DIV_2     M_SQRT1_2
25
  #define CONST_PI              M_PI
26
 
27
 float processValueOffset(float,     // * returns an estimate of the process value offset
28
      float);                          //   as a proportion of the amplitude                                        
29
 
30
  float *input;
31
  float *output;
32
  float setpoint;
33
 
34
  float oStep;
35
  float noiseBand;
36
  uint8_t nLookBack;
37
  uint8_t controlType;                     // * selects autotune algorithm
38
 
39
  enum AutoTunerState state;            // * state of autotuner finite state machine
40
  unsigned long lastTime;
41
  unsigned long sampleTime;
42
  enum Peak peakType;
43
  unsigned long lastPeakTime[5];        // * peak time, most recent in array element 0
44
  float lastPeaks[5];                  // * peak value, most recent in array element 0
45
  uint8_t peakCount;
46
  float lastInputs[101];               // * process values, most recent in array element 0
47
  uint8_t inputCount;
48
  float outputStart;
49
  float workingNoiseBand;
50
  float workingOstep;
51
  float inducedAmplitude;
52
  float Kp, Ti, Td;
53
 
54
  // used by AMIGOf tuning rule
55
  float calculatePhaseLag(float);     // * calculate phase lag from noiseBand and inducedAmplitude
56
  float fastArcTan(float);
57
  float newWorkingNoiseBand;
58
  float K_process;
59
 
60
#if defined AUTOTUNE_RELAY_BIAS  
61
  float relayBias;
62
  unsigned long lastStepTime[5];        // * step time, most recent in array element 0
63
  float sumInputSinceLastStep[5];      // * integrated process values, most recent in array element 0
64
  uint8_t stepCount;
65
#endif  
66
 
67
 
68
// order must be match enumerated type for auto tune methods
69
struct Tuning tuningRule[NO_OVERSHOOT_PID + 1]  PROGMEM =
70
{  
71
  { {  44, 24,   0 } },  // ZIEGLER_NICHOLS_PI
72
  { {  34, 40, 160 } },  // ZIEGLER_NICHOLS_PID
73
  { {  64,  9,   0 } },  // TYREUS_LUYBEN_PI
74
  { {  44,  9, 126 } },  // TYREUS_LUYBEN_PID
75
  { {  66, 80,   0 } },  // CIANCONE_MARLIN_PI
76
  { {  66, 88, 162 } },  // CIANCONE_MARLIN_PID
77
  { {  28, 50, 133 } },  // PESSEN_INTEGRAL_PID
78
  { {  60, 40,  60 } },  // SOME_OVERSHOOT_PID
79
  { { 100, 40,  60 } }   // NO_OVERSHOOT_PID
80
};
81
 
82
static bool PID_ATune_PI_controller(void)
83
{
84
return pgm_read_byte_near(&tuningRule[2]) == 0;
85
}
86
 
87
static float PID_ATune_divisor( uint8_t index)  
88
{
89
return (float)pgm_read_byte_near(&tuningRule[index]) * 0.05;
90
}
91
 
92
void PID_ATune_Init(float* Input, float* Output)
93
{
94
  input = Input;
95
  output = Output;
96
 
97
  // constructor defaults
98
  controlType = ZIEGLER_NICHOLS_PI;
99
  noiseBand = 0.5;
100
  state = AUTOTUNER_OFF;
101
  oStep = 10.0;
102
  PID_ATune_SetLookbackSec(10);
103
}
104
 
105
void PID_ATune_Cancel(void)
106
{
107
  state = AUTOTUNER_OFF;
108
}
109
 
110
float inline PID_ATune_fastArcTan(float x)
111
{
112
  // source: “Efficient approximations for the arctangent function”, Rajan, S. Sichun Wang Inkol, R. Joyal, A., May 2006
113
  //return CONST_PI / 4.0 * x - x * (abs(x) - 1.0) * (0.2447 + 0.0663 * abs(x));
114
 
115
  // source: "Understanding Digital Signal Processing", 2nd Ed, Richard G. Lyons, eq. 13-107
116
  return x / (1.0 + 0.28125 * pow(x, 2));
117
}
118
 
119
float PID_ATune_calculatePhaseLag(float inducedAmplitude)
120
{
121
  // calculate phase lag
122
  // NB hysteresis = 2 * noiseBand;
123
  float ratio = 2.0 * workingNoiseBand / inducedAmplitude;
124
  if (ratio > 1.0)
125
  {
126
    return CONST_PI / 2.0;
127
  }
128
  else
129
  {
130
    //return CONST_PI - asin(ratio);
131
    return CONST_PI - PID_ATune_fastArcTan(ratio / sqrt( 1.0 - pow(ratio, 2)));
132
  }
133
}
134
 
135
bool PID_ATune_Runtime(void)
136
{
137
  // check ready for new input
138
  unsigned long now = Timer_GetTicks();
139
  //unsigned long now = millis();
140
 
141
  if (state == AUTOTUNER_OFF)
142
  {
143
    // initialize working variables the first time around
144
    peakType = NOT_A_PEAK;
145
    inputCount = 0;
146
    peakCount = 0;
147
    setpoint = *input;
148
    outputStart = *output;
149
    lastPeakTime[0] = now;
150
    workingNoiseBand = noiseBand;
151
    newWorkingNoiseBand = noiseBand;  
152
    workingOstep = oStep;
153
 
154
#if defined (AUTOTUNE_RELAY_BIAS) 
155
    relayBias = 0.0;
156
    stepCount = 0;  
157
    lastStepTime[0] = now;
158
    sumInputSinceLastStep[0] = 0.0;
159
#endif    
160
 
161
    // move to new state
162
    if (controlType == AMIGOF_PI)
163
    {
164
      state = STEADY_STATE_AT_BASELINE;
165
    }
166
    else
167
    {
168
      state = RELAY_STEP_UP;
169
    }
170
  }
171
 
172
  // otherwise check ready for new input
173
  else if ((now - lastTime) < sampleTime)
174
  {
175
    return false;
176
  }
177
 
178
  // get new input
179
  lastTime = now;
180
  float refVal = *input;
181
 
182
#if defined (AUTOTUNE_RELAY_BIAS) 
183
  // used to calculate relay bias
184
  sumInputSinceLastStep[0] += refVal;
185
#endif  
186
 
187
  // local flag variable
188
  bool justChanged = false;
189
 
190
  // check input and change relay state if necessary
191
  if ((state == RELAY_STEP_UP) && (refVal > setpoint + workingNoiseBand))
192
  {
193
    state = RELAY_STEP_DOWN;
194
    justChanged = true;
195
  }
196
  else if ((state == RELAY_STEP_DOWN) && (refVal < setpoint - workingNoiseBand))
197
  {
198
    state = RELAY_STEP_UP;
199
    justChanged = true;
200
  }
201
  if (justChanged)
202
  {
203
    workingNoiseBand = newWorkingNoiseBand;
204
 
205
#if defined (AUTOTUNE_RELAY_BIAS)
206
    // check symmetry of oscillation
207
    // and introduce relay bias if necessary
208
    if (stepCount > 4)
209
    {
210
      float avgStep1 = 0.5 * (float) ((lastStepTime[0] - lastStepTime[1]) + (lastStepTime[2] - lastStepTime[3]));
211
      float avgStep2 = 0.5 * (float) ((lastStepTime[1] - lastStepTime[2]) + (lastStepTime[3] - lastStepTime[4]));
212
      if ((avgStep1 > 1e-10) && (avgStep2 > 1e-10))
213
      {
214
        float asymmetry = (avgStep1 > avgStep2) ?
215
                           (avgStep1 - avgStep2) / avgStep1 : (avgStep2 - avgStep1) / avgStep2;
216
 
217
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
218
        Serial.print(F("asymmetry "));
219
        Serial.println(asymmetry);
220
#endif
221
 
222
        if (asymmetry > AUTOTUNE_STEP_ASYMMETRY_TOLERANCE)
223
        {
224
          // relay steps are asymmetric
225
          // calculate relay bias using
226
          // "Autotuning of PID Controllers: A Relay Feedback Approach",
227
          //  by Cheng-Ching Yu, 2nd Edition, equation 7.39, p. 148
228
 
229
          // calculate change in relay bias
230
          float deltaRelayBias = - PID_ATune_processValueOffset(avgStep1, avgStep2) * workingOstep;
231
          if (state == RELAY_STEP_DOWN)
232
          {
233
            deltaRelayBias = -deltaRelayBias;
234
          }
235
 
236
          if (abs(deltaRelayBias) > workingOstep * AUTOTUNE_STEP_ASYMMETRY_TOLERANCE)
237
          {
238
            // change is large enough to bother with
239
            relayBias += deltaRelayBias;
240
 
241
            /*
242
            // adjust step height with respect to output limits
243
            // commented out because the auto tuner does not
244
            // necessarily know what the output limits are
245
            float relayHigh = outputStart + workingOstep + relayBias;
246
            float relayLow  = outputStart - workingOstep + relayBias;
247
            if (relayHigh > outMax)
248
            {
249
              relayHigh = outMax;
250
            }
251
            if (relayLow  < outMin)
252
            {
253
              relayHigh = outMin;
254
            }
255
            workingOstep = 0.5 * (relayHigh - relayLow);
256
            relayBias = relayHigh - outputStart - workingOstep;
257
            */
258
 
259
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
260
            Serial.print(F("deltaRelayBias "));
261
            Serial.println(deltaRelayBias);
262
            Serial.print(F("relayBias "));
263
            Serial.println(relayBias);
264
#endif
265
 
266
            // reset relay step counter
267
            // to give the process value oscillation
268
            // time to settle with the new relay bias value
269
            stepCount = 0;
270
          }
271
        }
272
      }
273
    }
274
 
275
    // shift step time and integrated process value arrays
276
    for (uint8_t i = (stepCount > 4 ? 4 : stepCount); i > 0; i--)
277
    {
278
      lastStepTime[i] = lastStepTime[i - 1];
279
      sumInputSinceLastStep[i] = sumInputSinceLastStep[i - 1];
280
    }
281
    stepCount++;
282
    lastStepTime[0] = now;
283
    sumInputSinceLastStep[0] = 0.0;
284
 
285
#if defined (AUTOTUNE_DEBUG
286
    for (uint8_t i = 1; i < (stepCount > 4 ? 5 : stepCount); i++)
287
    {
288
      Serial.print(F("step time "));
289
      Serial.println(lastStepTime[i]);
290
      Serial.print(F("step sum "));
291
      Serial.println(sumInputSinceLastStep[i]);
292
    }
293
#endif
294
 
295
#endif // if defined AUTOTUNE_RELAY_BIAS
296
 
297
  } // if justChanged
298
 
299
  // set output
300
  // FIXME need to respect output limits
301
  // not knowing output limits is one reason 
302
  // to pass entire PID object to autotune method(s)
303
  if (((uint8_t) state & (STEADY_STATE_AFTER_STEP_UP | RELAY_STEP_UP)) > 0)
304
  {
305
 
306
#if defined (AUTOTUNE_RELAY_BIAS)    
307
    *output = outputStart + workingOstep + relayBias;
308
#else    
309
    *output = outputStart + workingOstep;
310
#endif    
311
 
312
  }
313
  else if (state == RELAY_STEP_DOWN)
314
  {
315
 
316
#if defined (AUTOTUNE_RELAY_BIAS)    
317
    *output = outputStart - workingOstep + relayBias;
318
#else
319
    *output = outputStart - workingOstep;
320
#endif
321
 
322
  }
323
 
324
#if defined (AUTOTUNE_DEBUG)
325
  Serial.print(F("refVal "));
326
  Serial.println(refVal);
327
  Serial.print(F("setpoint "));
328
  Serial.println(setpoint);
329
  Serial.print(F("output "));
330
  Serial.println(*output);
331
  Serial.print(F("state "));
332
  Serial.println(state);
333
#endif
334
 
335
  // store initial inputs
336
  // we don't want to trust the maxes or mins
337
  // until the input array is full
338
  inputCount++;
339
  if (inputCount <= nLookBack)
340
  {
341
    lastInputs[nLookBack - inputCount] = refVal;
342
    return false;
343
  }
344
 
345
  // shift array of process values and identify peaks
346
  inputCount = nLookBack;
347
  bool isMax = true;
348
  bool isMin = true;
349
  for (int i = inputCount - 1; i >= 0; i--)
350
  {
351
    float val = lastInputs[i];
352
    if (isMax)
353
    {
354
      isMax = (refVal >= val);
355
    }
356
    if (isMin)
357
    {
358
      isMin = (refVal <= val);
359
    }
360
    lastInputs[i + 1] = val;
361
  }
362
  lastInputs[0] = refVal;
363
 
364
  // for AMIGOf tuning rule, perform an initial
365
  // step change to calculate process gain K_process
366
  // this may be very slow for lag-dominated processes
367
  // and may never terminate for integrating processes 
368
  if (((uint8_t) state & (STEADY_STATE_AT_BASELINE | STEADY_STATE_AFTER_STEP_UP)) > 0)
369
  {
370
    // check that all the recent inputs are 
371
    // equal give or take expected noise
372
    float iMax = lastInputs[0];
373
    float iMin = lastInputs[0];
374
    float avgInput = 0.0;
375
    for (uint8_t i = 0; i <= inputCount; i++)
376
    {
377
      float val = lastInputs[i];
378
      if (iMax < val)
379
      {
380
        iMax = val;
381
      }
382
        if (iMin > val)
383
      {
384
        iMin = val;
385
      }
386
      avgInput += val;
387
    }
388
    avgInput /= (float)(inputCount + 1);
389
 
390
#if defined (AUTOTUNE_DEBUG)
391
  Serial.print(F("iMax "));
392
  Serial.println(iMax);
393
  Serial.print(F("iMin "));
394
  Serial.println(iMin);
395
  Serial.print(F("avgInput "));
396
  Serial.println(avgInput);
397
  Serial.print(F("stable "));
398
  Serial.println((iMax - iMin) <= 2.0 * workingNoiseBand);
399
#endif 
400
 
401
    // if recent inputs are stable
402
    if ((iMax - iMin) <= 2.0 * workingNoiseBand)
403
    {
404
 
405
#if defined (AUTOTUNE_RELAY_BIAS)      
406
      lastStepTime[0] = now;
407
#endif
408
 
409
      if (state == STEADY_STATE_AT_BASELINE)
410
      {
411
        state = STEADY_STATE_AFTER_STEP_UP;
412
        lastPeaks[0] = avgInput;  
413
        inputCount = 0;
414
        return false;
415
      }
416
      // else state == STEADY_STATE_AFTER_STEP_UP
417
      // calculate process gain
418
      K_process = (avgInput - lastPeaks[0]) / workingOstep;
419
 
420
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
421
      Serial.print(F("Process gain "));
422
      Serial.println(K_process);
423
#endif
424
 
425
      // bad estimate of process gain
426
      if (K_process < 1e-10) // zero
427
      {
428
        state = AUTOTUNER_OFF;
429
        return false;
430
      }
431
      state = RELAY_STEP_DOWN;
432
 
433
#if defined (AUTOTUNE_RELAY_BIAS)      
434
      sumInputSinceLastStep[0] = 0.0;
435
#endif
436
 
437
      return false;
438
    }
439
    else
440
    {
441
      return false;
442
    }
443
  }
444
 
445
  // increment peak count 
446
  // and record peak time 
447
  // for both maxima and minima 
448
  justChanged = false;
449
  if (isMax)
450
  {
451
    if (peakType == MINIMUM)
452
    {
453
      justChanged = true;
454
    }
455
    peakType = MAXIMUM;
456
  }
457
  else if (isMin)
458
  {
459
    if (peakType == MAXIMUM)
460
    {
461
      justChanged = true;
462
    }
463
    peakType = MINIMUM;
464
  }
465
 
466
  // update peak times and values
467
  if (justChanged)
468
  {
469
    peakCount++;
470
 
471
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
472
    Serial.println(F("peakCount "));
473
    Serial.println(peakCount);
474
    Serial.println(F("peaks"));
475
    for (uint8_t i = 0; i < (peakCount > 4 ? 5 : peakCount); i++)
476
    {
477
      Serial.println(lastPeaks[i]);
478
    }
479
#endif
480
 
481
    // shift peak time and peak value arrays
482
    for (uint8_t i = (peakCount > 4 ? 4 : peakCount); i > 0; i--)
483
    {
484
      lastPeakTime[i] = lastPeakTime[i - 1];
485
      lastPeaks[i] = lastPeaks[i - 1];
486
    }
487
  }
488
  if (isMax || isMin)
489
  {
490
    lastPeakTime[0] = now;
491
    lastPeaks[0] = refVal;
492
 
493
#if defined (AUTOTUNE_DEBUG)
494
    Serial.println();
495
    Serial.println(F("peakCount "));
496
    Serial.println(peakCount);
497
    Serial.println(F("refVal "));
498
    Serial.println(refVal);
499
    Serial.print(F("peak type "));
500
    Serial.println(peakType);
501
    Serial.print(F("isMin "));
502
    Serial.println(isMin);
503
    Serial.print(F("isMax "));
504
    Serial.println(isMax);
505
    Serial.println();
506
    Serial.println(F("lastInputs:"));
507
    for (uint8_t i = 0; i <= inputCount; i++)
508
    {
509
      Serial.println(lastInputs[i]);
510
    }
511
    Serial.println();
512
#endif
513
 
514
  }
515
 
516
  // check for convergence of induced oscillation
517
  // convergence of amplitude assessed on last 4 peaks (1.5 cycles)
518
  float inducedAmplitude = 0.0;
519
  float phaseLag;
520
  if (
521
 
522
#if defined (AUTOTUNE_RELAY_BIAS)  
523
    (stepCount > 4) &&
524
#endif
525
 
526
    justChanged &&
527
    (peakCount > 4)
528
  )
529
  {
530
    float absMax = lastPeaks[1];
531
    float absMin = lastPeaks[1];
532
    for (uint8_t i = 2; i <= 4; i++)
533
    {
534
      float val = lastPeaks[i];
535
      inducedAmplitude += fabs( val - lastPeaks[i - 1]);
536
      if (absMax < val)
537
      {
538
         absMax = val;
539
      }
540
      if (absMin > val)
541
      {
542
         absMin = val;
543
      }
544
    }
545
    inducedAmplitude /= 6.0;
546
 
547
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
548
    Serial.print(F("amplitude "));
549
    Serial.println(inducedAmplitude);
550
    Serial.print(F("absMin "));
551
    Serial.println(absMin);
552
    Serial.print(F("absMax "));
553
    Serial.println(absMax);
554
    Serial.print(F("convergence criterion "));
555
    Serial.println((0.5 * (absMax - absMin) - inducedAmplitude) / inducedAmplitude);
556
#endif
557
 
558
    // source for AMIGOf PI auto tuning method:
559
    // "Revisiting the Ziegler-Nichols tuning rules for PI control — 
560
    //  Part II. The frequency response method."
561
    // T. Hägglund and K. J. Åström
562
    // Asian Journal of Control, Vol. 6, No. 4, pp. 469-482, December 2004
563
    // http://www.ajc.org.tw/pages/paper/6.4PD/AC0604-P469-FR0371.pdf
564
    if (controlType == AMIGOF_PI)
565
    {
566
      phaseLag = PID_ATune_calculatePhaseLag(inducedAmplitude);
567
 
568
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
569
      Serial.print(F("phase lag "));
570
      Serial.println(phaseLag / CONST_PI * 180.0);
571
#endif
572
 
573
      // check that phase lag is within acceptable bounds, ideally between 120° and 140°
574
      // but 115° to 145° will just about do, and might converge quicker
575
      if (fabs(phaseLag - CONST_PI * 130.0 / 180.0) > (CONST_PI * 15.0 / 180.0))
576
      {
577
        // phase lag outside the desired range
578
        // set noiseBand to new estimate
579
        // aiming for 135° = 0.75 * pi (radians)
580
        // sin(135°) = sqrt(2)/2
581
        // NB noiseBand = 0.5 * hysteresis
582
        newWorkingNoiseBand = 0.5 * inducedAmplitude * CONST_SQRT2_DIV_2;
583
 
584
#if defined (AUTOTUNE_RELAY_BIAS)
585
        // we could reset relay step counter because we can't rely
586
        // on constant phase lag for calculating
587
        // relay bias having changed noiseBand
588
        // but this would essentially preclude using relay bias
589
        // with AMIGOf tuning, which is already a compile option
590
        /*
591
        stepCount = 0;
592
        */
593
#endif        
594
 
595
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
596
        Serial.print(F("newWorkingNoiseBand "));
597
        Serial.println(newWorkingNoiseBand);  
598
#endif
599
 
600
        return false;
601
      }
602
    }
603
 
604
    // check convergence criterion for amplitude of induced oscillation
605
    if (((0.5 * (absMax - absMin) - inducedAmplitude) / inducedAmplitude) < AUTOTUNE_PEAK_AMPLITUDE_TOLERANCE)
606
    {
607
      state = CONVERGED;
608
    }
609
  }
610
 
611
  // if the autotune has not already converged
612
  // terminate after 10 cycles 
613
  // or if too long between peaks
614
  // or if too long between relay steps
615
  if (
616
 
617
#if defined (AUTOTUNE_RELAY_BIAS)  
618
    ((now - lastStepTime[0]) > (unsigned long) (AUTOTUNE_MAX_WAIT_MINUTES * 60000)) ||
619
#endif
620
 
621
    ((now - lastPeakTime[0]) > (unsigned long) (AUTOTUNE_MAX_WAIT_MINUTES * 60000)) ||
622
    (peakCount >= 20)
623
  )
624
  {
625
    state = FAILED;
626
  }
627
 
628
  if (((uint8_t) state & (CONVERGED | FAILED)) == 0)
629
  {
630
    return false;
631
  }
632
 
633
  // autotune algorithm has terminated 
634
  // reset autotuner variables
635
  *output = outputStart;
636
 
637
  if (state == FAILED)
638
  {
639
    // do not calculate gain parameters
640
 
641
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
642
    Serial.println("failed");
643
#endif
644
 
645
    return true;
646
  }
647
 
648
  // finish up by calculating tuning parameters
649
 
650
  // calculate ultimate gain
651
  float Ku = 4.0 * workingOstep / (inducedAmplitude * CONST_PI);
652
 
653
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
654
  Serial.print(F("ultimate gain "));
655
  Serial.println(1.0 / Ku);
656
  Serial.println(Ku);
657
#endif
658
 
659
  // calculate ultimate period in seconds
660
  float Pu = (float) 0.5 * ((lastPeakTime[1] - lastPeakTime[3]) + (lastPeakTime[2] - lastPeakTime[4])) / 1000.0;  
661
 
662
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
663
  Serial.print(F("ultimate period "));
664
  Serial.println(Pu);
665
#endif 
666
 
667
  // calculate gain parameters using tuning rules
668
  // NB PID generally outperforms PI for lag-dominated processes
669
 
670
  // AMIGOf is slow to tune, especially for lag-dominated processes, because it
671
  // requires an estimate of the process gain which is implemented in this
672
  // routine by steady state change in process variable after step change in set point
673
  // It is intended to give robust tunings for both lag- and delay- dominated processes
674
  if (controlType == AMIGOF_PI)
675
  {
676
    // calculate gain ratio
677
    float kappa_phi = (1.0 / Ku) / K_process;
678
 
679
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
680
  Serial.print(F("gain ratio kappa "));
681
  Serial.println(kappa_phi);
682
#endif
683
 
684
    // calculate phase lag
685
    phaseLag = PID_ATune_calculatePhaseLag(inducedAmplitude);
686
 
687
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
688
  Serial.print(F("phase lag "));
689
  Serial.println(phaseLag / CONST_PI * 180.0);
690
#endif
691
 
692
    // calculate tunings
693
    Kp = (( 2.50 - 0.92 * phaseLag) / (1.0 + (10.75 - 4.01 * phaseLag) * kappa_phi)) * Ku;
694
    Ti = ((-3.05 + 1.72 * phaseLag) / pow(1.0 + (-6.10 + 3.44 * phaseLag) * kappa_phi, 2)) * Pu;
695
    Td = 0.0;
696
 
697
    // converged
698
    return true;
699
  }
700
 
701
  Kp = Ku / (float) PID_ATune_divisor(KP_DIVISOR);
702
  Ti = Pu / (float) PID_ATune_divisor(TI_DIVISOR);
703
 
704
  Td = PID_ATune_PI_controller() ?
705
       0.0 : Pu / (float) PID_ATune_divisor(TD_DIVISOR);;
706
/*
707
  Kp = Ku / (float) tuningRule[controlType].divisor(KP_DIVISOR);
708
  Ti = Pu / (float) tuningRule[controlType].divisor(TI_DIVISOR);
709
  Td = tuningRule[controlType].PI_controller() ?
710
       0.0 : Pu / (float) tuningRule[controlType].divisor(TD_DIVISOR);
711
*/  
712
  // converged
713
  return true;
714
}
715
 
716
#if defined (AUTOTUNE_RELAY_BIAS)
717
float PID_ATune_processValueOffset(float avgStep1, float avgStep2)
718
{
719
  // calculate offset of oscillation in process value
720
  // as a proportion of the amplitude
721
  // approximation assumes a trapezoidal oscillation 
722
  // that is stationary over the last 2 relay cycles
723
  // needs constant phase lag, so recent changes to noiseBand are bad 
724
 
725
  if (avgStep1 < 1e-10)
726
  {
727
    return 1.0;
728
  }
729
  if (avgStep2 < 1e-10)
730
  {
731
    return -1.0;
732
  }
733
  // ratio of step durations
734
  float r1 = avgStep1 / avgStep2;
735
 
736
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
737
  Serial.print(F("r1 "));
738
  Serial.println(r1);
739
#endif
740
 
741
  float s1 = (sumInputSinceLastStep[1] + sumInputSinceLastStep[3]);
742
  float s2 = (sumInputSinceLastStep[2] + sumInputSinceLastStep[4]);
743
  if (s1 < 1e-10)
744
  {
745
    return 1.0;
746
  }
747
  if (s2 < 1e-10)
748
  {
749
    return -1.0;
750
  }
751
  // ratio of integrated process values
752
  float r2 = s1 / s2;
753
 
754
#if defined (AUTOTUNE_DEBUG) || defined (USE_SIMULATION)
755
  Serial.print(F("r2 "));
756
  Serial.println(r2);
757
#endif
758
 
759
  // estimate process value offset assuming a trapezoidal response curve
760
  //
761
  // assume trapezoidal wave with amplitude a, cycle period t, time at minimum/maximum m * t (0 <= m <= 1)
762
  // 
763
  // with no offset:
764
  // area under half wave of process value given by
765
  //   a * m * t/2 + a/2 * (1 - m) * t/2 = a * (1 + m) * t / 4
766
  //
767
  // now with offset d * a (-1 <= d <= 1): 
768
  // step time of relay half-cycle given by
769
  //   m * t/2 + (1 - d) * (1 - m) * t/2 = (1 - d + d * m) * t/2
770
  //
771
  // => ratio of step times in cycle given by:
772
  // (1) r1 = (1 - d + d * m) / (1 + d - d * m)
773
  //
774
  // area under offset half wave = a * (1 - d) * m * t/2 + a/2 * (1 - d) * (1 - d) * (1 - m) * t/2
775
  //                             = a * (1 - d) * (1 - d + m * (1 + d)) * t/4 
776
  //
777
  // => ratio of area under offset half waves given by:
778
  // (2) r2 = (1 - d) * (1 - d + m * (1 + d)) / ((1 + d) * (1 + d + m * (1 - d)))
779
  //
780
  // want to calculate d as a function of r1, r2; not interested in m
781
  //
782
  // rearranging (1) gives:
783
  // (3) m = 1 - (1 / d) * (1 - r1) / (1 + r1)
784
  //
785
  // substitute (3) into (2):
786
  // r2 = ((1 - d) * (1 - d + 1 + d - (1 + d) / d * (1 - r1) / (1 + r1)) / ((1 + d) * (1 + d + 1 - d - (1 - d) / d * (1 - r1) / (1 + r1)))   
787
  //
788
  // after much algebra, we arrive at: 
789
  // (4) (r1 * r2 + 3 * r1 + 3 * r2 + 1) * d^2 - 2 * (1 + r1)(1 - r2) * d + (1 - r1) * (1 - r2) = 0
790
  //
791
  // quadratic solution to (4):
792
  // (5) d = ((1 + r1) * (1 - r2) +/- 2 * sqrt((1 - r2) * (r1^2 - r2))) / (r1 * r2 + 3 * r1 + 3 * r2 + 1)
793
 
794
  // estimate offset as proportion of amplitude
795
  float discriminant = (1.0 - r2) * (pow(r1, 2) - r2);
796
  if (discriminant < 1e-10)
797
  {
798
    // catch negative values
799
    discriminant = 0.0;
800
  }
801
 
802
  // return estimated process value offset
803
  return ((1.0 + r1) * (1.0 - r2) + ((r1 > 1.0) ? 1.0 : -1.0) * sqrt(discriminant)) /
804
         (r1 * r2 + 3.0 * r1 + 3.0 * r2 + 1.0);
805
}
806
#endif // if defined AUTOTUNE_RELAY_BIAS
807
 
808
float PID_ATune_GetKp(void)
809
{
810
  return Kp;
811
}
812
 
813
float PID_ATune_GetKi(void)
814
{
815
  return Kp / Ti;
816
}
817
 
818
float PID_ATune_GetKd(void)
819
{
820
  return Kp * Td;
821
}
822
 
823
void PID_ATune_SetOutputStep(float Step)
824
{
825
  oStep = Step;
826
}
827
 
828
float PID_ATune_GetOutputStep(void)
829
{
830
  return oStep;
831
}
832
 
833
void PID_ATune_SetControlType(uint8_t type)
834
{
835
  controlType = type;
836
}
837
 
838
uint8_t PID_ATune_GetControlType(void)
839
{
840
  return controlType;
841
}
842
 
843
void PID_ATune_SetNoiseBand(float band)
844
{
845
  noiseBand = band;
846
}
847
 
848
float PID_ATune_GetNoiseBand(void)
849
{
850
  return noiseBand;
851
}
852
 
853
void PID_ATune_SetLookbackSec(uint16_t value)
854
{
855
  if (value < 1)
856
  {
857
    value = 1;
858
  }
859
  if (value < 25)
860
  {
861
    nLookBack = value * 4;
862
    sampleTime = 250;
863
  }
864
  else
865
  {
866
    nLookBack = 100;
867
    sampleTime = value * 10;
868
  }
869
}
870
 
871
int PID_ATune_GetLookbackSec(void)
872
{
873
  return nLookBack * sampleTime / 1000.0;
874
}