Можно вообще все сделать на
Вот после публикации набрал и спросил у ИИ.
можешь сделать схему платы для esp01 реле с добавкой датчика даллас?
Да, конечно! Сделать такую схему несложно, но важно учесть одну аппаратную особенность ESP-01: у него всего 4 свободных GPIO, и большинство из них участвуют в логике загрузки микроконтроллера.
Если вы используете популярный китайский готовый модуль реле для ESP-01 (черная или синяя платка, куда вставляется ESP-01), то реле там управляется через пин GPIO0. Лучшим и самым безопасным выбором для подключения датчика Dallas (DS18B20) остается пин GPIO2. [1, 2, 3]
Ниже представлена текстовая схема и схема подключения для этого случая.
Схема подключений (Распиновка)
Для работы датчика Dallas DS18B20 обязательно нужен подтягивающий резистор на 4.7 кОм (или близкий, например, 10 кОм) между линиями данных (DQ) и питания (3.3V). [4, 5]
| Компонент | Вывод компонента | Куда подключать на ESP-01 / плате реле | Примечание |
|---|---|---|---|
| Реле (уже распаяно) | Вход управления (In) | GPIO0 | Включается логическим нулем или единицей (зависит от версии платы v1.0 или v4.0). |
| Датчик DS18B20 | GND (Черный) | GND | Общая земля. |
| Датчик DS18B20 | VDD / VCC (Красный) | 3.3V | Важно: Питать строго от 3.3V, чтобы не повредить GPIO ESP8266. |
| Датчик DS18B20 | DQ / DATA (Желтый) | GPIO2 | Сигнальный провод. |
| Резистор 4.7 кОм | Вывод 1 | GPIO2 (к датчику DQ) | Подтяжка шины 1-Wire. |
| Резистор 4.7 кОм | Вывод 2 | 3.3V (к питанию датчика) |
Принципиальная текстовая схема
Если вы собираете плату с нуля самостоятельно (на макетке или текстолите), вам также нужно правильно обвязать сам чип ESP-01, чтобы он стабильно запускался:
+-----------------------+
| ESP-01 (Вид сверху) |
| |
GND ----| [1] GND TX [5] |---- (Для прошивки)
GPIO2 ----| [2] GPIO2 CH [6] |---- [Резистор 10кОм] ---- 3.3V (Обязательно для старта!)
(К реле) ----| [3] GPIO0 |
3.3V ----| [4] VCC RX [8] |---- (Для прошивки)
+-----------------------+
| |
| +---------+
| |
v v
[Датчик DS18B20] [Модуль Реле]
+--------------+ +--------------+
| GND DQ VDD| | GND IN VCC|
+--------------+ +--------------+
| | | | | |
GND --------+ | +--- 3.3V -----+ | +--- 5V/3.3V (Питание реле)
| | |
+----+ |
[Резистор 4.7к] +---- (к GPIO0)
Важные нюансы при сборке
- Логика загрузки ESP8266: Пин GPIO2 во время включения ESP-01 обязательно должен быть подтянут к питанию (HIGH), иначе модуль уйдет в режим ошибки и не загрузится. Подтягивающий резистор 4.7 кОм для датчика Dallas идеально выполняет эту роль, поэтому система будет стартовать стабильно. [2]
- Питание: Датчик DS18B20 может работать и от 5V, но не подключайте резистор подтяжки к 5V! Порты ESP-01 рассчитаны строго на 3.3V. Подтягивайте датчик только к линии 3.3V. [6, 7, 8]
- Готовые платы реле: Если вы подпаиваетесь к готовому китайскому модулю реле, найдите на нем разъем, куда вставляется ESP-01. Вам достаточно припаять резистор и датчик прямо к ножкам
GPIO2,GNDи3.3Vэтого разъема с обратной стороны платы. [9]
Подскажите, вы планируете разводить собственную печатную плату (например, в EasyEDA / KiCad) или хотите доработать готовый модуль реле? Я могу подсказать, как правильно организовать питание всей схемы, или помочь с кодом для опроса датчика и реле.
[3] https://randomnerdtutorials.com
[4] https://diotlabs.daraghbyrne.me
[5] https://www.circuits-diy.com
[9] https://community.home-assistant.io
дорабатываем готовый модуль.
Отличный выбор, это сэкономит много времени. Модификация готового модуля сводится к аккуратной подпайке трех проводов датчика и одного резистора с обратной стороны платы.
Ниже приведена пошаговая инструкция, как сделать это быстро и надежно.
Что понадобится
- Датчик DS18B20 (в виде транзистора TO-92 или герметичный с проводами).
- Резистор номиналом 4.7 кОм (подойдет выводной 0.25 Вт или SMD).
- Выводы разъема ESP-01 (будем паять к ножкам со стороны дорожек).
Распиновка разъема ESP-01 на плате (Вид снизу / Со стороны пайки)
Переверните плату реле дорожками к себе. Найдите 8 контактов разъема, куда вставляется ESP-01. Ориентируйтесь по этой схеме:
[Край платы реле]
+-----------+
| RX VCC | <-- 3.3V (Питание)
| GPIO0 RST |
| GPIO2 CH | <-- GPIO2 (Данные датчика)
| TX GND | <-- GND (Земля)
+-----------+
Пошаговый алгоритм доработки
- Подготовка резистора: Возьмите резистор 4.7 кОм. Если он выводной, укоротите ножки.
- Монтаж резистора подтяжки: Припаяйте резистор с обратной стороны платы прямо между контактами GPIO2 и VCC (3.3V) разъема ESP-01.
- Подключение датчика Dallas:
- GND датчика (черный провод) припаяйте к контакту GND.
- VDD датчика (красный провод) припаяйте к контакту VCC (туда же, где ножка резистора).
- DQ / Data датчика (желтый/белый провод) припаяйте к контакту GPIO2 (туда же, где вторая ножка резистора).
Совет: Чтобы провода датчика не оторвались от платы при использовании, зафиксируйте их каплей горячего клея или стяжкой у края модуля.
Важная проверка перед включением
Китайские модули реле для ESP-01 бывают двух популярных версий (v1.0 и v4.0). Проверьте, какая у вас:
- Версия без оптопары (обычно синяя или старая черная): Работает напрямую. GPIO0 выдает
LOW— реле включается. При включении ESP-01 реле может кратковременно щелкать. - Версия с оптопарой и контроллером (обычно v4.0 черная): На ней стоит дополнительный маленький чип (STC MCU). Он ждет от ESP-01 специальную шестнадцатеричную команду по серийному порту (
0xA0 0x01 0x01 0xA2для включения). Если у вас такая плата, управлять реле через простойdigitalWrite(0, HIGH)не получится — нужно будет инициализировать Serial на скорости 9600.
Если вы уже знаете версию вашей платы (или на ней есть маленький 8-ногий чип помимо реле), скажите мне. Я могу сразу написать для вас готовый скетч для Arduino IDE или конфигурацию для ESPHome, которая учитывает особенности вашей платы и корректно считывает температуру с датчика.
И итогом можно не иметь центральной платы управления. Все можно просто допаять на модуль и прошивать. Они свяжутся между собой и будут управление.
Я, так-то, не против модульности (только на кой она нужна в единичном, конкретном изделии), да вот отзывов по этим модулям о неработоспособности очень много было раньше (лет 5 назад). Если уж делать отдельные модули, то на … барабанная дробь … на rs485 или CAN ![]()
Владыка 485 @WladDrakula , к тебе взываем. Тут модули хотят обмотать проводами, чтобы побольше было.
Чтобы потом не разбирать и все не переделывать. Без модульности, про… схемы и прошивки, и забывчивости создателя - это просто выкинут.
А по модульной схеме на esp01 relay модулями с датчиками всего то нужно сделать прошивку, где он будет передавать всем температуру и по датчику и по приходящей от других модулей температуре - выполнять действие, а именно - включать свое реле или выключать.
Даешь программирование таких гномиков!
Вот описание для esp01, где датчик подключается к GPIO 2, а реле GPIO 0.
В этой версии прошивки для Arduino IDE полностью переработана логика управления и сетевой обмен: алгоритм усреднения температур удален. Вместо него внедрена система логических имен и точечной обработки данных от конкретных устройств.
Теперь каждый модуль имеет свое Имя модуля (например, Kotelnaya, Gostinaya), а его физический термодатчик имеет Имя датчика (например, FloorTemp, AirTemp).
Изменение сетевого протокола (без JSON)
Поскольку формат JSON был исключен ранее для экономии памяти ESP-01 (1 МБ), а текстовые имена передавать необходимо, теперь данные упаковываются по UDP в виде компактной бинарной структуры фиксированной длины (40 байт):
- Байты 0–15: Имя модуля (строка до 15 символов + нулевой байт)
- Байты 16–31: Имя датчика (строка до 15 символов + нулевой байт)
- Байты 32–35: Температура (тип
float, 4 байта) - Байты 36–39: Текущее состояние реле (тип
int, 4 байта)
Новая логика обработки и новые настройки в WEB
Теперь в веб-интерфейсе вместо абстрактных «весов» появились конкретные поля:
- Имя этого модуля — текстовый идентификатор устройства в сети (до 15 символов).
- Имя этого датчика — текстовый идентификатор установленного на нем датчика DS18B20.
- Режим логики контроля — выпадающий список из трех вариантов:
- «Только свой датчик» — модуль работает автономно и управляет реле исключительно по показаниям локального DS18B20. При этом он продолжает транслировать свои данные в сеть для других участников.
- «Любой из датчиков в сети» — реле сработает, если хотя бы один из активных датчиков в сети (включая свой собственный) потребует включения по уставке и гистерезису.
- «Специфичный датчик» — модуль будет полностью игнорировать показания своего датчика (для реле) и переключит реле только на основании данных от конкретного удаленного датчика, имя которого вы введете в поле ниже.
- Имя целевого датчика контроля — поле ввода, которое становится активным при выборе режима «Специфичный датчик». Модуль ищет в UDP-эфире пакет, где совпадает имя датчика, и берет его температуру за основу.
Обновленная структура WEB-интерфейса
На главной странице (http://[IP_АДРЕС_МОДУЛЯ]) теперь отображаются:
- Блок статуса: Отображает имя текущего модуля, имя локального датчика, его текущую температуру, а также имя и температуру того датчика, который в данный момент управляет локальным реле.
- Сводная таблица сети: Динамический список всех обнаруженных в Wi-Fi сети устройств. Столбцы:
IP-адрес|Имя модуля|Имя датчика|Температура (°C)|Статус реле. - Блок конфигурации: Все текстовые поля для настройки имен, выбора режимов работы, ввода уставки, гистерезиса и типа инверсии реле с кнопкой сохранения в энергонезависимую память
LittleFS.
Алгоритм безопасности при потере связи
- Если выбран режим «Специфичный датчик», а удаленный модуль с этим датчиком не присылал UDP-пакеты более 60 секунд (выключился, пропал Wi-Fi), модуль считает связь потерянной. Реле в целях безопасности автоматически выключается, а на веб-интерфейсе выводится предупреждение об аварии связи.
- При физическом обрыве локального датчика (
-127°C) реле также принудительно отключается, чтобы не допустить неконтролируемой работы исполнительного устройства.
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <ESP8266WebServer.h>
#include <LittleFS.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// --- Настройки аппаратной части ---
#define RELAY_PIN 0 // GPIO0 управляет реле
#define ONE_WIRE_BUS 2 // GPIO2 вход датчика Dallas DS18B20
#define UDP_PORT 4210 // Порт для обмена данными в сети
// --- Структуры данных ---
// Структура сетевого пакета (ровно 40 байт, без JSON)
struct __attribute__((packed)) MeshPacket {
char moduleName[16];
char sensorName[16];
float temperature;
int relayState;
};
// Структура для хранения данных о соседних модулях в ОЗУ
struct RemoteDevice {
IPAddress ip;
char moduleName[16];
char sensorName[16];
float temperature;
int relayState;
unsigned long lastSeen;
bool active;
};
#define MAX_DEVICES 20
RemoteDevice remoteDevices[MAX_DEVICES];
// Настройки, сохраняемые в LittleFS
struct Config {
char wifi_ssid[32] = "";
char wifi_pass[32] = "";
char module_name[16] = "ESP_Node";
char sensor_name[16] = "LocalTemp";
char target_sensor[16] = "TargetTemp";
int logic_mode = 0; // 0: Только свой, 1: Любой в сети, 2: Специфичный датчик
int relay_mode = 0; // 0: Нагрев, 1: Охлаждение
int relay_inv = 1; // 0: Прямая логика (High=ВКЛ), 1: Инверсная (Low=ВКЛ)
float target_temp = 25.0;
float hysteresis = 0.5;
} config;
// --- Глобальные объекты ---
ESP8266WebServer server(80);
WiFiUDP udp;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
float localTemperature = -127.0;
bool localSensorError = true;
unsigned long lastUdpSend = 0;
unsigned long lastSensorRead = 0;
int currentRelayState = 0;
bool networkAlert = false;
// --- Функции работы с памятью (LittleFS) ---
void loadConfig() {
if (!LittleFS.begin()) return;
if (LittleFS.exists("/config.bin")) {
File configFile = LittleFS.open("/config.bin", "r");
if (configFile) {
configFile.read((uint8_t*)&config, sizeof(config));
configFile.close();
}
}
}
void saveConfig() {
File configFile = LittleFS.open("/config.bin", "w");
if (configFile) {
configFile.write((uint8_t*)&config, sizeof(config));
configFile.close();
}
}
// --- Управление физическим реле ---
void setRelay(int state) {
currentRelayState = state;
if (config.relay_inv == 1) {
digitalWrite(RELAY_PIN, state == 1 ? LOW : HIGH);
} else {
digitalWrite(RELAY_PIN, state == 1 ? HIGH : LOW);
}
}
// --- Обработка списка устройств ---
void updateRemoteDevice(IPAddress ip, MeshPacket packet) {
int emptySlot = -1;
for (int i = 0; i < MAX_DEVICES; i++) {
if (remoteDevices[i].active && strcmp(remoteDevices[i].sensorName, packet.sensorName) == 0 && strcmp(remoteDevices[i].moduleName, packet.moduleName) == 0) {
remoteDevices[i].temperature = packet.temperature;
remoteDevices[i].relayState = packet.relayState;
remoteDevices[i].lastSeen = millis();
remoteDevices[i].ip = ip;
return;
}
if (!remoteDevices[i].active && emptySlot == -1) {
emptySlot = i;
}
}
if (emptySlot != -1) {
remoteDevices[emptySlot].active = true;
remoteDevices[emptySlot].ip = ip;
strncpy(remoteDevices[emptySlot].moduleName, packet.moduleName, 16);
strncpy(remoteDevices[emptySlot].sensorName, packet.sensorName, 16);
remoteDevices[emptySlot].temperature = packet.temperature;
remoteDevices[emptySlot].relayState = packet.relayState;
remoteDevices[emptySlot].lastSeen = millis();
}
}
void checkDeadDevices() {
unsigned long now = millis();
for (int i = 0; i < MAX_DEVICES; i++) {
if (remoteDevices[i].active && (now - remoteDevices[i].lastSeen > 60000)) {
remoteDevices[i].active = false; // Удаляем устройство, если от него нет вестей 60 секунд
}
}
}
// --- Логика термостата ---
void processThermostatLogic() {
if (localSensorError) {
setRelay(0); // Аварийное отключение при поломке локального датчика
return;
}
float controlTemp = localTemperature;
bool validSensorFound = false;
networkAlert = false;
if (config.logic_mode == 0) { // Только свой датчик
controlTemp = localTemperature;
validSensorFound = true;
}
else if (config.logic_mode == 1) { // Любой из датчиков сети (включая свой)
bool needAction = false;
// Проверяем свой
if (config.relay_mode == 0 && localTemperature < (config.target_temp - config.hysteresis)) needAction = true;
if (config.relay_mode == 1 && localTemperature > (config.target_temp + config.hysteresis)) needAction = true;
// Проверяем чужие
for (int i = 0; i < MAX_DEVICES; i++) {
if (remoteDevices[i].active && remoteDevices[i].temperature > -127.0) {
if (config.relay_mode == 0 && remoteDevices[i].temperature < (config.target_temp - config.hysteresis)) needAction = true;
if (config.relay_mode == 1 && remoteDevices[i].temperature > (config.target_temp + config.hysteresis)) needAction = true;
}
}
// В децентрализованном режиме "любой" проверяем коридор отключения отдельно
if (needAction) {
setRelay(1);
} else {
// Отключаем, только если ВСЕ вышли из критической зоны перегрева/переохлаждения
bool safeToTurnOff = true;
if (config.relay_mode == 0 && localTemperature < (config.target_temp + config.hysteresis)) safeToTurnOff = false;
if (config.relay_mode == 1 && localTemperature > (config.target_temp - config.hysteresis)) safeToTurnOff = false;
for (int i = 0; i < MAX_DEVICES; i++) {
if (remoteDevices[i].active && remoteDevices[i].temperature > -127.0) {
if (config.relay_mode == 0 && remoteDevices[i].temperature < (config.target_temp + config.hysteresis)) safeToTurnOff = false;
if (config.relay_mode == 1 && remoteDevices[i].temperature > (config.target_temp - config.hysteresis)) safeToTurnOff = false;
}
}
if (safeToTurnOff) setRelay(0);
}
return;
}
else if (config.logic_mode == 2) { // Специфичный датчик
for (int i = 0; i < MAX_DEVICES; i++) {
if (remoteDevices[i].active && strcmp(remoteDevices[i].sensorName, config.target_sensor) == 0) {
controlTemp = remoteDevices[i].temperature;
validSensorFound = true;
break;
}
}
if (!validSensorFound) {
networkAlert = true;
setRelay(0); // Авария связи — выключаем нагрузку
return;
}
}
if (validSensorFound) {
if (config.relay_mode == 0) { // Нагрев
if (controlTemp < (config.target_temp - config.hysteresis)) setRelay(1);
else if (controlTemp > (config.target_temp + config.hysteresis)) setRelay(0);
} else { // Охлаждение
if (controlTemp > (config.target_temp + config.hysteresis)) setRelay(1);
else if (controlTemp < (config.target_temp - config.hysteresis)) setRelay(0);
}
}
}
// --- WEB-интерфейс (HTML генерация) ---
void handleRoot() {
String html = "<!DOCTYPE html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>ESP-01 Mesh Node</title>";
html += "<style>body{font-family:sans-serif;margin:20px;background:#f0f2f5;color:#333;} .card{background:white;padding:20px;border-radius:8px;box-shadow:0 2px 4px rgba(0,0,0,0.1);margin-bottom:20px;} h2{margin-top:0;} table{width:100%;border-collapse:collapse;margin:15px 0;} th,td{border:1px solid #ddd;padding:8px;text-align:left;} th{background:#f8f9fa;} .btn{background:#007bff;color:white;border:0;padding:10px 15px;border-radius:4px;cursor:pointer;} .alert{color:red;font-weight:bold;}</style></head><body>";
html += "<h1>Модуль: " + String(config.module_name) + "</h1>";
// Карточка текущего состояния
html += "<div class='card'><h2>Текущий статус</h2>";
html += "<p>Локальный датчик (<b>" + String(config.sensor_name) + "</b>): " + (localSensorError ? "<span class='alert'>ОШИБКА ДАТЧИКА</span>" : String(localTemperature) + " °C") + "</p>";
html += "<p>Состояние реле: <b>" + String(currentRelayState == 1 ? "ВКЛЮЧЕНО" : "ВЫКЛЮЧЕНО") + "</b></p>";
if (networkAlert) html += "<p class='alert'>ВНИМАНИЕ: Целевой датчик [" + String(config.target_sensor) + "] не найден в сети! Реле аварийно отключено.</p>";
html += "</div>";
// Таблица сети
html += "<div class='card'><h2>Устройства в сети (UDP Mesh)</h2>";
html += "<table><tr><th>IP Адрес</th><th>Имя модуля</th><th>Имя датчика</th><th>Температура</th><th>Реле</th></tr>";
for (int i = 0; i < MAX_DEVICES; i++) {
if (remoteDevices[i].active) {
html += "<tr><td>" + remoteDevices[i].ip.toString() + "</td><td>" + String(remoteDevices[i].moduleName) + "</td><td>" + String(remoteDevices[i].sensorName) + "</td><td>" + String(remoteDevices[i].temperature) + " °C</td><td>" + String(remoteDevices[i].relayState == 1 ? "ВКЛ" : "ВЫКЛ") + "</td></tr>";
}
}
html += "</table></div>";
// Форма настроек
html += "<div class='card'><h2>Настройки</h2><form action='/save' method='POST'>";
html += "<p>Wi-Fi SSID:<br><input type='text' name='ssid' value='" + String(config.wifi_ssid) + "' required></p>";
html += "<p>Wi-Fi Пароль:<br><input type='password' name='pass' value='" + String(config.wifi_pass) + "'></p>";
html += "<p>Имя этого модуля:<br><input type='text' name='m_name' maxlength='15' value='" + String(config.module_name) + "' required></p>";
html += "<p>Имя локального датчика:<br><input type='text' name='s_name' maxlength='15' value='" + String(config.sensor_name) + "' required></p>";
html += "Режим логики контроля:"; html += "<option value='0'" + String(config.logic_mode == 0 ? " selected" : "") + ">0: Только свой датчик"; html += "<option value='1'" + String(config.logic_mode == 1 ? " selected" : "") + ">1: Любой из датчиков в сети"; html += "<option value='2'" + String(config.logic_mode == 2 ? " selected" : "") + ">2: Специфичный датчик"; html += ""; html += "Имя целевого датчика контроля (для режима 2):"; html += "Целевая температура (°C):"; html += "Гистерезис (°C):"; html += "Режим работы:"; html += "<option value='0'" + String(config.relay_mode == 0 ? " selected" : "") + ">Нагрев"; html += "<option value='1'" + String(config.relay_mode == 1 ? " selected" : "") + ">Охлаждение"; html += ""; html += "Тип реле (Железо):"; html += "<option value='1'" + String(config.relay_inv == 1 ? " selected" : "") + ">Инверсное (Low = ВКЛ, типично для ESP-01)"; html += "<option value='0'" + String(config.relay_inv == 0 ? " selected" : "") + ">Прямое (High = ВКЛ)"; html += ""; html += ""; html += ""; server.send(200, "text/html", html);
} void handleSave() {
if (server.method() == HTTP_POST) {
strncpy(config.wifi_ssid, server.arg("ssid").c_str(), 32);
strncpy(config.wifi_pass, server.arg("pass").c_str(), 32);
strncpy(config.module_name, server.arg("m_name").c_str(), 16);
strncpy(config.sensor_name, server.arg("s_name").c_str(), 16);
strncpy(config.target_sensor, server.arg("t_sensor").c_str(), 16);
config.logic_mode = server.arg("l_mode").toInt();
config.relay_mode = server.arg("r_mode").toInt();
config.relay_inv = server.arg("r_inv").toInt();
config.target_temp = server.arg("t_temp").toFloat();
config.hysteresis = server.arg("hyst").toFloat();
saveConfig();
server.send(200, "text/html", "Настройки сохранены! Модуль перезагружается...setTimeout(function(){window.location.href='/';},5000);");
delay(2000);
ESP.restart();
}
}
// --- Главные циклы инициализации и работы ---
void setup() {
// Настройка пина реле на выход в безопасном состоянии
loadConfig();
setRelay(0);
pinMode(RELAY_PIN, OUTPUT);
sensors.begin();
// Пытаемся подключиться к Wi-Fi
WiFi.mode(WIFI_STA);
if (String(config.wifi_ssid).length() > 0) {
WiFi.begin(config.wifi_ssid, config.wifi_pass);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
delay(500);
attempts++;
}
}
// Если не подключились — поднимаем конфигурационную точку доступа
if (WiFi.status() != WL_CONNECTED) {
WiFi.mode(WIFI_AP);
WiFi.softAP("ESP-01-Config-AP", "12345678");
} else {
udp.begin(UDP_PORT);
}
// Маршрутизация веб-сервера
server.on("/", handleRoot);
server.on("/save", handleSave);
server.begin();
}
void loop() {
server.handleClient();
if (WiFi.status() == WL_CONNECTED) {
unsigned long now = millis();
// 1. Опрос физического датчика (раз в 5 секунд)
if (now - lastSensorRead > 5000 || lastSensorRead == 0) {
lastSensorRead = now;sensors.requestTemperatures();
float temp = sensors.getTempCByIndex(0);
if (temp == DEVICE_DISCONNECTED_C || temp < -50.0) {
localSensorError = true;
localTemperature = -127.0;
} else {
localSensorError = false;
localTemperature = temp;
}
checkDeadDevices();
processThermostatLogic();
}
// 2. Рассылка своей температуры по UDP Broadcast (раз в 10 секунд)
if (now - lastUdpSend > 10000 || lastUdpSend == 0) {
lastUdpSend = now;
MeshPacket outPacket;
memset(&outPacket, 0, sizeof(MeshPacket));
strncpy(outPacket.moduleName, config.module_name, 15);
strncpy(outPacket.sensorName, config.sensor_name, 15);
outPacket.temperature = localTemperature;
outPacket.relayState = currentRelayState;
IPAddress broadcastIP = WiFi.localIP();
broadcastIP[3] = 255;
// Формируем широковещательный адрес подсети
udp.beginPacket(broadcastIP, UDP_PORT);
udp.write((uint8_t*)&outPacket, sizeof(MeshPacket));
udp.endPacket();
}
// 3. Чтение входящих UDP пакетов от соседей
int packetSize = udp.parsePacket();
if (packetSize == sizeof(MeshPacket)) {
MeshPacket inPacket;
udp.read((uint8_t*)&inPacket, sizeof(MeshPacket));
updateRemoteDevice(udp.remoteIP(), inPacket);processThermostatLogic();
// Мгновенно реагируем на новые сетевые данные
}}}
}
}
}
у меня вроде валяются где то с 1 и 2 реле, насколько помню для подключения к пинам пришлось припаять провод и соединить с реле)))
всего пинов там 2 ? кроме rx tx(кстате какое у них поведение ?) будет ли какое соединение плат вывод и их управление из 1го места ? и если да какой протокол, esp now например ? или другое, и памяти хватит ?))) а если датчик температуры еще подключить с библиотекой…
По итогу собираем 2 модуля. Настраиваем и проверяем. Далее наращиваем. На каждом этапе можно на отладочной плате настроить работу esp01, а потом воткнуть в модуль реле.
И еще. Схема не зависит от количества датчиков и реле. Каждый элемент может быть перестроен отдельно, без остаеовки работы всего сразу.
как вы уже смогли убедиться я лентяй, у которого возникли вопросы, и сомнения в том что вообще надо делать на них, но если они потянут все это тогда вроде как и можно пытаться так что то сделать… но как лентяй не думаю что в итоге будет успех, и проверять лень…)))
Diskless или вы уже все проверили и они все потянут ?
Вот специально объясняю тебе. Модуль снимает показание датчика, отправляет остальным, далее проверяет прием и нужное пока сохраняет в памяти. Затем приступает к анализу. Если надо, то включает или выключает реле.
Что конкретно не понятно?
спс
допустим их будет снимать esp32 и на ней будет логика
не понятно хватит ли памяти у гномика, что бы общаться с esp32 и на каком протоколе…
не понятно проверили ли вы уже основу кода, или только предполагаете в ближайшее время проверить…
так же хочу заметить что в этот раз я код смотрел! официально заявляю о лишних скобках, с ними код не компилируется! и жду косарь на адрики, потому что сегодня я работал! код смотрел, и не ленился!
так же я не могу найти у себя плату esp 01, подскажите что бы не качать новое ядро, указать generic esp8266 module можно ?
. Variables and constants in RAM (global, static), used 32940 / 80192 bytes (41%)
║ SEGMENT BYTES DESCRIPTION
╠══ DATA 1636 initialized variables
╠══ RODATA 4048 constants
╚══ BSS 27256 zeroed variables
. Instruction RAM (IRAM_ATTR, ICACHE_RAM_ATTR), used 59867 / 65536 bytes (91%) Troubles
║ SEGMENT BYTES DESCRIPTION
╠══ ICACHE 32768 reserved space for flash instruction cache
╚══ IRAM 27099 code in IRAM
. Code in flash (default, ICACHE_FLASH_ATTR), used 304784 / 1048576 bytes (29%)
║ SEGMENT BYTES DESCRIPTION
╚══ IROM 304784 code in flash
воо даже проверил сам! во скока это весит, да это вы лентяй похоже а не я…
вникаю дальше без загрузки, а смотрю на пк интерфейс… подскажите я правильно его воспроизвел ?)))
пытаюсь понять как если что устройствами управлять в ручном режиме…
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP-01 Mesh Thermostat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f0f2f5;
padding: 20px;
color: #333;
}
.container { max-width: 1200px; margin: 0 auto; }
h1 {
text-align: center;
color: #1a1a2e;
margin-bottom: 30px;
font-size: 28px;
}
.card {
background: white;
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.card h2 {
color: #1a1a2e;
font-size: 20px;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid #e8ecf1;
}
.status-item {
background: #f8f9fa;
padding: 16px;
border-radius: 8px;
margin-bottom: 12px;
}
.status-item .label {
font-size: 14px;
color: #888;
font-weight: 500;
}
.status-item .value {
font-size: 22px;
font-weight: bold;
margin-top: 4px;
}
.value .temp { color: #e74c3c; }
.value .relay-on { color: #27ae60; }
.value .relay-off { color: #95a5a6; }
.value .error { color: #e74c3c; font-size: 16px; }
.alert {
background: #fde8e8;
border-left: 4px solid #e74c3c;
padding: 12px 16px;
border-radius: 4px;
color: #c0392b;
font-weight: 500;
margin-top: 12px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 12px;
}
table th {
background: #f8f9fa;
text-align: left;
padding: 12px;
font-size: 13px;
font-weight: 600;
color: #666;
border-bottom: 2px solid #e8ecf1;
}
table td {
padding: 12px;
border-bottom: 1px solid #e8ecf1;
}
table tr:hover { background: #f8f9fa; }
.badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.badge-on { background: #d5f5e3; color: #27ae60; }
.badge-off { background: #ebedef; color: #7f8c8d; }
.empty-state { text-align: center; padding: 32px; color: #999; }
.last-update {
text-align: right;
font-size: 12px;
color: #999;
margin-top: 12px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
font-weight: 600;
margin-bottom: 6px;
font-size: 14px;
color: #555;
}
.form-group input, .form-group select {
width: 100%;
padding: 10px 14px;
border: 2px solid #dce0e6;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s;
background: white;
}
.form-group input:focus, .form-group select:focus {
outline: none;
border-color: #3498db;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.btn {
background: #3498db;
color: white;
border: none;
padding: 12px 32px;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.3s;
width: 100%;
}
.btn:hover { background: #2980b9; }
@media (max-width: 768px) {
.form-row { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
.status-item .value { font-size: 18px; }
}
</style>
</head>
<body>
<div class="container">
<h1>🌡️ ESP-01 Mesh Thermostat</h1>
<!-- Карточка статуса (ТОЧНО как в коде ESP) -->
<div class="card" id="statusCard">
<h2>📊 Текущий статус</h2>
<div id="statusContent">
<div class="status-item">
<div class="label">Модуль</div>
<div class="value" id="moduleName">Загрузка...</div>
</div>
<div class="status-item">
<div class="label">Локальный датчик</div>
<div class="value" id="localTemp">-- °C</div>
</div>
<div class="status-item">
<div class="label">Состояние реле</div>
<div class="value" id="relayState">--</div>
</div>
</div>
<div id="alertContainer"></div>
<div class="last-update" id="lastUpdate">Обновлено: --</div>
</div>
<!-- Таблица устройств (ТОЧНО как в коде ESP) -->
<div class="card">
<h2>🌐 Устройства в сети (UDP Mesh)</h2>
<div style="overflow-x: auto;">
<table>
<thead>
<tr>
<th>IP Адрес</th>
<th>Имя модуля</th>
<th>Имя датчика</th>
<th>Температура</th>
<th>Реле</th>
</tr>
</thead>
<tbody id="devicesTable">
<tr>
<td colspan="5" class="empty-state">Нет устройств в сети</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Форма настроек (ТОЧНО как в коде ESP с исправленными полями) -->
<div class="card">
<h2>⚙️ Настройки</h2>
<form id="settingsForm" action="/save" method="POST">
<div class="form-group">
<label>Wi-Fi SSID</label>
<input type="text" name="ssid" id="ssid" placeholder="Введите имя WiFi" required>
</div>
<div class="form-group">
<label>Wi-Fi Пароль</label>
<input type="password" name="pass" id="pass" placeholder="Введите пароль WiFi">
</div>
<div class="form-group">
<label>Имя этого модуля</label>
<input type="text" name="m_name" id="m_name" maxlength="15" placeholder="ESP_Node" required>
</div>
<div class="form-group">
<label>Имя локального датчика</label>
<input type="text" name="s_name" id="s_name" maxlength="15" placeholder="LocalTemp" required>
</div>
<div class="form-group">
<label>Режим логики контроля</label>
<select name="l_mode" id="l_mode">
<option value="0">0: Только свой датчик</option>
<option value="1">1: Любой из датчиков в сети</option>
<option value="2">2: Специфичный датчик</option>
</select>
</div>
<div class="form-group">
<label>Имя целевого датчика контроля (для режима 2)</label>
<input type="text" name="t_sensor" id="t_sensor" maxlength="15" placeholder="TargetTemp">
</div>
<div class="form-group">
<label>Целевая температура (°C)</label>
<input type="number" step="0.1" name="t_temp" id="t_temp" value="25.0">
</div>
<div class="form-group">
<label>Гистерезис (°C)</label>
<input type="number" step="0.1" name="hyst" id="hyst" value="0.5">
</div>
<div class="form-group">
<label>Режим работы</label>
<select name="r_mode" id="r_mode">
<option value="0">Нагрев</option>
<option value="1">Охлаждение</option>
</select>
</div>
<div class="form-group">
<label>Тип реле (Железо)</label>
<select name="r_inv" id="r_inv">
<option value="1">Инверсное (Low = ВКЛ, типично для ESP-01)</option>
<option value="0">Прямое (High = ВКЛ)</option>
</select>
</div>
<button type="submit" class="btn">💾 Сохранить</button>
</form>
</div>
</div>
<script>
// --- КОНФИГУРАЦИЯ ---
// ЗДЕСЬ УКАЖИТЕ IP ВАШЕГО ESP-01!
const ESP_IP = '192.168.1.100'; // <-- ИЗМЕНИТЕ НА ВАШ IP!
const API_URL = `http://${ESP_IP}`;
// --- ФУНКЦИЯ ПОЛУЧЕНИЯ ДАННЫХ С ESP ---
async function fetchStatus() {
try {
const response = await fetch(API_URL + '/', {
mode: 'cors',
headers: { 'Accept': 'text/html' }
});
if (!response.ok) throw new Error('Network response was not ok');
const html = await response.text();
parseHTML(html);
document.getElementById('lastUpdate').textContent = 'Обновлено: ' + new Date().toLocaleTimeString();
} catch (error) {
console.error('Error fetching status:', error);
document.getElementById('moduleName').textContent = '⚠️ Ошибка подключения к ESP';
document.getElementById('moduleName').style.color = '#e74c3c';
}
}
// --- ПАРСИНГ HTML С ESP (ТОЧНО ПО ВАШЕМУ КОДУ) ---
function parseHTML(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
// 1. Имя модуля (из <h1>)
const h1 = doc.querySelector('h1');
if (h1) {
const match = h1.textContent.match(/Модуль:\s*(.+)/);
if (match) {
document.getElementById('moduleName').textContent = match[1];
document.getElementById('moduleName').style.color = '#333';
}
}
// 2. Локальный датчик (из первого <p> в карточке)
const paragraphs = doc.querySelectorAll('.card p');
if (paragraphs.length > 0) {
// Ищем "Локальный датчик"
for (let p of paragraphs) {
const text = p.textContent;
if (text.includes('Локальный датчик')) {
const match = text.match(/\(([^)]+)\):\s*([\d.]+|ОШИБКА ДАТЧИКА)/);
if (match) {
const sensorName = match[1];
const tempValue = match[2];
const display = document.getElementById('localTemp');
if (tempValue === 'ОШИБКА ДАТЧИКА') {
display.textContent = '❌ ОШИБКА ДАТЧИКА';
display.className = 'error';
} else {
display.textContent = tempValue + ' °C';
display.className = 'temp';
}
}
break;
}
}
// 3. Состояние реле (из второго <p>)
for (let p of paragraphs) {
const text = p.textContent;
if (text.includes('Состояние реле') || text.includes('Реле')) {
const match = text.match(/Реле:\s*(\S+)/);
if (match) {
const relayState = match[1];
const display = document.getElementById('relayState');
if (relayState === 'ВКЛЮЧЕНО') {
display.textContent = '🔴 ВКЛЮЧЕНО';
display.className = 'relay-on';
} else {
display.textContent = '⚪ ВЫКЛЮЧЕНО';
display.className = 'relay-off';
}
}
break;
}
}
}
// 4. Предупреждения (class="alert")
const alertElement = doc.querySelector('.alert');
const alertContainer = document.getElementById('alertContainer');
if (alertElement) {
alertContainer.innerHTML = `<div class="alert">⚠️ ${alertElement.textContent}</div>`;
} else {
alertContainer.innerHTML = '';
}
// 5. Таблица устройств
const table = doc.querySelector('table');
const tbody = document.getElementById('devicesTable');
if (table) {
const rows = table.querySelectorAll('tr');
let htmlRows = '';
let deviceCount = 0;
rows.forEach((row, index) => {
if (index === 0) return; // Пропускаем заголовок
const cells = row.querySelectorAll('td');
if (cells.length >= 5) {
deviceCount++;
const ip = cells[0].textContent.trim();
const moduleName = cells[1].textContent.trim();
const sensorName = cells[2].textContent.trim();
const temp = cells[3].textContent.trim();
const relay = cells[4].textContent.trim();
const relayBadge = relay === 'ВКЛ' ?
'<span class="badge badge-on">ВКЛ</span>' :
'<span class="badge badge-off">ВЫКЛ</span>';
htmlRows += `
<tr>
<td>${ip}</td>
<td><strong>${moduleName}</strong></td>
<td>${sensorName}</td>
<td>${temp}</td>
<td>${relayBadge}</td>
</tr>
`;
}
});
if (deviceCount > 0) {
tbody.innerHTML = htmlRows;
} else {
tbody.innerHTML = `<tr><td colspan="5" class="empty-state">Нет устройств в сети</td></tr>`;
}
}
}
// --- ЗАГРУЗКА ТЕКУЩИХ НАСТРОЕК ---
async function loadSettings() {
try {
const response = await fetch(API_URL + '/', {
mode: 'cors',
headers: { 'Accept': 'text/html' }
});
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
// Заполняем поля формы из ESP
const form = doc.querySelector('form');
if (form) {
const inputs = form.querySelectorAll('input, select');
inputs.forEach(input => {
const name = input.getAttribute('name');
const value = input.value;
const element = document.getElementById(name);
if (element) {
if (element.tagName === 'SELECT') {
element.value = value;
} else {
element.value = value;
}
}
});
}
} catch (error) {
console.error('Error loading settings:', error);
}
}
// --- ОТПРАВКА НАСТРОЕК ---
document.getElementById('settingsForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const params = new URLSearchParams();
for (let [key, value] of formData.entries()) {
params.append(key, value);
}
try {
const response = await fetch(API_URL + '/save?' + params.toString(), {
method: 'POST',
mode: 'cors'
});
const result = await response.text();
alert('Настройки сохранены! Модуль перезагружается...');
setTimeout(() => {
location.reload();
}, 5000);
} catch (error) {
console.error('Error saving settings:', error);
alert('Ошибка сохранения настроек! Проверьте подключение к ESP.');
}
});
// --- АВТООБНОВЛЕНИЕ ---
setInterval(fetchStatus, 10000);
// --- ЗАПУСК ---
fetchStatus();
loadSettings();
console.log('🌡️ ESP-01 Mesh Thermostat Monitor запущен!');
console.log(`📡 Подключено к: ${API_URL}`);
console.log('🔄 Автообновление каждые 10 секунд');
</script>
</body>
</html>
в целом не могу понять это хорошая основа или нет))) с одной стороны обмен данными есть, аж 20 устройств…
логику можно доделать для каждого модуля… и корректировку через сеть…
-
поменяйте мне лампочку в фаре авто
-
да тебе надо масло и охлаждайку менять…
и давя и давят и давят.
Тут IP адрес - всего лишь информация для подключения к модулю и настройка. Завтра попробую набросать логику обработки действий. Все модули будут знать друг про друга, поскольку при запросе есть src адрес, а по имени в пакете все прекрасно можно разрулить. И MAC тоже можно найти. По сути прошивка для всех модулей будет одинакова. Поэтому это удобно потом менять и наращивать.
Тут прям просится esp32forh, но мы же взяли esp01. Если применять esp32c3, то можно его вшить, а потом писать прямо в модуле программу.
Вау!
Шьешь forth, а потом по месту закидываешь все. И менять потом можно.
а вдруг у нас система умного дома выйдет с 0 офигенная ?
нужно наверное ручное управление еще… перевести все на локальную версию, и может другой протокол…
я пока что нашел всего 1 датчик температуры)))
отыскал 5 кабелей, и только 1 определился на вин 10 для такого программатора https://aliexpress.ru/item/1005004876091486.html при чем самый старый usb вроде, новые кабеля упорно не хотят с ним работать)))
Вчера, наконец, удалось добиться передачи данных между микроконтроллерами. Загруженные скетчи
для nano:
Спойлер
/*
ПРОГРАММА ДЛЯ КОНТРОЛЛЕРА NANO
*/
// ===================================================
// 1. ПОДКЛЮЧАЕМ БИБЛИОТЕКИ
// ===================================================
#include <OneWire.h>
OneWire ds(2); // Пин для датчиков DS18B20
#include "microDS3231.h"
MicroDS3231 rtc; // Часы реального времени
// ===================================================
// 2. ПИНЫ ДЛЯ СЧЁТЧИКОВ
// ===================================================
#define pinVoda_tick 4 // Импульсный вход расходомера воды
#define pinElectr_tick 5 // Импульсный вход электросчётчика
// ===================================================
// 3. АДРЕСА ДАТЧИКОВ ТЕМПЕРАТУРЫ DS18B20
// ===================================================
byte ADDR_DS18B20 [10][8] = {
{0x28, 0xFF, 0x94, 0xAE, 0xA0, 0x16, 0x03, 0x0E}, // 0 - Воздух в комнате
{0x28, 0xFF, 0x48, 0x83, 0xA0, 0x16, 0x04, 0x1F}, // 1 - Вода на выходе из гидроразделителя
{0x28, 0xFF, 0xAA, 0xAB, 0xA0, 0x16, 0x05, 0x74}, // 2 - Вода на выходе из бака-аккумулятора
{0x28, 0xFF, 0xEC, 0x98, 0xA0, 0x16, 0x04, 0x9F}, // 3 - Воздух на улице
{0x28, 0xD5, 0x97, 0x76, 0x0, 0x0, 0x0, 0x76}, // 4 - Фреон на выходе из компрессора
{0x28, 0x4F, 0x9C, 0x73, 0x0, 0x0, 0x0, 0xF9}, // 5 - Вода на входе в теплообменник
{0x28, 0xFC, 0xD1, 0x72, 0x0, 0x0, 0x0, 0x2D}, // 6 - Вода на выходе из теплообменника
{0x28, 0x95, 0x28, 0x75, 0x0, 0x0, 0x0, 0xAB}, // 7 - Фреон на входе в компрессор
{0x28, 0xEF, 0x8C, 0x76, 0x0, 0x0, 0x0, 0x58}, // 8 - Нижняя часть теплообменника
{0x28, 0x8C, 0x3A, 0xB4, 0xE, 0x0, 0x0, 0xDB} // 9 - Фреон в начале испарителя
};
// ===================================================
// 4. ОСНОВНЫЕ ПЕРЕМЕННЫЕ
// ===================================================
int16_t Temper[10] = {0}; // Массив температур (x10)
uint16_t output_to_relays = 0; // Битовая маска реле
uint8_t PWM = 0; // ШИМ для вентилятора (0-255)
uint8_t WaterPresure = 1; // Давление: 1=норма, 2=макс, 3=мин
boolean gettingTempComplete = false; // Флаг готовности температур
uint8_t mistakesFrom_nano = 0; // Код ошибки, битовая маска
//бит 0 - ошибка CRC при передаче данных от Mega к nano, менее нескольких раз подряд
//бит 1 - ошибка CRC при передаче данных от Mega к nano, более нескольких раз подряд
//бит 2 - ошибка потери связи с МК Mega, менее нескольких раз подряд
//бит 3 - ошибка потери связи с МК Mega, более нескольких раз подряд
uint8_t mistakesFrom_nano_0_Count;//счётчик ошибки CRC при передаче данных от Mega к nano (до нескольких раз)
uint8_t mistakesFrom_nano_2_Count;//счётчик ошибки потери связи с МК Mega (до нескольких раз)
// ===================================================
// 5. КОНТРОЛЬ СВЯЗИ С MEGA
// ===================================================
uint32_t lastRxTime = 0; // Время последнего приёма
// ===================================================
// 6. ПЕРЕМЕННЫЕ ДЛЯ РАСХОДОМЕРА ВОДЫ
// ===================================================
boolean stateCheck_voda;
uint32_t tmr, WaterStartTimer = 0, WaterCountTimer;
boolean gWaterFlag = false, FirstImpulsWater = false, SecondImpulsWater = false;
uint32_t gWater = 0; // Расход воды (м3/ч)/100
// ===================================================
// 7. ПЕРЕМЕННЫЕ ДЛЯ ЭЛЕКТРОСЧЁТЧИКА
// ===================================================
boolean stateCheck_electr;
uint32_t tmr2, ElectrStartTimer = 0, ElectrCountTimer;
boolean ElectrFlag = false;
uint32_t wElectr = 0; // Мощность (кВт)/100
// ===================================================
// 8. УПРАВЛЕНИЕ ПРИВОДОМ КЛАПАНА
// ===================================================
uint32_t TimerStartPrivodUp = 0;
uint32_t TimerStartPrivodDown = 0;
uint32_t timePrivodUp = 0;
uint32_t timePrivodDown = 0;
// ===================================================
// 9. СТРУКТУРЫ ДАННЫХ ДЛЯ ОБМЕНА С MEGA
// ===================================================
// От NANO к MEGA (все данные + состояние реле)
struct DataToMega {
int16_t t1, t2, t3, t4, t5, t6, t7, t8, t9, t10;
uint8_t P;
uint8_t minutes, hours, days, months;
uint8_t Water;
uint8_t Electr;
uint16_t relaysState;
uint8_t mistakesFrom_nano;
uint8_t crc;
} dataToMega;
// От MEGA к NANO (только параметры)
struct DataFromMega {
uint16_t output_from_Mega_to_nano;
uint8_t actuatorMoveTime;
uint8_t crc;
} dataFromMega;
// ===================================================
// 10. ФУНКЦИЯ CRC-8
// ===================================================
uint8_t crc8(const void* data, size_t length, uint8_t crc = 0) {
const uint8_t* p = (const uint8_t*)data;
while (length--) {
uint8_t b = *p++, j = 8;
while (j--) {
crc = ((crc ^ b) & 1) ? (crc >> 1) ^ 0x8C : (crc >> 1);
b >>= 1;
}
}
return crc;
}
// ===================================================
// 11. ФУНКЦИИ ДЛЯ РАБОТЫ С ДАТЧИКАМИ ТЕМПЕРАТУРЫ
// ===================================================
bool readDS18B20(byte *addr, int16_t *dest) {
uint8_t data[9];
ds.reset();
ds.select(addr);
ds.write(0xBE);
for (uint8_t i = 0; i < 9; i++) data[i] = ds.read();
if (crc8(data, 8) != data[8]) return false;
int16_t raw = (int16_t)((data[1] << 8) | data[0]);
*dest = (raw * 5) / 8;
return true;
}
void resetTemp() {
ds.reset();
ds.write(0xCC);
ds.write(0x44);
}
void gettingTemp() {
int16_t temp;
for (uint8_t i = 0; i < 10; i++) {
if (readDS18B20(ADDR_DS18B20[i], &temp)) {
Temper[i] = temp;
} else {
Temper[i] = 22222; // Ошибка датчика
}
}
gettingTempComplete = true;
}
// ===================================================
// 12. ОБРАБОТЧИК ИМПУЛЬСОВ РАСХОДОМЕРА ВОДЫ
// ===================================================
void voda_tick() {
boolean current_status = digitalRead(pinVoda_tick);
if (stateCheck_voda && !current_status) {
tmr = millis();
gWaterFlag = true;
stateCheck_voda = current_status;
return;
}
if (!stateCheck_voda && current_status) {
stateCheck_voda = current_status;
return;
}
if (current_status) {
gWaterFlag = false;
return;
}
if (!current_status && (millis() - tmr >= 1000) && gWaterFlag == true) {
if (WaterStartTimer == 0) {
WaterStartTimer = millis();
gWaterFlag = false;
return;
}
WaterCountTimer = millis() - WaterStartTimer;
WaterStartTimer = millis();
gWaterFlag = false;
if (FirstImpulsWater == true) SecondImpulsWater = true;
FirstImpulsWater = true;
}
}
// ===================================================
// 13. ОБРАБОТЧИК ИМПУЛЬСОВ ЭЛЕКТРОСЧЁТЧИКА
// ===================================================
void electr_tick() {
boolean current_status = digitalRead(pinElectr_tick);
if (stateCheck_electr && !current_status) {
tmr2 = millis();
ElectrFlag = true;
stateCheck_electr = current_status;
return;
}
if (!stateCheck_electr && current_status) {
stateCheck_electr = current_status;
return;
}
if (current_status) {
ElectrFlag = false;
return;
}
if (!current_status && (millis() - tmr2 >= 50) && ElectrFlag == true) {
if (ElectrStartTimer == 0) {
ElectrStartTimer = millis();
ElectrFlag = false;
return;
}
ElectrCountTimer = millis() - ElectrStartTimer;
ElectrStartTimer = millis();
ElectrFlag = false;
}
}
// ===================================================
// 14. ПРИЁМ ПАРАМЕТРОВ ОТ MEGA (БИНАРНЫЙ)
// ===================================================
void receiveFromMega() {
if (Serial.available()) {
DataFromMega rx;
if (Serial.readBytes((uint8_t*)&rx, sizeof(rx))) {
uint8_t crc = crc8(&rx, sizeof(rx));
if (crc == 0) {
output_to_relays = rx.output_from_Mega_to_nano;
if (bitRead(output_to_relays, 7) == HIGH)
timePrivodDown = rx.actuatorMoveTime * 100;
else if (bitRead(output_to_relays, 8) == HIGH)
timePrivodUp = rx.actuatorMoveTime * 100;
else
{
timePrivodDown = 0;
timePrivodUp = 0;
}
bitClear(mistakesFrom_nano, 0);
mistakesFrom_nano_0_Count = 0;
} else {
bitSet(mistakesFrom_nano, 0);
mistakesFrom_nano_0_Count++;
if (mistakesFrom_nano_0_Count > 3)
{
bitSet(mistakesFrom_nano, 1);
output_to_relays = 0;//аварийный останов
}
}
lastRxTime = millis();
bitClear(mistakesFrom_nano, 2);
mistakesFrom_nano_2_Count = 0;
}
}
// ====== ПРОВЕРКА ПОТЕРИ СВЯЗИ ======
uint32_t timeSinceLastRx = millis() - lastRxTime;
if (timeSinceLastRx >= 15000) {
bitSet(mistakesFrom_nano, 2);
mistakesFrom_nano_2_Count++;
lastRxTime = millis();//чтобы прошло ещё время, прежде чем увеличится счётчик mistakesFrom_nano_2_Count
if (mistakesFrom_nano_2_Count > 3)
{
bitSet(mistakesFrom_nano, 3);
output_to_relays = 0;//аварийный останов
}
}
}
// ===================================================
// 15. УПРАВЛЕНИЕ ПРИВОДОМ РЕГУЛИРУЮЩЕГО КЛАПАНА
// ===================================================
void actuatorMove() {
// Закрывание (бит 7)
if (bitRead(output_to_relays, 7) == HIGH && digitalRead(13) == HIGH) {
TimerStartPrivodDown = millis();
digitalWrite(13, LOW);
}
if (digitalRead(13) == LOW && millis() - TimerStartPrivodDown >= timePrivodDown) {
digitalWrite(13, HIGH);
bitClear(output_to_relays, 7);
}
// Открывание (бит 8)
if (bitRead(output_to_relays, 8) == HIGH && digitalRead(12) == HIGH) {
TimerStartPrivodUp = millis();
digitalWrite(12, LOW);
}
if (digitalRead(12) == LOW && millis() - TimerStartPrivodUp >= timePrivodUp) {
digitalWrite(12, HIGH);
bitClear(output_to_relays, 8);
}
}
// ===================================================
// 16. ОТПРАВКА ДАННЫХ НА MEGA
// ===================================================
void sendToMega() {
dataToMega.t1 = Temper[0];
dataToMega.t2 = Temper[1];
dataToMega.t3 = Temper[2];
dataToMega.t4 = Temper[3];
dataToMega.t5 = Temper[4];
dataToMega.t6 = Temper[5];
dataToMega.t7 = Temper[6];
dataToMega.t8 = Temper[7];
dataToMega.t9 = Temper[8];
dataToMega.t10 = Temper[9];
dataToMega.P = WaterPresure;
dataToMega.minutes = rtc.getMinutes();
dataToMega.hours = rtc.getHours();
dataToMega.days = rtc.getDate();
dataToMega.months = rtc.getMonth();
dataToMega.Water = (uint8_t)gWater;
dataToMega.Electr = (uint8_t)wElectr;
dataToMega.relaysState = output_to_relays;
dataToMega.mistakesFrom_nano = mistakesFrom_nano;
uint8_t crc = crc8(&dataToMega, sizeof(dataToMega) - 1);
dataToMega.crc = crc;
Serial.write((uint8_t*)&dataToMega, sizeof(dataToMega));
}
// ===================================================
// 17. УПРАВЛЕНИЕ РЕЛЕ (физические выходы)
// ===================================================
void setRelays() {
// 0 - Дизельный котёл
digitalWrite(9, bitRead(output_to_relays, 0) ? LOW : HIGH);
// 1 - ТЭН 2.5 кВт
digitalWrite(8, bitRead(output_to_relays, 1) ? LOW : HIGH);
// 2 - ТЭН 7.5 кВт
digitalWrite(7, bitRead(output_to_relays, 2) ? LOW : HIGH);
// 3 - Шаровой кран
digitalWrite(6, bitRead(output_to_relays, 3) ? LOW : HIGH);
// 4 - Насос радиаторов
digitalWrite(10, bitRead(output_to_relays, 4) ? LOW : HIGH);
// 5 - Насос тёплых полов
digitalWrite(A3, bitRead(output_to_relays, 5) ? LOW : HIGH);
// 6 - Вентилятор
digitalWrite(11, bitRead(output_to_relays, 6) ? LOW : HIGH);
// 7 - Закрывание клапана
digitalWrite(13, bitRead(output_to_relays, 7) ? LOW : HIGH);
// 8 - Открывание клапана
digitalWrite(12, bitRead(output_to_relays, 8) ? LOW : HIGH);
// 9 - Компрессор
digitalWrite(A2, bitRead(output_to_relays, 9) ? LOW : HIGH);
// 10 - Четырёхходовой клапан
digitalWrite(A1, bitRead(output_to_relays, 10) ? LOW : HIGH);
// 11 - Насос теплообменника
digitalWrite(A0, bitRead(output_to_relays, 11) ? LOW : HIGH);
// ШИМ для вентилятора
analogWrite(3, PWM);
}
// ===================================================
// 18. НАЧАЛЬНАЯ НАСТРОЙКА
// ===================================================
void setup() {
Serial.begin(9600);
// === Настройка пинов ===
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
// Выходы реле (HIGH = выключено, LOW = включено)
pinMode(6, OUTPUT); digitalWrite(6, HIGH);
pinMode(7, OUTPUT); digitalWrite(7, HIGH);
pinMode(8, OUTPUT); digitalWrite(8, HIGH);
pinMode(9, OUTPUT); digitalWrite(9, HIGH);
pinMode(10, OUTPUT); digitalWrite(10, HIGH);
pinMode(11, OUTPUT); digitalWrite(11, HIGH);
pinMode(12, OUTPUT); digitalWrite(12, HIGH);
pinMode(13, OUTPUT); digitalWrite(13, HIGH);
pinMode(A0, OUTPUT); digitalWrite(A0, HIGH);
pinMode(A1, OUTPUT); digitalWrite(A1, HIGH);
pinMode(A2, OUTPUT); digitalWrite(A2, HIGH);
pinMode(A3, OUTPUT); digitalWrite(A3, HIGH);
// === Часы реального времени ===
if (rtc.lostPower()) rtc.setTime(COMPILE_TIME);
delay(1000);
}
// ===================================================
// 19. ГЛАВНЫЙ ЦИКЛ
// ===================================================
void loop() {
// === Обработчики импульсов ===
voda_tick();
electr_tick();
// === Приём данных от Mega ===
receiveFromMega();
// === Управление клапаном ===
actuatorMove();
// === Цикл 10 секунд ===
static uint32_t timer = 0;
static uint8_t step = 0;
// Шаг 1: Запуск измерения температур (2 сек)
if (millis() - timer >= 2000 && step < 1) {
resetTemp();
step = 1;
}
// Шаг 2: Чтение температур (4 сек)
if (millis() - timer >= 4000 && step < 2) {
gettingTemp();
step = 2;
}
// Шаг 3: Управление (6 сек)
if (millis() - timer >= 6000 && step < 3) {
// === Давление ===
if (analogRead(A6) < 307) WaterPresure = 2;
else if (analogRead(A7) < 307) WaterPresure = 3;
else WaterPresure = 1;
// === Расход воды ===
if (SecondImpulsWater == false) gWater = 10;
else gWater = round(3600000 / WaterCountTimer);
// === Мощность ===
if (ElectrCountTimer == 0 || (millis() - ElectrStartTimer > 60000))
wElectr = 0;
else wElectr = 72000 / ElectrCountTimer;
step = 3;
}
// Шаг 4: Управление реле (8 сек)
if (millis() - timer >= 8000 && step < 4) {
setRelays();
step = 4;
}
// Шаг 5: Отправка данных на Mega (10 сек)
if (millis() - timer >= 10000 && step < 5) {
if (gettingTempComplete) {
sendToMega();
gettingTempComplete = false;
step = 5;
}
timer = millis();
step = 0;
}
}
для Mega:
Спойлер
/*
ПРОГРАММА ДЛЯ КОНТРОЛЛЕРА MEGA
*/
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;
// ===================================================
// 1. НАСТРОЙКИ ДИСПЛЕЯ
// ===================================================
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4
// ===================================================
// 2. ЦВЕТА
// ===================================================
#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 DARKGREY 0x7BEF
#define DARKGREEN 0x0C80
// ===================================================
// 3. ПЕРЕМЕННЫЕ
// ===================================================
uint8_t kindDisplay = 1;
uint8_t kindDisplayPrev = 1;
uint16_t mistakes = 0;
//бит 0 - ошибка CRC при передаче данных от Mega к nano, менее нескольких раз подряд
//бит 1 - ошибка CRC при передаче данных от Mega к nano, более нескольких раз подряд
//бит 2 - ошибка потери связи с МК Mega, менее нескольких раз подряд
//бит 3 - ошибка потери связи с МК Mega, более нескольких раз подряд
//бит 15 - ошибка CRC при передаче данных от nano к Mega, менее нескольких раз подряд
//бит 14 - ошибка CRC при передаче данных от nano к Mega, ,более нескольких раз подряд
//бит 13 - ошибка потери связи с МК nano, менее нескольких раз подряд
//бит 12 - ошибка потери связи с МК nano, более нескольких раз подряд
uint8_t mistake_15_Count;//счётчик количества ошибок подряд, mistakes, бит 15
uint8_t mistake_13_Count;//счётчик количества ошибок подряд, mistakes, бит 13
uint16_t output_from_Mega_to_nano = 0;
//0 - дизельный котёл и насос дизельного котла
//1 - ТЭН 2,5 кВт
//2 - ТЭН 7,5 кВт
//3 - шаровой кран
//4 - насос радиаторов
//5 - насос тёплых полов
//6 - вентилятор наружного блока
//7 - закрывание клапана
//8 - открывание клапана
//9 - компрессор
//10 - четырёхходовой клапан
//11 - насос пластинчатого теплообменника
uint8_t actuatorMoveTime;//время в течении которого перемещается клапан (выражено в 0, 1 сек.: 10 = 1 сек.)
uint32_t actuatorTimer;//для выдержки времени между передачей команды клапану
boolean gettingFrom_nano;//флаг о получении данных от nano
//-------------- проверка работы реле ---------------
uint8_t cycleCount = 0;
//-------------- (проверка работы реле) ---------------
// Данные от nano
struct DataFromNano {
int16_t t1, t2, t3, t4, t5, t6, t7, t8, t9, t10;
uint8_t P;
uint8_t minutes, hours, days, months;
uint8_t Water;
uint8_t Electr;
uint16_t relaysState;
uint8_t mistakesFrom_nano;
uint8_t crc;
} rxData;
// Данные от UNO
struct DataFromUNO {
uint8_t Display;
} rxDataFromUNO;
// Массивы для графиков
uint8_t t1_Graph[320], t2_Graph[320], t3_Graph[320], t4_Graph[320];
uint8_t t5_Graph[320], t6_Graph[320], t7_Graph[320], t8_Graph[320];
uint8_t t9_Graph[320], t10_Graph[320];
uint8_t Water_Graph[320], Electr_Graph[320];
// ===================================================
// 4. CRC
// ===================================================
uint8_t crc8(const void* data, size_t length, uint8_t crc = 0) {
const uint8_t* p = (const uint8_t*)data;
while (length--) {
uint8_t b = *p++, j = 8;
while (j--) {
crc = ((crc ^ b) & 1) ? (crc >> 1) ^ 0x8C : (crc >> 1);
b >>= 1;
}
}
return crc;
}
// ===================================================
// 5. ПРЕОБРАЗОВАНИЕ ДЛЯ ГРАФИКОВ
// ===================================================
int16_t getDataForUintMassiv(int16_t a) {
if (a < -400) return 0;
else if (a >= -400 && a < 500) return round((a + 400) / 5);
else if (a >= 500 && a < 1250) return round(180 + (a - 500) / 10);
else return 255;
}
uint8_t getDataForUintMassiv8(uint8_t a) {
if (a <= 160) return round(40 + a);
else if (a < 255) return round(200 + (a - 160) / 10);
else return 255;
}
// ===================================================
// 6. ОТРИСОВКА ГРАФИКОВ
// ===================================================
void getGraphics(uint16_t c, uint16_t d, uint8_t g[320]) {
for (int16_t x = 1; x < 320; x++) {
tft.drawLine(x - 1, (c - g[x - 1]), x, (c - g[x]), d);
}
}
// ===================================================
// 7. ОСНОВНОЙ ЭКРАН
// ===================================================
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:");
}
// ===================================================
// 8. ТЕКСТОВЫЙ РЕЖИМ
// ===================================================
void getAllLastDataToDisplay() {
tft.fillScreen(BLACK);
tft.setTextSize(1);
tft.setTextColor(YELLOW);
tft.setCursor(0, 0); tft.print("tRoom: "); tft.print(rxData.t1 / 10.0); tft.print(" C");
tft.setCursor(0, 10); tft.print("tGS: "); tft.print(rxData.t2 / 10.0); tft.print(" C");
tft.setCursor(0, 20); tft.print("tBA: "); tft.print(rxData.t3 / 10.0); tft.print(" C");
tft.setCursor(0, 30); tft.print("tOutdoor: "); tft.print(rxData.t4 / 10.0); tft.print(" C");
tft.setCursor(0, 40); tft.print("tKompressorOut: "); tft.print(rxData.t5 / 10.0); tft.print(" C");
tft.setCursor(0, 50); tft.print("tWaterIn: "); tft.print(rxData.t6 / 10.0); tft.print(" C");
tft.setCursor(0, 60); tft.print("tWaterOut: "); tft.print(rxData.t7 / 10.0); tft.print(" C");
tft.setCursor(0, 70); tft.print("tKompressorIn: "); tft.print(rxData.t8 / 10.0); tft.print(" C");
tft.setCursor(0, 80); tft.print("tDefrost: "); tft.print(rxData.t9 / 10.0); tft.print(" C");
tft.setCursor(0, 90); tft.print("tEvaporator: "); tft.print(rxData.t10 / 10.0); tft.print(" C");
tft.setCursor(0, 100); tft.print("Pressure: ");
if (rxData.P == 1) tft.print("OK");
else if (rxData.P == 2) tft.print("HIGH");
else tft.print("LOW");
tft.setCursor(0, 110); tft.print("Time: "); tft.print(rxData.hours); tft.print(":"); tft.print(rxData.minutes);
tft.setCursor(0, 120); tft.print("Date: "); tft.print(rxData.days); tft.print("."); tft.print(rxData.months);
tft.setCursor(0, 130); tft.print("Water flow: "); tft.print(rxData.Water / 100.0); tft.print(" m3/h");
tft.setCursor(0, 140); tft.print("Power: "); tft.print(rxData.Electr / 100.0); tft.print(" W");
tft.setCursor(0, 150); tft.print("Relays: "); tft.print(rxData.relaysState, BIN);
tft.setCursor(0, 160); tft.print("Mistakes: "); tft.print(mistakes, BIN);
}
// ===================================================
// 9. ОТОБРАЖЕНИЕ ОШИБОК
// ===================================================
void showMistakes() {
tft.fillScreen(BLACK);
tft.setTextSize(2);
tft.setTextColor(RED);
tft.setCursor(0, 80);
tft.print("❌ ERROR");
tft.setCursor(0, 120);
tft.setTextSize(1);
if (bitRead(mistakes, 0) == HIGH)
tft.println("CRC error from Mega");
if (bitRead(mistakes, 1) == HIGH)
tft.println("Stop: CRC error from Mega");
if (bitRead(mistakes, 2) == HIGH)
tft.println("Connection from Mega to nano LOST!");
if (bitRead(mistakes, 3) == HIGH)
tft.println("Stop: connection from Mega to nano LOST!");
if (bitRead(mistakes, 15) == HIGH)
tft.println("CRC error from nano");
if (bitRead(mistakes, 14) == HIGH)
tft.println("Stop: CRC error from nano");
if (bitRead(mistakes, 13) == HIGH)
tft.println("Connection from nano to Mega LOST!");
if (bitRead(mistakes, 12) == HIGH)
tft.println("Stop: connection from nano to Mega LOST!");
else if (bitRead(mistakes, 0) == LOW and bitRead(mistakes, 1) == LOW and bitRead(mistakes, 2) == LOW and bitRead(mistakes, 3) == LOW and bitRead(mistakes, 15) == LOW and bitRead(mistakes, 14) == LOW and bitRead(mistakes, 13) == LOW and bitRead(mistakes, 12) == LOW)
tft.println("Unknown error");
}
// ===================================================
// 10. ОТПРАВКА ПАРАМЕТРОВ НА NANO
// ===================================================
void sendParamsToNano() {
struct ParamsToNano {
uint16_t output_from_Mega_to_nano;
uint8_t actuatorMoveTime;
uint8_t crc;
} params;
params.output_from_Mega_to_nano = output_from_Mega_to_nano;
params.actuatorMoveTime = actuatorMoveTime;
params.crc = crc8(¶ms, sizeof(params) - 1);
Serial1.write((uint8_t*)¶ms, sizeof(params));
}
// ===================================================
// 11. ФОРМИРОВАНИЕ КОМАНДЫ РЕГУЛИРУЮЩЕМУ КЛАПАНУ
// ===================================================
void actuatorMove(String a, uint8_t b)
{
if (millis() - actuatorTimer > 15000)//если после предыдущего перемещения клапана прошло время
{
if (a == "DOWN")
bitSet(output_from_Mega_to_nano, 7);//закрывать регулирующий клапан
else if (a == "UP")
bitSet(output_from_Mega_to_nano, 8);//открывать регулирующий клапан
actuatorMoveTime = b;
actuatorTimer = millis();
}
}
// ===================================================
// 12. ФИКСАЦИЯ ОШИБОК ОТ nano
// ===================================================
void updateMistakes()
{
if (bitRead(rxData.mistakesFrom_nano, 0) == HIGH)
bitSet(mistakes, 0);
else if (bitRead(rxData.mistakesFrom_nano, 0) == LOW)
bitClear(mistakes, 0);
if (bitRead(rxData.mistakesFrom_nano, 1) == HIGH)
bitSet(mistakes, 1);
else if (bitRead(rxData.mistakesFrom_nano, 1) == LOW)
bitClear(mistakes, 1);
if (bitRead(rxData.mistakesFrom_nano, 2) == HIGH)
bitSet(mistakes, 2);
else if (bitRead(rxData.mistakesFrom_nano, 2) == LOW)
bitClear(mistakes, 2);
if (bitRead(rxData.mistakesFrom_nano, 3) == HIGH)
bitSet(mistakes, 3);
else if (bitRead(rxData.mistakesFrom_nano, 3) == LOW)
bitClear(mistakes, 3);
}
// ===================================================
// 13. СМЕНА ИЗОБРАЖЕНИЯ НА ДИСПЛЕЕ
// ===================================================
void selectDisplay()
{
if (kindDisplay != kindDisplayPrev)
{
if (kindDisplay == 1)
getAllLastDataToDisplay();
kindDisplayPrev = kindDisplay;
}
}
// ===================================================
// 14. ОБНОВЛЕНИЕ МАССИВОВ ДЛЯ ГРАФИКОВ
// ===================================================
void updateArrays() {
for (int i = 0; i < 319; i++) {
t1_Graph[i] = t1_Graph[i + 1];
t2_Graph[i] = t2_Graph[i + 1];
t3_Graph[i] = t3_Graph[i + 1];
t4_Graph[i] = t4_Graph[i + 1];
t5_Graph[i] = t5_Graph[i + 1];
t6_Graph[i] = t6_Graph[i + 1];
t7_Graph[i] = t7_Graph[i + 1];
t8_Graph[i] = t8_Graph[i + 1];
t9_Graph[i] = t9_Graph[i + 1];
t10_Graph[i] = t10_Graph[i + 1];
Water_Graph[i] = Water_Graph[i + 1];
Electr_Graph[i] = Electr_Graph[i + 1];
}
t1_Graph[319] = getDataForUintMassiv(rxData.t1);
t2_Graph[319] = getDataForUintMassiv(rxData.t2);
t3_Graph[319] = getDataForUintMassiv(rxData.t3);
t4_Graph[319] = getDataForUintMassiv(rxData.t4);
t5_Graph[319] = getDataForUintMassiv(rxData.t5);
t6_Graph[319] = getDataForUintMassiv(rxData.t6);
t7_Graph[319] = getDataForUintMassiv(rxData.t7);
t8_Graph[319] = getDataForUintMassiv(rxData.t8);
t9_Graph[319] = getDataForUintMassiv(rxData.t9);
t10_Graph[319] = getDataForUintMassiv(rxData.t10);
Water_Graph[319] = getDataForUintMassiv8(rxData.Water);
Electr_Graph[319] = getDataForUintMassiv8(rxData.Electr);
}
// ===================================================
// 15. НАЧАЛЬНАЯ НАСТРОЙКА
// ===================================================
void setup() {
Serial.begin(9600);
Serial1.begin(9600); // Связь с nano
Serial2.begin(9600); // Связь с UNO
// === Настройка дисплея ===
tft.reset();
tft.begin(38022);
tft.invertDisplay(true);
// === Инициализация графиков ===
for (int i = 0; i < 320; i++) {
t1_Graph[i] = t2_Graph[i] = t3_Graph[i] = t4_Graph[i] = 80;
t5_Graph[i] = t6_Graph[i] = t7_Graph[i] = t8_Graph[i] = 80;
t9_Graph[i] = t10_Graph[i] = 80;
Water_Graph[i] = Electr_Graph[i] = 40;
}
delay(1000);
getMainDisplay();
// Отправляем начальные параметры
delay(500);
sendParamsToNano();
}
// ===================================================
// 16. ГЛАВНЫЙ ЦИКЛ
// ===================================================
void loop() {
static uint32_t lastGet = 0;//для исчисления времени последних полученных данных
selectDisplay();//моментальная смена изображения на дисплее
// === 1. Приём данных от UNO ===
if (Serial2.available()) {
DataFromUNO window;
if (Serial2.readBytes((uint8_t*)&window, sizeof(window))) {
rxDataFromUNO = window;
kindDisplay = rxDataFromUNO.Display;
}
}
// === 2. Приём данных от nano ===
if (Serial1.available()) {
DataFromNano temp;
if (Serial1.readBytes((uint8_t*)&temp, sizeof(temp))) {
uint8_t crc = crc8(&temp, sizeof(temp));
if (crc == 0) {
rxData = temp;
updateArrays();
updateMistakes();
bitClear(mistakes, 15);//снять ошибку передачи данных от nano к Mega
mistake_15_Count = 0;
// === 3. Отображение ===
if (mistakes > 0)
showMistakes;
else {
if (kindDisplay == 1) {
getAllLastDataToDisplay();
} else {
getMainDisplay();
getGraphics(245, RED, t1_Graph);
getGraphics(245, YELLOW, t2_Graph);
getGraphics(245, GREEN, t3_Graph);
getGraphics(245, MAGENTA, t4_Graph);
getGraphics(245, CYAN, t5_Graph);
getGraphics(245, RED, t6_Graph);
getGraphics(245, YELLOW, t7_Graph);
getGraphics(245, GREEN, t8_Graph);
getGraphics(245, MAGENTA, t9_Graph);
getGraphics(245, CYAN, t10_Graph);
getGraphics(475, RED, Water_Graph);
getGraphics(475, YELLOW, Electr_Graph);
}
}
}
else
{
bitSet(mistakes, 15);//зафиксирована ошибка передачи данных от nano к Mega
mistake_15_Count++;
if (mistake_15_Count > 3)
bitSet(mistakes, 14);//зафиксирована несколько раз подряд ошибка передачи данных от nano к Mega
showMistakes;
}
bitClear(mistakes, 13);
mistake_13_Count = 0;
lastGet = millis();
gettingFrom_nano = true;
}
}
// === 4. Периодическая отправка параметров ===
if (millis() - lastGet > 5000 and gettingFrom_nano == true) { //через 5 сек. после получения данных от nano
//-------------- проверка работы реле ---------------
cycleCount++;
if (cycleCount > 24)
bitSet(output_from_Mega_to_nano, 0); //включить дизельный котёл и насос дизельного котла
if (cycleCount > 22)
bitSet(output_from_Mega_to_nano, 1);//включить ТЭН 2,5 кВт
if (cycleCount > 20)
bitSet(output_from_Mega_to_nano, 2);//включить ТЭН 7,5 кВт
if (cycleCount > 18)
bitSet(output_from_Mega_to_nano, 3);//открыть шаровой кран
if (cycleCount == 17)
actuatorMove("DOWN", 20);
else
bitClear(output_from_Mega_to_nano, 7);
if (cycleCount == 15)
actuatorMove("UP", 20);
else
bitClear(output_from_Mega_to_nano, 8);
if (cycleCount > 12)
bitSet(output_from_Mega_to_nano, 6);//включить вентилятор наружного блока
if (cycleCount > 10)
bitSet(output_from_Mega_to_nano, 4);//включить насос радиаторов
if (cycleCount > 8)
bitSet(output_from_Mega_to_nano, 5);//включить насос тёплых полов
//if (cycleCount > 6)
//bitSet(output_from_Mega_to_nano, 9);//включить компрессор
//if (cycleCount > 4)
//bitSet(output_from_Mega_to_nano, 10);//включить четырёхходовый клапан
if (cycleCount > 2)
bitSet(output_from_Mega_to_nano, 11);//включить насос пластинчатого теплообменника
if (cycleCount > 26)
{
cycleCount = 0;
output_from_Mega_to_nano = 0b0000000000000000;
}
//-------------- (проверка работы реле) ---------------
sendParamsToNano();
gettingFrom_nano = false;
}
// === 5. Фиксация потери связи ===
if (millis() - lastGet > 15000) {//если в течение 15 сек. не было связи с nano
{
bitSet(mistakes, 13);//зафиксирована потеря связи с nano
mistake_13_Count++;
lastGet = millis();//попытка связи с nano
if (mistake_13_Count > 3)
{
bitSet(mistakes, 12);//зафиксирована продолжительная потеря связи с nano
output_from_Mega_to_nano = 0; //аварийный останов
}
showMistakes;
}
}
}
именно пересылка данных (общение между МК) - работают по моим наблюдениям нормально. И это самое важное для меня на данный момент. Скетчи делал - на основе кода от BABOS из сообщения 612.
Просьба к вам такая (по возможности) - посмотрите, насколько оптимально организована именно связь. То есть - корректно ли и при нужных ли условиях - осуществляется передача данных. Сначала (или с конца - не совсем понимаю, кто тут главный) передача данных от Mega к nano, а затем уже от nano к Mega.
Я хочу сначала разобраться с этим (возможно, самым важным моментом) - оптимально ли заданы условия, когда должна пройти передача пакета. Оптимально ли организован приём этого пакета и опять передача (уже от второго МК к первому). Под словом “оптимально” - я имею в виду надёжность. Также надо, чтобы в случае пропусков (по каким-либо причинам) передачи пакетов - микроконтролллеры ещё в течение какого-то времени (около минуты) пытались бы наладить связь (возобновить передачу пакетов).
/*
ПРОГРАММА ДЛЯ КОНТРОЛЛЕРА NANO
Версия с исправленной CRC и улучшенной надёжностью связи
*/
// ===================================================
// 1. ПОДКЛЮЧАЕМ БИБЛИОТЕКИ
// ===================================================
#include <OneWire.h>
OneWire ds(2); // Пин для датчиков DS18B20
#include "microDS3231.h"
MicroDS3231 rtc; // Часы реального времени
// ===================================================
// 2. ПИНЫ ДЛЯ СЧЁТЧИКОВ
// ===================================================
#define pinVoda_tick 4 // Импульсный вход расходомера воды
#define pinElectr_tick 5 // Импульсный вход электросчётчика
// ===================================================
// 3. АДРЕСА ДАТЧИКОВ ТЕМПЕРАТУРЫ DS18B20
// ===================================================
byte ADDR_DS18B20 [10][8] = {
{0x28, 0xFF, 0x94, 0xAE, 0xA0, 0x16, 0x03, 0x0E}, // 0 - Воздух в комнате
{0x28, 0xFF, 0x48, 0x83, 0xA0, 0x16, 0x04, 0x1F}, // 1 - Вода на выходе из гидроразделителя
{0x28, 0xFF, 0xAA, 0xAB, 0xA0, 0x16, 0x05, 0x74}, // 2 - Вода на выходе из бака-аккумулятора
{0x28, 0xFF, 0xEC, 0x98, 0xA0, 0x16, 0x04, 0x9F}, // 3 - Воздух на улице
{0x28, 0xD5, 0x97, 0x76, 0x0, 0x0, 0x0, 0x76}, // 4 - Фреон на выходе из компрессора
{0x28, 0x4F, 0x9C, 0x73, 0x0, 0x0, 0x0, 0xF9}, // 5 - Вода на входе в теплообменник
{0x28, 0xFC, 0xD1, 0x72, 0x0, 0x0, 0x0, 0x2D}, // 6 - Вода на выходе из теплообменника
{0x28, 0x95, 0x28, 0x75, 0x0, 0x0, 0x0, 0xAB}, // 7 - Фреон на входе в компрессор
{0x28, 0xEF, 0x8C, 0x76, 0x0, 0x0, 0x0, 0x58}, // 8 - Нижняя часть теплообменника
{0x28, 0x8C, 0x3A, 0xB4, 0xE, 0x0, 0x0, 0xDB} // 9 - Фреон в начале испарителя
};
// ===================================================
// 4. ОСНОВНЫЕ ПЕРЕМЕННЫЕ
// ===================================================
int16_t Temper[10] = {0}; // Массив температур (x10)
uint16_t output_to_relays = 0; // Битовая маска реле
uint8_t PWM = 0; // ШИМ для вентилятора (0-255)
uint8_t WaterPresure = 1; // Давление: 1=норма, 2=макс, 3=мин
boolean gettingTempComplete = false; // Флаг готовности температур
uint8_t mistakesFrom_nano = 0; // Код ошибки, битовая маска
//бит 0 - ошибка CRC при передаче данных от Mega к nano, менее нескольких раз подряд
//бит 1 - ошибка CRC при передаче данных от Mega к nano, более нескольких раз подряд
//бит 2 - ошибка потери связи с МК Mega, менее нескольких раз подряд
//бит 3 - ошибка потери связи с МК Mega, более нескольких раз подряд
uint8_t mistakesFrom_nano_0_Count = 0; // счётчик ошибки CRC (до нескольких раз)
uint8_t mistakesFrom_nano_2_Count = 0; // счётчик ошибки потери связи (до нескольких раз)
// ===================================================
// 5. КОНТРОЛЬ СВЯЗИ С MEGA
// ===================================================
uint32_t lastRxTime = 0; // Время последнего приёма
// ===================================================
// 6. ПЕРЕМЕННЫЕ ДЛЯ РАСХОДОМЕРА ВОДЫ
// ===================================================
boolean stateCheck_voda;
uint32_t tmr, WaterStartTimer = 0, WaterCountTimer;
boolean gWaterFlag = false, FirstImpulsWater = false, SecondImpulsWater = false;
uint32_t gWater = 0; // Расход воды (м3/ч)/100
// ===================================================
// 7. ПЕРЕМЕННЫЕ ДЛЯ ЭЛЕКТРОСЧЁТЧИКА
// ===================================================
boolean stateCheck_electr;
uint32_t tmr2, ElectrStartTimer = 0, ElectrCountTimer;
boolean ElectrFlag = false;
uint32_t wElectr = 0; // Мощность (кВт)/100
// ===================================================
// 8. УПРАВЛЕНИЕ ПРИВОДОМ КЛАПАНА
// ===================================================
uint32_t TimerStartPrivodUp = 0;
uint32_t TimerStartPrivodDown = 0;
uint32_t timePrivodUp = 0;
uint32_t timePrivodDown = 0;
// ===================================================
// 9. СТРУКТУРЫ ДАННЫХ ДЛЯ ОБМЕНА С MEGA
// ===================================================
// От NANO к MEGA (все данные + состояние реле)
struct DataToMega {
int16_t t1, t2, t3, t4, t5, t6, t7, t8, t9, t10;
uint8_t P;
uint8_t minutes, hours, days, months;
uint8_t Water;
uint8_t Electr;
uint16_t relaysState;
uint8_t mistakesFrom_nano;
uint8_t crc;
} dataToMega;
// От MEGA к NANO (только параметры)
struct DataFromMega {
uint16_t output_from_Mega_to_nano;
uint8_t actuatorMoveTime;
uint8_t crc;
} dataFromMega;
// ===================================================
// 10. ФУНКЦИЯ CRC-8
// ===================================================
uint8_t crc8(const void* data, size_t length, uint8_t crc = 0) {
const uint8_t* p = (const uint8_t*)data;
while (length--) {
uint8_t b = *p++, j = 8;
while (j--) {
crc = ((crc ^ b) & 1) ? (crc >> 1) ^ 0x8C : (crc >> 1);
b >>= 1;
}
}
return crc;
}
// ===================================================
// 11. ФУНКЦИИ ДЛЯ РАБОТЫ С ДАТЧИКАМИ ТЕМПЕРАТУРЫ
// ===================================================
bool readDS18B20(byte *addr, int16_t *dest) {
uint8_t data[9];
ds.reset();
ds.select(addr);
ds.write(0xBE);
for (uint8_t i = 0; i < 9; i++) data[i] = ds.read();
if (crc8(data, 8) != data[8]) return false;
int16_t raw = (int16_t)((data[1] << 8) | data[0]);
*dest = (raw * 5) / 8;
return true;
}
void resetTemp() {
ds.reset();
ds.write(0xCC);
ds.write(0x44);
}
void gettingTemp() {
int16_t temp;
for (uint8_t i = 0; i < 10; i++) {
if (readDS18B20(ADDR_DS18B20[i], &temp)) {
Temper[i] = temp;
} else {
Temper[i] = 22222; // Ошибка датчика
}
}
gettingTempComplete = true;
}
// ===================================================
// 12. ОБРАБОТЧИК ИМПУЛЬСОВ РАСХОДОМЕРА ВОДЫ
// ===================================================
void voda_tick() {
boolean current_status = digitalRead(pinVoda_tick);
if (stateCheck_voda && !current_status) {
tmr = millis();
gWaterFlag = true;
stateCheck_voda = current_status;
return;
}
if (!stateCheck_voda && current_status) {
stateCheck_voda = current_status;
return;
}
if (current_status) {
gWaterFlag = false;
return;
}
if (!current_status && (millis() - tmr >= 1000) && gWaterFlag == true) {
if (WaterStartTimer == 0) {
WaterStartTimer = millis();
gWaterFlag = false;
return;
}
WaterCountTimer = millis() - WaterStartTimer;
WaterStartTimer = millis();
gWaterFlag = false;
if (FirstImpulsWater == true) SecondImpulsWater = true;
FirstImpulsWater = true;
}
}
// ===================================================
// 13. ОБРАБОТЧИК ИМПУЛЬСОВ ЭЛЕКТРОСЧЁТЧИКА
// ===================================================
void electr_tick() {
boolean current_status = digitalRead(pinElectr_tick);
if (stateCheck_electr && !current_status) {
tmr2 = millis();
ElectrFlag = true;
stateCheck_electr = current_status;
return;
}
if (!stateCheck_electr && current_status) {
stateCheck_electr = current_status;
return;
}
if (current_status) {
ElectrFlag = false;
return;
}
if (!current_status && (millis() - tmr2 >= 50) && ElectrFlag == true) {
if (ElectrStartTimer == 0) {
ElectrStartTimer = millis();
ElectrFlag = false;
return;
}
ElectrCountTimer = millis() - ElectrStartTimer;
ElectrStartTimer = millis();
ElectrFlag = false;
}
}
// ===================================================
// 14. ПРИЁМ ПАРАМЕТРОВ ОТ MEGA (исправленная версия)
// ===================================================
void receiveFromMega() {
// Проверяем, есть ли в буфере достаточно данных для полного пакета
if (Serial.available() >= sizeof(DataFromMega)) {
DataFromMega rx;
// Читаем с таймаутом 50 мс, чтобы не блокировать цикл
Serial.setTimeout(50);
if (Serial.readBytes((uint8_t*)&rx, sizeof(rx)) == sizeof(rx)) {
// ПРОВЕРЯЕМ CRC: вычисляем для данных (без поля crc)
uint8_t calculatedCrc = crc8(&rx, sizeof(rx) - 1);
if (calculatedCrc == rx.crc) {
// === ПАКЕТ ПРИНЯТ КОРРЕКТНО ===
output_to_relays = rx.output_from_Mega_to_nano;
// Обработка времени перемещения клапана
if (bitRead(output_to_relays, 7) == HIGH)
timePrivodDown = rx.actuatorMoveTime * 100;
else if (bitRead(output_to_relays, 8) == HIGH)
timePrivodUp = rx.actuatorMoveTime * 100;
else {
timePrivodDown = 0;
timePrivodUp = 0;
}
// Сбрасываем ошибки CRC
bitClear(mistakesFrom_nano, 0);
mistakesFrom_nano_0_Count = 0;
// Обновляем время последнего успешного приёма
lastRxTime = millis();
bitClear(mistakesFrom_nano, 2);
mistakesFrom_nano_2_Count = 0;
} else {
// === ОШИБКА CRC ===
bitSet(mistakesFrom_nano, 0);
mistakesFrom_nano_0_Count++;
if (mistakesFrom_nano_0_Count > 3) {
bitSet(mistakesFrom_nano, 1);
output_to_relays = 0; // аварийный останов
}
}
}
}
// ====== ПРОВЕРКА ПОТЕРИ СВЯЗИ ======
uint32_t timeSinceLastRx = millis() - lastRxTime;
if (timeSinceLastRx >= 15000) {
bitSet(mistakesFrom_nano, 2);
mistakesFrom_nano_2_Count++;
lastRxTime = millis(); // чтобы не увеличивать счётчик каждую миллисекунду
if (mistakesFrom_nano_2_Count > 3) {
bitSet(mistakesFrom_nano, 3);
output_to_relays = 0; // аварийный останов
}
}
}
// ===================================================
// 15. УПРАВЛЕНИЕ ПРИВОДОМ РЕГУЛИРУЮЩЕГО КЛАПАНА
// ===================================================
void actuatorMove() {
// Закрывание (бит 7)
if (bitRead(output_to_relays, 7) == HIGH && digitalRead(13) == HIGH) {
TimerStartPrivodDown = millis();
digitalWrite(13, LOW);
}
if (digitalRead(13) == LOW && millis() - TimerStartPrivodDown >= timePrivodDown) {
digitalWrite(13, HIGH);
bitClear(output_to_relays, 7);
}
// Открывание (бит 8)
if (bitRead(output_to_relays, 8) == HIGH && digitalRead(12) == HIGH) {
TimerStartPrivodUp = millis();
digitalWrite(12, LOW);
}
if (digitalRead(12) == LOW && millis() - TimerStartPrivodUp >= timePrivodUp) {
digitalWrite(12, HIGH);
bitClear(output_to_relays, 8);
}
}
// ===================================================
// 16. ОТПРАВКА ДАННЫХ НА MEGA (исправленная версия)
// ===================================================
void sendToMega() {
// Заполняем структуру данными
dataToMega.t1 = Temper[0];
dataToMega.t2 = Temper[1];
dataToMega.t3 = Temper[2];
dataToMega.t4 = Temper[3];
dataToMega.t5 = Temper[4];
dataToMega.t6 = Temper[5];
dataToMega.t7 = Temper[6];
dataToMega.t8 = Temper[7];
dataToMega.t9 = Temper[8];
dataToMega.t10 = Temper[9];
dataToMega.P = WaterPresure;
dataToMega.minutes = rtc.getMinutes();
dataToMega.hours = rtc.getHours();
dataToMega.days = rtc.getDate();
dataToMega.months = rtc.getMonth();
dataToMega.Water = (uint8_t)gWater;
dataToMega.Electr = (uint8_t)wElectr;
dataToMega.relaysState = output_to_relays;
dataToMega.mistakesFrom_nano = mistakesFrom_nano;
// ВЫЧИСЛЯЕМ CRC ТОЛЬКО ДЛЯ ДАННЫХ (без поля crc)
dataToMega.crc = crc8(&dataToMega, sizeof(dataToMega) - 1);
// Отправляем всю структуру (включая CRC)
Serial.write((uint8_t*)&dataToMega, sizeof(dataToMega));
}
// ===================================================
// 17. УПРАВЛЕНИЕ РЕЛЕ (физические выходы)
// ===================================================
void setRelays() {
// 0 - Дизельный котёл
digitalWrite(9, bitRead(output_to_relays, 0) ? LOW : HIGH);
// 1 - ТЭН 2.5 кВт
digitalWrite(8, bitRead(output_to_relays, 1) ? LOW : HIGH);
// 2 - ТЭН 7.5 кВт
digitalWrite(7, bitRead(output_to_relays, 2) ? LOW : HIGH);
// 3 - Шаровой кран
digitalWrite(6, bitRead(output_to_relays, 3) ? LOW : HIGH);
// 4 - Насос радиаторов
digitalWrite(10, bitRead(output_to_relays, 4) ? LOW : HIGH);
// 5 - Насос тёплых полов
digitalWrite(A3, bitRead(output_to_relays, 5) ? LOW : HIGH);
// 6 - Вентилятор
digitalWrite(11, bitRead(output_to_relays, 6) ? LOW : HIGH);
// 7 - Закрывание клапана
digitalWrite(13, bitRead(output_to_relays, 7) ? LOW : HIGH);
// 8 - Открывание клапана
digitalWrite(12, bitRead(output_to_relays, 8) ? LOW : HIGH);
// 9 - Компрессор
digitalWrite(A2, bitRead(output_to_relays, 9) ? LOW : HIGH);
// 10 - Четырёхходовой клапан
digitalWrite(A1, bitRead(output_to_relays, 10) ? LOW : HIGH);
// 11 - Насос теплообменника
digitalWrite(A0, bitRead(output_to_relays, 11) ? LOW : HIGH);
// ШИМ для вентилятора
analogWrite(3, PWM);
}
// ===================================================
// 18. НАЧАЛЬНАЯ НАСТРОЙКА
// ===================================================
void setup() {
Serial.begin(9600);
// === Настройка пинов ===
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
// Выходы реле (HIGH = выключено, LOW = включено)
pinMode(6, OUTPUT); digitalWrite(6, HIGH);
pinMode(7, OUTPUT); digitalWrite(7, HIGH);
pinMode(8, OUTPUT); digitalWrite(8, HIGH);
pinMode(9, OUTPUT); digitalWrite(9, HIGH);
pinMode(10, OUTPUT); digitalWrite(10, HIGH);
pinMode(11, OUTPUT); digitalWrite(11, HIGH);
pinMode(12, OUTPUT); digitalWrite(12, HIGH);
pinMode(13, OUTPUT); digitalWrite(13, HIGH);
pinMode(A0, OUTPUT); digitalWrite(A0, HIGH);
pinMode(A1, OUTPUT); digitalWrite(A1, HIGH);
pinMode(A2, OUTPUT); digitalWrite(A2, HIGH);
pinMode(A3, OUTPUT); digitalWrite(A3, HIGH);
// === Часы реального времени ===
if (rtc.lostPower()) rtc.setTime(COMPILE_TIME);
delay(1000);
}
// ===================================================
// 19. ГЛАВНЫЙ ЦИКЛ
// ===================================================
void loop() {
// === Обработчики импульсов ===
voda_tick();
electr_tick();
// === Приём данных от Mega ===
receiveFromMega();
// === Управление клапаном ===
actuatorMove();
// === Цикл 10 секунд ===
static uint32_t timer = 0;
static uint8_t step = 0;
// Шаг 1: Запуск измерения температур (2 сек)
if (millis() - timer >= 2000 && step < 1) {
resetTemp();
step = 1;
}
// Шаг 2: Чтение температур (4 сек)
if (millis() - timer >= 4000 && step < 2) {
gettingTemp();
step = 2;
}
// Шаг 3: Управление (6 сек)
if (millis() - timer >= 6000 && step < 3) {
// === Давление ===
if (analogRead(A6) < 307) WaterPresure = 2;
else if (analogRead(A7) < 307) WaterPresure = 3;
else WaterPresure = 1;
// === Расход воды ===
if (SecondImpulsWater == false) gWater = 10;
else gWater = round(3600000 / WaterCountTimer);
// === Мощность ===
if (ElectrCountTimer == 0 || (millis() - ElectrStartTimer > 60000))
wElectr = 0;
else wElectr = 72000 / ElectrCountTimer;
step = 3;
}
// Шаг 4: Управление реле (8 сек)
if (millis() - timer >= 8000 && step < 4) {
setRelays();
step = 4;
}
// Шаг 5: Отправка данных на Mega (10 сек)
if (millis() - timer >= 10000 && step < 5) {
if (gettingTempComplete) {
sendToMega();
gettingTempComplete = false;
step = 5;
}
timer = millis();
step = 0;
}
}
/*
ПРОГРАММА ДЛЯ КОНТРОЛЛЕРА MEGA
Версия с исправленной CRC и улучшенной надёжностью связи
*/
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;
// ===================================================
// 1. НАСТРОЙКИ ДИСПЛЕЯ
// ===================================================
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4
// ===================================================
// 2. ЦВЕТА
// ===================================================
#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 DARKGREY 0x7BEF
#define DARKGREEN 0x0C80
// ===================================================
// 3. ПЕРЕМЕННЫЕ
// ===================================================
uint8_t kindDisplay = 1;
uint8_t kindDisplayPrev = 1;
uint16_t mistakes = 0;
//бит 0 - ошибка CRC при передаче данных от Mega к nano, менее нескольких раз подряд
//бит 1 - ошибка CRC при передаче данных от Mega к nano, более нескольких раз подряд
//бит 2 - ошибка потери связи с МК Mega, менее нескольких раз подряд
//бит 3 - ошибка потери связи с МК Mega, более нескольких раз подряд
//бит 15 - ошибка CRC при передаче данных от nano к Mega, менее нескольких раз подряд
//бит 14 - ошибка CRC при передаче данных от nano к Mega, более нескольких раз подряд
//бит 13 - ошибка потери связи с МК nano, менее нескольких раз подряд
//бит 12 - ошибка потери связи с МК nano, более нескольких раз подряд
uint8_t mistake_15_Count = 0; // счётчик количества ошибок подряд, mistakes, бит 15
uint8_t mistake_13_Count = 0; // счётчик количества ошибок подряд, mistakes, бит 13
uint16_t output_from_Mega_to_nano = 0;
//0 - дизельный котёл и насос дизельного котла
//1 - ТЭН 2,5 кВт
//2 - ТЭН 7,5 кВт
//3 - шаровой кран
//4 - насос радиаторов
//5 - насос тёплых полов
//6 - вентилятор наружного блока
//7 - закрывание клапана
//8 - открывание клапана
//9 - компрессор
//10 - четырёхходовой клапан
//11 - насос пластинчатого теплообменника
uint8_t actuatorMoveTime = 0; // время перемещения клапана (в 0,1 сек.: 10 = 1 сек.)
uint32_t actuatorTimer = 0; // для выдержки времени между передачей команды клапану
uint32_t lastGet = 0; // время последнего получения данных от nano
uint32_t lastSend = 0; // время последней отправки команд на nano
uint32_t lastControl = 0; // время последнего формирования команд
boolean gettingFrom_nano = false; // флаг о получении данных от nano
//-------------- проверка работы реле ---------------
uint8_t cycleCount = 0;
//-------------- (проверка работы реле) ---------------
// Данные от nano
struct DataFromNano {
int16_t t1, t2, t3, t4, t5, t6, t7, t8, t9, t10;
uint8_t P;
uint8_t minutes, hours, days, months;
uint8_t Water;
uint8_t Electr;
uint16_t relaysState;
uint8_t mistakesFrom_nano;
uint8_t crc;
} rxData;
// Данные от UNO
struct DataFromUNO {
uint8_t Display;
} rxDataFromUNO;
// Массивы для графиков
uint8_t t1_Graph[320], t2_Graph[320], t3_Graph[320], t4_Graph[320];
uint8_t t5_Graph[320], t6_Graph[320], t7_Graph[320], t8_Graph[320];
uint8_t t9_Graph[320], t10_Graph[320];
uint8_t Water_Graph[320], Electr_Graph[320];
// ===================================================
// 4. CRC
// ===================================================
uint8_t crc8(const void* data, size_t length, uint8_t crc = 0) {
const uint8_t* p = (const uint8_t*)data;
while (length--) {
uint8_t b = *p++, j = 8;
while (j--) {
crc = ((crc ^ b) & 1) ? (crc >> 1) ^ 0x8C : (crc >> 1);
b >>= 1;
}
}
return crc;
}
// ===================================================
// 5. ПРЕОБРАЗОВАНИЕ ДЛЯ ГРАФИКОВ
// ===================================================
int16_t getDataForUintMassiv(int16_t a) {
if (a < -400) return 0;
else if (a >= -400 && a < 500) return round((a + 400) / 5);
else if (a >= 500 && a < 1250) return round(180 + (a - 500) / 10);
else return 255;
}
uint8_t getDataForUintMassiv8(uint8_t a) {
if (a <= 160) return round(40 + a);
else if (a < 255) return round(200 + (a - 160) / 10);
else return 255;
}
// ===================================================
// 6. ОТРИСОВКА ГРАФИКОВ
// ===================================================
void getGraphics(uint16_t c, uint16_t d, uint8_t g[320]) {
for (int16_t x = 1; x < 320; x++) {
tft.drawLine(x - 1, (c - g[x - 1]), x, (c - g[x]), d);
}
}
// ===================================================
// 7. ОСНОВНОЙ ЭКРАН
// ===================================================
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:");
}
// ===================================================
// 8. ТЕКСТОВЫЙ РЕЖИМ
// ===================================================
void getAllLastDataToDisplay() {
tft.fillScreen(BLACK);
tft.setTextSize(1);
tft.setTextColor(YELLOW);
tft.setCursor(0, 0); tft.print("tRoom: "); tft.print(rxData.t1 / 10.0); tft.print(" C");
tft.setCursor(0, 10); tft.print("tGS: "); tft.print(rxData.t2 / 10.0); tft.print(" C");
tft.setCursor(0, 20); tft.print("tBA: "); tft.print(rxData.t3 / 10.0); tft.print(" C");
tft.setCursor(0, 30); tft.print("tOutdoor: "); tft.print(rxData.t4 / 10.0); tft.print(" C");
tft.setCursor(0, 40); tft.print("tKompressorOut: "); tft.print(rxData.t5 / 10.0); tft.print(" C");
tft.setCursor(0, 50); tft.print("tWaterIn: "); tft.print(rxData.t6 / 10.0); tft.print(" C");
tft.setCursor(0, 60); tft.print("tWaterOut: "); tft.print(rxData.t7 / 10.0); tft.print(" C");
tft.setCursor(0, 70); tft.print("tKompressorIn: "); tft.print(rxData.t8 / 10.0); tft.print(" C");
tft.setCursor(0, 80); tft.print("tDefrost: "); tft.print(rxData.t9 / 10.0); tft.print(" C");
tft.setCursor(0, 90); tft.print("tEvaporator: "); tft.print(rxData.t10 / 10.0); tft.print(" C");
tft.setCursor(0, 100); tft.print("Pressure: ");
if (rxData.P == 1) tft.print("OK");
else if (rxData.P == 2) tft.print("HIGH");
else tft.print("LOW");
tft.setCursor(0, 110); tft.print("Time: "); tft.print(rxData.hours); tft.print(":"); tft.print(rxData.minutes);
tft.setCursor(0, 120); tft.print("Date: "); tft.print(rxData.days); tft.print("."); tft.print(rxData.months);
tft.setCursor(0, 130); tft.print("Water flow: "); tft.print(rxData.Water / 100.0); tft.print(" m3/h");
tft.setCursor(0, 140); tft.print("Power: "); tft.print(rxData.Electr / 100.0); tft.print(" W");
tft.setCursor(0, 150); tft.print("Relays: "); tft.print(rxData.relaysState, BIN);
tft.setCursor(0, 160); tft.print("Mistakes: "); tft.print(mistakes, BIN);
}
// ===================================================
// 9. ОТОБРАЖЕНИЕ ОШИБОК
// ===================================================
void showMistakes() {
tft.fillScreen(BLACK);
tft.setTextSize(2);
tft.setTextColor(RED);
tft.setCursor(0, 80);
tft.print("❌ ERROR");
tft.setCursor(0, 120);
tft.setTextSize(1);
if (bitRead(mistakes, 0) == HIGH)
tft.println("CRC error from Mega");
if (bitRead(mistakes, 1) == HIGH)
tft.println("Stop: CRC error from Mega");
if (bitRead(mistakes, 2) == HIGH)
tft.println("Connection from Mega to nano LOST!");
if (bitRead(mistakes, 3) == HIGH)
tft.println("Stop: connection from Mega to nano LOST!");
if (bitRead(mistakes, 15) == HIGH)
tft.println("CRC error from nano");
if (bitRead(mistakes, 14) == HIGH)
tft.println("Stop: CRC error from nano");
if (bitRead(mistakes, 13) == HIGH)
tft.println("Connection from nano to Mega LOST!");
if (bitRead(mistakes, 12) == HIGH)
tft.println("Stop: connection from nano to Mega LOST!");
if (mistakes == 0)
tft.println("Unknown error");
}
// ===================================================
// 10. ОТПРАВКА ПАРАМЕТРОВ НА NANO (исправленная версия)
// ===================================================
void sendParamsToNano() {
struct ParamsToNano {
uint16_t output_from_Mega_to_nano;
uint8_t actuatorMoveTime;
uint8_t crc;
} params;
params.output_from_Mega_to_nano = output_from_Mega_to_nano;
params.actuatorMoveTime = actuatorMoveTime;
params.crc = crc8(¶ms, sizeof(params) - 1);
Serial1.write((uint8_t*)¶ms, sizeof(params));
}
// ===================================================
// 11. ПРИЁМ ДАННЫХ ОТ NANO (новая функция)
// ===================================================
void receiveFromNano() {
// Проверяем наличие полного пакета
if (Serial1.available() >= sizeof(DataFromNano)) {
DataFromNano temp;
// Читаем с таймаутом 50 мс
Serial1.setTimeout(50);
if (Serial1.readBytes((uint8_t*)&temp, sizeof(temp)) == sizeof(temp)) {
// ПРОВЕРЯЕМ CRC (без поля crc)
uint8_t calculatedCrc = crc8(&temp, sizeof(temp) - 1);
if (calculatedCrc == temp.crc) {
// === ДАННЫЕ ПРИНЯТЫ КОРРЕКТНО ===
rxData = temp;
updateArrays();
updateMistakesFromNano();
// Сбрасываем ошибки связи с Nano
bitClear(mistakes, 15);
mistake_15_Count = 0;
bitClear(mistakes, 13);
mistake_13_Count = 0;
lastGet = millis();
gettingFrom_nano = true;
// Обновляем дисплей (если нет ошибок)
if (mistakes == 0) {
if (kindDisplay == 1) {
getAllLastDataToDisplay();
} else {
getMainDisplay();
getGraphics(245, RED, t1_Graph);
getGraphics(245, YELLOW, t2_Graph);
getGraphics(245, GREEN, t3_Graph);
getGraphics(245, MAGENTA, t4_Graph);
getGraphics(245, CYAN, t5_Graph);
getGraphics(245, RED, t6_Graph);
getGraphics(245, YELLOW, t7_Graph);
getGraphics(245, GREEN, t8_Graph);
getGraphics(245, MAGENTA, t9_Graph);
getGraphics(245, CYAN, t10_Graph);
getGraphics(475, RED, Water_Graph);
getGraphics(475, YELLOW, Electr_Graph);
}
}
} else {
// === ОШИБКА CRC ===
bitSet(mistakes, 15);
mistake_15_Count++;
if (mistake_15_Count > 3) {
bitSet(mistakes, 14);
}
showMistakes();
}
}
}
}
// ===================================================
// 12. ФОРМИРОВАНИЕ КОМАНДЫ РЕГУЛИРУЮЩЕМУ КЛАПАНУ
// ===================================================
void actuatorMove(String a, uint8_t b) {
if (millis() - actuatorTimer > 15000) { // если после предыдущего перемещения клапана прошло время
if (a == "DOWN")
bitSet(output_from_Mega_to_nano, 7); // закрывать регулирующий клапан
else if (a == "UP")
bitSet(output_from_Mega_to_nano, 8); // открывать регулирующий клапан
actuatorMoveTime = b;
actuatorTimer = millis();
}
}
// ===================================================
// 13. ФИКСАЦИЯ ОШИБОК ОТ NANO
// ===================================================
void updateMistakesFromNano() {
if (bitRead(rxData.mistakesFrom_nano, 0) == HIGH)
bitSet(mistakes, 0);
else
bitClear(mistakes, 0);
if (bitRead(rxData.mistakesFrom_nano, 1) == HIGH)
bitSet(mistakes, 1);
else
bitClear(mistakes, 1);
if (bitRead(rxData.mistakesFrom_nano, 2) == HIGH)
bitSet(mistakes, 2);
else
bitClear(mistakes, 2);
if (bitRead(rxData.mistakesFrom_nano, 3) == HIGH)
bitSet(mistakes, 3);
else
bitClear(mistakes, 3);
}
// ===================================================
// 14. СМЕНА ИЗОБРАЖЕНИЯ НА ДИСПЛЕЕ
// ===================================================
void selectDisplay() {
if (kindDisplay != kindDisplayPrev) {
if (kindDisplay == 1)
getAllLastDataToDisplay();
kindDisplayPrev = kindDisplay;
}
}
// ===================================================
// 15. ОБНОВЛЕНИЕ МАССИВОВ ДЛЯ ГРАФИКОВ
// ===================================================
void updateArrays() {
for (int i = 0; i < 319; i++) {
t1_Graph[i] = t1_Graph[i + 1];
t2_Graph[i] = t2_Graph[i + 1];
t3_Graph[i] = t3_Graph[i + 1];
t4_Graph[i] = t4_Graph[i + 1];
t5_Graph[i] = t5_Graph[i + 1];
t6_Graph[i] = t6_Graph[i + 1];
t7_Graph[i] = t7_Graph[i + 1];
t8_Graph[i] = t8_Graph[i + 1];
t9_Graph[i] = t9_Graph[i + 1];
t10_Graph[i] = t10_Graph[i + 1];
Water_Graph[i] = Water_Graph[i + 1];
Electr_Graph[i] = Electr_Graph[i + 1];
}
t1_Graph[319] = getDataForUintMassiv(rxData.t1);
t2_Graph[319] = getDataForUintMassiv(rxData.t2);
t3_Graph[319] = getDataForUintMassiv(rxData.t3);
t4_Graph[319] = getDataForUintMassiv(rxData.t4);
t5_Graph[319] = getDataForUintMassiv(rxData.t5);
t6_Graph[319] = getDataForUintMassiv(rxData.t6);
t7_Graph[319] = getDataForUintMassiv(rxData.t7);
t8_Graph[319] = getDataForUintMassiv(rxData.t8);
t9_Graph[319] = getDataForUintMassiv(rxData.t9);
t10_Graph[319] = getDataForUintMassiv(rxData.t10);
Water_Graph[319] = getDataForUintMassiv8(rxData.Water);
Electr_Graph[319] = getDataForUintMassiv8(rxData.Electr);
}
// ===================================================
// 16. НАЧАЛЬНАЯ НАСТРОЙКА
// ===================================================
void setup() {
Serial.begin(9600);
Serial1.begin(9600); // Связь с nano
Serial2.begin(9600); // Связь с UNO
// === Настройка дисплея ===
tft.reset();
tft.begin(38022);
tft.invertDisplay(true);
// === Инициализация графиков ===
for (int i = 0; i < 320; i++) {
t1_Graph[i] = t2_Graph[i] = t3_Graph[i] = t4_Graph[i] = 80;
t5_Graph[i] = t6_Graph[i] = t7_Graph[i] = t8_Graph[i] = 80;
t9_Graph[i] = t10_Graph[i] = 80;
Water_Graph[i] = Electr_Graph[i] = 40;
}
delay(1000);
getMainDisplay();
// Отправляем начальные параметры
delay(500);
sendParamsToNano();
}
// ===================================================
// 17. ГЛАВНЫЙ ЦИКЛ
// ===================================================
void loop() {
static uint32_t lastGet = 0; // для исчисления времени последних полученных данных
static uint32_t lastSend = 0; // для периодической отправки команд
selectDisplay(); // моментальная смена изображения на дисплее
// === 1. Приём данных от UNO ===
if (Serial2.available()) {
DataFromUNO window;
if (Serial2.readBytes((uint8_t*)&window, sizeof(window))) {
rxDataFromUNO = window;
kindDisplay = rxDataFromUNO.Display;
}
}
// === 2. Приём данных от nano ===
receiveFromNano();
// === 3. Периодическая отправка параметров на nano (каждые 2 секунды) ===
if (millis() - lastSend >= 2000) {
sendParamsToNano();
lastSend = millis();
}
// === 4. Формирование команд для реле (ваша логика) ===
// (этот блок можно выполнять раз в 5 секунд или по событию)
static uint32_t lastControl = 0;
if (millis() - lastControl >= 5000 && gettingFrom_nano == true) {
//-------------- проверка работы реле ---------------
cycleCount++;
if (cycleCount > 24)
bitSet(output_from_Mega_to_nano, 0); // включить дизельный котёл и насос дизельного котла
if (cycleCount > 22)
bitSet(output_from_Mega_to_nano, 1); // включить ТЭН 2,5 кВт
if (cycleCount > 20)
bitSet(output_from_Mega_to_nano, 2); // включить ТЭН 7,5 кВт
if (cycleCount > 18)
bitSet(output_from_Mega_to_nano, 3); // открыть шаровой кран
if (cycleCount == 17)
actuatorMove("DOWN", 20);
else
bitClear(output_from_Mega_to_nano, 7);
if (cycleCount == 15)
actuatorMove("UP", 20);
else
bitClear(output_from_Mega_to_nano, 8);
if (cycleCount > 12)
bitSet(output_from_Mega_to_nano, 6); // включить вентилятор наружного блока
if (cycleCount > 10)
bitSet(output_from_Mega_to_nano, 4); // включить насос радиаторов
if (cycleCount > 8)
bitSet(output_from_Mega_to_nano, 5); // включить насос тёплых полов
// if (cycleCount > 6)
// bitSet(output_from_Mega_to_nano, 9); // включить компрессор
// if (cycleCount > 4)
// bitSet(output_from_Mega_to_nano, 10); // включить четырёхходовый клапан
if (cycleCount > 2)
bitSet(output_from_Mega_to_nano, 11); // включить насос пластинчатого теплообменника
if (cycleCount > 26) {
cycleCount = 0;
output_from_Mega_to_nano = 0b0000000000000000;
}
//-------------- (проверка работы реле) ---------------
lastControl = millis();
gettingFrom_nano = false;
}
// === 5. Фиксация потери связи ===
if (millis() - lastGet > 15000) { // если в течение 15 сек. не было связи с nano
bitSet(mistakes, 13); // зафиксирована потеря связи с nano
mistake_13_Count++;
lastGet = millis(); // попытка связи с nano
if (mistake_13_Count > 3) {
bitSet(mistakes, 12); // зафиксирована продолжительная потеря связи с nano
output_from_Mega_to_nano = 0; // аварийный останов
}
showMistakes();
}
}
Основные изменения в коде:
1. Исправлена проверка CRC (самое важное):
На отправке: crc = crc8(&data, sizeof(data) - 1)
На приёме: if (crc8(&rx, sizeof(rx) - 1) == rx.crc)
2. Добавлена проверка на полный пакет:
if (Serial.available() >= sizeof(DataFromMega)) — ждём полный пакет, чтобы не читать частичные данные.
3. Установлен таймаут для чтения:
Serial.setTimeout(50) — не блокируем цикл более 50 мс.
4. Mega теперь отправляет команды каждые 2 секунды (независимо от приёма):
Это обеспечивает постоянное «сердцебиение» для Nano.
5. Создана отдельная функция receiveFromNano() для лучшей организации кода.
6. Исправлена логика сброса таймаута:
При ошибке CRC время последнего приёма не обновляется, что правильно.
Объясняю на пальцах. Использование serial интерфейса в железках - крайний случай. Сам интерфейс имеет только контроль четности. Поэтому применяется для отладки. При заливке - это отдельная песня, поскольку данный режим - монопольный и не прерывается. Применение wifi это не просто избавление от проводов, а использование TCP/IP протокола, где средств контроля передачи на порядок больше. И еще преймущесво в том, что это не связь точка-точка, а сеть из устройств. Это позволяет использовать штатные средства связи с ПК, смартфонами и другими устройствами, которые имеют сетевые возможности. Если вы поборите в себе этот порог непонимания, то дальше для вас открываются средства связи с различными фишками. Например можно диагностику отправлять в мессанжеры, если устройства будут иметь доступ, который может быть посредством тонкого соединения по gsm (мобильного интернет) с применением модулей gsm или роутера с gsm модемом. Это удобно.
Кто же первым вернёт ТС на землю ?
Никто. Такая уж судьба - все зависит от силы удара граблей, которые аккуратно разложены.
пусть развлекается, надо же чем-то заниматься
