163 lines
7.4 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
GyverPID - библиотека PID регулятора для Arduino
Документация: https://alexgyver.ru/gyverpid/
GitHub: https://github.com/GyverLibs/GyverPID
Возможности:
- Время одного расчёта около 70 мкс
- Режим работы по величине или по её изменению (для интегрирующих процессов)
- Возвращает результат по встроенному таймеру или в ручном режиме
- Встроенные калибровщики коэффициентов
- Режим работы по ошибке и по ошибке измерения
- Встроенные оптимизаторы интегральной суммы
AlexGyver, alex@alexgyver.ru
https://alexgyver.ru/
MIT License
Версии:
v1.1 - убраны дефайны
v1.2 - возвращены дефайны
v1.3 - вычисления ускорены, библиотека облегчена
v2.0 - логика работы чуть переосмыслена, код улучшен, упрощён и облегчён
v2.1 - integral вынесен в public
v2.2 - оптимизация вычислений
v2.3 - добавлен режим PID_INTEGRAL_WINDOW
v2.4 - реализация внесена в класс
v3.0
- Добавлен режим оптимизации интегральной составляющей (см. доку)
- Добавлены автоматические калибровщики коэффициентов (см. примеры и доку)
v3.1 - исправлен режиме ON_RATE, добавлено автоограничение инт. суммы
v3.2 - чуть оптимизации, добавлена getResultNow
v3.3 - в тюнерах можно передать другой обработчик класса Stream для отладки
*/
#ifndef _GyverPID_h
#define _GyverPID_h
#include <Arduino.h>
#if defined(PID_INTEGER) // расчёты с целыми числами
typedef int16_t pidtype;
#else // расчёты с float числами
typedef float pidtype;
#endif
#define NORMAL 0
#define REVERSE 1
#define ON_ERROR 0
#define ON_RATE 1
class GyverPID {
public:
float lastP = 0;
float lastI = 0;
float lastD = 0;
// ==== pidtype это float или int, в зависимости от выбранного (см. пример integer_calc) ====
GyverPID() {}
// kp, ki, kd, dt
GyverPID(float new_kp, float new_ki, float new_kd, uint32_t new_dt = 100) {
setDt(new_dt);
Kp = new_kp;
Ki = new_ki;
Kd = new_kd;
}
// направление регулирования: NORMAL (0) или REVERSE (1)
void setDirection(bool direction) {
_direction = direction;
}
// режим: работа по входной ошибке ON_ERROR (0) или по изменению ON_RATE (1)
void setMode(bool mode) {
_mode = mode;
}
// лимит выходной величины (например для ШИМ ставим 0-255)
void setLimits(int16_t min_output, int16_t max_output) {
_minOut = min_output;
_maxOut = max_output;
}
// установка времени дискретизации (для getResultTimer)
void setDt(uint32_t new_dt) {
_dt_s = new_dt / 1000.0f;
_dt = new_dt;
}
pidtype setpoint = 0; // заданная величина, которую должен поддерживать регулятор
pidtype input = 0; // сигнал с датчика (например температура, которую мы регулируем)
pidtype output = 0; // выход с регулятора на управляющее устройство (например величина ШИМ или угол поворота серво)
float Kp = 0.0; // коэффициент P
float Ki = 0.0; // коэффициент I
float Kd = 0.0; // коэффициент D
float integral = 0.0; // интегральная сумма
// возвращает новое значение при вызове (если используем свой таймер с периодом dt!)
pidtype getResult() {
pidtype error = setpoint - input; // ошибка регулирования
pidtype delta_input = prevInput - input; // изменение входного сигнала за dt
prevInput = input; // запомнили предыдущее
if (_direction) { // смена направления
error = -error;
delta_input = -delta_input;
}
lastP = _mode ? 0 : (error * Kp); // пропорциональая составляющая
output = lastP;
lastD = delta_input * Kd / _dt_s; // дифференциальная составляющая
output += lastD;
#if (PID_INTEGRAL_WINDOW > 0)
// ЭКСПЕРИМЕНТАЛЬНЫЙ РЕЖИМ ИНТЕГРАЛЬНОГО ОКНА
if (++t >= PID_INTEGRAL_WINDOW) t = 0; // перемотка t
integral -= errors[t]; // вычитаем старое
errors[t] = error * Ki * _dt_s; // запоминаем в массив
integral += errors[t]; // прибавляем новое
#else
integral += error * Ki * _dt_s; // обычное суммирование инт. суммы
#endif
#ifdef PID_OPTIMIZED_I
// ЭКСПЕРИМЕНТАЛЬНЫЙ РЕЖИМ ОГРАНИЧЕНИЯ ИНТЕГРАЛЬНОЙ СУММЫ
output = constrain(output, _minOut, _maxOut);
if (Ki != 0) integral = constrain(integral, (_minOut - output) / (Ki * _dt_s), (_maxOut - output) / (Ki * _dt_s));
#endif
if (_mode) integral += delta_input * Kp; // режим пропорционально скорости
integral = constrain(integral, _minOut, _maxOut); // ограничиваем инт. сумму
lastI = integral;
output += integral; // интегральная составляющая
output = constrain(output, _minOut, _maxOut); // ограничиваем выход
return output;
}
// возвращает новое значение не ранее, чем через dt миллисекунд (встроенный таймер с периодом dt)
pidtype getResultTimer() {
if (millis() - pidTimer >= _dt) {
pidTimer = millis();
getResult();
}
return output;
}
// посчитает выход по реальному прошедшему времени между вызовами функции
pidtype getResultNow() {
setDt(millis() - pidTimer);
pidTimer = millis();
return getResult();
}
private:
uint32_t _dt = 100; // время итерации в мс
float _dt_s = 0.1; // время итерации в с
bool _mode = 0, _direction = 0;
int16_t _minOut = 0, _maxOut = 255;
pidtype prevInput = 0;
uint32_t pidTimer = 0;
#if (PID_INTEGRAL_WINDOW > 0)
pidtype errors[PID_INTEGRAL_WINDOW];
uint16_t t = 0;
#endif
};
#endif