КОРЗИНА
магазина
8 (499) 500-14-56 | ПН. - ПТ. 12:00-18:00
ЛЕСНОРЯДСКИЙ ПЕРЕУЛОК, 18С2, БЦ "ДМ-ПРЕСС"

Пиксельные часы с таймером

Общие сведения

Часы "NeoPixel" - проект, разработанный на основе NeoPixel-кольца, Piranha UNO, а также модулей I2C: энкодер, датчик света, RTC-модуль, и I2C Hub. Вы можете приобрести комплект для сборки, или собрать часы из имеющихся у вас модулей. Также вы можете купить только корпус для часов.

Часы показывают время с помощью разноцветных светодиодов, также есть возможность настроить таймер на время до 1 часа. После заданного времени раздастся звуковой сигнал, а таймер начнёт отсчитывать время, пройденное с момента срабатывания. 

Яркость часов адаптивна окружающей освещенности, а минимальная и максимальная яркость настраивается с помощью энкодера, и остаётся в энергонезависимой памяти устройства.

Модуль реального времени позволяет сохранять установленное время даже при отключении основного питания часов. 

Видео

Редактируется...

В готовый набор входят:

    Библиотеки:

    При необходимости, ознакомьтесь с нашей инструкцией по установке библиотек в Arduino IDE.

    Установка адресов энкодера и датчика освещенности

    Необходимо установить адрес датчику освещенности 0х0А, а энкодеру - 0х0В. Это можно сделать двумя способами:

    1. Используя установщик адресов I2C

    Об установке адресов при помощи установщика адресов можно почитать на странице установщика адресов

    2. Используя Piranha UNO и Trema Shield

    Подключим датчик освещенности, загрузим скетч, откроем монитор порта, и установим адрес  в десятичной системе счисления (для данного проекта адрес датчика освещенности в десятичной системе счисления - 10). 

    Далее, то же самое проделаем с энкодером (адрес - 11), предварительно поменяв название объекта в директиве define (7 строка).

    Для удобства подключения используем Trema Shield.


    Скетч установки адресов

    #include <Wire.h>                     // Подключаем библиотеку для работы с аппаратной шиной I2C, до подключения библиотек iarduino_I2C_Encoder и iarduino_I2C_DSL.
    #include <iarduino_I2C_Encoder.h>     //  Подключаем библиотеку для работы с энкодером
    #include <iarduino_I2C_DSL.h>         //  Подключаем библиотеку для работы с датчиков освещенности
    
    iarduino_I2C_DSL lightSensor;         //  Создаём объекты для работы с методами библиотеки 
    iarduino_I2C_Encoder enc;             //  Создаём объекты для работы с методами библиотеки 
    
    #define module lightSensor     // lightSensor  или enc  
    
    // Функция очистки буфера последовательного порта
    void DiscardSerial()
    {
        while(Serial.available())
            Serial.read();
    }
    
    void setup()
    {
        // Инициируем последовательный порт.
        Serial.begin(9600);
        while(!Serial){;}
        delay(500);
    
        // Проверяем наличие модуля
        if (!module.begin()) {
            Serial.println("Модуль не найден, проверьте подключение.");
            return;
        }
    
        // Устанавливаем адрес
        while(true) {
            Serial.print("Текущий адрес в десятеричной системе: ");
            Serial.println(module.getAddress());
            Serial.println(
                    "Введите новый адрес и нажмите "
                    "<enter> или \"Отправить\""
                    );
    
            DiscardSerial();
    
            while(!Serial.available());
    
            String newAddress = Serial.readStringUntil('\n');
            newAddress.trim();
            uint8_t newAddr = newAddress.toInt();
    
            if (module.changeAddress(newAddr)) {
                Serial.println("Адрес изменён.");
            }
            else {
                Serial.println(
                        "Адрес не изменён, "
                        "проверьте подключение "
                        "и нажмите \"RES\""
                        );
                break;
            }
        }
    }
    
    void loop()
    {
        delay(1);
    }

    Подключение

    Мы предполагаем, что NeoPixel-кольцо у вас уже собрано (если вы используете готовый набор, или уже спаяли самостоятельно). Если нет, сперва соберите его по этой инструкции, а затем вернитесь к данному проекту. 

    Установите Trema Shield на Piranha Uno 

    Соедините по схеме все компоненты при помощи проводов «мама-мама». В I2C Hub датчики можно подключать к любым номерам колодок. 

    К 3-й колодке контроллера подключено NeoPixel-кольцо, к 5 выводу - зуммер. 

    TREMA SHIELDПодключение
    3NeoPixel-кольцо
    5Зуммер
    I2CI2C Hub
    VCC+5V
    GND-5V (GND)

    Подключаем USB-кабель, часы получают питание от него. Дополнительный источник питания не нужен. 

    Скетч проекта

    #include <Wire.h>                     // Подключаем библиотеку для работы с аппаратной шиной I2C, до подключения библиотек iarduino_I2C_Encoder, iarduino_RTC и iarduino_I2C_DSL.
    #include <iarduino_NeoPixel.h>        //  Подключаем библиотеку для работы с NeoPixel-кольцом
    #include <iarduino_I2C_DSL.h>         //  Подключаем библиотеку для работы с датчиков освещенности
    #include <iarduino_I2C_Encoder.h>     //  Подключаем библиотеку для работы с энкодером
    #include <iarduino_RTC.h>             //  Подключаем библиотеку для работы с модулем реального времени
    #include <EEPROM.h>                   //  Подключаем библиотеку для работы с энергонезависимой памятью
    
    iarduino_I2C_DSL lightSensor;         //  Создаём объекты для работы с методами библиотеки 
    iarduino_I2C_Encoder enc;             //  Создаём объекты для работы с методами библиотеки 
    iarduino_RTC watch(RTC_DS3231);       //  Создаём объекты для работы с методами библиотеки 
    iarduino_NeoPixel led(3, 60);         //  Светодиодное кольцо подключено к 3 пину
    #define zummerPin 5                   //  зуммер подключен к 7 пину
    
    #define INIT_ADDR 1023                // номер резервной ячейки (для EEPROM)
    #define INIT_KEY 50                   // ключ первого запуска. 0-254, на выбор (для EEPROM)
    
    
    /////////////////////////НАСТРОЙКИ//////////////////////////////////////////////////
    int maxLuminance = 45;           //  максимальная яркость (0...255)
    int minLuminance = 4;            //  минимальная яркость (0...255)
    #define nightBorderLux 2         //  нижняя граница освещенности, при которой включится "ночной режим", Люкс
    #define dayBorderLux 400         //  верхняя граница освещенности, соответствующая полной яркости свечения светодиодов, Люкс
    #define K_BACK 0.2               //  коэффициент, определяющий яркость свечения заднего фона относительно цифр (0...1)
    #define blinkPeriod 300          //  период мигания светодиода (время горения, мс)
    uint8_t backMode = 3;            //  установка цвета фона. 0-3
    #define arr_size 60              //  число светодиодов в кольце
    #define timeAfterClick 60000     //  время ожидания после последнего нажатия энкодера, мс.
    #define blinkTime 300            //  время полупериода мигания параметра в режиме настройки
    #define currentLimit 45          //  ограничение тока потребления: предел яркости, больше которой выставить её невозможно (0...255) При максимальной яркости (255) кольцо будет потрбелять около 2А
    const float average = 5;         // Определяем константу усреднения показаний датчика освещенности (чем выше значение, тем выше инерционность выводимых показаний).
    ////////////////////////////////////////////////////////////////////////////////////
    
    uint8_t menuMode = 0;               //  режим: 0-отображение времени. 1-оторажение таймера. 
                                        //  2-настройка времени. 3-настройка таймера
    uint8_t luminance=1;                  //  яркость, рассчитанная  на основании уровня освещенности
    uint8_t realLuminance;              //  яркость, рассчитываемая в функции яркости
    int lightValue;                     //  уровень освещенности, полученный с датчика, Люкс
    int8_t h,m,s;                       //  переменные хранения часов, минут, секунд
    int8_t mt=1, st=0;                  //  переменные хранения минут и секунд таймера
    long lastKlickTime;                 //  время последнего щелчка
    uint8_t blinkMode = 1;              //  режим для мигания параметрами (чамы, минуты секунды)
    uint32_t lastChengeBlinkTime;       //  время последней смены состояния параметра (для мигания в режиме настройки)
    bool blinkState = true;             //  состояние параметра для мигания
    uint32_t steps;                     //  число "шагов" таймера - по сути количество секунд
    int32_t stepTime;                   //  время каждого шага таймера, мс
    bool endTimerFlag = false;          //  флаг окончания таймера
    uint8_t currentStep = 0;            //  текущий шаг при заполнении кольца во время таймера
    int8_t minutesAfterEnd = 0;         //  минуты, после завершения отсчёта таймера
    int8_t secondsAfterEnd = 0;         //  секунды, после завершения отсчёта таймера
    uint32_t timeEndTimer;              //  время, когда завершился отсчёт таймера
    bool startZummerFlag = false;       //  флаг, показывающий что зуммер может включаться
    uint8_t zummerCount = 0;            //  число звуков, которые уже издал зуммер
    uint32_t timeZummerStart;           //  время запуска зуммера
    uint32_t startTimerTime;            //  время запуска таймера
    bool noMemuFlag = true;             //  флаг, что мы находимся не в меню настроек
    bool zummerLuminFlag = true;        //  флаг работы зуммера в режиме настройки уровней освещения
    uint32_t timeInterrupt = 0;         //  время для обращения к функции определения освещенности 
    
    void setup() {
      Serial.begin(9600);
      lightSensor.begin();                       // Инициируем модули
      enc.begin();                               //  -//-
      watch.begin();                             //  -//-
      led.begin();                               //  -//-
    
      lightSensor.changeAddress(0x0A);
      enc.changeAddress(0x0B);
          
      if (EEPROM.read(INIT_ADDR) != INIT_KEY) {     // первый запуск
        EEPROM.write(INIT_ADDR, INIT_KEY);          // записываем ключ
        EEPROM.put(0, minLuminance);                // записываем стандартное значение яркости
        EEPROM.put(2, maxLuminance);                // -//-
      }
      EEPROM.get(0, minLuminance);                  //  читаем значение яркости
      EEPROM.get(2, maxLuminance);                  // -//-
    }
    
    void loop() {
      lumin();                         //  обращается к функции, получаем текущие значения освещённости
      luminance *= average-1;                      // Умножаем предыдущее значение яркости на коэффициент усреднения-1
      luminance += realLuminance;                  // Добавляем к полученному значению новые показания яркости
      luminance /= average;                        // Делим полученное значение на коэффициент усреднения
    
      timeUpdate();                    //  обращаемся к функции, обновляем значения времени
      
      switch (menuMode){               //  переход в режим в соответствии с menuMode
        case 1:                      
          timerUpdate();               //  отображение таймера
        break;
        case 2: 
          clockSetArrUpdate();         //  настройка времени
        break;
        case 3: 
          timerSetArrUpdate();         //  настройка таймера
        break;
        case 4: 
          setLuminance();              //  настройка яркости
        break;
        default:                       //  по умолчанию (или menuMode = 0):
          clockArrUpdate();            //  обновление времени
        break;
      }
      
    uint32_t encTime = enc.getButton(KEY_TIME_PRESSED);      //  получаем время удержания энкодера и записываем в переменную
    
    if (noMemuFlag && takeEncoder()>0){                      //  если были не в меню и кнопка энкодера нажата
        
        mt=1;
        menuMode = 3;                                        //  переходим в режим настройки таймера
        noMemuFlag = false;                                  //  меняем флаг "в меню"
        blinkMode = 1;                                       //  устанавливаем режим мигания - час
        
      }
      if (encTime > 1000 && encTime < 3000){                 //  если время от 1 до 3 секунд
        lastKlickTime = millis();                            //  запоминаем время предыдущего клика
        noMemuFlag = false;                                  //  меняем флаг "в меню"
        menuMode = 2;                                        //  переходим в режим настройки времени
        blinkMode = 1;                                       //  устанавливаем режим мигания - час
      }
      if (encTime > 3000){                                   //  если время больше 3с.
        menuMode = 4;                                        //  переходим в режим настройки таймера
        noMemuFlag = false;                                  //  меняем флаг "в меню"
        blinkMode = 1;                                       //  устанавливаем режим мигания - 1
        takeEncoder();                                       //  сбрасываем значения энкодера
      }
    }
    
    void setLuminance(){                                                        //  функция установки яркости
      if (blinkMode == 1){                                                      //  если режим 1
        if (zummerLuminFlag){                                                   //  если впервые зашли в режим настройки яркости
          zummerLuminFlag = false;                                              //  меняем флаг, чтобы больше не заходить
          tone(zummerPin, 500, 500);                                            //  выводим звуковой сигнал с частотой 500 Гц и длительностью 0,5 сек
        }
        minLuminance = correctLuminance(minLuminance+(2*(int)takeEncoder()));   //  устанавливаем минимальную яркость
        if (minLuminance > maxLuminance) minLuminance = maxLuminance;           //  если минимальная яркость больше максимальной, не даём ей больше расти
        luminance = minLuminance;                                               //  при этом на циферблат выводим вычисляемую сейчас яркость, чтобы сразу её видеть
      }
      if (blinkMode == 2){                                                      //  если режим 2
        if (zummerLuminFlag){                                                   //  если впервые зашли в режим настройки яркости
          zummerLuminFlag = false;                                              //  меняем флаг, чтобы больше не заходить
          tone(zummerPin, 3000, 500);                                           //  выводим звуковой сигнал с частотой 3000 Гц и длительностью 0,5 сек
        }
        maxLuminance = correctLuminance(maxLuminance+(2*(int)takeEncoder()));   //  устанавливаем максимальную яркость
        if (maxLuminance > currentLimit) maxLuminance = currentLimit; 
        if (maxLuminance < minLuminance) maxLuminance = minLuminance;           //  если максимальная яркость меньше минимальной, не даём ей уменьшаться
        luminance = maxLuminance;                                               //  при этом на циферблат выводим вычисляемую сейчас яркость, чтобы сразу её видеть
      }
      if (enc.getButton(KEY_PUSHED)){             //  если было нажатие энкодера
        lastKlickTime = millis();                 //  запоминаем время предыдущего клика
          blinkMode += 1;                         //  увеличиваем режим
          if (blinkMode > 2){                     //  если режим больше 2
            blinkMode = 1;                        //  сбрасываем
            menuMode = 0;                         //  переходим в меню
            noMemuFlag = true;                    //  меняем влаг "в меню"
          }
          zummerLuminFlag = true;                 //  сбрасываем флаг первого вхождения в настройку яркости
          EEPROM.put(0, minLuminance);            //  записываем в энергонезависимую память вычисленные значения яркости
          EEPROM.put(2, maxLuminance);            // -//-
        }
    
      if (lastKlickTime+timeAfterClick < millis()){         //  если предыдущий клик (или оборот энкодера) был не очень поздно (меньше заданного времени timeAfterClick)  
        menuMode = 0;                                       //  переходим в режим хода часов
        noMemuFlag = true;                                  //  меняем влаг "в меню"
        blinkMode = 1;                                      //  сбрасываем
      }
        clockArrUpdate();                                   //  обновляем часы
    }
    
    int correctLuminance(int param){                        //  функция корректировки яркости
      if (param <= 0) return 0;                             //  если переданный параметр меньше 0 возвращаем 0
      if (param >= 255) return 255;                         //  если переданный параметр больше 255 возвращаем 255
      return param;                                         //  иначе возвращаем исходный параметр
    }
    
    void timeUpdate(){                                       //  функция обновления времени
      uint8_t lastH = h;                                     //  запоминаем старый час (необходим для включению режима радуги)
      watch.gettime();                                       //  чтение времени
      h = watch.hours;                                       //  получаем текущие часы        1-12
      m = watch.minutes;                                     //  получаем текущие минуты      0-59
      s = watch.seconds;                                     //  получаем текущие секунды     0-59
    }
    
    void clockArrUpdate(){                                   //  обновление времени
      long clockArr[arr_size] = {};                          //  задание массива для хранения цвета каждого светодиода
      for (uint8_t i = 0; i < arr_size; i++){                //  для всех светодиодов кольца
        clockArr[i] = background(backMode);                  //  запись фона в i-й элемент массива времени
      } 
      clockArr[correctValue((5*h)-1)] = clockArr[correctValue(5*h)] =  clockArr[correctValue(5*h)+1] = getColor(luminance, 0, 0);  //  запись в массив времени в светодиод часа и +- один от него красного цвета
      clockArr[correctValue(m)] = clockArr[correctValue(m-1)] = getColor(0, luminance, 0);   //  запись в массив времени в светодиод минут и - один от него зеленого цвета
      clockArr[correctValue(s)] = getColor(0, 0, luminance);   //  запись в массив времени в светодиод секунд синего цвета
      arrPrint(clockArr);
    }
    
    void clockSetArrUpdate(){                                //  настройка времени
      long clockSetArr[arr_size] = {};                       //  задание массива для хранения цвета каждого светодиода
      for (uint8_t i = 0; i < arr_size; i++){                //  для всех светодиодов кольца
        clockSetArr[i] = background(backMode);               //  запись фона в i-й элемент массива установки времени
      }
      clockSetArr[correctValue((5*h)-1)] = clockSetArr[correctValue(5*h)] =  clockSetArr[correctValue(5*h)+1] = getColor(luminance, 0, 0);  //  запись в массив установки времени в светодиод часа и +- один от него красного цвета
      clockSetArr[correctValue(m)] = clockSetArr[correctValue(m-1)] = getColor(0, luminance, 0);   //  запись в массив установки времени в светодиод минут и - один от него зеленого цвета
      clockSetArr[correctValue(s)] = getColor(0, 0, luminance);   //  запись в массив установки времени в светодиод секунд синего цвета
      
      if(enc.getButton(KEY_PUSHED)){                      //  если кнопка энкодера была нажата
        blinkMode += 1;                                   //  режим мигания параметром увеличиваем на 1
        lastKlickTime = millis();                         //  запоминаем время предыдущего клика
        if (blinkMode > 3) {                              //  если режим мигания больше 3
          menuMode = 0;                                   //  переходим в режим хода часов
          blinkMode = 1;                                  //  сбрасываем
          noMemuFlag = true;                              //  меняем влаг "в меню"
          blinkState = true;                              //  меняем состояние параметра для мигания
        }  
      }
        if (blinkMode == 1) h += takeEncoder();           //  если режим мигания равен 1 (мигаем часом), прибавляем число шагов энкодера
        if(h > 12) h=1;                                   //  при переполнении значения часа, корректируем
        if (h < 1) h=12;
        if (blinkMode == 2) m += takeEncoder();           //  если режим мигания равен 2 (мигаем минутами), прибавляем число шагов энкодера
        if(m > 59) m=0;                                   //  при переполнении значения минут, корректируем
        if (m < 0) m=59;
        if (blinkMode == 3) s=0;                          //  если режим мигания равен 3 (мигаем секундами), прибавляем число шагов энкодера
        watch.settime(s,m,h);                             //  записываем время в модуль RTC
        
      if (lastKlickTime+timeAfterClick > millis()){       //  если предыдущий клик (или оборот энкодера) был не очень поздно (меньше заданного времени timeAfterClick)
        if (lastChengeBlinkTime+blinkTime < millis()){    //  если пришло время поменять состояние мигающего параметра
          lastChengeBlinkTime = millis();                 //  обновляем время смены состояния мигающего параметра
          blinkState = !blinkState;                       //  инвертируем состояние
        }
        if (blinkMode == 1){                                                                                                                                    //  если мигаем часом
          if (blinkState) clockSetArr[correctValue((5*h)-1)] = clockSetArr[correctValue(5*h)] =  clockSetArr[correctValue(5*h)+1] = getColor(luminance, 0, 0);  //  если состояние blinkState = true, зажигаем светодиоды часа
          else clockSetArr[correctValue((5*h)-1)] = clockSetArr[correctValue(5*h)] =  clockSetArr[correctValue(5*h)+1] = background(backMode);                  //  иначе, гасим (зажигаем фоном)
        }
        if (blinkMode == 2){                                                                                           //  если мигаем минутами
          if (blinkState) clockSetArr[correctValue(m)] = clockSetArr[correctValue(m-1)] = getColor(0, luminance, 0);   //  если состояние blinkState = true, зажигаем светодиоды минут
          else clockSetArr[correctValue(m)] = clockSetArr[correctValue(m-1)] = background(backMode);                   //  иначе, гасим (зажигаем фоном)
        }
        if (blinkMode == 3){                                                             //  если мигаем секундами
          if (blinkState) clockSetArr[correctValue(s)] = getColor(0, 0, luminance);      //  если состояние blinkState = true, зажигаем светодиод секунд
          else clockSetArr[correctValue(s)] = background(backMode);                      //  иначе, гасим (зажигаем фоном)
        }
      }
      else {
        menuMode = 0;          //  иначе, преходим в режим хода часов
        noMemuFlag = true;     //  флаг "не в меню"
        blinkMode = 1;         //  сбрасываем режим мигания
      }
      arrPrint(clockSetArr);   //  выводм (обновляем) часы
    }
    
    void timerSetArrUpdate(){                          //  установка таймера
      long timerSetArr[arr_size] = {};                 //  задание массива для хранения цвета каждого светодиода
      for (uint8_t i = 0; i < arr_size; i++){          //  для всех светодиодов кольца
        timerSetArr[i] = background(backMode+1);       //  запись фона в i-й элемент массива установки таймера
      }
      for (uint8_t i=1; i <= mt; i++){                 //  от 1 светодиода до текущего значения минут
        timerSetArr[i] = getColor(0, luminance, 0);    //  заливаем светодиод зеленым цветом
      }
      
      if (blinkMode == 1) {                            //  если устанавливаем минуты
        int8_t encSteps = takeEncoder();
        mt += encSteps;                                //  к минутам прибавляем число шагов энкодера
        for (uint8_t i=0; i < round(fabs((float)encSteps)); i++){
          tone(zummerPin, 1500, 10);                   //  выводим звуковой сигнал с частотой 2048 Гц и длительностью 10 мс
          delay (30);
        }
      }
        if (mt > 60) mt = 60;                          //  корректировка значений
        if (mt < 0) mt = 0;                            //  -//-
      
      if(lastKlickTime + 3000 < millis()){             //  если прошло больше 3-х секунд бездействия
        lastKlickTime = millis();                      //  запоминаем время предыдущего клика
          menuMode = 1;                                //  переходим в режим отображения таймера
          noMemuFlag = false;                          //  сбрасываем флаг "не в меню"
          tone(zummerPin, 1500, 300);                  //  выводим звуковой сигнал с частотой 2048 Гц и длительностью 0,3 сек
          
      }  
      stepTime = round((((int32_t)mt*60)+(int32_t)st)*1000/arr_size);   //  вычисляем время одного шага для заливки кольца белым к концу отсчёта таймера
      endTimerFlag = false;                                 //  сбрасываем флаг окончания таймера
      currentStep = 0;                                      //  сбрасываем текущее положение таймера
      startTimerTime = millis();                            //  записываем время начала отсчёта таймера
    
      if (lastKlickTime+timeAfterClick > millis()){         //  если предыдущий клик (или оборот энкодера) был не очень поздно (меньше заданного времени timeAfterClick)  
        for (uint8_t i=1; i <= mt; i++){                    //  от 1 светодиода до текущего значения минут
          timerSetArr[correctValue(i)] = getColor(0, luminance, 0); //  зажигаем светодиоды зеленым
        }
      }
      else {
        menuMode = 0;         //  иначе переходим в режим хода часов
        noMemuFlag = true;    //  сбрасываем флаг "не меню"
        blinkMode = 1;        //  сбрасываем режим мигания
      }
      if (mt == 0){
        menuMode = 0;         //  иначе переходим в режим хода часов
        noMemuFlag = true;    //  сбрасываем флаг "не меню"
        blinkMode = 1;        //  сбрасываем режим мигания
      }
      arrPrint(timerSetArr);  //  выводим на светодиоды массив цветов установки таймера
      enc.getButton(KEY_PUSHED);                                 //  сбрасываем значения хранеия нажатия кнопки
    }
    
    void timerUpdate(){                                          // отображение таймера
      long timerArr[arr_size] = {};                              //  задание массива для хранения цвета каждого светодиода
      uint8_t colorRed = round(map(currentStep, 0, 60, 0, luminance*K_BACK));        //перевод числа шагов steps в диапазон цвета 0...200
      uint8_t colorGreen = round(map(currentStep, 0, 60, (luminance*K_BACK)/2, 0));  //перевод числа шагов steps в диапазон цвета 0...100
    
      if (endTimerFlag) {                                        //  если таймер дошел до конца
        if (lastChengeBlinkTime+blinkTime < millis()){           //  если пришло время поменять состояние мигающего параметра
          lastChengeBlinkTime = millis();                        //  обновляем время смены состояния мигающего параметра
          blinkState = !blinkState;                              //  инвертируем состояние
        }
        if (blinkState){                                         //  если состояние blinkState = true
          for (uint8_t i = 0; i < arr_size; i++){                //  гасим все светодиоды кольца
            timerArr[i] = getColor(0, 0, 0);              
          }
        }
        else { 
         for (uint8_t i = 0; i < arr_size; i++){                 //  иначе зажигаем красным все светодиоды кольца
            timerArr[i] = getColor(luminance*K_BACK, 0, 0);
          }
        }
        if (millis() - timeEndTimer >= 60000) {                  //  если прошла минута с момента окончания отсчёта таймера
          minutesAfterEnd+=1;                                    //  увеличиваем значение прошедших минут на 1
          timeEndTimer = millis();                               //  сбрасываем время окончания таймера
          startZummerFlag = true;                                //  активируем флаг зуммера
        }
        if (minutesAfterEnd > 60) {                              //  если минут больше 60
          menuMode = 0;                                          //  переходим в режим хода часов
          noMemuFlag = true;                                     //  флаг "не меню"
        }                                                        
        for (uint8_t i=1; i <= minutesAfterEnd; i++){            //  зажигаем зеленым минуты после истечения таймера
          timerArr[correctValue(i)] = getColor(0, luminance, 0);
        }
      }
      else{                                                                         //  иначе
       if (stepTime*(long)currentStep < millis()-startTimerTime){                   //  если пришло время зажечь новый светодиод при заполнении таймера
          currentStep += 1;                                                         //  увеличиваем текущий шаг на 1
          if (currentStep > arr_size){                                              //  если заполнено всё кольцо
            currentStep = 0;                                                        //  обнуляем текущий шаг
            endTimerFlag = true;                                                    //  устанавливаем флаг окончания таймера
            timeEndTimer = millis();                                                //  устанавливаем время окончания отсчёта таймера
            startZummerFlag = true;                                                 //  устанавливаем флаг зуммера
            timeZummerStart = millis();                                             //  устанавливаем время начала работы зуммера
            minutesAfterEnd = 0;                                                    //  обнуляем значения минут, прошедших с начала работы таймера
            secondsAfterEnd = 0;                                                    //  обнуляем значения секунд, прошедших с начала работы таймера
          }
        }
        for (uint8_t i = 0; i < arr_size; i++){                                     //  каждый светодиод кольца
          timerArr[i] = getColor(colorRed, colorGreen, 0);                          //  задаём цвет
        }
        for (uint8_t i = 1; i < currentStep; i++){                                  //  от 0 светодиода до текущего
          timerArr[correctValue(i)] = getColor(luminance, luminance, luminance);    //  задаём белый цвет
        }
      }
    
       if(enc.getButton(KEY_PUSHED) || (enc.getEncoder() != 0)){   //  Если нажат или повернут вал энкодера
        currentStep = 0;                                           //  обнуляем значение текущего шага
        endTimerFlag = false;                                      //  сбрасываем флаг окончания таймера
        menuMode = 0;                                              //  устанавливаем режим меню (отображение часов)
        noMemuFlag = true;                                         //  флаг "не меню"
        startZummerFlag = false;                                   //  сбрасываем флаг работы зуммера
        lastKlickTime = millis();                                  //  запоминаем время предыдущего клика
       }
    
       if (startZummerFlag){                                       //  если флаг зуммера true
        if (timeZummerStart + 200 < millis()){                     //  если пришло время включения очередного тона
          tone(zummerPin, 2048, 100);                              //  выводим звуковой сигнал с частотой 2048 Гц и длительностью 0,1 сек
          zummerCount++;                                           //  увеличиваем количество воспроизведенных тонов
          timeZummerStart = millis();                              //  обновляем время старта зуммера
          if (zummerCount > 10){                                   //  если воспроизведено больше 20 тонов
            zummerCount = 0;                                       //  обнуляем их количество
            startZummerFlag = false;                               //  сбрасываем флаг активности зуммера
          }
        }
       }
       arrPrint(timerArr);                                         //  выводим массив цветов на кольцо
    }
    
    int8_t takeEncoder(){                                          //  тело функции takeEncoder()
      int8_t value = enc.getEncoder();                             //  получаем значение с энкодера (число шагов)
      if (value != 0) lastKlickTime = millis();                    //  если оно не равно 0, обновляем время чтения этого значения (необходимо, чтобы не выходить из  настойки времени, если мы крутим энкодер)
      return value;                                                //  возвращаем это значение
    }
    
    int8_t correctValue(int8_t param){                             //  тело функции correctValue() - корректировка параметров
        if (param > 59) return 0;                                  //  если переданный параметр (часы/минуты) больше 59, возвращаем 0
        else if (param < 0) return 59;                             //  если меньше 0, возвращаем 59
        else return param;                                         //  иначе, возвращаем без изменений
    }
    
    void arrPrint(long *arr){                                      //  функция вывода значений массива на кольцо
      for (uint8_t i = 0; i < arr_size; i++){                      //  для каждого элемента массива
        led.setColor(i, arr[i]);                                   //  записываем в i-й светодиод цвет, указанный в i-м месте массива
      }
      led.write();                                                 //  зажигаем
    }
    
    long background(uint8_t backMode){                                                           //  функция заливки фона
      if (backMode > 4) backMode = 0;                                                            //  проверяем режим фона на корректность
      switch (backMode) {                                                                        
        case 0: return getColor(luminance*K_BACK, luminance*K_BACK, 0);                          //  в зависимости от режима, вызываем функцию получения цвета с заданными параметрами
        break;                                                                                           
        case 1: return getColor(0, luminance*K_BACK, luminance*K_BACK-0.5*luminance*K_BACK);     //  -//-
        break;                                                                                           
        case 2: return getColor(luminance*K_BACK, 0, luminance*K_BACK);                          //  -//-
        break;                                                                                            
        case 3: return getColor(luminance*K_BACK, luminance*K_BACK, luminance*K_BACK);           //  -//-
        break;
        case 4: return getColor(0, 0, luminance*K_BACK);                                         //  -//-
        break;
      }
    }
    
    long getColor(uint8_t r, uint8_t g, uint8_t b){           //  функция преобразования цвета из десятичной системы в шестнадцатеричную       
      String Ost, ResR, ResG, ResB;                           
      String String16 = "0123456789ABCDEF";                   
      Ost = String16[r%16];
      r = floor(r/16);
      ResR = String16[r];
      ResR += Ost;
      
      Ost = String16[g%16];
      g = floor(g/16);
      ResG = String16[g];
      ResG += Ost;
      
      Ost = String16[b%16];
      b = floor(b/16);
      ResB = String16[b];
      ResB += Ost;
      String StringResult = "0x"+ResR+ResG+ResB;
      long resLong = 0;
      sscanf(StringResult.substring(2,4).c_str(), "%x", (int)&resLong+2);
      sscanf(StringResult.substring(4,8).c_str(), "%x", (int)&resLong);
      return resLong;
    }
    
    void lumin(){
      lightValue = lightSensor.getLux();                                                           //  получение освещенности с датчика 
      if (lightValue <= nightBorderLux) {                                                          //  если освещенность превышает границу ночного режима освещенности
        realLuminance = minLuminance;                                                              //  яркость светодиодов равна минимальной
      }
      else if (lightValue > nightBorderLux && lightValue < dayBorderLux){                          //  иначе, если освещенность находится в диапазоне между нижней и верхней границей освещенности
        realLuminance = map(lightValue, nightBorderLux, dayBorderLux, minLuminance, maxLuminance); //  вычисляем яркость светодиодов в зависимости от уровня освещения
      }
      else if (lightValue >= dayBorderLux) {                                                       //  иначе, если освещенность больше максимальной границы
        realLuminance = maxLuminance;                                                              //  яркость светодиодов равна максимальной
      }
    }

    Инструкция по работе

    Настройка времени

      При удержании кнопки энкодера от 1-й до 3-х секунд, произойдет переход в режим настройки времени. Начнут мигать светодиоды, показывающие час. Вращением ручки энкодера можно настроить время в часах. Клик по кнопке энкодера переведёт в режим установки минут, здесь время настраивается аналогично. Еще один клик переведет в режим установки секунд, при этом светодиод, отвечающий за время в секундах, установится на 0 и будет мигать, ожидая следующего нажатия, с помощью которого новое установленное время запускается и секундная "стрелка" начинает движение с 0. Таким образом можно настроить время с пуском устройства точно в нужный момент. 

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

    Настройка таймера

    При повороте ручки энкодера произойдет переход в режим настройки таймера. Вращением ручки можно настроить время в минутах. Через три секунды после последнего поворота, прозвучит звуковой сигнал, таймер запустится, а светодиодное кольцо начнёт заполняться белым цветом, при этом фон кольца будет меняться от зеленого к красному по мере приближения отсчёта к концу.

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

    Через 60 минут после окончания таймера, произойдет автоматический переход в режим часов.   

    Настройка яркости

    Яркость часов изменяется автоматически в зависимости от освещённости окружающей среды. Однако, минимальная и максимальная яркость, между которыми будет происходить автоматическое регулирование, может быть настроена вручную. 

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

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

    Прочее

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

    Сборка корпуса

    1. Подготовьте все элементы;

    2. Установите указатели цифр в углубления;

    3. Закрепите цифры с помощью винтов;

    4. Установите NeoPixel-кольцо: закрепите кольцо ниткой, проденьте её концы через отверстия, завяжите на специальных держателях. После того, как все держатели будут установлены, вращая их, можно увеличить натяжение нитей;

    5. Установите энкодер;

    6. Закрепите контроллер Piranha UNO на стойках;

    7. Соберите настенное крепление;

    8. Установите датчики на площадки согласно рисунку;

    9. Установите Trema Shield;

    10. Установите площадки с датчиками на основу;

    11. Соедините компоненты согласно схемы (см.часть "Подключение");

    12. Установите опоры для возможности использования часов в качестве настольных;

      Ссылки




      Обсуждение

      Гарантии и возврат Используя сайт Вы соглашаетесь с условями