====== Часы Nixie Clock: инструкция, примеры использования и документация ====== Используйте [[amp>product/nixie-clock?utm_source=proj&utm_campaign=nixie&utm_medium=wiki | часы Nixie Clock]] в качестве стильного гаджета на своём столе дома, мастерской или на работе. Часы не просто показывают текущее время, а делает это максимально зрелищно благодаря прозрачному циферблату, который имитирует знаменитые газоразрядные индикаторы Nixie Tube. Только вместо олдскульных газоразрядных ламп наподобие ИН-12, ИН-14, ИН-18 — современная управляемая LED-подсветка на WS2812B. {{ :nixie:nixie-clock.1.jpg?nolink |}} ===== Что такое Nixie Clock? ===== В конце 90-х было модно собирать часы на газоразрядных лампах Nixie Tube моделей ИН-12, ИН-14, ИН-18.{{ :nixie:nixie-clock-info.1.jpg?nolink |}} Мы решили дать возможность каждому собрать модные олдскульные часы. Однако вместо дефицитных газоразрядных ламп используем акриловые пластины с выгравированными цифрами, которые подсветим адресными светодиодами WS2812B.{{ :nixie:nixie-clock-info.2.jpg?nolink |}} Каждый разряд часов состоит из десяти вырезанных лазерным резаком прямоугольников из прозрачного акрила.{{ :nixie:nixie-clock-info.3.png?nolink&250 |}} На каждом прямоугольнике выполнена гравировка цифры от 0 до 9.{{ :nixie:nixie-clock-info.4.png?nolink&550 |}} Если посветить в торец прямоугольника, то выгравированная цифра начнет светиться цветом, которым светит светодиод. Собрав десять таких цифр в блок и подсвечивая каждую пластинку акрила отдельным светодиодом можно по-отдельности зажигать каждую цифру. Поскольку свет входит в акрил с края, происходит полное внутреннее отражение и акрил ведет себя как световод. {{ :nixie:nixie-clock-info.5.gif?nolink&200 |}} С теорией разобрались, вперёд собирать стильные часы. ===== Как собрать? ===== ==== Комплектация ==== {{ :nixie:nixie-clock-parts.png?nolink |}} ==== Шаг 1 ==== Удалите защитную плёнку с всех пластиковых деталей в наборе. Детали покрыты защитной плёнкой с двух сторон. Плёнку можно легко снять поддев её на краю детали используя острый инструмент. ==== Шаг 2 ==== Вставьте батарейку типоразмера CR1225 в батарейный отсек платы Nixie Clock. Батарейка идет в комплекте с часами. В часах Nixie Clock батарейка используется для хранения пользовательских настроек цвета, анимации и отсчета времени при отключении часов от питания. {{ :nixie:nixie-clock-build.2.png?nolink |}} ==== Шаг 3 ==== Установите пластины ''А'', ''B'' и ''С'' снизу платы Nixie Clock, используя пять винтов M3×25. {{ :nixie:nixie-clock-build.3.png?nolink |}} ==== Шаг 4 ==== Вставьте секундный рассеиватель в пластину ''Е''. Установите пластины ''E'' и ''D'' сверху платы Nixie Clock и закрепите их пятью алюминиевыми стойками. {{ :nixie:nixie-clock-build.4.png?nolink |}} Не затягивайте винты слишком сильно! Сильно затянутые винты создадут трение между пластинами ''А'', ''В'' и ''С'' что будет препятствовать нормальному нажатию кнопок. ==== Шаг 5 ==== Через пластину F установите в часы четыре набора цифр. Один набор цифр состоит из 10 пластиковых деталей с цифрами от 0 до 9. Цифры устанавливаются по возрастанию. В каждом разряде цифра 0 находится ближе всего к лицевой стороне часов. {{ :nixie:nixie-clock-build.5.png?nolink |}} Для удобства установки цифр, пластину ''F'' можно временно закрепить винтами к алюминиевым стойкам. ==== Шаг 6 ==== Установите сверху часов пластину ''G'' и закрепите её пятью винтами М3×10. {{ :nixie:nixie-clock-build.6.png?nolink |}} ==== Шаг 7 ==== Вуаля часы собраны, остаётся только подать питания. Подключите 5 В к часам через разъём Micro-USB. В качестве источника питания подойдёт USB-порт компьютера или наш сетевой адаптер 5 В. {{ :nixie:nixie-clock-build.7.png?nolink |}} Вот и всё, часы тикают и радуют ваш взгляд. Если стандартной работы часов вам мало и чувствуете что можете улучшить гаджет, [[#раздел_разработчика|переходите в раздел разботчика]]. ===== Раздел разработчика ===== По умолчанию на Nixie Clock установлена заводская прошивка. Но вы можете запрограммировать гаджет по-своему. - Подключите Nixie Clock к компьютеру - [[amp>product/page/arduino-ide?utm_source=wiki&utm_campaign=nixie&utm_medium=wiki |Установите и настройте Arduino IDE]]. Часы выполнены на микроконтроллере ATmega328P с зашитим загрузчиком от Arduino Uno. Однако на плате Nixie Clock распаяна дополнительная переферия: часы реального времени, кнопки и светодиоды. Особенности распиновки вы найдёте ниже. И не бойтесь экспеременитировать, штатный код часов так же приложен ниже. ==== Распиновка платы Nixie Clock ==== Для создания собственной прошивки часов вам понадобится информация о задействованных пинах микроконтроллера ATmega328P. {{ :nixie:nixie-clock-pinout.png?nolink |}} ==== Прошивка платы ==== Часы Nixie Clock поставляются прошитыми, однако при желании вы можете изменить или дополнить оригинальную прошивку. === Необходимые библиотеки === В прошивке платы Nixie Clock используются следующие Arduino библиотеки: * [[https://github.com/adafruit/Adafruit_NeoPixel|Adafruit NeoPixel Library]] - библиотека для управления с адресными светодиодами WS2812B. * [[https://github.com/amperka/TroykaButton|TroykaButton]] - библиотека для работы с кнопками. * [[https://github.com/amperka/TroykaRTC|TroykaRTC]] - библиотека для работы с часами реального времени DS1307. Убедитесь что эти библиотеки установлены в ваше рабочее пространство Arduino IDE. === Штатный код часов === #include #include #include #include // Uncomment the line below to source time from an MCU circuit rather than RTC. // Use one having best precision in hardware. // #define TIME_SOURCE_MCU // Pin definition constexpr uint8_t BUTTON_MODE_PIN = A3; constexpr uint8_t BUTTON_OK_PIN = A2; constexpr uint8_t BUTTON_UP_PIN = A1; constexpr uint8_t BUTTON_DOWN_PIN = A0; constexpr uint8_t WS2812_DOT_PIN = 9; constexpr uint8_t WS2812_MATRIX_PIN = 2; // Class objects Adafruit_NeoPixel dot = Adafruit_NeoPixel(1, WS2812_DOT_PIN, NEO_GRB + NEO_KHZ800); Adafruit_NeoPixel matrix = Adafruit_NeoPixel(40, WS2812_MATRIX_PIN, NEO_GRB + NEO_KHZ800); RTC clock; TroykaButton button_mode(BUTTON_MODE_PIN, 1000, true, 200); TroykaButton button_ok(BUTTON_OK_PIN, 1000, true, 200); TroykaButton button_up(BUTTON_UP_PIN, 1000, true, 200); TroykaButton button_down(BUTTON_DOWN_PIN, 1000, true, 200); // Time variables uint8_t current_hour = 0; uint8_t current_minute = 0; uint8_t target_hour = 0; uint8_t target_minute = 0; #ifdef TIME_SOURCE_MCU volatile uint16_t isr_counter = 0; // Used for clock source from AVR hardware timer volatile uint8_t isr_current_hour = 0; // Used for clock source from AVR hardware timer volatile uint8_t isr_current_minute = 0; // Used for clock source from AVR hardware timer #endif // LED variables uint32_t now_time = 0; uint32_t digit_color[4] = { 0, 0, 0, 0 }; uint32_t digit_previous_color[4] = { 0, 0, 0, 0 }; uint8_t digit_current_led[4] = { 255, 255, 255, 255 }; uint8_t digit_previous_led[4] = { 255, 255, 255, 255 }; uint8_t digit_old_led[4] = { 255, 255, 255, 255 }; bool dot_blinking_state = false; #ifndef TIME_SOURCE_MCU uint32_t dot_blinking_last_time = 0; #endif constexpr uint16_t MATRIX_BLINKING_INTERVAL_MS = 250; uint32_t matrix_blinking_last_time = 0; bool matrix_blinking_state = false; // Modes variables and definitions enum WorkingMode { WORKING_MODE_CLOCK = 1, WORKING_MODE_SET_TIME_HOURS = 2, WORKING_MODE_SET_TIME_MINUTES = 3, WORKING_MODE_MENU_1 = 4, WORKING_MODE_MENU_2 = 5, WORKING_MODE_MENU_3 = 6, }; uint8_t current_working_mode = WORKING_MODE_CLOCK; bool new_working_mode = false; uint8_t current_setting_1_value = 0; uint8_t current_setting_2_value = 0; uint8_t current_setting_3_value = 0; uint8_t target_setting_1_value = 0; uint8_t target_setting_2_value = 0; uint8_t target_setting_3_value = 0; constexpr uint8_t TOTAL_SETTING_1_VALUES = 17; constexpr uint8_t TOTAL_SETTING_2_VALUES = 4; constexpr uint8_t TOTAL_SETTING_3_VALUES = 3; // Color variables int16_t delta_hue[4] = { 0, 0, 0, 0 }; uint8_t digit_brightness[4] = { 255, 255, 255, 255 }; uint8_t digit_previous_brightness[4] = { 255, 255, 255, 255 }; // Animation variables enum ColorAnimation { COLOR_ANIMATION_NONE = 0, COLOR_ANIMATION_PENDULUM = 1, COLOR_ANIMATION_WAVE_PENDULUM = 2, COLOR_ANIMATION_DEEPNESS = 3, }; constexpr float COLOR_ANIMATION_RATE_MS = 12.0 / 60000.0; // Changes in ms constexpr float COLOR_ANIMATION_PHASE_SHIFT = 0.125; uint32_t previous_color_animation_time = 0; float color_animation_timer = 0; enum DigitAnimation { DIGIT_ANIMATION_NONE = 0, DIGIT_ANIMATION_RUNNING_LED = 1, DIGIT_ANIMATION_FADE = 2, }; bool digit_animation_is_preview = false; uint32_t previous_digit_animation_fade_preview_time = 0; uint32_t previous_digit_animation_running_led_preview_time = 0; constexpr uint32_t DIGIT_ANIMATION_RUNNING_LED_PREVIEW_TIME_MS = 4000; constexpr uint32_t DIGIT_ANIMATION_FADE_PREVIEW_TIME_MS = 6500; uint8_t running_led[4] = { 255, 255, 255, 255 }; constexpr uint32_t DIGIT_ANIMATION_RUNNING_LED_RATE_MS = 30; uint32_t previous_digit_animation_running_led_time = 0; constexpr float FADE_ANIMATION_RATE_MS = 3000.0; // 3 sec uint32_t previous_fade_animation_time[4] = { 0, 0, 0, 0 }; float fade_animation_timer[4] = { 0, 0, 0, 0 }; // Auxiliary uint16_t degree_hue_to_uint16_hue(int16_t degree) { return (float)((degree % 360) / 360.0 * 65535.0); } // CLOCK void read_time() { clock.read(); current_hour = clock.getHour(); current_minute = clock.getMinute(); #ifdef TIME_SOURCE_MCU set_volatile_data(); #endif } void read_ram_data() { uint8_t data = clock.getRAMData(0x0A); // Menu 1 setting current_setting_1_value = ((data > 0) && (data < TOTAL_SETTING_1_VALUES)) ? data : 0; data = clock.getRAMData(0x0B); // Menu 2 setting current_setting_2_value = ((data > 0) && (data < TOTAL_SETTING_2_VALUES)) ? data : 0; data = clock.getRAMData(0x0C); // Menu 3 setting current_setting_3_value = ((data > 0) && (data < TOTAL_SETTING_3_VALUES)) ? data : 0; } void set_ram_data() { clock.setRAMData(0x0A, current_setting_1_value); // Menu 1 setting clock.setRAMData(0x0B, current_setting_2_value); // Menu 2 setting clock.setRAMData(0x0C, current_setting_3_value); // Menu 3 setting } // MODE button void button_mode_handler() { button_mode.read(); if (button_mode.isClick()) { switch (current_working_mode) { case WORKING_MODE_CLOCK: current_working_mode = WORKING_MODE_SET_TIME_HOURS; break; case WORKING_MODE_SET_TIME_HOURS: clock.setHour(target_hour); clock.setSecond(0); read_time(); current_working_mode = WORKING_MODE_SET_TIME_MINUTES; break; case WORKING_MODE_SET_TIME_MINUTES: clock.setMinute(target_minute); clock.setSecond(0); read_time(); current_working_mode = WORKING_MODE_CLOCK; break; case WORKING_MODE_MENU_1: current_setting_1_value = target_setting_1_value; set_ram_data(); current_working_mode = WORKING_MODE_MENU_2; break; case WORKING_MODE_MENU_2: current_setting_2_value = target_setting_2_value; set_ram_data(); current_working_mode = WORKING_MODE_MENU_3; break; case WORKING_MODE_MENU_3: current_setting_3_value = target_setting_3_value; set_ram_data(); current_working_mode = WORKING_MODE_CLOCK; break; default: break; } new_working_mode = true; } else if (button_mode.isHold()) { switch (current_working_mode) { case WORKING_MODE_CLOCK: current_working_mode = WORKING_MODE_MENU_1; break; default: break; } new_working_mode = true; } } // UP button void button_up_handler() { button_up.read(); if (!button_up.isClickOnHold() && !button_up.justPressed()) return; switch (current_working_mode) { case WORKING_MODE_SET_TIME_HOURS: target_hour = (target_hour + 1) % 24; break; case WORKING_MODE_SET_TIME_MINUTES: target_minute = (target_minute + 1) % 60; break; case WORKING_MODE_MENU_1: target_setting_1_value = (target_setting_1_value + 1) % TOTAL_SETTING_1_VALUES; break; case WORKING_MODE_MENU_2: target_setting_2_value = (target_setting_2_value + 1) % TOTAL_SETTING_2_VALUES; break; case WORKING_MODE_MENU_3: target_setting_3_value = (target_setting_3_value + 1) % TOTAL_SETTING_3_VALUES; break; default: break; } } // DOWN button void button_down_handler() { button_down.read(); if (!button_down.isClickOnHold() && !button_down.justPressed()) return; switch (current_working_mode) { case WORKING_MODE_SET_TIME_HOURS: target_hour = (target_hour - 1 + 24) % 24; break; case WORKING_MODE_SET_TIME_MINUTES: target_minute = (target_minute - 1 + 60) % 60; break; case WORKING_MODE_MENU_1: target_setting_1_value = (target_setting_1_value - 1 + TOTAL_SETTING_1_VALUES) % TOTAL_SETTING_1_VALUES; break; case WORKING_MODE_MENU_2: target_setting_2_value = (target_setting_2_value - 1 + TOTAL_SETTING_2_VALUES) % TOTAL_SETTING_2_VALUES; break; case WORKING_MODE_MENU_3: target_setting_3_value = (target_setting_3_value - 1 + TOTAL_SETTING_3_VALUES) % TOTAL_SETTING_3_VALUES; break; default: break; } } // OK button void button_ok_handler() { button_ok.read(); if (!button_ok.isClick()) return; switch (current_working_mode) { case WORKING_MODE_SET_TIME_HOURS: case WORKING_MODE_SET_TIME_MINUTES: clock.setHour(target_hour); clock.setMinute(target_minute); clock.setSecond(0); read_time(); current_working_mode = WORKING_MODE_CLOCK; break; case WORKING_MODE_MENU_1: case WORKING_MODE_MENU_2: case WORKING_MODE_MENU_3: current_setting_1_value = target_setting_1_value; current_setting_2_value = target_setting_2_value; current_setting_3_value = target_setting_3_value; set_ram_data(); current_working_mode = WORKING_MODE_CLOCK; break; default: break; } new_working_mode = true; } void working_mode_handler() { if (!new_working_mode) return; switch (current_working_mode) { case WORKING_MODE_SET_TIME_HOURS: target_hour = current_hour; target_minute = current_minute; break; case WORKING_MODE_MENU_1: target_setting_1_value = current_setting_1_value; break; case WORKING_MODE_MENU_2: target_setting_2_value = current_setting_2_value; break; case WORKING_MODE_MENU_3: target_setting_3_value = current_setting_3_value; break; default: break; } new_working_mode = false; } uint32_t nth_preset_color(uint8_t current_color_setting, int16_t hue_change = 0, uint8_t brightness = 255) { return (current_color_setting < 16 && current_color_setting > 0) ? Adafruit_NeoPixel::ColorHSV(degree_hue_to_uint16_hue((current_color_setting - 1) * 24 + hue_change), 255, brightness) : 0xFFFFFFFF; } void dot_handler() { uint32_t dot_color; switch (current_working_mode) { case WORKING_MODE_CLOCK: if (dot_blinking_state) { dot_color = nth_preset_color(current_setting_1_value); dot.setPixelColor(0, dot_color); } else { dot.setPixelColor(0, 0); } break; default: dot.setPixelColor(0, 0); break; } dot.show(); } float saw_wave(float x) { float fract = fmod(x, 1); float y = (fract < 0.5) ? (fract / 0.5) : (((fract - 0.5) / -0.5) + 1.0); return y = 2 * y - 1; } void color_animation_handler() { uint32_t dt = now_time - previous_color_animation_time; color_animation_timer = color_animation_timer + (dt * COLOR_ANIMATION_RATE_MS); float a = 0; previous_color_animation_time = now_time; if (target_setting_2_value == COLOR_ANIMATION_PENDULUM || current_setting_2_value == COLOR_ANIMATION_PENDULUM) { a = saw_wave(color_animation_timer); for (uint8_t i = 0; i < 4; i++) delta_hue[i] = 24 * a; } else if (target_setting_2_value == COLOR_ANIMATION_WAVE_PENDULUM || current_setting_2_value == COLOR_ANIMATION_WAVE_PENDULUM) { for (uint8_t i = 0; i < 4; i++) { a = saw_wave(color_animation_timer + (i + 1) * COLOR_ANIMATION_PHASE_SHIFT); delta_hue[i] = 24 * a; } } else if (target_setting_2_value == COLOR_ANIMATION_DEEPNESS || current_setting_2_value == COLOR_ANIMATION_DEEPNESS) { for (uint8_t i = 0; i < 4; i++) delta_hue[i] = (digit_current_led[i] - 10 * i) * 24.0 / 10.0; } else // COLOR_ANIMATION_NONE for (uint8_t i = 0; i < 4; i++) delta_hue[i] = 0; } void animation_running_led_handler() { if (now_time - previous_digit_animation_running_led_time > DIGIT_ANIMATION_RUNNING_LED_RATE_MS) { for (uint8_t i = 0; i < 4; i++) if (running_led[i] > digit_old_led[i]) running_led[i]--; previous_digit_animation_running_led_time = now_time; } for (uint8_t i = 0; i < 4; i++) { if (digit_current_led[i] != digit_old_led[i]) { running_led[i] = 10 * (i + 1) - 1; digit_old_led[i] = digit_current_led[i]; } } // Preview if (digit_animation_is_preview && (now_time - previous_digit_animation_running_led_preview_time > DIGIT_ANIMATION_RUNNING_LED_PREVIEW_TIME_MS)) { for (uint8_t i = 0; i < 4; i++) running_led[i] = 10 * (i + 1) - 1; previous_digit_animation_running_led_preview_time = now_time; } // Show for (uint8_t i = 0; i < 4; i++) digit_current_led[i] = running_led[i]; } float fade_wave(float x) { float fract = fmod(x, 1); return pow(fract == 0 ? 1 : fract, 2); } void animation_fade_handler() { for (uint8_t i = 0; i < 4; i++) { if (digit_current_led[i] != digit_old_led[i]) { digit_previous_led[i] = digit_old_led[i]; if (fade_animation_timer[i] >= 1.0) { fade_animation_timer[i] = 0; } } } for (uint8_t i = 0; i < 4; i++) { if (fade_animation_timer[i] < 1.0) { uint32_t dt = now_time - previous_fade_animation_time[i]; fade_animation_timer[i] = fade_animation_timer[i] + (dt * (1.0 / FADE_ANIMATION_RATE_MS)); } if (fade_animation_timer[i] >= 1.0) { fade_animation_timer[i] = 1.0; digit_old_led[i] = digit_current_led[i]; } previous_fade_animation_time[i] = now_time; } // Preview if (digit_animation_is_preview && (now_time - previous_digit_animation_fade_preview_time > DIGIT_ANIMATION_FADE_PREVIEW_TIME_MS)) { for (uint8_t i = 0; i < 4; i++) { digit_previous_led[i] = 255; fade_animation_timer[i] = 0; } previous_digit_animation_fade_preview_time = now_time; } // Show for (uint8_t i = 0; i < 4; i++) { float a = fade_wave(fade_animation_timer[i]); digit_brightness[i] = 255 * a; digit_previous_brightness[i] = 255 * (1 - a); } } void matrix_handler() { matrix.clear(); memset(digit_brightness, 255, 4 * sizeof(digit_brightness[0])); // Choose digits switch (current_working_mode) { case WORKING_MODE_SET_TIME_HOURS: case WORKING_MODE_SET_TIME_MINUTES: digit_current_led[0] = target_hour / 10; digit_current_led[1] = (target_hour % 10) + 10; digit_current_led[2] = (target_minute / 10) + 20; digit_current_led[3] = (target_minute % 10) + 30; break; case WORKING_MODE_CLOCK: digit_current_led[0] = current_hour / 10; digit_current_led[1] = (current_hour % 10) + 10; digit_current_led[2] = (current_minute / 10) + 20; digit_current_led[3] = (current_minute % 10) + 30; break; case WORKING_MODE_MENU_1: digit_current_led[0] = 1; // Menu 1 digit_current_led[1] = 10; digit_current_led[2] = target_setting_1_value >= 9 ? (((target_setting_1_value + 1) / 10) + 20) : 20; digit_current_led[3] = ((target_setting_1_value + 1) % 10) + 30; break; case WORKING_MODE_MENU_2: digit_current_led[0] = 2; // Menu 2 digit_current_led[1] = 10; digit_current_led[2] = target_setting_2_value >= 9 ? (((target_setting_2_value + 1) / 10) + 20) : 20; digit_current_led[3] = ((target_setting_2_value + 1) % 10) + 30; break; case WORKING_MODE_MENU_3: digit_current_led[0] = 3; // Menu 3 digit_current_led[1] = 10; digit_current_led[2] = target_setting_3_value >= 9 ? (((target_setting_3_value + 1) / 10) + 20) : 20; digit_current_led[3] = ((target_setting_3_value + 1) % 10) + 30; break; default: break; } // Color animation color_animation_handler(); switch (current_working_mode) { case WORKING_MODE_CLOCK: digit_animation_is_preview = false; if (current_setting_3_value == DIGIT_ANIMATION_RUNNING_LED) animation_running_led_handler(); else if (current_setting_3_value == DIGIT_ANIMATION_FADE) animation_fade_handler(); break; case WORKING_MODE_MENU_3: digit_animation_is_preview = true; if (target_setting_3_value == DIGIT_ANIMATION_RUNNING_LED) animation_running_led_handler(); else if (target_setting_3_value == DIGIT_ANIMATION_FADE) animation_fade_handler(); break; default: break; } // Choose colors switch (current_working_mode) { case WORKING_MODE_SET_TIME_HOURS: case WORKING_MODE_SET_TIME_MINUTES: case WORKING_MODE_CLOCK: for (uint8_t i = 0; i < 4; i++) { if (current_setting_1_value == 16) { digit_color[i] = Adafruit_NeoPixel::ColorHSV(degree_hue_to_uint16_hue(i * 48 + delta_hue[i]), 255, digit_brightness[i]); if (current_setting_3_value == DIGIT_ANIMATION_FADE) digit_previous_color[i] = Adafruit_NeoPixel::ColorHSV(degree_hue_to_uint16_hue(i * 48 + delta_hue[i]), 255, digit_previous_brightness[i]); } else { digit_color[i] = nth_preset_color(current_setting_1_value, delta_hue[i], digit_brightness[i]); if (current_setting_3_value == DIGIT_ANIMATION_FADE) digit_previous_color[i] = nth_preset_color(current_setting_1_value, delta_hue[i], digit_previous_brightness[i]); } } break; case WORKING_MODE_MENU_1: case WORKING_MODE_MENU_2: case WORKING_MODE_MENU_3: for (uint8_t i = 0; i < 4; i++) if (target_setting_1_value == 16) digit_color[i] = Adafruit_NeoPixel::ColorHSV(degree_hue_to_uint16_hue(i * 48 + delta_hue[i]), 255, digit_brightness[i]); else digit_color[i] = nth_preset_color(target_setting_1_value, delta_hue[i], digit_brightness[i]); break; default: break; } // Show digits switch (current_working_mode) { case WORKING_MODE_SET_TIME_HOURS: case WORKING_MODE_SET_TIME_MINUTES: if ((now_time - matrix_blinking_last_time) >= MATRIX_BLINKING_INTERVAL_MS) { matrix_blinking_last_time = now_time; matrix_blinking_state = !matrix_blinking_state; } if (button_up.isPressed() || button_down.isPressed()) matrix_blinking_state = true; if (current_working_mode == WORKING_MODE_SET_TIME_HOURS) { matrix.setPixelColor(digit_current_led[0], matrix_blinking_state ? digit_color[0] : 0); matrix.setPixelColor(digit_current_led[1], matrix_blinking_state ? digit_color[1] : 0); matrix.setPixelColor(digit_current_led[2], digit_color[2]); matrix.setPixelColor(digit_current_led[3], digit_color[3]); } else if (current_working_mode == WORKING_MODE_SET_TIME_MINUTES) { matrix.setPixelColor(digit_current_led[0], digit_color[0]); matrix.setPixelColor(digit_current_led[1], digit_color[1]); matrix.setPixelColor(digit_current_led[2], matrix_blinking_state ? digit_color[2] : 0); matrix.setPixelColor(digit_current_led[3], matrix_blinking_state ? digit_color[3] : 0); } break; case WORKING_MODE_CLOCK: for (uint8_t i = 0; i < 4; i++) { matrix.setPixelColor(digit_current_led[i], digit_color[i]); if (current_setting_3_value == DIGIT_ANIMATION_FADE) matrix.setPixelColor(digit_previous_led[i], digit_previous_color[i]); } break; case WORKING_MODE_MENU_1: matrix.setPixelColor(digit_current_led[0], digit_color[0]); matrix.setPixelColor(digit_current_led[2], digit_color[2]); matrix.setPixelColor(digit_current_led[3], digit_color[3]); break; case WORKING_MODE_MENU_2: matrix.setPixelColor(digit_current_led[0], digit_color[0]); matrix.setPixelColor(digit_current_led[2], digit_color[2]); matrix.setPixelColor(digit_current_led[3], digit_color[3]); break; case WORKING_MODE_MENU_3: matrix.setPixelColor(digit_current_led[0], digit_color[0]); matrix.setPixelColor(digit_current_led[2], digit_color[2]); matrix.setPixelColor(digit_current_led[3], digit_color[3]); break; default: break; } matrix.setBrightness(255); matrix.show(); } // 1Hz hardware timer #ifdef TIME_SOURCE_MCU ISR(TIMER1_COMPA_vect) { isr_counter++; if (isr_counter >= 60) { isr_counter = 0; isr_current_minute++; } if (isr_current_minute >= 60) { isr_current_minute = 0; isr_current_hour++; } if (isr_current_hour >= 24) isr_current_hour = 0; dot_blinking_state = !dot_blinking_state; } void start_blink_timer() { cli(); TCCR1A = 0; // Set entire TCCR1A register to 0 TCCR1B = 0; // Same for TCCR1B TCNT1 = 0; // Initialize counter value to 0 // Set compare match register for 1hz increments OCR1A = 15624; // = (16*10^6) / (1*1024) - 1 (must be <65536) TCCR1B |= _BV(WGM12); // Turn on CTC mode TCCR1B |= _BV(CS12) | _BV(CS10); // Set CS12 and CS10 bits for 1024 prescaler TIMSK1 |= _BV(OCIE1A); // Enable blinking TIMSK1 |= _BV(OCIE1A); sei(); } void stop_blink_timer() { TCCR1A = 0; // Set entire TCCR1A register to 0 TCCR1B = 0; // Same for TCCR1B TCNT1 = 0; // Initialize counter value to 0 } void fetch_volatile_data() { cli(); current_hour = isr_current_hour; current_minute = isr_current_minute; sei(); } void set_volatile_data() { cli(); isr_current_hour = current_hour; isr_current_minute = current_minute; sei(); } #endif // Setup void setup() { button_mode.begin(); button_ok.begin(); button_up.begin(); button_down.begin(); clock.begin(); read_time(); clock.set(current_hour, current_minute, 0, 3, 1, 2022, 1); read_ram_data(); dot.begin(); matrix.begin(); dot.clear(); dot.setBrightness(50); matrix.clear(); #ifdef TIME_SOURCE_MCU start_blink_timer(); #endif } void loop() { now_time = millis(); #ifdef TIME_SOURCE_MCU fetch_volatile_data(); #else read_time(); if (now_time - dot_blinking_last_time >= 1000) { dot_blinking_state = !dot_blinking_state; dot_blinking_last_time = now_time; } #endif button_mode_handler(); button_ok_handler(); button_up_handler(); button_down_handler(); working_mode_handler(); matrix_handler(); dot_handler(); } === Тест светодиодов === Если вам кажется что какой-то из светодиодов на плате Nixie Clock перестал работать как положено, вы можете проверить работу всех светодиодов загрузив на плату следующий код: #include constexpr uint8_t DELAYVAL = 50; Adafruit_NeoPixel matrix(40, 2, NEO_GRB + NEO_KHZ800); Adafruit_NeoPixel dot(1, 9, NEO_GRB + NEO_KHZ800); void setup() { matrix.begin(); dot.begin(); } void loop() { matrix.clear(); dot.clear(); dot.setPixelColor(0, dot.Color(0, 150, 0)); dot.show(); for (int i = 0; i < 10; i++) { matrix.setPixelColor(i, matrix.Color(150, 150, 0)); matrix.show(); delay(DELAYVAL); } for (int i = 10; i < 20; i++) { matrix.setPixelColor(i, matrix.Color(150, 0, 150)); matrix.show(); delay(DELAYVAL); } for (int i = 20; i < 30; i++) { matrix.setPixelColor(i, matrix.Color(0, 150, 150)); matrix.show(); delay(DELAYVAL); } for (int i = 30; i < 40; i++) { matrix.setPixelColor(i, matrix.Color(0, 150, 0)); matrix.show(); delay(DELAYVAL); } delay(10000); } ===== Ресурсы ===== [[amp>product/nixie-clock?utm_source=proj&utm_campaign=nixie&utm_medium=wiki | Часы Nixie Clock]] в магазине