Общие сведения
Часы "NeoPixel" - проект, разработанный на основе NeoPixel-кольца, Piranha UNO, а также модулей I2C: энкодер, датчик света, RTC-модуль, и I2C Hub. Вы можете приобрести комплект для сборки, или собрать часы из имеющихся у вас модулей. Также вы можете купить только корпус для часов.
Часы показывают время с помощью разноцветных светодиодов, также есть возможность настроить таймер на время до 1 часа. После заданного времени раздастся звуковой сигнал, а таймер начнёт отсчитывать время, пройденное с момента срабатывания.
Яркость часов адаптивна окружающей освещенности, а минимальная и максимальная яркость настраивается с помощью энкодера, и остаётся в энергонезависимой памяти устройства.
Модуль реального времени позволяет сохранять установленное время даже при отключении основного питания часов.
Видео
Редактируется...
В готовый набор входят:
- 1х Piranha Uno R3
- 1х NeoPixel кольцо 60 светодиодов (уже спаянное, не потребует наличия паяльника)
- 1х Энкодер, потенциометр, FLASH-I2C (Trema-модуль)
- 1х Датчик освещенности, люксметр, FLASH-I2C (Trema-модуль)
- 1х Часы реального времени, RTC (Trema-модуль v2.0)
- 1х Зуммер (Trema-модуль)
- 1х i2C Hub (Trema-модуль)
- 1х Trema Shield
- 1x 4-проводной шлейф «мама-мама» 20 см
- 1х ПВХ-конструктор (корпус часов)
- 1x Стойка М3*6 Nylon-black, 4 штуки
- 7x Винт М3*6 Nylon-black, 4 штуки
- 1x Нитки (для крепления NeoPixel-кольца);
- 1x Кабель USB 2.0 (A-B)
- 1х Колпачок для энкодера
- 1х Крестовая отвертка
- 1х Шпаргалка по программированию в Arduino IDE
- 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 | Подключение |
3 | NeoPixel-кольцо |
5 | Зуммер |
I2C | I2C 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. Установите опоры для возможности использования часов в качестве настольных;
Обсуждение