Еще одни часы на матрицах

vk007
Offline
Зарегистрирован: 16.06.2015

yucan пишет:

Спустя 3 дня график почти заполнен. Получается это барограф)

Ну, почти. Из-за ограничения разрешения экрана по вертикали всего в 8 точек, график приходится масштабировать по высоте, поскольку разброс между максимальным и минимальным значением за период, что умещается на экране, часто больше 8 мм рт.ст. Поэтому в одной точке может быть от 1 мм и больше. Соответственно и сам график с каждым новым значением может сжиматься или снова растягиваться (по вертикали). Т.е., например, визуально точно рассчитать по точкам давление, которое было некоторое время назад не получится. Но оценить его изменение или даже немного его спрогнозировать - довольно просто.

yucan
Offline
Зарегистрирован: 20.04.2015

Спасибо за пояснение. А то я прикидывал - сколько едениц в одной точке...

Да, главное знать в какую сторону меняется давление и с какой скоростью. Сейчас, напрмер начало расти, солнышко выглянуло) Пару предыдущих дней - дожди шли.

AleksBAM
Offline
Зарегистрирован: 13.09.2014

Добрый день.

А можно заменить SHT21 на DHT22. И переписать скетч с заменой. 

vk007
Offline
Зарегистрирован: 16.06.2015

AleksBAM пишет:

А можно заменить SHT21 на DHT22. И переписать скетч с заменой. 

Выбор SHT21 обусловлен исключительно тем, что он у меня уже был. Для себя можете переписывать как угодно и что угодно, под любые датчики, какими располагаете - ограничений нет. Опрос датчиков происходит через библиотеки, поэтому подключаете нужную и в скетче в соответствующих местах подправляете вызов функций для опроса вашего датчика (в сетапе - проверка наличия и инициализация датчика, и в лупе - запрос температуры и влажности).

misir
Offline
Зарегистрирован: 14.05.2018

Подскажите кто сталкивался не определяется ds18b20 все остальное работает ,пробовал разные ,пробовал садить на другой пин.

vk007
Offline
Зарегистрирован: 16.06.2015

Не определяется в этом проекте или вообще?

misir
Offline
Зарегистрирован: 14.05.2018

В этом проекте в других работает 

yucan
Offline
Зарегистрирован: 20.04.2015

misir пишет:

В этом проекте в других работает 

Внимательно проверь монтаж.  У меня всё работает

misir
Offline
Зарегистрирован: 14.05.2018

yucan пишет:

misir пишет:

В этом проекте в других работает 

Внимательно проверь монтаж.  У меня всё работает

Заработало пришлось поменять ардуинку оказалась китайская подделка вместо атмеги328 стоит awga328 я и не обратил внимание свиду одинаковые.

yucan
Offline
Зарегистрирован: 20.04.2015

Что  такое awga328? Яндех не в курсе.

misir
Offline
Зарегистрирован: 14.05.2018

В яндексе и я искал посмотри здесь: https://elchupanibrei.livejournal.com/36239.html

AleksBAM
Offline
Зарегистрирован: 13.09.2014

Добрый вечер.

Вопрос. Как можно изменить частоту измерения освещенности по фото датчику. А то видно как пульсируем яркость дисплея. 

AleksBAM
Offline
Зарегистрирован: 13.09.2014

И еще вопрос.

Как скорректировать показания температуры и влажности в помещении. Влажность занижается, а температура завышается.

vk007
Offline
Зарегистрирован: 16.06.2015

AleksBAM пишет:
Как можно изменить частоту измерения освещенности по фото датчику. А то видно как пульсируем яркость дисплея.

Я уже упоминал о решении подобной проблемы. Скорее всего идет засветка датчика от матрицы. Вобщем, читайте сообщение #44., там я расписал, что надо подправить.

Что же касается температуры и влажности, то SHT21 довольно точный датчик. Попробуйте его расположить в корпусе устройства согласно даташиту.

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

Доюрый день!

Как отключить отображение напряжения батарейки? оно тут лишнее как мне кажется

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

еще минимальнои и максимальную температуру с временем. тоже лишнее имхо.

 

Radon
Offline
Зарегистрирован: 19.02.2018

Gavrilov_S пишет:

еще минимальнои и максимальную температуру с временем. тоже лишнее имхо.

 

Так отредактируйте скетч и все дела. Уберите ненужные Вам.

yucan
Offline
Зарегистрирован: 20.04.2015

Gavrilov_S пишет:

Доюрый день!

Как отключить отображение напряжения батарейки? оно тут лишнее как мне кажется

Проверьте состояние батарейки. Если напряжение батарейки в норме - отображение отключено.

Минимальную и максимальную температуру тоже легко отлючить. В скетче есть комментарии. 

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

yucan, спасибо за разъяснение. дело в том что я к батарейке провод еще не тянул. если батарейка в норме то если он не будет показывать это очень даже хорошо.

с отключениями мин макс темперетурой попробую покопаться. я в програмирование не силен. знаю только Basic и Pascal они совсем не похожи 

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

нашел как отключить мин. макс. температуру. сам написал комментарий :)

     // Tminmax(0); //включение отображения макс. темпереатуры
     // Tminmax(1); //включение отображения мин. темпереатуры
Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

мне тоже кажется что бегущая строка слишком быстро пробегает. куда там нужна delay() напписать чтобы задержку включить? :)

bwn
Offline
Зарегистрирован: 25.08.2014

Gavrilov_S пишет:

 я в програмирование не силен. знаю только Basic и Pascal они совсем не похожи 

Та ну напуй.

vk007
Offline
Зарегистрирован: 16.06.2015

Gavrilov_S пишет:

мне тоже кажется что бегущая строка слишком быстро пробегает. куда там нужна delay() напписать чтобы задержку включить? :)

Тут еще и двух страниц не набралось, а вы ленитесь прочитать предыдущие комментарии. Смотрите #14.

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

vk007, добавил паузу. Все отлично! Теперь хочу график отключить. Он мне без надобности :) достаточно атмосферное давление показать

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

vk007, забыл сказать Большое Спасибо за суперские часы! 

Давно искал такие! На матрицах! У других функционал слабоват и шрифты не красивые!

Моим друзьям тоже понравилась! Уже двое захотели себе собрать. :)

bwn
Offline
Зарегистрирован: 25.08.2014

Gavrilov_S пишет:

Ну зачем так орать? Вроде давно на форуме, знаете, что так здесь не принято.((((

vk007
Offline
Зарегистрирован: 16.06.2015

Gavrilov_S пишет:
Теперь хочу график отключить. Он мне без надобности :) достаточно атмосферное давление показать

Найдите в скетче блок, отвечающий за вывод давления на экран - он начинается с комментария

// +++++ Давление +++++

И вместо него вставьте следующий код

  if(bmpOK && (AlarmNotActive() || !beeper))
  {
    StringToSprite("\xAE\xA0mmHg");
    ScrollVertical(0, 31, 0);
    delay(500);
    PresToString(0.51 + BMP.readPressure() * 760.0 / 101325.0);
    StringToSprite(string_buffer);
    ScrollVertical(9, 31, 1);
    delay(1000);
    for(byte i = 0; i < 32; i++) sprite_buffer[i] = 0;
  }

Также из сетапа можете удалить блок

if(bmpOK)
{
  ...
}

 

yucan
Offline
Зарегистрирован: 20.04.2015

А по моему важно знать не само значение давления (на меня оно никак не влияет), а то, как оно меняется, чтобы прикидывать изменение погоды. Поэтому график нужен. Тем более время его показа незначительно.

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

Спассбо за помощ!

Скетч подправил! Все работает как надо! Без графика! 

Yucan, Если нужен график то его можно включить обратно. строки программы только закоментировал на всякий случай

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

vk007, еще одну хотелку хочу поставить! 

Чтобы каждый час когда например 12-00. на любой свободный порт логическая 1 появилась на 30 секунд(время буду точно подбирать потом по секундно) . буду мп3 плеер подключать

 

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

будет типа кукушки :)

vk007
Offline
Зарегистрирован: 16.06.2015

Gavrilov_S пишет:

еще одну хотелку хочу поставить! 

Дык, кто ж вам запретит - делайте! Спрашивать разрешение не обязательно. Можете даже не одну сделать, а вообще перекроить скетч под свои желания. С тем функционалом, что там уже есть, пара килобайт кода туда еще влезет, а если скетч еще и прооптимизировать...

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

***

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

я только паять умею 

Добрые люди! Помогите пожалуйста с кодом. Чтобы в каждый час (пример в 12-00, 13-00 и.т.д.) на свободном порте появилась логическая "1" на 30 секунд (чтобы это время можно было настраивать) для Кукушки

Заранее Спасибо!

vk007
Offline
Зарегистрирован: 16.06.2015

Gavrilov_S пишет:
я только паять умею

За почти 4 года, что Вы на форуме можно было бы уже хоть немного научиться программировать. Ну правда.

Могу дать направление, в каком стоит попробовать двигаться. В DS3231 есть аж 2 будильника, и оба они свободные (в часах я писал свои будильники). Любой из них может быть настроен на срабатывание в том числе ежечасно. Вот и воспользуйтесь этим. При срабатывании будильника на ноге SQW модуль выставит низкий уровень (при соответствующей настройке). Этот сигнал можно использовать как прерывание для ардуинки. Ну а дальше чистый полет фантазии...

Правда, библиотека, которую использует скетч, не умеет работать с будильниками. Поэтому придется или использовать другую библиотеку для работы с часовым модулем (которая умеет работать с будильниками) и подправить весь скетч под нее, или посмотреть, как работают с будильниками в этой другой библиотеке, и дописать код для будильника самому.

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

а нельзя как на бейсике. что то подобное

где h - Это часы

if h=0 then let pin5=1 

else

if h=1 then let pin5=1

else

if h=2 then let pin5=1

else 

if h=3 then let pin5=1

 

 

 

vk007
Offline
Зарегистрирован: 16.06.2015

Gavrilov_S пишет:

а нельзя как на бейсике

Можно как угодно, но алгоритм и логика ведь должны быть в любом случае правильными.

А по вашей логике "pin5=1" будет вечно, чему бы h не равнялось.

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

ну да. верно. логика тут не верна. я просто накинул. что можно простым способом переделать. и часы не нужно сканировать. надо сканировать минуты и секунды.

типа

h=часы, m=минуты, sek=секунды, kuku=включение и отключение кукушки

так вроде должа работать 30 секунд каждый час. 

if m=0 and sek<=30 then kuku=1 else kuku=0

 

а если нужно только в определенные часы то 

if h=1 and m=0 and sek<=30 then kuku=1 else kuku=0

if h=12 and m=0 and sek<=30 then kuku=1 else kuku=0

vk007
Offline
Зарегистрирован: 16.06.2015

Ну сейчас да. Почти так, как вам нужно.

Но есть одно но. Такой алгоритм не пригоден для существующего скетча, вернее, не совсем пригоден. Кукушка не всегда будет срабатывать. Вот если бы часы показывали только время, а так там навешано отображение кучи другой информации. Если смена часа произойдет в момент показа времени, то кукушка сработает, иначе врядли вы ее дождетесь.

Представьте, сейчас 11:59:55 и тут наступает время отображения дополнительной информации: дата, обе температуры, влажность, давление. И даже если вы у себя отключили вывод макс/мин температуры и график давления, то все-равно за 5 сек эта инфа не успеет пробежать по экрану. А если еще учесть, что вы уменьшили скорость скроллинга? А если это последние дни декабря и добавится "до нового года осталось..."? А если разряжена батарейка? А если день рождения любимой тещи и вы так ее любите, что накатали текст поздравлялки, который еле влез во все 16 сообщений? Тут не то что 5 сек, тут и нескольких минут не хватит.

Даже если использовать флаг (было ку-ку в этом часу или нет) и куковать уже после вывода доп.инфы - ну вернутся часы на отображение времени, скажем, в 12:03 и выдадут "ку-ку". Как будет выглядеть это "ку-ку" в 12:03 этой слегка подгулявшей кукушки?

Т.е., или в корне переделывать скетч на короткий луп, что немного хлопотно (проще написать все с нуля), или воспользоваться средствами DS3231, как я описывал немного раньше, что проще - досточно добавить нужную настройку будильника DS3231 и завести функцию обработки прерывания от будильника. Будильник DS3231 не зависит от нашего лупа и сработает в нужное время, не зависимо, что будет на экране наших часов.

vk007
Offline
Зарегистрирован: 16.06.2015

Но есть еще один вариант. Велосипедный, но вариант - сделать так, как в скетче сделаны будильники.

Будильник в часах, работающих от сети 220В, то еще удовольствие. Если не было электричества, когда должен был сработать будильник - значит проспал. Поэтому будильники делались одними из последних по принципу "пускай уже будет" и все переделывать на короткий луп уже не хотелось. Еще я не хотел привязываться к какому-то конкретному модулю RTC - DS1307 или DS3231. Т.к. в DS1307, насколько мне известно (могу и ошибаться), нет выхода прерывания. А хотелось, чтобы была возможность сделать часы на обоих модулях. Потому и такой велосипед.

Так вот, в скетче перед каждым выводом какой-либо информации идет проверка, а не пора ли включить сигнал будильника. И если пора, то игнорируем то, что еще не успели показать, возвращаемся к отображению времени и включаем будильник. Таким образом, задержка хоть и может быть, но она небольшая - считаные секунды.

Можете пойти и таким путем, вместе с будильником проверять еще и нули минут и включать кукушку.

Только меня интересует один вопрос - зачем устанавливать на пине нужный уровень, длительностью определенное (да еще и изменяемое) время? MP3-модули ведь работают по команде, дали команду - проигрался файл.

Gavrilov_S
Offline
Зарегистрирован: 13.11.2014

плеер у меня простой. https://ru.aliexpress.com/item/TF-card-MP3-decoder-board-with-2W-power-d.... лог 1 нужна для имеено такого плеера. у него нет i2c только кнопки можно приделать. запишу туда 1 файл ку-ку т он будет включаться при появлении 5 вольт

Xer0x_king
Offline
Зарегистрирован: 22.06.2017

Кто может помочь? Выкинул sht, пытаюсь втиснуть bme280, в результате везде нули... ЧЯДНТ? куда копать?

/*** Dot Matrix Clock ************************************************************\
* Для нормального отображения кириллицы и пиктограмм необходимо перед компиляцией *
*       в файле preferences.txt установить preproc.substitute_unicode=false       *
\************************************************************************* VK007 */

#include <Wire.h>
#include <RTClib.h>
#include <Adafruit_BME280.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <LedControl.h>
#include <EEPROM.h>
#include "fonts.h"

#define DS3231          // тип RTC (DS3231 или DS1307)
#define RUS             // язык (RUS, UKR, BLR, ENG)

#define ONE_WIRE_BUS 9  // уличный ds18b20
#define BEEPER_PIN   8  // пищалка (с генератором)
#define UP_PIN       7  // кнопка ВВЕРХ/ВПРАВО
#define OK_PIN       6  // кнопка ОК
#define DOWN_PIN     5  // кнопка ВНИЗ/ВЛЕВО
#define DIN_PIN      10 // DIN 1-й матрицы
#define CLK_PIN     11  // CLK матриц
#define CS_PIN      13  // CS матриц
#define BATT_PIN    A6  // напряжение батарейки часов
#define SENSOR_PIN  A7  // фотодатчик

#define TIME_SHOW_MSEC       30000UL    // продолжительность отображения времени (30 сек)
#define PRESSURE_UPDATE_MSEC 7200000UL  // интервал обновления графика давления (7 200 000 мс = 2 часа)
#define PRESSURE_VALUE_SHIFT 560        // разница между измеренным и сохраняемым значением давления
                                        // чтобы уместить в byte (диапазон значений давления 560 - 814 мм)
#define ALARMS_ADDR   256 // будильники (256..276 = 21 байт, 7х3 (вкл/выкл, часы, минуты)
#define MESSAGES_ADDR 288 // сообщения (288..1023 = 736 байт, 16х46 (день, месяц, текст до 44 символов))

const float voltage_divider_coefficient = 3.2; // коэффициент делителя напряжения
const float Vref = 1.043;   // опорное напряжение

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature Dallas(&oneWire);
#ifdef DS1307
  RTC_DS1307 RTC;
#else
  RTC_DS3231 RTC;
#endif
Adafruit_BME280 bme;
LedControl lc = LedControl(DIN_PIN, CLK_PIN, CS_PIN, 4);
DateTime NOW;
byte HOUR_OLD;
byte MINUTE_OLD;
boolean bmeOK, dallasOK;
boolean beeper = 1;
unsigned int counter;
byte pressure_addr;
char string_buffer[52];
byte screen_buffer[32];
byte sprite_buffer[255];
byte sprite_length;
float t_min, t_max;
byte t_min_h, t_min_m, t_max_h, t_max_m;
const char daysOfTheWeek[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
#ifdef RUS
  const char sunday[]      PROGMEM = "воскресенье";
  const char monday[]      PROGMEM = "понедельник";
  const char tuesday[]     PROGMEM = "вторник";
  const char wednesday[]   PROGMEM = "среда";
  const char thursday[]    PROGMEM = "четверг";
  const char friday[]      PROGMEM = "пятница";
  const char saturday[]    PROGMEM = "суббота";
  const char january[]     PROGMEM = "января";
  const char february[]    PROGMEM = "февраля";
  const char march[]       PROGMEM = "марта";
  const char april[]       PROGMEM = "апреля";
  const char may[]         PROGMEM = "мая";
  const char june[]        PROGMEM = "июня";
  const char july[]        PROGMEM = "июля";
  const char august[]      PROGMEM = "августа";
  const char september[]   PROGMEM = "сентября";
  const char october[]     PROGMEM = "октября";
  const char november[]    PROGMEM = "ноября";
  const char december[]    PROGMEM = "декабря";
  const char yyyy[]        PROGMEM = " года";
  const char message_UNY[] PROGMEM = " до Нового года";
  const char message_HNY[] PROGMEM = "С Новым годом!";
  const char days1[]       PROGMEM = " день";
  const char days2_4[]     PROGMEM = " дня";
  const char days5_0[]     PROGMEM = " дней";
  const char hours1[]      PROGMEM = " час";
  const char hours2_4[]    PROGMEM = " часа";
  const char hours5_0[]    PROGMEM = " часов";
  const char minutes1[]    PROGMEM = " минута";
  const char minutes2_4[]  PROGMEM = " минуты";
  const char minutes5_0[]  PROGMEM = " минут";
  const char seconds1[]    PROGMEM = " секунда";
  const char seconds2_4[]  PROGMEM = " секунды";
  const char seconds5_0[]  PROGMEM = " секунд";
#endif

const char* const months[] PROGMEM = {january, february, march, april, may, june, july, august, september, october, november, december};
const char* const dow[] PROGMEM = {sunday, monday, tuesday, wednesday, thursday, friday, saturday};
const char* const dimension[] PROGMEM = {days1, days2_4, days5_0, hours1, hours2_4, hours5_0, minutes1, minutes2_4, minutes5_0, seconds1, seconds2_4, seconds5_0};

//----------------------------------------------------------------------------------------------
// Установка яркости дисплея по фотодатчику
//----------------------------------------------------------------------------------------------
void SetBrightness()
{
  byte brightness = map(constrain(analogRead(SENSOR_PIN), 200, 900), 200, 900, 0, 15);
  for(byte i = 0; i < 4; i++)
   lc.setIntensity(i, brightness);
}
//----------------------------------------------------------------------------------------------
// Выводит на экран текущее время
// первый параметр - тип скролла:
// 0 - вывод без эффектов
// 1 - последняя цифра (единицы минут)
// 2 - две последние цифры (минуты)
// 3 - три последние цифры (единицы часов и минуты)
// 4 - все цифры (часы и минуты)
// 5 - вертикальный скролл каждой цифры по-очереди (начиная с первой слева)
// второй параметр - направление: 0 - вниз, 1 - вверх
//----------------------------------------------------------------------------------------------
void DisplayTime(byte scroll_mode, boolean dir)
{
  NOW = RTC.now();
  for(byte i = 0; i < 32; i++) sprite_buffer[i] = 0;
  for(byte i = 1; i < 7; i++)
    if(NOW.hour() / 10) sprite_buffer[i] = pgm_read_byte_near(font_digit + (NOW.hour() / 10) * 6 + i - 1);
  for(byte i = 8; i < 14; i++)
    sprite_buffer[i] = pgm_read_byte_near(font_digit + (NOW.hour() % 10) * 6 + i - 8);
  for(byte i = 18; i < 24; i++)
    sprite_buffer[i] = pgm_read_byte_near(font_digit + (NOW.minute() / 10) * 6 + i - 18);
  for(byte i = 25; i < 31; i++)
    sprite_buffer[i] = pgm_read_byte_near(font_digit + (NOW.minute() % 10) * 6 + i - 25);
  switch(scroll_mode)
  {
    case 0: { Show(0); break; }
    case 1: { ScrollVertical( 24, 31, dir); break; }
    case 2: { ScrollVertical( 17, 31, dir); break; }
    case 3: { ScrollVertical(  8, 31, dir); break; }
    case 4: { ScrollVertical(  0, 31, dir); break; }
    case 5:
    {
      sprite_buffer[15] = B01100110;
      sprite_buffer[16] = B01100110;
      ScrollVertical( 0,  7, dir);
      ScrollVertical( 8, 14, dir);
      ScrollVertical(15, 16, dir);
      ScrollVertical(17, 23, dir);
      ScrollVertical(24, 31, dir);
      break;
    }
  }
  HOUR_OLD = NOW.hour();
  MINUTE_OLD = NOW.minute();
}
//----------------------------------------------------------------------------------------------
// Возвращает адрес символа в массиве со шрифтом
//----------------------------------------------------------------------------------------------
const int char_addr[] = {1, 513, 646, 0, 1183, 184, 1117, 1149, 86, 1165, 100, 1201, 1141, 1123, 1174, 59, 451, 1192, 287, 511, 192, 1157, 1129, 1111, 9, 1135, 51, 1223, 0, 0, 0, 66};
int CharAddress(byte ccc)
{
  if(ccc < 32) return 0;
  if(ccc < 127) return (ccc - 32) * 7;
  if(ccc < 160) return 0;
  if(ccc < 192) return char_addr[ccc - 160];
  else return (ccc - 97) * 7;
}
//----------------------------------------------------------------------------------------------
// Формирует из строки изображение (сохраняет в массиве - sprite_buffer)
// второй параметр - промежуток между символами (в точках), необязательно (по умолчанию = 1)
//----------------------------------------------------------------------------------------------
void StringToSprite(char *str, byte space = 1)
{
  for(byte j = 0; j < sizeof(sprite_buffer); j++) sprite_buffer[j] = 0;
  byte i = 0;
  sprite_length = 0;
  while(str[i] != '\0' && sprite_length < sizeof(sprite_buffer))
  {
    for(byte j = 1; j <= pgm_read_byte_near(font + CharAddress(str[i])) && sprite_length < sizeof(sprite_buffer); j++)
      sprite_buffer[sprite_length++] = pgm_read_byte_near(font + CharAddress(str[i]) + j);
    for(byte j = 0; j < space && sprite_length < sizeof(sprite_buffer); j++) 
      sprite_length++;
    if(space > 0 && str[i] == '7' && (str[i + 1] == '4' || str[i + 1] == ',' || str[i + 1] == '.'))
      sprite_length--;
    i++;
  }
  sprite_length -= space;
}
//----------------------------------------------------------------------------------------------
// Переводит значение температуры из float в строку нужного формата (сохраняет в string_buffer)
// второй параметр необязательный - добавлять или нет в конце строки символ 'C' (по умолчанию = 1)
//----------------------------------------------------------------------------------------------
void TempToString(float temp, boolean mode = 1)
{
  byte i = 1;
  byte int_temp = 0.051 + abs(temp);
  byte frac_temp = int((0.051 + abs(temp)) * 10) % 10;
  if(int_temp == 0 && frac_temp == 0)
    string_buffer[0] = '0';
  else
  {
    if(temp < 0)
      string_buffer[0] = '-';
    else
      string_buffer[0] = '+';
    if(int_temp > 9)
    {
      string_buffer[i++] = int_temp / 10 + '0';
      string_buffer[i++] = int_temp % 10 + '0';
    }
    else
      string_buffer[i++] = int_temp + '0';
    string_buffer[i++] = ',';
    string_buffer[i++] = frac_temp + '0';
  }
  string_buffer[i++] = '°';
  if(mode) string_buffer[i++] = 'C';
  string_buffer[i] = '\0';
}
//----------------------------------------------------------------------------------------------
// Переводит значение влажности из float в строку нужного формата (сохраняет в string_buffer)
//----------------------------------------------------------------------------------------------
void HumToString(float hum)
{
  string_buffer[0] = '\xB7';
  string_buffer[1] = '\xA0';
  byte i = 2;
  byte int_hum = 0.51 + hum;
  if(int_hum > 9)
  {
    string_buffer[i++] = int_hum / 10 + '0';
    string_buffer[i++] = int_hum % 10 + '0';
  }
  else
    string_buffer[i++] = int_hum + '0';
  string_buffer[i] = '%';
  string_buffer[i + 1] = 'R';
  string_buffer[i + 2] = 'H';
  string_buffer[i + 3] = '\0';
}
//----------------------------------------------------------------------------------------------
// Переводит значение давления из int в строку нужного формата (сохраняет в string_buffer)
//----------------------------------------------------------------------------------------------
void PresToString(int int_pres)
{
  string_buffer[0] = '\xAE';
  string_buffer[1] = ' ';
  string_buffer[4] = int_pres % 10 + '0';
  int_pres = int_pres / 10;
  string_buffer[3] = int_pres % 10 + '0';
  string_buffer[2] = int_pres / 10 + '0';
  string_buffer[5] = '\0';
}
//----------------------------------------------------------------------------------------------
// Вспомогательная функция для Tminmax() - центрирует картинку с температурой/временем
//----------------------------------------------------------------------------------------------
void ShiftT(boolean mode)
{
  byte shift = (26 - sprite_length)/2 + 6;
  for(byte i = 1; i <= sprite_length; i++)
    sprite_buffer[sprite_length + shift - i] = sprite_buffer[sprite_length - i];
  for(byte i = 5; i < shift; i++)
    sprite_buffer[i] = 0;
  for(byte i = 0; i < 5; i++)
    sprite_buffer[i] = pgm_read_byte_near(font + CharAddress(mode ? '¶' : '№') + i + 1);
  sprite_length += shift;
}
//----------------------------------------------------------------------------------------------
// Выводит на экран минимальную/максимальную температуру
// параметр - 0 (min температура), 1 (max температура)
//----------------------------------------------------------------------------------------------
void Tminmax(boolean mode)
{
  TempToString(mode ? t_max : t_min, 0);
  StringToSprite(string_buffer);
  ShiftT(mode);
  for(byte i = 0; i < 8; i++)
  {
    ScrollVerticalOneRow(0, 5, mode);
    ScrollVerticalOneRow(6, 31, !mode);
    Show(0);
  }
  delay(1500);
  string_buffer[0] = (mode ? t_max_h : t_min_h) / 10 + '0';
  string_buffer[1] = (mode ? t_max_h : t_min_h) % 10 + '0';
  string_buffer[2] = ':';
  string_buffer[3] = (mode ? t_max_m : t_min_m) / 10 + '0';
  string_buffer[4] = (mode ? t_max_m : t_min_m) % 10 + '0';
  string_buffer[5] = 0;
  StringToSprite(string_buffer);
  ShiftT(mode);
  ScrollVertical(6, 31, !mode);
  delay(1500);
}
//----------------------------------------------------------------------------------------------
// Используется для вывода в бегущей строке "обратного отсчета"
//----------------------------------------------------------------------------------------------
void ShowCountdown(byte num, byte ndim)
{
  string_buffer[0] = num < 10 ? ' ' : num / 10 + '0';
  string_buffer[1] = num % 10 + '0';
  string_buffer[2] = 0;
  StringToSprite(string_buffer);
  ScrollBufferToScreen(0);
  #ifdef ENG
    if(num != 1) ndim++;
  #else
    if(string_buffer[1] > '1' && string_buffer[1] < '5' && string_buffer[0] != '1') ndim++;
    else if(string_buffer[1] > '4' || string_buffer[1] == '0' || string_buffer[0] == '1') ndim += 2;
  #endif
  strcpy_P(string_buffer, (char*)pgm_read_word(&(dimension[ndim])));
  StringToSprite(string_buffer);
  ScrollBufferToScreen(0);
}
//----------------------------------------------------------------------------------------------
// Выводит изображение из спрайт-буфера на экран со сдвигом влево всего содержимого (экран + спрайт-буфер)
// если указан параметр 0, то на экране останется "хвост" картинки/текста
// если 1 - то картинка полностью "уедет" за левый край экрана
//----------------------------------------------------------------------------------------------
void ScrollBufferToScreen(boolean mode)
{
  for(byte i = 0; i < sprite_length; i++)
    ScrollScreenBufferOneLeft(1);
  if(mode)
    for(byte i = 0; i < 32; i++)
      ScrollScreenBufferOneLeft(0);
}
//----------------------------------------------------------------------------------------------
// Выводит в центр экрана изображение из спрайт-буфера (со сдвигом влево)
//----------------------------------------------------------------------------------------------
void SpriteToCenterScreen()
{
  for(byte i = 0; i <= (32 - sprite_length) / 2; i++)
    ScrollScreenBufferOneLeft(0);
  for(byte i = 0; i < sprite_length / 2 + 16; i++)
    ScrollScreenBufferOneLeft(1);
}
//----------------------------------------------------------------------------------------------
// Сдвигает экран на одну точку влево
// если параметр 0, то сдвигается только экран (последняя колонка очищается)
// если 1 - то сдвигается экран + спрайт-буфер (в последнюю колонку переносятся данные из спрайт-буфера)
//----------------------------------------------------------------------------------------------
void ScrollScreenBufferOneLeft(boolean mode)
{
  for(byte row = 0; row < 32; row++)
    screen_buffer[row] = screen_buffer[row + 1];
  if(mode)
  {
    screen_buffer[31] = sprite_buffer[0];
    for(byte i = 0; i < sprite_length - 1; i++)
    {
      sprite_buffer[i] = sprite_buffer[i + 1];
      sprite_buffer[i + 1] = 0;
    }
  }
  else
    screen_buffer[31] = 0;
  Show(0);
}
//----------------------------------------------------------------------------------------------
// Вертикальный скролл на один ряд экранного буфера (без отображения на экране)
// данные из экранного буфера вытесняются данными из аналогичной области спрайт-буфера (ячейки 0 - 31)
// первые два параметра - с какой колонки скроллировать и по какую (0...31)
// третий - направление: 1 - вверх, 0 - вниз
//----------------------------------------------------------------------------------------------
void ScrollVerticalOneRow(byte from, byte to, boolean dir)
{
  for(byte row = from; row < to + 1; row++)
  {
    if(dir)
    {
      screen_buffer[row] >>= 1;
      bitWrite(screen_buffer[row], 7, bitRead(sprite_buffer[row], 0));
      sprite_buffer[row] >>= 1;
    }
    else
    {
      screen_buffer[row] <<= 1;
      bitWrite(screen_buffer[row], 0, bitRead(sprite_buffer[row], 7));
      sprite_buffer[row] <<= 1;
    }
  }
}
//----------------------------------------------------------------------------------------------
// Вертикальный скролл (на все 8 точек) части экрана
// данные на экране (из экранного буфера) вытесняются данными из аналогичной области спрайт-буфера (ячейки 0 - 31)
// первые два параметра - с какой колонки скроллировать и по какую (0...31)
// третий - направление: 1 - вверх, 0 - вниз
//----------------------------------------------------------------------------------------------
void ScrollVertical(byte from, byte to, boolean dir)
{
  for(byte i = 0; i < 8; i++)
  {
    ScrollVerticalOneRow(from, to, dir);
    Show(0);
  }
}
//----------------------------------------------------------------------------------------------
// Вывод содержимого буфера на экран
// параметр - тип буфера: 0 - из экранного, 1 - из спрайт-буфера
//----------------------------------------------------------------------------------------------
void Show(boolean mode)
{
  for(byte row = 0; row < 32; row++)
    lc.setRow(row / 8, row % 8, mode ? sprite_buffer[row] : screen_buffer[row]);
}
//----------------------------------------------------------------------------------------------
// Мерцание определенной матрицы (0...3)
//----------------------------------------------------------------------------------------------
void Blink(byte n)
{
  byte c_brightness = 0;
  char direction = 1;
  while(digitalRead(OK_PIN) && digitalRead(UP_PIN) && digitalRead(DOWN_PIN))
  {
    byte brightness = map(constrain(analogRead(SENSOR_PIN), 200, 900), 200, 900, 0, 15);
    for(byte i = 0; i < 4; i++) lc.setIntensity(i, n == i ? c_brightness : brightness);
    if(c_brightness == 0) direction = 1;
    if(c_brightness == 15) direction = -1;
    c_brightness += direction;
    delay(20);
  }
}
//----------------------------------------------------------------------------------------------
// Мигающий курсор
// в параметрах - начало и конец курсора (0...31)
//----------------------------------------------------------------------------------------------
void Cursor(byte from, byte to)
{
  boolean on = 1;
  while(digitalRead(OK_PIN) && digitalRead(UP_PIN) && digitalRead(DOWN_PIN))
  {
    counter = 0;
    if((millis() % 500 < 250) == on)
    {
      for(byte i = from; i <= to; i++)
        lc.setLed(i / 8, i % 8, 0, on);
      on = !on;
    }
    SetBrightness();
  }
}
//----------------------------------------------------------------------------------------------
// Выдает короткий звук (щелчок) на пищалку
//----------------------------------------------------------------------------------------------
void Beep()
{
  digitalWrite(BEEPER_PIN, 1);
  delay(2);
  digitalWrite(BEEPER_PIN, 0);
}
//----------------------------------------------------------------------------------------------
// Выдает короткий звук (щелчок) на пищалку и ждет отпускания кнопки
//----------------------------------------------------------------------------------------------
void ButtonPressed()
{
  Beep();
  while(!digitalRead(OK_PIN) || !digitalRead(UP_PIN) || !digitalRead(DOWN_PIN)) delay(50);
}
//----------------------------------------------------------------------------------------------
// Проверка будильника
//----------------------------------------------------------------------------------------------
boolean AlarmNotActive()
{
  NOW = RTC.now();
  return !(EEPROM.read(NOW.dayOfTheWeek() * 3 + ALARMS_ADDR) == 1 && NOW.hour() == EEPROM.read(NOW.dayOfTheWeek() * 3 + ALARMS_ADDR + 1) && NOW.minute() == EEPROM.read(NOW.dayOfTheWeek() * 3 + ALARMS_ADDR + 2));
}
//----------------------------------------------------------------------------------------------
// Настройка времени
//----------------------------------------------------------------------------------------------
void SetupTime()
{
  NOW = RTC.now();
  char HOUR = NOW.hour();
  char MINUTE = NOW.minute();
  byte pos = 0;
  while(pos < 2)
  {
    string_buffer[0] = ' ';
    string_buffer[1] = ' ';
    byte i = 2;
    if(HOUR / 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = HOUR / 10 + '0';
    string_buffer[i++] = HOUR % 10 + '0';
    if(HOUR % 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = ':';
    if(MINUTE / 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = MINUTE / 10 + '0';
    string_buffer[i++] = MINUTE % 10 + '0';
    string_buffer[i] = '\0';
    StringToSprite(string_buffer);
    Show(1);
    Cursor(pos == 0 ? 6 : 18, pos == 0 ? 14 : 26);
    if(!digitalRead(UP_PIN))
    {
      if(!counter) Beep();
      if(!counter || counter > 4)
      {
        if(pos == 0)
        {
          HOUR++; if(HOUR > 23) HOUR = 0;
        }
        else
        {
          MINUTE++; if(MINUTE > 59) MINUTE = 0;
        }
      }
      counter++;
      delay(100);
    }
    if(!digitalRead(DOWN_PIN))
    {
      if(!counter) Beep();
      if(!counter || counter > 4)
      {
        if(pos == 0)
        {
          HOUR--; if(HOUR < 0) HOUR = 23;
        }
        else
        {
          MINUTE--; if(MINUTE < 0) MINUTE = 59;
        }
      }
      counter++;
      delay(100);
    }
    if(!digitalRead(OK_PIN))
    {
      ButtonPressed();
      pos++;
    }
  }
  NOW = RTC.now();
  RTC.adjust(DateTime(NOW.year(), NOW.month(), NOW.day(), HOUR, MINUTE, 0));
}
//----------------------------------------------------------------------------------------------
// Настройка даты
//----------------------------------------------------------------------------------------------
void SetupDate()
{
  NOW = RTC.now();
  int YEAR = NOW.year();
  char MONTH = NOW.month();
  char DAY = NOW.day();
  byte pos = 0;
  while(pos < 3)
  {
    string_buffer[0] = '\xA0';
    byte i = 1;
    if(DAY / 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = DAY / 10 + '0';
    string_buffer[i++] = DAY % 10 + '0';
    if(DAY % 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = '\xA0';
    if(MONTH / 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = MONTH / 10 + '0';
    string_buffer[i++] = MONTH % 10 + '0';
    if(MONTH % 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = '\xA0';
    if((YEAR - 2000) / 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = (YEAR - 2000) / 10 + '0';
    string_buffer[i++] = (YEAR - 2000) % 10 + '0';
    string_buffer[i] = '\0';
    StringToSprite(string_buffer);
    Show(1);
    if(pos == 0) Cursor(1, 9);
    else
      if(pos == 1) Cursor(12, 20);
      else Cursor(23, 31);
    if(!digitalRead(UP_PIN))
    {
      if(!counter) Beep();
      if(!counter || counter > 4)
      {
        if(pos == 0)
        {
          DAY++; if(DAY > 31) DAY = 1;
        }
        else
        {
          if(pos == 1)
          {
            MONTH++; if(MONTH > 12) MONTH = 1;
          }
          else
          {
            YEAR++; if(YEAR > 2099) YEAR = 2000;
          }
        }
      }
      counter++;
      delay(100);
    }
    if(!digitalRead(DOWN_PIN))
    {
      if(!counter) Beep();
      if(!counter || counter > 4)
      {
        if(pos == 0)
        {
          DAY--; if(DAY < 1) DAY = 31;
        }
        else
        {
          if(pos == 1)
          {
            MONTH--; if(MONTH < 1) MONTH = 12;
          }
          else
          {
            YEAR--; if(YEAR < 2000 || YEAR > 2099) YEAR = 2099;
          }
        }
      }
      counter++;
      delay(100);
    }
    if(!digitalRead(OK_PIN))
    {
      ButtonPressed();
      pos++;
    }
  }
  if((DAY == 31 && (MONTH == 4 || MONTH == 6 || MONTH == 9 || MONTH == 11)) || (MONTH == 2 && ((DAY > 28 && (YEAR % 4)) || (DAY > 29 && !(YEAR % 4)))))
  {
    DAY = 1; MONTH++;
  }
  NOW = RTC.now();
  RTC.adjust(DateTime(YEAR, MONTH, DAY, NOW.hour(), NOW.minute(), NOW.second()));
}
//----------------------------------------------------------------------------------------------
// Настройка будильников
//----------------------------------------------------------------------------------------------
void SetupAlarms()
{
  byte ndow = 1;
  byte pos = 0;
  byte state = 0;
  char HOUR = 0;
  char MINUTE = 0;
  while(pos < 4)
  {
    if(pos == 0)
    {
      if(ndow == 8)
      {
        state = 0; HOUR = 0; MINUTE = 0;
      }
      else
      {
        state = EEPROM.read(ndow < 7 ? ndow * 3 + ALARMS_ADDR : ALARMS_ADDR) == 1;
        HOUR = EEPROM.read(ndow < 7 ? ndow * 3 + ALARMS_ADDR + 1 : ALARMS_ADDR + 1);
        MINUTE = EEPROM.read(ndow < 7 ? ndow * 3 + ALARMS_ADDR + 2 : ALARMS_ADDR + 2);
      }
    }
    if(HOUR < 0 || HOUR > 23 || MINUTE < 0 || MINUTE > 59)
    {
      HOUR = 0; MINUTE = 0;
    }
    string_buffer[0] = '!';
    string_buffer[1] = state ? '±' : '¤';
    byte i = 2;
    if(HOUR / 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = HOUR / 10 + '0';
    string_buffer[i++] = HOUR % 10 + '0';
    if(HOUR % 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = ':';
    if(MINUTE / 10 == 1) string_buffer[i++] = '\xA0';
    string_buffer[i++] = MINUTE / 10 + '0';
    string_buffer[i++] = MINUTE % 10 + '0';
    string_buffer[i] = '\0';
    StringToSprite(string_buffer);
    sprite_buffer[0] = ndow < 8 ? 1 << (ndow - 1) : B01111111;
    Show(1);
    switch(pos)
    {
      case 0: { Cursor(0, 1); break; }
      case 1: { Cursor(2, 9); break; }
      case 2: { Cursor(11, 19); break; }
      case 3: { Cursor(23, 31); break; }
    }
    if(!digitalRead(UP_PIN))
    {
      if(!counter) Beep();
      if(!counter || counter > 4)
      {
        switch(pos)
        {
          case 0: { ndow--; if(ndow < 1) ndow = 8; break; }
          case 1: { state = !state; break; }
          case 2: { HOUR++; if(HOUR > 23) HOUR = 0; break; }
          case 3: { MINUTE++; if(MINUTE > 59) MINUTE = 0; break; }
        }
      }
      counter++;
      delay(100);
    }
    if(!digitalRead(DOWN_PIN))
    {
      if(!counter) Beep();
      if(!counter || counter > 4)
      {
        switch(pos)
        {
          case 0: { ndow++; if(ndow > 8) ndow = 1; break; }
          case 1: { state = !state; break; }
          case 2: { HOUR--; if(HOUR < 0) HOUR = 23; break; }
          case 3: { MINUTE--; if(MINUTE < 0) MINUTE = 59; break; }
        }
      }
      counter++;
      delay(100);
    }
    if(!digitalRead(OK_PIN))
    {
      ButtonPressed();
      pos++;
    }
  }
  if(ndow == 7) ndow = 0;
  if(ndow < 7)
  {
    EEPROM.write(ndow * 3 + ALARMS_ADDR, state);
    EEPROM.write(ndow * 3 + ALARMS_ADDR + 1, HOUR);
    EEPROM.write(ndow * 3 + ALARMS_ADDR + 2, MINUTE);
  }
  else
    for(byte i = 0; i < 7; i++)
    {
      EEPROM.write(i * 3 + ALARMS_ADDR, state);
      EEPROM.write(i * 3 + ALARMS_ADDR + 1, HOUR);
      EEPROM.write(i * 3 + ALARMS_ADDR + 2, MINUTE);
    }
}
//----------------------------------------------------------------------------------------------
// Вспомогательная функция для SerialSetup()
//----------------------------------------------------------------------------------------------
void SetAlarmTime(byte n)
{
  EEPROM.write(n * 3 + ALARMS_ADDR + 1, string_buffer[8]);
  EEPROM.write(n * 3 + ALARMS_ADDR + 2, string_buffer[9]);
  Serial.print(F("Set time alarm "));
  Serial.print(n, DEC);
  Serial.print(F(" ("));
  Serial.print(daysOfTheWeek[n]);
  Serial.print(F("): "));
  Serial.print(string_buffer[3]);
  Serial.print(string_buffer[4]);
  Serial.print(":");
  Serial.print(string_buffer[5]);
  Serial.print(string_buffer[6]);
  if(string_buffer[7])
  {
    EEPROM.write(n * 3 + ALARMS_ADDR, string_buffer[7] == '+' ? 1 : 0);
    Serial.print(string_buffer[7] == '+' ? F(" on") : F(" off"));
  }
  Serial.println();
}
//----------------------------------------------------------------------------------------------
// Настройка через Serial
//----------------------------------------------------------------------------------------------
void SerialSetup()
{
  while(Serial.available()) Serial.read();
  Serial.setTimeout(50);
  Serial.println(F("Thhmmss - set time (e.g. T094500 = 09:45:00)"));
  Serial.println(F("DddMMyy - set date (e.g. D050418 = 05 Apr 2018)"));
  Serial.println(F("Ax=hhmmz - set alarm, x = 0(or 7) - 6 (Sun/Boc - Sat/Cy6) or * (all alarms)"));
  Serial.println(F("\t   z = + (on), - (off) or nothing (state does not change)"));
  Serial.println(F("\t   (e.g. A*=0700+ = set alarm on for 07:00 every day of the week)"));
  Serial.println(F("Ax+\t- alarm on"));
  Serial.println(F("Ax-\t- alarm off"));
  Serial.println(F("A\t- list of alarm clocks"));
  Serial.println(F("Mn=ddMMtext - save message (n = 0 - 9, A - F; text max 44 characters)"));
  Serial.println(F("\t  (e.g. M0=2509Happy Birthday!)"));
  Serial.println(F("Mn-\t- delete message"));
  Serial.println(F("Mn\t- show message"));
  Serial.println(F("M\t- list of messages"));
  Serial.println(F("OK\n"));
  StringToSprite("\xA9<\xAD>\xB5");
  Show(1);
  while(digitalRead(OK_PIN) && digitalRead(UP_PIN) && digitalRead(DOWN_PIN))
  {
    if(Serial.available())
    {
      delay(20);
      string_buffer[Serial.readBytes(string_buffer, sizeof(string_buffer) - 1)] = 0;
      delay(20);
      while(Serial.available()) { Serial.read(); delay(5); }
      Serial.println(string_buffer);
      boolean OK = 1;
      bitClear(string_buffer[0], 5);
      switch(string_buffer[0])
      {
        case 'T':
        {
          string_buffer[7] = (string_buffer[1] - '0') * 10 + (string_buffer[2] - '0');
          string_buffer[8] = (string_buffer[3] - '0') * 10 + (string_buffer[4] - '0');
          string_buffer[9] = (string_buffer[5] - '0') * 10 + (string_buffer[6] - '0');
          if(byte(string_buffer[7]) < 24 && byte(string_buffer[8]) < 60 && byte(string_buffer[9]) < 60)
          {
            NOW = RTC.now();
            RTC.adjust(DateTime(NOW.year(), NOW.month(), NOW.day(), string_buffer[7], string_buffer[8], string_buffer[9]));
            Serial.print(F("Set time: "));
            Serial.print(string_buffer[1]);
            Serial.print(string_buffer[2]);
            Serial.print(":");
            Serial.print(string_buffer[3]);
            Serial.print(string_buffer[4]);
            Serial.print(":");
            Serial.print(string_buffer[5]);
            Serial.println(string_buffer[6]);
          }
          else OK = 0;
          break;
        }
        case 'D':
        {
          string_buffer[7] = (string_buffer[1] - '0') * 10 + (string_buffer[2] - '0');
          string_buffer[8] = (string_buffer[3] - '0') * 10 + (string_buffer[4] - '0');
          string_buffer[9] = (string_buffer[5] - '0') * 10 + (string_buffer[6] - '0');
          if(string_buffer[7] > 0 && !(string_buffer[7] > 31 || (string_buffer[7] > 30 && (string_buffer[8] == 4 || string_buffer[8] == 6 || string_buffer[8] == 9 || string_buffer[8] == 11)) || (string_buffer[8] == 2 && ((string_buffer[7] > 28 && (string_buffer[9] % 4)) || (string_buffer[7] > 29 && !(string_buffer[9] % 4))))) && string_buffer[8] > 0 && string_buffer[8] < 13 && byte(string_buffer[9]) < 100)
          {
            NOW = RTC.now();
            RTC.adjust(DateTime(2000 + string_buffer[9], string_buffer[8], string_buffer[7], NOW.hour(), NOW.minute(), NOW.second()));
            Serial.print(F("Set date: "));
            Serial.print(string_buffer[1]);
            Serial.print(string_buffer[2]);
            Serial.print(".");
            Serial.print(string_buffer[3]);
            Serial.print(string_buffer[4]);
            Serial.print(F(".20"));
            Serial.print(string_buffer[5]);
            Serial.println(string_buffer[6]);
          }
          else OK = 0;
          break;
        }
        case 'A':
        {
          if(string_buffer[1] == 0)
            for(byte i = 1; i < 8; i++)
            {
              Serial.print(F("Alarm "));
              Serial.print(i, DEC);
              Serial.print(F(" ("));
              Serial.print(daysOfTheWeek[i % 7]);
              Serial.print(F("): "));
              string_buffer[2] = EEPROM.read(i % 7 * 3 + ALARMS_ADDR + 1);
              string_buffer[3] = EEPROM.read(i % 7 * 3 + ALARMS_ADDR + 2);
              if(byte(string_buffer[2]) < 24 && byte(string_buffer[3]) < 60)
              {
                if(string_buffer[2] < 10) Serial.print("0");
                Serial.print(string_buffer[2], DEC);
                Serial.print(":");
                if(string_buffer[3] < 10) Serial.print("0");
                Serial.print(string_buffer[3], DEC);
                Serial.println(EEPROM.read(i % 7 * 3 + ALARMS_ADDR) == 1 ? F(" on") : F(" off"));
              }
              else Serial.println(F("not set"));
            }
          else
          {
            string_buffer[1] = string_buffer[1] - '0';
            if(string_buffer[1] == 7) string_buffer[1] = 0;
            if(byte(string_buffer[1]) < 7 || string_buffer[1] == -6)
            {
              if(string_buffer[2] == '=')
              {
                string_buffer[8] = (string_buffer[3] - '0') * 10 + (string_buffer[4] - '0');
                string_buffer[9] = (string_buffer[5] - '0') * 10 + (string_buffer[6] - '0');
                if(byte(string_buffer[8]) < 24 && byte(string_buffer[9]) < 60 && (string_buffer[7] == '+' || string_buffer[7] == '-' || string_buffer[7] == 0))
                {
                  if(string_buffer[1] == -6) for(byte i = 0; i < 7; i++) SetAlarmTime(i);
                  else SetAlarmTime(string_buffer[1]);
                }
                else OK = 0;
              }
              else
              {
                if(string_buffer[2] == '+' || string_buffer[2] == '-')
                {
                  if(string_buffer[1] == -6)
                  {
                    for(byte i = 0; i < 7; i++) EEPROM.write(i * 3 + ALARMS_ADDR, string_buffer[2] == '+' ? 1 : 0);
                    Serial.print(F("All alarms "));
                  }
                  else
                  {
                    EEPROM.write(string_buffer[1] * 3 + ALARMS_ADDR, string_buffer[2] == '+' ? 1 : 0);
                    Serial.print(F("Alarm "));
                    Serial.print(string_buffer[1], DEC);
                    Serial.print(F(" ("));
                    Serial.print(daysOfTheWeek[string_buffer[1]]);
                    Serial.print(F(") "));
                  }
                  Serial.println(string_buffer[2] == '+' ? F("on") : F("off"));
                }
                else OK = 0;
              }
            }
            else OK = 0;
          }
          break;
        }
        case 'M':
        {
          if(string_buffer[1] == 0)
          {
            for(char i = 0; i < 16; i++)
            {
              Serial.print(F("Message "));
              Serial.print(i < 10 ? char(i + '0') : char(i - 10 + 'A'));
              Serial.print(F(": "));
              string_buffer[2] = EEPROM.read(i * 46 + MESSAGES_ADDR);
              string_buffer[3] = EEPROM.read(i * 46 + MESSAGES_ADDR + 1);
              if(string_buffer[2] > 0 && string_buffer[2] < 32 && string_buffer[3] > 0 && string_buffer[3] < 13)
              {
                if(string_buffer[2] < 10) Serial.print("0");
                Serial.print(string_buffer[2], DEC);
                Serial.print(".");
                if(string_buffer[3] < 10) Serial.print("0");
                Serial.print(string_buffer[3], DEC);
                Serial.print(" ");
                for(byte c = 0; c < 44; c++)
                {
                  char read_char = EEPROM.read(i * 46 + MESSAGES_ADDR + 2 + c);
                  if(read_char != 0) Serial.print(read_char);
                  else break;
                }
                Serial.println();
              }
              else Serial.println(F("not set"));
            }
          }
          else
          {
            if(string_buffer[1] >= 'a') bitClear(string_buffer[1], 5);
            if((string_buffer[1] >= '0' && string_buffer[1] <= '9') || (string_buffer[1] >= 'A' && string_buffer[1] <= 'F'))
            {
              string_buffer[1] = string_buffer[1] > '9' ? string_buffer[1] - 'A' + 10 : string_buffer[1] - '0';
              if(string_buffer[2] == '=')
              {
                string_buffer[0] = (string_buffer[3] - '0') * 10 + (string_buffer[4] - '0');
                string_buffer[2] = (string_buffer[5] - '0') * 10 + (string_buffer[6] - '0');
                if(string_buffer[0] > 0 && string_buffer[0] < 32 && string_buffer[2] > 0 && string_buffer[2] < 13 && string_buffer[7])
                {
                  EEPROM.write(string_buffer[1] * 46 + MESSAGES_ADDR, string_buffer[0]);
                  EEPROM.write(string_buffer[1] * 46 + MESSAGES_ADDR + 1, string_buffer[2]);
                  for(byte c = 0; c < 44; c++)
                  {
                    EEPROM.write(string_buffer[1] * 46 + MESSAGES_ADDR + 2 + c, string_buffer[c + 7]);
                    if(string_buffer[c + 7] == 0) break;
                  }
                  Serial.print(F("Message "));
                  Serial.print(string_buffer[1] < 10 ? char(string_buffer[1] + '0') : char(string_buffer[1] - 10 + 'A'));
                  Serial.println(F(" saved"));
                }
                else OK = 0;
              }
              else
              {
                if(string_buffer[2] == 0)
                {
                  if(EEPROM.read(string_buffer[1] * 46 + MESSAGES_ADDR) > 0 && EEPROM.read(string_buffer[1] * 46 + MESSAGES_ADDR) < 32 && EEPROM.read(string_buffer[1] * 46 + MESSAGES_ADDR + 1) > 0 && EEPROM.read(string_buffer[1] * 46 + MESSAGES_ADDR + 1) < 13)
                  {
                    string_buffer[sizeof(string_buffer) - 1] = string_buffer[1];
                    for(byte c = 0; c < 44; c++)
                    {
                      string_buffer[c] = EEPROM.read(string_buffer[sizeof(string_buffer) - 1] * 46 + MESSAGES_ADDR + 2 + c);
                      if(string_buffer[c] == 0) break;
                    }
                    StringToSprite(string_buffer);
                    ScrollBufferToScreen(1);
                    StringToSprite("\xA9<\xAD>\xB5");
                    Show(1);
                  }
                  else
                  {
                    Serial.print(F("Message "));
                    Serial.print(string_buffer[1] < 10 ? char(string_buffer[1] + '0') : char(string_buffer[1] - 10 + 'A'));
                    Serial.println(F(" not set"));
                  }
                }
                else
                {
                  if(string_buffer[2] == '-')
                  {
                    EEPROM.write(string_buffer[1] * 46 + MESSAGES_ADDR, 0);
                    Serial.print(F("Message "));
                    Serial.print(string_buffer[1] < 10 ? char(string_buffer[1] + '0') : char(string_buffer[1] - 10 + 'A'));
                    Serial.println(F(" deleted"));
                  }
                  else OK = 0;
                }
              }
            }
            else OK = 0;
          }
          break;
        }
        default: OK = 0;
      }
      Serial.println(OK ? F("OK\n") : F("Error\n"));
    }
  }
  ButtonPressed();
}
//==============================================================================================
void setup()
{
  Wire.begin();
  RTC.begin();
  Dallas.begin();
  dallasOK = Dallas.getDeviceCount();
  bme.begin();
    Serial.begin(57600);
  for(byte i = 0; i < 4; i++)
  {
    lc.shutdown(i, false);
    lc.clearDisplay(i);
  }
  SetBrightness();
  pinMode(DOWN_PIN, INPUT);
  pinMode(UP_PIN, INPUT);
  pinMode(OK_PIN, INPUT);
  pinMode(BEEPER_PIN, OUTPUT);
  #ifdef DS1307
    if(!RTC.isrunning())
    {
      SetupTime(); SetupDate();
    }
  #else
    if(RTC.lostPower())
    {
      SetupTime(); SetupDate();
    }
  #endif
  if(dallasOK)
  {
    Dallas.requestTemperatures();
    t_min = Dallas.getTempCByIndex(0);
    t_max = t_min;
    NOW = RTC.now();
    t_min_h = NOW.hour();
    t_min_m = NOW.minute();
    t_max_h = t_min_h;
    t_max_m = t_min_m;
  }
  
    int i = 0;
    while(i < 256)
    {
      if(EEPROM.read(i) == 255)
      {
        pressure_addr = i;
        break;
      }
      i++;
    }
}
//==============================================================================================
void loop()
{
  unsigned long current_millis = millis();
  DisplayTime(5, 0);
  boolean flag_blink = 1;
  if(!beeper) beeper = AlarmNotActive();
  unsigned long time_previous_millis = millis();
  while((millis() - time_previous_millis < TIME_SHOW_MSEC && digitalRead(UP_PIN) && digitalRead(DOWN_PIN)) || (beeper && !AlarmNotActive()))
  {
    SetBrightness();
    if((millis() - time_previous_millis) % 2000 < 1000)
    {
      if(!flag_blink)
      {
        lc.setRow(1, 7, B01100110);
        lc.setRow(2, 0, B01100110);
        flag_blink = 1;
        if(!AlarmNotActive() && beeper) digitalWrite(BEEPER_PIN, 1);
      }
    }
    else
    {
      if(flag_blink)
      {
        lc.setRow(1, 7, 0);
        lc.setRow(2, 0, 0);
        screen_buffer[15] = 0;
        screen_buffer[16] = 0;
        flag_blink = 0;
        NOW = RTC.now();
        if(MINUTE_OLD != NOW.minute())
          DisplayTime(1 + (MINUTE_OLD / 10 != NOW.minute() / 10) + (HOUR_OLD % 10 != NOW.hour() % 10) + (HOUR_OLD / 10 != NOW.hour() / 10), 1);
        digitalWrite(BEEPER_PIN, 0);
      }
    }

    if(beeper && (!digitalRead(OK_PIN) || !digitalRead(UP_PIN) || !digitalRead(DOWN_PIN)) && !AlarmNotActive())
    {
      beeper = 0;
      ButtonPressed();
    }

// +++++ Настройки +++++
    if(!digitalRead(OK_PIN))
    {
      ButtonPressed();
      StringToSprite("\xA9\xAC\xA7\xB5");
      ScrollVertical(0, 31, 1);
      for(byte i = 0; i < 32; i++) screen_buffer[i] = 0;
      byte pos_menu = 0;
      while(pos_menu < 4)
      {
        Blink(pos_menu);
        if(!digitalRead(UP_PIN))
        {
          ButtonPressed();
          pos_menu++;
        }
        if(!digitalRead(DOWN_PIN))
        {
          ButtonPressed();
          pos_menu--;
        }
        if(!digitalRead(OK_PIN))
        {
          ButtonPressed();
          SetBrightness();
          switch(pos_menu)
          {
            case 0: { SetupTime(); break; }
            case 1: { SetupDate(); break; }
            case 2: { SetupAlarms(); break; }
            case 3: { SerialSetup(); break; }
          }
        }
        StringToSprite("\xA9\xAC\xA7\xB5");
        Show(1);
      }
      SetBrightness();
      DisplayTime(5, 0);
      time_previous_millis = millis();
    }
  }
  digitalWrite(BEEPER_PIN, 0);

// +++++ Дата +++++
  if(AlarmNotActive() || !beeper)
  {
    StringToSprite("  ", 28);
    ScrollVertical(0, 31, 0);
    strcpy_P(string_buffer, (char*)pgm_read_word(&(dow[NOW.dayOfTheWeek()])));
    StringToSprite(string_buffer);
    ScrollBufferToScreen(0);
    StringToSprite("  ", 0);
    ScrollBufferToScreen(0);
    if(NOW.day() > 9)
    {
      string_buffer[0] = NOW.day() / 10 + '0';
      string_buffer[1] = NOW.day() % 10 + '0';
      string_buffer[2] = '\0';
    }
    else
    {
      string_buffer[0] = NOW.day() + '0';
      string_buffer[1] = '\0';
    }
    StringToSprite(string_buffer);
    ScrollBufferToScreen(0);
    StringToSprite(" \xA0");
    ScrollBufferToScreen(0);
    strcpy_P(string_buffer, (char*)pgm_read_word(&(months[NOW.month() - 1])));
    StringToSprite(string_buffer);
    ScrollBufferToScreen(0);
    string_buffer[0] = ' ';
    string_buffer[1] = '2';
    string_buffer[2] = '0';
    string_buffer[3] = (NOW.year() - 2000) / 10 + '0';
    string_buffer[4] = (NOW.year() - 2000) % 10 + '0';
    string_buffer[5] = '\0';
    StringToSprite(string_buffer);
    ScrollBufferToScreen(0);
    for (byte i = 0; i <= strlen_P(yyyy); i++)
      string_buffer[i] = pgm_read_byte_near(yyyy + i);
    StringToSprite(string_buffer);
    ScrollBufferToScreen(1);
  }

// +++++ Вывод сообщений +++++
  if(AlarmNotActive() || !beeper)
  {
    // отсчет до НГ
    if(NOW.month() == 12 && NOW.day() > 20)
    {
      DateTime NY (NOW.year() + 1, 1, 1, 0, 0, 0);
      unsigned long difference = NY.unixtime() - NOW.unixtime();
      byte difference_hour = difference / 3600;
      byte difference_min = difference / 60 % 60;
      byte difference_sec = difference % 60;
      if(NOW.day() == 31)
      {
        if(!difference_hour && !difference_min) ShowCountdown(difference_sec, 9);
        else
        {
          if(difference_hour)
          {
            ShowCountdown(difference_hour, 3);
            if(difference_min)
            {
              StringToSprite(" \xA0\xA0");
              ScrollBufferToScreen(0);
            }
          }
          if(difference_min) ShowCountdown(difference_min, 6);
        }
      }
      else ShowCountdown(31 - NOW.day(), 0);
      for (byte i = 0; i <= strlen_P(message_UNY); i++)
        string_buffer[i] = pgm_read_byte_near(message_UNY + i);
      StringToSprite(string_buffer);
      ScrollBufferToScreen(1);
    }
    // С Новым годом!
    if(NOW.day() == 1 && NOW.month() == 1)
    {
      for (byte i = 0; i <= strlen_P(message_HNY); i++)
        string_buffer[i] = pgm_read_byte_near(message_HNY + i);
      StringToSprite(string_buffer);
      ScrollBufferToScreen(1);
    }
    // Пользовательские сообщения из EEPROM
    boolean mes = 0;
    for(byte i = 0; i < 16 && AlarmNotActive(); i++)
    {
      if(NOW.day() == EEPROM.read(i * 46 + MESSAGES_ADDR) && NOW.month() == EEPROM.read(i * 46 + MESSAGES_ADDR + 1))
      {
        mes = 1;
        for(byte c = 0; c < 44; c++)
        {
          string_buffer[c] = EEPROM.read(i * 46 + MESSAGES_ADDR + 2 + c);
          if(string_buffer[c] == 0) break;
        }
        StringToSprite(string_buffer);
        ScrollBufferToScreen(0);
        StringToSprite(" \xA0\xA0");
        ScrollBufferToScreen(0);
      }
    }
    if(mes)
    {
      StringToSprite(" ", 0);
      ScrollBufferToScreen(1);
    }
  }

// +++++ Уличная температура +++++
  if(AlarmNotActive() || !beeper)
  {
    StringToSprite("\xA0\xA6\xA0\xBB");
    for(byte i = 0; i < 8; i++)
    {
      ScrollVerticalOneRow(0, 6, 1);
      ScrollVerticalOneRow(7, 31, 0);
      Show(0);
    }
    if(dallasOK)
    {
      Dallas.requestTemperatures();
      float temperature = Dallas.getTempCByIndex(0);
      TempToString(temperature);
      StringToSprite(string_buffer);
      SpriteToCenterScreen();
      delay(1000);
      if(NOW.hour() < t_min_h || NOW.hour() < t_max_h)
      {
        t_min = temperature;
        t_max = temperature;
        t_min_h = NOW.hour();
        t_min_m = NOW.minute();
        t_max_h = t_min_h;
        t_max_m = t_min_m;
      }
      else
      {
        if(t_min > temperature)
        {
          t_min = temperature;
          t_min_h = NOW.hour();
          t_min_m = NOW.minute();
        }
        if(t_max < temperature)
        {
          t_max = temperature;
          t_max_h = NOW.hour();
          t_max_m = NOW.minute();
        }
      }
      Tminmax(0);
      Tminmax(1);
    }
    else
    {
      delay(700);
      StringToSprite("???°C");
      SpriteToCenterScreen();
      delay(1000);
    }
  }
// +++++ Температура в помещении +++++
    
      StringToSprite("\xA0\xA6\xA0\xA0\xAB");
      for(byte i = 0; i < 8; i++)
      {
        ScrollVerticalOneRow(0, 6, 0);
        ScrollVerticalOneRow(7, 31, 1);
        Show(0);
      }
      delay(300);
      TempToString(bme.readTemperature());
      StringToSprite(string_buffer);
      SpriteToCenterScreen();
      delay(1000);
    

// +++++ Влажность в помещении +++++
    
      HumToString(bme.readHumidity());
      StringToSprite(string_buffer);
      for(byte i = 0; i < 8; i++)
      {
        ScrollVerticalOneRow(0, 6, 1);
        ScrollVerticalOneRow(7, 31, 0);
        Show(0);
      }
      delay(1500);
    
// +++++ Давление +++++
      static unsigned long pressure_previous_millis;
    if(current_millis - pressure_previous_millis >= PRESSURE_UPDATE_MSEC)
    {
      pressure_previous_millis = current_millis;
      EEPROM.write(pressure_addr++, (0.51 + bme.readPressure() * 760.0 / 101325.0) - PRESSURE_VALUE_SHIFT);
      EEPROM.write(pressure_addr, 255);
    }
    StringToSprite("\xAE\xA0mmHg");
    ScrollVertical(0, 31, 0);
    delay(500);
    PresToString(0.51 + bme.readPressure() * 760.0 / 101325.0);
    StringToSprite(string_buffer);
    ScrollVertical(9, 31, 1);
    delay(1000);
    float scale = 1.0;    // масштаб графика (для большого разброса)
    byte shift = 0;       // сдвиг к середине (для малого разброса)
    byte value = EEPROM.read(byte(pressure_addr - 1));
    byte pressure_min = value;
    byte pressure_max = value;
    for(byte i = 2; i < 24; i++)
    {
      value = EEPROM.read(byte(pressure_addr - i));
      if(pressure_min > value) pressure_min = value;
      if(pressure_max < value && value != 255) pressure_max = value;
    }
    byte pressure_range = pressure_max - pressure_min + 1;
    if(pressure_range > 8)
    {
      scale = 8.0 / pressure_range;
      shift = 0;
    }
    else
    {
      scale = 1.0;
      shift = 4 - (pressure_range / 2);
    }
    for(byte i = 1; i < 24; i++)
    {
      value = EEPROM.read(byte(pressure_addr - i));
      if(value != 255)
      {
        byte bar = scale * (value - pressure_min);
        sprite_buffer[32 - i] = 255 << (7 - bar - shift);
      }
      else sprite_buffer[32 - i] = i % 2 ? B00010000 : 0;
    }
    ScrollVertical(9, 31, 1);
    for(byte i = 0; i < 32; i++) sprite_buffer[i] = 0;
    delay(1200);

// +++++ Проверка батарейки RTC +++++
  if(AlarmNotActive() || !beeper)
  {
    analogReference(INTERNAL); for(byte i = 0; i < 4; i++) { analogRead(BATT_PIN); delay(20); }
    float Vbatt = analogRead(BATT_PIN) * Vref * voltage_divider_coefficient / 1023.0;
    analogReference(DEFAULT); for(byte i = 0; i < 4; i++) { analogRead(SENSOR_PIN); delay(20); }
    if(Vbatt <= 2.3 || !digitalRead(OK_PIN) || !digitalRead(UP_PIN) || !digitalRead(DOWN_PIN))
    {
      for(byte i = 0; i < 4; i++) string_buffer[i] = ' ';
      string_buffer[4] = '\xA0'; string_buffer[5] = '\xA0';
      byte v = Vbatt * 10;
      string_buffer[6] = v / 10 + '0';
      string_buffer[7] = ',';
      string_buffer[8] = v % 10 + '0';
      string_buffer[9] = 'V';
      string_buffer[10] = '\0';
      StringToSprite(string_buffer);
      sprite_buffer[1] = B00111110;
      for(byte i = 2; i < 10; i++) sprite_buffer[i] = B00100010;
      sprite_buffer[10] = B00110110;
      sprite_buffer[11] = B00011100;
      ScrollVertical(0, 31, 1);
      time_previous_millis = millis();
      while(millis() - time_previous_millis < 6000)
      {
        if(Vbatt > 2.3)
          for(byte i = 2; i <= map(constrain(v, 23, 36), 23, 36, 3, 10); i++)
            lc.setRow(i / 8, i % 8, B00111110);
        else
        {
          if((millis() % 2000 < 1000) == flag_blink)
          {
            lc.setRow(0, 2, flag_blink ? B00100010 : B00111110);
            lc.setRow(0, 3, flag_blink ? B00100010 : B00111110);
            flag_blink = !flag_blink;
          }
        }
        SetBrightness();
      }
    }
    ScrollVertical(0, 31, 0);
  }
}
//==============================================================================================

 

yucan
Offline
Зарегистрирован: 20.04.2015

Сам датчик с использованной вами библиотекой работает?

b707
Offline
Зарегистрирован: 26.05.2017

Xer0x_king пишет:

Кто может помочь? Выкинул sht, пытаюсь втиснуть bme280, в результате везде нули... ЧЯДНТ? куда копать?

Вы офигели? Думаете кто-то станет разбираться, что вы там изменили в 1400 строчках?

Кто ж сразу новое оборудование в огромную программу пихает? Надо было для начала написать проверочный скетч для датчика, короткий, строк на 15-20. Убедится, что все работает. Или, если не работает - искать ошибку.. Или спросить на форуме -со скетчем в 15 строк и форум бы выстрее помог.

 

Xer0x_king
Offline
Зарегистрирован: 22.06.2017

Блин.Я не фигел.
Одним подавай кусок кода, другим подавай весь код.Всем не угодишь. Кому то лень написать что то не унизительное?
И да, еси чо, я проверял датчик, загружал скетч. Татчик опрашивается и показывает значения в мониторе последовательного порта. Или вы думаете, что настолько руки обьектно ориентированы?

Xer0x_king
Offline
Зарегистрирован: 22.06.2017

Конечно работает. Проверял.Подозреваю, что ошибка кроется где то в внесении данных в буфер и выводе их на экран.

vk007
Offline
Зарегистрирован: 16.06.2015

Xer0x_king пишет:
Кто может помочь? Выкинул sht, пытаюсь втиснуть bme280, в результате везде нули... ЧЯДНТ? куда копать?

Дайте ссылку на используемую вами библиотеку Adafruit_BME280.

Xer0x_king
Offline
Зарегистрирован: 22.06.2017

Всем спасибо за помощь. Скетч рабочий. Единственное, что изменил: указал  адрес инициализации bme - bme.begin (0x76). Заменил ардуино и скомпилировал на другой машине.
Можно использовать в качестве замены при использовани bme280 вместо dht и sht.

AleksandrM
Offline
Зарегистрирован: 20.07.2017

Подскажите для  BMP180(280) питния 5 вольт не много? По даташиту у них максимум 4,25 вольта....

 

Xer0x_king
Offline
Зарегистрирован: 22.06.2017

Питание для БМП/бме идеально 3.3 вольта. Если юзаем нано-берем с ноги стабилизатора,если про мини-с амс117-3.3