Это какое-же сообщение навело вас на енту мысль? ![]()
А по CRC странненькая ситуация: а на кой он в таком виде тут нужен? Данные пришли в мк, преобразовались в значение crc и тут же в этом же мк сравниваются с “самими себями”. Они что могут не совпасть?
Это какое-же сообщение навело вас на енту мысль? ![]()
А по CRC странненькая ситуация: а на кой он в таком виде тут нужен? Данные пришли в мк, преобразовались в значение crc и тут же в этом же мк сравниваются с “самими себями”. Они что могут не совпасть?
пока во мне есть адреналин раш, я могу попытаться вас запрограммировать на решение вопроса))))
те товарищи просто программируют вас на другом языке, а я программирую на энлипи…
ну так вот, для решения вашего вопроса, вам надо осознать
что бы никакая гадость данные не изменяла, а если бы изменила, мы бы им не поверили
если не понятен предыдущий код, то придется читать новый… вот лично мне бы было лень, и я бы попытался понять старый именно на вашем месте))) а PPeterr сам определится))
// Каждый датчик DS18B20 имеет уникальный адрес (как серийный номер)
// Это массив из 6 датчиков, у каждого адрес из 8 байт
byte ADDR_DS18B20 [6][8] = {
{0x28, 0xD5, 0x97, 0x76, 0x0, 0x0, 0x0, 0x76}, // Датчик на компрессоре
{0x28, 0x4F, 0x9C, 0x73, 0x0, 0x0, 0x0, 0xF9}, // Датчик воды на входе
{0x28, 0xFC, 0xD1, 0x72, 0x0, 0x0, 0x0, 0x2D}, // Датчик воды на выходе
{0x28, 0x95, 0x28, 0x75, 0x0, 0x0, 0x0, 0xAB}, // Дополнительный датчик 3
{0x28, 0xEF, 0x8C, 0x76, 0x0, 0x0, 0x0, 0x58}, // Датчик разморозки
{0x28, 0x8C, 0x3A, 0xB4, 0xE, 0x0, 0x0, 0xDB} // Датчик испарителя
};
Вдруг кто-то будет интересоваться кроме меня…
“Датчик на компрессоре” - датчик установлен на выходе фреона из компрессора. Измеряет температуру нагретого фреона.
“Дополнительный датчик 3” - датчик установлен в точке входе фреона в компрессор. Позволяет совместно с датчиком температуры на испарителе узнать важный параметр, называемый “перегрев”.
“Датчик разморозки” - датчик установлен на пластинчатом теплообменнике фреон/вода, - в таком месте, где в режиме разморозки (также режим “оттайка”) теплообменник имеет наименьшую температуру (наибольшая вероятность появления льда, что критично для теплообменника).
Для обсудить (ни в коем случае не с претензией):
// ========== 8. ТОЧНЫЕ ЗНАЧЕНИЯ (с запасом по точности) ==========
// int16_t - целое число от -32768 до 32767
// int32_t - целое число от -2147483648 до 2147483647
int16_t tOverheatReal; // Реальный перегрев (разница температур)
int32_t gWaterReal; // Реальный расход воды
int32_t heatPowerReal; // Реальная тепловая мощность
int32_t ElectrPowReal; // Реальная электрическая мощность
int32_t COPreal; // Реальный КОП
Я оставил “int32_t” для значений - только лишь потому, что были какие-то проблемы при попытке создать математическое выражение в int16_t. Думаю - это связано с тем, что исходные значения (электроэнергия и расход воды) - берутся по импульсу. В свою очередь - импульс связан со временем. Которое в свою очередь связано с millis(). Даже принудительная инициализация типа “(int_16t)” перед переменной - не помогали получить нормальное вычисление. В итоге - решил в этом месте не экономить память и присвоить int32_t… Хотя понятно - что это избыточно.
// ========== 9. СРЕДНИЕ ЗНАЧЕНИЯ ==========
int32_t countHeatAndElectr = 0; // Счётчик для усреднения
int32_t countHeat = 0; // Сумма тепловой мощности для среднего
int32_t countElectr = 0; // Сумма электрической мощности для среднего
Я это сделал сначала временно. Потом мне понравилось - и решил оставить. Хотя, - не нашёл подходящего места для этого на экране. Так же - не совсем понимаю, как надолго хватит int32_t, ведь значение там набегает быстро. И пойдёт ли оно автоматически на второй круг когда закончится и какие при этом будут значения… В общем - тут я не разобрался до конца.
// Включаем насос на 16 минут если нет расхода воды
if (HeatOptionPauseFlag == true && digitalRead(51) == HIGH && millis() - HeatOptionPauseStartTime >= 960000) {
if (digitalRead(51) == HIGH) {
WaterStartTimer = millis();
FirstImpulsWater = false;
SecondImpulsWater = false;
}
bitSet(relay, 3); // Включаем насос
HeatOptionPauseStartTime = millis();
}
Да. Но только - не “на 16 минут”, а “если прошло 16 минут”. И не “если нет расхода воды”, а “если насос отключен и отключен уже в течение 16 минут”… ну, типа того… По коду - всё верно (вроде)
// Заканчиваем разморозку через 15 минут или при температуре >= 2°C
if (millis() - DefrostTime >= 900000 && Temper[5] >= 20) {
DefrostOptionFlag = false;
StartHeatTime = millis();
StartHeatFlag = true; // Переходим в стартовый режим
}
Да. Но только не “15 минут или при температуре >= 2°C”, а должно выполниться оба условия. Код - по-моему верный.
// Выводим новые значения
// Если датчик ошибается - выводим голубым, иначе - цветным
tft.setTextColor((MistakeTemp[4] > 0) ? CYAN : CYAN);
Вот тут не получится. Но я что-нибудь придумаю. Либо переделаю опять по своему - чтобы были цифры на фоне. Иначе - не бросается в глаза, что значение некорректное.
В любом случае - спасибо за код. Пойду, попробую загрузить его в контроллер.
По порядку.
Вчера, когда пошёл загружать новый код - предыдущий код продолжал работать (находясь в ошибке по температурам), цикл отрабатывал. Прошло для его рабочего состояния - более суток.
Далее. При загрузке нового кода (из сообщения 115), появилась ошибка. Эта ошибка мучает меня давно, но я уже как-то свыкся с ней. Сейчас подумал - может она имеет значение. Дело в том, что частенько, после очередных корректировок кода (скетча) и попытки его загрузки в контроллер - происходит ситуация, когда уже по логу “загрузка выполнена успешно” появляется сообщение, наподобие “отсутствует ком порт”. Насколько я пытался выяснить в интернете с чем это связано - нашёл, что якобы после загрузки кода идёт проверка контрольных сумм и ответ от контроллера приходит о несоответствии этих контрольных сумм.
При этом - если убрать галочку выделенную красным:
/*
* ПРОГРАММА ДЛЯ МОНИТОРИНГА ТЕПЛОВОГО НАСОСА
*
* Что делает программа:
* 1. Читает температуру с 6 датчиков DS18B20
* 2. Считает расход воды и электричества
* 3. Управляет реле (компрессор, насос, вентилятор, клапан)
* 4. Рисует графики на дисплее
* 5. Защищает систему от ошибок
*/
// ========== 1. ПОДКЛЮЧАЕМ БИБЛИОТЕКИ ==========
#include <MCUFRIEND_kbv.h> // Библиотека для работы с TFT дисплеем
MCUFRIEND_kbv tft; // Создаём объект для управления дисплеем
#include <OneWire.h> // Библиотека для работы с датчиками по протоколу 1-Wire
OneWire ds(21); // Говорим, что датчики подключены к пину 21 Arduino
// ========== 2. НАСТРОЙКИ ПИНОВ ДЛЯ ДИСПЛЕЯ ==========
#define LCD_CS A3 // Пин выбора дисплея (Chip Select)
#define LCD_CD A2 // Пин команд/данных
#define LCD_WR A1 // Пин записи
#define LCD_RD A0 // Пин чтения
#define LCD_RESET A4 // Пин сброса дисплея
// ========== 3. ЗАДАЁМ ЦВЕТА (как краски на палитре) ==========
// Каждый цвет - это число. Дисплей понимает цвета как числа.
#define BLACK 0x0000 // Чёрный (все компоненты выключены)
#define BLUE 0x001F // Синий
#define RED 0xF800 // Красный
#define GREEN 0x07E0 // Зелёный
#define CYAN 0x07FF // Голубой (синий + зелёный)
#define MAGENTA 0xF81F // Фиолетовый (красный + синий)
#define YELLOW 0xFFE0 // Жёлтый (красный + зелёный)
#define WHITE 0xFFFF // Белый (все компоненты на максимум)
#define WHITEGREY 0xC638 // Светло-серый (чуть меньше белого)
#define DARKGREY 0x7BEF // Тёмно-серый (половинная яркость)
#define DARKGREEN 0x0C80 // Тёмно-зелёный
// ========== 4. ПИНЫ ДЛЯ СЧЁТЧИКОВ ВОДЫ И ЭЛЕКТРИЧЕСТВА ==========
#define pinVoda_tick 18 // Пин, куда приходят импульсы от счётчика воды
boolean stateCheck_voda; // Переменная для отслеживания состояния пина воды
#define pinElectr_tick 19 // Пин, куда приходят импульсы от электросчётчика
boolean stateCheck_electr; // Переменная для отслеживания состояния пина электричества
// ========== 5. ПЕРЕМЕННАЯ ДЛЯ УПРАВЛЕНИЯ РЕЛЕ ==========
uint8_t relay; // 8-битное число (0-255), каждый бит управляет одним реле
// Бит 0: вентилятор
// Бит 1: 4-ходовой клапан
// Бит 2: компрессор
// Бит 3: насос
// ========== 6. АДРЕСА ДАТЧИКОВ ТЕМПЕРАТУРЫ ==========
// Каждый датчик DS18B20 имеет уникальный адрес (как серийный номер)
// Это массив из 6 датчиков, у каждого адрес из 8 байт
byte ADDR_DS18B20 [6][8] = {
{0x28, 0xD5, 0x97, 0x76, 0x0, 0x0, 0x0, 0x76}, // Датчик на компрессоре
{0x28, 0x4F, 0x9C, 0x73, 0x0, 0x0, 0x0, 0xF9}, // Датчик воды на входе
{0x28, 0xFC, 0xD1, 0x72, 0x0, 0x0, 0x0, 0x2D}, // Датчик воды на выходе
{0x28, 0x95, 0x28, 0x75, 0x0, 0x0, 0x0, 0xAB}, // Дополнительный датчик 3
{0x28, 0xEF, 0x8C, 0x76, 0x0, 0x0, 0x0, 0x58}, // Датчик разморозки
{0x28, 0x8C, 0x3A, 0xB4, 0xE, 0x0, 0x0, 0xDB} // Датчик испарителя
};
// ========== 7. МАССИВЫ ДЛЯ ХРАНЕНИЯ ГРАФИКОВ ==========
// 320 точек по горизонтали (ширина экрана), значения 0-255 по вертикали
uint8_t tIsparitel[320]; // График температуры испарителя
uint8_t tOverheat[320]; // График перегрева
uint8_t tKompressor[320]; // График температуры компрессора
uint8_t tWaterIn[320]; // График температуры воды на входе
uint8_t tWaterOut[320]; // График температуры воды на выходе
uint8_t tDefrost[320]; // График температуры разморозки
uint8_t heatPower[320]; // График тепловой мощности
uint8_t gWater[320]; // График расхода воды
uint8_t ElectrPow[320]; // График электрической мощности
uint8_t COP[320]; // График КОП (эффективности)
// ========== 8. ТОЧНЫЕ ЗНАЧЕНИЯ (с запасом по точности) ==========
// int16_t - целое число от -32768 до 32767
// int32_t - целое число от -2147483648 до 2147483647
int16_t tOverheatReal; // Реальный перегрев (разница температур)
int32_t gWaterReal; // Реальный расход воды
int32_t heatPowerReal; // Реальная тепловая мощность
int32_t ElectrPowReal; // Реальная электрическая мощность
int32_t COPreal; // Реальный КОП
int32_t gWaterRealPrew; // Предыдущее значение расхода воды
boolean FlagWater = false; // Флаг: есть ли данные по воде
boolean FirstImpulsWater = false; // Флаг: был ли первый импульс
boolean SecondImpulsWater = false; // Флаг: был ли второй импульс
// ========== 9. СРЕДНИЕ ЗНАЧЕНИЯ ==========
int32_t countHeatAndElectr = 0; // Счётчик для усреднения
int32_t countHeat = 0; // Сумма тепловой мощности для среднего
int32_t countElectr = 0; // Сумма электрической мощности для среднего
// ========== 10. ФЛАГИ ЦИКЛА (управление порядком действий) ==========
boolean Flag1 = true; // Флаг для шага 1 (запуск измерения)
boolean Flag2 = true; // Флаг для шага 2 (чтение температур)
boolean Flag3 = true; // Флаг для шага 3 (расчёты и логика)
boolean Flag4 = true; // Флаг для шага 4 (управление реле)
uint8_t countGetGrafic = 9; // Счётчик для обновления графиков
uint32_t Timer; // Таймер для отслеживания времени
// ========== 11. МАССИВЫ ДЛЯ ГРАФИКОВ СОСТОЯНИЯ РЕЛЕ ==========
int16_t trend_fan[20]; // История включений вентилятора (20 ячеек по 16 бит)
int16_t trend_kompressor[20]; // История включений компрессора
int16_t trend_pump[20]; // История включений насоса
int16_t trend_4way_valve[20]; // История включений 4-ходового клапана
// ========== 12. ПЕРЕМЕННЫЕ ДЛЯ РАСХОДОМЕРА ВОДЫ ==========
uint32_t tmr; // Таймер для защиты от дребезга контактов
boolean gWaterFlag = false; // Флаг: идёт ли измерение
uint32_t WaterStartTimer; // Время начала измерения
uint32_t WaterCountTimer; // Длительность импульса
// ========== 13. ПЕРЕМЕННЫЕ ДЛЯ ЭЛЕКТРОСЧЁТЧИКА ==========
uint32_t tmr2; // Таймер для защиты от дребезга
boolean ElectrFlag = false; // Флаг: идёт ли измерение
uint32_t ElectrStartTimer; // Время начала измерения
uint32_t ElectrCountTimer; // Длительность импульса
// ========== 14. ФЛАГИ РЕЖИМОВ РАБОТЫ ==========
boolean StartHeatFlag; // Режим: начальный нагрев
boolean StopFlag; // Режим: аварийная остановка
boolean HeatOptionFlag; // Режим: рабочий нагрев
boolean HeatOptionPauseFlag; // Режим: пауза между нагревом
boolean DefrostOptionFlag; // Режим: разморозка
// ========== 15. СЧЁТЧИКИ ДЛЯ ЗАЩИТЫ ==========
uint8_t tKompressorCounter = 0; // Счётчик для управления компрессором
uint8_t HeatOptionCout = 0; // Счётчик для окончания нагрева
uint8_t HeatOptionPauseCout = 0; // Счётчик для окончания паузы
uint8_t defrostCount = 0; // Счётчик для разморозки
uint8_t StopMaxTemp = 0; // Счётчик превышения температуры
uint8_t StopMaxElectr = 0; // Счётчик превышения мощности
uint8_t StopMinTempHeater = 0; // Счётчик низкой температуры нагревателя
uint8_t StopNo4xvalve = 0; // Счётчик ошибки клапана
uint8_t StopNoElectric = 0; // Счётчик отсутствия электричества
uint8_t StopKind = 0; // Код причины остановки
uint8_t MistakeTemp[6] = {}; // Счётчики ошибок для каждого датчика
boolean getMistakeFlag = false; // Флаг первой записи температур
// ========== 16. ВРЕМЕННЫЕ МЕТКИ ==========
uint32_t HeatOptionPauseStartTime; // Время начала паузы
uint32_t StartHeatTime; // Время начала нагрева
uint32_t StopTime; // Время аварийной остановки
uint32_t DefrostTime; // Время начала разморозки
// ========== 17. МАССИВЫ ТЕМПЕРАТУР ==========
int16_t Temper[6] = {0, 0, 0, 0, 0, 0}; // Текущие температуры (в десятых долях)
int16_t TemperPrev[6]; // Предыдущие температуры (для проверки)
// ========== 18. ФУНКЦИЯ ПРОВЕРКИ ЦЕЛОСТНОСТИ ДАННЫХ (CRC) ==========
// CRC - это как контрольная сумма. Если данные повредились при передаче,
// CRC не совпадёт, и мы поймём, что данным нельзя доверять.
uint8_t dallas_crc8(const uint8_t *data, uint8_t len) {
uint8_t crc = 0; // Начальное значение CRC
// Проходим по всем байтам данных
for (uint8_t i = 0; i < len; i++) {
uint8_t inbyte = data[i]; // Берём очередной байт
// Обрабатываем каждый бит байта
for (uint8_t j = 0; j < 8; j++) {
// Проверяем, нужно ли делать XOR (операция "исключающее ИЛИ")
uint8_t mix = (crc ^ inbyte) & 0x01;
crc >>= 1; // Сдвигаем CRC вправо
// Если младший бит был 1, делаем XOR с полиномом
if (mix) crc ^= 0x8C;
inbyte >>= 1; // Сдвигаем байт данных вправо
}
}
return crc; // Возвращаем посчитанный CRC
}
// ========== 19. ФУНКЦИЯ ЧТЕНИЯ ТЕМПЕРАТУРЫ С ДАТЧИКА ==========
// Возвращает температуру в десятых долях (например 156 = 15.6°C)
// Если ошибка - возвращает 0x7FFF (максимальное число)
int16_t readDS18B20(byte *addr) {
uint8_t data[9]; // Массив для 9 байт данных от датчика
// 1. Начинаем общение с датчиком
ds.reset(); // Сброс шины 1-Wire
ds.select(addr); // Выбираем конкретный датчик по адресу
ds.write(0xBE); // Команда "прочитай мне данные"
// 2. Читаем 9 байт от датчика
for (uint8_t i = 0; i < 9; i++) {
data[i] = ds.read(); // Читаем байт
}
// 3. Проверяем CRC (целостность данных)
if (dallas_crc8(data, 8) != data[8]) {
return 0x7FFF; // Данные повреждены, возвращаем ошибку
}
// 4. Собираем температуру из двух байтов
int16_t raw = (data[1] << 8) | data[0]; // Сдвигаем старший байт и объединяем
return (raw * 10) >> 4; // Преобразуем в десятые доли градуса
}
// ========== 20. ФУНКЦИЯ ЗАПУСКА ИЗМЕРЕНИЯ ТЕМПЕРАТУРЫ ==========
// Говорим всем датчикам: "Начинайте измерять температуру!"
void resetTemp() {
ds.reset(); // Сброс шины
ds.write(0xCC); // Команда "обратиться ко всем датчикам"
ds.write(0x44); // Команда "начать измерение температуры"
}
// ========== 21. ФУНКЦИЯ ПОЛУЧЕНИЯ ТЕМПЕРАТУР ==========
// Читаем температуру со всех 6 датчиков
void gettingTemp() {
// Проходим по всем датчикам (от 0 до 5)
for (uint8_t i = 0; i < 6; i++) {
int16_t temp = readDS18B20(ADDR_DS18B20[i]); // Читаем датчик
if (temp != 0x7FFF) {
// Данные достоверны - сохраняем
Temper[i] = temp; // Текущая температура
TemperPrev[i] = temp; // Запоминаем как предыдущую
// Сбрасываем счётчик ошибок (если ошибок было меньше 6)
if (MistakeTemp[i] <= 6) MistakeTemp[i] = 0;
} else {
// Данные повреждены (ошибка CRC)
// Используем предыдущее значение вместо ошибочного
Temper[i] = TemperPrev[i];
// Увеличиваем счётчик ошибок, если система не остановлена
if (StopFlag == false) {
MistakeTemp[i]++; // +1 к ошибкам
// Если ошибок больше 6 - аварийная остановка
if (MistakeTemp[i] > 6) {
StopFlag = true; // Включаем флаг остановки
StopTime = millis(); // Запоминаем время остановки
StopKind = 6; // Причина: ошибка датчика
}
}
}
}
}
// ========== 22. ПРОВЕРКА ГРАНИЦ ТЕМПЕРАТУР ==========
// Проверяем, не выходит ли температура за разумные пределы
void tempMistake(uint8_t a, int16_t b, int16_t c, int16_t d) {
// a - номер датчика (0-5)
// b - минимально допустимая температура
// c - максимально допустимая температура
// d - максимально допустимое изменение за раз
// Если уже есть ошибки CRC - пропускаем проверку
if (MistakeTemp[a] > 0) return;
// Проверяем три условия:
// 1. Температура >= минимума
// 2. Температура <= максимума
// 3. Изменение не больше допустимого
if (Temper[a] >= b && Temper[a] <= c && abs(Temper[a] - TemperPrev[a]) <= d) {
// Всё нормально - ничего не делаем
} else {
// Ошибка! Используем предыдущее значение
Temper[a] = TemperPrev[a];
// Увеличиваем счётчик ошибок
if (StopFlag == false) MistakeTemp[a]++;
// Если ошибок больше 6 - аварийная остановка
if (MistakeTemp[a] > 6 && StopFlag == false) {
StopFlag = true;
StopTime = millis();
StopKind = 6; // Ошибка показаний датчика
}
}
}
// ========== 23. ОБРАБОТЧИК ИМПУЛЬСОВ СЧЁТЧИКА ВОДЫ ==========
// Вызывается часто в loop() для отслеживания импульсов
void voda_tick() {
// Читаем текущее состояние пина (HIGH или LOW)
boolean current_status = digitalRead(pinVoda_tick);
// Если сигнал изменился с HIGH на LOW (передний фронт импульса)
if (stateCheck_voda && !current_status) {
tmr = millis(); // Запоминаем время
gWaterFlag = true; // Ставим флаг: началось измерение
stateCheck_voda = current_status; // Запоминаем новое состояние
}
// Если сигнал изменился с LOW на HIGH (задний фронт)
if (!stateCheck_voda && current_status) {
stateCheck_voda = current_status; // Запоминаем состояние
}
// Если сигнал HIGH - сбрасываем флаг измерения
if (current_status) gWaterFlag = false;
// Если прошло больше 1 секунды и флаг измерения активен
// (защита от дребезга контактов - случайных ложных срабатываний)
if (!current_status && millis() - tmr >= 1000 && gWaterFlag == true) {
// Измеряем время между импульсами
WaterCountTimer = millis() - WaterStartTimer;
WaterStartTimer = millis(); // Запоминаем время этого импульса
gWaterFlag = false; // Сбрасываем флаг
// Отмечаем, что были импульсы
if (FirstImpulsWater == true) SecondImpulsWater = true;
FirstImpulsWater = true;
}
}
// ========== 24. ОБРАБОТЧИК ИМПУЛЬСОВ ЭЛЕКТРОСЧЁТЧИКА ==========
// Работает так же, как и счётчик воды
void electr_tick() {
boolean current_status = digitalRead(pinElectr_tick);
// Передний фронт импульса
if (stateCheck_electr && !current_status) {
tmr2 = millis(); // Запоминаем время
ElectrFlag = true; // Ставим флаг измерения
stateCheck_electr = current_status;
}
// Задний фронт
if (!stateCheck_electr && current_status) {
stateCheck_electr = current_status;
}
if (current_status) ElectrFlag = false;
// Если прошло 50 миллисекунд (защита от дребезга)
if (!current_status && millis() - tmr2 >= 50 && ElectrFlag == true) {
ElectrCountTimer = millis() - ElectrStartTimer; // Измеряем период
ElectrStartTimer = millis(); // Запоминаем время
ElectrFlag = false; // Сбрасываем флаг
}
}
// ========== 25. ФУНКЦИИ ВЫВОДА ЧИСЕЛ НА ДИСПЛЕЙ ==========
// Вывод числа с точностью до сотых (например 1234 = 12.34)
void getDataInt100(int16_t a) {
if (a >= -9 && a < 0) {
// Для чисел от -0.09 до 0
tft.print("-0.0");
tft.print(abs(a));
} else if (a < 9 && a >= 0) {
// Для чисел от 0 до 0.09
tft.print("0.0");
tft.print(a);
} else if (a <= -10 && a > -100) {
// Для чисел от -0.99 до -0.10
tft.print("-0.");
tft.print(abs(a % 100));
} else {
// Для всех остальных чисел
tft.print(a / 100); // Целая часть
tft.print(".");
if (abs(a % 100) < 10) tft.print("0"); // Добавляем ноль если нужно
tft.print(abs(a % 100)); // Дробная часть
}
}
// Вывод числа с точностью до десятых (например 123 = 12.3)
void getDataInt10(int16_t a) {
if (a >= -9 && a < 0) {
// Для чисел от -0.9 до 0
tft.print("-0.");
tft.print(abs(a));
} else if (a < 9 && a >= 0) {
// Для чисел от 0 до 0.9
tft.print("0.");
tft.print(a);
} else {
// Для всех остальных чисел
tft.print(a / 10); // Целая часть
tft.print(".");
tft.print(abs(a % 10)); // Дробная часть
}
}
// ========== 26. ПРЕОБРАЗОВАНИЕ ЧИСЕЛ ДЛЯ ГРАФИКА ==========
// Преобразует реальное значение (например -40°C) в позицию на экране (0-255)
int16_t getDataForUintMassiv(int16_t a) {
int16_t LastInMassive;
// Разбиваем на диапазоны для лучшего отображения
if (a < -400) LastInMassive = 0; // Всё что ниже -40°C - в самый низ
else if (a >= -400 && a < 500) LastInMassive = round((a + 400) / 5); // От -40°C до 50°C
else if (a >= 500 && a < 1250) LastInMassive = round(180 + (a - 500) / 10); // До 125°C
else LastInMassive = 255; // Всё что выше - в самый верх
return LastInMassive;
}
// То же самое, но для больших чисел (мощности)
int32_t getDataForUintMassiv32(int32_t a) {
int32_t LastInMassive;
if (a < -400) LastInMassive = 0;
else if (a >= -400 && a < 0) LastInMassive = round((a + 400) / 10);
else if (a >= 0 && a <= 160) LastInMassive = round(40 + a);
else if (a > 160 && a < 710) LastInMassive = round(200 + (a - 160) / 10);
else LastInMassive = 255;
return LastInMassive;
}
// ========== 27. ОТРИСОВКА ГРАФИКОВ ==========
// Рисует график по массиву значений
void getGraphics(uint16_t c, uint16_t d, uint8_t g[320]) {
// c - базовая линия по Y (где находится "ноль" графика)
// d - цвет линии
// g - массив с данными (320 точек)
Serial.println("proverka");
int16_t oldX = 0; // Предыдущая X координата
int16_t oldY = g[0]; // Предыдущая Y координата
// Рисуем линию, соединяя все точки графика
for (int16_t x = 1; x < 320; x++) {
int16_t nxt_x = x; // Текущая X координата
// Рисуем линию от предыдущей точки до текущей
tft.drawLine(oldX, (c - oldY), nxt_x, (c - g[x]), d);
oldY = g[x]; // Запоминаем текущую точку как предыдущую
oldX = nxt_x;
}
}
// ========== 28. ОТРИСОВКА ВСЕГО ЭКРАНА ==========
void getMainDisplay() {
tft.fillScreen(BLACK); // Заливаем экран чёрным (очищаем)
// Рисуем вертикальные линии сетки (по времени)
for (uint8_t i = 1; i < 8; i++) {
// Верхняя часть экрана (температуры)
tft.drawFastVLine((40 * i), 20, 190, DARKGREY);
// Нижняя часть экрана (мощности)
tft.drawFastVLine((40 * i), 230, 210, DARKGREY);
}
// Рисуем горизонтальные линии сетки (по значениям)
for (uint8_t i = 0; i < 11; i++) {
if (i == 4 || i == 5 || i == 6 || i == 7 || i == 9 || i == 10)
tft.drawFastHLine(0, (5 + 20 * i), 320, DARKGREEN); // Зелёные линии
else if (i == 1 || i == 2 || i == 3 || i == 8)
tft.drawFastHLine(0, (5 + 20 * i), 320, BLUE); // Синие линии
}
// Дополнительные линии для нижних графиков
for (uint8_t i = 0; i < 5; i++)
tft.drawFastHLine(0, (231 + 10 * i), 320, BLUE);
tft.drawFastHLine(0, 275, 320, BLUE);
for (uint8_t i = 0; i < 8; i++)
tft.drawFastHLine(0, (295 + 20 * i), 320, DARKGREEN);
// Подписи значений температуры
tft.setTextSize(1); // Маленький шрифт
tft.setCursor(0, 22); tft.setTextColor(WHITE, BLACK); tft.print("90");
tft.setCursor(0, 42); tft.print("70");
tft.setCursor(0, 62); tft.print("50");
tft.setCursor(0, 82); tft.print("40");
tft.setCursor(0, 102); tft.print("30");
tft.setCursor(0, 122); tft.print("20");
tft.setCursor(0, 142); tft.print("10");
tft.setCursor(0, 162); tft.print("0");
tft.setCursor(0, 182); tft.print("-10");
tft.setCursor(0, 202); tft.print("-20");
// Подписи для графиков мощностей
tft.setCursor(0, 238); tft.print("5");
tft.setCursor(0, 258); tft.print("3");
tft.setCursor(0, 272); tft.print("1.6");
tft.setCursor(0, 292); tft.print("1.4");
tft.setCursor(0, 312); tft.print("1.2");
tft.setCursor(0, 332); tft.print("1");
tft.setCursor(0, 352); tft.print("0.8");
tft.setCursor(0, 372); tft.print("0.6");
tft.setCursor(0, 392); tft.print("0.4");
tft.setCursor(0, 412); tft.print("0.2");
tft.setCursor(0, 432); tft.print("0");
// Подписи датчиков
tft.setCursor(0, 0); tft.setTextColor(RED); tft.print("Kompressor:");
tft.setCursor(10, 10); tft.setTextColor(MAGENTA); tft.print("Overheat:");
tft.setCursor(125, 10); tft.setTextColor(CYAN); tft.print("Defrost:");
tft.setCursor(220, 10); tft.setTextColor(RED); tft.print("Isparitel:");
tft.setCursor(120, 0); tft.setTextColor(YELLOW); tft.print("Water in:");
tft.setCursor(220, 0); tft.setTextColor(GREEN); tft.print("Water out:");
tft.setCursor(5, 210); tft.setTextColor(RED); tft.print("Heat power:");
tft.setCursor(152, 210); tft.setTextColor(YELLOW); tft.print("COP:");
tft.setCursor(218, 210); tft.setTextColor(GREEN); tft.print("Electr pow:");
tft.setCursor(22, 220); tft.setTextColor(MAGENTA); tft.print("G water:");
}
// ========== 29. НАЧАЛЬНАЯ НАСТРОЙКА (ВЫПОЛНЯЕТСЯ ОДИН РАЗ) ==========
void setup() {
Serial.begin(9600); // Запускаем Serial порт для отладки
// Настраиваем дисплей
tft.reset(); // Сбрасываем дисплей
tft.begin(38022); // Инициализируем с ID драйвера
tft.invertDisplay(true); // Инвертируем цвета
// Настраиваем пины счётчиков как входы с подтяжкой
pinMode(18, INPUT_PULLUP);
stateCheck_voda = digitalRead(pinVoda_tick); // Читаем начальное состояние
pinMode(19, INPUT_PULLUP);
stateCheck_electr = digitalRead(pinElectr_tick);
// Заполняем массивы графиков начальными значениями
for (int i = 0; i < 320; i++) {
tIsparitel[i] = 80; // Среднее значение
tOverheat[i] = 80;
tKompressor[i] = 80;
tWaterIn[i] = 80;
tWaterOut[i] = 80;
tDefrost[i] = 80;
heatPower[i] = 40;
gWater[i] = 40;
ElectrPow[i] = 40;
COP[i] = 40;
}
// Настраиваем пины управления реле как выходы
// И сразу выключаем все реле (HIGH - выключено)
pinMode(48, OUTPUT); digitalWrite(48, HIGH); // Вентилятор
pinMode(49, OUTPUT); digitalWrite(49, HIGH); // 4-ходовой клапан
pinMode(50, OUTPUT); digitalWrite(50, HIGH); // Компрессор
pinMode(51, OUTPUT); digitalWrite(51, HIGH); // Насос
// Устанавливаем начальные флаги
StopFlag = false; // Система не остановлена
HeatOptionFlag = false; // Нет активного нагрева
HeatOptionPauseFlag = false; // Нет паузы
DefrostOptionFlag = false; // Нет разморозки
StartHeatFlag = true; // Начинаем с режима старта
StartHeatTime = millis(); // Запоминаем время старта
Timer = millis(); // Запоминаем время для цикла
delay(1000); // Ждём 1 секунду для стабилизации
}
// ========== 30. ГЛАВНЫЙ ЦИКЛ (ВЫПОЛНЯЕТСЯ БЕСКОНЕЧНО) ==========
void loop() {
// Постоянно проверяем импульсы счётчиков
voda_tick(); // Проверяем счётчик воды
electr_tick(); // Проверяем электросчётчик
// ===== ШАГ 1: ЗАПУСК ИЗМЕРЕНИЯ ТЕМПЕРАТУРЫ (3-5 секунд после старта) =====
if (millis() - Timer >= 3000 && millis() - Timer < 5000 && Flag1 == true) {
resetTemp(); // Говорим датчикам начать измерение
Flag1 = false; // Сбрасываем флаг, чтобы не повторять
}
// ===== ШАГ 2: ЧТЕНИЕ ТЕМПЕРАТУР (5-7 секунд) =====
if (millis() - Timer >= 5000 && millis() - Timer < 7000 && Flag2 == true) {
gettingTemp(); // Читаем все датчики
Flag2 = false; // Сбрасываем флаг
}
// ===== ШАГ 3: РАСЧЁТЫ И ЛОГИКА (7-9 секунд) =====
if (millis() - Timer >= 7000 && millis() - Timer < 9000 && Flag3 == true) {
// При первом запуске запоминаем температуры как предыдущие
if (getMistakeFlag == false) {
for (uint8_t i = 0; i < 6; i++) {
TemperPrev[i] = Temper[i];
}
getMistakeFlag = true;
}
// Проверяем каждую температуру на разумные границы
// Параметры: номер датчика, мин.темп, макс.темп, макс.изменение
tempMistake(0, -100, 1200, 200); // Компрессор: от -10°C до 120°C
tempMistake(1, 0, 600, 100); // Вода вход: от 0°C до 60°C
tempMistake(2, 0, 900, 50); // Вода выход: от 0°C до 90°C
tempMistake(3, -400, 800, 400); // Доп.датчик: от -40°C до 80°C
tempMistake(4, -400, 600, 300); // Разморозка: от -40°C до 60°C
tempMistake(5, -400, 800, 200); // Испаритель: от -40°C до 80°C
// Считаем перегрев (разница между датчиками 3 и 5)
tOverheatReal = (Temper[3] - Temper[5]);
// ===== РАСЧЁТ РАСХОДА ВОДЫ =====
if (digitalRead(51) == HIGH) {
// Если насос выключен - расхода нет
gWaterReal = 0;
gWaterRealPrew = gWaterReal;
FlagWater = false;
} else if (digitalRead(51) == LOW && millis() - WaterStartTimer > 400000) {
// Если насос включён, но импульсов нет больше 400 секунд - ошибка
StopFlag = true;
StopTime = millis();
StopKind = 7; // Нет расхода воды
} else if (SecondImpulsWater == false) {
// Ещё нет двух импульсов - используем предыдущее значение
gWaterReal = gWaterRealPrew;
FlagWater = true;
} else {
// Есть два импульса - считаем расход
// Формула: 3600000 / время между импульсами
gWaterReal = round(3600000 / WaterCountTimer);
gWaterRealPrew = gWaterReal;
FlagWater = false;
}
// ===== РАСЧЁТ ЭЛЕКТРИЧЕСКОЙ МОЩНОСТИ =====
if (millis() - ElectrStartTimer > 60000) {
// Если импульсов нет больше 60 секунд - мощность 0
ElectrPowReal = 0;
} else {
// Считаем мощность: 72000 / время между импульсами
ElectrPowReal = 72000 / ElectrCountTimer;
}
// ===== РАСЧЁТ ТЕПЛОВОЙ МОЩНОСТИ =====
// Формула: разница температур * расход воды * 1163 / 10000
heatPowerReal = ((int32_t)Temper[2] - (int32_t)Temper[1]) * gWaterReal * 1163 / 10000;
// ===== РАСЧЁТ КОП (эффективности) =====
if (ElectrPowReal < 10) {
COPreal = 0; // Если электричества почти нет - КОП = 0
} else {
// КОП = тепловая мощность / электрическая мощность * 100
COPreal = 100 * heatPowerReal / ElectrPowReal;
}
// ===== СЧИТАЕМ СРЕДНИЕ ЗНАЧЕНИЯ =====
countHeat = countHeat + heatPowerReal;
countElectr = countElectr + ElectrPowReal;
countHeatAndElectr++; // Счётчик измерений
// ===== УПРАВЛЕНИЕ ВЕНТИЛЯТОРОМ ПРИ ПЕРЕГРЕВЕ =====
if (HeatOptionFlag == true && Temper[0] >= 850 && tOverheatReal >= 300 && digitalRead(48) == LOW) {
// Если температура компрессора >= 85°C и перегрев >= 30°C
tKompressorCounter++; // Увеличиваем счётчик
if (tKompressorCounter >= 4) { // Если 4 раза подряд
bitClear(relay, 0); // Выключаем вентилятор (бит 0 = 0)
tKompressorCounter = 0;
}
} else if (HeatOptionFlag == true && (Temper[0] <= 750 || tOverheatReal <= 200) && digitalRead(48) == HIGH) {
// Если температура <= 75°C или перегрев <= 20°C
tKompressorCounter++;
if (tKompressorCounter >= 2) { // Если 2 раза подряд
bitSet(relay, 0); // Включаем вентилятор (бит 0 = 1)
tKompressorCounter = 0;
}
} else {
tKompressorCounter = 0; // Сбрасываем счётчик
}
// ===== ОКОНЧАНИЕ РЕЖИМА НАГРЕВА =====
if (HeatOptionFlag == true && Temper[1] >= 360) {
// Если вода на входе >= 36°C
HeatOptionCout++;
if (HeatOptionCout >= 6) { // 6 раз подряд
HeatOptionFlag = false; // Выключаем нагрев
HeatOptionPauseFlag = true; // Включаем паузу
HeatOptionCout = 0;
HeatOptionPauseStartTime = millis(); // Запоминаем время
bitClear(relay, 2); // Выключаем компрессор
bitClear(relay, 0); // Выключаем вентилятор
}
} else {
HeatOptionCout = 0;
}
// ===== ЛОГИКА ПАУЗЫ =====
// Выключаем клапан через 2 минуты паузы
if (HeatOptionPauseFlag == true && digitalRead(49) == LOW && millis() - HeatOptionPauseStartTime >= 120000) {
bitClear(relay, 1); // Выключаем 4-ходовой клапан
}
// Включаем насос на 16 минут если нет расхода воды
if (HeatOptionPauseFlag == true && digitalRead(51) == HIGH && millis() - HeatOptionPauseStartTime >= 960000) {
if (digitalRead(51) == HIGH) {
WaterStartTimer = millis();
FirstImpulsWater = false;
SecondImpulsWater = false;
}
bitSet(relay, 3); // Включаем насос
HeatOptionPauseStartTime = millis();
}
// Выключаем насос через минуту работы
if (HeatOptionPauseFlag == true && digitalRead(51) == LOW && millis() - HeatOptionPauseStartTime >= 60000) {
bitClear(relay, 3); // Выключаем насос
HeatOptionPauseStartTime = millis();
}
// ===== ОКОНЧАНИЕ ПАУЗЫ =====
if (HeatOptionPauseFlag == true && Temper[1] <= 280) {
// Если вода остыла до 28°C
HeatOptionPauseCout++;
if (HeatOptionPauseCout >= 5) { // 5 раз подряд
HeatOptionPauseFlag = false; // Выключаем паузу
HeatOptionPauseCout = 0;
StartHeatTime = millis();
StartHeatFlag = true; // Начинаем нагрев заново
}
} else {
HeatOptionPauseCout = 0;
}
// ===== ЗАЩИТА: НЕТ КЛАПАНА =====
if (HeatOptionFlag == true && digitalRead(49) == HIGH) {
StopNo4xvalve++;
if (StopNo4xvalve >= 3) { // 3 раза подряд
bitClear(relay, 2); // Выключаем компрессор
StopFlag = true;
StopTime = millis();
StopKind = 1; // Причина: нет 4-ходового клапана
StopNo4xvalve = 0;
}
} else {
StopNo4xvalve = 0;
}
// ===== ЗАЩИТА: ПЕРЕГРЕВ КОМПРЕССОРА =====
if ((StartHeatFlag == true || HeatOptionFlag == true || HeatOptionPauseFlag == true || DefrostOptionFlag == true) && Temper[0] >= 1000) {
StopMaxTemp++;
if (StopMaxTemp >= 5) { // 5 раз подряд >= 100°C
StopFlag = true;
StopTime = millis();
StopKind = 2; // Причина: перегрев компрессора
StopMaxTemp = 0;
}
} else {
StopMaxTemp = 0;
}
// ===== ЗАЩИТА: ПРЕВЫШЕНИЕ ЭЛЕКТРИЧЕСКОЙ МОЩНОСТИ =====
if ((StartHeatFlag == true || HeatOptionFlag == true || HeatOptionPauseFlag == true || DefrostOptionFlag == true) && ElectrPowReal >= 151) {
StopMaxElectr++;
if (StopMaxElectr >= 6) { // 6 раз подряд >= 151
StopFlag = true;
StopTime = millis();
StopKind = 3; // Причина: превышение мощности
StopMaxElectr = 0;
}
} else {
StopMaxElectr = 0;
}
// ===== ЗАЩИТА: НИЗКАЯ ТЕМПЕРАТУРА НАГРЕВАТЕЛЯ =====
if ((StartHeatFlag == true || HeatOptionFlag == true || HeatOptionPauseFlag == true) && Temper[4] <= 50) {
StopMinTempHeater++;
if (StopMinTempHeater >= 3) { // 3 раза подряд <= 5°C
StopFlag = true;
StopTime = millis();
StopKind = 4; // Причина: низкая температура нагревателя
StopMinTempHeater = 0;
}
} else {
StopMinTempHeater = 0;
}
// ===== ЗАЩИТА: НЕТ ЭЛЕКТРИЧЕСТВА =====
if (digitalRead(50) == LOW && ElectrPowReal <= 10) {
StopNoElectric++;
if (StopNoElectric >= 6) { // 6 раз подряд
StopFlag = true;
StopTime = millis();
StopKind = 5; // Причина: нет электричества
StopNoElectric = 0;
}
} else {
StopNoElectric = 0;
}
// ===== ЛОГИКА СТАРТА НАГРЕВА =====
// Через 20 секунд включаем насос
if (StartHeatFlag == true && millis() - StartHeatTime >= 20000 && Temper[1] >= 50) {
if (digitalRead(51) == HIGH) {
WaterStartTimer = millis();
FirstImpulsWater = false;
SecondImpulsWater = false;
}
bitSet(relay, 3); // Включаем насос
}
// Через 30 секунд включаем вентилятор
if (StartHeatFlag == true && digitalRead(51) == LOW && millis() - StartHeatTime >= 30000 && Temper[0] <= 850) {
bitSet(relay, 0); // Включаем вентилятор
}
// Через 40 секунд включаем клапан
if (StartHeatFlag == true && digitalRead(51) == LOW && digitalRead(48) == LOW && millis() - StartHeatTime >= 40000) {
bitSet(relay, 1); // Включаем 4-ходовой клапан
}
// Через 45 секунд включаем компрессор
if (StartHeatFlag == true && digitalRead(51) == LOW && digitalRead(48) == LOW && digitalRead(49) == LOW && millis() - StartHeatTime >= 45000) {
bitSet(relay, 2); // Включаем компрессор
}
// Когда всё включено - переходим в рабочий режим
if (StartHeatFlag == true && digitalRead(51) == LOW && digitalRead(48) == LOW && digitalRead(50) == LOW && digitalRead(49) == LOW) {
StartHeatFlag = false; // Выключаем стартовый режим
HeatOptionFlag = true; // Включаем рабочий режим
}
// ===== АВАРИЙНАЯ ОСТАНОВКА =====
if (StopFlag == true) {
StartHeatFlag = false;
HeatOptionFlag = false;
HeatOptionPauseFlag = false;
DefrostOptionFlag = false;
bitClear(relay, 2); // Выключаем компрессор сразу
bitClear(relay, 0); // Выключаем вентилятор сразу
if (millis() - StopTime >= 120000) bitClear(relay, 1); // Клапан через 2 минуты
if (millis() - StopTime >= 180000) bitClear(relay, 3); // Насос через 3 минуты
}
// ===== РЕЖИМ РАЗМОРОЗКИ =====
if (DefrostOptionFlag == true) {
if (digitalRead(51) == HIGH) {
WaterStartTimer = millis();
FirstImpulsWater = false;
SecondImpulsWater = false;
}
bitSet(relay, 3); // Включаем насос
bitClear(relay, 2); // Выключаем компрессор
bitSet(relay, 0); // Включаем вентилятор
if (millis() - DefrostTime >= 120000) bitClear(relay, 1); // Выключаем клапан через 2 минуты
// Заканчиваем разморозку через 15 минут или при температуре >= 2°C
if (millis() - DefrostTime >= 900000 && Temper[5] >= 20) {
DefrostOptionFlag = false;
StartHeatTime = millis();
StartHeatFlag = true; // Переходим в стартовый режим
}
}
// ===== ВЫВОД ДАННЫХ НА ДИСПЛЕЙ =====
// Сначала стираем старые значения (заливаем чёрным)
tft.fillRect(74, 0, 30, 20, BLACK); // Место для температуры компрессора
tft.fillRect(181, 0, 30, 20, BLACK); // Место для температуры воды вход
tft.fillRect(289, 0, 30, 20, BLACK); // Место для температуры воды выход
tft.fillRect(74, 210, 30, 20, BLACK); // Место для тепловой мощности
tft.fillRect(181, 210, 30, 10, BLACK); // Место для КОП
tft.fillRect(289, 210, 30, 10, BLACK); // Место для электрической мощности
tft.fillRect(152, 220, 60, 10, BLACK); // Место для расхода воды
tft.fillRect(100, 470, 30, 8, BLACK); // Средняя тепловая мощность
tft.fillRect(200, 470, 30, 8, BLACK); // Средняя электрическая мощность
// Выводим новые значения
// Если датчик ошибается - выводим голубым, иначе - цветным
tft.setCursor(74, 0);
tft.setTextColor((MistakeTemp[0] > 0) ? CYAN : RED);
getDataInt10(Temper[0]); // Температура компрессора
tft.setCursor(181, 0);
tft.setTextColor((MistakeTemp[1] > 0) ? CYAN : YELLOW);
getDataInt10(Temper[1]); // Температура воды вход
tft.setCursor(289, 0);
tft.setTextColor((MistakeTemp[2] > 0) ? CYAN : GREEN);
getDataInt10(Temper[2]); // Температура воды выход
tft.setCursor(74, 10);
tft.setTextColor((MistakeTemp[3] > 0) ? CYAN : MAGENTA);
getDataInt10(tOverheatReal); // Перегрев
tft.setCursor(181, 10);
tft.setTextColor((MistakeTemp[4] > 0) ? CYAN : CYAN);
getDataInt10(Temper[4]); // Температура разморозки
tft.setCursor(289, 10);
tft.setTextColor((MistakeTemp[5] > 0) ? CYAN : RED);
getDataInt10(Temper[5]); // Температура испарителя
// Значения мощностей
tft.setCursor(74, 210); tft.setTextColor(RED); getDataInt100(heatPowerReal);
tft.setCursor(181, 210); tft.setTextColor(YELLOW); getDataInt100(COPreal);
tft.setCursor(289, 210); tft.setTextColor(GREEN); getDataInt100(ElectrPowReal);
tft.setCursor(74, 220);
tft.setTextColor(FlagWater ? CYAN : MAGENTA);
getDataInt100(gWaterReal);
// Средние значения
tft.setCursor(100, 470); tft.setTextColor(RED);
getDataInt100(countHeat / countHeatAndElectr); // Средняя тепловая мощность
tft.setCursor(200, 470); tft.setTextColor(GREEN);
getDataInt100(countElectr / countHeatAndElectr); // Средняя электрическая мощность
// Вывод режима работы
tft.setCursor(152, 220); tft.setTextColor(RED);
if (StopFlag == true) {
tft.print("Stop");
switch (StopKind) {
case 1: tft.print(" No 4x valve"); break; // Нет клапана
case 2: tft.print(" maxT Kompr"); break; // Перегрев компрессора
case 3: tft.print(" maxW Electr"); break; // Превышение мощности
case 4: tft.print(" minT Heater"); break; // Холодный нагреватель
case 5: tft.print(" No Electr"); break; // Нет электричества
case 6: tft.print(" Err Temp"); break; // Ошибка датчика
case 7: tft.print(" No G_water"); break; // Нет расхода воды
case 8: tft.print(" No Option"); break; // Нет режима
}
} else if (StartHeatFlag == true) {
tft.print("Start heat"); // Режим старта нагрева
} else if (HeatOptionFlag == true) {
tft.print("Heat option"); // Рабочий нагрев
} else if (HeatOptionPauseFlag == true) {
tft.print("Pause heat"); // Пауза
} else if (DefrostOptionFlag == true) {
tft.print("Defrost"); // Разморозка
} else {
StopFlag = true;
StopTime = millis();
StopKind = 8;
}
// Состояния реле (красный = включено, синий = выключено)
tft.setCursor(0, 440);
tft.setTextColor(bitRead(relay, 2) ? RED : BLUE);
tft.print("Kompressor");
tft.setCursor(0, 450);
tft.setTextColor(bitRead(relay, 3) ? RED : BLUE);
tft.print("Pump");
tft.setCursor(0, 460);
tft.setTextColor(bitRead(relay, 1) ? RED : BLUE);
tft.print("4 way valve");
tft.setCursor(0, 470);
tft.setTextColor(bitRead(relay, 0) ? RED : BLUE);
tft.print("Fan");
Flag3 = false; // Сбрасываем флаг шага 3
}
// ===== ШАГ 4: УПРАВЛЕНИЕ РЕЛЕ (9-10 секунд) =====
if (millis() - Timer >= 9000 && millis() - Timer < 10000 && Flag4 == true) {
// Применяем состояния реле к физическим пинам
// LOW - включено, HIGH - выключено (инверсная логика)
digitalWrite(48, bitRead(relay, 0) ? LOW : HIGH); // Вентилятор
digitalWrite(49, bitRead(relay, 1) ? LOW : HIGH); // Клапан
digitalWrite(50, bitRead(relay, 2) ? LOW : HIGH); // Компрессор
digitalWrite(51, bitRead(relay, 3) ? LOW : HIGH); // Насос
Flag4 = false; // Сбрасываем флаг
}
// ===== ШАГ 5: ОБНОВЛЕНИЕ ГРАФИКОВ (каждые 10 секунд) =====
if (millis() - Timer >= 10000) {
Timer = millis(); // Сбрасываем таймер
// Обновляем графики каждый 10-й цикл
if (countGetGrafic >= 9) {
// Сдвигаем все графики на 1 точку влево
// (старые данные уходят, освобождая место для новых)
for (uint16_t x = 0; x < 319; x++) {
tIsparitel[x] = tIsparitel[x + 1];
tOverheat[x] = tOverheat[x + 1];
tKompressor[x] = tKompressor[x + 1];
tWaterIn[x] = tWaterIn[x + 1];
tWaterOut[x] = tWaterOut[x + 1];
gWater[x] = gWater[x + 1];
heatPower[x] = heatPower[x + 1];
ElectrPow[x] = ElectrPow[x + 1];
COP[x] = COP[x + 1];
tDefrost[x] = tDefrost[x + 1];
}
// Добавляем новые значения в последнюю точку (справа)
tKompressor[319] = (uint8_t)getDataForUintMassiv(Temper[0]);
tWaterIn[319] = (uint8_t)getDataForUintMassiv(Temper[1]);
tWaterOut[319] = (uint8_t)getDataForUintMassiv(Temper[2]);
tOverheat[319] = (uint8_t)getDataForUintMassiv(tOverheatReal);
tDefrost[319] = (uint8_t)getDataForUintMassiv(Temper[4]);
tIsparitel[319] = (uint8_t)getDataForUintMassiv(Temper[5]);
heatPower[319] = (uint8_t)getDataForUintMassiv32(heatPowerReal);
COP[319] = (uint8_t)getDataForUintMassiv32(COPreal);
ElectrPow[319] = (uint8_t)getDataForUintMassiv32(ElectrPowReal);
gWater[319] = (uint8_t)getDataForUintMassiv32(gWaterReal);
// Обновляем историю состояний реле
for (uint8_t i = 0; i < 20; i++) {
trend_fan[i] = trend_fan[i] << 1; // Сдвигаем влево
bitWrite(trend_fan[i], 0, bitRead(trend_fan[i + 1], 15)); // Добавляем бит
trend_kompressor[i] = trend_kompressor[i] << 1;
bitWrite(trend_kompressor[i], 0, bitRead(trend_kompressor[i + 1], 15));
trend_pump[i] = trend_pump[i] << 1;
bitWrite(trend_pump[i], 0, bitRead(trend_pump[i + 1], 15));
trend_4way_valve[i] = trend_4way_valve[i] << 1;
bitWrite(trend_4way_valve[i], 0, bitRead(trend_4way_valve[i + 1], 15));
}
// Добавляем текущие состояния реле
bitWrite(trend_fan[19], 0, bitRead(relay, 0));
bitWrite(trend_kompressor[19], 0, bitRead(relay, 2));
bitWrite(trend_pump[19], 0, bitRead(relay, 3));
bitWrite(trend_4way_valve[19], 0, bitRead(relay, 1));
// Рисуем всё заново
getMainDisplay(); // Сетка и подписи
// Рисуем графики температур
getGraphics(245, RED, tKompressor); // Температура компрессора - красный
getGraphics(245, YELLOW, tWaterIn); // Вода вход - жёлтый
getGraphics(245, GREEN, tWaterOut); // Вода выход - зелёный
getGraphics(245, MAGENTA, tOverheat); // Перегрев - фиолетовый
getGraphics(245, CYAN, tDefrost); // Разморозка - голубой
getGraphics(245, RED, tIsparitel); // Испаритель снова красный
// Рисуем графики мощностей
getGraphics(475, RED, heatPower); // Тепловая мощность - красный
getGraphics(475, YELLOW, COP); // КОП - жёлтый
getGraphics(475, GREEN, ElectrPow); // Электрическая мощность - зелёный
getGraphics(475, MAGENTA, gWater); // Расход воды - фиолетовый
// Рисуем историю реле (жёлтые точки)
for (uint8_t i = 0; i < 20; i++) {
for (uint8_t j = 0; j < 16; j++) {
if (bitRead(trend_fan[i], j) == 1) tft.drawPixel((i * 16 + (15 - j)), 479, YELLOW);
if (bitRead(trend_kompressor[i], j) == 1) tft.drawPixel((i * 16 + (15 - j)), 449, YELLOW);
if (bitRead(trend_pump[i], j) == 1) tft.drawPixel((i * 16 + (15 - j)), 459, YELLOW);
if (bitRead(trend_4way_valve[i], j) == 1) tft.drawPixel((i * 16 + (15 - j)), 469, YELLOW);
}
}
countGetGrafic = 0; // Сбрасываем счётчик графиков
}
countGetGrafic++; // Увеличиваем счётчик
// Сбрасываем все флаги для нового цикла
Flag1 = true;
Flag2 = true;
Flag3 = true;
Flag4 = true;
}
}
при этом, предыдущий код, из из сообщения 58 загружался без появления подобной ошибки.
Когда загружается код без “плясок” наподобие добавления “Serial.println(“proverka”)” - мне радостно на душе. Вчера вот - не получилось этого. Подумал, следует сообщить. Вдруг это что-то значит.
Далее. После загрузки нового кода, некоторое время простоял рядом, понаблюдал
битые данные с датчика → temp * 10 >> 4 вычисляет мусорное значение температуры например, +127°C для WaterOutC
Логика управления видит: “Ага, температура воды на выходе дикая!” → срабатывает защита → контроллер меняет режим.
В режиме остановки или паузы насос выключается/включается по другим правилам.
Расход воды (G water) пересчитывается в этот момент некорректно — вы видите “вылет” на графике.
В новом коде (даже с тем багом) CRC молча отбрасывает битые данные.
но это не точно, вам самим думать надо…)))
может вам камеру уже поставить, и выяснить что происходит перед перезагрузкой ?)))
а то я что то не представляю как это иначе проделать… и может быть вывод показаний сделать чаще…
Вы явно злоупотребляете картинками.
Возможно, при исправной работе они и имеют какую-то ценность, но для отладки нужны текстовые логи, в которых кроме обработанных должны быть и “сырые” данные.
Почему не записать просто?
tft.setTextColor(CYAN);
Скажите, Вы изучали такую науку как Логика?
Если помните: из истинного выражения может следовать только истинное, а из ложного - как ложное, так и истинное.
В программировании примерно так же: правильная программа всегда работает правильно, а неправильная - когда как (в том числе иногда - правильно). Это аксиома, поэтому вопрос “почему моя неправильная программа в каком-то случае сработала правильно” не имеет смысла.
Не знаю для чего, но загрузчик у меги реагирует на последовательности в загружаемом коде. Если например в скетче будет строка с тремя восклицательными знаками подряд, то код не загрузится с похожими на симптомы у вас.
Какие ещё есть варианты последовательностей я не изучал …
Может и ваш скетч при компиляции формирует бинарник с важной для меги последовательностью кодов …
Как вариант - попробовать залить “проблемный” бинарник через программатор.
Срочно копишь 300-500р и бежишь на МП и покупаешь программатор для avr, ибо бутлоадеру пофик на прошивку а вот сама ФЛЕШЬ кристалла может уже УСТАЛА!!!
Лично я бы смастырил структурку вместе с id датчика и хранил там предидущее значение, считанное только что и количество ОШИБОК этого датчика.
У него же там усе-на-атипись, взяли данные, crc пофик, записали предидущее значение и прибавили ашипку, а дальше = ПОФИКДЫМ. )))
Прочитал сообщение от BABOS. Ну и, как часто это бывает, - мало что понял… только вот, испытал какие-то приятные эмоции. Если действительно, “вылеты” по “G water” - были связаны с “мусором” от датчиков температур - то в этом случае как бы, на душе становится легче. Как бы - появляется вера .
А то знаете… когда совсем что-то неведомое, и пытаешься менять провода, их расположение… невольно в голову приходят мысли “да во всём виновата эта китайская Ардуина, которая, тупо, - попалась мне с браком”… А потом, вот, начинаешь ковыряться - и оказывается, что это не китайская Ардуина виновата. Но… честно говоря - где расход воды, а где температура. Вроде как - совсем не связаны, на первый взгляд.
temp * 10 >> 4 было сделано только лишь для того, чтобы получить трёхначное число, которое в себе содержит градусы, до десятых. Мне просто далее по коду было удобно обрабатывать и при этом иметь точность до десятых градуса. Чаще всего в примерах скетчей по DS18B20 результат выводится в float. Но мне не нравится этот тип. С ним тяжелее дальше работать (сложно для усваивания). Вопрос: вся проблема в том, что получаю “целое в десятых”? Если перейти как штатно во float - проблема связанная с получением “мусора” - уйдёт?
Если сработает защита и тем более поменяется режим - то контроллер попадает в “Stop”-option. А вылетавшие ранее значения по “G water” - были при штатном режиме “Нагрев” - то есть, это было ещё до “контроллер меняет режим”.
То что насос включается и отключается по разным правилам в разных режимах - это понятно, само собой.
А вот при чём здесь “G water” - не понятно. Да, есть условие, что если насос выключен, то “G water” равно нулю. Но там - и не было никогда вылетов значений по “G water” (когда насос выключен).
Не понимаю. Побеждены “вылеты” на графике “G water”?
Это стёб? У меня нет камеры. Я могу конечно - купить её, либо одолжить у кого-то… но по-моему, Вы всё же пошутили.
У меня было ранее вывод показаний каждые 9 сек. При этом - и новые значения температур отображались и новая точка графика появлялась. Можно так сделать. Но тогда - на экране умещается 48 минут работы. И если “reset” произойдёт на 48 минут раньше чем я посмотрю на экран - то я даже и не узнаю, что был этот “reset”.
Сомнительно, но… ОКей (С) Тинькофф
Я могу её прятать под “спойлер”. Но пока что картинка - единственная возможность для меня отследить, что и когда случилось.
Как накапливать текстовые логи - я не умею, к сожалению… да, конечно, мне говорили здесь ранее что “сколько раз вошёл, в какую процедуру, где споткнулся…” - но я не знаю, как физически это сделать. Знаю только “Serial.println()” с подключённым проводом ноутбуком. Но на основе практики, как я наблюдал при отладки ранее - это дикость. Не успеет он зафиксировать входы и выходы в процедуры. Особенно в loop то что быстро повторяется.
Я так понимаю - снова стёб.
Понимаю. Но на другом уровне общаться - не дорос. Пытаюсь, как могу.
Где физически находится этот кристалл, который устал?
проблема получения мусора может быть при скачках напряжения, или наводок от проводов, или еще чего, crc решает эту проблему, вот для этого она там и есть что бы если что то случилось мы цифру 3 например не прочитали как 221, и не волновались о том что мы неправильно получили данные, и работали над логикой программы))) искали в ней причину неисправности
а мне вовсе не понятна ваша логика работы программы,(потому что до конца я в нее не вникал) это вы ее писали, так что касается логики, и режимов давайте вы попробуете с ними сами разобраться, я просто перенес их, а если что не так, вам надо их справлять
почему стеб то))) камера в телефоне есть, вы наводите на показания, и начинаете запись, при выходе из строя будите знать что происходило до этого… что бы увидеть данные до сбоя, что бы по ним попытаться понять что произошло там, что в итоге привело к перезагрузке или зависанию, вроде отличный способ, если сможете это сделать как то иначе, можно и по другому конечно реализовать…
собственно очень странно почему вы это не проделали раньше, но может вы это где то уже писали и я пропустил…
" а когда ты мне подмигнула, мне из под тяжка - я чуть не упал со стула = кошечка" (С) В. Асмолов
ну это вы, вы код скиньте если не лень, а если лень, можете воспользоваться тем что я скинул, и работать с ним, и искать причину там)))
Яб на есп32 токое сделал. на меге - бесперспективно!
надо сначала сделать, если не лень))) а если лень, надо копаться в чужом коде)))
и желательно сделать на меге, потому что с esp32 могут быть проблемы различные…
если вы уверенны что все таки сделаете все с 0, по своему, это надо наверное доказать, взять и сделать, а если опять же лениво делать, то можно просто попытаться помочь человеку который делает))) но делает он на меге а не на esp
здается мне, шо тут 328р хватит за_глаза.
Да и глупо просто так делать = сначала надо ЖЕЛЕЗО отполировать а уже потом код.
Попытки надеть трусы через голову могут иметь место быть - но не для меня.
Попытаюсь сейчас повставлять “Serial.print” везде. В любом случае - это разумнее, чем стоять рядом с включённой камерой телефона. С момента “reset” контроллер работает уже более 5 часов. И такие красивые графики давно не рисовал. Стоять столько с камерой рядом - это бред.
ну если все собрать, и протестировать, это слишком легко, и лениво собирать)))
я тут лично оттачиваю талант помогать без комплектующих, высший пилотаж! а вы похоже пишите о том что бы вы могли собрать)))
в любом случае скучно, просто убиваем время, и что то может поможет автору… но если писать лишние сообщения, наверное он совсем потеряется, или купит, но не факт что со своим опытом осилит… сейчас вон кажется сидит на паранойи, и ему кажется что вовсе не пытаются помочь, а только стебутся)))
да наверное, но я и не говорил вам с ней стоять рядом, я советовал заснять это все, и посмотреть что происходило до сбоя…