diff --git a/hot_fermentation.ino b/hot_fermentation.ino index a54ddd8..ef70a8a 100644 --- a/hot_fermentation.ino +++ b/hot_fermentation.ino @@ -68,8 +68,9 @@ Profile profiles[] = { {"Gradual", 10, {{46, 120}, {90, 30}}, 2}, {"Long gradual", 15, {{46, 120}, {80, 60}, {90, 15}}, 3}, {"Wheat", 1, {{46, 120}, {53, 40}, {65, 40}, {72, 40}, {85, 40}}, 5}, - {"Amazake", 1, {{51, 480}}, 1}, - {"SV", 0, {{46, 45}}, 1}, + // {"Amazake", 1, {{51, 480}}, 1}, + {"37, 3 days", 1, {{37, 1440}, {37, 1440}, {37, 1440}}, 3}, + // {"SV", 0, {{46, 45}}, 1}, // {"Yoghurt maker", 0, {{40, 300}, {40, 300}, {30, 300}}, 3}, // {"TEST", 0, {{46, 1}}, 1}, }; @@ -88,9 +89,10 @@ bool inSelectionMode = true; // double Kp = 0.5, Ki = 1, Kd = 0.05; // PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); -#include "GyverPID.h" +// #include "GyverPID.h" +#include "pid.h" double Setpoint, Input, Output; -GyverPID regulator(50, 1, 5, 1000); +GyverPID regulator(50, 1, 20, 1000); bool temperatureSensorError = false; diff --git a/pid.h b/pid.h new file mode 100644 index 0000000..3e96301 --- /dev/null +++ b/pid.h @@ -0,0 +1,162 @@ +/* + 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 + +#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 diff --git a/t_heater.ino b/t_heater.ino index bece136..947a4ed 100644 --- a/t_heater.ino +++ b/t_heater.ino @@ -22,11 +22,10 @@ void HandlePwmHeaterDisplay() { byte graphHeight = 10; byte currentAmount = Output==0 ? 0 : (Output-1)/99.0 * (graphHeight) + 1; - Serial.println(currentAmount); graphStates[currentGraphItemNumber] = currentAmount; - byte rightMargin = 128; + byte rightMargin = 127; byte topMargin = 63-graphHeight; byte itemsDisplayed = 0; @@ -44,11 +43,27 @@ void HandlePwmHeaterDisplay() { itemsDisplayed++; } - oled.setCursor(126-graphItems - 2*8, 7); + byte leftPosition = 126-graphItems - 3*6; + oled.setCursor(leftPosition, 7); sprintf(timeBuffer, "%3d", (int)(Output)); oled.invertText(true); oled.print(timeBuffer); oled.invertText(false); + + leftPosition -= 6*3; + oled.setCursor(leftPosition, 7); + sprintf(timeBuffer, "%3d", (int)(regulator.lastD)); + oled.print(timeBuffer); + + leftPosition -= 6*3; + oled.setCursor(leftPosition, 7); + sprintf(timeBuffer, "%3d", (int)(regulator.lastI)); + oled.print(timeBuffer); + + leftPosition -= 6*3; + oled.setCursor(leftPosition, 7); + sprintf(timeBuffer, "%3d", (int)(regulator.lastP)); + oled.print(timeBuffer); } // LP_TIMER(1000, HandleHeater);