pav2000 можно как-то с вами связаться? Хотел бы проконсультироваться по вопросу устройства подполья в вашем случае и системы вентиляции для него. Извините что не в тему.
pav2000 можно как-то с вами связаться? Хотел бы проконсультироваться по вопросу устройства подполья в вашем случае и системы вентиляции для него. Извините что не в тему.
Приехали датчики.. Естественно нужна калибровка.. Появиля вопрос :- как изменить графики(масштаб), что бы увидеть реальную температуру? Сейчас на улице 27,3*С внутри 21*С. Соответственно отн. вл. 58,6% и 50,77% (13,93г/м3 и 13,4г/м3) ?
Графики тут не причем, они только показывают что намерили датчики. В программе реализована калибровка датчиков по одной точке. Если потребуется можно реализовать по двум точкам.
Калибровочные константы задаются кодом
// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для каждого ID свой. ОШИБКИ ДОБАВЛЯЮТСЯ!!
#define TOUT_ERR -40 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR -40 // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR -30 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR +30 // Ошибка домового датчика влажности в сотых долях %
Нужны образцовые гигрометр и термометр. Алгоритм следующий
1. Все калибровочные константы в 0 компилируем и загружаем скейч в плату.
2. Запихиваем образцовый прибор и оба датчика в банку, сверху прекрываем. Что бы небыло конвекции воздуха.
3. Берем листочек бумаги и считываем три значения в течении 2- 3 часов раз 10-20.
4. Обрабатываем полученные результаты в меру своих математических способностей. Простейший случай усредняем по 10-20 значениям. В результате получается три значения. Эталон и два датчика.
5. Вычисляем ошибки. Если образцовый больше то ошибка положительная если меньше отрицательная.
6. Переводим в машинные еденицы типа сотых градуса х100. И заносим эти константы в программу.
7. Компилим и загружаем. Наблюдаем уменьшение ошибки.
Все, профит =)
Смотрим на других значениях диапазона. Если ошибка не устраивает, то надо делать калибровку хотя бы по двум точкам. Это значит наклон графиков передаточной функции датчика и эталона сильно разный.
Датчики цифровые. АЦП для их работы не используется. Я использую ацп для измерения питания коонтроллера. По хорошому его надо откалибровать (точнее ион). На показания датчиков ацп не влияет
Спасибо. С этим все ясно..Инвертировал (попытался) работу реле вентилятора и нагревателя. С логикой вентилятора все ок, а нагреватель постоянно включен. Даже в режиме "выключено". Я конечно могу добавить пару инверторов для работы реле, но мне кажется это был бы не правильный подход :)
Увы мои скудные познания в СИ не дают мне (пока) разобраться в работе программы...
У меня нагреватель пока не подключен. Все твердотельное реле не куплю.
По этому программировал без отладки. Если инверсная логика то в сетапе надо поставить heat_off. Для начала. И смотреть. Просто я в начале программирую ноги и эта нога устанавливается 0.
Heat_off ставить ниже этих строк или вместо low high поставить в нижнюю строчку.
Калорифер выключится. При включенных датчиках он включится ТОЛЬКО при температуре 1.5 градуса в подвале. То есть сейчас он не будет включатся. Нет условий.
Только сейчас подумал. В режиме выключено, выключение не распространяется на нагреватель. Гарантированная защита от замораживания подвала. Так сейчас сделано. Можно изменить на отключение. Но ихмо сейчас надежнее.
Ps
Вчера дрюкался с блоком, испытывал новые изменения и дополнения. Кнопка с rc цепочкой отлично работает, ни одного ложного срабатывания.
Разобрался с реле. Теперь все как надо работает. Спасибо...
Теперь надо радио подключать.... Как оно у Вас подключено? через отдельные делители или параллельно дисплею? У меня преобразователи уровня собраны на 4х транзисторах. И конечно вопрос:- " что с ним (радио) делать дальше?". :)
По поводу калибровки датчиков... А зачем нам приведение показаний к образцовому прибору? В этой схеме вполне достаточно одинаковых показаний обоих датчиков (в попугаях) для принятия решения. Более менее точный нужен датчик температуры, для управления нагревом и охлаждением..
Еще один вопрос: - Нужно ли опускать влажность ниже... скажем 50-55% ? Если на улице влажность 35%, то нужно ли опускать в подвале до такой влажности? Я думаю, это напрасный расход энергии (вентилятор ). Не претендую на истину. :)
Nrf24l01 можно цеплять к 5 вольтам без делителей. Толеранты к 5 вольтам из даташита.
Т.е. цепляются к ногам ардуино на прямую.
"Делать дальше" постараюсь выложить пост по приемнику, сейчас допиливаю код. Есть проблемы со множеством передатчиков. Думаю над идеологией. Напечатал корпус, приделал энкодер. Сейчас допиливаю интерфейс. Приемник будет только stm32 онли. Три прерывания использую. Есть рроблема ио spi шиной дисплея и nrf24l01. Пока это не решил. В очередной раз опитимизирую код по радиоканалу.
Мне кажется что калибровка датчиков по обрацовому нужна, мы вычисляем абсолютную влажность, а она зависит от температуры и влажности. Желательно определить тх.
Отлично! У меня уже есть в наличии stm32 и пара енкодеров. Но STM32 для меня нечто новое :) . И нужно устанавливать какое то ИДЕ для него. Если у Вас есть немного времени, посоветуйте как организовать среду...
Для STM существует куча всяких ИДЕ...В том числе и ардуино... Но где брать библиотеки? Тем более библиотеки которые Вы сами портировали под этот проект..
По stm32 изложу в отдельной ветке про либы и иде в среде ардуино. Сейчас в деревне между рюмками пишу посты. У нас теплый вечер. +20. Отмечаем мой отпуск -))
Проект близится к завершению. Исправлений все меньше и меньше. В новой версии :
1. В очередной раз оптимизирован код радиоканала.
2. В процессе работы забыл записывать заначения моточасов в eeprom. Сейчас это поправлено запись производится раз в час.
3. Добавил сохранение максимальных и минимальных температур подвала и улицы, с их выводом на информационный экран. Сброс проводится одновременно с моточасами. (наверное это не верно, надо отдельной процедурой)
4. Мелкие исправления и оптимизация кода.
#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 справка https://code.google.com/p/ucglib/wiki/
#include "rusFont.h" // Русские шрифты
#include "nRF24L01.h" // Беcпроводной модуль надо использовать библиотеку http://tmrh20.github.io/RF24
#include "RF24.h" // Беcпроводной модуль используются не стандартные функции https://github.com/TMRh20
// - ОПЦИИ -------------------------------
//#define DEBUG // Отладочную информацию в ком порт посылает
//#define DEMO // Признак демонстрации - данные с датчиков генерятся рандом
#define BEEP // Использовать пищалку
#define RADIO // Признак использования радио модуля
#define VERSION "Ver. 0.69 19/09/15" // Текущая версия
#define ID 0x22 // уникально Идентификатор устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
#define LABEL "Подвал" // "Подпол" "Подвал" // Место установки блока - отоображается как заголовок колонки в таблице. Не более 6 символов а то не влезает
// Макросы для работы с портами скорость и место
#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; } // выключить мотор
#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,2,HIGH); FLAG_HEAT_ON; } // включить калорифер
#define HEAT_OFF { WritePort(C,2,LOW); FLAG_HEAT_OFF; } // выключить калорифер
// - КОНСТАНТЫ --------------------------------------
#define dH_OFF 5 // Гистерезис абсолютной влажности в сотых грамма на куб
#define dT_OFF 11 // Гистерезис температуры в сотых градуса
#define TEMP_LOW 150 // Температура подвала критическая - может быть замораживание (в сотых градуса) - система выключается и включается нагреватель
// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для каждого ID свой. ОШИБКИ ДОБАВЛЯЮТСЯ!!
#if ID == 0x21
#define TOUT_ERR -40 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR -40 // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR -30 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR +30 // Ошибка домового датчика влажности в сотых долях %
#elif ID == 0x22
#define TOUT_ERR -12 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR -10 // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR -250 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR +250 // Ошибка домового датчика влажности в сотых долях %
#else
#define TOUT_ERR 0 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR 0 // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR 0 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR 0 // Ошибка домового датчика влажности в сотых долях %
#endif
// - ВРЕМЕНА ---------------------------------------
#ifdef DEMO // Для демо все быстрее и случайным образом
#define NUM_SAMPLES 2 // Число усреднений измерений датчика
#define TIME_SCAN_SENSOR 1000 // Время опроса датчиков мсек, для демки быстрее
#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 LONG_KEY 2000 // Длительное нажатие кнопки мсек, появляется Экран инфо
#define SHORT_KEY 100 // Короткое нажатие кнопки более мсек
#define NRF24_CHANEL 100 // Номер канала nrf24
// НОГИ к которым прицеплена переферия (SPI используется для TFT и NRF24 - 11,12,13)
#define PIN_HEAT 16 // Ножка куда повешен калорифер A2 (port C2)
#ifdef BEEP
#define PIN_BEEP 15 // Ножка куда повешена пищалка A1 (port C1)
#endif
#define PIN_RELAY 14 // Ножка на которую повешено реле (SSR) вентилятора - аналоговый вход A0 через резистор 470 ом (port C0)
#define PIN_CS 10 // TFT дисплей spi
#define PIN_CD 9 // TFT дисплей spi
#define PIN_RESET 8 // TFT дисплей spi
#define PIN_CE 7 // nrf24 ce
#define PIN_CSN 6 // nrf24 cs
#define PIN_DHT22a 5 // Первый датчик DHT22 IN ДОМ
#define PIN_DHT22b 4 // Второй датчик DHT22 OUT УЛИЦА
#define PIN_KEY 3 // Кнопка, повешена на прерывание, что бы ресурсов не тратить (port D3)
#define PIN_IRQ_NRF24 2 // Ножка куда заведено прерывание от NRF24 (пока не используется)
// Настройки
#define NUM_SETTING 7 // Число вариантов настроек
#define BLOCK_OFF 0 // Выключено (вариант настроек)
#define HOOD_ON 1 // Режим вытяжки (вариант настроек)
#define COOLING 2 // Режим охлаждение (вариант настроек)
// АЦП ----------------------------------------
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; // Время нажатия копки
byte last_error=100; // Предыдущая ошибка чтения датчиков
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
unsigned long hour_heat=0; // мото часы нагревателя измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
int16_t tOutMin=5555,tInMin=5555; // Минимальные температуры за период температуры в сотых градуса !!! место экономим
int16_t tOutMax,tInMax=-5555; // Максимальные температуры за период температуры в сотых градуса. (Наблюдаю глюк при tOutMax=-5000)
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 id=ID; // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
byte DHT_error; // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал
int16_t tOut=-500,tIn=500; // Текущие температуры в сотых градуса !!! место экономим
uint16_t absHOut=123,absHIn=123; // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
uint16_t relHOut=123,relHIn=123; // Относительные влажности сотых процента !!! место экономим
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; // Порог выключения вентилятора по температуре в ДЕСЯТЫХ долях градуса, только положительные значения
uint8_t count=0; // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
char note[14] = LABEL; // Примечание не более 13 байт + "0" байт Русские буквы в два раза меньше т.к. UTF-8
} packet;
struct type_sensors // структура для усреднения измерений
{
int num=0; // сколько отсчетов уже сложили не болле NUM_SAMPLES
long sum_tOut=0,sum_tIn=0; // Сумма для усреднения Текущие температуры в сотых градуса !!! место экономим
long sum_relHOut=0,sum_relHIn=0; // Сумма для усреднения Относительные влажности сотых процента !!! место экономим
int tOut=-5000,tIn=5000; // Текущие температуры в сотых градуса !!! место экономим
int absHOut=55555,absHIn=55555; // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
int relHOut=55555,relHIn=55555; // Относительные влажности сотых процента !!! место экономим
} sensors;
// Массивы для графиков
uint8_t tOutChart[120];
uint8_t tInChart[120];
uint8_t absHOutChart[120];
uint8_t absHInChart[120];
uint8_t posChart=0; // Позиция в массиве графиков - начало вывода от 0 до 120-1
uint8_t TimeChart=0; // Время до вывода очередной точки на график.
bool ChartMotor=false; // Признак работы мотора во время интервала графика если мотор был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
bool ChartHeat=false; // Признак работы нагревателя во время интервала графика если нагреватель был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
#ifdef RADIO // Радио модуль NRF42l
RF24 radio(PIN_CE, PIN_CSN); // определение управляющих ног
bool send_packet_ok=false; // признак удачной отправки последнего пакета
unsigned long err_ask=0; // число не полученных ответов
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ПРОГРАММА
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
#ifdef DEBUG
Serial.begin(9600);
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
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,2); // Подключить Калорифер
WritePort(C,2,LOW);
pinMode(PIN_DHT22a, OUTPUT); // Датчик DHT22 #1
digitalWrite(PIN_DHT22a, HIGH);
pinMode(PIN_DHT22b, OUTPUT); // Датчик DHT22 #2
digitalWrite(PIN_DHT22b, HIGH);
reset_ili9341(); // сброс дисплея
readEeprom(); // Прочитать настройки
byte i=ReadPort(D,3);
if (i==0) // Если при включении нажата кнопка то стираем Eeprom
{
settingRAM.mode=0;
settingRAM.hour_unit=0;
settingRAM.hour_motor=0;
settingRAM.hour_heat=0;
settingRAM.tOutMin=5000;
settingRAM.tInMin=5000;
settingRAM.tOutMax=-5000;
settingRAM.tInMax=-5000;
ucg.setColor(255, 255, 255);
print_StrXY(10,50,F("Сброс настроек и счетчиков"));
for(i=0;i<3;i++)
{
delay(1000);
ucg.print(F(" ."));
}
writeEeprom(); // Запись в EEPROM
delay(1000);
ucg.clearScreen();
}
wdt_enable(WDTO_8S); // Сторожевой таймер Для тестов не рекомендуется устанавливать значение менее 8 сек.
// Запуск задач по таймеру
myOS.begin();
myOS.addTask(measurement,TIME_SCAN_SENSOR); // Измерение
attachInterrupt(1, scanKey, CHANGE); // КНОПКА Прерывания по обоим фронтам
print_static(); // распечатать таблицу
Setting(); // Применить настройки
measurement(); // Считать данные
// printInfo();
#ifdef BEEP
beep(200);
delay(300);
beep(400);
#endif
resetKey();
}
void loop()
{
wdt_reset(); // Сбросить сторожевой таймер
// Обработка нажатия кнопки
if (pressKey==true) // Кнопка была нажата и отпущена
{
if (flagKey!=false) // Обработчик кнопки
if ((time_key>=LONG_KEY)&&(infoScreen!=true)) // Длительное нажатие кнопки и информационный экран не показан
printInfo(); // Вывод информационного экрана
else
if (time_key >= SHORT_KEY) // Короткое нажатие кнопки все остальное игнорируется
{
#ifdef BEEP
beep(30); // Звук нажатия на клавишу
#endif
if (infoScreen==true) clearInfo(); // если информационный экран показан то стереть его
else {if (settingRAM.mode >= NUM_SETTING) settingRAM.mode=0; // Кольцевой счетчик настроек
else settingRAM.mode++; // В противном случае следующая настройка
Setting(); }
}
resetKey(); // Сброс состояния кнопки
}
}
void resetKey(void) // Сброс состяния кнопки
{
flagKey=false;
pressKey=false;
}
void print_static() // Печать статической картинки
{
int i;
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("ОСУШИТЕЛЬ ID: 0x"));
ucg.print( hex(packet.id >> 4));
ucg.print( hex(packet.id&0x0f));
#ifdef DEMO
ucg.print(F(" demo"));
#endif
// Таблица для данных
ucg.setColor(0, 200, 0);
ucg.drawHLine(0,25,320-1);
ucg.drawHLine(0,25+23*1,320-1);
ucg.drawHLine(0,25+23*2,320-1);
ucg.drawHLine(0,25+23*3,320-1);
ucg.drawHLine(0,25+23*4,320-1);
ucg.drawVLine(200-4,25,24+23*3);
ucg.drawVLine(260,25,24+23*3);
// Заголовки таблиц
ucg.setColor(255, 255, 0);
// В зависимости от id разные надписи - привязка местоположения блока к ID
// print_StrXY(180+30-9,25+0+18,F(LABEL));
ucg.setPrintPos(180+30-9,25+0+18);
ucg.print(packet.note);
print_StrXY(250+20,25+0+18,F("Улица"));
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(10,135+0,F("Температура"));
print_StrXY(20+154,135+0,F("Абс. влажность"));
// надписи на графиках
print_StrXY(128,154,F("+20"));
print_StrXY(135,194,F("0"));
print_StrXY(128,233,F("-20"));
print_StrXY(296,164,F("15"));
print_StrXY(296,194,F("10"));
print_StrXY(296,223,F("5"));
// Горизонтальная шкала по часам
// ucg.setColor(255, 255, 0);
for(i=0;i<=120;i=i+12)
{
ucg.drawPixel(4+i,239);
ucg.drawPixel(4+i,238);
ucg.drawPixel(167+i,239);
ucg.drawPixel(167+i,238);
}
sei();
}
void print_status(void) // Печать панели статуса Значки на статус панели
{
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
cli();
// 1. печать ошибки чтения датчиков
print_error_DHT();
// 2. Признак включения мотора или нагревателя
if (FLAG_MOTOR_CHECK) ucg.setColor(0, 240, 0);
else if (FLAG_HEAT_CHECK) ucg.setColor(240, 0, 0);
else ucg.setColor(0, 40, 0);
ucg.drawBox(290-32, 5, 14, 14);
#ifdef RADIO
// 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);
#endif
sei();
}
void print_error_DHT(void) // Печать ошибки чтения датчиков выводится при каждом чтении датчика
{
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
// 1. печать ошибки чтения датчиков
if (packet.DHT_error!=last_error) // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
{
cli();
last_error=packet.DHT_error;
ucg.setColor(0, 0, 180); // Сначала стереть
ucg.drawBox(290, 0, 26, 18);
ucg.setPrintPos(280,18);
ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
if (packet.DHT_error>0)
{
ucg.setColor(255, 100, 100);
print_StrXY(280,19,F("0x"));
ucg.print( hex(packet.DHT_error >> 4));
ucg.print( hex(packet.DHT_error & 0x0f));
}
else { ucg.setColor(200, 240, 0); ucg.print(F(" ok ")); }
sei();
}
}
// вывод на экран данных (то что меняется)
void print_data()
{
// Статистика по моточасам, время ведется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR а потом пересчитывается в часы при выводе.
settingRAM.hour_unit++;
if (FLAG_MOTOR_CHECK) settingRAM.hour_motor++; // если мотор включен
if (FLAG_HEAT_CHECK) settingRAM.hour_heat++; // если нагреватель включен
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
cli();
// Печать значений
ucg.setFontMode(UCG_FONT_MODE_SOLID);
ucg.setColor(250, 0, 100); // Цвет ДОМА
print_floatXY(200+0,25+23*1+18,((float)packet.tIn)/100);
print_floatXY(200+0,25+23*2+18,((float)packet.relHIn)/100);
print_floatXY(200+0,25+23*3+18,((float)packet.absHIn)/100);
ucg.setColor(0, 250, 100); // Цвет УЛИЦЫ
print_floatXY(260+4,25+23*1+18,((float)packet.tOut)/100);
print_floatXY(260+6,25+23*2+18,((float)packet.relHOut)/100);
print_floatXY(260+6,25+23*3+18,((float)packet.absHOut)/100);
sei();
}
// Печать графика на экране, добавляется одна точка и график сдвигается
void printChart()
{
byte i,x=0;
uint8_t tmp;
// Работаем через кольцевой буфер
// Добавить новую точку в кольцевой буфер
// Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
if (packet.tIn<=-2500) tInChart[posChart]=0; // Если температура меньше -25 то округляем до -25
else if (packet.tIn>=2500) tInChart[posChart]=100-1; // Если температура больше 25 то округляем до 25
else tInChart[posChart]=(packet.tIn+2500)/50; // внутри -25...+25 растягиваем в два раза
if (ChartMotor==true) tInChart[posChart]|=0x80; // Признак включения мотора- старший бит в 1 - цвет фона на графике меняется
ChartMotor=false;
// Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
if (packet.tOut<=-2500) tOutChart[posChart]=0; // Если температура меньше -25 то округляем до -25
else if (packet.tOut>=2500) tOutChart[posChart]=100-1; // Если температура больше 25 то округляем до 25
else tOutChart[posChart]=(packet.tOut+2500)/50; // внутри -25...+25 растягиваем в два раза
if (ChartHeat==true) tOutChart[posChart]|=0x80; // Признак включения нагревателя- старший бит в 1 - цвет фона на графике меняется
ChartHeat=false;
// Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
if (packet.absHIn>=2000) absHInChart[posChart]=100-1;
else absHInChart[posChart]=packet.absHIn/20; // внутри 0...20 растягиваем в пять раз в сотых % по этому делем не на 100 а на 20
// Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
if (packet.absHOut>=2000) absHOutChart[posChart]=100-1;
else absHOutChart[posChart]=packet.absHOut/20; // внутри 0...20 растягиваем в пять раз, в сотых % по этому делем не на 100 а на 20
if (infoScreen==false) // если отображен информационный экран то ничего не выводим
{
cli();
for(i=0;i<120;i++) // График слева на право
{
// Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
if (posChart<i) x=120+posChart-i; else x=posChart-i;
// нарисовать фон в зависимости от статуса мотора
if (tOutChart[x]>=0x80) ucg.setColor(90, 60, 0); // Нагреватель был ключен - бледно желтый
else if (tInChart[x]>=0x80) ucg.setColor(0, 60, 90); // Мотор был ключен - бледно синий
else ucg.setColor(0, 0, 0); // все выключено
ucg.drawVLine(5+120-i,237-100,100);
ucg.drawVLine(5+120-i+162,237-100,100);
ucg.setColor(180, 180, 180);
if (i%5==0) // Пунктирные линии графика
{
ucg.drawPixel(5+120-i,236-10-1);
ucg.drawPixel(5+120-i,236-50-1);
ucg.drawPixel(5+120-i,236-90-1);
ucg.drawPixel(5+120-i+162,236-25-1);
ucg.drawPixel(5+120-i+162,236-50-1);
ucg.drawPixel(5+120-i+162,236-75-1);
}
// Вывести новую точку
tmp=tInChart[x]&0x7f; // Отбросить старший разряд - признак включения мотора
if ((tmp==0)||(tmp==100)) ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100);
ucg.drawPixel(5+120-i,236-tmp);
tmp=tOutChart[x]&0x7f; // Отбросить старший разряд - признак включения калорифера
if ((tmp==0) || (tmp==100)) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100);
ucg.drawPixel(5+120-i,236-tmp);
if (absHInChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100);
ucg.drawPixel(5+120-i+162,236-absHInChart[x]);
if (absHOutChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100);
ucg.drawPixel(5+120-i+162,236-absHOutChart[x]);
}
sei();
}
if (posChart<120-1) posChart++; else posChart=0; // Изменили положение в буфере и Замкнули буфер
}
// ---ПЕРЕДАЧА ДАННЫХ ЧЕРЕЗ РАДИОМОДУЛЬ -----------------------------
#ifdef RADIO // Радио модуль NRF42l
void send_packet()
{ radio.powerUp();
radio.stopListening(); // Остановить приемник
delay(2);
// radio.openWritingPipe(0xF0F0F0F0E1LL); // передатчик
cli();
// send_packet_ok = radio.write(&packet,sizeof(packet));
radio.writeBlocking(&packet,sizeof(packet),200); // Writes 1 payload to the buffers
send_packet_ok=radio.txStandBy();
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++; // при переполнении сам сбросится
// if (packet.count>255) packet.count=0;
}
#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()
{
myOS.pauseTask(measurement); // Обязательно здесь, а то датчики плохо читаются мешает leos
wdt_reset(); // Сбросить сторожевой таймер
packet.DHT_error=readDHT(PIN_DHT22a); // ПЕРВЫЙ ДАТЧИК ДОМ Новый пакет, сбросить все ошибки и прочитать первый датчик
#ifdef DEMO
DHT.temperature=packet.tIn/100+random(-20,30)/10.0;
if (DHT.temperature>20) DHT.temperature=19;
if (DHT.temperature<-10) DHT.temperature=-9;
DHT.humidity=packet.relHIn/100+(float)random(-5,8);
if (DHT.humidity>96) DHT.humidity=90;
if (DHT.humidity<1) DHT.humidity=10;
packet.DHT_error=0; // в Демо режиме
// DHT.temperature=3.0;
// DHT.humidity=21.0;
#endif
sensors.tIn=(int)(DHT.temperature*100.0)+TIN_ERR; // Запомнить результаты для суммирования с учетом ошибок
sensors.relHIn=(int)(DHT.humidity*100.0)+HIN_ERR;
#ifdef DEBUG
Serial.print(F("Sensor read samples:")); Serial.println(sensors.num);
Serial.print(F("IN T="));Serial.print(sensors.tIn);Serial.print(F(" H=")); Serial.print(sensors.relHIn); Serial.print(F(" error=")); Serial.println(packet.DHT_error);
#endif
packet.DHT_error=packet.DHT_error+16*readDHT(PIN_DHT22b);// ВТОРОЙ ДАТЧИК УЛИЦА ошибки в старшие четыре бита
#ifdef DEMO
DHT.temperature=packet.tOut/100+random(-20,30)/10.0;
if (DHT.temperature>30) DHT.temperature=19;
if (DHT.temperature<-30) DHT.temperature=-9;
DHT.humidity=packet.relHOut/100+(float)random(-5,8);
if (DHT.humidity>96) DHT.humidity=90;
if (DHT.humidity<1) DHT.humidity=10;
packet.DHT_error=0; // в Демо режиме
// DHT.temperature=-10.0;
// DHT.humidity=40.0;
#endif
sensors.tOut=(int)(DHT.temperature*100.0)+TOUT_ERR; // Запомнить результаты для суммирования с учетом ошибок
sensors.relHOut=(int)(DHT.humidity*100.0)+HOUT_ERR;
#ifdef DEBUG
Serial.print(F("OUT T="));Serial.print(sensors.tOut);Serial.print(F(" H=")); Serial.print(sensors.relHOut); Serial.print(F(" error=")); Serial.println(packet.DHT_error);
#endif
print_error_DHT(); // Вывод ошибки чтения датчика при каждом чтении контроль за качеством связи с датчиками
if (packet.DHT_error==0)// Если чтение без ошибок у ДВУХ датчиков копим сумму для усреднения
{
sensors.sum_tIn=sensors.sum_tIn+sensors.tIn;
sensors.sum_relHIn=sensors.sum_relHIn+sensors.relHIn;
sensors.sum_tOut=sensors.sum_tOut+sensors.tOut;
sensors.sum_relHOut=sensors.sum_relHOut+sensors.relHOut;
sensors.num++;
}
// набрали в сумме нужное число отсчетов рассчитываем усреднение и выводим
if (sensors.num>=NUM_SAMPLES) // Пора усреднять и выводить значения
{
// вычисление средних значений
packet.tIn=sensors.sum_tIn/NUM_SAMPLES;
packet.relHIn=sensors.sum_relHIn/NUM_SAMPLES;
packet.tOut=sensors.sum_tOut/NUM_SAMPLES;
packet.relHOut=sensors.sum_relHOut/NUM_SAMPLES;
reset_sum(); // Сброс счетчиков и сумм
// вычисление абсолютной влажности
packet.absHIn=(int)(calculationAbsH((float)(packet.tIn/100.0),(float)(packet.relHIn/100.0))*100.0);
packet.absHOut=(int)(calculationAbsH((float)(packet.tOut/100.0),(float)(packet.relHOut/100.0))*100.0);
#ifdef DEBUG
Serial.println(F("Average value >>>>>>>>>>"));
Serial.print(F("IN T="));Serial.print(packet.tIn);Serial.print(F(" H=")); Serial.print(packet.relHIn); Serial.print(F(" abs H=")); Serial.println(packet.absHIn);
Serial.print(F("OUT T="));Serial.print(packet.tOut);Serial.print(F(" H=")); Serial.print(packet.relHOut); Serial.print(F(" abs H=")); Serial.println(packet.absHOut);
#endif
#ifdef RADIO // Радио модуль NRF42l
send_packet(); // Послать данные
#endif
CheckON(); // Проверка статуса вентилятора
print_data(); // вывод усредненных значений
print_status(); // панель состояния
// Обновляем максимум и минимум температур в EEPROM
// Cделано не честно - обновляем раз в 30 секунд (NUM_SAMPLES*TIME_SCAN_SENSOR) а пишем раз в час, могут пропасть данные при сбросе
if (settingRAM.tInMax<packet.tIn) settingRAM.tInMax=packet.tIn;
else if (settingRAM.tInMin>packet.tIn) settingRAM.tInMin=packet.tIn;
if (settingRAM.tOutMax<packet.tOut) settingRAM.tOutMax=packet.tOut;
else if (settingRAM.tOutMin>packet.tOut) settingRAM.tOutMin=packet.tOut;
// Пишем в EEPROM не каждый раз!! экономим ресурс записей 100000 мин.
settingRAM.tick_eeprom++;
if (((long)settingRAM.tick_eeprom*NUM_SAMPLES*TIME_SCAN_SENSOR)>=(long)TIME_HOUR) // пора писать eeprom
{ writeEeprom(); settingRAM.tick_eeprom=0; }
// Запись статистики в EEPROM
if (FLAG_MOTOR_CHECK) ChartMotor=true; // Признак того что надо показывать включение мотора на графике
if (FLAG_HEAT_CHECK) ChartHeat=true; // Признак того что надо показывать включение нагревателя на графике
TimeChart++;
if ((long)((long)TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проврека не пора ли выводить график
{ printChart(); TimeChart=0; // Сдвиг графика и вывод новой точки
#ifdef DEBUG
Serial.println(F("Point add chart ++++++++++++++++++++"));
#endif
#ifdef BEEP
// beep(50);
#endif
}
}
myOS.restartTask(measurement); // Пустить задачи
}
// Функция переводит относительную влажность в абсолютную
// 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();
}
// Проверка статуса вытяжки, не пора ли переключится
void CheckON(void)
{
int tmp=0;
// 0. Проверить замораживание подвала КАЛОРИФЕР
if (packet.tIn<=TEMP_LOW) { MOTOR_OFF; HEAT_ON; return;} // Контроль от промораживания подвала по идеи здесь надо включать калорифер
if ((FLAG_HEAT_CHECK)&&(packet.tIn>TEMP_LOW+dT_OFF)) HEAT_OFF; // Выключить калорифер
// 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.tIn>(packet.T_min*10))&&((packet.tIn-packet.tOut)>packet.dH_min)) // dH_min используется не штатно для температуры
{MOTOR_ON; return;} // мотор выключен, температура выше установленной и снаружи температура ниже на 2 градуса то ВКЛЮЧЕНИЕ мотора
if ((FLAG_MOTOR_CHECK)&&(packet.tIn<=packet.tOut))
{MOTOR_OFF; return;} // мотор включен и темература внутри ниже наружней то ВЫКЛЮЧЕННИЕ мотора
return; // изменений нет выходим
}
// 3. В режиме осушения - проверка на достижение минимальной температуры помещения в режиме осушения - СРОЧНО ВЫКЛЮЧИТЬ третий приоритет
if (packet.tIn<=(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.absHIn*(packet.dH_min/10)/100); // Если режимы используют ДЕСЯТЫЕ % от абсолютной разности внутренней температуры
if ((~FLAG_MOTOR_CHECK)&&(packet.tIn>(packet.T_min*10))&&((packet.absHIn-tmp)>packet.absHOut))
{MOTOR_ON; return;} // мотор выключен, темература выше критической, абс влажность с наружи меньше то ВКЛЮЧЕНИЕ мотора
if ((FLAG_MOTOR_CHECK)&&((packet.tIn<=(tmp+dT_OFF))||(packet.absHIn<(packet.absHOut+dH_OFF))))
{MOTOR_OFF; return;} // мотор включен и темература ниже критической или абс влажность внутри ниже то ВЫКЛЮЧЕННИЕ мотора
}
// Вывод информации о настройках и сохрание индекса настроек в eeprom ---------------------------------
void Setting()
{
// Настройка
cli();
ucg.setColor(0, 100, 255);
ucg.setFontMode(UCG_FONT_MODE_SOLID);
ucg.setPrintPos(0,25+0+18);
switch (settingRAM.mode)
{
case BLOCK_OFF: ucg.print(F("Выключено ")); packet.dH_min=255; packet.T_min=255; FLAG_ABS_H_ON; break;
case HOOD_ON: ucg.print(F("Режим вытяжки ")); packet.dH_min=0; packet.T_min=0; FLAG_ABS_H_ON; break;
case COOLING: ucg.print(F("Охлаждение T>10 dT>2")); packet.dH_min=200; packet.T_min=100; FLAG_ABS_H_ON; break; // dH_min используется не штатно для температуры
case 3: ucg.print(F("Осушение T>+3 dH>0.2 ")); packet.dH_min=20; packet.T_min=30; FLAG_ABS_H_ON; break;
case 4: ucg.print(F("Осушение T>+3 dH>3% ")); packet.dH_min=30; packet.T_min=30; FLAG_ABS_H_OFF; break;
case 5: ucg.print(F("Осушение T>+4 dH>0.3 ")); packet.dH_min=30; packet.T_min=40; FLAG_ABS_H_ON; break;
case 6: ucg.print(F("Осушение T>+4 dH>6% ")); packet.dH_min=60; packet.T_min=40; FLAG_ABS_H_OFF; break;
case 7: ucg.print(F("Осушение T>+5 dH>0.7 ")); packet.dH_min=70; packet.T_min=50; FLAG_ABS_H_ON; break;
}
writeEeprom(); // Запись в EEPROM новых настроек
SET_MODE_BITS; // В пакет для передачи добавили смену режима
MOTOR_OFF; // Поменяли настройки - отключить мотор, пусть заново настройки сработают если потребуется
CheckON(); // Возможно надо включить мотор
print_status(); // Показать панель состояния (смена настроек)
sei();
}
// Вывод float с двумя десятичными знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
ucg.setPrintPos(x,y);
ucg.print(v,2);
ucg.print(F(" ")); // Стереть хвост от предыдущего числа
}
// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
ucg.setPrintPos(x,y);
ucg.print(b);
}
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+230,15+16*2,readVcc()/1000.0);
print_StrXY(5+10,15+16*3,F("2 Температура блока гр."));
print_floatXY(5+230,15+16*3,GetTemp());
print_StrXY(5+10,15+16*4,F("3 Мото часы блока"));
ucg.setPrintPos(5+230,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+230,15+16*5);
ucg.print(settingRAM.hour_motor/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));
print_StrXY(5+10,15+16*6,F("5 Мото часы нагревателя"));
ucg.setPrintPos(5+230,15+16*6);
ucg.print(settingRAM.hour_heat/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));
print_StrXY(5+10,15+16*7,F("6 Мах температуры in/out"));
ucg.setPrintPos(5+230,15+16*7);
ucg.print(settingRAM.tInMax/100);
ucg.print(F("/"));
ucg.print(settingRAM.tOutMax/100);
print_StrXY(5+10,15+16*8,F("7 Мin температуры in/out"));
ucg.setPrintPos(5+230,15+16*8);
ucg.print(settingRAM.tInMin/100);
ucg.print(F("/"));
ucg.print(settingRAM.tOutMin/100);
print_StrXY(5+10,15+16*9,F("8 Канал NRF24l01+"));
ucg.setPrintPos(5+230,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);
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();
#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();
}
// Чтение свободной памяти --------------------------------------------------------------------
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; // Рассчитать величину усреднения
sensors.sum_tOut=0;
sensors.sum_tIn=0;
sensors.sum_relHOut=0;
sensors.sum_relHIn=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
Вопрос: проводились ли тесты системы при отрицательных температурах?
Думаю мой холодильник, удобный стенд. Морозилка от -32 до -16*С, основная камера от+2 до +8*С .Как раз порог +4* можно проверить.
Есть смысл заняться проверкой?
Отрицательных температур быть не должно. Отрицательные температуры отрицательно влияют на жки и конденсаторы (импульсный источник может не завестись) и положительно на полупроводниковые элементы (обратные токи и токи утечки меньше). Смысла тестирования не вижу. Первым откажит индикатор в районе -5÷-10 градусов.
Я имел ввиду расположение датчиков в холодильнике..Насчет климатических условий для различных компонентов я в курсе уже лет 35...
Сейчас занимаюсь калибровкой датчиков....Нашлось штук 5 разных термометров и 3 цифровых с измерением влажности + лабораторный психрометр (2 градусника-сухой и мокрый) -этот можно взять за эталон, но в банку его засунуть не получится...
Сейчас занимаюсь калибровкой датчиков....Нашлось штук 5 разных термометров и 3 цифровых с измерением влажности + лабораторный психрометр (2 градусника-сухой и мокрый) -этот можно взять за эталон, но в банку его засунуть не получится...
Так по влажности, пользуйтесь солями. Для дома самое простое NaCl. Во всем положительном диапазоне температур от 74 до 76%.
Именно так, правда не морочился с температурой, для NaCl сами видите она не актуальна (других солей под рукой не было). Соль правда брал от водоочистки (там таблетки), и поближе к х.ч. чем из магазина. Майонезная банка, таблетка, на нее 2-4 капель воды из пипетки (должна быть влажная, но без лужи внизу). Датчики примерно в середину банки, сверху пленкой-стрейчем заклеил.
Режим в банке устанавливается достаточно долго (часы), далее держал сутки, посматривал, на am2301 колебался примерно 1%, посчитал для целей подвала, более чем достаточно. Сейчас висит он и неподалеку выносной модуль от погодной станции, разница около 2%, но постоянная.
1. В информационном экране длительное нажатие, без отпускания (5 сек.) кнопки сбрасывает минимальные и максимальные сохраненные температуры.
2. В основном экране длительное нажатие без отпускания кнопки (10 сек.) приводит к сбросу контроллера дисплея. Сделано для того что бы сбрасывать дисплей ili9341 если он зависнет. У меня этого нет, но пусть будет. Изображение и графики перечерчиваются заново.
#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 справка https://code.google.com/p/ucglib/wiki/
#include "rusFont.h" // Русские шрифты
#include "nRF24L01.h" // Беcпроводной модуль надо использовать библиотеку http://tmrh20.github.io/RF24
#include "RF24.h" // Беcпроводной модуль используются не стандартные функции https://github.com/TMRh20
// - ОПЦИИ -------------------------------
//#define DEBUG // Отладочную информацию в ком порт посылает
#define DEMO // Признак демонстрации - данные с датчиков генерятся рандом
#define BEEP // Использовать пищалку
#define RADIO // Признак использования радио модуля
#define VERSION "Ver. 0.70 25/09/15" // Текущая версия
#define ID 0x22 // уникально Идентификатор устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
#define LABEL "Подвал" // "Подпол" "Подвал" // Место установки блока - отоображается как заголовок колонки в таблице. Не более 6 символов а то не влезает
// Макросы для работы с портами скорость и место
#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; } // выключить мотор
#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,2,HIGH); FLAG_HEAT_ON; } // включить калорифер
#define HEAT_OFF { WritePort(C,2,LOW); FLAG_HEAT_OFF; } // выключить калорифер
// - КОНСТАНТЫ --------------------------------------
#define dH_OFF 5 // Гистерезис абсолютной влажности в сотых грамма на куб
#define dT_OFF 11 // Гистерезис температуры в сотых градуса
#define TEMP_LOW 150 // Температура подвала критическая - может быть замораживание (в сотых градуса) - система выключается и включается нагреватель
// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для каждого ID свой. ОШИБКИ ДОБАВЛЯЮТСЯ!!
#if ID == 0x21
#define TOUT_ERR -40 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR -40 // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR -30 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR +30 // Ошибка домового датчика влажности в сотых долях %
#elif ID == 0x22
#define TOUT_ERR -12 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR -10 // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR -250 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR +250 // Ошибка домового датчика влажности в сотых долях %
#else
#define TOUT_ERR 0 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR 0 // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR 0 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR 0 // Ошибка домового датчика влажности в сотых долях %
#endif
// - ВРЕМЕНА ---------------------------------------
#ifdef DEMO // Для демо все быстрее и случайным образом
#define NUM_SAMPLES 2 // Число усреднений измерений датчика
#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 16 // Ножка куда повешен калорифер A2 (port C2)
#ifdef BEEP
#define PIN_BEEP 15 // Ножка куда повешена пищалка A1 (port C1)
#endif
#define PIN_RELAY 14 // Ножка на которую повешено реле (SSR) вентилятора - аналоговый вход A0 через резистор 470 ом (port C0)
#define PIN_CS 10 // TFT дисплей spi CS
#define PIN_CD 9 // TFT дисплей spi CD
#define PIN_RESET 8 // TFT дисплей spi Reset дисплей
#define PIN_CE 7 // nrf24 ce
#define PIN_CSN 6 // nrf24 cs
#define PIN_DHT22a 5 // Первый датчик DHT22 IN ДОМ
#define PIN_DHT22b 4 // Второй датчик DHT22 OUT УЛИЦА
#define PIN_KEY 3 // Кнопка, повешена на прерывание, что бы ресурсов не тратить (port D3)
#define PIN_IRQ_NRF24 2 // Ножка куда заведено прерывание от NRF24 (пока не используется)
// Настройки
#define NUM_SETTING 7 // Число вариантов настроек
#define BLOCK_OFF 0 // Выключено (вариант настроек)
#define HOOD_ON 1 // Режим вытяжки (вариант настроек)
#define COOLING 2 // Режим охлаждение (вариант настроек)
// АЦП ----------------------------------------
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; // Предыдущая ошибка чтения датчиков
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
unsigned long hour_heat=0; // мото часы нагревателя измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
int16_t tOutMin=5555,tInMin=5555; // Минимальные температуры за период температуры в сотых градуса !!! место экономим
int16_t tOutMax,tInMax=-5555; // Максимальные температуры за период температуры в сотых градуса. (Наблюдаю глюк при tOutMax=-5000)
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 id=ID; // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
byte DHT_error; // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал
int16_t tOut=-500,tIn=500; // Текущие температуры в сотых градуса !!! место экономим
uint16_t absHOut=123,absHIn=123; // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
uint16_t relHOut=123,relHIn=123; // Относительные влажности сотых процента !!! место экономим
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; // Порог выключения вентилятора по температуре в ДЕСЯТЫХ долях градуса, только положительные значения
uint8_t count=0; // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
char note[14] = LABEL; // Примечание не более 13 байт + "0" байт Русские буквы в два раза меньше т.к. UTF-8
} packet;
struct type_sensors // структура для усреднения измерений
{
int num=0; // сколько отсчетов уже сложили не болле NUM_SAMPLES
long sum_tOut=0,sum_tIn=0; // Сумма для усреднения Текущие температуры в сотых градуса !!! место экономим
long sum_relHOut=0,sum_relHIn=0; // Сумма для усреднения Относительные влажности сотых процента !!! место экономим
int tOut=-5000,tIn=5000; // Текущие температуры в сотых градуса !!! место экономим
int absHOut=55555,absHIn=55555; // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
int relHOut=55555,relHIn=55555; // Относительные влажности сотых процента !!! место экономим
} sensors;
// Массивы для графиков
uint8_t tOutChart[120];
uint8_t tInChart[120];
uint8_t absHOutChart[120];
uint8_t absHInChart[120];
uint8_t posChart=0; // Позиция в массиве графиков - начало вывода от 0 до 120-1
uint8_t TimeChart=0; // Время до вывода очередной точки на график.
bool ChartMotor=false; // Признак работы мотора во время интервала графика если мотор был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
bool ChartHeat=false; // Признак работы нагревателя во время интервала графика если нагреватель был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
#ifdef RADIO // Радио модуль NRF42l
RF24 radio(PIN_CE, PIN_CSN); // определение управляющих ног
bool send_packet_ok=false; // признак удачной отправки последнего пакета
unsigned long err_ask=0; // число не полученных ответов
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ПРОГРАММА
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
#ifdef DEBUG
Serial.begin(9600);
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
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,2); // Подключить Калорифер
WritePort(C,2,LOW);
pinMode(PIN_DHT22a, OUTPUT); // Датчик DHT22 #1
digitalWrite(PIN_DHT22a, HIGH);
pinMode(PIN_DHT22b, OUTPUT); // Датчик DHT22 #2
digitalWrite(PIN_DHT22b, HIGH);
reset_ili9341(); // сброс дисплея
readEeprom(); // Прочитать настройки
byte i=ReadPort(D,3);
if (i==0) // Если при включении нажата кнопка то стираем Eeprom
{
settingRAM.mode=0;
settingRAM.hour_unit=0;
settingRAM.hour_motor=0;
settingRAM.hour_heat=0;
settingRAM.tOutMin=5000;
settingRAM.tInMin=5000;
settingRAM.tOutMax=-5000;
settingRAM.tInMax=-5000;
ucg.setColor(255, 255, 255);
print_StrXY(10,50,F("Сброс настроек и счетчиков"));
for(i=0;i<3;i++)
{
delay(1000);
ucg.print(F(" ."));
}
writeEeprom(); // Запись в EEPROM
delay(1000);
ucg.clearScreen();
}
wdt_enable(WDTO_8S); // Сторожевой таймер Для тестов не рекомендуется устанавливать значение менее 8 сек.
// Запуск задач по таймеру
myOS.begin();
myOS.addTask(measurement,TIME_SCAN_SENSOR); // Измерение
attachInterrupt(1, scanKey, CHANGE); // КНОПКА Прерывания по обоим фронтам
print_static(); // распечатать таблицу
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("Сброс контроллера дисплея ili9341 . . ."));
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. Второй приоритет - сброс памяти температур работает только в информационном экране
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 сек сбрасываем температуры
{
settingRAM.tOutMin= 5000;
settingRAM.tInMin= 5000;
settingRAM.tOutMax=-5000;
settingRAM.tInMax= -5000;
writeEeprom(); // Сбросили температуры сохраняем в EEPROM
#ifdef BEEP
beep(500); // Звук нажатия на клавишу
#endif
printInfo(); // Обновить экран с новыми температурами
resetKey();
}
}
else // 3. Остальной обработчик нажатия на кнопок - Кнопку надо нажать и отпустить
if ((flagKey!=false)&&(pressKey==true)) // Кнопка была нажата и отпущена
{
// if (flagKey!=false) // Обработчик кнопки
if ((time_key>=LONG_KEY)&&(infoScreen!=true)) // Длительное нажатие кнопки и информационный экран не показан
printInfo(); // Вывод информационного экрана
else
if (time_key >= SHORT_KEY) // Короткое нажатие кнопки все остальное игнорируется
{
#ifdef BEEP
beep(30); // Звук нажатия на клавишу
#endif
if (infoScreen==true) clearInfo(); // если информационный экран показан то стереть его
else {if (settingRAM.mode >= NUM_SETTING) settingRAM.mode=0; // Кольцевой счетчик настроек
else settingRAM.mode++; // В противном случае следующая настройка
Setting(); }
}
resetKey(); // Сброс состояния кнопки
}
}
void resetKey(void) // Сброс состояния кнопки
{
flagKey=false;
pressKey=false;
time_key_res_temp=-1; // сброс состояния сброса температур
time_key_res_tft=-1; // сброс состояния сброса дисплея
}
void print_static() // Печать статической картинки
{
int i;
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("ОСУШИТЕЛЬ ID: 0x"));
ucg.print( hex(packet.id >> 4));
ucg.print( hex(packet.id&0x0f));
#ifdef DEMO
ucg.print(F(" demo"));
#endif
// Таблица для данных
ucg.setColor(0, 200, 0);
ucg.drawHLine(0,25,320-1);
ucg.drawHLine(0,25+23*1,320-1);
ucg.drawHLine(0,25+23*2,320-1);
ucg.drawHLine(0,25+23*3,320-1);
ucg.drawHLine(0,25+23*4,320-1);
ucg.drawVLine(200-4,25,24+23*3);
ucg.drawVLine(260,25,24+23*3);
// Заголовки таблиц
ucg.setColor(255, 255, 0);
// В зависимости от id разные надписи - привязка местоположения блока к ID
// print_StrXY(180+30-9,25+0+18,F(LABEL));
ucg.setPrintPos(180+30-9,25+0+18);
ucg.print(packet.note);
print_StrXY(250+20,25+0+18,F("Улица"));
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(10,135+0,F("Температура"));
print_StrXY(20+154,135+0,F("Абс. влажность"));
// надписи на графиках
print_StrXY(128,154,F("+20"));
print_StrXY(135,194,F("0"));
print_StrXY(128,233,F("-20"));
print_StrXY(296,164,F("15"));
print_StrXY(296,194,F("10"));
print_StrXY(296,223,F("5"));
// Горизонтальная шкала по часам
// ucg.setColor(255, 255, 0);
for(i=0;i<=120;i=i+12)
{
ucg.drawPixel(4+i,239);
ucg.drawPixel(4+i,238);
ucg.drawPixel(167+i,239);
ucg.drawPixel(167+i,238);
}
sei();
}
void print_status(void) // Печать панели статуса Значки на статус панели
{
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
cli();
// 1. печать ошибки чтения датчиков
print_error_DHT();
// 2. Признак включения мотора или нагревателя
if (FLAG_MOTOR_CHECK) ucg.setColor(0, 240, 0);
else if (FLAG_HEAT_CHECK) ucg.setColor(240, 0, 0);
else ucg.setColor(0, 40, 0);
ucg.drawBox(290-32, 5, 14, 14);
#ifdef RADIO
// 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);
#endif
sei();
}
void print_error_DHT(void) // Печать ошибки чтения датчиков выводится при каждом чтении датчика
{
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
// 1. печать ошибки чтения датчиков
if (packet.DHT_error!=last_error) // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
{
cli();
last_error=packet.DHT_error;
ucg.setColor(0, 0, 180); // Сначала стереть
ucg.drawBox(290, 0, 26, 18);
ucg.setPrintPos(280,18);
ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
if (packet.DHT_error>0)
{
ucg.setColor(255, 100, 100);
print_StrXY(280,19,F("0x"));
ucg.print( hex(packet.DHT_error >> 4));
ucg.print( hex(packet.DHT_error & 0x0f));
}
else { ucg.setColor(200, 240, 0); ucg.print(F(" ok ")); }
sei();
}
}
// вывод на экран данных (то что меняется)
void print_data()
{
// Статистика по моточасам, время ведется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR а потом пересчитывается в часы при выводе.
settingRAM.hour_unit++;
if (FLAG_MOTOR_CHECK) settingRAM.hour_motor++; // если мотор включен
if (FLAG_HEAT_CHECK) settingRAM.hour_heat++; // если нагреватель включен
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
cli();
// Печать значений
ucg.setFontMode(UCG_FONT_MODE_SOLID);
ucg.setColor(250, 0, 100); // Цвет ДОМА
print_floatXY(200+0,25+23*1+18,((float)packet.tIn)/100);
print_floatXY(200+0,25+23*2+18,((float)packet.relHIn)/100);
print_floatXY(200+0,25+23*3+18,((float)packet.absHIn)/100);
ucg.setColor(0, 250, 100); // Цвет УЛИЦЫ
print_floatXY(260+4,25+23*1+18,((float)packet.tOut)/100);
print_floatXY(260+6,25+23*2+18,((float)packet.relHOut)/100);
print_floatXY(260+6,25+23*3+18,((float)packet.absHOut)/100);
sei();
}
// Печать графика на экране, добавляется одна точка и график сдвигается
void printChart()
{
byte i,x=0;
uint8_t tmp;
// Работаем через кольцевой буфер
// Добавить новую точку в кольцевой буфер
// Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
if (packet.tIn<=-2500) tInChart[posChart]=0; // Если температура меньше -25 то округляем до -25
else if (packet.tIn>=2500) tInChart[posChart]=100-1; // Если температура больше 25 то округляем до 25
else tInChart[posChart]=(packet.tIn+2500)/50; // внутри -25...+25 растягиваем в два раза
if (ChartMotor==true) tInChart[posChart]|=0x80; // Признак включения мотора- старший бит в 1 - цвет фона на графике меняется
ChartMotor=false;
// Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
if (packet.tOut<=-2500) tOutChart[posChart]=0; // Если температура меньше -25 то округляем до -25
else if (packet.tOut>=2500) tOutChart[posChart]=100-1; // Если температура больше 25 то округляем до 25
else tOutChart[posChart]=(packet.tOut+2500)/50; // внутри -25...+25 растягиваем в два раза
if (ChartHeat==true) tOutChart[posChart]|=0x80; // Признак включения нагревателя- старший бит в 1 - цвет фона на графике меняется
ChartHeat=false;
// Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
if (packet.absHIn>=2000) absHInChart[posChart]=100-1;
else absHInChart[posChart]=packet.absHIn/20; // внутри 0...20 растягиваем в пять раз в сотых % по этому делем не на 100 а на 20
// Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
if (packet.absHOut>=2000) absHOutChart[posChart]=100-1;
else absHOutChart[posChart]=packet.absHOut/20; // внутри 0...20 растягиваем в пять раз, в сотых % по этому делем не на 100 а на 20
if (infoScreen==false) // если отображен информационный экран то ничего не выводим
{
cli();
for(i=0;i<120;i++) // График слева на право
{
// Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
if (posChart<i) x=120+posChart-i; else x=posChart-i;
// нарисовать фон в зависимости от статуса мотора
if (tOutChart[x]>=0x80) ucg.setColor(90, 60, 0); // Нагреватель был ключен - бледно желтый
else if (tInChart[x]>=0x80) ucg.setColor(0, 60, 90); // Мотор был ключен - бледно синий
else ucg.setColor(0, 0, 0); // все выключено
ucg.drawVLine(5+120-i,237-100,100);
ucg.drawVLine(5+120-i+162,237-100,100);
ucg.setColor(180, 180, 180);
if (i%5==0) // Пунктирные линии графика
{
ucg.drawPixel(5+120-i,236-10-1);
ucg.drawPixel(5+120-i,236-50-1);
ucg.drawPixel(5+120-i,236-90-1);
ucg.drawPixel(5+120-i+162,236-25-1);
ucg.drawPixel(5+120-i+162,236-50-1);
ucg.drawPixel(5+120-i+162,236-75-1);
}
// Вывести новую точку
tmp=tInChart[x]&0x7f; // Отбросить старший разряд - признак включения мотора
if ((tmp==0)||(tmp==100)) ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100);
ucg.drawPixel(5+120-i,236-tmp);
tmp=tOutChart[x]&0x7f; // Отбросить старший разряд - признак включения калорифера
if ((tmp==0) || (tmp==100)) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100);
ucg.drawPixel(5+120-i,236-tmp);
if (absHInChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100);
ucg.drawPixel(5+120-i+162,236-absHInChart[x]);
if (absHOutChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100);
ucg.drawPixel(5+120-i+162,236-absHOutChart[x]);
}
sei();
}
if (posChart<120-1) posChart++; else posChart=0; // Изменили положение в буфере и Замкнули буфер
}
// ---ПЕРЕДАЧА ДАННЫХ ЧЕРЕЗ РАДИОМОДУЛЬ -----------------------------
#ifdef RADIO // Радио модуль NRF42l
void send_packet()
{ radio.powerUp();
radio.stopListening(); // Остановить приемник
delay(2);
// radio.openWritingPipe(0xF0F0F0F0E1LL); // передатчик
cli();
// send_packet_ok = radio.write(&packet,sizeof(packet));
radio.writeBlocking(&packet,sizeof(packet),200); // Writes 1 payload to the buffers
send_packet_ok=radio.txStandBy();
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++; // при переполнении сам сбросится
// if (packet.count>255) packet.count=0;
}
#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()
{
myOS.pauseTask(measurement); // Обязательно здесь, а то датчики плохо читаются мешает leos
wdt_reset(); // Сбросить сторожевой таймер
packet.DHT_error=readDHT(PIN_DHT22a); // ПЕРВЫЙ ДАТЧИК ДОМ Новый пакет, сбросить все ошибки и прочитать первый датчик
#ifdef DEMO
DHT.temperature=packet.tIn/100+random(-20,30)/10.0;
if (DHT.temperature>20) DHT.temperature=19;
if (DHT.temperature<-10) DHT.temperature=-9;
DHT.humidity=packet.relHIn/100+(float)random(-5,8);
if (DHT.humidity>96) DHT.humidity=90;
if (DHT.humidity<1) DHT.humidity=10;
packet.DHT_error=0; // в Демо режиме
// DHT.temperature=3.0;
// DHT.humidity=21.0;
#endif
sensors.tIn=(int)(DHT.temperature*100.0)+TIN_ERR; // Запомнить результаты для суммирования с учетом ошибок
sensors.relHIn=(int)(DHT.humidity*100.0)+HIN_ERR;
#ifdef DEBUG
Serial.print(F("Sensor read samples:")); Serial.println(sensors.num);
Serial.print(F("IN T="));Serial.print(sensors.tIn);Serial.print(F(" H=")); Serial.print(sensors.relHIn); Serial.print(F(" error=")); Serial.println(packet.DHT_error);
#endif
packet.DHT_error=packet.DHT_error+16*readDHT(PIN_DHT22b);// ВТОРОЙ ДАТЧИК УЛИЦА ошибки в старшие четыре бита
#ifdef DEMO
DHT.temperature=packet.tOut/100+random(-20,30)/10.0;
if (DHT.temperature>30) DHT.temperature=19;
if (DHT.temperature<-30) DHT.temperature=-9;
DHT.humidity=packet.relHOut/100+(float)random(-5,8);
if (DHT.humidity>96) DHT.humidity=90;
if (DHT.humidity<1) DHT.humidity=10;
packet.DHT_error=0; // в Демо режиме
// DHT.temperature=-10.0;
// DHT.humidity=40.0;
#endif
sensors.tOut=(int)(DHT.temperature*100.0)+TOUT_ERR; // Запомнить результаты для суммирования с учетом ошибок
sensors.relHOut=(int)(DHT.humidity*100.0)+HOUT_ERR;
#ifdef DEBUG
Serial.print(F("OUT T="));Serial.print(sensors.tOut);Serial.print(F(" H=")); Serial.print(sensors.relHOut); Serial.print(F(" error=")); Serial.println(packet.DHT_error);
#endif
print_error_DHT(); // Вывод ошибки чтения датчика при каждом чтении контроль за качеством связи с датчиками
if (packet.DHT_error==0)// Если чтение без ошибок у ДВУХ датчиков копим сумму для усреднения
{
sensors.sum_tIn=sensors.sum_tIn+sensors.tIn;
sensors.sum_relHIn=sensors.sum_relHIn+sensors.relHIn;
sensors.sum_tOut=sensors.sum_tOut+sensors.tOut;
sensors.sum_relHOut=sensors.sum_relHOut+sensors.relHOut;
sensors.num++;
}
// набрали в сумме нужное число отсчетов рассчитываем усреднение и выводим
if (sensors.num>=NUM_SAMPLES) // Пора усреднять и выводить значения
{
// вычисление средних значений
packet.tIn=sensors.sum_tIn/NUM_SAMPLES;
packet.relHIn=sensors.sum_relHIn/NUM_SAMPLES;
packet.tOut=sensors.sum_tOut/NUM_SAMPLES;
packet.relHOut=sensors.sum_relHOut/NUM_SAMPLES;
reset_sum(); // Сброс счетчиков и сумм
// вычисление абсолютной влажности
packet.absHIn=(int)(calculationAbsH((float)(packet.tIn/100.0),(float)(packet.relHIn/100.0))*100.0);
packet.absHOut=(int)(calculationAbsH((float)(packet.tOut/100.0),(float)(packet.relHOut/100.0))*100.0);
#ifdef DEBUG
Serial.println(F("Average value >>>>>>>>>>"));
Serial.print(F("IN T="));Serial.print(packet.tIn);Serial.print(F(" H=")); Serial.print(packet.relHIn); Serial.print(F(" abs H=")); Serial.println(packet.absHIn);
Serial.print(F("OUT T="));Serial.print(packet.tOut);Serial.print(F(" H=")); Serial.print(packet.relHOut); Serial.print(F(" abs H=")); Serial.println(packet.absHOut);
#endif
#ifdef RADIO // Радио модуль NRF42l
send_packet(); // Послать данные
#endif
CheckON(); // Проверка статуса вентилятора
print_data(); // вывод усредненных значений
print_status(); // панель состояния
// Обновляем максимум и минимум температур в EEPROM
// Cделано не честно - обновляем раз в 30 секунд (NUM_SAMPLES*TIME_SCAN_SENSOR) а пишем раз в час, могут пропасть данные при сбросе
if (settingRAM.tInMax<packet.tIn) settingRAM.tInMax=packet.tIn;
else if (settingRAM.tInMin>packet.tIn) settingRAM.tInMin=packet.tIn;
if (settingRAM.tOutMax<packet.tOut) settingRAM.tOutMax=packet.tOut;
else if (settingRAM.tOutMin>packet.tOut) settingRAM.tOutMin=packet.tOut;
// Пишем в EEPROM не каждый раз!! экономим ресурс записей 100000 мин.
settingRAM.tick_eeprom++;
if (((long)settingRAM.tick_eeprom*NUM_SAMPLES*TIME_SCAN_SENSOR)>=(long)TIME_HOUR) // пора писать eeprom
{ writeEeprom(); settingRAM.tick_eeprom=0; }
// Запись статистики в EEPROM
if (FLAG_MOTOR_CHECK) ChartMotor=true; // Признак того что надо показывать включение мотора на графике
if (FLAG_HEAT_CHECK) ChartHeat=true; // Признак того что надо показывать включение нагревателя на графике
TimeChart++;
if ((long)((long)TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проврека не пора ли выводить график
{ printChart(); TimeChart=0; // Сдвиг графика и вывод новой точки
#ifdef DEBUG
Serial.println(F("Point add chart ++++++++++++++++++++"));
#endif
#ifdef BEEP
// beep(50);
#endif
}
}
myOS.restartTask(measurement); // Пустить задачи
}
// Функция переводит относительную влажность в абсолютную
// 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();
}
// Проверка статуса вытяжки, не пора ли переключится
void CheckON(void)
{
int tmp=0;
// 0. Проверить замораживание подвала НАГРЕВАТЕЛЬ
if (packet.tIn<=TEMP_LOW) { MOTOR_OFF; HEAT_ON; return;} // Контроль от промораживания подвала по идеи здесь надо включать калорифер
if ((FLAG_HEAT_CHECK)&&(packet.tIn>TEMP_LOW+dT_OFF+50)) HEAT_OFF; // Выключить калорифер когда температура поднимится на 0.5 градуса
// 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.tIn>(packet.T_min*10))&&((packet.tIn-packet.tOut)>packet.dH_min)) // dH_min используется не штатно для температуры
{MOTOR_ON; return;} // мотор выключен, температура выше установленной и снаружи температура ниже на 2 градуса то ВКЛЮЧЕНИЕ мотора
if ((FLAG_MOTOR_CHECK)&&(packet.tIn<=packet.tOut))
{MOTOR_OFF; return;} // мотор включен и темература внутри ниже наружней то ВЫКЛЮЧЕННИЕ мотора
return; // изменений нет выходим
}
// 3. В режиме осушения - проверка на достижение минимальной температуры помещения в режиме осушения - СРОЧНО ВЫКЛЮЧИТЬ третий приоритет
if (packet.tIn<=(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.absHIn*(packet.dH_min/10)/100); // Если режимы используют ДЕСЯТЫЕ % от абсолютной разности внутренней температуры
if ((~FLAG_MOTOR_CHECK)&&(packet.tIn>(packet.T_min*10))&&((packet.absHIn-tmp)>packet.absHOut))
{MOTOR_ON; return;} // мотор выключен, темература выше критической, абс влажность с наружи меньше то ВКЛЮЧЕНИЕ мотора
if ((FLAG_MOTOR_CHECK)&&((packet.tIn<=(tmp+dT_OFF))||(packet.absHIn<(packet.absHOut+dH_OFF))))
{MOTOR_OFF; return;} // мотор включен и темература ниже критической или абс влажность внутри ниже то ВЫКЛЮЧЕННИЕ мотора
}
// Вывод информации о настройках и сохрание индекса настроек в eeprom ---------------------------------
void Setting()
{
// Настройка
cli();
ucg.setColor(0, 100, 255);
ucg.setFontMode(UCG_FONT_MODE_SOLID);
ucg.setPrintPos(0,25+0+18);
switch (settingRAM.mode)
{
case BLOCK_OFF: ucg.print(F("Выключено ")); packet.dH_min=255; packet.T_min=255; FLAG_ABS_H_ON; break;
case HOOD_ON: ucg.print(F("Режим вытяжки ")); packet.dH_min=0; packet.T_min=0; FLAG_ABS_H_ON; break;
case COOLING: ucg.print(F("Охлаждение T>10 dT>2")); packet.dH_min=200; packet.T_min=100; FLAG_ABS_H_ON; break; // dH_min используется не штатно для температуры
case 3: ucg.print(F("Осушение T>+3 dH>0.2 ")); packet.dH_min=20; packet.T_min=30; FLAG_ABS_H_ON; break;
case 4: ucg.print(F("Осушение T>+3 dH>3% ")); packet.dH_min=30; packet.T_min=30; FLAG_ABS_H_OFF; break;
case 5: ucg.print(F("Осушение T>+4 dH>0.3 ")); packet.dH_min=30; packet.T_min=40; FLAG_ABS_H_ON; break;
case 6: ucg.print(F("Осушение T>+4 dH>6% ")); packet.dH_min=60; packet.T_min=40; FLAG_ABS_H_OFF; break;
case 7: ucg.print(F("Осушение T>+5 dH>0.7 ")); packet.dH_min=70; packet.T_min=50; FLAG_ABS_H_ON; break;
}
writeEeprom(); // Запись в EEPROM новых настроек
SET_MODE_BITS; // В пакет для передачи добавили смену режима
MOTOR_OFF; // Поменяли настройки - отключить мотор, пусть заново настройки сработают если потребуется
CheckON(); // Возможно надо включить мотор
print_status(); // Показать панель состояния (смена настроек)
sei();
}
// Вывод float с двумя десятичными знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
ucg.setPrintPos(x,y);
ucg.print(v,2);
ucg.print(F(" ")); // Стереть хвост от предыдущего числа
}
// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
ucg.setPrintPos(x,y);
ucg.print(b);
}
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+230,15+16*2,readVcc()/1000.0);
print_StrXY(5+10,15+16*3,F("2 Температура блока гр."));
print_floatXY(5+230,15+16*3,GetTemp());
print_StrXY(5+10,15+16*4,F("3 Мото часы блока"));
ucg.setPrintPos(5+230,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+230,15+16*5);
ucg.print(settingRAM.hour_motor/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));
print_StrXY(5+10,15+16*6,F("5 Мото часы нагревателя"));
ucg.setPrintPos(5+230,15+16*6);
ucg.print(settingRAM.hour_heat/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));
print_StrXY(5+10,15+16*7,F("6 Мах температуры in/out"));
ucg.setPrintPos(5+230,15+16*7);
ucg.print(settingRAM.tInMax/100);
ucg.print(F("/"));
ucg.print(settingRAM.tOutMax/100);
print_StrXY(5+10,15+16*8,F("7 Мin температуры in/out"));
ucg.setPrintPos(5+230,15+16*8);
ucg.print(settingRAM.tInMin/100);
ucg.print(F("/"));
ucg.print(settingRAM.tOutMin/100);
print_StrXY(5+10,15+16*9,F("8 Канал NRF24l01+"));
ucg.setPrintPos(5+230,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);
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();
#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();
}
// Чтение свободной памяти --------------------------------------------------------------------
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;
sensors.sum_tOut=0;
sensors.sum_tIn=0;
sensors.sum_relHOut=0;
sensors.sum_relHIn=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
1. Действительно пропустил 3,3В -исправил, поставил LD1117s3.3
2.Было предусмотрено кидать проводок на D2, между переходнымим отверстиями. сейчас кинул перемычку.
3.Керамика разбрасывается прямо на шины питания, потому я их так близко развел.
4. Дело в том, что у меня уже все установлено в подвале гаража.. Шилд на 4 реле с опторазвязкой в гермобоксе и подведено силовое питание 220В. Тепловентилятор и вентилятор приточной трубы установлены штатно уже 5 лет. Модуль управления будет находиться на верху в гараже, дистанция около 6м...Для соединения с датчиками и реле будет использован экранированный кабель 8х0,22. Радио канал в гараже использоваться не будет.
А второй комплект для дачи с подвалом, будет делаться в полном объеме в т.ч. и радио.
П.С.
Что-то не смог найти tft2.8" или 3,2" с SPI Ili 9341 без тачьпада по нормальной цене. 2,4 полно..
Места не хватит, если только вместо nrf24 но все равно места мало.
На короткие расстояния (до 50 метров ) nrf24 с усилком и внешенй антенной работает достаточно стабильно. Я сеть планирую прикручивать к приемнику (который на stm32). Он будет собирать со всех устройств данные и потом можно будет смотреть через инет их. Лог на карту или на USB флеш. Но это пока планы.
miaua пишет:
4. Дело в том, что у меня уже все установлено в подвале гаража.. Шилд на 4 реле с опторазвязкой в гермобоксе и подведено силовое питание 220В. Тепловентилятор и вентилятор приточной трубы установлены штатно уже 5 лет. Модуль управления будет находиться на верху в гараже, дистанция около 6м...Для соединения с датчиками и реле будет использован экранированный кабель 8х0,22. Радио канал в гараже использоваться не будет.
А второй комплект для дачи с подвалом, будет делаться в полном объеме в т.ч. и радио.
П.С.
Что-то не смог найти tft2.8" или 3,2" с SPI Ili 9341 без тачьпада по нормальной цене. 2,4 полно..
Там можно развести сразу оба варианта на одной плате (с выводами на реле и с силовой частью ) . И первый экземпляр распаять частично, а второй полностью. ИХМО трудозатрат будет меньше. И сделать сразу две три одинаковые платы.
Я почитал даташит по "Твердотельные реле серий SSR и TSR"... А там такое написано:- "применяется только с резистивной, емкостной нагрузкой и нельзя с высокоиндуктивной."
Любое твердотельное реле не любит индуктивную нагрузку. Самоиндукция может пробить его. Но вентилятор 30-50 ватт наверно является не сильно индуктивной нагрузкой.
Я использую шарп s2002s02. И паралельно реле установлен конденсатор 0.1 мкф 400 вольт.
Напомню, использование обычного реле у меня приводило к сбросу контроллера дисплея. Имено из за индуктивной нагрузки.
miaua, для вентиляторов у меня разведены связки MOC3061+BT134. (схема в даташите на MOC). Снабберные цепочки не ставил, с Домовентами второй год работает без проблем, радиаторы не требуются.
Твердотелы есть смысл ставить на мощные нагрузки, где требуется большой теплоотвод или кто не разводит плату, иначе - выброшенные деньги. ИМХО.
pav2000, 400В конденсатор очень на пределе, лучше 600В.
Очередная версия кода. Код разросся и в режиме демо уже не влезает - пришлось отключть часть кода в этом режиме (инфо экран).
Основные изменения - встроил часы. Время передается в ответе головного блока (в stm32 нашел встроенные rtc). Далее блок сам считает секунды (по таймеру измерения). При новом ответе головного блока производится корректировка времени. Получилось изящно и без аппартных затрат.
Немного скорректировал настройки блока с учетом его эксплуатации - сделал более "грубое" включение.
Наверное проект пришел к своему логическому завершению - я реализовал все свои мыслимые задумки.
/*
Описание
1. Часы реального времени. Данные приходят от головного блока. Потом обновлются по таймеру (). Пришедшие данные имеют более высокий приоритет
1.1 Установить библиотеку Time library - https://github.com/PaulStoffregen/Time
У нее не удачное имя (винды регистронезависимы и имя совпадает с системным -- среда глючит)
переименовать в stmTime директорию, файлы и ссылки в них !!!
*/
#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 справка https://code.google.com/p/ucglib/wiki/
#include "rusFont.h" // Русские шрифты
#include "nRF24L01.h" // Беcпроводной модуль надо использовать библиотеку http://tmrh20.github.io/RF24
#include "RF24.h" // Беcпроводной модуль используются не стандартные функции https://github.com/TMRh20
// - ОПЦИИ -------------------------------
//#define DEBUG // Отладочную информацию в ком порт посылает
//#define DEMO // Признак демонстрации - данные с датчиков генерятся рандом
#define BEEP // Использовать пищалку
#define RADIO // Признак использования радио модуля
#define VERSION "Ver. 0.72 07/10/15" // Текущая версия
#define ID 0x21 // уникально Идентификатор устройства (тип) - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
#define LABEL "Подпол" // "Подпол" "Подвал" // Место установки блока - отоображается как заголовок колонки в таблице. Не более 6 символов а то не влезает
// Макросы для работы с портами скорость и место
#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; } // выключить мотор
#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,2,HIGH); FLAG_HEAT_ON; } // включить калорифер
#define HEAT_OFF { WritePort(C,2,LOW); FLAG_HEAT_OFF; } // выключить калорифер
// - КОНСТАНТЫ --------------------------------------
#define dH_OFF 5 // Гистерезис абсолютной влажности в сотых грамма на куб
#define dT_OFF 11 // Гистерезис температуры в сотых градуса
#define TEMP_LOW 150 // Температура подвала критическая - может быть замораживание (в сотых градуса) - система выключается и включается нагреватель
// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для каждого ID свой. ОШИБКИ ДОБАВЛЯЮТСЯ!!
#if ID == 0x21 // ОТКАЛИБРОВАНО не менять
#define TOUT_ERR +10 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR +50 // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR -0 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR +50 // Ошибка домового датчика влажности в сотых долях %
#elif ID == 0x22 // ОТКАЛИБРОВАНО не менять
#define TOUT_ERR +50 // Ошибка уличного датчика температуры в сотых долях градуса +50
#define TIN_ERR +50 // Ошибка домового датчика температуры в сотых долях градуса +50
#define HOUT_ERR -230+60 // Ошибка уличного датчика влажности в сотых долях % -260
#define HIN_ERR +490 // Ошибка домового датчика влажности в сотых долях % +480
#else
#define TOUT_ERR 0 // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR 0 // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR 0 // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR 0 // Ошибка домового датчика влажности в сотых долях %
#endif
// - ВРЕМЕНА ---------------------------------------
#ifdef DEMO // Для демо все быстрее и случайным образом
#define NUM_SAMPLES 2 // Число усреднений измерений датчика ТОЛЬКО целые тысячи для точности часов
#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 16 // Ножка куда повешен калорифер A2 (port C2)
#ifdef BEEP
#define PIN_BEEP 15 // Ножка куда повешена пищалка A1 (port C1)
#endif
#define PIN_RELAY 14 // Ножка на которую повешено реле (SSR) вентилятора - аналоговый вход A0 через резистор 470 ом (port C0)
#define PIN_CS 10 // TFT дисплей spi CS
#define PIN_CD 9 // TFT дисплей spi CD
#define PIN_RESET 8 // TFT дисплей spi Reset дисплей
#define PIN_CE 7 // nrf24 ce
#define PIN_CSN 6 // nrf24 cs
#define PIN_DHT22a 5 // Первый датчик DHT22 IN ДОМ
#define PIN_DHT22b 4 // Второй датчик DHT22 OUT УЛИЦА
#define PIN_KEY 3 // Кнопка, повешена на прерывание, что бы ресурсов не тратить (port D3)
#define PIN_IRQ_NRF24 2 // Ножка куда заведено прерывание от NRF24 (пока не используется)
// Настройки
#define NUM_SETTING 7 // Число вариантов настроек
#define BLOCK_OFF 0 // Выключено (вариант настроек)
#define HOOD_ON 1 // Режим вытяжки (вариант настроек)
#define COOLING 2 // Режим охлаждение (вариант настроек)
#include "stmTime.h" // Time library - https://github.com/PaulStoffregen/Time
//#define TZ "UTC+3" // Часовой пояс
unsigned long tt=0; // Время пришедшее от головного блока если 0 то вроемя не приходило ничего не выводим
// АЦП ----------------------------------------
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; // Предыдущая ошибка чтения датчиков
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
unsigned long hour_heat=0; // мото часы нагревателя измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
int16_t tOutMin=5555,tInMin=5555; // Минимальные температуры за период температуры в сотых градуса !!! место экономим
int16_t tOutMax,tInMax=-5555; // Максимальные температуры за период температуры в сотых градуса. (Наблюдаю глюк при tOutMax=-5000)
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 id=ID; // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
byte DHT_error; // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал
int16_t tOut=-500,tIn=500; // Текущие температуры в сотых градуса !!! место экономим
uint16_t absHOut=123,absHIn=123; // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
uint16_t relHOut=123,relHIn=123; // Относительные влажности сотых процента !!! место экономим
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; // Порог выключения вентилятора по температуре в ДЕСЯТЫХ долях градуса, только положительные значения
uint8_t count=0; // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
char note[14] = LABEL; // Примечание не более 13 байт + "0" байт Русские буквы в два раза меньше т.к. UTF-8
} packet;
struct type_sensors // структура для усреднения измерений
{
int num=0; // сколько отсчетов уже сложили не болле NUM_SAMPLES
long sum_tOut=0,sum_tIn=0; // Сумма для усреднения Текущие температуры в сотых градуса !!! место экономим
long sum_relHOut=0,sum_relHIn=0; // Сумма для усреднения Относительные влажности сотых процента !!! место экономим
int tOut=-5000,tIn=5000; // Текущие температуры в сотых градуса !!! место экономим
int absHOut=55555,absHIn=55555; // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
int relHOut=55555,relHIn=55555; // Относительные влажности сотых процента !!! место экономим
} sensors;
// Массивы для графиков
uint8_t tOutChart[120];
uint8_t tInChart[120];
uint8_t absHOutChart[120];
uint8_t absHInChart[120];
uint8_t posChart=0; // Позиция в массиве графиков - начало вывода от 0 до 120-1
uint8_t TimeChart=0; // Время до вывода очередной точки на график.
bool ChartMotor=false; // Признак работы мотора во время интервала графика если мотор был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
bool ChartHeat=false; // Признак работы нагревателя во время интервала графика если нагреватель был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
#ifdef RADIO // Радио модуль NRF42l
RF24 radio(PIN_CE, PIN_CSN); // определение управляющих ног
bool send_packet_ok=false; // признак удачной отправки последнего пакета
unsigned long err_ask=0; // число не полученных ответов
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ПРОГРАММА
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
#ifdef DEBUG
Serial.begin(9600);
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
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,2); // Подключить Калорифер
WritePort(C,2,LOW);
pinMode(PIN_DHT22a, OUTPUT); // Датчик DHT22 #1
digitalWrite(PIN_DHT22a, HIGH);
pinMode(PIN_DHT22b, OUTPUT); // Датчик DHT22 #2
digitalWrite(PIN_DHT22b, HIGH);
reset_ili9341(); // сброс дисплея
readEeprom(); // Прочитать настройки
byte i=ReadPort(D,3);
if (i==0) // Если при включении нажата кнопка то стираем Eeprom
{
settingRAM.mode=0;
settingRAM.hour_unit=0;
settingRAM.hour_motor=0;
settingRAM.hour_heat=0;
settingRAM.tOutMin=5000;
settingRAM.tInMin=5000;
settingRAM.tOutMax=-5000;
settingRAM.tInMax=-5000;
ucg.setColor(255, 255, 255);
print_StrXY(10,50,F("Сброс настроек и счетчиков"));
for(i=0;i<3;i++)
{
delay(1000);
ucg.print(F(" ."));
}
writeEeprom(); // Запись в EEPROM
delay(1000);
ucg.clearScreen();
}
wdt_enable(WDTO_8S); // Сторожевой таймер Для тестов не рекомендуется устанавливать значение менее 8 сек.
// Запуск задач по таймеру
myOS.begin();
myOS.addTask(measurement,TIME_SCAN_SENSOR); // Измерение
attachInterrupt(1, scanKey, CHANGE); // КНОПКА Прерывания по обоим фронтам
print_static(); // распечатать таблицу
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("Сброс контроллера дисплея ILI9341 . . ."));
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. Второй приоритет - сброс памяти температур работает только в информационном экране
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 сек сбрасываем температуры
{
settingRAM.tOutMin= 5000;
settingRAM.tInMin= 5000;
settingRAM.tOutMax=-5000;
settingRAM.tInMax= -5000;
writeEeprom(); // Сбросили температуры сохраняем в EEPROM
#ifdef BEEP
beep(500); // Звук нажатия на клавишу
#endif
printInfo(); // Обновить экран с новыми температурами
resetKey();
}
}
else // 3. Остальной обработчик нажатия на кнопок - Кнопку надо нажать и отпустить
if ((flagKey!=false)&&(pressKey==true)) // Кнопка была нажата и отпущена
{
// if (flagKey!=false) // Обработчик кнопки
if ((time_key>=LONG_KEY)&&(infoScreen!=true)) // Длительное нажатие кнопки и информационный экран не показан
printInfo(); // Вывод информационного экрана
else
if (time_key >= SHORT_KEY) // Короткое нажатие кнопки все остальное игнорируется
{
#ifdef BEEP
beep(30); // Звук нажатия на клавишу
#endif
if (infoScreen==true) clearInfo(); // если информационный экран показан то стереть его
else {if (settingRAM.mode >= NUM_SETTING) settingRAM.mode=0; // Кольцевой счетчик настроек
else settingRAM.mode++; // В противном случае следующая настройка
Setting(); }
}
resetKey(); // Сброс состояния кнопки
}
}
void resetKey(void) // Сброс состояния кнопки
{
flagKey=false;
pressKey=false;
time_key_res_temp=-1; // сброс состояния сброса температур
time_key_res_tft=-1; // сброс состояния сброса дисплея
}
void print_static() // Печать статической картинки
{
int i;
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("ОСУШИТЕЛЬ ID: 0x"));
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);
ucg.drawVLine(200-4,25,24+23*3);
ucg.drawVLine(260,25,24+23*3);
// Заголовки таблиц
ucg.setColor(255, 255, 0);
// В зависимости от id разные надписи - привязка местоположения блока к ID
// print_StrXY(180+30-9,25+0+18,F(LABEL));
ucg.setPrintPos(180+30-9,25+0+18);
ucg.print(packet.note);
print_StrXY(250+20,25+0+18,F("Улица"));
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(10,135+0,F("Температура"));
print_StrXY(20+154,135+0,F("Абс. влажность"));
// надписи на графиках
print_StrXY(128,154,F("+20"));
print_StrXY(135,194,F("0"));
print_StrXY(128,233,F("-20"));
print_StrXY(296,164,F("15"));
print_StrXY(296,194,F("10"));
print_StrXY(296,223,F("5"));
// Горизонтальная шкала по часам
// ucg.setColor(255, 255, 0);
for(i=0;i<=120;i=i+12)
{
ucg.drawPixel(4+i,239);
ucg.drawPixel(4+i,238);
ucg.drawPixel(167+i,239);
ucg.drawPixel(167+i,238);
}
sei();
}
void print_status(void) // Печать панели статуса Значки на статус панели
{
byte temp;
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
cli();
// 1. печать ошибки чтения датчиков
print_error_DHT();
// 2. Признак включения мотора или нагревателя
if (FLAG_MOTOR_CHECK) ucg.setColor(0, 240, 0);
else if (FLAG_HEAT_CHECK) ucg.setColor(240, 0, 0);
else ucg.setColor(0, 40, 0);
ucg.drawBox(290-32, 5, 14, 14);
#ifdef RADIO
// 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. печать ошибки чтения датчиков
if (packet.DHT_error!=last_error) // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
{
cli();
last_error=packet.DHT_error;
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>0)
{
ucg.setColor(255, 100, 100);
print_StrXY(280,18,F("0x"));
ucg.print( hex(packet.DHT_error >> 4));
ucg.print( hex(packet.DHT_error & 0x0f));
}
else { ucg.setColor(200, 240, 0); ucg.print(F(" ok ")); }
sei();
}
}
// вывод на экран данных (то что меняется)
void print_data()
{
// Статистика по моточасам, время ведется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR а потом пересчитывается в часы при выводе.
settingRAM.hour_unit++;
if (FLAG_MOTOR_CHECK) settingRAM.hour_motor++; // если мотор включен
if (FLAG_HEAT_CHECK) settingRAM.hour_heat++; // если нагреватель включен
if (infoScreen==true) return; // если отображен информационный экран то ничего не выводим
cli();
// Печать значений
ucg.setFontMode(UCG_FONT_MODE_SOLID);
ucg.setColor(250, 0, 100); // Цвет ДОМА
print_floatXY(200+0,25+23*1+18,((float)packet.tIn)/100);
print_floatXY(200+0,25+23*2+18,((float)packet.relHIn)/100);
print_floatXY(200+0,25+23*3+18,((float)packet.absHIn)/100);
ucg.setColor(0, 250, 100); // Цвет УЛИЦЫ
print_floatXY(260+4,25+23*1+18,((float)packet.tOut)/100);
print_floatXY(260+6,25+23*2+18,((float)packet.relHOut)/100);
print_floatXY(260+6,25+23*3+18,((float)packet.absHOut)/100);
sei();
}
// Печать графика на экране, добавляется одна точка и график сдвигается
void printChart()
{
byte i,x=0;
uint8_t tmp;
// Работаем через кольцевой буфер
// Добавить новую точку в кольцевой буфер
// Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
if (packet.tIn<=-2500) tInChart[posChart]=0; // Если температура меньше -25 то округляем до -25
else if (packet.tIn>=2500) tInChart[posChart]=100-1; // Если температура больше 25 то округляем до 25
else tInChart[posChart]=(packet.tIn+2500)/50; // внутри -25...+25 растягиваем в два раза
if (ChartMotor==true) tInChart[posChart]|=0x80; // Признак включения мотора- старший бит в 1 - цвет фона на графике меняется
ChartMotor=false;
// Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
if (packet.tOut<=-2500) tOutChart[posChart]=0; // Если температура меньше -25 то округляем до -25
else if (packet.tOut>=2500) tOutChart[posChart]=100-1; // Если температура больше 25 то округляем до 25
else tOutChart[posChart]=(packet.tOut+2500)/50; // внутри -25...+25 растягиваем в два раза
if (ChartHeat==true) tOutChart[posChart]|=0x80; // Признак включения нагревателя- старший бит в 1 - цвет фона на графике меняется
ChartHeat=false;
// Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
if (packet.absHIn>=2000) absHInChart[posChart]=100-1;
else absHInChart[posChart]=packet.absHIn/20; // внутри 0...20 растягиваем в пять раз в сотых % по этому делем не на 100 а на 20
// Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
if (packet.absHOut>=2000) absHOutChart[posChart]=100-1;
else absHOutChart[posChart]=packet.absHOut/20; // внутри 0...20 растягиваем в пять раз, в сотых % по этому делем не на 100 а на 20
if (infoScreen==false) // если отображен информационный экран то ничего не выводим
{
cli();
for(i=0;i<120;i++) // График слева на право
{
// Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
if (posChart<i) x=120+posChart-i; else x=posChart-i;
// нарисовать фон в зависимости от статуса мотора
if (tOutChart[x]>=0x80) ucg.setColor(90, 60, 0); // Нагреватель был ключен - бледно желтый
else if (tInChart[x]>=0x80) ucg.setColor(0, 60, 90); // Мотор был ключен - бледно синий
else ucg.setColor(0, 0, 0); // все выключено
ucg.drawVLine(5+120-i,237-100,100);
ucg.drawVLine(5+120-i+162,237-100,100);
ucg.setColor(180, 180, 180);
if (i%5==0) // Пунктирные линии графика
{
ucg.drawPixel(5+120-i,236-10-1);
ucg.drawPixel(5+120-i,236-50-1);
ucg.drawPixel(5+120-i,236-90-1);
ucg.drawPixel(5+120-i+162,236-25-1);
ucg.drawPixel(5+120-i+162,236-50-1);
ucg.drawPixel(5+120-i+162,236-75-1);
}
// Вывести новую точку
tmp=tInChart[x]&0x7f; // Отбросить старший разряд - признак включения мотора
if ((tmp==0)||(tmp==100)) ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100);
ucg.drawPixel(5+120-i,236-tmp);
tmp=tOutChart[x]&0x7f; // Отбросить старший разряд - признак включения калорифера
if ((tmp==0) || (tmp==100)) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100);
ucg.drawPixel(5+120-i,236-tmp);
if (absHInChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100);
ucg.drawPixel(5+120-i+162,236-absHInChart[x]);
if (absHOutChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100);
ucg.drawPixel(5+120-i+162,236-absHOutChart[x]);
}
sei();
}
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()
{
myOS.pauseTask(measurement); // Обязательно здесь, а то датчики плохо читаются мешает leos
wdt_reset(); // Сбросить сторожевой таймер
packet.DHT_error=readDHT(PIN_DHT22a); // ПЕРВЫЙ ДАТЧИК ДОМ Новый пакет, сбросить все ошибки и прочитать первый датчик
#ifdef DEMO
DHT.temperature=packet.tIn/100+random(-20,30)/10.0;
if (DHT.temperature>20) DHT.temperature=19;
if (DHT.temperature<-10) DHT.temperature=-9;
DHT.humidity=packet.relHIn/100+(float)random(-5,8);
if (DHT.humidity>96) DHT.humidity=90;
if (DHT.humidity<1) DHT.humidity=10;
packet.DHT_error=0; // в Демо режиме
// DHT.temperature=3.0;
// DHT.humidity=21.0;
#endif
sensors.tIn=(int)(DHT.temperature*100.0)+TIN_ERR; // Запомнить результаты для суммирования с учетом ошибок
sensors.relHIn=(int)(DHT.humidity*100.0)+HIN_ERR;
#ifdef DEBUG
Serial.print(F("Sensor read samples:")); Serial.println(sensors.num);
Serial.print(F("IN T="));Serial.print(sensors.tIn);Serial.print(F(" H=")); Serial.print(sensors.relHIn); Serial.print(F(" error=")); Serial.println(packet.DHT_error);
#endif
packet.DHT_error=packet.DHT_error+16*readDHT(PIN_DHT22b);// ВТОРОЙ ДАТЧИК УЛИЦА ошибки в старшие четыре бита
#ifdef DEMO
DHT.temperature=packet.tOut/100+random(-20,30)/10.0;
if (DHT.temperature>30) DHT.temperature=19;
if (DHT.temperature<-30) DHT.temperature=-9;
DHT.humidity=packet.relHOut/100+(float)random(-5,8);
if (DHT.humidity>96) DHT.humidity=90;
if (DHT.humidity<1) DHT.humidity=10;
packet.DHT_error=0; // в Демо режиме
// DHT.temperature=-10.0;
// DHT.humidity=40.0;
#endif
sensors.tOut=(int)(DHT.temperature*100.0)+TOUT_ERR; // Запомнить результаты для суммирования с учетом ошибок
sensors.relHOut=(int)(DHT.humidity*100.0)+HOUT_ERR;
#ifdef DEBUG
Serial.print(F("OUT T="));Serial.print(sensors.tOut);Serial.print(F(" H=")); Serial.print(sensors.relHOut); Serial.print(F(" error=")); Serial.println(packet.DHT_error);
#endif
print_error_DHT(); // Вывод ошибки чтения датчика при каждом чтении контроль за качеством связи с датчиками
if (packet.DHT_error==0)// Если чтение без ошибок у ДВУХ датчиков копим сумму для усреднения
{
sensors.sum_tIn=sensors.sum_tIn+sensors.tIn;
sensors.sum_relHIn=sensors.sum_relHIn+sensors.relHIn;
sensors.sum_tOut=sensors.sum_tOut+sensors.tOut;
sensors.sum_relHOut=sensors.sum_relHOut+sensors.relHOut;
sensors.num++;
}
// набрали в сумме нужное число отсчетов рассчитываем усреднение и выводим
if (sensors.num>=NUM_SAMPLES) // Пора усреднять и выводить значения
{
// вычисление средних значений
packet.tIn=sensors.sum_tIn/NUM_SAMPLES;
packet.relHIn=sensors.sum_relHIn/NUM_SAMPLES;
packet.tOut=sensors.sum_tOut/NUM_SAMPLES;
packet.relHOut=sensors.sum_relHOut/NUM_SAMPLES;
reset_sum(); // Сброс счетчиков и сумм
// вычисление абсолютной влажности
packet.absHIn=(int)(calculationAbsH((float)(packet.tIn/100.0),(float)(packet.relHIn/100.0))*100.0);
packet.absHOut=(int)(calculationAbsH((float)(packet.tOut/100.0),(float)(packet.relHOut/100.0))*100.0);
#ifdef DEBUG
Serial.println(F("Average value >>>>>>>>>>"));
Serial.print(F("IN T="));Serial.print(packet.tIn);Serial.print(F(" H=")); Serial.print(packet.relHIn); Serial.print(F(" abs H=")); Serial.println(packet.absHIn);
Serial.print(F("OUT T="));Serial.print(packet.tOut);Serial.print(F(" H=")); Serial.print(packet.relHOut); Serial.print(F(" abs H=")); Serial.println(packet.absHOut);
#endif
#ifdef RADIO // Радио модуль NRF42l
send_packet(); // Послать данные
#endif
CheckON(); // Проверка статуса вентилятора
print_data(); // вывод усредненных значений
print_status(); // панель состояния
// Обновляем максимум и минимум температур в EEPROM
// Cделано не честно - обновляем раз в 30 секунд (NUM_SAMPLES*TIME_SCAN_SENSOR) а пишем раз в час, могут пропасть данные при сбросе
if (settingRAM.tInMax<packet.tIn) settingRAM.tInMax=packet.tIn;
else if (settingRAM.tInMin>packet.tIn) settingRAM.tInMin=packet.tIn;
if (settingRAM.tOutMax<packet.tOut) settingRAM.tOutMax=packet.tOut;
else if (settingRAM.tOutMin>packet.tOut) settingRAM.tOutMin=packet.tOut;
// Пишем в EEPROM не каждый раз!! экономим ресурс записей 100000 мин.
settingRAM.tick_eeprom++;
if (((long)settingRAM.tick_eeprom*NUM_SAMPLES*TIME_SCAN_SENSOR)>=(long)TIME_HOUR) // пора писать eeprom
{ writeEeprom(); settingRAM.tick_eeprom=0; }
// Запись статистики в EEPROM
if (FLAG_MOTOR_CHECK) ChartMotor=true; // Признак того что надо показывать включение мотора на графике
if (FLAG_HEAT_CHECK) ChartHeat=true; // Признак того что надо показывать включение нагревателя на графике
TimeChart++;
if ((long)((long)TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проврека не пора ли выводить график
{ printChart(); TimeChart=0; // Сдвиг графика и вывод новой точки
#ifdef DEBUG
Serial.println(F("Point add chart ++++++++++++++++++++"));
#endif
#ifdef BEEP
// beep(50);
#endif
}
}
#ifdef RADIO // Радио модуль NRF42l
// Часы работают если только был получено время от головного блока (хотя бы один раз) в противном случае они даже не показываются на экране
if (tt>0) tt=tt+TIME_SCAN_SENSOR/1000; // Обновить текущее время - что бы часы шли и без связи с базовым блоком
#endif
myOS.restartTask(measurement); // Пустить задачи
}
// Функция переводит относительную влажность в абсолютную
// 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();
}
// Проверка статуса вытяжки, не пора ли переключится
void CheckON(void)
{
int tmp=0;
// 0. Проверить замораживание подвала НАГРЕВАТЕЛЬ
if (packet.tIn<=TEMP_LOW) { MOTOR_OFF; HEAT_ON; return;} // Контроль от промораживания подвала по идеи здесь надо включать калорифер
if ((FLAG_HEAT_CHECK)&&(packet.tIn>TEMP_LOW+dT_OFF+50)) HEAT_OFF; // Выключить калорифер когда температура поднимится на 0.5 градуса
// 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.tIn>(packet.T_min*10))&&((packet.tIn-packet.tOut)>packet.dH_min)) // dH_min используется не штатно для температуры
{MOTOR_ON; return;} // мотор выключен, температура выше установленной и снаружи температура ниже на 2 градуса то ВКЛЮЧЕНИЕ мотора
if ((FLAG_MOTOR_CHECK)&&(packet.tIn<=packet.tOut))
{MOTOR_OFF; return;} // мотор включен и темература внутри ниже наружней то ВЫКЛЮЧЕННИЕ мотора
return; // изменений нет выходим
}
// 3. В режиме осушения - проверка на достижение минимальной температуры помещения в режиме осушения - СРОЧНО ВЫКЛЮЧИТЬ третий приоритет
if (packet.tIn<=(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.absHIn*(packet.dH_min/10)/100); // Если режимы используют ДЕСЯТЫЕ % от абсолютной разности внутренней температуры
if ((~FLAG_MOTOR_CHECK)&&(packet.tIn>(packet.T_min*10))&&((packet.absHIn-tmp)>packet.absHOut))
{MOTOR_ON; return;} // мотор выключен, темература выше критической, абс влажность с наружи меньше то ВКЛЮЧЕНИЕ мотора
if ((FLAG_MOTOR_CHECK)&&((packet.tIn<=(tmp+dT_OFF))||(packet.absHIn<(packet.absHOut+dH_OFF))))
{MOTOR_OFF; return;} // мотор включен и темература ниже критической или абс влажность внутри ниже то ВЫКЛЮЧЕННИЕ мотора
}
// Вывод информации о настройках и сохрание индекса настроек в eeprom ---------------------------------
void Setting()
{ // Настройка
cli();
ucg.setColor(0, 100, 255);
ucg.setFontMode(UCG_FONT_MODE_SOLID);
ucg.setPrintPos(0,25+18);
switch (settingRAM.mode)
{
case BLOCK_OFF: ucg.print(F("Выключено ")); packet.dH_min=255; packet.T_min=255; FLAG_ABS_H_ON; break;
case HOOD_ON: ucg.print(F("Режим вытяжки ")); packet.dH_min=0; packet.T_min=0; FLAG_ABS_H_ON; break;
case COOLING: ucg.print(F("Охлаждение T>10 dT>2")); packet.dH_min=200; packet.T_min=100; FLAG_ABS_H_ON; break; // dH_min используется не штатно для температуры
case 3: ucg.print(F("Осушение T>+3 dH>0.3 ")); packet.dH_min=30; packet.T_min=30; FLAG_ABS_H_ON; break;
case 4: ucg.print(F("Осушение T>+3 dH>5% ")); packet.dH_min=50; packet.T_min=30; FLAG_ABS_H_OFF; break;
case 5: ucg.print(F("Осушение T>+4 dH>0.6 ")); packet.dH_min=60; packet.T_min=40; FLAG_ABS_H_ON; break;
case 6: ucg.print(F("Осушение T>+4 dH>10% ")); packet.dH_min=100; packet.T_min=40; FLAG_ABS_H_OFF; break;
case 7: ucg.print(F("Осушение T>+5 dH>0.8 ")); packet.dH_min=80; packet.T_min=50; FLAG_ABS_H_ON; break;
}
writeEeprom(); // Запись в EEPROM новых настроек
SET_MODE_BITS; // В пакет для передачи добавили смену режима
MOTOR_OFF; // Поменяли настройки - отключить мотор, пусть заново настройки сработают если потребуется
CheckON(); // Возможно надо включить мотор
print_status(); // Показать панель состояния (смена настроек)
sei();
}
// Вывод float с двумя десятичными знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
ucg.setPrintPos(x,y);
ucg.print(v,2);
ucg.print(F(" ")); // Стереть хвост от предыдущего числа
}
// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
ucg.setPrintPos(x,y);
ucg.print(b);
}
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)));
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)));
print_StrXY(5+10,15+16*7,F("6 Мах температуры in/out"));
ucg.setPrintPos(5+222,15+16*7);
ucg.print((float)settingRAM.tInMax/100,1);
ucg.print(F("/"));
ucg.print((float)settingRAM.tOutMax/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.tInMin/100,1);
ucg.print(F("/"));
ucg.print((float)settingRAM.tOutMin/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();
}
// Чтение свободной памяти --------------------------------------------------------------------
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;
sensors.sum_tOut=0;
sensors.sum_tIn=0;
sensors.sum_relHOut=0;
sensors.sum_relHIn=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
У нас уже был минус на улице. Блок показал свою эффективность. У меня в подавле влажность за неделю упала с 7 грамм на куб до 3.8 грамма на куб. И еще у меня картошка в этом подвале лежит.
Установил второй блок во втором подвале. Откалибровал датчики. Один выбросил - врал по влажности на +15% решил что это не примелемо.
За месяц эксплуатации AM2302, оба датчика начали завышать показания. И что удивительно, тот что был на улице и попадал под дождь завышает на 1,45%, а второй, в комнате- на 2,54%. Это данные по NaCl -75%. А по влажности над поверхностью воды, уличный выдал 97,65%, а комнатный 100,65%. Данные по выдержке более 24 часов.
Меня больше волнует, что будет с датчиками через 5-6 месяцев. Моя система это время работает автономно, т.е. до весны. +- 5% меня не волнует. Главное чтобы не поменялась функция прибора на обратную.. :)
Скорее всего все будет нормально. Дрейф у вас даже за заявленную погрешность не вышел. Наблюдается он у любых датчиков, а наиболее сильно в первые часы работы. Поверку приборов не зря придумали)))).
Мой АМ2301 если и уполз, то на единицы %, поэтому по серьезному не проверял, иногда выносной модуль OREGON к нему заношу на сутки.
Ну а оставлять бесконтрольную систему на полгода, даже для отработанных решений не рекомендуется. Светик мигающий наружу выведите и соседа за вкусный напиток посматривать попросите, либо что то дублирующее (типа нагревателей с термостатом.) ИМХО.
Отличная работа miaua! Получилось железо отлично. Теперь откорпусить и в бой.
По датчикам (по моим наблюдениям).
Дейстиветльно используемые со временем "уходят". По этому я второе устройство калибровал после недели работы. Заоодно проверил и первое - показания не ушли. У меня два устройства, и я могу сравниватьих показания. т.к. датчики установлены в разных местах на улице, то показания разные. У меня разница по температуре бывает до 2 градусов. Я проверял термометром и он показал правильность показаний обоих датчиков. Действительно в разных местах разная температура (у меня один датчик не очень правильно весит). Также разнятся и влажности до 3-4 %. Самое интерсное что абсолютные влажности отличаются на сотые грамма.
Сейчас едут ко мне из китая еще два типа датчиков влажности буду их щупать. В первую очередь их буду ставить в автономный (питание от батарейки) блок контроля температуры и влажности. Понравится поставлю в этот блок.
Как я понял измерение влажности проблемная вещь везде. у датчика есть открытая чуствительная поверхность на которую осаждается из воздуха разные вещества (и датчик уходит). По этому я теперь все датчики монтирую сеткой вниз и в банках (дно вверху, банка перевернута и в боковых стенках есть небольшие отверстия) Попадание воды в датчик приводит к фатальным последсвиям (у меня он стал завышать показания на 15%).
Понаблюдав за показаниями (дневной разброс авболютной влажности достигает 3-4 грамма на куб на улице) я пришел к выводу что выставленные границы для включения и выключения у меня малы. Я их увеличил в последней выложенной версии кода. Возможно их надо делать еще больше.
Все выше сказанное позволяет надеятся что показания датчиков будут достоверны и стабильны во времени.
Но проверить (у меня хотя бы сравнивать ) раз в год надо.
Если есть сосед ему можно вручить дистанционный пульт пусть использует как метеостанцию - смотит влажность и температура улицы. Заодно и за вашим устройством приглядит.
/*
time.h - low level time and date functions
*/
/*
July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this)
- fixed daysToTime_t macro (thanks maniacbug)
*/
#ifndef _Time_h
#ifdef __cplusplus
#define _Time_h
#include <inttypes.h>
#ifndef __AVR__
#include <sys/types.h> // for __time_t_defined, but avr libc lacks sys/types.h
#endif
#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc
typedef unsigned long time_t;
#endif
// This ugly hack allows us to define C++ overloaded functions, when included
// from within an extern "C", as newlib's sys/stat.h does. Actually it is
// intended to include "time.h" from the C library (on ARM, but AVR does not
// have that file at all). On Mac and Windows, the compiler will find this
// "Time.h" instead of the C library "time.h", so we may cause other weird
// and unpredictable effects by conflicting with the C library header "time.h",
// but at least this hack lets us define C++ functions as intended. Hopefully
// nothing too terrible will result from overriding the C library header?!
extern "C++" {
typedef enum {timeNotSet, timeNeedsSync, timeSet
} timeStatus_t ;
typedef enum {
dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday
} timeDayOfWeek_t;
typedef enum {
tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields
} tmByteFields;
typedef struct {
uint8_t Second;
uint8_t Minute;
uint8_t Hour;
uint8_t Wday; // day of week, sunday is day 1
uint8_t Day;
uint8_t Month;
uint8_t Year; // offset from 1970;
} tmElements_t, TimeElements, *tmElementsPtr_t;
//convenience macros to convert to and from tm years
#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year
#define CalendarYrToTm(Y) ((Y) - 1970)
#define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000
#define y2kYearToTm(Y) ((Y) + 30)
typedef time_t(*getExternalTime)();
//typedef void (*setExternalTime)(const time_t); // not used in this version
/*==============================================================================*/
/* Useful Constants */
#define SECS_PER_MIN (60UL)
#define SECS_PER_HOUR (3600UL)
#define SECS_PER_DAY (SECS_PER_HOUR * 24UL)
#define DAYS_PER_WEEK (7UL)
#define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK)
#define SECS_PER_YEAR (SECS_PER_WEEK * 52UL)
#define SECS_YR_2000 (946684800UL) // the time at the start of y2k
/* Useful Macros for getting elapsed time */
#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN)
#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN)
#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR)
#define dayOfWeek(_time_) ((( _time_ / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday
#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) // this is number of days since Jan 1 1970
#define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight
// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971
// Always set the correct time before settting alarms
#define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day
#define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day
#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that week starts on day 1
#define previousSunday(_time_) (_time_ - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time
#define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time
/* Useful Macros for converting elapsed time to a time_t */
#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN)
#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR)
#define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011
#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK)
/*============================================================================*/
/* time and date functions */
int hour(); // the hour now
int hour(time_t t); // the hour for the given time
int hourFormat12(); // the hour now in 12 hour format
int hourFormat12(time_t t); // the hour for the given time in 12 hour format
uint8_t isAM(); // returns true if time now is AM
uint8_t isAM(time_t t); // returns true the given time is AM
uint8_t isPM(); // returns true if time now is PM
uint8_t isPM(time_t t); // returns true the given time is PM
int minute(); // the minute now
int minute(time_t t); // the minute for the given time
int second(); // the second now
int second(time_t t); // the second for the given time
int day(); // the day now
int day(time_t t); // the day for the given time
int weekday(); // the weekday now (Sunday is day 1)
int weekday(time_t t); // the weekday for the given time
int month(); // the month now (Jan is month 1)
int month(time_t t); // the month for the given time
int year(); // the full four digit year: (2009, 2010 etc)
int year(time_t t); // the year for the given time
time_t now(); // return the current time as seconds since Jan 1 1970
void setTime(time_t t);
void setTime(int hr,int min,int sec,int day, int month, int yr);
void adjustTime(long adjustment);
/* date strings */
#define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null)
char* monthStr(uint8_t month);
char* dayStr(uint8_t day);
char* monthShortStr(uint8_t month);
char* dayShortStr(uint8_t day);
/* time sync functions */
timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized
void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider
void setSyncInterval(time_t interval); // set the number of seconds between re-sync
/* low level functions to convert to and from system time */
void breakTime(time_t time, tmElements_t &tm); // break time_t into elements
time_t makeTime(tmElements_t &tm); // convert time elements into time_t
} // extern "C++"
#endif // __cplusplus
#endif /* _Time_h */
stmTime.cpp
/*
time.c - low level time and date functions
Copyright (c) Michael Margolis 2009-2014
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1.0 6 Jan 2010 - initial release
1.1 12 Feb 2010 - fixed leap year calculation error
1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this)
1.3 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update
status, updated examples for Arduino 1.0, fixed ARM
compatibility issues, added TimeArduinoDue and TimeTeensy3
examples, add error checking and messages to RTC examples,
add examples to DS1307RTC library.
1.4 5 Sep 2014 - compatibility with Arduino 1.5.7
*/
#if ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#include "stmTime.h"
static tmElements_t tm; // a cache of time elements
static time_t cacheTime; // the time the cache was updated
static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds
void refreshCache(time_t t) {
if (t != cacheTime) {
breakTime(t, tm);
cacheTime = t;
}
}
int hour() { // the hour now
return hour(now());
}
int hour(time_t t) { // the hour for the given time
refreshCache(t);
return tm.Hour;
}
int hourFormat12() { // the hour now in 12 hour format
return hourFormat12(now());
}
int hourFormat12(time_t t) { // the hour for the given time in 12 hour format
refreshCache(t);
if( tm.Hour == 0 )
return 12; // 12 midnight
else if( tm.Hour > 12)
return tm.Hour - 12 ;
else
return tm.Hour ;
}
uint8_t isAM() { // returns true if time now is AM
return !isPM(now());
}
uint8_t isAM(time_t t) { // returns true if given time is AM
return !isPM(t);
}
uint8_t isPM() { // returns true if PM
return isPM(now());
}
uint8_t isPM(time_t t) { // returns true if PM
return (hour(t) >= 12);
}
int minute() {
return minute(now());
}
int minute(time_t t) { // the minute for the given time
refreshCache(t);
return tm.Minute;
}
int second() {
return second(now());
}
int second(time_t t) { // the second for the given time
refreshCache(t);
return tm.Second;
}
int day(){
return(day(now()));
}
int day(time_t t) { // the day for the given time (0-6)
refreshCache(t);
return tm.Day;
}
int weekday() { // Sunday is day 1
return weekday(now());
}
int weekday(time_t t) {
refreshCache(t);
return tm.Wday;
}
int month(){
return month(now());
}
int month(time_t t) { // the month for the given time
refreshCache(t);
return tm.Month;
}
int year() { // as in Processing, the full four digit year: (2009, 2010 etc)
return year(now());
}
int year(time_t t) { // the year for the given time
refreshCache(t);
return tmYearToCalendar(tm.Year);
}
/*============================================================================*/
/* functions to convert to and from system time */
/* These are for interfacing with time serivces and are not normally needed in a sketch */
// leap year calulator expects year argument as years offset from 1970
#define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )
static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0
void breakTime(time_t timeInput, tmElements_t &tm){
// break the given time_t into time components
// this is a more compact version of the C library localtime function
// note that year is offset from 1970 !!!
uint8_t year;
uint8_t month, monthLength;
uint32_t time;
unsigned long days;
time = (uint32_t)timeInput;
tm.Second = time % 60;
time /= 60; // now it is minutes
tm.Minute = time % 60;
time /= 60; // now it is hours
tm.Hour = time % 24;
time /= 24; // now it is days
tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1
year = 0;
days = 0;
while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
year++;
}
tm.Year = year; // year is offset from 1970
days -= LEAP_YEAR(year) ? 366 : 365;
time -= days; // now it is days in this year, starting at 0
days=0;
month=0;
monthLength=0;
for (month=0; month<12; month++) {
if (month==1) { // february
if (LEAP_YEAR(year)) {
monthLength=29;
} else {
monthLength=28;
}
} else {
monthLength = monthDays[month];
}
if (time >= monthLength) {
time -= monthLength;
} else {
break;
}
}
tm.Month = month + 1; // jan is month 1
tm.Day = time + 1; // day of month
}
time_t makeTime(tmElements_t &tm){
// assemble time elements into time_t
// note year argument is offset from 1970 (see macros in time.h to convert to other formats)
// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9
int i;
uint32_t seconds;
// seconds from 1970 till 1 jan 00:00:00 of the given year
seconds= tm.Year*(SECS_PER_DAY * 365);
for (i = 0; i < tm.Year; i++) {
if (LEAP_YEAR(i)) {
seconds += SECS_PER_DAY; // add extra days for leap years
}
}
// add days for this year, months start from 1
for (i = 1; i < tm.Month; i++) {
if ( (i == 2) && LEAP_YEAR(tm.Year)) {
seconds += SECS_PER_DAY * 29;
} else {
seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0
}
}
seconds+= (tm.Day-1) * SECS_PER_DAY;
seconds+= tm.Hour * SECS_PER_HOUR;
seconds+= tm.Minute * SECS_PER_MIN;
seconds+= tm.Second;
return (time_t)seconds;
}
/*=====================================================*/
/* Low level system time functions */
static uint32_t sysTime = 0;
static uint32_t prevMillis = 0;
static uint32_t nextSyncTime = 0;
static timeStatus_t Status = timeNotSet;
getExternalTime getTimePtr; // pointer to external sync function
//setExternalTime setTimePtr; // not used in this version
#ifdef TIME_DRIFT_INFO // define this to get drift data
time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync
#endif
time_t now() {
// calculate number of seconds passed since last call to now()
while (millis() - prevMillis >= 1000) {
// millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference
sysTime++;
prevMillis += 1000;
#ifdef TIME_DRIFT_INFO
sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift
#endif
}
if (nextSyncTime <= sysTime) {
if (getTimePtr != 0) {
time_t t = getTimePtr();
if (t != 0) {
setTime(t);
} else {
nextSyncTime = sysTime + syncInterval;
Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync;
}
}
}
return (time_t)sysTime;
}
void setTime(time_t t) {
#ifdef TIME_DRIFT_INFO
if(sysUnsyncedTime == 0)
sysUnsyncedTime = t; // store the time of the first call to set a valid Time
#endif
sysTime = (uint32_t)t;
nextSyncTime = (uint32_t)t + syncInterval;
Status = timeSet;
prevMillis = millis(); // restart counting from now (thanks to Korman for this fix)
}
void setTime(int hr,int min,int sec,int dy, int mnth, int yr){
// year can be given as full four digit year or two digts (2010 or 10 for 2010);
//it is converted to years since 1970
if( yr > 99)
yr = yr - 1970;
else
yr += 30;
tm.Year = yr;
tm.Month = mnth;
tm.Day = dy;
tm.Hour = hr;
tm.Minute = min;
tm.Second = sec;
setTime(makeTime(tm));
}
void adjustTime(long adjustment) {
sysTime += adjustment;
}
// indicates if time has been set and recently synchronized
timeStatus_t timeStatus() {
now(); // required to actually update the status
return Status;
}
void setSyncProvider( getExternalTime getTimeFunction){
getTimePtr = getTimeFunction;
nextSyncTime = sysTime;
now(); // this will sync the clock
}
void setSyncInterval(time_t interval){ // set the number of seconds between re-sync
syncInterval = (uint32_t)interval;
nextSyncTime = sysTime + syncInterval;
}
DateStrings.cpp
/*
time.c - low level time and date functions
Copyright (c) Michael Margolis 2009-2014
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1.0 6 Jan 2010 - initial release
1.1 12 Feb 2010 - fixed leap year calculation error
1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this)
1.3 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update
status, updated examples for Arduino 1.0, fixed ARM
compatibility issues, added TimeArduinoDue and TimeTeensy3
examples, add error checking and messages to RTC examples,
add examples to DS1307RTC library.
1.4 5 Sep 2014 - compatibility with Arduino 1.5.7
*/
#if ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#include "stmTime.h"
static tmElements_t tm; // a cache of time elements
static time_t cacheTime; // the time the cache was updated
static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds
void refreshCache(time_t t) {
if (t != cacheTime) {
breakTime(t, tm);
cacheTime = t;
}
}
int hour() { // the hour now
return hour(now());
}
int hour(time_t t) { // the hour for the given time
refreshCache(t);
return tm.Hour;
}
int hourFormat12() { // the hour now in 12 hour format
return hourFormat12(now());
}
int hourFormat12(time_t t) { // the hour for the given time in 12 hour format
refreshCache(t);
if( tm.Hour == 0 )
return 12; // 12 midnight
else if( tm.Hour > 12)
return tm.Hour - 12 ;
else
return tm.Hour ;
}
uint8_t isAM() { // returns true if time now is AM
return !isPM(now());
}
uint8_t isAM(time_t t) { // returns true if given time is AM
return !isPM(t);
}
uint8_t isPM() { // returns true if PM
return isPM(now());
}
uint8_t isPM(time_t t) { // returns true if PM
return (hour(t) >= 12);
}
int minute() {
return minute(now());
}
int minute(time_t t) { // the minute for the given time
refreshCache(t);
return tm.Minute;
}
int second() {
return second(now());
}
int second(time_t t) { // the second for the given time
refreshCache(t);
return tm.Second;
}
int day(){
return(day(now()));
}
int day(time_t t) { // the day for the given time (0-6)
refreshCache(t);
return tm.Day;
}
int weekday() { // Sunday is day 1
return weekday(now());
}
int weekday(time_t t) {
refreshCache(t);
return tm.Wday;
}
int month(){
return month(now());
}
int month(time_t t) { // the month for the given time
refreshCache(t);
return tm.Month;
}
int year() { // as in Processing, the full four digit year: (2009, 2010 etc)
return year(now());
}
int year(time_t t) { // the year for the given time
refreshCache(t);
return tmYearToCalendar(tm.Year);
}
/*============================================================================*/
/* functions to convert to and from system time */
/* These are for interfacing with time serivces and are not normally needed in a sketch */
// leap year calulator expects year argument as years offset from 1970
#define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )
static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0
void breakTime(time_t timeInput, tmElements_t &tm){
// break the given time_t into time components
// this is a more compact version of the C library localtime function
// note that year is offset from 1970 !!!
uint8_t year;
uint8_t month, monthLength;
uint32_t time;
unsigned long days;
time = (uint32_t)timeInput;
tm.Second = time % 60;
time /= 60; // now it is minutes
tm.Minute = time % 60;
time /= 60; // now it is hours
tm.Hour = time % 24;
time /= 24; // now it is days
tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1
year = 0;
days = 0;
while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
year++;
}
tm.Year = year; // year is offset from 1970
days -= LEAP_YEAR(year) ? 366 : 365;
time -= days; // now it is days in this year, starting at 0
days=0;
month=0;
monthLength=0;
for (month=0; month<12; month++) {
if (month==1) { // february
if (LEAP_YEAR(year)) {
monthLength=29;
} else {
monthLength=28;
}
} else {
monthLength = monthDays[month];
}
if (time >= monthLength) {
time -= monthLength;
} else {
break;
}
}
tm.Month = month + 1; // jan is month 1
tm.Day = time + 1; // day of month
}
time_t makeTime(tmElements_t &tm){
// assemble time elements into time_t
// note year argument is offset from 1970 (see macros in time.h to convert to other formats)
// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9
int i;
uint32_t seconds;
// seconds from 1970 till 1 jan 00:00:00 of the given year
seconds= tm.Year*(SECS_PER_DAY * 365);
for (i = 0; i < tm.Year; i++) {
if (LEAP_YEAR(i)) {
seconds += SECS_PER_DAY; // add extra days for leap years
}
}
// add days for this year, months start from 1
for (i = 1; i < tm.Month; i++) {
if ( (i == 2) && LEAP_YEAR(tm.Year)) {
seconds += SECS_PER_DAY * 29;
} else {
seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0
}
}
seconds+= (tm.Day-1) * SECS_PER_DAY;
seconds+= tm.Hour * SECS_PER_HOUR;
seconds+= tm.Minute * SECS_PER_MIN;
seconds+= tm.Second;
return (time_t)seconds;
}
/*=====================================================*/
/* Low level system time functions */
static uint32_t sysTime = 0;
static uint32_t prevMillis = 0;
static uint32_t nextSyncTime = 0;
static timeStatus_t Status = timeNotSet;
getExternalTime getTimePtr; // pointer to external sync function
//setExternalTime setTimePtr; // not used in this version
#ifdef TIME_DRIFT_INFO // define this to get drift data
time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync
#endif
time_t now() {
// calculate number of seconds passed since last call to now()
while (millis() - prevMillis >= 1000) {
// millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference
sysTime++;
prevMillis += 1000;
#ifdef TIME_DRIFT_INFO
sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift
#endif
}
if (nextSyncTime <= sysTime) {
if (getTimePtr != 0) {
time_t t = getTimePtr();
if (t != 0) {
setTime(t);
} else {
nextSyncTime = sysTime + syncInterval;
Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync;
}
}
}
return (time_t)sysTime;
}
void setTime(time_t t) {
#ifdef TIME_DRIFT_INFO
if(sysUnsyncedTime == 0)
sysUnsyncedTime = t; // store the time of the first call to set a valid Time
#endif
sysTime = (uint32_t)t;
nextSyncTime = (uint32_t)t + syncInterval;
Status = timeSet;
prevMillis = millis(); // restart counting from now (thanks to Korman for this fix)
}
void setTime(int hr,int min,int sec,int dy, int mnth, int yr){
// year can be given as full four digit year or two digts (2010 or 10 for 2010);
//it is converted to years since 1970
if( yr > 99)
yr = yr - 1970;
else
yr += 30;
tm.Year = yr;
tm.Month = mnth;
tm.Day = dy;
tm.Hour = hr;
tm.Minute = min;
tm.Second = sec;
setTime(makeTime(tm));
}
void adjustTime(long adjustment) {
sysTime += adjustment;
}
// indicates if time has been set and recently synchronized
timeStatus_t timeStatus() {
now(); // required to actually update the status
return Status;
}
void setSyncProvider( getExternalTime getTimeFunction){
getTimePtr = getTimeFunction;
nextSyncTime = sysTime;
now(); // this will sync the clock
}
void setSyncInterval(time_t interval){ // set the number of seconds between re-sync
syncInterval = (uint32_t)interval;
nextSyncTime = sysTime + syncInterval;
}
По поводу 3.3 вольта.
А смысл переводить на 3.3 ? от батареек все равно не реализуешь, и реле (если механическое) на 3.3 работать плохо будет.
16 мгц на 3.3 работает (но не документировано). Правда дисплей можно без резисторов (делители) использовать.
У меня головной блок от 3.3 вольта питается. Особой разницы пока не почуствовал, есть заморочки с питанием.
stmTime\stmTime.cpp.o: In function `breakTime(unsigned long, tmElements_t&)': C:\Program Files\Arduino\libraries\stmTime/stmTime.cpp:151: multiple definition of `breakTime(unsigned long, tmElements_t&)' stmTime\DateStrings.cpp.o:C:\Program Files\Arduino\libraries\stmTime/DateStrings.cpp:151: first defined here stmTime\stmTime.cpp.o: In function `breakTime(unsigned long, tmElements_t&)': C:\Program Files\Arduino\libraries\stmTime/stmTime.cpp:151: multiple definition of `refreshCache(unsigned long)' stmTime\DateStrings.cpp.o:C:\Program Files\Arduino\libraries\stmTime/DateStrings.cpp:151: first defined here stmTime\stmTime.cpp.o: In function `breakTime(unsigned long, tmElements_t&)': C:\Program Files\Arduino\libraries\stmTime/stmTime.cpp:151: multiple definition of `hour(unsigned long)' stmTime\DateStrings.cpp.o:C:\Program Files\Arduino\libraries\stmTime/DateStrings.cpp:151: first defined here
Есть в загородном доме два подвала которые гидроизолированы от земли (нет поступления воды из грунта). Зимой на стенах появляется конденсат, предметы покрываются влагой. Температура в подвалах не опускается ниже 0...+2 градусов, т.е. вода не замерзает. Это достигнуто утеплением стен подвала снаружи ЭППС и нагревом пола подвала (он тепло не изолирован) землей.
Задача убрать выпадение конденсата в подвалах.
.....
можно добавить на вытяжную трубу подвала инжектор (конус с флюгером) и заслонку на сервоприводе, и скорость ветра замерять. И вообще посади туда грибы...
Сейчас допиливаю код головного блока будет web сервер (по витой паре). Функционал будет аналогичный, т.е. показ таблицы данных и графиков изменения за последние 10 часов, На одной странице информация об всех удаленных блоках. Пока есть верстия на TFT экране (опубликована на этом форуме) и есть версия чисто web сервера (пока ничего не выкладывал). Возможно получится объеденить в одном коде и экран и сервер. Реализация stm32, для сети используется плата w5100 (можно w5500).
Нашел способ прикрутит POE (пассивный) к плате w5100, думаю об актуальности.
Thunderix пишет:
Перешил загрузчик на прохе, пробовал на 3.3 вольта, работает :)
Ваяю платку.
Тоже отличная плата и компактная. А что Вы используете для коммутации нагрузки?
А я все ленюсь делать платы под каждый проект (все силы уходят в разработку кода -))). Сейчас приглядываюсь к орлу, может его буду использовать для схем и разводки.
Жалко что на форуме нельзя выкладывать архивы, а то разводку (уже две версии есть) надо было бы выложить для "потомков" -))
Aleksahek пишет:
можно добавить на вытяжную трубу подвала инжектор (конус с флюгером) и заслонку на сервоприводе, и скорость ветра замерять. И вообще посади туда грибы...
А смысл измерения скороси ветра в трубе ???
Посадка грибов для мнея не актуальна -))
1. Грибы это влажность близкая к 100% а у меня деревянные перекрытия.
2. Выращивать на "продажу" - это не мой бизнес и объем мал. А выращивать "для себя" не нужно - у меня лес в 100 метрах и в грибное время мы набираем грибов сколько захотим. Объем заготовк ограничивается фразой - "мы столько не съедим и не раздарим".
Немного не правильно. Ножка контроллера присоединяется к точке соединения резистора 220 ом и конденсатора 0.1 Мкф.
В Вашем случае цепочка не работает.
pav2000 можно как-то с вами связаться? Хотел бы проконсультироваться по вопросу устройства подполья в вашем случае и системы вентиляции для него. Извините что не в тему.
Хотел бы проконсультироваться по вопросу устройства подполья.
А вот это уже, экстремисткая, наказуемая статья))))
pav2000 можно как-то с вами связаться? Хотел бы проконсультироваться по вопросу устройства подполья в вашем случае и системы вентиляции для него. Извините что не в тему.
Почта pav2000 собака nm точка ру
Подробное описание подвала здесь - https://www.forumhouse.ru/threads/151893/page-19#post-9928669
Приехали датчики.. Естественно нужна калибровка.. Появиля вопрос :- как изменить графики(масштаб), что бы увидеть реальную температуру? Сейчас на улице 27,3*С внутри 21*С. Соответственно отн. вл. 58,6% и 50,77% (13,93г/м3 и 13,4г/м3) ?
2 Для чего нужна калибровка АЦП ?
Графики тут не причем, они только показывают что намерили датчики. В программе реализована калибровка датчиков по одной точке. Если потребуется можно реализовать по двум точкам.
Калибровочные константы задаются кодом
Нужны образцовые гигрометр и термометр. Алгоритм следующий
1. Все калибровочные константы в 0 компилируем и загружаем скейч в плату.
2. Запихиваем образцовый прибор и оба датчика в банку, сверху прекрываем. Что бы небыло конвекции воздуха.
3. Берем листочек бумаги и считываем три значения в течении 2- 3 часов раз 10-20.
4. Обрабатываем полученные результаты в меру своих математических способностей. Простейший случай усредняем по 10-20 значениям. В результате получается три значения. Эталон и два датчика.
5. Вычисляем ошибки. Если образцовый больше то ошибка положительная если меньше отрицательная.
6. Переводим в машинные еденицы типа сотых градуса х100. И заносим эти константы в программу.
7. Компилим и загружаем. Наблюдаем уменьшение ошибки.
Все, профит =)
Смотрим на других значениях диапазона. Если ошибка не устраивает, то надо делать калибровку хотя бы по двум точкам. Это значит наклон графиков передаточной функции датчика и эталона сильно разный.
Датчики цифровые. АЦП для их работы не используется. Я использую ацп для измерения питания коонтроллера. По хорошому его надо откалибровать (точнее ион). На показания датчиков ацп не влияет
Спасибо. С этим все ясно..Инвертировал (попытался) работу реле вентилятора и нагревателя. С логикой вентилятора все ок, а нагреватель постоянно включен. Даже в режиме "выключено". Я конечно могу добавить пару инверторов для работы реле, но мне кажется это был бы не правильный подход :)
Увы мои скудные познания в СИ не дают мне (пока) разобраться в работе программы...
Менял только эти две группы:
У меня нагреватель пока не подключен. Все твердотельное реле не куплю.
По этому программировал без отладки. Если инверсная логика то в сетапе надо поставить heat_off. Для начала. И смотреть. Просто я в начале программирую ноги и эта нога устанавливается 0.
Heat_off ставить ниже этих строк или вместо low high поставить в нижнюю строчку.
Калорифер выключится. При включенных датчиках он включится ТОЛЬКО при температуре 1.5 градуса в подвале. То есть сейчас он не будет включатся. Нет условий.
Только сейчас подумал. В режиме выключено, выключение не распространяется на нагреватель. Гарантированная защита от замораживания подвала. Так сейчас сделано. Можно изменить на отключение. Но ихмо сейчас надежнее.
Ps
Вчера дрюкался с блоком, испытывал новые изменения и дополнения. Кнопка с rc цепочкой отлично работает, ни одного ложного срабатывания.
Разобрался с реле. Теперь все как надо работает. Спасибо...
Теперь надо радио подключать.... Как оно у Вас подключено? через отдельные делители или параллельно дисплею? У меня преобразователи уровня собраны на 4х транзисторах. И конечно вопрос:- " что с ним (радио) делать дальше?". :)
По поводу калибровки датчиков... А зачем нам приведение показаний к образцовому прибору? В этой схеме вполне достаточно одинаковых показаний обоих датчиков (в попугаях) для принятия решения. Более менее точный нужен датчик температуры, для управления нагревом и охлаждением..
Еще один вопрос: - Нужно ли опускать влажность ниже... скажем 50-55% ? Если на улице влажность 35%, то нужно ли опускать в подвале до такой влажности? Я думаю, это напрасный расход энергии (вентилятор ). Не претендую на истину. :)
Nrf24l01 можно цеплять к 5 вольтам без делителей. Толеранты к 5 вольтам из даташита.
Т.е. цепляются к ногам ардуино на прямую.
"Делать дальше" постараюсь выложить пост по приемнику, сейчас допиливаю код. Есть проблемы со множеством передатчиков. Думаю над идеологией. Напечатал корпус, приделал энкодер. Сейчас допиливаю интерфейс. Приемник будет только stm32 онли. Три прерывания использую. Есть рроблема ио spi шиной дисплея и nrf24l01. Пока это не решил. В очередной раз опитимизирую код по радиоканалу.
Мне кажется что калибровка датчиков по обрацовому нужна, мы вычисляем абсолютную влажность, а она зависит от температуры и влажности. Желательно определить тх.
Отлично! У меня уже есть в наличии stm32 и пара енкодеров. Но STM32 для меня нечто новое :) . И нужно устанавливать какое то ИДЕ для него. Если у Вас есть немного времени, посоветуйте как организовать среду...
Для STM существует куча всяких ИДЕ...В том числе и ардуино... Но где брать библиотеки? Тем более библиотеки которые Вы сами портировали под этот проект..
По stm32 изложу в отдельной ветке про либы и иде в среде ардуино. Сейчас в деревне между рюмками пишу посты. У нас теплый вечер. +20. Отмечаем мой отпуск -))
Приятного Вечера! И вдохновения!
Проект близится к завершению. Исправлений все меньше и меньше. В новой версии :
1. В очередной раз оптимизирован код радиоканала.
2. В процессе работы забыл записывать заначения моточасов в eeprom. Сейчас это поправлено запись производится раз в час.
3. Добавил сохранение максимальных и минимальных температур подвала и улицы, с их выводом на информационный экран. Сброс проводится одновременно с моточасами. (наверное это не верно, надо отдельной процедурой)
4. Мелкие исправления и оптимизация кода.
Проверил на уно. Вроде бы все хорошо...
Вопрос: проводились ли тесты системы при отрицательных температурах?
Думаю мой холодильник, удобный стенд. Морозилка от -32 до -16*С, основная камера от+2 до +8*С .Как раз порог +4* можно проверить.
Есть смысл заняться проверкой?
Вопрос: проводились ли тесты системы при отрицательных температурах?
Думаю мой холодильник, удобный стенд. Морозилка от -32 до -16*С, основная камера от+2 до +8*С .Как раз порог +4* можно проверить.
Есть смысл заняться проверкой?
Отрицательных температур быть не должно. Отрицательные температуры отрицательно влияют на жки и конденсаторы (импульсный источник может не завестись) и положительно на полупроводниковые элементы (обратные токи и токи утечки меньше). Смысла тестирования не вижу. Первым откажит индикатор в районе -5÷-10 градусов.
Добавил тему про приемник на stm32 http://arduino.ru/forum/proekty/udalennyi-monitoring-za-datchikami-na-maple-mini
Я имел ввиду расположение датчиков в холодильнике..Насчет климатических условий для различных компонентов я в курсе уже лет 35...
Сейчас занимаюсь калибровкой датчиков....Нашлось штук 5 разных термометров и 3 цифровых с измерением влажности + лабораторный психрометр (2 градусника-сухой и мокрый) -этот можно взять за эталон, но в банку его засунуть не получится...
2Начинаю изучать ветку по приемнику STM.
Спасибо.
Сейчас занимаюсь калибровкой датчиков....Нашлось штук 5 разных термометров и 3 цифровых с измерением влажности + лабораторный психрометр (2 градусника-сухой и мокрый) -этот можно взять за эталон, но в банку его засунуть не получится...
Так по влажности, пользуйтесь солями. Для дома самое простое NaCl. Во всем положительном диапазоне температур от 74 до 76%.
Так по влажности, пользуйтесь солями. Для дома самое простое NaCl. Во всем положительном диапазоне температур от 74 до 76%.
По этой методике ?
Именно так, правда не морочился с температурой, для NaCl сами видите она не актуальна (других солей под рукой не было). Соль правда брал от водоочистки (там таблетки), и поближе к х.ч. чем из магазина. Майонезная банка, таблетка, на нее 2-4 капель воды из пипетки (должна быть влажная, но без лужи внизу). Датчики примерно в середину банки, сверху пленкой-стрейчем заклеил.
Режим в банке устанавливается достаточно долго (часы), далее держал сутки, посматривал, на am2301 колебался примерно 1%, посчитал для целей подвала, более чем достаточно. Сейчас висит он и неподалеку выносной модуль от погодной станции, разница около 2%, но постоянная.
Откалибровал датчики AM2302 по соли - врали почти на 5 % повлажности. А по температуре на 5соток... Более чем достаточно!
Не знаю в какой теме писать?
Пробовал компилировать скечи для STM - ошибки:
Калибровка это хорошо. Неплохо поверить в нескольких точках.
По stm32 предлагаю писать в соответвующей ветке.
Плата выбрана правильно.
По ошибкам комилятор не находит функций. В проекте (в директории) должно лежать три файла которые я выкладывал.
Новая версия прошивки. Добавлен новый функционал:
1. В информационном экране длительное нажатие, без отпускания (5 сек.) кнопки сбрасывает минимальные и максимальные сохраненные температуры.
2. В основном экране длительное нажатие без отпускания кнопки (10 сек.) приводит к сбросу контроллера дисплея. Сделано для того что бы сбрасывать дисплей ili9341 если он зависнет. У меня этого нет, но пусть будет. Изображение и графики перечерчиваются заново.
Отлично. Проверим... У меня ниразу не зависал дисплей...
Потратил немного времени, попытался изобразить мамку, покритикуйте...
Размер 83х60
Отличный проект, собираю подобное утроство, почерпнул много нового.
Спасибо!
Потратил немного времени, попытался изобразить мамку, покритикуйте...
Размер 83х60
Критика -))
1. Что то я стабилизатора на 3.3 вольта не вижу для nrf24, Встроенный на про мини не потянет.
2. nrf24 ногу прерывания лучше завести на 2 или 3 ногу про мини (котрая свободна от кнопки), Плата получится более универсальной.
3. Конденсаторы керамические пару тройку по питанию раскидать.
4. Честно говоря если заморачиваться платой то наверное имеет смысл на нее вкрячить твердртельные реле и отказаться от реле шилда ввобще.
По п.4, тогда уж не морочиться твердотелкой, а просто развести моську с симистором (дешевле раз в пять).
Спасибо за подсказки:
1. Действительно пропустил 3,3В -исправил, поставил LD1117s3.3
2.Было предусмотрено кидать проводок на D2, между переходнымим отверстиями. сейчас кинул перемычку.
3.Керамика разбрасывается прямо на шины питания, потому я их так близко развел.
4. Дело в том, что у меня уже все установлено в подвале гаража.. Шилд на 4 реле с опторазвязкой в гермобоксе и подведено силовое питание 220В. Тепловентилятор и вентилятор приточной трубы установлены штатно уже 5 лет. Модуль управления будет находиться на верху в гараже, дистанция около 6м...Для соединения с датчиками и реле будет использован экранированный кабель 8х0,22. Радио канал в гараже использоваться не будет.
А второй комплект для дачи с подвалом, будет делаться в полном объеме в т.ч. и радио.
П.С.
Что-то не смог найти tft2.8" или 3,2" с SPI Ili 9341 без тачьпада по нормальной цене. 2,4 полно..
Ethernet бы еще прикрутить
Ethernet бы еще прикрутить
Места не хватит, если только вместо nrf24 но все равно места мало.
На короткие расстояния (до 50 метров ) nrf24 с усилком и внешенй антенной работает достаточно стабильно. Я сеть планирую прикручивать к приемнику (который на stm32). Он будет собирать со всех устройств данные и потом можно будет смотреть через инет их. Лог на карту или на USB флеш. Но это пока планы.
4. Дело в том, что у меня уже все установлено в подвале гаража.. Шилд на 4 реле с опторазвязкой в гермобоксе и подведено силовое питание 220В. Тепловентилятор и вентилятор приточной трубы установлены штатно уже 5 лет. Модуль управления будет находиться на верху в гараже, дистанция около 6м...Для соединения с датчиками и реле будет использован экранированный кабель 8х0,22. Радио канал в гараже использоваться не будет.
А второй комплект для дачи с подвалом, будет делаться в полном объеме в т.ч. и радио.
П.С.
Что-то не смог найти tft2.8" или 3,2" с SPI Ili 9341 без тачьпада по нормальной цене. 2,4 полно..
Там можно развести сразу оба варианта на одной плате (с выводами на реле и с силовой частью ) . И первый экземпляр распаять частично, а второй полностью. ИХМО трудозатрат будет меньше. И сделать сразу две три одинаковые платы.
Дисплей 2.8 брал здесь http://www.ebay.com/itm/251885164786?_trksid=p2057872.m2749.l2649&ssPageName=STRK%3AMEBIDX%3AIT
Убрать nrf24 и экранчик впринципе можно тоже, тогда место будет. А смотреть удаленно. утпшку раскидать проще и дешевле.
Я почитал даташит по "Твердотельные реле серий SSR и TSR"... А там такое написано:- "применяется только с резистивной, емкостной нагрузкой и нельзя с высокоиндуктивной."
А какого типа Вы хотите применить реле?
Любое твердотельное реле не любит индуктивную нагрузку. Самоиндукция может пробить его. Но вентилятор 30-50 ватт наверно является не сильно индуктивной нагрузкой.
Я использую шарп s2002s02. И паралельно реле установлен конденсатор 0.1 мкф 400 вольт.
Напомню, использование обычного реле у меня приводило к сбросу контроллера дисплея. Имено из за индуктивной нагрузки.
miaua, для вентиляторов у меня разведены связки MOC3061+BT134. (схема в даташите на MOC). Снабберные цепочки не ставил, с Домовентами второй год работает без проблем, радиаторы не требуются.
Твердотелы есть смысл ставить на мощные нагрузки, где требуется большой теплоотвод или кто не разводит плату, иначе - выброшенные деньги. ИМХО.
pav2000, 400В конденсатор очень на пределе, лучше 600В.
Очередная версия кода. Код разросся и в режиме демо уже не влезает - пришлось отключть часть кода в этом режиме (инфо экран).
Основные изменения - встроил часы. Время передается в ответе головного блока (в stm32 нашел встроенные rtc). Далее блок сам считает секунды (по таймеру измерения). При новом ответе головного блока производится корректировка времени. Получилось изящно и без аппартных затрат.
Немного скорректировал настройки блока с учетом его эксплуатации - сделал более "грубое" включение.
Наверное проект пришел к своему логическому завершению - я реализовал все свои мыслимые задумки.
У нас уже был минус на улице. Блок показал свою эффективность. У меня в подавле влажность за неделю упала с 7 грамм на куб до 3.8 грамма на куб. И еще у меня картошка в этом подвале лежит.
Установил второй блок во втором подвале. Откалибровал датчики. Один выбросил - врал по влажности на +15% решил что это не примелемо.
За месяц эксплуатации AM2302, оба датчика начали завышать показания. И что удивительно, тот что был на улице и попадал под дождь завышает на 1,45%, а второй, в комнате- на 2,54%. Это данные по NaCl -75%. А по влажности над поверхностью воды, уличный выдал 97,65%, а комнатный 100,65%. Данные по выдержке более 24 часов.
И немного фото.
miaua, это вы уже придираетесь, у них по даташиту, вроде 2% погрешность. А еще скидка на желтых братьев.
Причем, выше 90 и ниже 15 уже до 5% погрешность.
Меня больше волнует, что будет с датчиками через 5-6 месяцев. Моя система это время работает автономно, т.е. до весны. +- 5% меня не волнует. Главное чтобы не поменялась функция прибора на обратную.. :)
Скорее всего все будет нормально. Дрейф у вас даже за заявленную погрешность не вышел. Наблюдается он у любых датчиков, а наиболее сильно в первые часы работы. Поверку приборов не зря придумали)))).
Мой АМ2301 если и уполз, то на единицы %, поэтому по серьезному не проверял, иногда выносной модуль OREGON к нему заношу на сутки.
Ну а оставлять бесконтрольную систему на полгода, даже для отработанных решений не рекомендуется. Светик мигающий наружу выведите и соседа за вкусный напиток посматривать попросите, либо что то дублирующее (типа нагревателей с термостатом.) ИМХО.
Отличная работа miaua! Получилось железо отлично. Теперь откорпусить и в бой.
По датчикам (по моим наблюдениям).
Дейстиветльно используемые со временем "уходят". По этому я второе устройство калибровал после недели работы. Заоодно проверил и первое - показания не ушли. У меня два устройства, и я могу сравниватьих показания. т.к. датчики установлены в разных местах на улице, то показания разные. У меня разница по температуре бывает до 2 градусов. Я проверял термометром и он показал правильность показаний обоих датчиков. Действительно в разных местах разная температура (у меня один датчик не очень правильно весит). Также разнятся и влажности до 3-4 %. Самое интерсное что абсолютные влажности отличаются на сотые грамма.
Сейчас едут ко мне из китая еще два типа датчиков влажности буду их щупать. В первую очередь их буду ставить в автономный (питание от батарейки) блок контроля температуры и влажности. Понравится поставлю в этот блок.
Как я понял измерение влажности проблемная вещь везде. у датчика есть открытая чуствительная поверхность на которую осаждается из воздуха разные вещества (и датчик уходит). По этому я теперь все датчики монтирую сеткой вниз и в банках (дно вверху, банка перевернута и в боковых стенках есть небольшие отверстия) Попадание воды в датчик приводит к фатальным последсвиям (у меня он стал завышать показания на 15%).
Понаблюдав за показаниями (дневной разброс авболютной влажности достигает 3-4 грамма на куб на улице) я пришел к выводу что выставленные границы для включения и выключения у меня малы. Я их увеличил в последней выложенной версии кода. Возможно их надо делать еще больше.
Все выше сказанное позволяет надеятся что показания датчиков будут достоверны и стабильны во времени.
Но проверить (у меня хотя бы сравнивать ) раз в год надо.
Если есть сосед ему можно вручить дистанционный пульт пусть использует как метеостанцию - смотит влажность и температура улицы. Заодно и за вашим устройством приглядит.
Не пробовали в качестве датчика BME280?
Товарищи выложите библиотеку time переделанную, не могу сообразить в самих файлах что менять и в каких.:(
А ни кто не пробывал питатся от 3.3 на всё, вроде где то читал что от 3.3 работает на 16 мгц про мини.
Лови библиотеку (три файла)
stmTime.h
stmTime.cpp
DateStrings.cpp
По поводу 3.3 вольта.
А смысл переводить на 3.3 ? от батареек все равно не реализуешь, и реле (если механическое) на 3.3 работать плохо будет.
16 мгц на 3.3 работает (но не документировано). Правда дисплей можно без резисторов (делители) использовать.
У меня головной блок от 3.3 вольта питается. Особой разницы пока не почуствовал, есть заморочки с питанием.
Не компилица чтото.
stmTime\stmTime.cpp.o: In function `breakTime(unsigned long, tmElements_t&)': C:\Program Files\Arduino\libraries\stmTime/stmTime.cpp:151: multiple definition of `breakTime(unsigned long, tmElements_t&)' stmTime\DateStrings.cpp.o:C:\Program Files\Arduino\libraries\stmTime/DateStrings.cpp:151: first defined here stmTime\stmTime.cpp.o: In function `breakTime(unsigned long, tmElements_t&)': C:\Program Files\Arduino\libraries\stmTime/stmTime.cpp:151: multiple definition of `refreshCache(unsigned long)' stmTime\DateStrings.cpp.o:C:\Program Files\Arduino\libraries\stmTime/DateStrings.cpp:151: first defined here stmTime\stmTime.cpp.o: In function `breakTime(unsigned long, tmElements_t&)': C:\Program Files\Arduino\libraries\stmTime/stmTime.cpp:151: multiple definition of `hour(unsigned long)' stmTime\DateStrings.cpp.o:C:\Program Files\Arduino\libraries\stmTime/DateStrings.cpp:151: first defined here
Ошибка повторное определение функции
У меня библитоека stmTime лежит в домашней папке/ардуино/либрарис.
ну и у меня
C:\Program Files\Arduino\libraries\stmTime
Перешил загрузчик на прохе, пробовал на 3.3 вольта, работает :)
Ваяю платку.
можно добавить на вытяжную трубу подвала инжектор (конус с флюгером) и заслонку на сервоприводе, и скорость ветра замерять. И вообще посади туда грибы...
Ethernet бы еще прикрутить
По просьбам трудящихся -))
Сейчас допиливаю код головного блока будет web сервер (по витой паре). Функционал будет аналогичный, т.е. показ таблицы данных и графиков изменения за последние 10 часов, На одной странице информация об всех удаленных блоках. Пока есть верстия на TFT экране (опубликована на этом форуме) и есть версия чисто web сервера (пока ничего не выкладывал). Возможно получится объеденить в одном коде и экран и сервер. Реализация stm32, для сети используется плата w5100 (можно w5500).
Нашел способ прикрутит POE (пассивный) к плате w5100, думаю об актуальности.
Перешил загрузчик на прохе, пробовал на 3.3 вольта, работает :)
Ваяю платку.
Тоже отличная плата и компактная. А что Вы используете для коммутации нагрузки?
А я все ленюсь делать платы под каждый проект (все силы уходят в разработку кода -))). Сейчас приглядываюсь к орлу, может его буду использовать для схем и разводки.
Жалко что на форуме нельзя выкладывать архивы, а то разводку (уже две версии есть) надо было бы выложить для "потомков" -))
можно добавить на вытяжную трубу подвала инжектор (конус с флюгером) и заслонку на сервоприводе, и скорость ветра замерять. И вообще посади туда грибы...
А смысл измерения скороси ветра в трубе ???
Посадка грибов для мнея не актуальна -))
1. Грибы это влажность близкая к 100% а у меня деревянные перекрытия.
2. Выращивать на "продажу" - это не мой бизнес и объем мал. А выращивать "для себя" не нужно - у меня лес в 100 метрах и в грибное время мы набираем грибов сколько захотим. Объем заготовк ограничивается фразой - "мы столько не съедим и не раздарим".
Человек первый раз на форуме, спросить больше не о чём (Это я про грибы) .:)
Плату рисовал в свободное на работе время, Еще не делал пока. Силовую часть буду делать на moc3023 и bta208-600.
Жду с нетерпением головную часть с етхернетом.