/*
Представляю свой вариант пивоварни на ардуино. Данная пивоварня полностью
автоматизирует процесс затирания и варки.
Обзор здесь https://www.youtube.com/watch?v=bIaC9XsAsao&list=PLHYZQO_g_zSHyv_OUIhgqWRMPNfkqeIX7
Последние изменения:
1. Добавил защиту от сбоя питания(Пока не сохраняет время,только № паузы)
2. Добавил возможность управления насосом и миксером(меню "Настройки").
3. Добавил в настройки возможность настройки ПИД регулятора. Первичная
настройка обязательна. У меня корректно работает с параметрами kP=25;
Ki=0.3; kD=15, Im=2 (Im это ограничение интегральной составляющей,
оно нужно чтобы избежать интегрального перенасыщения и соответственно
перегрева)
4. Добавил возможность вызова настроек из ручного режима и во время выполнения
программы с помощью двойного клика. При этом программа не прерывается
(продолжает проходить по запрограммированным паузам.)
5. Добавил возможность регулировки скорости работы насоса и миксера
(работает только с коллекторными двигателями постоянного тока)
6. Добавил кнопку режима редактирования. Когда данная кнопка нажата при обычном
повороте энкодера происходит изменение параметра, а не перемещение курсора.
7. Реализовал полный цикл затирание и варка.
8. Устранил баг с непрекращающимся писком при выходе из программы.
9. Сделал выключение ТЭНа по завершении варки.
10.Усовершентствовал защиту от сбоя питания(Теперь сохраняет время с точностью до 1 минуты).
Идеи на будущее:
1. Контроль температуры реле
2. Поддержка 2 независимых тенов(для промывочной воды во время затирания)
3. Включение миксера после засыпи и выключение с программой.
Код в данном проекте изменен тк данный сайт не полностью поддерживает
датчик температуры DS18B20, соответственно в реальном проекте вместо
DHT22 нужно подключать именно его, вместо красного светодиода
твердотельное реле(желательно с контролем нуля) управления ТЭНом,
вместо зеленого реле управления насосом, вместо синего реле управления
миксером. Нужно удалить или закомментировать все строки с комментарием
"удалить" и раскомментировать все строки с комментарием "Раскомментировать"
перед заливкой скетча в ардуино.
Готовый скетч можно скачать здесь https://youtu.be/bIaC9XsAsao
Попробуйте дебетовую карту Tinkoff Black с кэшбэком рублями до 30% и
ежемесячным процентом на остаток. При заказе карты по этой ссылке
https://www.tinkoff.ru/baf/g9uqDgStYb
вы получите 500 рублей в подарок
Так же по этой ссылке можно заказать другие продукты Tinkoff и получить различные подарки.
Поддержать проект можно:
1. По номеру телефона +7 (920) 029-5558 через СБП (ЮМани, Максим К)
2. На кошелек ЮМани 410018618410083
3. По ссылке https://yoomoney.ru/to/410018618410083
=================================================================================
MIT License
Copyright (c) 2023 Max
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
==================================================================================
*/
// Начальные установки
#define EB_NO_FOR // отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки)
#define EB_NO_CALLBACK // отключить обработчик событий attach (экономит 2 байта оперативки)
#define EB_NO_COUNTER // отключить счётчик энкодера [VirtEncoder, Encoder, EncButton] (экономит 4 байта оперативки)
#define EB_NO_BUFFER // отключить буферизацию энкодера (экономит 2 байта оперативки)
#define EB_DEB_TIME 50 // таймаут гашения дребезга кнопки (кнопка)
#define EB_CLICK_TIME 200 // таймаут ожидания кликов (кнопка)
#define EB_HOLD_TIME 500 // таймаут удержания (кнопка)
#define EB_STEP_TIME 200 // таймаут импульсного удержания (кнопка)
#define EB_FAST_TIME 30 // таймаут быстрого поворота (энкодер)
#define heater_pin 6 //пин управление тэн
#define buzzer_pin 5 // Пин подключения пассивной пищалки
#define pump_pin 9 //пин управление насосом
#define mixer_pin 10 //пин управление миксером
#define program_adress 0 //адрес ячейки в которой хранится номер текущей программы
#define pause_adress 1 //адрес ячейки в которой хранится номер текущей паузы
#define settings_adress 3 //адрес храннения настроек, занимает 13 ячеек
#define time_adress 768 //адрес храннения времении, занимает 255 ячеек
#define kP float(settings[8])/5 //Пропорциональный коэффициент, меняется от 0 до 51 с шагом 0,2
#define kI float(settings[9])/10 //Интегральный коэффициент, меняется от 0 до 25,5 с шагом 0,1
#define kD float(settings[10])/10 //Дифференциальный коэффициент, меняется от 0 до 25,5 с шагом 0,1
#define Imax float(settings[11])/10 //Максимальное значение интегральной составляющей при приближении температуры к заданной
#define boil_temp (float(settings[12])/10 + 80) // Температура кипения
#include <math.h> // Математическая библиотека
#include <EEPROM.h> // Библиотека для работы с EEPROM
#include <U8g2lib.h> // Библиотека работы с графическим дисплеем
#include <SPI.h> // Библиотека для работы с SPI
#include <Wire.h> // Библиотека для работы с шиной I2C
#include <microDS18B20.h> // Библиотека датчика температуры
#include <EncButton.h> // Библиотека энкодера
EncButton enc(2, 3, 4); // Энкодер с кнопкой <A, B, KEY>
Button edit_btn(12); //Кнопка режима редактирования
//MicroDS18B20<7> ds; //датчик температуры на 6 пин Раскоментировать
#include <dht.h> // Удалить
#define DHT22_PIN 7 // Удалить
dht DHT; // Удалить
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); //Инициализируем работу дисплея SSD1306 по шине I2C
//U8G2_ST7920_128X64_F_HW_SPI u8g2(U8G2_R0, /* CS=*/ U8X8_PIN_NONE, /* reset=*/ A5); //Инициализируем работу дисплея ST7920 по шине SPI
//U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ U8X8_PIN_NONE, /* dc=*/ A4, /* reset=*/ A5); //Инициализируем работу дисплея SSD1309 по шине I2C
//U8G2_ST7565_NHD_C12864_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ U8X8_PIN_NONE, /* dc=*/ A4, /* reset=*/ A5); //Инициализируем работу дисплея ST7565 по шине SPI
uint8_t main_ptr = 1; //Указатель главного меню
uint8_t program_ptr = 1; //Указатель меню выбора и редактирования программ
uint8_t settings_ptr = 1; //Указатель меню настроек
bool settings_flag = false; //Флаг вызова настроек
bool PID_settings_flag = false; //Флаг настроек ПИД регулятора
bool end_loop; //Флаг выхода из меню выбора и редактирования программ
uint8_t time = 0; //Сколько времени прошло
volatile float cur_temp = 0; //текущаяя температура
//uint8_t settings[13]; //настройки //Раскоментировать
uint8_t settings[ ] = {1, 1, 78, 255, 1, 1, 78, 255, 125, 3, 150, 20, 0}; //Удалить
volatile int32_t temp_timer = 0; //Таймер температуры
volatile uint32_t pump_timer = 0; //Таймер насоса
volatile uint32_t mixer_timer = 0; //Таймер миксера
const uint8_t max_font[1313] U8G2_FONT_SECTION("max_font") = // вставляем свой шрифт
"v\0\3\2\3\4\3\3\4\5\11\0\376\7\376\7\376\0\0\0\0\2\12 \5\0\322\1!\7\71\323"
"\61(\1%\11=\322!MY\247I,\10\42\313\61$\12\0-\6\15\336\61\10.\6\22\323\61\4"
"\60\11=\322\263d\336\222\5\61\10\273\322\223H]\6\62\12=\322\263da\326\66\10\63\13=\322\61"
"\210Y\246j\311\2\64\14=\322\227II)\31\264\60\1\65\13=\322q\34\322PK\26\0\66\14="
"\322%e\341\220dZ\262\0\67\13=\322\61\210Y\230\205%\0\70\14=\322\263dZ\262dZ\262\0"
"\71\14=\322\263dZ\62\204Y$\1:\10*\323\61DC\0;\11:\313\61DC\242\0=\10\35"
"\326\61\250\203\0D\16=\322\61D\225(\211\222(\211\6\5F\12=\322q\14\207$,\2G\13="
"\322\263dbiK\26\0I\10\273\322\261D]\6J\12=\322\265\205-Q$\1L\10=\322\21\366"
"\66\14N\13=\322\221i\223\222H\233\26Q\13=\322\263d.\211\24)\1R\14=\322\61$\231\66"
"(\245J\26S\13=\322\263d\352\252%\13\0U\11=\322\221\371\226,\0V\12=\322\221yKj"
"\21\0W\12=\322\221yI\224.\0Y\12=\322\221iI-l\2Z\11=\322\61\210Y\307Ab"
"\13=\322\21\206C\222\331\6\5d\12=\322Y\31\64[\62\4f\12=\322%U\262-\254\1g\13"
"=\312\263d\266d\10\223\5h\12=\322\21\206C\222\271\5i\10\273\322\23J-\3j\12L\312\27"
"kmR\242\0k\13=\322\21\326\244d\252d\1l\7\273\322!\365\62m\13-\322\241\264(\211\222"
"(\5n\11-\322\221\230\64[\0q\12=\312\63h\266d\10\13r\11-\322\221\230\304\42\0s\10"
"-\322\63\250\7\5t\13=\322\23f[X\212\24\0u\11-\322\221\71)J\0v\11-\322\221\331"
"\222Z\4w\12-\322\221Y\22\245\13\0z\11-\322\61hm\203\0\260\11\244\336\243DR\242\0\0"
"\0\0\4\377\377\4\20\14=\322\263d\332\60d\266\0\4\21\14=\322q\34\222\314\66(\0\4\22\16"
"=\322\61$\231\66(\231\66(\0\4\23\11=\322q\23;\2\4\24\20E\316%%Q\22%Q\22"
"%\311\260\5\4\25\14=\322q\14\207$\14\7\1\4\26\15=\322\221$Je\313\226\212R\4\27\14"
"=\322\263da\244j\311\2\4\30\14=\322\221\231\226d\322\264\0\4\31\15=\322\221$\232\264$\223"
"\246\5\4\32\15=\322\221IIIK\242J\26\4\33\17=\322\265DI\224DI\224DZ\0\4\34"
"\14=\322\221-K\242$\232-\4\35\13=\322\221\331\206!\263\5\4\36\12=\322\263d\336\222\5\4"
"\37\10=\322q\363-\4 \14=\322\61$\231\66(a\21\4!\13=\322\263db[\262\0\4\42"
"\12=\322q)\205\235\0\4#\14=\322\221\271%C\230,\0\4$\15=\322\225-\25%Q*["
"\4\4%\14=\322\221iI\255R\323\2\4&\21M\312\21%Q\22%Q\22%Q\62\210\5\4'"
"\12=\322\221\331\222!l\4(\20=\322\221$J\242$J\242$J\62\14\4)\21M\312\221$J"
"\242$J\242$J\62\214\5\4*\14=\322!\26\247J\224,\0\4+\14=\322\221\331&%Q\222"
")\4,\14\274\322\221\325\226H\32\22\0\4-\15=\322\263da\62\204Z\262\0\4.\20=\322\21"
"%\25%\31\22%Q\22)\1\4/\15=\322\63hZ\62DII\13\4\60\13-\322\263&\203\226"
"\14\1\4\61\14=\322\263\204C\222\331\222\5\4\62\14-\322\61$\331\240d\203\2\4\63\10-\322q"
"l\4\4\64\15\65\316%%Q\22%\311\260\5\4\65\13-\322\263d\203\222.\0\4\66\12-\322\221"
"T\266li\4\67\14-\322\263d\221\222%\13\0\4\70\13-\322\221IK\62i\1\4\71\14=\322"
"\63g\322\222LZ\0\4:\13\254\322\21)\211\224\224\2\4;\14-\322\265DI\224DZ\0\4<"
"\13-\322\221-K\242i\1\4=\13-\322\221i\303\220i\1\4>\12-\322\263d\266d\1\4\77"
"\10-\322qs\13\4@\14=\312\61$\231mP\302\20\4A\13-\322\263db\226,\0\4B\12"
"-\322\61Ha\23\0\4C\14=\312\221\271%C\230,\0\4D\15=\312\263T\224D\251la\4"
"\4E\12-\322\221%\265J-\4F\16\65\316\21%Q\22%Q\62\210\1\4G\12-\322\221i\311"
"\20\26\4H\15-\322\221$J\242$J\62\14\4I\16=\312\221$J\242$J\62\214\5\4J\12"
"-\322!\206Se\1\4K\13-\322\221i\223\222L\1\4L\13\254\322\221eK\64$\0\4M\12"
"\254\322\61&\333\220\0\4N\15-\322\21%\225!Q\22)\1\4O\13\254\322\63D\311\222H\1\0"
"";
// Конец шрифта
ISR(TIMER0_COMPA_vect){ //функция вызываемая в прерываниях каждую милисекунду
//Измеряем температуру
if (millis() - temp_timer >= 1000){
temp_timer = millis();
DHT.read22(DHT22_PIN); //Удалить
cur_temp = DHT.temperature; //Удалить
/*if (ds.readTemp()) { //Раскоментировать
cur_temp = ds.getTemp(); //Раскоментировать
ds.requestTemp(); //Раскоментировать
} */ //Раскоментировать
}
//Управление насосом и миксером
if (cur_temp < settings[2]){ //Если текущая температура меньше температуры отключения
if (millis() - pump_timer < uint32_t(settings[0])*6000){ //Если таймер насоса меньше времени его работы
analogWrite(pump_pin, settings[3] * 2 + 55); //Записываем значение скорости в пин насоса
} else if (millis() - pump_timer <= (uint32_t(settings[0]) + uint32_t(settings[1]))*6000){ //Иначе, если таймер насоса больше времени его работы, но меньше суммы времени работы и отдыха
digitalWrite(pump_pin, LOW); //Выключаем насос
} else pump_timer = millis(); //Иначе сбрасываем таймер
} else digitalWrite(pump_pin, LOW);
if (cur_temp < settings[6]){ //Если текущая температура меньше температуры отключения
if (millis() - mixer_timer < uint32_t(settings[4])*6000){ //Если таймер миксера меньше времени его работы
analogWrite(mixer_pin, settings[7] * 2 + 55); //Записываем значение скорости в пин миксера
} else if (millis() - mixer_timer < (uint32_t(settings[4]) + uint32_t(settings[5]))*6000){ //Иначе, если таймер миксера больше времени его работы, но меньше суммы времени работы и отдыха
digitalWrite(mixer_pin, LOW); //Выключаем миксер
} else mixer_timer = millis(); //Иначе сбрасываем таймер
} else digitalWrite(mixer_pin, LOW);
}
void setup() {
TCCR1A = 0b00000001; // Эти две строки повышают частоту ШИМ на 9 и 10 пинах до 31.4 кГц
TCCR1B = 0b00000001; //
// Таймер 0
TCCR0A |= (1 << WGM01); // Режим работы таймера "сброс"
OCR0A = 0xF9; //Счет при котором происходит прерывание (249) при делителе 64 это будет 1мс ля таймера 0
TIMSK0 |= (1 << OCIE0A ); //Разрешить прерывание при совпадении с регистром А
TCCR0B |= (0 << CS02) | (1 << CS01) | (1 << CS00); //Устанавливаем делитель частоты 001-1, 010-8, 011-64, 100-256, 101-1024
sei(); // Разрешить прерывания /**/
pinMode(buzzer_pin, OUTPUT);
pinMode(heater_pin, OUTPUT);
pinMode(pump_pin, OUTPUT);
pinMode(mixer_pin, OUTPUT);
//Serial.begin(115200);
attachInterrupt(0, isr_enc, CHANGE); //Подкючаем аппаратное прерывание по изменению состояния пина D2
attachInterrupt(1, isr_enc, CHANGE); //Подкючаем аппаратное прерывание по изменению состояния пина D3
u8g2.begin(); //Стартуем экран
u8g2.enableUTF8Print(); //Включаем поддержку UTF8
u8g2.setDrawColor(2); //Устанавливаем цвет шрифта, 2-инверсия
u8g2.setFont(max_font);//Устанавливаем шрифт
int8_t data_test [] = {1, 40, 100, 2, 65, 100, 2, 78, 100}; //Удалить
int8_t data_test2 [] = {5, 100, 50, 4, 2, 0, 0, 0, 0}; //Удалить
for (int8_t i = 1; i < 10; i++){ //Удалить
EEPROM.put(i*50, data_test); //Удалить
EEPROM.put(i*50+30, data_test2); //Удалить
} //Удалить
EEPROM.put(settings_adress, settings); //Удалить
EEPROM.get(settings_adress, settings); //считывание настороек
if (EEPROM[program_adress] > 9) EEPROM.update(program_adress, 0);
if (EEPROM[program_adress]){ // Если программа завершилась Не корректно
for(uint16_t i = 0; EEPROM[time_adress + i] && i < 255 ; i++) {
time++; // считываем данные о времени
}
program_ptr = EEPROM[program_adress]; // Устанавливаем номер программы
Program_exec(EEPROM[pause_adress]); // Запуск прерванной программы
} else { // Если программа завершилась Корректно
for(uint16_t i = 0; i < 255; i++) {
EEPROM.update(time_adress + i, 0); // Удаляем данные о времени
}
}
}
void loop() {
enc.tick(); // Опрос энкодера
u8g2.firstPage(); //очищает буфер экрана
u8g2.setCursor(28, 9);
u8g2.print(F("Главное меню"));
u8g2.drawLine(26, 12, 100, 12);
u8g2.setCursor(2, 24);
u8g2.print(F("Выбор программы"));
u8g2.setCursor(2, 36);
u8g2.print(F("Редактор программ"));
u8g2.setCursor(2, 48);
u8g2.print(F("Ручной режим"));
u8g2.setCursor(2, 60);
u8g2.print(F("Настройки"));
u8g2.drawRBox(0,main_ptr*12+3,127,12,0);
u8g2.nextPage();
if (enc.right()) {
main_ptr++;
if (main_ptr == 5) main_ptr = 1;
}
if (enc.left()) {
main_ptr--;
if (main_ptr == 0) main_ptr = 4;
}
if (enc.click()) {
if (main_ptr == 1) Program_selection();
if (main_ptr == 2) Program_editing();
if (main_ptr == 3) Manual_mod();
if (main_ptr == 4) {
for(;;){
enc.tick(); // Опрос энкодера
edit_btn.tick(); //Опрос кнопки режима редактирования
u8g2.firstPage(); //очищает буфер экрана
Settings();
u8g2.nextPage();
if (enc.hold()) {
EEPROM.put(settings_adress, settings);
PID_settings_flag = false;
break;
}
}
}
}
}
void Program_selection() { //Функция выбора программы
for (end_loop = false; !end_loop;) {
u8g2.firstPage(); //очищает буфер экрана
u8g2.setCursor(19, 11);
u8g2.print(F("Выбор программы"));
u8g2.drawLine(17, 14, 109, 14);
Program_menu();
u8g2.nextPage();
if (enc.click()) {
uint8_t data [3];
EEPROM.get(program_ptr * 50, data);
uint8_t amt = data[0];
bool prog_correct = false; //Проверка корректности записанной программы
if (amt <10) prog_correct = true; // Количество пауз меньше 10
for (int8_t i = 0; i <= amt && prog_correct && amt; i++) {
EEPROM.get(program_ptr * 50 + i * 3, data);
if (i != 0) {
if (data [0] < 1 || data [0] > 120) prog_correct = false; //длительность пауз должна быть не меньше 1 и не больше 120 минут.
}
if (data [1] > 100) prog_correct = false; // температура не должна быть больше 100°С
if (data [2] > 100) prog_correct = false; //мощность должна быть не более 100%
}
if(!EEPROM[program_ptr * 50 +30] && !amt) prog_correct = false; // и время варки и количество пауз не должно быть = 0
if(EEPROM[program_ptr * 50 +30]){ // если длительность варки не = 0
if(EEPROM[program_ptr * 50 + 31] > 100) prog_correct = false; // если мощность ТЕНа до > 100 и < 10
if(EEPROM[program_ptr * 50 + 32] > 100) prog_correct = false; // если мощность ТЕНа после > 100 и < 10
if(EEPROM[program_ptr * 50 + 33] > EEPROM[program_ptr * 50 + 30]) prog_correct = false; // Время занесения первого солода не должно быть больше времени варки.
for(int8_t i = 34; i < 39 && prog_correct; i++){
if(EEPROM[program_ptr * 50 + i] != 0 && EEPROM[program_ptr * 50 + i] >= EEPROM[program_ptr * 50 - 1 + i]) prog_correct = false;
// Время занесения следующего солода должно быть меньше предыдущего или = 0
}
}
// Конец проверки корректности программы
if (prog_correct){
if(amt) { // Если количество пауз не = 0
Program_exec(0); // Запускаем программу с начала
} else {
Program_exec(2); //Запускаем программу сразу с варки
}
} else {
for(;;) {
enc.tick();
u8g2.firstPage(); //очищает буфер экрана
u8g2.setCursor(10, 14);
u8g2.print(F("Программа записана"));
u8g2.drawLine(8, 17, 118, 17);
u8g2.setCursor(23, 30);
u8g2.print(F("некорректно!!!"));
u8g2.drawLine(21, 33, 105, 33);
u8g2.setCursor(34, 56);
u8g2.print(F("Нажмите ОК"));
u8g2.nextPage();
if (enc.click()) break;
}
}
}
}
}
void Program_editing() { //Функция редактирования программы
for (end_loop = false; !end_loop;) {
u8g2.firstPage(); //очищает буфер экрана
u8g2.setCursor(15, 11);
u8g2.print(F("Редактор программ"));
u8g2.drawLine(13, 14, 117, 14);
Program_menu(); //Вызываем функцию отрисовки меню
u8g2.nextPage(); // Выводит содержимое буфера на экран
if (enc.click()) {
uint8_t data [3]; //Массив
EEPROM.get(program_ptr * 50, data);
uint8_t amt;
int8_t int_ptr = 1;
for(int8_t pause_ptr = 0;;){
u8g2.firstPage(); //очищает буфер экрана
if (pause_ptr == 0){
if(data[0] > 200) data[0] = 0; //Исключаем перелет с 0 на 9
data[0] = constrain(data[0], 0, 9); //Ограничиваем количество пауз от 1 до 9
amt = data[0];
u8g2.setCursor(31, 11);
u8g2.print(F("Программа "));
u8g2.print(program_ptr);
u8g2.drawLine(29, 14, 97, 14);
u8g2.setCursor(2, 28);
u8g2.print(F("Количество пауз "));
u8g2.print(data[0]);
u8g2.setCursor(1, 42);
u8g2.print(F("Темпер-ра засыпи "));
} else if (pause_ptr <= amt){
data[0] = constrain(data[0], 1, 120); //Ограничиваем длинну пауз от 1 до 120 мин.
u8g2.setCursor(5, 11);
u8g2.print(F("Программа "));
u8g2.print(program_ptr);
u8g2.print(F(", Пауза "));
u8g2.print(pause_ptr);
u8g2.drawLine(3, 14, 125, 14);
u8g2.setCursor(2, 28);
u8g2.print(F("Длительность "));
u8g2.print(data[0]);
u8g2.print(F(" мин."));
u8g2.setCursor(1, 42);
u8g2.print(F("Температура "));
} else if (pause_ptr == amt + 1){
u8g2.setCursor(2, 28);
u8g2.print(F("Длительность "));
u8g2.print(data[0]);
u8g2.print(F(" мин."));
u8g2.setCursor(1, 42);
u8g2.print(F("Мощ-ть ТЭНа до "));
u8g2.print(data[1]);
u8g2.print(F("%"));
u8g2.setCursor(2, 56);
u8g2.print(F("Мощ-ть ТЭНа после "));
u8g2.print(data[2]);
u8g2.print(F("%"));
data[1] = constrain(data[1], 10, 100); //Ограничиваем мощность от 10 до 100%
data[2] = constrain(data[2], 10, 100); //Ограничиваем мощность от 10 до 100%
} else {
for (int8_t i=1; i < 4; i++){
u8g2.setCursor(2, i*14+14);
u8g2.print(i+3*(pause_ptr-amt-2));
u8g2.print(F("-й хмель за "));
u8g2.print(data[i-1]);
u8g2.print(F(" мин."));
}
}
if (pause_ptr <= amt){
data[1] = constrain(data[1], 20, 80); //ограничиваем температуру от20 до 80°С
data[2] = constrain(data[2], 10, 100); //Ограничиваем мощность от 10 до 100%
u8g2.print(data[1]);
u8g2.print(F("°С"));
u8g2.setCursor(2, 56);
u8g2.print(F("Мощность ТЭНа "));
u8g2.print(data[2]);
u8g2.print(F("%"));
} else {
u8g2.setCursor(10, 11);
u8g2.print(F("Программа "));
u8g2.print(program_ptr);
u8g2.print(F(", Варка"));
u8g2.drawLine(8, 14, 119, 14);
}
u8g2.drawRBox(0,int_ptr*14+3,127,14,0); //отрисовываем указатель
u8g2.nextPage(); // Выводит содержимое буфера на экран
enc.tick();
edit_btn.tick();
if (enc.hold()) {
EEPROM.put(program_ptr * 50 + (pause_ptr <= amt ? (pause_ptr * 3) : (27 + (pause_ptr - amt) * 3)), data);
break;
}
if (enc.right() && !edit_btn.pressing()) {
int_ptr++;
if (int_ptr == 4) {
EEPROM.put(program_ptr * 50 + (pause_ptr <= amt ? (pause_ptr * 3) : (27 + (pause_ptr - amt) * 3)), data);
pause_ptr++;
if(pause_ptr > amt + 3) pause_ptr = 0;
EEPROM.get(program_ptr * 50 + (pause_ptr <= amt ? (pause_ptr * 3) : (27 + (pause_ptr - amt) * 3)), data);
int_ptr = 1;
}
}
if (enc.rightH() || (enc.right() && edit_btn.pressing())) data[int_ptr - 1]++;
if (enc.left() && !edit_btn.pressing()) {
int_ptr--;
if (int_ptr == 0) {
EEPROM.put(program_ptr * 50 + (pause_ptr <= amt ? (pause_ptr * 3) : (27 + (pause_ptr - amt) * 3)), data);
pause_ptr--;
if(pause_ptr < 0) pause_ptr = amt +3;
EEPROM.get(program_ptr * 50 + (pause_ptr <= amt ? (pause_ptr * 3) : (27 + (pause_ptr - amt) * 3)), data);
int_ptr = 3;
}
}
if (enc.leftH() || (enc.left() && edit_btn.pressing())) data[int_ptr-1]--;
}
}
}
}
void Program_exec(uint8_t pause_ptr) { //Функция исполнения программы
uint8_t data1 [3]; // Массив с данными о кол-ве/времени пауз, температуре, мощности.
uint8_t data2 [10]; // Массив с данными о времени кипячения количестве хмеля, мощности
bool temp_flag = false; // Флаг выхода на температурный режим
bool exit_flag = false; // Флаг запроса завершения программы
bool hop_flag = false; // Флаг внесения хмеля
uint32_t Tmr_ms = 0; //Таймер температурного режима (милисекунды)
uint16_t Tmr_s; //Таймер температурного режима (секунды)
uint8_t Tmr_min = 0; //Таймер температурного режима (минуты)
uint8_t amt; //Количество пауз
uint8_t hop_ctr = 1; //Счетчик хмеля
int8_t exit_ptr; //Указатель меню выхода
enc.reset(); // Сброс состояний энкодера
EEPROM.update(program_adress, program_ptr); //Записываем № программы в ПЗУ
EEPROM.update(pause_adress, pause_ptr); //Записываем № паузы в ПЗУ
EEPROM.get(program_ptr * 50 + 30, data2); //Запрашиваем данные из ПЗУ
data2[9] = 0; // последний элемент массива должен быть равен 0
EEPROM.get(program_ptr * 50, amt); //Запрос количества пауз
if (pause_ptr <= amt) EEPROM.get(program_ptr * 50 + pause_ptr * 3, data1);//Запрашиваем данные из ПЗУ
else if (pause_ptr == amt + 1) EEPROM.get(program_ptr * 50 + (pause_ptr - 1) * 3, data1); // Последняя пауза поддерживает параметры предыдущей.
else { // Кипячение
data1[0] = data2[0]; // Устанавливаем длительность варки
data1[1] = 100; // Устанавливаем максимальную температуру
data1[2] = data2[1]; // Мощность до закипания
}
for(; pause_ptr <= amt + 3;){
u8g2.firstPage(); //очищает буфер экрана
enc.tick(); //Опрашиваем экодер
edit_btn.tick(); //Опрашиваем кнопку
if (enc.hold()) { //Если произошло удержание энкодера
if (!settings_flag) { // Если не вызваны настройки
exit_flag = !exit_flag; //Вызываем/отзываем запрос на завершение программы.
exit_ptr = 2; //Устанавливаем курсор на "отмена"
} else { //Иначе
settings_flag = false; //закрываем настройки
PID_settings_flag = false;
EEPROM.put(settings_adress, settings); //Сохраняем настройки в ПЗУ
}
}
if (enc.hasClicks(2) && !exit_flag) { //Если был двойной клик и не вызвано меню завершения программы
settings_flag = !settings_flag; //Вызываем/отзываем настройки
PID_settings_flag = false;
EEPROM.put(settings_adress, settings); //Сохраняем настройки в ПЗУ
}
if (exit_flag){ //Если вызвано меню выхода
if (enc.right()) exit_ptr++; //Если поворот вправо увеличиваем указатель
if (enc.left()) exit_ptr--; //Если поворот влево уменьшаем указатель
exit_ptr = constrain(exit_ptr, 0, 2); //Ограничиваем возможные значения указателя
u8g2.setCursor(2, 48);
u8g2.print(F("Завершить программу?"));
u8g2.setCursor(2, 60);
u8g2.print(F("Да Нет Отмена"));
u8g2.drawRBox(exit_ptr * 42,51,42,12,0); //Отрисовываем курсор
if (enc.click()){ //Если был клик энкодера
if(exit_ptr > 0) exit_flag = false; //Если указатель больше 0(нет, отмена) отзываем меню выхода
else {
break; //Иначе выход из программы
}
}
}
heat(data1[1], data1[2]); //Вызываем функцию нагрева
if (!temp_flag && (pause_ptr <= amt && cur_temp > data1[1] - 0.5 || pause_ptr == amt + 1 || pause_ptr == amt + 2 && cur_temp >= boil_temp || pause_ptr == amt + 3)){// Если достигнута температура или пауза последняя
temp_flag = true; //Устанавливаем температурный флаг
Tmr_ms = millis(); //Запускаем счетчик
if(pause_ptr == amt + 2) data1[2] = data2[2]; //Устанавливаем мощность после закипания
if(pause_ptr == amt + 3) data1[2] = 0; //Устанавливаем мощность 0 после завершения варки
}
if(settings_flag) Settings(); //Если вызваны настройки запускаем их
else {
if (pause_ptr == 0){ //Если пауза № 0 (до засыпи)
u8g2.setCursor(10, 9);
u8g2.print(F("Программа "));
u8g2.print(program_ptr); // Номер программы
u8g2.print(F(" Нагрев"));
u8g2.drawLine(8, 12, 118, 12);
}else if (pause_ptr == amt + 1){ //Если последняя пауза затирания
u8g2.setCursor(6, 9);
u8g2.print(F("Затирание завершено"));
u8g2.drawLine(4, 12, 123, 12);
}else if (pause_ptr >= amt + 2){ //Если Варка
if (pause_ptr == amt + 2){
u8g2.setCursor(13, 9);
u8g2.print(F("Программа "));
u8g2.print(program_ptr); // Номер программы
u8g2.print(F(" Варка"));
u8g2.drawLine(11, 12, 116, 12);
} else { //Самая последняя пауза
u8g2.setCursor(20, 9);
u8g2.print(F("Варка завершена"));
u8g2.drawLine(18, 12, 109, 12);
}
u8g2.setCursor(2, 36);
u8g2.print(F("Мощность "));
u8g2.print(data1[2]);
u8g2.print(F("%"));
if (enc.right() && !exit_flag) data1[2]++;
if (enc.left() && !exit_flag) data1[2]--;
if (data1[2] > 100 && data1[2] < 200) data1[2] = 0; //Переход от 100 к 0
data1[2] = constrain(data1[2], 0, 100); //Ограничиваем мощность от 10 до 100%
}else{ // В случае других(промежуточных) пауз
u8g2.setCursor(7, 9);
u8g2.print(F("Программа "));
u8g2.print(program_ptr);
u8g2.print(F(" Пауза "));
u8g2.print(pause_ptr); //Номер паузы
u8g2.drawLine(5, 12, 121, 12);
}
u8g2.setCursor(2, 24);
u8g2.print(F("Тек. темп-ра "));
u8g2.print(cur_temp, 1);
u8g2.print(F("°С"));
if (pause_ptr < amt + 2) {
u8g2.setCursor(2, 36);
u8g2.print(F("Уст. темп-ра "));
u8g2.print(data1[1]);
u8g2.print(F("°С"));
}
}
if (temp_flag){ //Если установлен флаг достижения требуемой температуры
Tmr_s = floor((millis() - Tmr_ms)/1000) + time * 60; // Таймер полных секунд + время из памяти после сбоя.
if (pause_ptr !=0 && pause_ptr != amt + 1 && pause_ptr != amt + 3 && Tmr_min < (floor(Tmr_s/60) - time)) { // если не нагрев и не ожидание после затирания и не ожидание после варки и прошла минута
EEPROM.update(time_adress + Tmr_min + time, 1); //Записываем её в ПЗУ
Tmr_min++; //Увеличиваем таймер минут на 1
}
if (pause_ptr == amt + 3){
if(Tmr_s % 2 == 0) analogWrite(buzzer_pin, 127); //Включаем пищалку на четных секундах
else digitalWrite(buzzer_pin, LOW); // Выключаем на нечетных
} else {
if(Tmr_s % 4 < 2 && Tmr_s < 10) analogWrite(buzzer_pin, 127); //Включаем пищалку на 0-й, 4-й и 8-й секунде
if(Tmr_s % 4 > 1 && Tmr_s <= 10) digitalWrite(buzzer_pin, LOW); // Выключаем на 2-й, 6-й и 10-й
}
if (pause_ptr == amt + 2 && data2[0] *60 - Tmr_s <= data2[hop_ctr + 2] * 60 && !hop_flag && data2[hop_ctr + 2]) { //Если варка и пора вносить хмель а флаг не поднят и время хмеля не 0
hop_flag = true; // Поднимаем флаг хмеля
}
if (pause_ptr == 0 && !exit_flag && !settings_flag){ //Если пауза № 0 (до засыпи) и не вызвано меню выхода или настроек
u8g2.setCursor(2, 48);
u8g2.print(F("Засыпьте солод"));
u8g2.setCursor(2, 60);
u8g2.print(F("и нажмите ОК!"));
if (enc.hasClicks(1)){ //Если был клик энкодера
pause_ptr++; //Увеличиваем № паузы
EEPROM.update(pause_adress, pause_ptr); //Записываем № паузы в ПЗУ
EEPROM.get(program_ptr * 50 + pause_ptr * 3, data1); //Получаем данные следующей паузы
temp_flag = false; //Сбрасывем температурный флаг
digitalWrite(buzzer_pin, LOW); //Выключаем пищалку
}
} else if ((pause_ptr == amt + 1 || pause_ptr == amt + 3) && !exit_flag && !settings_flag && !hop_flag) { //Если последняя пауза и не вызвано меню выхода или настроек
u8g2.setCursor(2, 60);
Time(Tmr_s); //Отображаем сколько времени прошло
u8g2.print(F(" нажмите ОК"));
if (enc.hasClicks(1)){
pause_ptr++; // Увеличиваем № паузы
EEPROM.update(pause_adress, pause_ptr); //Записываем № паузы в ПЗУ
temp_flag = false; // Сбрасывем температурный флаг
if (data2[0]) { // Если время варки не равно нулю
data1[0] = data2[0]; // Устанавливаем длительность варки
data1[1] = 100; // устанавливаем температуру кипения
data1[2] = data2[1]; // Устанавливаем мощность до закипания
} else {
pause_ptr += 2; // Увеличиваем указатель до amt + 4
}
digitalWrite(buzzer_pin, LOW); //Выключаем пищалку
}
} else{ // В случае других(промежуточных) пауз
if (!exit_flag && !settings_flag && !hop_flag){ //Если не вызвано меню выхода или настроек
if(pause_ptr == amt + 2) {
if(data2[hop_ctr+2]) { // проверяем что не все хмели заложены
u8g2.setCursor(2, 48);
u8g2.print(F("До "));
u8g2.print(hop_ctr);
u8g2.print(F("-го хмеля "));
Time(data2[0] * 60 - Tmr_s - data2[hop_ctr+2] *60); //Отображаем сколько времени осталось до следующего хмеля
}
u8g2.setCursor(2, 60);
u8g2.print(F("До завершения "));
} else {
u8g2.setCursor(2, 48);
u8g2.print(F("До конца паузы"));
u8g2.setCursor(2, 60);
u8g2.print(F("осталось "));
}
Time(data1[0] * 60 - Tmr_s); //Отображаем сколько времени осталось до конца паузы
}
if (data1[0] * 60 - Tmr_s <= 0){ //Если время паузы вышло
pause_ptr++; //Увеличиваем № паузы
time = 0; //Стираем время предыдущей паузы
EEPROM.update(pause_adress, pause_ptr); //Записываем № паузы в ПЗУ
for(uint16_t i = 0; i < 255; i++) {
EEPROM.update(time_adress + i, 0); // Удаляем данные о времени
}
temp_flag = false; //Сбрасывем температурный флаг
if (pause_ptr <= amt) { //Если пауза не последняя
EEPROM.get(program_ptr * 50 + pause_ptr * 3, data1); //Получаем данные следующей паузы
}
}
}
}
if(hop_flag) {
if(Tmr_s % 2 == 0) analogWrite(buzzer_pin, 127); //Включаем пищалку на четных секундах
else digitalWrite(buzzer_pin, LOW); // Выключаем на нечетных
if (!exit_flag && !settings_flag){ //Если не вызвано меню выхода или настроек
u8g2.setCursor(2, 48);
u8g2.print(F("Внесите "));
u8g2.print(hop_ctr);
u8g2.print(F("-й хмель и"));
u8g2.setCursor(2, 60);
u8g2.print(F("нажмите Ок"));
if (enc.hasClicks(1) || time > data2[0] - data2[hop_ctr + 2]){ //Если был клик энкодера или программа восстановилась после сбоя со временем большим чем время до закладки хмеля.
hop_ctr++; //Увеличиваем счетчик хмеля
hop_flag = false; //Сбрасываем флаг хмеля
digitalWrite(buzzer_pin, LOW); // выключаем пищалку
}
}
}
u8g2.nextPage();
}
digitalWrite(buzzer_pin, LOW); //Выключаем пищалку
digitalWrite(heater_pin, LOW);
EEPROM.update(program_adress, 0);
time = 0; //Стираем время предыдущей паузы
for(uint16_t i = 0; i < 255; i++) {
EEPROM.update(time_adress + i, 0); // Удаляем данные о времени из ПЗУ
}
}
void Program_menu() { //Функция отрисовки меню программ
enc.tick();
for (uint8_t i = 1; i <= 3; i++) { //Отрисовываем меню
u8g2.setCursor(2, i * 14 + 14);
u8g2.print(F("Программа "));
if (program_ptr < 4) u8g2.print(i);
else if (program_ptr > 6) u8g2.print(i+6);
else u8g2.print(i+3);
}
u8g2.drawRBox(0,((program_ptr-1)%3+1)*14+3,127,14,0);
if (enc.right()) program_ptr++; //Поворот вправо
if (program_ptr == 10) program_ptr = 1;
if (enc.left())program_ptr--; // Поворот влево
if (program_ptr == 0) program_ptr = 9;
if (enc.hold()) end_loop = true; // Долгое нажатие
}
void Manual_mod() { //Ручной режим
int8_t ptr = 1; // Указатель
bool temp_flag = false; // Флаг выхода на температурный режим
uint32_t Tmr_ms; //Таймер температурного режима милисекунды
uint32_t Tmr_s; //Таймер температурного режима секунды
static int8_t temp_pow[] = {50, 80}; // температура, мощность
bool heater_on = false; //Нагреватель выключен
enc.reset(); // Сброс состояний энкодера
for (;;) {
if (!temp_flag && cur_temp > temp_pow[0] - 0.5){ //если температура приблизилась к установленной а температурный флаг не установлен.
temp_flag = true; // Ставим флаг температура достигнута
Tmr_ms = millis(); // Запускаем счетчик
}
if (temp_flag){ //Если заданная температура достигнута
Tmr_s = floor((millis() - Tmr_ms)/1000); //Подсчитываем количество полных секунд
//if (Tmr_s >= 36000) Tmr_ms = millis(); // Сбрасываем счетчик по истечении 10 часов
if(Tmr_s % 4 < 2 && Tmr_s < 10) analogWrite(buzzer_pin, 127); //Включаем пищалку на 0-й, 4-й и 8-й секунде
if(Tmr_s % 4 > 1 && Tmr_s <= 10) digitalWrite(buzzer_pin, LOW); // Выключаем на 2-й, 6-й и 10-й
}
enc.tick(); //Опрос энкодера
edit_btn.tick(); //Опрашиваем кнопку
if (enc.hasClicks(2)) { //Если был двойной клик
settings_flag = !settings_flag; //Вызываем/отзываем настройки
PID_settings_flag = false;
EEPROM.put(settings_adress, settings); //Сохраняем настройки в ПЗУ
}
u8g2.firstPage(); //очищает буфер экрана
if (heater_on) heat(temp_pow[0], temp_pow[1]);//Если ТЭН включен вызываем функцию нагрева
else digitalWrite(heater_pin, LOW); //Иначе выключаем ТЭН
if (settings_flag) Settings(); //Если вызваны настройки запускаем их
else {
u8g2.setCursor(28, 9);
u8g2.print(F("Ручной режим"));
u8g2.drawLine(26, 12, 100, 12);
u8g2.setCursor(2, 24);
u8g2.print(F("Тек. темп-ра"));
u8g2.setCursor(2, 36);
u8g2.print(F("Уст. темп-ра"));
u8g2.setCursor(2, 48);
u8g2.print(F("Мощность ТЭНа"));
u8g2.setCursor(85, 48);
u8g2.print(temp_pow [1]); //Установленная мощьность
u8g2.print(F("%"));
u8g2.setCursor(2, 60);
if (heater_on){
u8g2.print(F("ТЭН Вкл"));
} else {
u8g2.print(F("ТЭН Выкл"));
}
u8g2.setCursor(85, 24);
u8g2.print(cur_temp,1); //Текущая температура
u8g2.print(F("°С"));
u8g2.setCursor(85, 36);
u8g2.print(temp_pow [0]); //Установленная температура
u8g2.print(F("°С"));
if (temp_flag){ //Если заданная температура достигнута
u8g2.setCursor(85, 60);
u8g2.print((Tmr_s / 3600) % 10); //Выводим часы
u8g2.print(F(":"));
u8g2.print((Tmr_s / 600) % 6); //Выводим десятки минут
u8g2.print((Tmr_s / 60) % 10); //Выводим минуты
u8g2.print(F(":"));
u8g2.print((Tmr_s / 10) % 6); //Выводим десятки секунд
u8g2.print(Tmr_s % 10); //Выводим секунды
}
u8g2.drawRBox(0,ptr*12+15,127,12,0); //Рисуем курсор
if (enc.leftH() || (enc.left() && edit_btn.pressing())) { // Если был нажатый поворот влево или простой поворот с нажатой кнопкой
if (ptr < 3) { // Если курсор не на состоянии ТЭНа
temp_pow[ptr-1]--; // Уменьшаем значение
} else { // Если курсор на состоянии ТЭНа
heater_on = !heater_on; // Включаем/выключаем ТЭН
}
if(ptr == 1 && temp_flag){ // Если изменилась установленная температура
temp_flag = false; // Сбрасываем флаг
digitalWrite(buzzer_pin, LOW); // Отключаем пищалку
}
}
if (enc.left() && !edit_btn.pressing()){ // Если был поворот влево и не нажата кнопка
ptr--; // Перемещаем курсор вверх
if(ptr == 0) ptr=3; // Если курсор выше допустимого перемещаем его в самый низ.
}
if (enc.rightH() || (enc.right() && edit_btn.pressing())) { // Если был нажатый поворот вправо или простой поворот с нажатой кнопкой
if (ptr < 3) { // Если курсор не на состоянии ТЭНа
temp_pow[ptr-1]++; // Увеличиваем значение
} else { // Если курсор на состоянии ТЭНа
heater_on = !heater_on; // Включаем/выключаем ТЭН
}
if(ptr == 1 && temp_flag){ // Если изменилась установленная температура
temp_flag = false; // Сбрасываем флаг
digitalWrite(buzzer_pin, LOW);// Отключаем пищалку
}
}
if (enc.right() && !edit_btn.pressing()){ // Если был поворот вправо и не нажата кнопка
ptr++; // Перемещаем курсор вниз
if(ptr == 4) ptr=1; // Если курсор ниже допустимого перемещаем его в самый верх.
}
if (enc.hasClicks(1) && ptr == 3) heater_on = !heater_on; //Если был клик и курсора на состоянии ТЭНа включаем/выключаем ТЭН
temp_pow[0] = constrain(temp_pow[0], 20, 100);
temp_pow[1] = constrain(temp_pow[1], 10, 100);
}
u8g2.nextPage(); // Выводит содержимое буфера на экран
if (enc.hold()) { //Если произошло удержание энкодера
if (settings_flag) { //Если вызваны настройки
settings_flag = false; //Отзываем настройки
PID_settings_flag = false;
EEPROM.put(settings_adress, settings); //Сохраняем настройки в ПЗУ
} else break; //иначе выход
}
}
digitalWrite(heater_pin, LOW);
digitalWrite(buzzer_pin, LOW);
}
void isr_enc() { //Функция вызываемая в прерывании
enc.tickISR(); // тикер в прерывании. Не вызывает подключенные коллбэки внутри прерывания!!!
}
void heat(int8_t set_temp, int8_t power){ //Функция нагрева
static uint16_t activePeriod = 0;
static uint32_t pwm_tmr = 0;
float cur_temp2;
static float integral = 0, prevErr = 0, err = 0, D =0;
if (millis() - pwm_tmr >= 1000) {
pwm_tmr = millis();
if (set_temp < 100){ // пид регулятор
cli(); //остановка прерываний
cur_temp2 = cur_temp;
sei(); //Запуск прерываний
err = set_temp - cur_temp2; // Пропорциональная составляющая
integral = constrain(integral + err, (-Imax - fabs(err) * 10), (Imax + 10 * fabs(err))); //Интегральная составляющая
D = err - prevErr; //Дифференциальная состовляющая
prevErr = err;
activePeriod = round(constrain(err * kP + integral * kI + D * kD, 0, power)) * 10;
/*Serial.print(cur_temp);
Serial.print(F(" ,"));
Serial.print(err);
Serial.print(F(" ,"));
Serial.print(err * kP);
Serial.print(F(" ,"));
Serial.print(integral * kI);
Serial.print(F(" ,"));
Serial.print(D * kD);
Serial.print(F(" ,"));
Serial.println(activePeriod/10);*/
} else {
activePeriod = power * 10;
}
}
if (millis() - pwm_tmr < activePeriod) digitalWrite(heater_pin, HIGH);
else {
digitalWrite(heater_pin, LOW);
}
if (PID_settings_flag) {
u8g2.setCursor(64, 24);
u8g2.print(F("Р="));
u8g2.print(err * kP,1);
u8g2.setCursor(64, 36);
u8g2.print(F("I="));
u8g2.print(integral * kI,1);
u8g2.setCursor(64, 48);
u8g2.print(F("D="));
u8g2.print(D * kD,1);
u8g2.setCursor(64, 60);
u8g2.print(F("t="));
u8g2.print(cur_temp,1);
u8g2.print(F("°С"));
}
}
void Settings(){ //Настройки
switch (settings_ptr) {
case 1 ... 4:
PID_settings_flag = false;
u8g2.setCursor(10, 9);
u8g2.print(F("Управление насосом"));
u8g2.drawLine(8, 12, 118, 12);
u8g2.setCursor(1, 24);
u8g2.print(F("Время работы "));
u8g2.print(float(settings[0])/10,1);
u8g2.print(F(" мин."));
u8g2.setCursor(1, 36);
u8g2.print(F("Время отдыха "));
u8g2.print(float(settings[1])/10,1);
u8g2.print(F(" мин."));
u8g2.setCursor(1, 48);
u8g2.print(F("Темп-ра откл-я "));
u8g2.print(settings[2]);
u8g2.print(F("°С"));
u8g2.setCursor(1, 60);
u8g2.print(F("Скорость "));
u8g2.print(settings[3]);
u8g2.print(F("%"));
u8g2.drawRBox(0,(settings_ptr)*12+3,127,12,0);
break;
case 5 ... 8:
PID_settings_flag = false;
u8g2.setCursor(7, 9);
u8g2.print(F("Управление миксером"));
u8g2.drawLine(5, 12, 121, 12);
u8g2.setCursor(1, 24);
u8g2.print(F("Время работы "));
u8g2.print(float(settings[4])/10,1);
u8g2.print(F(" мин."));
u8g2.setCursor(1, 36);
u8g2.print(F("Время отдыха "));
u8g2.print(float(settings[5])/10,1);
u8g2.print(F(" мин."));
u8g2.setCursor(1, 48);
u8g2.print(F("Темп-ра откл-я "));
u8g2.print(settings[6]);
u8g2.print(F("°С"));
u8g2.setCursor(1, 60);
u8g2.print(F("Скорость "));
u8g2.print(settings[7]);
u8g2.print(F("%"));
u8g2.drawRBox(0,(settings_ptr - 4)*12+3,127,12,0);
break;
case 9 ... 12:
PID_settings_flag = true;
u8g2.setCursor(25, 9);
u8g2.print(F("ПИД Регулятор"));
u8g2.drawLine(23, 12, 103, 12);
u8g2.setCursor(1, 24);
u8g2.print(F("kР="));
u8g2.print(kP,1);
u8g2.setCursor(1, 36);
u8g2.print(F("kI="));
u8g2.print(kI,1);
u8g2.setCursor(1, 48);
u8g2.print(F("kD="));
u8g2.print(kD,1);
u8g2.setCursor(1, 60);
u8g2.print(F("Im="));
u8g2.print(Imax,1);
u8g2.drawRBox(0,(settings_ptr - 8)*12+3,43,12,0);
break;
case 13 ... 16:
PID_settings_flag = false;
u8g2.setCursor(22, 9);
u8g2.print(F("Доп. настройки"));
u8g2.drawLine(20, 12, 107, 12);
u8g2.setCursor(1, 24);
u8g2.print(F("Темп-ра кип-я "));
u8g2.print(boil_temp,1);
u8g2.print(F("°С"));
u8g2.drawRBox(0,(settings_ptr - 12)*12+3,127,12,0);
break;
}
if (enc.rightH() || (enc.right() && edit_btn.pressing())) settings[settings_ptr - 1]++;
if (enc.leftH() || (enc.left() && edit_btn.pressing())) settings[settings_ptr - 1]--;
if (enc.right() && !edit_btn.pressing()) {
settings_ptr++;
EEPROM.put(settings_adress, settings);
if (settings_ptr == 14) settings_ptr = 1;
}
if (enc.left() && !edit_btn.pressing()) {
settings_ptr--;
EEPROM.put(settings_adress, settings);
if (settings_ptr == 0) settings_ptr = 13;
}
settings[2] = constrain(settings[2], 40, 100); //Ограничение температуры отключения насоса
settings[6] = constrain(settings[6], 40, 100); //Ограничение температуры отключения миксера
settings[3] = constrain(settings[3], 0, 100); //Ограничение регулировки скорости насоса
settings[7] = constrain(settings[7], 0, 100); //Ограничение регулировки скорости миксера
if (settings[12] == 255) settings[12] = 0; //Избегаем перескока с 80 на 105
settings[12] = constrain(settings[12], 0, 250); //Ограничение температуры кипения
}
void Time(uint16_t tmr) {//функция отображения времени
u8g2.print((tmr / 3600) % 10); //Часы
u8g2.print(":");
u8g2.print((tmr / 600) % 6); //Десятки минут
u8g2.print((tmr / 60) % 10); //Минуты
u8g2.print(":");
u8g2.print((tmr / 10) % 6); //Десятки секунд
u8g2.print(tmr % 10); //Секунды
}