У нас в Крыму морозы до -35 встречаются крайне редко, но обычные -5...-10 он вполне нормально переживал. При этом работал и что-то там показывал на экране - вероятно тепловыделение работает в качестве обогрева :) Корпус у меня практически герметичный.
AnDrEyKa_111, как с Вами связаться для консультации?
Даже не знаю в каком вопросе я смогу дать какую-либо консультацию... Я просто следовал схеме автора (с небольшими корректировками под свои нужды) и, честно говоря, не силён в этой всей схемотехнике... Ну и немного алгоритм перепилил, опять таки под свои конкретные нужды...
1. У меня вообще двухскоростной вентилятор, то есть с тремя контактами. А на схеме первый - это вентилятор, а второй - нагреватель. Кто где - настраивается в скетче, там если в дефайны внимательно посмотреть и почитать описание, то всё будет понятно.
2. Да, вот моя последняя версия (для удобства опубликую отдельно в сл. сообщении)
3. Настраивается переменной SENSORS_QUANTITY в самом начале, я обошёлся в итоге без вентиляции внутристенного пространства и у меня как раз то что вам надо, то есть три сенсора.
4. Нет, калибровка датчиков - это отдельная процедура и она обязательна. Она вроде была где-то в этой теме описана.
/*
Описание
1. Часы реального времени. Данные приходят от головного блока. Потом обновлются по таймеру (). Пришедшие данные имеют более высокий приоритет
1.1 Установить библиотеку Time library - <a href="https://github.com/PaulStoffregen/Time" rel="nofollow">https://github.com/PaulStoffregen/Time</a>
У нее не удачное имя (винды регистронезависимы и имя совпадает с системным -- среда глючит)
переименовать в stmTime директорию, файлы и ссылки в них !!!
*/
// - ОПЦИИ -------------------------------
//#define DEBUG // Отладочную информацию в ком порт посылает
//#define DEBUG2 // Отладочную информацию в ком порт посылает
//#define DEBUG3
//#define DEMO // Признак демонстрации - данные с датчиков генерятся рандом
//#define BEEP // Использовать пищалку
//#define RADIO // Признак использования радио модуля
//#define HEATER // Признак использования нагревателя
//#define INFOSCREEN // Куски с информационным экраном
#define SENSORS_QUANTITY 3 // Предусмотрено три варианта опции: 2, либо 3, либо 4 сенсора температуры
#define SERVO // Использование заслонок с серво-приводами
#define VERSION "v.076 09.02.20" // - базовая версия (оригинальная) - Ver. 0.75 05/01/16
// на её основе создана версия от AnDrEyKa
//#define ID 0x21 // уникально Идентификатор устройства (тип) - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
#if ID == 0x21 // Место установки блока - отоображается как заголовок колонки в таблице. Не более 6 символов а то не влезает
#define LABEL "Гараж"
#elif ID == 0x22
#define LABEL "Подвал"
#else
#define LABEL "none"
#endif
#pragma GCC optimize ("-Os")
//#pragma pack(push, 1) // выравнивание по одному байту ????????
#include <SPI.h>
#include <EEPROM.h>
#include <avr/wdt.h>
#include <leOS.h> // Шедуллер задач
#include <dht.h> // Дачик влажности и температуры
#include "Ucglib.h" // ВНИМАНИЕ использовать библиотеку не познее 1.01 справка <a href="https://code.google.com/p/ucglib/wiki/" rel="nofollow">https://code.google.com/p/ucglib/wiki/</a>
#include "rusFont.h" // Русские шрифты
#ifdef SERVO
#include <Adafruit_PWMServoDriver.h>
#endif
#ifdef RADIO
#include "nRF24L01.h" // Беcпроводной модуль надо использовать библиотеку <a href="http://tmrh20.github.io/RF24" rel="nofollow">http://tmrh20.github.io/RF24</a>
#include "RF24.h" // Беcпроводной модуль используются не стандартные функции <a href="https://github.com/TMRh20" rel="nofollow">https://github.com/TMRh20</a>
#endif
// Макросы для работы с портами скорость и место
#define SetOutput(port,bit) DDR ## port |= _BV(bit)
#define SetInput(port,bit) DDR ## port &= ~_BV(bit)
#define SetBit(port,bit) PORT ## port |= _BV(bit)
#define ClearBit(port,bit) PORT ## port &= ~_BV(bit)
#define WritePort(port,bit,value) PORT ## port = (PORT ## port & ~_BV(bit)) | ((value & 1) << bit)
#define ReadPort(port,bit) (PIN ## port >> bit) & 1
#define PullUp(port,bit) { SetInput(port,bit); SetBit(port,bit); }
#define Release(port,bit) { SetInput(port,bit); ClearBit(port,bit); }
// Мои макросы
#define MOTOR_BIT 0 // бит мотора в packet.flags
#define HEAT_BIT 1 // бит калорифера в packet.flags
#define ABS_H_BIT 2 // бит кодирования влажности абс или % в packet.flags
#define MODE_BIT 5 // первый бит режима в packet.flags
//#define MASK_MODE_BITS 0xe0 // маска для выделения номера режима в packet.flags
#define SET_MODE_BITS packet.flags |= (settingRAM.mode<<MODE_BIT) // запись номера режима в packet.flags
#define FLAG_ABS_H_ON packet.flags |= (1<<ABS_H_BIT) // бит ABS_H_BIT установить в 1
#define FLAG_ABS_H_OFF packet.flags &= ~(1<<ABS_H_BIT) // бит ABS_H_BIT установить в 0
#define FLAG_ABS_H_CHECK packet.flags & (1<<ABS_H_BIT) // бит ABS_H_BIT проверить на 1
#define FLAG_MOTOR_ON packet.flags |= (1<<MOTOR_BIT) // бит мотора установить в 1
#define FLAG_MOTOR_OFF packet.flags &= ~(1<<MOTOR_BIT) // бит мотора установить в 0
#define FLAG_MOTOR_CHECK packet.flags & (1<<MOTOR_BIT) // бит мотора проверить на 1
#define MOTOR_ON { WritePort(C,0,HIGH); FLAG_MOTOR_ON; } // включить мотор
#define MOTOR_OFF { WritePort(C,0,LOW) ; FLAG_MOTOR_OFF; } // выключить мотор
#ifdef SERVO
bool MotorFastSpeed=false;
#define MOTOR_FAST_SPEED { WritePort(C,2,HIGH); MotorFastSpeed=true;} // Переключить вент в быстрый режим
#define MOTOR_SLOW_SPEED { WritePort(C,2,LOW); MotorFastSpeed=false;} // Переключить вент в медленный режим
#endif
#define FLAG_HEAT_ON packet.flags |= (1<<HEAT_BIT) // бит калорифера установить в 1
#define FLAG_HEAT_OFF packet.flags &= ~(1<<HEAT_BIT) // бит калорифера установить в 0
#define FLAG_HEAT_CHECK packet.flags & (1<<HEAT_BIT) // бит калорифера проверить на 1
#define HEAT_ON { WritePort(C,3,HIGH); FLAG_HEAT_ON; } // включить калорифер
#define HEAT_OFF { WritePort(C,3,LOW); FLAG_HEAT_OFF; } // выключить калорифер
// - КОНСТАНТЫ --------------------------------------
//#define dH_OFF 20 // Гистерезис абсолютной влажности в сотых грамма на куб - переведена в переменную как 1/4 от дельта
#define dT_OFF 20 // Гистерезис температуры в сотых градуса
#define TEMP_LOW 150 // Температура подвала критическая - может быть замораживание (в сотых градуса) - система выключается и включается нагреватель
// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для каждого ID свой. ОШИБКИ ДОБАВЛЯЮТСЯ!!
#if ID == 0x21 // ОТКАЛИБРОВАНО не менять
#define TOUT_ERR +0 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR +0 // Ошибка гаражного датчика температуры в сотых долях градуса
#define TPIT_ERR +0 // Ошибка ямного датчика температуры в сотых долях градуса
#define TWALL_ERR +0 // Ошибка стенового датчика температуры в сотых долях градуса
#define HOUT_ERR -0 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR +0 // Ошибка гаражного датчика влажности в сотых долях %
#define HPIT_ERR -0 // Ошибка ямного датчика влажности в сотых долях %
#define HWALL_ERR +0 // Ошибка стенового датчика влажности в сотых долях %
#elif ID == 0x22 // ОТКАЛИБРОВАНО не менять
#define TOUT_ERR +0 // Ошибка уличного датчика температуры в сотых долях градуса +50
#define TIN_ERR +0 // Ошибка домового датчика температуры в сотых долях градуса +50
#define HOUT_ERR -0 // Ошибка уличного датчика влажности в сотых долях % -260
#define HIN_ERR +0 // Ошибка домового датчика влажности в сотых долях % +480
#elif defined DEBUG3
#define TOUT_ERR -2550 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR -3644 // Ошибка гаражного датчика температуры в сотых долях градуса
#define TPIT_ERR -3000 // Ошибка ямного датчика температуры в сотых долях градуса
#define TWALL_ERR 0 // Ошибка стенового датчика температуры в сотых долях градуса
#define HOUT_ERR 0 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR 52 // Ошибка гаражного датчика влажности в сотых долях %
#define HPIT_ERR 0 // Ошибка ямного датчика влажности в сотых долях %
#define HWALL_ERR 0 // Ошибка стенового датчика влажности в сотых долях %
#else
#define TOUT_ERR -1 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR 3 // Ошибка гаражного датчика температуры в сотых долях градуса
#define TPIT_ERR -2 // Ошибка ямного датчика температуры в сотых долях градуса
#define TWALL_ERR 0 // Ошибка стенового датчика температуры в сотых долях градуса
#define HOUT_ERR 0 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR 52 // Ошибка гаражного датчика влажности в сотых долях %
#define HPIT_ERR 0 // Ошибка ямного датчика влажности в сотых долях %
#define HWALL_ERR 0 // Ошибка стенового датчика влажности в сотых долях %
#endif
// Сохранённая погрешность по разным номерам датчиков, над NaCl H%=75.3
// 1 T= H=-1460
// 2 T= H=-960
// 3 T=-34 H=-2460 - на выброс
// 4 T= H=-900
// 5 T= H=430 - 2305 #1
// 6 T=-1 H=26 - 2305 #2
// 7 T=3 H=78 - 2305 #3
// 8 T=-2 H=-104 - 2305 #4
// 9 T= H=
// - ВРЕМЕНА ---------------------------------------
//#ifdef DEMO // Для демо все быстрее и случайным образом
#if defined(DEMO) || defined(DEBUG3)
#define NUM_SAMPLES 3 // Число усреднений измерений датчика ТОЛЬКО целые тысячи для точности часов
#define TIME_SCAN_SENSOR 2000 // Время опроса датчиков мсек, для демки быстрее
#define TIME_PRINT_CHART 4000 // Время вывода точки графика мсек, для демки быстрее
#define TIME_HOUR 50000 // Число мсек в часе, для демки быстрее
#else
#define NUM_SAMPLES 10 // Число усреднений измерений датчика
#define TIME_SCAN_SENSOR 3000 // Время опроса датчиков мсек ТОЛЬКО целые тысячи для точности часов
#define TIME_PRINT_CHART 300000 // Время вывода точки графика мсек
#define TIME_HOUR 3600000 // Число мсек в часе
#endif
#define RESET_TFT_KEY 9999 // Очень длительное нажатие на клавишу мсек, сброс контроллера дисплея и появляется основной экран данные сохраняются
#define LONG_KEY 2000 // Длительное нажатие кнопки мсек, появляется Экран инфо
#define SHORT_KEY 100 // Короткое нажатие кнопки более мсек
#define RESET_TEMP_KEY 4000 // Время нажатия кнопки более мсек для сброса температур
#define NRF24_CHANEL 100 // Номер канала nrf24
// НОГИ к которым прицеплена переферия (SPI используется для TFT и NRF24 - 11,12,13)
#define PIN_HEAT 17 // Ножка куда повешен калорифер A3 (port C3)
#ifdef BEEP
#define PIN_BEEP 15 // Ножка куда повешена пищалка A1 (port C1)
#endif
#define PIN_RELAY 14 // Ножка на которую повешено реле (SSR) вентилятора - аналоговый вход A0 через резистор 470 ом (port C0)
#define PIN_SPEED 16 // Ножка куда повешено реле переключения скоростей вентилятора A2 (port C2)
#define PIN_CS 10 // TFT дисплей spi CS
#define PIN_CD 9 // TFT дисплей spi CD
#define PIN_RESET 8 // TFT дисплей spi Reset дисплей
#define PIN_DHT22a 7 // Первый датчик DHT22 OUT УЛИЦА
#define PIN_DHT22b 6 // Второй датчик DHT22 IN ДОМ
#define PIN_DHT22c 5 // Третий датчик DHT22 PIT ЯМА
#define PIN_DHT22d 4 // Четвёртый датчик DHT22 WALL СТЕНЫ
#define PIN_KEY 3 // Кнопка, повешена на прерывание, что бы ресурсов не тратить (port D3)
#define PIN_IRQ_NRF24 2 // Ножка куда заведено прерывание от NRF24 (пока не используется)
// Настройки
#define NUM_SETTING 8 // Число вариантов настроек
#define BLOCK_OFF 0 // Выключено (вариант настроек)
#define HOOD_ON 1 // Режим вытяжки (вариант настроек)
#define COOLING 2 // Режим охлаждение (вариант настроек)
// Константы для масивов графиков
#define CHART_QUANTITY 2
#define DOTS_X_QUANTITY 120
#define DOTS_Y_QUANTITY 100
#define OUT 0
#define IN 1
#define PIT 2
#define WALL 3
#define TEMP 0
#define ABS_HUM 1
#define REL_HUM 2
//#include "stmTime.h" // Time library - <a href="https://github.com/PaulStoffregen/Time" rel="nofollow">https://github.com/PaulStoffregen/Time</a>
//#define TZ "UTC+3" // Часовой пояс
#ifdef RADIO
unsigned long tt=0; // Время пришедшее от головного блока если 0 то вроемя не приходило ничего не выводим
#endif
// АЦП ----------------------------------------
const long ConstADC=1126400; // Калибровка встроенного АЦП (встроенный ИОН) по умолчанию 1126400 дальше измеряем питание и смотрим на дисплей
Ucglib_ILI9341_18x240x320_HWSPI ucg(/*cd=*/PIN_CD, /*cs=*/PIN_CS, /*reset=*/PIN_RESET); // Аппаратный SPI на дисплей ILI9341
leOS myOS; // многозадачность
dht DHT; // Датчики температура влажность
bool infoScreen=false; // Признак отображениея иформационного экрана 1 - на экран ничего не выводится кроме информационного экрана
bool flagKey=false; // Флаг нажатия клавиши
bool pressKey=false; // Флаг необходимости обработки кнопки
unsigned long time_key=0; // Время нажатия копки
long time_key_res_temp=-1; // время нажатия кнопки для сброса температур -1 кнопка не нажата
long time_key_res_tft=-1; // время нажатия кнопки для сброса дисплея -1 кнопка не нажата
byte last_error=100; // Предыдущая ошибка чтения датчиков
byte dH_OFF=20; // Гистерезис абсолютной влажности в сотых грамма на куб - переведена в переменную как 1/4 от дельта
struct type_setting_eeprom // Структура для сохранения данных в eeprom
{
byte mode = 0; // Какой режим работы блока
unsigned long hour_unit=0; // мото часы блок измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
unsigned long hour_motor=0; // мото часы мотора измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
#ifdef HEATER
unsigned long hour_heat=0; // мото часы нагревателя измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
#endif
int16_t Min[SENSORS_QUANTITY], Max[SENSORS_QUANTITY];
int16_t tick_eeprom=0; // Переменная для хранения времени записи статистики раз в один час хранится в NUM_SAMPLES*TIME_SCAN_SENSOR сравнение с TIME_HOUR
};
type_setting_eeprom settingRAM; // Рабочая копия счетчиков в памяти
type_setting_eeprom settingEEPROM EEMEM; // Копия счетчиков в eeprom - туда пишем
// Пакет передаваемый, используется также для хранения результатов.
struct type_packet_NRF24 // Версия 2.4!! адаптация для stm32 Структура передаваемого пакета 32 байта - 32 максимум
{
byte DHT_error[SENSORS_QUANTITY]; // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал
int16_t Measurement[3][SENSORS_QUANTITY]={{0}}; // Текущие температуры в сотых градуса !!! место экономим
uint8_t flags=0x00; // байт флагов
// 0 бит - мотор включен/выключен
// 1 бит - нагреватель включен/выключен
// 2 бит -[1 - dH_min задается в сотых грамма на м*3] [0 - dH_min задается в ДЕСЯТЫХ процента от absHIn]
// 3-4 - пока пусто
// 5-7 - номер настройки = settingRAM.mode до 8 настроек, надо передавать, что бы на приемнике восстановить
uint8_t dH_min; // Порог включения вентилятора по РАЗНИЦЕ абсолютной влажности в сотых грамма на м*3 или в ДЕСЯТЫХ % см flags:2
uint8_t T_min; // Порог выключения вентилятора по температуре в ДЕСЯТЫХ долях градуса, только положительные значения
#ifdef RADIO
byte id=ID; // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
uint8_t count=0; // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
#endif
// char note[14] = LABEL; // Примечание не более 13 байт + "0" байт Русские буквы в два раза меньше т.к. UTF-8
} packet;
struct type_SensorError{
int16_t Temp;
int16_t RelHum;
} SensorError[SENSORS_QUANTITY];
struct type_sensors // структура для усреднения измерений
{
int num=0; // сколько отсчетов уже сложили не болле NUM_SAMPLES
long Sum[2][SENSORS_QUANTITY]={{0}};
int Measurement[3][SENSORS_QUANTITY]={{0}};
byte Pin[SENSORS_QUANTITY];
} sensors;
// Массивы для графиков
bool Fan[DOTS_X_QUANTITY];
#ifdef HEATER
bool Heat[DOTS_X_QUANTITY];
#endif
struct type_chart{
int8_t Dots[SENSORS_QUANTITY][DOTS_X_QUANTITY];
int16_t Max, //минимальная и максимальная границы графика
Min,
MinCaptionStep; // Минимальный шаг подписей на графике - должен быть кратный 100, иначе деление на 100 будет давать неверный zoom и график будет неправльно рисоваться
byte Zoom,
Captions,
CaptionStepDots;
} Chart[CHART_QUANTITY];
uint8_t posChart=0; // Позиция в массиве графиков - начало вывода от 0 до 120-1
uint8_t TimeChart=0; // Время до вывода очередной точки на график.
bool ChartMotor=false; // Признак работы мотора во время интервала графика если мотор был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
#ifdef HEATER
bool ChartHeat=false; // Признак работы нагревателя во время интервала графика если нагреватель был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
#endif
#ifdef RADIO // Радио модуль NRF42l
RF24 radio(PIN_CE, PIN_CSN); // определение управляющих ног
bool send_packet_ok=false; // признак удачной отправки последнего пакета
unsigned long err_ask=0; // число не полученных ответов
#endif
#ifdef SERVO
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
bool MainScreen=false;
class class_valve{
private:
byte Valve_num;
public:
int Opened, Closed;
public:
void Init(byte Num, int O, int C){
Valve_num = Num;
Opened = O;
Closed = C;
Open();
}
void Open(){
pwm.setPWM(Valve_num, 0, Opened);
delay(500);
}
void Close(){
pwm.setPWM(Valve_num, 0, Closed);
delay(500); }
};
class class_area{
private:
byte Valve_quantity;
byte Area_num;
public:
class_valve *Valves;
bool State;
public:
void Init(byte n, byte q){
Valves = (class_valve *) malloc(q * sizeof(class_valve));
Valve_quantity = q;
Area_num = n;
}
void Open(){
for (byte i=0; i<Valve_quantity; i++)
Valves[i].Open();
State=true;
UpdateScreen();
}
void Close(){
for (byte i=0; i<Valve_quantity; i++)
Valves[i].Close();
State=false;
UpdateScreen();
}
void UpdateScreen(){
if (MainScreen){
if(State)
ucg.setColor(0, 200, 0);
else
ucg.setColor(200, 0, 0);
ucg.drawHLine(211-55*Area_num, 48, 54);
}
}
} Area[SENSORS_QUANTITY-1];
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ПРОГРАММА
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
#if defined(DEBUG) || defined(DEBUG2) || defined(DEBUG3)
Serial.begin(9600);
#ifdef DEBUG
Serial.println(F("DEBUG MODE"));
#ifdef BEEP
Serial.println(F("BEEP ON"));
#else
Serial.println(F("BEEP OFF"));
#endif
#ifdef RADIO
Serial.println(F("RADIO ON"));
#else
Serial.println(F("RADIO OFF"));
#endif
#endif
#endif
reset_sum();
#ifdef RADIO // Радио модуль NRF42l первичная настройка
radio.begin();
radio.setDataRate(RF24_250KBPS); // выбор скорости RF24_250KBPS RF24_1MBPS RF24_2MBPS
radio.setPALevel(RF24_PA_MAX); // выходная мощность передатчика
radio.setChannel(NRF24_CHANEL); //тут установка канала
radio.setCRCLength(RF24_CRC_16); // использовать контрольную сумму в 16 бит
radio.setAutoAck(true); // выключить аппаратное потверждение
// radio.enableDynamicPayloads(); // разрешить Dynamic Payloads
radio.enableAckPayload(); // Разрешить ответ приемника AckPayload
radio.setRetries(50,10); // Количество пауза и количество повторов
// Рекомендуют первые 2-4 байта адреса устанавливать в E7 или 18 он проще детектируется чипом
radio.openWritingPipe(0xE7E7E7E7E1LL); // передатчик
radio.openReadingPipe(1,0xE7E7E7E7D2LL); // приемник
radio.startListening();
#endif
#ifdef BEEP
SetOutput(C,1); // Настройка ноги для динамика
WritePort(C,1,LOW);
#endif
SetInput(D,3); // Включена кнопка
WritePort(D,3,HIGH);
SetOutput(C,0); // Подключить Реле вентилятора
WritePort(C,0,LOW);
SetOutput(C,3); // Подключить Калорифер
WritePort(C,3,LOW);
#ifdef SERVO
SetOutput(C,2); // Подключить реле переключения скоростей вентилятора
WritePort(C,2,LOW);
#endif
sensors.Pin[OUT]=PIN_DHT22a;
sensors.Pin[IN]=PIN_DHT22b;
#if SENSORS_QUANTITY >= 3
sensors.Pin[PIT]=PIN_DHT22c;
#endif
#if SENSORS_QUANTITY >= 4
sensors.Pin[WALL]=PIN_DHT22d;
#endif
for (byte j=0; j<SENSORS_QUANTITY; j++){
pinMode(sensors.Pin[j], OUTPUT);
digitalWrite(sensors.Pin[j], HIGH);
}
SensorError[IN].Temp=TIN_ERR;
SensorError[IN].RelHum=HIN_ERR;
SensorError[OUT].Temp=TOUT_ERR;
SensorError[OUT].RelHum=HOUT_ERR;
#if SENSORS_QUANTITY >= 3
SensorError[PIT].Temp=TPIT_ERR;
SensorError[PIT].RelHum=HPIT_ERR;
#endif
#if SENSORS_QUANTITY >= 4
SensorError[WALL].Temp=TWALL_ERR;
SensorError[WALL].RelHum=HWALL_ERR;
#endif
Chart[TEMP].Max=2000;
Chart[TEMP].Min=-2000;
Chart[ABS_HUM].Max=2000;
Chart[ABS_HUM].Min=0;
Chart[TEMP].MinCaptionStep=500; // Минимальный шаг подписей на графике - должен быть кратный 100, иначе деление на 100 будет давать неверный zoom и график будет неправльно рисоваться
Chart[ABS_HUM].MinCaptionStep=100; // Минимальный шаг подписей на графике - должен быть кратный 100, иначе деление на 100 будет давать неверный zoom и график будет неправльно рисоваться
for (byte i=0; i<CHART_QUANTITY; i++){
for (byte j=0; j<SENSORS_QUANTITY; j++)
for (byte k=0; k<DOTS_X_QUANTITY; k++)
Chart[i].Dots[j][k]=-128;
Chart[i].Zoom=(Chart[i].Max-Chart[i].Min)/DOTS_Y_QUANTITY;
}
for (byte k=0; k<DOTS_X_QUANTITY; k++){
Fan[k]=false;
#ifdef HEATER
Heat[k]=false;
#endif
}
for (byte j=0; j<SENSORS_QUANTITY; j++){
settingRAM.Max[j]=-3500;
settingRAM.Min[j]=3500;
}
reset_ili9341(); // сброс дисплея
readEeprom(); // Прочитать настройки
byte tmp=ReadPort(D,3);
if (tmp==0){ // Если при включении нажата кнопка то стираем Eeprom
settingRAM.mode=0;
settingRAM.hour_unit=0;
settingRAM.hour_motor=0;
#ifdef HEATER
settingRAM.hour_heat=0;
#endif
for (byte j=0; j<SENSORS_QUANTITY; j++){
settingRAM.Max[j]=-3500;
settingRAM.Min[j]=3500;
}
ucg.setColor(255, 255, 255);
print_StrXY(10,50,F("Сброс настроек и счетчиков"));
for(byte i=0;i<3;i++){
delay(1000);
ucg.print(F(" ."));
}
writeEeprom(); // Запись в EEPROM
delay(1000);
ucg.clearScreen();
}
#ifdef SERVO
pwm.begin();
pwm.setPWMFreq(60); // Analog servos run at ~60 Hz updates
Area[0].Init(0, 1); // Задаём количество серво-клапанов в каждой зоне
Area[1].Init(1, 2);
// Клапаны гаража
Area[0].Valves[0].Init(0, 100, 335); // Настраиваем соответсвие между клапанами в зонах и выводами на плате драйвера, настраиваем положение "Открыто" и "закрыто" для каждого клапана
// Клапаны ямы
Area[1].Valves[0].Init(1, 100, 335);
Area[1].Valves[1].Init(2, 100, 335);
for(byte i=0; i<SENSORS_QUANTITY-1; i++){
Area[i].Open();}
#endif
#ifndef DEBUG3
boot_test(); // Тестирование исполнительных устройств
#endif
#ifdef SERVO
MainScreen = true;
#endif
wdt_enable(WDTO_8S); // Сторожевой таймер Для тестов не рекомендуется устанавливать значение менее 8 сек.
// Запуск задач по таймеру
myOS.begin();
myOS.addTask(measurement,TIME_SCAN_SENSOR); // Измерение
attachInterrupt(1, scanKey, CHANGE); // КНОПКА Прерывания по обоим фронтам
print_static(); // распечатать таблицу
packet.Measurement[TEMP][IN]=TEMP_LOW+1; // Для исключения включения обогревателя до измерений
#if SENSORS_QUANTITY >= 3
packet.Measurement[TEMP][PIT]=TEMP_LOW+1; // Для исключения включения обогревателя до измерений
#endif
Setting(); // Применить настройки
measurement(); // Считать данные
#ifdef BEEP
beep(300);
delay(300);
beep(600);
#endif
resetKey();
}
void loop()
{
wdt_reset(); // Сбросить сторожевой таймер
// Обработка нажатия кнопки
// 1. Высший приоритет - сброс контроллера дисплея если он зависнeт - держать долго сброс пройдет отпустить кнопку
if ((flagKey==true)&&(pressKey!=true)){
if (time_key_res_tft==-1) time_key_res_tft=millis(); // первый вход - нажата кнопка запомнить время нажатия
else if (millis()>time_key_res_tft+RESET_TFT_KEY){ // кнопка нажата более RESET_TFT_KEY сек сбрасываем дисплей
reset_ili9341(); // сброс дисплея
ucg.setColor(255, 255, 255);
print_StrXY(10,100,F("Сброс дисплея"));
print_StrXY(10,129,F("Кнопку нужно отпустить"));
delay(1000);
reset_ili9341(); // сброс дисплея
print_static(); // распечатать таблицу
Setting(); // Применить настройки
print_data(); // вывод усредненных значений
print_status(); // панель состояния
printChart(); // показать график
infoScreen=false; // нет информационного экрана
#ifdef BEEP
beep(400); // Звук нажатия на клавишу
#endif
resetKey();
}
}
// 2. Второй приоритет - сброс памяти температур работает только в информационном экране
#ifdef INFOSCREEN
if ((flagKey==true)&&(pressKey!=true)&&(infoScreen==true)) // Во время информационнного экрана нажата кнопка - подготовка и стирание температур min max
{
if (time_key_res_temp==-1) time_key_res_temp=millis(); // первый вход - нажата кнопка для стирания температур
else if (millis()>time_key_res_temp+RESET_TEMP_KEY){ // кнопка нажата более RESET_TEMP_KEY сек сбрасываем температуры
for (byte j=0; j<SENSORS_QUANTITY; j++){
settingRAM.Max[j]=3500;
settingRAM.Min[j]=-3500;
}
writeEeprom(); // Сбросили температуры сохраняем в EEPROM
#ifdef BEEP
beep(500); // Звук нажатия на клавишу
#endif
printInfo(); // Обновить экран с новыми температурами
resetKey();
}
}
#endif
else // 3. Остальной обработчик нажатия на кнопок - Кнопку надо нажать и отпустить
if ((flagKey!=false)&&(pressKey==true)){ // Кнопка была нажата и отпущена
// if (flagKey!=false) // Обработчик кнопки
#ifdef INFOSCREEN
if ((time_key>=LONG_KEY)&&(infoScreen!=true)) // Длительное нажатие кнопки и информационный экран не показан
printInfo(); // Вывод информационного экрана
else if (time_key >= SHORT_KEY){ // Короткое нажатие кнопки все остальное игнорируется
#endif
#ifdef BEEP
beep(30); // Звук нажатия на клавишу
#endif
#ifdef INFOSCREEN
if (infoScreen==true) clearInfo(); // если информационный экран показан то стереть его
else {
#endif
if (settingRAM.mode >= (NUM_SETTING-1) ) settingRAM.mode=0; // Кольцевой счетчик настроек
else settingRAM.mode++; // В противном случае следующая настройка
Setting();
#ifdef INFOSCREEN
}
}
#endif
resetKey(); // Сброс состояния кнопки
}
}
void resetKey(void) // Сброс состояния кнопки
{
flagKey=false;
pressKey=false;
time_key_res_temp=-1; // сброс состояния сброса температур
time_key_res_tft=-1; // сброс состояния сброса дисплея
}
void print_static() // Печать статической картинки
{
byte i, j;
cli();
// Заголовок
ucg.setColor(0, 0, 180);
ucg.drawBox(0, 0, 320-1, 23);
// ucg.setColor(250, 250, 250);
// ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
// print_StrXY(2,19,F("Управление климатом гаража"));
//ucg.print( hex(packet.id >> 4));
//ucg.print( hex(packet.id&0x0f));
#ifdef DEMO
ucg.print(F(" demo"));
#endif
// Таблица для данных
ucg.setColor(0, 200, 0);
for (i=0;i<5;i++)
ucg.drawHLine(0,25+23*i,320-1);
for (i=0;i<SENSORS_QUANTITY;i++)
ucg.drawVLine(265-i*55, 25, 24+23*3);
// Заголовки таблиц
ucg.setColor(255, 255, 0);
// В зависимости от id разные надписи - привязка местоположения блока к ID
// ucg.setPrintPos(180+30-9,25+0+18);
// ucg.print(packet.note);
print_StrXY(250+20,25+0+18,F("Улица"));
print_StrXY(215,25+0+18,F("Гараж"));
#if SENSORS_QUANTITY >= 3
print_StrXY(160,25+0+18,F("Яма"));
#endif
#if SENSORS_QUANTITY >= 4
print_StrXY(105,25+0+18,F("Стены"));
#endif
print_StrXY(0,25+23*1+18,F("Темп. *C"));
print_StrXY(0,25+23*2+18,F("Отн. вл. %"));
print_StrXY(0,25+23*3+18,F("Абс.вл. г/м3"));
// Графики
ucg.setColor(210, 210, 210);
// ucg.drawHLine(1,240-2,130);
// ucg.drawVLine(1,135,105);
// ucg.drawHLine(10+154,240-2,130);
// ucg.drawVLine(10+154,135,105);
print_StrXY(7,135+0,F("Температура"));
print_StrXY(7+162,135+0,F("Абс. влажность"));
// надписи на графиках
for(i=0;i<CHART_QUANTITY;i++){
print_chart_axes(i);
// Горизонтальная шкала по часам
for(j=0;j<=DOTS_X_QUANTITY;j+=12){
ucg.drawPixel(5+DOTS_X_QUANTITY-j+162*i,238);
ucg.drawPixel(5+DOTS_X_QUANTITY-j+162*i,239);
}
}
#ifdef SERVO
for(i=0; i<SENSORS_QUANTITY-1; i++)
Area[i].UpdateScreen();
#endif
}
void print_status(void) // Печать панели статуса Значки на статус панели
{
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
cli();
// 1. печать ошибки чтения датчиков
print_error_DHT();
// 2. Признак включения мотора или нагревателя
if (FLAG_MOTOR_CHECK) ucg.setColor(0, 240, 0);
#ifdef HEATER
else if (FLAG_HEAT_CHECK) ucg.setColor(240, 0, 0);
#endif
else ucg.setColor(0, 40, 0);
ucg.drawBox(290-32, 5, 14, 14);
#ifdef SERVO
if (FLAG_MOTOR_CHECK){
ucg.setColor(0, 40, 0);
ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
if(MotorFastSpeed){
print_StrXY(290-30,19,F("2"));
}else{
print_StrXY(290-30,19,F("1"));
}
}
#endif
#ifdef RADIO
byte temp;
// 3. Признак удачной передачи информации по радиоканалу - получение квитанции
if (send_packet_ok==true) ucg.setColor(0, 240, 0);
else ucg.setColor(0, 40, 0);
ucg.setPrintDir(3);
ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
print_StrXY(290-40,20,F(">>"));
ucg.setPrintDir(0);
// ucg.setFontMode(UCG_FONT_MODE_SOLID);
// 4. Вывод времени на экран
if (tt>0) // выводим если было первоначальная установка от головного блока
{
ucg.setColor(0, 0, 180); //
ucg.drawBox(180, 0, 45, 23);
ucg.setColor(250, 250, 0);
ucg.setPrintPos(180,19);
temp=hour(tt);
if (temp<10) ucg.print("0");
ucg.print(temp);
ucg.print(":");
temp=minute(tt);
if (temp<10) ucg.print("0");
ucg.print(temp);
}
#endif
sei();
}
void print_error_DHT(void) // Печать ошибки чтения датчиков выводится при каждом чтении датчика
{
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
// 1. печать ошибки чтения датчиков
for (byte j=0; j<SENSORS_QUANTITY; j++){
if (packet.DHT_error[j]!=last_error) { // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
cli();
last_error=packet.DHT_error[j];
ucg.setColor(0, 0, 180); // Сначала стереть
ucg.drawBox(280, 0, 36, 18);
ucg.setPrintPos(280,18);
ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
if (packet.DHT_error[j]>0) {
ucg.setColor(255, 100, 100);
print_StrXY(280,18,F("0x"));
ucg.print( hex(packet.DHT_error[j] >> 4));
ucg.print( hex(packet.DHT_error[j] & 0x0f));
}
else { ucg.setColor(200, 240, 0); ucg.print(F(" ok ")); }
sei();
}
}
}
// вывод на экран данных (то что меняется)
void print_data()
{
byte y=66, dx=55, dy=23;
int16_t x=270;
// Статистика по моточасам, время ведется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR а потом пересчитывается в часы при выводе.
settingRAM.hour_unit++;
if (FLAG_MOTOR_CHECK) settingRAM.hour_motor++; // если мотор включен
#ifdef HEATER
if (FLAG_HEAT_CHECK) settingRAM.hour_heat++; // если нагреватель включен
#endif
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
cli();
// Печать значений
ucg.setFontMode(UCG_FONT_MODE_SOLID);
for (byte j=0; j<SENSORS_QUANTITY; j++){
switch (j){
case 0:
ucg.setColor(0, 250, 100); // Цвет УЛИЦЫ
break;
case 1:
ucg.setColor(250, 0, 100); // Цвет ДОМА
break;
case 2:
ucg.setColor(0, 0, 255); // Цвет ЯМЫ
break;
case 3:
ucg.setColor(255, 255, 0); // Цвет СТЕН
}
print_floatXY(x-j*dx, y+0*dy,((float)packet.Measurement[TEMP][j])/100);
print_floatXY(x-j*dx, y+1*dy,((float)packet.Measurement[REL_HUM][j])/100);
print_floatXY(x-j*dx, y+2*dy,((float)packet.Measurement[ABS_HUM][j])/100);
}
sei();
}
void print_chart_axes(byte ChartNum){
int16_t j, CaptionStepDots, CaptionStep, Caption;
uint8_t x=132, y=237, dx=162;
ucg.setColor(0, 0, 0);
ucg.drawBox(x+ChartNum*dx, 130, 35, 110);
ucg.setColor(210, 210, 210);
ucg.setPrintPos(x+ChartNum*dx, y+3);
if (Chart[ChartNum].Min%100==0)
ucg.print(Chart[ChartNum].Min/100);
else
ucg.print((float)Chart[ChartNum].Min/100, 1);
Chart[ChartNum].Captions = (Chart[ChartNum].Max-Chart[ChartNum].Min)/Chart[ChartNum].MinCaptionStep;
while (Chart[ChartNum].Captions>4){
if (Chart[ChartNum].Captions>4)
Chart[ChartNum].Captions/=2;
}
CaptionStep=(int)(( (Chart[ChartNum].Max-Chart[ChartNum].Min)/(Chart[ChartNum].Captions) )/Chart[ChartNum].MinCaptionStep)*Chart[ChartNum].MinCaptionStep;
Chart[ChartNum].CaptionStepDots=CaptionStep/Chart[ChartNum].Zoom;
for(j=y+8-Chart[ChartNum].CaptionStepDots, Caption=Chart[ChartNum].Min+CaptionStep; j>=y+8-DOTS_Y_QUANTITY; j-=Chart[ChartNum].CaptionStepDots, Caption+=CaptionStep){
ucg.setPrintPos(x+ChartNum*dx, j);
if (Caption%100==0)
ucg.print(Caption/100);
else
ucg.print((float)Caption/100, 1);
}
}
// Печать графика на экране, добавляется одна точка и график сдвигается
void printChart()
{
byte i, j, k, y=237, yi, dx=162, xi, Zoom_old;
int16_t delta_Chart=0, Min_tmp=0, Max_tmp=0, x=5+DOTS_X_QUANTITY;
// ChartMotor=false;
// ChartHeat=false;
// ----------------- занесение новых значений в массив значений графиков и масштабирование всех значени если графики выходят за пределы
for (i=0; i<CHART_QUANTITY; i++){
for (j=0; j<SENSORS_QUANTITY; j++) //Переводим значение в единицах сенсаора в значение в точках графика
// Работаем через кольцевой буфер, Добавить новую точку в кольцевой буфер
if ( (int)((packet.Measurement[i][j]-Chart[i].Min)/Chart[i].Zoom) > 127) //защита от переполнения
Chart[i].Dots[j][posChart]=127;
else
Chart[i].Dots[j][posChart]=(packet.Measurement[i][j]-Chart[i].Min)/Chart[i].Zoom;
//Ищем в массивах данных минимум и максимум
Max_tmp=Chart[i].Dots[TEMP][posChart];
Min_tmp=Chart[i].Dots[TEMP][posChart];
for (k=0; k<DOTS_X_QUANTITY; k++)
for (j=0; j<SENSORS_QUANTITY; j++){
if ( (Chart[i].Dots[j][k]>-128) && (Chart[i].Dots[j][k]>Max_tmp) )
Max_tmp=Chart[i].Dots[j][k];
if ( (Chart[i].Dots[j][k]>-128) && (Chart[i].Dots[j][k]<Min_tmp) )
Min_tmp=Chart[i].Dots[j][k];
}
// Вычисляем границы графика, кратные MinCaptionStep
if (Min_tmp*Chart[i].Zoom+Chart[i].Min<0) // поправка минимума для отрицательных значений графика
Min_tmp=(int16_t)((Min_tmp*Chart[i].Zoom+Chart[i].Min)/Chart[i].MinCaptionStep)*Chart[i].MinCaptionStep-Chart[i].MinCaptionStep;
else
Min_tmp=(int16_t)((Min_tmp*Chart[i].Zoom+Chart[i].Min)/Chart[i].MinCaptionStep)*Chart[i].MinCaptionStep;
if (Max_tmp*Chart[i].Zoom+Chart[i].Min<0) // поправка максимума для отрицательных значений графика
Max_tmp=(int16_t)((Max_tmp*Chart[i].Zoom+Chart[i].Min)/Chart[i].MinCaptionStep)*Chart[i].MinCaptionStep;
else
Max_tmp=(int16_t)((Max_tmp*Chart[i].Zoom+Chart[i].Min)/Chart[i].MinCaptionStep)*Chart[i].MinCaptionStep+Chart[i].MinCaptionStep;
#ifdef DEBUG3
Serial.print("\nMin_tmp="); Serial.print(Min_tmp); Serial.print(" Max_tmp="); Serial.print(Max_tmp);
#endif
// Если границы графика сдвинулись - пересчитать значения в таблицах графиков
if ((Min_tmp!=Chart[i].Min)||(Max_tmp!=Chart[i].Max)){
Zoom_old=Chart[i].Zoom;
Chart[i].Zoom=(Max_tmp-Min_tmp)/DOTS_Y_QUANTITY;
for(k=0;k<DOTS_X_QUANTITY;k++){
for (j=0; j<SENSORS_QUANTITY; j++)
if(Chart[i].Dots[j][k]>-128)
Chart[i].Dots[j][k] = (Chart[i].Dots[j][k] * Zoom_old + Chart[i].Min - Min_tmp) / Chart[i].Zoom;
}
Chart[i].Max=Max_tmp;
Chart[i].Min=Min_tmp;
print_chart_axes(i);
}
}
if (ChartMotor) Fan[posChart]=true; else Fan[posChart]=false;
#ifdef HEATER
if (ChartHeat) Heat[posChart]=true; else Heat[posChart]=false;
#endif
//Отрисовка графиков - начало
if (infoScreen==false){ // если отображен информационный экран то ничего не выводим
cli();
for (i=0; i<CHART_QUANTITY; i++){
for (xi=0; xi<DOTS_X_QUANTITY; xi++){
// Вычислить координаты текущей точки в кольцевом массиве значений. Изменяются от 0 до 120-1
if (posChart<xi) k=DOTS_X_QUANTITY+posChart-xi; else k=posChart-xi;
// нарисовать фон в зависимости от статуса мотора
#ifdef HEATER
if (Heat[k]==true) // Heat
ucg.setColor(90, 60, 0); // Нагреватель был ключен - бледно желтый
else
#endif
if (Fan[k]==true)
ucg.setColor(0, 60, 90); // Мотор был ключен - бледно синий
else
ucg.setColor(0, 0, 0); // все выключено
ucg.drawVLine(x-xi+dx*i, y-DOTS_Y_QUANTITY, DOTS_Y_QUANTITY);
ucg.setColor(180, 180, 180);
if (xi%6==0) // Пунктирные линии графика - каждые 6 пикселей (т.е. каждые 30 минут)
for(yi=0; yi<=DOTS_Y_QUANTITY; yi+=Chart[i].CaptionStepDots)
ucg.drawPixel(x-xi+dx*i,y-yi);
// Вывод точки нужного цвета на график
for (j=0; j<SENSORS_QUANTITY; j++){
switch (j){
case 0:
ucg.setColor(0, 250, 100); // Цвет УЛИЦЫ
break;
case 1:
ucg.setColor(250, 0, 100); // Цвет ДОМА
break;
case 2:
ucg.setColor(0, 0, 255); // Цвет ЯМЫ
break;
case 3:
ucg.setColor(255, 255, 0); // Цвет СТЕН
}
ucg.drawPixel(x-xi+dx*i,y-Chart[i].Dots[j][k]);
}
}
}
sei();
}
//Отрисовка графиков - конец
#if defined(DEBUG) //|| defined(DEBUG2)
// Serial.print(F("\nPit T="));Serial.print((float)packet.tPit/100.0);Serial.print(F(" H=")); Serial.print((float)packet.relHPit/100.0); Serial.print(F("% abs H=")); Serial.println((float)packet.absHPit/100.0);
// Serial.print(F("\npacket.tIn="));Serial.print((float)packet.tPit);Serial.print(F(" tMinChart=")); Serial.print((float)tMinChart); Serial.print(F(" tMaxChart=")); Serial.print((float)tMaxChart); Serial.print(F(" tchart_zoom=")); Serial.println((float)tchart_zoom); Serial.print(F(" delta_tChart=")); Serial.println(delta_tChart);
// Serial.print("\n\ntIn="); Serial.print(packet.Measurement[TEMP][IN]); Serial.print(" tOut="); Serial.print(packet.Measurement[TEMP][OUT]);
// Serial.print(" tPit="); Serial.print(packet.Measurement[TEMP][PIT]); Serial.print(" tWall="); Serial.print(packet.Measurement[TEMP][WALL]);
Serial.print("\nChart[TEMP].Min="); Serial.print(Chart[TEMP].Min); Serial.print(" Chart[TEMP].Max="); Serial.print(Chart[TEMP].Max); Serial.print(" Chart[TEMP].Zoom="); Serial.print(Chart[TEMP].Zoom); Serial.print(" Chart[TEMP].CaptionStepDots="); Serial.print(Chart[TEMP].CaptionStepDots);
// Serial.print("\nMax: "); Serial.print(settingRAM.tOutMax); Serial.print(" "); Serial.print(settingRAM.tInMax); Serial.print(" "); Serial.print(settingRAM.tPitMax); Serial.print(" "); Serial.print(settingRAM.tWallMax);
// Serial.print("\nMin: "); Serial.print(settingRAM.tOutMin); Serial.print(" "); Serial.print(settingRAM.tInMin); Serial.print(" "); Serial.print(settingRAM.tPitMin); Serial.print(" "); Serial.print(settingRAM.tWallMin);
// Serial.print(F("tInChart[posChart]="));Serial.print(tInChart[posChart]);
// Serial.print(F(" tOutChart[posChart]="));Serial.print(tOutChart[posChart]);
/// Serial.print(F(" tPitChart[posChart]="));Serial.print(tPitChart[posChart]);
// Serial.print(F(" tWallChart[posChart]="));Serial.print(tWallChart[posChart]);
// beep(100);
#endif
if (posChart<120-1) posChart++; else posChart=0; // Изменили положение в буфере и Замкнули буфер
}
// ---ПЕРЕДАЧА ДАННЫХ ЧЕРЕЗ РАДИОМОДУЛЬ -----------------------------
#ifdef RADIO // Радио модуль NRF42l
void send_packet()
{ radio.powerUp();
radio.stopListening(); // Остановить приемник
delay(2);
cli();
radio.writeBlocking(&packet,sizeof(packet),200); // Writes 1 payload to the buffers
send_packet_ok=radio.txStandBy();
if ( radio.isAckPayloadAvailable() ) // Ждем получения -- основной блок передает текущее время
radio.read(&tt,sizeof(tt)); //... и имеем переменную tt с временем от приемника.
sei();
if (send_packet_ok!=true) err_ask++; // квитанция не получена добавить счетчик
#ifdef BEEP
if (send_packet_ok==true) beep(30); // Пакет передан успешно
#endif
#ifdef DEBUG
if (send_packet_ok==true) Serial.println(F("Packet sending ++++++++++"));
else Serial.println(F("Packet NOT sending -----------"));
#endif
radio.startListening(); // Включить приемник
packet.count++; // при переполнении сам сбросится
}
#endif
// Чтение датчика возвращает код ошибки:
// DHTLIB_OK 0
// DHTLIB_ERROR_CHECKSUM 1
// DHTLIB_ERROR_TIMEOUT 2
// DHTLIB_ERROR_CONNECT 3
// DHTLIB_ERROR_ACK_L 4
// DHTLIB_ERROR_ACK_H 5
byte readDHT(byte pin)
{
//delay(5);
cli();
byte err=-1*DHT.read22(pin); // Чтение датчика
sei();
return err;
}
// Измерение и обработка данных чтение датчиков --------------------------------------------------------------------------------
void measurement()
{ bool Error=false;
byte j;
myOS.pauseTask(measurement); // Обязательно здесь, а то датчики плохо читаются мешает leos
wdt_reset(); // Сбросить сторожевой таймер
#ifdef DEBUG
Serial.print(F("Sensor read samples:")); Serial.println(sensors.num);
#endif
for (j=0; j<SENSORS_QUANTITY; j++){
packet.DHT_error[j]=readDHT(sensors.Pin[j]);
if(packet.DHT_error[j]!=0) Error=true;
#ifdef DEMO
DHT.temperature=packet.Measurement[TEMP][j]/100+random(-20,30)/10.0;
if (DHT.temperature>20) DHT.temperature=19;
if (DHT.temperature<-10) DHT.temperature=-9;
DHT.humidity=packet.Measurement[REL_HUM][j]/100+(float)random(-5,8);
if (DHT.humidity>96) DHT.humidity=90;
if (DHT.humidity<1) DHT.humidity=10;
packet.DHT_error[j]=0; // в Демо режиме
#endif
sensors.Measurement[TEMP][j]=(int)(DHT.temperature*100.0)+SensorError[j].Temp; // Запомнить результаты для суммирования с учетом ошибок
sensors.Measurement[REL_HUM][j]=(int)(DHT.humidity*100.0)+SensorError[j].RelHum;
#ifdef DEBUG
Serial.print(F("Sensor #"));Serial.print(j);Serial.print(F(": T="));Serial.print((float)sensors.Measurement[TEMP][j]/100);Serial.print(F(" H=")); Serial.print((float)sensors.Measurement[REL_HUM][j]/100); Serial.print(F("% error=")); Serial.println(packet.DHT_error[j]);
#endif
}
print_error_DHT(); // Вывод ошибки чтения датчика при каждом чтении контроль за качеством связи с датчиками
// if (!Error){ // Если чтение без ошибок у ВСЕХ датчиков копим сумму для усреднения
for (j=0; j<SENSORS_QUANTITY; j++){
if (packet.DHT_error[j] == 0){ // Если чтение без ошибок у ВСЕХ датчиков копим сумму для усреднения
sensors.Sum[0][j]=sensors.Sum[0][j]+sensors.Measurement[TEMP][j];
sensors.Sum[1][j]=sensors.Sum[1][j]+sensors.Measurement[REL_HUM][j];
}
}
sensors.num++;
if (sensors.num>=NUM_SAMPLES){ // Пора усреднять и выводить значения
for (j=0; j<SENSORS_QUANTITY; j++){
// вычисление средних значений
packet.Measurement[TEMP][j]=sensors.Sum[0][j]/NUM_SAMPLES;
packet.Measurement[REL_HUM][j]=sensors.Sum[1][j]/NUM_SAMPLES;
packet.Measurement[ABS_HUM][j]=(int)(calculationAbsH((float)(packet.Measurement[TEMP][j]/100.0), (float)(packet.Measurement[REL_HUM][j]/100.0))*100.0);
}
reset_sum(); // Сброс счетчиков и сумм
#if defined(DEBUG)// || defined(DEBUG2)
Serial.println(F("\nAverage value >>>>>>>>>>"));
Serial.print(F("IN T="));Serial.print((float)packet.Measurement[TEMP][IN]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][IN]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][IN]/100.0);
Serial.print(F("OUT T="));Serial.print((float)packet.Measurement[TEMP][OUT]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][OUT]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][OUT]/100.0);
#if SENSORS_QUANTITY >= 3
Serial.print(F("Pit T="));Serial.print((float)packet.Measurement[TEMP][PIT]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][PIT]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][PIT]/100.0);
#endif
#if SENSORS_QUANTITY >= 4
Serial.print(F("Wall T="));Serial.print((float)packet.Measurement[TEMP][WALL]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][WALL]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][WALL]/100.0);
#endif
Serial.println(F("Average value <<<<<<<<<<<\n"));
#endif
CheckON(); // Проверка статуса вентилятора и включение
print_data(); // вывод усредненных значений
print_status(); // панель состояния
// Обновляем максимум и минимум температур в EEPROM
// Cделано не честно - обновляем раз в 30 секунд (NUM_SAMPLES*TIME_SCAN_SENSOR) а пишем раз в час, могут пропасть данные при сбросе
for (j=0; j<SENSORS_QUANTITY; j++){
if (settingRAM.Max[j] < packet.Measurement[TEMP][j]) settingRAM.Max[j]=packet.Measurement[TEMP][j];
else if (settingRAM.Min[j] > packet.Measurement[TEMP][j]) settingRAM.Min[j]=packet.Measurement[TEMP][j];
}
// Запись статистики в EEPROM
// Пишем в EEPROM не каждый раз!! экономим ресурс записей 100000 мин.
settingRAM.tick_eeprom++;
if (((long)settingRAM.tick_eeprom*NUM_SAMPLES*TIME_SCAN_SENSOR)>=(long)TIME_HOUR){ // пора писать eeprom
writeEeprom();
settingRAM.tick_eeprom=0;
}
if (FLAG_MOTOR_CHECK) ChartMotor=true; else ChartMotor=false; // Признак того что надо показывать включение мотора на графике
#ifdef HEATER
if (FLAG_HEAT_CHECK) ChartHeat=true; else ChartHeat=false; // Признак того что надо показывать включение нагревателя на графике
#endif
TimeChart++;
if ((long)((long)TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART){ // проврека не пора ли выводить график
printChart();
TimeChart=0; // Сдвиг графика и вывод новой точки
#if defined(DEBUG) || defined(DEBUG2)
Serial.println(F("\nAverage value >>>>>>>>>>"));
Serial.print(F("IN T="));Serial.print((float)packet.Measurement[TEMP][IN]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][IN]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][IN]/100.0);
Serial.print(F("OUT T="));Serial.print((float)packet.Measurement[TEMP][OUT]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][OUT]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][OUT]/100.0);
#if SENSORS_QUANTITY >= 3
Serial.print(F("Pit T="));Serial.print((float)packet.Measurement[TEMP][PIT]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][PIT]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][PIT]/100.0);
#endif
#if SENSORS_QUANTITY >= 4
Serial.print(F("Wall T="));Serial.print((float)packet.Measurement[TEMP][WALL]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][WALL]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][WALL]/100.0);
#endif
Serial.println(F("\n\n +++ Point add chart +++\n\n"));
#endif
#ifdef BEEP
// beep(50);
#endif
}
}
#ifdef RADIO // Радио модуль NRF42l
// Часы работают если только был получено время от головного блока (хотя бы один раз) в противном случае они даже не показываются на экране
if (tt>0) tt=tt+TIME_SCAN_SENSOR/1000; // Обновить текущее время - что бы часы шли и без связи с базовым блоком
#endif
myOS.restartTask(measurement); // Пустить задачи
}
#ifdef SERVO
// Проверка статуса вытяжки, не пора ли переключится
void CheckON(void)
{
int tmp=0;
byte i, ClosedAreas=0, Need4Motor;
// 0. Проверить замораживание подвала НАГРЕВАТЕЛЬ
#if SENSORS_QUANTITY >= 3
if (packet.Measurement[TEMP][PIT]<=TEMP_LOW) { // Контроль от промораживания подвала по идее здесь надо включать калорифер
#else
if (packet.Measurement[TEMP][IN]<=TEMP_LOW) { // Контроль от промораживания подвала по идее здесь надо включать калорифер
#endif
if(FLAG_MOTOR_CHECK){
MOTOR_OFF;
}
delay(3000);
for(i=0; i<SENSORS_QUANTITY-1; i++)
Area[i].Close();
MOTOR_SLOW_SPEED;
#ifdef HEATER
HEAT_ON;
#endif
return;
}
#ifdef HEATER
#if SENSORS_QUANTITY >= 3
if ((FLAG_HEAT_CHECK)&&(packet.Measurement[TEMP][PIT]>TEMP_LOW+dT_OFF+50))
#else
if ((FLAG_HEAT_CHECK)&&(packet.Measurement[TEMP][IN]>TEMP_LOW+dT_OFF+50))
#endif
HEAT_OFF; // Выключить калорифер когда температура поднимится на 0.5 градуса
#endif
// 1. Режимы не зависящие от влажности и температуры ВЫСШИЙ приоритет
// "Выключено"
if(settingRAM.mode==BLOCK_OFF) {
for(i=0; i<SENSORS_QUANTITY-1; i++)
Area[i].Open();
if(FLAG_MOTOR_CHECK){
MOTOR_OFF;
}
MOTOR_SLOW_SPEED;
return;
}
// "Режим вытяжки"
else if (settingRAM.mode==HOOD_ON ){
for(i=0; i<SENSORS_QUANTITY-1; i++)
Area[i].Open();
MOTOR_FAST_SPEED;
if(~FLAG_MOTOR_CHECK){
MOTOR_ON;
}
return;
}
// 2. Режим охлаждения (второй приоритет) температура внутри больше 10 градусов, темература снаружи меньше на dH_min градуса чем внутри, на влажность не смотрим
else if (settingRAM.mode==COOLING){ // Режим охлаждение
ClosedAreas=0;
for(i=1; i<SENSORS_QUANTITY; i++){
if(Area[i-1].State==false)
ClosedAreas++; // Считаем кол-во закрытых зон
}
if(ClosedAreas==SENSORS_QUANTITY-1){ //Если все зоны закрыты и вент работает останавливаем вент
if(FLAG_MOTOR_CHECK)
MOTOR_OFF;
MOTOR_SLOW_SPEED;
}
Need4Motor=SENSORS_QUANTITY-1; // Для подсчёта кол-ва зон, которым нужен вент
for(i=1; i<SENSORS_QUANTITY; i++){
if (packet.Measurement[TEMP][i]>(packet.T_min*10)){ // Если в зоне i тепло и
if ((packet.Measurement[TEMP][i]-packet.Measurement[TEMP][OUT])>packet.dH_min){ // Если в зоне теплее улицы на dH_min открываем клапаны и включаем вент
if(Area[i-1].State==false){
Area[i-1].Open();
ClosedAreas--;
}
if(i==IN)
MOTOR_FAST_SPEED;
if(~FLAG_MOTOR_CHECK)
MOTOR_ON;
}else if ((packet.Measurement[TEMP][i]-packet.Measurement[TEMP][OUT])>dT_OFF){ // иначе если в зоне теплее улицы на dT - просто открываем клапаны
if(Area[i-1].State==false){
Area[i-1].Open();
ClosedAreas--;
}
}else if((packet.Measurement[TEMP][i]-packet.Measurement[TEMP][OUT])<-dT_OFF){ // иначе если в зоне i холоднее улицы на dT - закрываем клапаны
Need4Motor--;
if(Area[i-1].State==true){
ClosedAreas++;
if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){ //Если все зоны закрыты и вент работает останавливаем вент и ждём остановки 3с
MOTOR_OFF;
MOTOR_SLOW_SPEED;
delay(3000);
}
Area[i-1].Close();
}
}else if((packet.Measurement[TEMP][i]-packet.Measurement[TEMP][OUT])<=dT_OFF){ // иначе если отличие Т в зоне i от улицы в пределах гистерезиса, можно просто остановить вент
if(i==IN)
MOTOR_SLOW_SPEED;
Need4Motor--;
}
}else if(Area[i-1].State==true){ // иначе если в зоне i совсем холодно и клапаны открыты - закрываем
ClosedAreas++;
if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){ //Если все зоны закрыты и вент работает останавливаем вент и ждём остановки
MOTOR_OFF;
MOTOR_SLOW_SPEED;
delay(3000);
}
Area[i-1].Close();
}
}
if((Need4Motor==0)&&(FLAG_MOTOR_CHECK)){ // Если во всех зонах тепло, разница Т в пределах гистерезиса улице и вент работает, то останавливаем вент, клапаны оставляем открытыми
MOTOR_OFF;
MOTOR_SLOW_SPEED;
}
return; // изменений нет выходим
}
// 3. В режиме осушения - проверка на достижение минимальной температуры помещения в режиме осушения - ВЫКЛЮЧИТЬ, третий приоритет
ClosedAreas=0;
for(i=1; i<SENSORS_QUANTITY; i++){
if(Area[i-1].State==false)
ClosedAreas++; // Считаем кол-во закрытых зон
}
if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){ // Если все зоны закрыты и вент работает останавливаем вент
MOTOR_OFF;
MOTOR_SLOW_SPEED;
}
Need4Motor=SENSORS_QUANTITY-1; // Здесь используется как признак, что везде слишком холодно
for(i=1; i<SENSORS_QUANTITY; i++){
if (packet.Measurement[TEMP][i]<=(packet.T_min*10)){ // Если в зоне i совсем холодно - закрываем клапаны
if(Area[i-1].State==true){
ClosedAreas++;
if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){ // Если все зоны закрыты и вент работает - останавливаем вент и ждём остановки
MOTOR_OFF;
MOTOR_SLOW_SPEED;
delay(3000);
}
Area[i-1].Close();
}
if(i==IN)
MOTOR_SLOW_SPEED;
Need4Motor--;
}
}
if(Need4Motor==0) // Если во всех зонах слишком холодно, клапаны везде закрыли, мотор выключили - больше ничего не интересно, выходим
return;
// 4. Режимы зависящие от температуры и влажности низший приоритет (что осталось)
// Расчитываем разность срабатывания по влажности
ClosedAreas=0;
for(i=1; i<SENSORS_QUANTITY; i++){
if(Area[i-1].State==false)
ClosedAreas++; // Считаем кол-во закрытых зон
}
if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){ //Если все зоны закрыты и вент работает останавливаем вент
MOTOR_SLOW_SPEED;
MOTOR_OFF;
}
Need4Motor=SENSORS_QUANTITY-1; // Для подсчёта кол-ва зон, которым нужен вент
for(i=1; i<SENSORS_QUANTITY; i++){
if(FLAG_ABS_H_CHECK) tmp=packet.dH_min; // Если режимы используют абсолютную разность в сотых грамма на куб
else tmp=(int)(packet.Measurement[ABS_HUM][i]*(packet.dH_min/10)/100); // Если режимы используют ДЕСЯТЫЕ % от абсолютной разности внутренней температуры
if(packet.Measurement[TEMP][i]>(packet.T_min*10)){ // темература в зоне i выше минимальной
if((packet.Measurement[ABS_HUM][i]-packet.Measurement[ABS_HUM][OUT])>tmp){ // Если разница абс влажностей больше на дельту то открываем клапаны и ВКЛЮЧАЕМ мотор
if(Area[i-1].State==false){
Area[i-1].Open();
ClosedAreas--;
}
if(i==IN)
MOTOR_FAST_SPEED;
if(~FLAG_MOTOR_CHECK)
MOTOR_ON;
}else if(packet.Measurement[ABS_HUM][i]-packet.Measurement[ABS_HUM][OUT]>dH_OFF){ // иначе если разница абс влажностей больше гистерезиса - просто открываем клапаны
if(Area[i-1].State==false){
Area[i-1].Open();
ClosedAreas--;
}
}else if(packet.Measurement[ABS_HUM][i]-packet.Measurement[ABS_HUM][OUT]<-dH_OFF){ // иначе если в зоне более сухо чем на улице на значение гистерезиса - закрыть клапаны
Need4Motor--;
if(Area[i-1].State==true){
ClosedAreas++;
if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){ //Если все зоны закрыты и вент работает останавливаем вент и ждём остановки 3с
MOTOR_OFF;
MOTOR_SLOW_SPEED;
delay(3000);
}
Area[i-1].Close();
}
}else if(packet.Measurement[ABS_HUM][i]-packet.Measurement[ABS_HUM][OUT]<=dH_OFF){ // иначе если разница абс влажностей в пределах гистерезиса - заслонки оставить как есть и можно отключать вент, а можно и крутить
if(i==IN)
MOTOR_SLOW_SPEED;
Need4Motor--;
}
}else if(Area[i-1].State==true){ // темература в зоне i меньше либо равна минимальной - закрываем клапаны зоны
ClosedAreas++;
if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){ //Если все зоны закрыты и вент работает останавливаем вент и ждём остановки
MOTOR_OFF;
MOTOR_SLOW_SPEED;
delay(3000);
}
Area[i-1].Close();
}
}
// ucg.setColor(210, 210, 210);
// print_StrXY(1,155+0, F("Need4Motor: ")); ucg.print(Need4Motor);
// print_StrXY(1,155+15,F("dH_OFF: ")); ucg.print(dH_OFF);
// print_StrXY(1,155+30,F("tmp: ")); ucg.print(tmp);
if((Need4Motor==0)&&(FLAG_MOTOR_CHECK)){ // Если во всех зонах тепло и H в пределах гистерезиса и вент работает, то останавливаем вент, клапаны оставляем открытыми
MOTOR_OFF;
MOTOR_SLOW_SPEED;
}
}
#else
// Проверка статуса вытяжки, не пора ли переключиться
void CheckON(void)
{
int tmp=0;
// 0. Проверить замораживание подвала НАГРЕВАТЕЛЬ
if (packet.Measurement[TEMP][IN]<=TEMP_LOW) { // Контроль от промораживания подвала по идее здесь надо включать калорифер
MOTOR_OFF;
#ifdef HEATER
HEAT_ON;
#endif
return;
}
#ifdef HEATER
if ((FLAG_HEAT_CHECK)&&(packet.Measurement[TEMP][IN]>TEMP_LOW+dT_OFF+50))
HEAT_OFF; // Выключить калорифер когда температура поднимится на 0.5 градуса
#endif
// 1. Режимы не зависящие от влажности и температуры ВЫСШИЙ приоритет
if ((settingRAM.mode==BLOCK_OFF)&&(~FLAG_MOTOR_CHECK)) return;
if ((settingRAM.mode==BLOCK_OFF)&&(FLAG_MOTOR_CHECK)) { MOTOR_OFF ; return;}
if ((settingRAM.mode==HOOD_ON )&&(FLAG_MOTOR_CHECK)) return;
if ((settingRAM.mode==HOOD_ON )&&(~FLAG_MOTOR_CHECK)) { MOTOR_ON ; return;}
// 2. Режим охлаждения (второй приоритет) температура внутри больше 10 градусов темература снаружи меньше на 2 градуса чем внутри, на влажность не смотрим
if (settingRAM.mode==COOLING){ // Режим охлаждение
if ((~FLAG_MOTOR_CHECK)&&(packet.Measurement[TEMP][IN]>(packet.T_min*10))&&((packet.Measurement[TEMP][IN]-packet.Measurement[TEMP][OUT])>packet.dH_min)) // dH_min используется не штатно для температуры
{MOTOR_ON; return;} // если мотор выключен, температура выше установленной и снаружи температура ниже на 2 градуса то ВКЛЮЧЕНИЕ мотора
if ((FLAG_MOTOR_CHECK)&&(packet.Measurement[TEMP][IN]<=packet.Measurement[TEMP][OUT]))
{MOTOR_OFF; return;} // если мотор включен и темература внутри ниже наружней то ВЫКЛЮЧЕННИЕ мотора
return; // изменений нет выходим
}
// 3. В режиме осушения - проверка на достижение минимальной температуры помещения в режиме осушения - ВЫКЛЮЧИТЬ, третий приоритет
if (packet.Measurement[TEMP][IN]<=(packet.T_min*10))
{
if (~FLAG_MOTOR_CHECK) return; // Мотор уже выключен, выходим
else { MOTOR_OFF; return;} // выключить и выйти
}
// 4. Режимы зависящие от температуры и влажности низший приоритет (что осталось)
// Расчитываем разность срабатывания по влажности
if (FLAG_ABS_H_CHECK) tmp=packet.dH_min; // Если режимы используют абсолютную разность в сотых грамма на куб
else tmp=(int)(packet.Measurement[ABS_HUM][IN]*(packet.dH_min/10)/100); // Если режимы используют ДЕСЯТЫЕ % от абсолютной разности внутренней температуры
if ((~FLAG_MOTOR_CHECK)&&(packet.Measurement[TEMP][IN]>(packet.T_min*10))&&((packet.Measurement[ABS_HUM][IN]-tmp)>packet.Measurement[ABS_HUM][OUT]))
{MOTOR_ON; return;} // мотор выключен, темература выше требуемой, абс влажность снаружи меньше на дельту то ВКЛЮЧЕНИЕ мотора
if ((FLAG_MOTOR_CHECK)&&((packet.Measurement[TEMP][IN]<=(tmp+dT_OFF))||(packet.Measurement[ABS_HUM][IN]<(packet.Measurement[ABS_HUM][OUT]+dH_OFF))))
{MOTOR_OFF; return;} // мотор включен и (темература меньше либо равна заданной или абс влажность внутри ниже) то ВЫКЛЮЧЕННИЕ мотора
}
#endif
// Вывод информации о настройках и сохрание индекса настроек в eeprom ---------------------------------
void Setting()
{ // Настройка
cli();
ucg.setColor(0, 0, 180); //
ucg.drawBox(0, 0, 280-1, 23);
ucg.setColor(255, 255, 255);
ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
// ucg.setPrintPos(0,25+18);
ucg.setPrintPos(2,19);
switch (settingRAM.mode){
case BLOCK_OFF: ucg.print(F("Выключено")); packet.dH_min=255; packet.T_min=255; FLAG_ABS_H_ON; dH_OFF=20; break;
case HOOD_ON: ucg.print(F("Режим вытяжки")); packet.dH_min=0; packet.T_min=0; FLAG_ABS_H_ON; dH_OFF=20; break;
case COOLING: ucg.print(F("Охлаждение T>10 dT>2")); packet.dH_min=200; packet.T_min=100; FLAG_ABS_H_ON; dH_OFF=(byte)(packet.dH_min/4); break; // dH_min используется не штатно для температуры
case 3: ucg.print(F("Осушение T>+3 dH>1.0")); packet.dH_min=100; packet.T_min=30; FLAG_ABS_H_ON; dH_OFF=(byte)(packet.dH_min/4); break;
case 4: ucg.print(F("Осушение T>+3 dH>5%")); packet.dH_min=50; packet.T_min=30; FLAG_ABS_H_OFF;dH_OFF=(byte)(packet.dH_min/4); break;
case 5: ucg.print(F("Осушение T>+4 dH>2.0")); packet.dH_min=200; packet.T_min=40; FLAG_ABS_H_ON; dH_OFF=(byte)(packet.dH_min/4); break;
case 6: ucg.print(F("Осушение T>+4 dH>10%")); packet.dH_min=100; packet.T_min=40; FLAG_ABS_H_OFF;dH_OFF=(byte)(packet.dH_min/4); break;
case 7: ucg.print(F("Осушение T>+5 dH>3.0")); packet.dH_min=300; packet.T_min=50; FLAG_ABS_H_ON; dH_OFF=(byte)(packet.dH_min/4); break;
}
writeEeprom(); // Запись в EEPROM новых настроек
SET_MODE_BITS; // В пакет для передачи добавили смену режима
// MOTOR_OFF; // Поменяли настройки - отключить мотор, пусть заново настройки сработают если потребуется
#ifdef HEATER
HEAT_OFF;
#endif
CheckON(); // Возможно надо включить мотор
print_status(); // Показать панель состояния (смена настроек)
sei();
}
// Вывод float с двумя десятичными знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
ucg.setPrintPos(x,y);
// ucg.print(F("-")); // Проверка числа с минусом
ucg.print(v,1);
// ucg.print(F(" ")); // Стереть хвост от предыдущего числа
}
// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
ucg.setPrintPos(x,y);
ucg.print(b);
}
#ifdef INFOSCREEN
void printInfo() // Окно с информацией о блоке, появляется при длительном нажатии на кнопку и ее отпускании
{
infoScreen=true;
cli();
ucg.setColor(250, 250, 250); //
ucg.drawBox(5, 5, 320-1-10, 240-1-10);
ucg.setColor(0, 50, 250);
ucg.drawFrame(5+5, 5+5, 320-1-20, 240-1-20);
ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
ucg.setColor(0, 200, 10);
print_StrXY(35,12+16,F("ОСУШИТЕЛЬ на Arduino Pro Mini"));
ucg.setColor(0, 50, 50);
print_StrXY(5+10,15+16*2,F("1 Напряжение питания В."));
print_floatXY(5+222,15+16*2,readVcc()/1000.0);
print_StrXY(5+10,15+16*3,F("2 Температура блока гр."));
print_floatXY(5+222,15+16*3,GetTemp());
print_StrXY(5+10,15+16*4,F("3 Мото часы блока"));
ucg.setPrintPos(5+222,15+16*4);
ucg.print(settingRAM.hour_unit/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));
print_StrXY(5+10,15+16*5,F("4 Мото часы вентилятора"));
ucg.setPrintPos(5+222,15+16*5);
ucg.print(settingRAM.hour_motor/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));
#ifdef HEATER
print_StrXY(5+10,15+16*6,F("5 Мото часы нагревателя"));
ucg.setPrintPos(5+222,15+16*6);
ucg.print(settingRAM.hour_heat/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));
#endif
print_StrXY(5+10,15+16*7,F("6 Мах температуры in/out"));
ucg.setPrintPos(5+222,15+16*7);
ucg.print((float)settingRAM.Max[IN]/100,1);
ucg.print(F("/"));
ucg.print((float)settingRAM.Max[OUT]/100,1);
print_StrXY(5+10,15+16*8,F("7 Мin температуры in/out"));
ucg.setPrintPos(5+222,15+16*8);
ucg.print((float)settingRAM.Min[IN]/100,1);
ucg.print(F("/"));
ucg.print((float)settingRAM.Min[OUT]/100,1);
print_StrXY(5+10,15+16*9,F("8 Канал NRF24l01+"));
ucg.setPrintPos(5+222,15+16*9);
ucg.print(NRF24_CHANEL);
print_StrXY(5+10,15+16*10,F("9 Гистерезис T/absH "));
ucg.print((float)dT_OFF/100.0);
ucg.print(F("/"));
ucg.print((float)dH_OFF/100.0);
#ifndef DEMO // Уже места не хватает в DEMO. В DEMO кусок ниже не показывается и код сокращается
print_StrXY(5+10,15+16*11,F("10 ERR Т/Н in:"));
ucg.print(TIN_ERR);
ucg.print(F("/"));
ucg.print(HIN_ERR);
ucg.print(F(" out:"));
ucg.print(TOUT_ERR);
ucg.print(F("/"));
ucg.print(HOUT_ERR);
ucg.drawHLine(40,18+16*11,320-1-80);
ucg.setColor(0, 0, 150);
print_StrXY(5+10,19+16*12,F("СБРОС - Вкл. при нажатой кнопке."));
ucg.setColor(250,80,80);
print_StrXY(5+10,19+16*13,F(VERSION));
#ifdef RADIO // Радио модуль NRF42l вывести число ошибок ask
ucg.setColor(0, 150, 10);
ucg.print(F(" AskErr:"));
ucg.print(err_ask);
#endif
sei();
#endif // Сокращение кода
#ifdef BEEP
beep(40);
#endif
}
void clearInfo() // Стереть информационный экран
{
infoScreen=false;
last_error=100; // Признак обновления ошибки
cli();
ucg.setColor(0, 0, 0); // залить черным
ucg.drawBox(5, 5, 320-1-10, 240-1-10);
print_static();
Setting();
print_data();
printChart();
sei();
}
#endif
// Чтение свободной памяти --------------------------------------------------------------------
int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
// Чтение внутреннего датчика температуры ---------------------------------------
double GetTemp(void)
{
unsigned int wADC;
double t;
sei(); // Должны быть разрешены прерывания
ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
ADCSRA |= _BV(ADEN);
delay(20);
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA,ADSC));
wADC = ADCW;
t = (wADC - 324.31 ) / 1.22;
return (t);
}
// Чтение напряжения питания ----------------------------------------------
long readVcc() {
long result;
// Read 1.1V reference against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = ConstADC / result; // Back-calculate AVcc in mV
return result;
}
// Запись счетчиков в Eeprom --------------------------------------------------
void writeEeprom()
{
cli();
eeprom_write_block((const void*)&settingRAM, (void*) &settingEEPROM, sizeof(settingRAM));
sei();
}
// Чтение счетчиков из Eeprom --------------------------------------------------
void readEeprom()
{
cli();
eeprom_read_block((void*)&settingRAM, (const void*) &settingEEPROM, sizeof(settingRAM));
if ((settingRAM.mode>(NUM_SETTING-1))||(settingRAM.mode<0)) settingRAM.mode=0; // гарантированно попадаем в диапазон
sei();
}
void reset_sum() // Сброс счетчиков накоплений
{
sensors.num=0;
for (byte i=0; i<2; i++)
for (byte j=0; j<SENSORS_QUANTITY; j++)
sensors.Sum[i][j]=0;
}
char hex(byte x) // Функция для вывода в hex
{
if(x >= 0 && x <= 9 ) return (char)(x + '0');
else return (char)('a'+x-10);
}
// Сброс дисплея --------------------------------------
bool reset_ili9341(void)
{
pinMode(PIN_RESET, OUTPUT); // Сброс дисплея сигнал активным является LOW
digitalWrite(PIN_RESET, LOW);
delay(100);
digitalWrite(PIN_RESET, HIGH);
ucg.begin(UCG_FONT_MODE_TRANSPARENT);
ucg.setFont(my14x10rus);
// ucg.setRotate90();
ucg.setRotate270();
ucg.clearScreen();
}
#ifdef BEEP
void beep(int x) // Пищать х мсек
{
WritePort(C,1,HIGH);
delay(x);
WritePort(C,1,LOW);
}
#endif
void boot_test(void)
{
byte i;
ucg.clearScreen();
ucg.setColor(255, 255, 255);
print_StrXY(10,50,F("Тест вентилятора"));
MOTOR_ON;
for(i=0;i<3;i++){
delay(1000);
ucg.print(F(" ."));
}
#ifdef SERVO
MOTOR_FAST_SPEED;
for(i=0;i<3;i++){
delay(1000);
ucg.print(F(" ."));
}
MOTOR_SLOW_SPEED;
#endif
MOTOR_OFF;
delay(1000);
#if ID == 0x21 // Только у 21 блока установлен нагреватель
#ifdef HEATER
ucg.setColor(255, 255, 255);
print_StrXY(10,70,F("Тест нагревателя"));
HEAT_ON;
for(i=0;i<5;i++){
delay(1000);
ucg.print(F(" ."));
}
HEAT_OFF;
#endif
#endif
#ifdef SERVO
print_StrXY(10,90,F("Тест сервоприводов"));
for(i=0; i<SENSORS_QUANTITY-1; i++){
ucg.print(F(" ."));
Area[i].Close();
delay(1000);
}
for(i=0; i<SENSORS_QUANTITY-1; i++){
ucg.print(F(" ."));
Area[i].Open();
delay(1000);
}
#endif
ucg.clearScreen();
}
// Функция переводит относительную влажность в абсолютную
// t-температура в градусах Цельсия h-относительная влажность в процентах
float calculationAbsH(float t, float h)
{
float temp;
temp=pow(2.718281828,(17.67*t)/(t+243.5));
return (6.112*temp*h*2.1674)/(273.15+t);
}
// Сканирование клавиш ------------------------------------------
void scanKey()
{
byte key,key1;
cli();
key=ReadPort(D,3); // Прочитать кнопку 0 - нажата
delay(25); // Задержка для подавления дребезга контактов
key1=ReadPort(D,3); // читаем еще раз кнопку
if (key!=key1) {resetKey();return;} // если значения не равны то ложное срабатывание выходим
if ((key==0)&&(flagKey==false)){ // Если кнопка была нажата и флаг ещё не стоит
flagKey=true; // Кнопка нажата, ставим флаг и ждем обратного фронта
time_key=millis(); // Время нажатия запомнили
}
if ((key==1)&&(flagKey==true)){ // Если кнопка отжата и флаг до сих пор стоит
time_key=millis()-time_key; // Рассчитать время нажатия
pressKey=true; // Поставить признак обработки кнопки
}
sei();
}
Я пробовал изначально Пельтье сушить, но он у меня хорошо работал только дома, а в яме результат был практически нулевой. Сделал вывод, что для хорошей конденсации большая разность температур нужна
А по моему, канальный вентилятор, тапа такого https://www.blauberg.ru/series/centro-m гораздо эффективнее справится. Попутно удалив ненужные газы, типа метана и радона. И по электричеству не накладно
У нас в Крыму морозы до -35 встречаются крайне редко, но обычные -5...-10 он вполне нормально переживал. При этом работал и что-то там показывал на экране - вероятно тепловыделение работает в качестве обогрева :) Корпус у меня практически герметичный.
Спасибо. А под датчики bme280 никто не переделывал? Почитал, что датчики dht22 деградируют в течении пары лет.
AnDrEyKa_111, как с Вами связаться для консультации?
AnDrEyKa_111, как с Вами связаться для консультации?
Даже не знаю в каком вопросе я смогу дать какую-либо консультацию... Я просто следовал схеме автора (с небольшими корректировками под свои нужды) и, честно говоря, не силён в этой всей схемотехнике... Ну и немного алгоритм перепилил, опять таки под свои конкретные нужды...
Это мое первое изделие на ардуино и скетчи у меня пока на нуле. Буду делать и учиться по пути.
Мне нужно конструкцию на подвал + гараж, соответственно два вентилятора (по одному на каждое помещение) и три датчика.
1. В Вашей схеме какой карлсон куда (где подвал и где верхний и какой из датчиков куда (нужно три, я так понимаю).
2. Можно ли получить прошивку, где масштабируется график, как на видео в ютубе?
3. Ну и стена в выводе на экран меня не интересует, как поправить?
4. Поправлять коэффициенты датчиков нужно, или заведется и так с приемлемой точностью?
1. У меня вообще двухскоростной вентилятор, то есть с тремя контактами. А на схеме первый - это вентилятор, а второй - нагреватель. Кто где - настраивается в скетче, там если в дефайны внимательно посмотреть и почитать описание, то всё будет понятно.
2. Да, вот моя последняя версия (для удобства опубликую отдельно в сл. сообщении)
3. Настраивается переменной SENSORS_QUANTITY в самом начале, я обошёлся в итоге без вентиляции внутристенного пространства и у меня как раз то что вам надо, то есть три сенсора.
4. Нет, калибровка датчиков - это отдельная процедура и она обязательна. Она вроде была где-то в этой теме описана.
Создал тему по второй версии устройства
http://arduino.ru/forum/proekty/kontrol-vlazhnosti-podvala-ver20-stm32-set
Основное отличие - использование stm32 и удаленное управление через ethernet
Вопрос. Такое решение способно прокачать подвал? Может по энергозатратам будет накладно. Самодельный осушитель воздуха на элементе Пельтье (usamodelkina.ru)
Я пробовал изначально Пельтье сушить, но он у меня хорошо работал только дома, а в яме результат был практически нулевой. Сделал вывод, что для хорошей конденсации большая разность температур нужна
А по моему, канальный вентилятор, тапа такого https://www.blauberg.ru/series/centro-m гораздо эффективнее справится. Попутно удалив ненужные газы, типа метана и радона. И по электричеству не накладно
Там же вода должна что называется сочиться. В яме без гидроизоляции? Ни какой осушитель не поможет.