Общие сведения
Часы "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. Установите опоры для возможности использования часов в качестве настольных;

Обсуждение