Содержание

Часы Nixie Clock: инструкция, примеры использования и документация

Используйте часы Nixie Clock в качестве стильного гаджета на своём столе дома, мастерской или на работе. Часы не просто показывают текущее время, а делает это максимально зрелищно благодаря прозрачному циферблату, который имитирует знаменитые газоразрядные индикаторы Nixie Tube. Только вместо олдскульных газоразрядных ламп наподобие ИН-12, ИН-14, ИН-18 — современная управляемая LED-подсветка на WS2812B.

Что такое Nixie Clock?

В конце 90-х было модно собирать часы на газоразрядных лампах Nixie Tube моделей ИН-12, ИН-14, ИН-18.

Мы решили дать возможность каждому собрать модные олдскульные часы. Однако вместо дефицитных газоразрядных ламп используем акриловые пластины с выгравированными цифрами, которые подсветим адресными светодиодами WS2812B.

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

На каждом прямоугольнике выполнена гравировка цифры от 0 до 9.

Если посветить в торец прямоугольника, то выгравированная цифра начнет светиться цветом, которым светит светодиод. Собрав десять таких цифр в блок и подсвечивая каждую пластинку акрила отдельным светодиодом можно по-отдельности зажигать каждую цифру. Поскольку свет входит в акрил с края, происходит полное внутреннее отражение и акрил ведет себя как световод.

С теорией разобрались, вперёд собирать стильные часы.

Как собрать

Комплектация

Шаг 1

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

Шаг 2

Вставьте батарейку типоразмера CR1225 в батарейный отсек платы Nixie Clock. Батарейка идет в комплекте с часами.

В часах Nixie Clock батарейка используется для хранения пользовательских настроек цвета, анимации и отсчета времени при отключении часов от питания.

Шаг 3

Установите пластины А, B и С снизу платы Nixie Clock, используя пять винтов M3×25.

Шаг 4

Вставьте секундный рассеиватель в пластину Е. Установите пластины E и D сверху платы Nixie Clock и закрепите их пятью алюминиевыми стойками.

Не затягивайте винты слишком сильно! Сильно затянутые винты создадут трение между пластинами А, В и С что будет препятствовать нормальному нажатию кнопок.

Шаг 5

Через пластину F установите в часы четыре набора цифр. Один набор цифр состоит из 10 пластиковых деталей с цифрами от 0 до 9. Цифры устанавливаются по возрастанию. В каждом разряде цифра 0 находится ближе всего к лицевой стороне часов.

Для удобства установки цифр, пластину F можно временно закрепить винтами к алюминиевым стойкам.

Шаг 6

Установите сверху часов пластину G и закрепите её пятью винтами М3×10.

Шаг 7

Вуаля часы собраны, остаётся только подать питания. Подключите 5 В к часам через разъём Micro-USB. В качестве источника питания подойдёт USB-порт компьютера или наш сетевой адаптер 5 В.

Вот и всё, часы тикают и радуют ваш взгляд.

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

Раздел разработчика

По умолчанию на Nixie Clock установлена заводская прошивка. Но вы можете запрограммировать гаджет по-своему.

  1. Подключите Nixie Clock к компьютеру
  2. Установите и настройте Arduino IDE.

Часы выполнены на микроконтроллере ATmega328P с зашитым загрузчиком от Arduino Uno. Однако на плате Nixie Clock распаяна дополнительная периферия: часы реального времени, кнопки и светодиоды.

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

Распиновка платы Nixie Clock

Для создания собственной прошивки часов вам понадобится информация о задействованных пинах микроконтроллера ATmega328P.

Прошивка платы

Часы Nixie Clock поставляются прошитыми, однако при желании вы можете изменить или дополнить оригинальную прошивку.

Необходимые библиотеки

В прошивке платы Nixie Clock используются следующие Arduino-библиотеки:

Убедитесь, что эти библиотеки установлены в ваше рабочее пространство Arduino IDE.

Обратите внимание

Если вы покупали Nixie Clock после 15.07.2024, то для корректной работы часов нужно раскомментировать 8-ю строку кода в штатной программе и прошить плату:

#define TIME_SOURCE_MCU

Штатный код часов

Clock.ino
  1. #include <Adafruit_NeoPixel.h>
  2. #include <TroykaButton.h>
  3. #include <TroykaRTC.h>
  4. #include <Wire.h>
  5.  
  6. // Uncomment the line below to source time from an MCU circuit rather than RTC.
  7. // Use one having best precision in hardware.
  8. // #define TIME_SOURCE_MCU
  9.  
  10. // Pin definition
  11.  
  12. constexpr uint8_t BUTTON_MODE_PIN = A3;
  13. constexpr uint8_t BUTTON_OK_PIN = A2;
  14. constexpr uint8_t BUTTON_UP_PIN = A1;
  15. constexpr uint8_t BUTTON_DOWN_PIN = A0;
  16. constexpr uint8_t WS2812_DOT_PIN = 9;
  17. constexpr uint8_t WS2812_MATRIX_PIN = 2;
  18.  
  19. // Class objects
  20.  
  21. Adafruit_NeoPixel dot = Adafruit_NeoPixel(1, WS2812_DOT_PIN, NEO_GRB + NEO_KHZ800);
  22. Adafruit_NeoPixel matrix = Adafruit_NeoPixel(40, WS2812_MATRIX_PIN, NEO_GRB + NEO_KHZ800);
  23. RTC clock;
  24. TroykaButton button_mode(BUTTON_MODE_PIN, 1000, true, 200);
  25. TroykaButton button_ok(BUTTON_OK_PIN, 1000, true, 200);
  26. TroykaButton button_up(BUTTON_UP_PIN, 1000, true, 200);
  27. TroykaButton button_down(BUTTON_DOWN_PIN, 1000, true, 200);
  28.  
  29. // Time variables
  30.  
  31. uint8_t current_hour = 0;
  32. uint8_t current_minute = 0;
  33. uint8_t target_hour = 0;
  34. uint8_t target_minute = 0;
  35.  
  36. #ifdef TIME_SOURCE_MCU
  37. volatile uint16_t isr_counter = 0; // Used for clock source from AVR hardware timer
  38. volatile uint8_t isr_current_hour = 0; // Used for clock source from AVR hardware timer
  39. volatile uint8_t isr_current_minute = 0; // Used for clock source from AVR hardware timer
  40. #endif
  41.  
  42. // LED variables
  43.  
  44. uint32_t now_time = 0;
  45.  
  46. uint32_t digit_color[4] = { 0, 0, 0, 0 };
  47. uint32_t digit_previous_color[4] = { 0, 0, 0, 0 };
  48.  
  49. uint8_t digit_current_led[4] = { 255, 255, 255, 255 };
  50. uint8_t digit_previous_led[4] = { 255, 255, 255, 255 };
  51. uint8_t digit_old_led[4] = { 255, 255, 255, 255 };
  52.  
  53. bool dot_blinking_state = false;
  54.  
  55. #ifndef TIME_SOURCE_MCU
  56. uint32_t dot_blinking_last_time = 0;
  57. #endif
  58.  
  59. constexpr uint16_t MATRIX_BLINKING_INTERVAL_MS = 250;
  60. uint32_t matrix_blinking_last_time = 0;
  61. bool matrix_blinking_state = false;
  62.  
  63. // Modes variables and definitions
  64.  
  65. enum WorkingMode {
  66. WORKING_MODE_CLOCK = 1,
  67. WORKING_MODE_SET_TIME_HOURS = 2,
  68. WORKING_MODE_SET_TIME_MINUTES = 3,
  69. WORKING_MODE_MENU_1 = 4,
  70. WORKING_MODE_MENU_2 = 5,
  71. WORKING_MODE_MENU_3 = 6,
  72. };
  73.  
  74. uint8_t current_working_mode = WORKING_MODE_CLOCK;
  75. bool new_working_mode = false;
  76.  
  77. uint8_t current_setting_1_value = 0;
  78. uint8_t current_setting_2_value = 0;
  79. uint8_t current_setting_3_value = 0;
  80.  
  81. uint8_t target_setting_1_value = 0;
  82. uint8_t target_setting_2_value = 0;
  83. uint8_t target_setting_3_value = 0;
  84.  
  85. constexpr uint8_t TOTAL_SETTING_1_VALUES = 17;
  86. constexpr uint8_t TOTAL_SETTING_2_VALUES = 4;
  87. constexpr uint8_t TOTAL_SETTING_3_VALUES = 3;
  88.  
  89. // Color variables
  90.  
  91. int16_t delta_hue[4] = { 0, 0, 0, 0 };
  92. uint8_t digit_brightness[4] = { 255, 255, 255, 255 };
  93. uint8_t digit_previous_brightness[4] = { 255, 255, 255, 255 };
  94.  
  95. // Animation variables
  96.  
  97. enum ColorAnimation {
  98. COLOR_ANIMATION_NONE = 0,
  99. COLOR_ANIMATION_PENDULUM = 1,
  100. COLOR_ANIMATION_WAVE_PENDULUM = 2,
  101. COLOR_ANIMATION_DEEPNESS = 3,
  102. };
  103.  
  104. constexpr float COLOR_ANIMATION_RATE_MS = 12.0 / 60000.0; // Changes in ms
  105. constexpr float COLOR_ANIMATION_PHASE_SHIFT = 0.125;
  106. uint32_t previous_color_animation_time = 0;
  107. float color_animation_timer = 0;
  108.  
  109. enum DigitAnimation {
  110. DIGIT_ANIMATION_NONE = 0,
  111. DIGIT_ANIMATION_RUNNING_LED = 1,
  112. DIGIT_ANIMATION_FADE = 2,
  113. };
  114.  
  115. bool digit_animation_is_preview = false;
  116. uint32_t previous_digit_animation_fade_preview_time = 0;
  117. uint32_t previous_digit_animation_running_led_preview_time = 0;
  118. constexpr uint32_t DIGIT_ANIMATION_RUNNING_LED_PREVIEW_TIME_MS = 4000;
  119. constexpr uint32_t DIGIT_ANIMATION_FADE_PREVIEW_TIME_MS = 6500;
  120.  
  121. uint8_t running_led[4] = { 255, 255, 255, 255 };
  122. constexpr uint32_t DIGIT_ANIMATION_RUNNING_LED_RATE_MS = 30;
  123. uint32_t previous_digit_animation_running_led_time = 0;
  124.  
  125. constexpr float FADE_ANIMATION_RATE_MS = 3000.0; // 3 sec
  126. uint32_t previous_fade_animation_time[4] = { 0, 0, 0, 0 };
  127. float fade_animation_timer[4] = { 0, 0, 0, 0 };
  128.  
  129. // Auxiliary
  130.  
  131. uint16_t degree_hue_to_uint16_hue(int16_t degree) {
  132. return (float)((degree % 360) / 360.0 * 65535.0);
  133. }
  134.  
  135. // CLOCK
  136.  
  137. void read_time() {
  138. clock.read();
  139. current_hour = clock.getHour();
  140. current_minute = clock.getMinute();
  141. #ifdef TIME_SOURCE_MCU
  142. set_volatile_data();
  143. #endif
  144. }
  145.  
  146. void read_ram_data() {
  147. uint8_t data = clock.getRAMData(0x0A); // Menu 1 setting
  148. current_setting_1_value = ((data > 0) && (data < TOTAL_SETTING_1_VALUES)) ? data : 0;
  149. data = clock.getRAMData(0x0B); // Menu 2 setting
  150. current_setting_2_value = ((data > 0) && (data < TOTAL_SETTING_2_VALUES)) ? data : 0;
  151. data = clock.getRAMData(0x0C); // Menu 3 setting
  152. current_setting_3_value = ((data > 0) && (data < TOTAL_SETTING_3_VALUES)) ? data : 0;
  153. }
  154.  
  155. void set_ram_data() {
  156. clock.setRAMData(0x0A, current_setting_1_value); // Menu 1 setting
  157. clock.setRAMData(0x0B, current_setting_2_value); // Menu 2 setting
  158. clock.setRAMData(0x0C, current_setting_3_value); // Menu 3 setting
  159. }
  160.  
  161. // MODE button
  162.  
  163. void button_mode_handler() {
  164. button_mode.read();
  165.  
  166. if (button_mode.isClick()) {
  167.  
  168. switch (current_working_mode) {
  169. case WORKING_MODE_CLOCK:
  170. current_working_mode = WORKING_MODE_SET_TIME_HOURS;
  171. break;
  172. case WORKING_MODE_SET_TIME_HOURS:
  173. clock.setHour(target_hour);
  174. clock.setSecond(0);
  175. read_time();
  176. current_working_mode = WORKING_MODE_SET_TIME_MINUTES;
  177. break;
  178. case WORKING_MODE_SET_TIME_MINUTES:
  179. clock.setMinute(target_minute);
  180. clock.setSecond(0);
  181. read_time();
  182. current_working_mode = WORKING_MODE_CLOCK;
  183. break;
  184. case WORKING_MODE_MENU_1:
  185. current_setting_1_value = target_setting_1_value;
  186. set_ram_data();
  187. current_working_mode = WORKING_MODE_MENU_2;
  188. break;
  189. case WORKING_MODE_MENU_2:
  190. current_setting_2_value = target_setting_2_value;
  191. set_ram_data();
  192. current_working_mode = WORKING_MODE_MENU_3;
  193. break;
  194. case WORKING_MODE_MENU_3:
  195. current_setting_3_value = target_setting_3_value;
  196. set_ram_data();
  197. current_working_mode = WORKING_MODE_CLOCK;
  198. break;
  199. default:
  200. break;
  201. }
  202. new_working_mode = true;
  203. } else if (button_mode.isHold()) {
  204.  
  205. switch (current_working_mode) {
  206. case WORKING_MODE_CLOCK:
  207. current_working_mode = WORKING_MODE_MENU_1;
  208. break;
  209. default:
  210. break;
  211. }
  212. new_working_mode = true;
  213. }
  214. }
  215.  
  216. // UP button
  217.  
  218. void button_up_handler() {
  219. button_up.read();
  220.  
  221. if (!button_up.isClickOnHold() && !button_up.justPressed())
  222. return;
  223.  
  224. switch (current_working_mode) {
  225. case WORKING_MODE_SET_TIME_HOURS:
  226. target_hour = (target_hour + 1) % 24;
  227. break;
  228. case WORKING_MODE_SET_TIME_MINUTES:
  229. target_minute = (target_minute + 1) % 60;
  230. break;
  231. case WORKING_MODE_MENU_1:
  232. target_setting_1_value = (target_setting_1_value + 1) % TOTAL_SETTING_1_VALUES;
  233. break;
  234. case WORKING_MODE_MENU_2:
  235. target_setting_2_value = (target_setting_2_value + 1) % TOTAL_SETTING_2_VALUES;
  236. break;
  237. case WORKING_MODE_MENU_3:
  238. target_setting_3_value = (target_setting_3_value + 1) % TOTAL_SETTING_3_VALUES;
  239. break;
  240. default:
  241. break;
  242. }
  243. }
  244.  
  245. // DOWN button
  246.  
  247. void button_down_handler() {
  248. button_down.read();
  249.  
  250. if (!button_down.isClickOnHold() && !button_down.justPressed())
  251. return;
  252.  
  253. switch (current_working_mode) {
  254. case WORKING_MODE_SET_TIME_HOURS:
  255. target_hour = (target_hour - 1 + 24) % 24;
  256. break;
  257. case WORKING_MODE_SET_TIME_MINUTES:
  258. target_minute = (target_minute - 1 + 60) % 60;
  259. break;
  260. case WORKING_MODE_MENU_1:
  261. target_setting_1_value = (target_setting_1_value - 1 + TOTAL_SETTING_1_VALUES) % TOTAL_SETTING_1_VALUES;
  262. break;
  263. case WORKING_MODE_MENU_2:
  264. target_setting_2_value = (target_setting_2_value - 1 + TOTAL_SETTING_2_VALUES) % TOTAL_SETTING_2_VALUES;
  265. break;
  266. case WORKING_MODE_MENU_3:
  267. target_setting_3_value = (target_setting_3_value - 1 + TOTAL_SETTING_3_VALUES) % TOTAL_SETTING_3_VALUES;
  268. break;
  269. default:
  270. break;
  271. }
  272. }
  273.  
  274. // OK button
  275.  
  276. void button_ok_handler() {
  277. button_ok.read();
  278.  
  279. if (!button_ok.isClick())
  280. return;
  281.  
  282. switch (current_working_mode) {
  283. case WORKING_MODE_SET_TIME_HOURS:
  284. case WORKING_MODE_SET_TIME_MINUTES:
  285. clock.setHour(target_hour);
  286. clock.setMinute(target_minute);
  287. clock.setSecond(0);
  288. read_time();
  289. current_working_mode = WORKING_MODE_CLOCK;
  290. break;
  291. case WORKING_MODE_MENU_1:
  292. case WORKING_MODE_MENU_2:
  293. case WORKING_MODE_MENU_3:
  294. current_setting_1_value = target_setting_1_value;
  295. current_setting_2_value = target_setting_2_value;
  296. current_setting_3_value = target_setting_3_value;
  297. set_ram_data();
  298. current_working_mode = WORKING_MODE_CLOCK;
  299. break;
  300. default:
  301. break;
  302. }
  303. new_working_mode = true;
  304. }
  305.  
  306. void working_mode_handler() {
  307. if (!new_working_mode)
  308. return;
  309.  
  310. switch (current_working_mode) {
  311. case WORKING_MODE_SET_TIME_HOURS:
  312. target_hour = current_hour;
  313. target_minute = current_minute;
  314. break;
  315. case WORKING_MODE_MENU_1:
  316. target_setting_1_value = current_setting_1_value;
  317. break;
  318. case WORKING_MODE_MENU_2:
  319. target_setting_2_value = current_setting_2_value;
  320. break;
  321. case WORKING_MODE_MENU_3:
  322. target_setting_3_value = current_setting_3_value;
  323. break;
  324. default:
  325. break;
  326. }
  327. new_working_mode = false;
  328. }
  329.  
  330. uint32_t nth_preset_color(uint8_t current_color_setting, int16_t hue_change = 0, uint8_t brightness = 255) {
  331. 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;
  332. }
  333.  
  334. void dot_handler() {
  335.  
  336. uint32_t dot_color;
  337.  
  338. switch (current_working_mode) {
  339. case WORKING_MODE_CLOCK:
  340. if (dot_blinking_state) {
  341. dot_color = nth_preset_color(current_setting_1_value);
  342. dot.setPixelColor(0, dot_color);
  343. } else {
  344. dot.setPixelColor(0, 0);
  345. }
  346. break;
  347. default:
  348. dot.setPixelColor(0, 0);
  349. break;
  350. }
  351. dot.show();
  352. }
  353.  
  354. float saw_wave(float x) {
  355. float fract = fmod(x, 1);
  356. float y = (fract < 0.5) ? (fract / 0.5) : (((fract - 0.5) / -0.5) + 1.0);
  357. return y = 2 * y - 1;
  358. }
  359.  
  360. void color_animation_handler() {
  361.  
  362. uint32_t dt = now_time - previous_color_animation_time;
  363. color_animation_timer = color_animation_timer + (dt * COLOR_ANIMATION_RATE_MS);
  364.  
  365. float a = 0;
  366.  
  367. previous_color_animation_time = now_time;
  368.  
  369. if (target_setting_2_value == COLOR_ANIMATION_PENDULUM || current_setting_2_value == COLOR_ANIMATION_PENDULUM) {
  370. a = saw_wave(color_animation_timer);
  371. for (uint8_t i = 0; i < 4; i++)
  372. delta_hue[i] = 24 * a;
  373. } else if (target_setting_2_value == COLOR_ANIMATION_WAVE_PENDULUM || current_setting_2_value == COLOR_ANIMATION_WAVE_PENDULUM) {
  374. for (uint8_t i = 0; i < 4; i++) {
  375. a = saw_wave(color_animation_timer + (i + 1) * COLOR_ANIMATION_PHASE_SHIFT);
  376. delta_hue[i] = 24 * a;
  377. }
  378. } else if (target_setting_2_value == COLOR_ANIMATION_DEEPNESS || current_setting_2_value == COLOR_ANIMATION_DEEPNESS) {
  379. for (uint8_t i = 0; i < 4; i++)
  380. delta_hue[i] = (digit_current_led[i] - 10 * i) * 24.0 / 10.0;
  381. } else // COLOR_ANIMATION_NONE
  382. for (uint8_t i = 0; i < 4; i++)
  383. delta_hue[i] = 0;
  384. }
  385.  
  386. void animation_running_led_handler() {
  387.  
  388. if (now_time - previous_digit_animation_running_led_time > DIGIT_ANIMATION_RUNNING_LED_RATE_MS) {
  389. for (uint8_t i = 0; i < 4; i++)
  390. if (running_led[i] > digit_old_led[i])
  391. running_led[i]--;
  392. previous_digit_animation_running_led_time = now_time;
  393. }
  394.  
  395. for (uint8_t i = 0; i < 4; i++) {
  396. if (digit_current_led[i] != digit_old_led[i]) {
  397. running_led[i] = 10 * (i + 1) - 1;
  398. digit_old_led[i] = digit_current_led[i];
  399. }
  400. }
  401.  
  402. // Preview
  403.  
  404. if (digit_animation_is_preview && (now_time - previous_digit_animation_running_led_preview_time > DIGIT_ANIMATION_RUNNING_LED_PREVIEW_TIME_MS)) {
  405. for (uint8_t i = 0; i < 4; i++)
  406. running_led[i] = 10 * (i + 1) - 1;
  407. previous_digit_animation_running_led_preview_time = now_time;
  408. }
  409.  
  410. // Show
  411.  
  412. for (uint8_t i = 0; i < 4; i++)
  413. digit_current_led[i] = running_led[i];
  414. }
  415.  
  416. float fade_wave(float x) {
  417. float fract = fmod(x, 1);
  418. return pow(fract == 0 ? 1 : fract, 2);
  419. }
  420.  
  421. void animation_fade_handler() {
  422.  
  423. for (uint8_t i = 0; i < 4; i++) {
  424. if (digit_current_led[i] != digit_old_led[i]) {
  425. digit_previous_led[i] = digit_old_led[i];
  426. if (fade_animation_timer[i] >= 1.0) {
  427. fade_animation_timer[i] = 0;
  428. }
  429. }
  430. }
  431.  
  432. for (uint8_t i = 0; i < 4; i++) {
  433. if (fade_animation_timer[i] < 1.0) {
  434. uint32_t dt = now_time - previous_fade_animation_time[i];
  435. fade_animation_timer[i] = fade_animation_timer[i] + (dt * (1.0 / FADE_ANIMATION_RATE_MS));
  436. }
  437. if (fade_animation_timer[i] >= 1.0) {
  438. fade_animation_timer[i] = 1.0;
  439. digit_old_led[i] = digit_current_led[i];
  440. }
  441. previous_fade_animation_time[i] = now_time;
  442. }
  443.  
  444. // Preview
  445.  
  446. if (digit_animation_is_preview && (now_time - previous_digit_animation_fade_preview_time > DIGIT_ANIMATION_FADE_PREVIEW_TIME_MS)) {
  447. for (uint8_t i = 0; i < 4; i++) {
  448. digit_previous_led[i] = 255;
  449. fade_animation_timer[i] = 0;
  450. }
  451. previous_digit_animation_fade_preview_time = now_time;
  452. }
  453.  
  454. // Show
  455.  
  456. for (uint8_t i = 0; i < 4; i++) {
  457. float a = fade_wave(fade_animation_timer[i]);
  458. digit_brightness[i] = 255 * a;
  459. digit_previous_brightness[i] = 255 * (1 - a);
  460. }
  461. }
  462.  
  463. void matrix_handler() {
  464. matrix.clear();
  465.  
  466. memset(digit_brightness, 255, 4 * sizeof(digit_brightness[0]));
  467.  
  468. // Choose digits
  469.  
  470. switch (current_working_mode) {
  471. case WORKING_MODE_SET_TIME_HOURS:
  472. case WORKING_MODE_SET_TIME_MINUTES:
  473. digit_current_led[0] = target_hour / 10;
  474. digit_current_led[1] = (target_hour % 10) + 10;
  475. digit_current_led[2] = (target_minute / 10) + 20;
  476. digit_current_led[3] = (target_minute % 10) + 30;
  477. break;
  478. case WORKING_MODE_CLOCK:
  479. digit_current_led[0] = current_hour / 10;
  480. digit_current_led[1] = (current_hour % 10) + 10;
  481. digit_current_led[2] = (current_minute / 10) + 20;
  482. digit_current_led[3] = (current_minute % 10) + 30;
  483. break;
  484. case WORKING_MODE_MENU_1:
  485. digit_current_led[0] = 1; // Menu 1
  486. digit_current_led[1] = 10;
  487. digit_current_led[2] = target_setting_1_value >= 9 ? (((target_setting_1_value + 1) / 10) + 20) : 20;
  488. digit_current_led[3] = ((target_setting_1_value + 1) % 10) + 30;
  489. break;
  490. case WORKING_MODE_MENU_2:
  491. digit_current_led[0] = 2; // Menu 2
  492. digit_current_led[1] = 10;
  493. digit_current_led[2] = target_setting_2_value >= 9 ? (((target_setting_2_value + 1) / 10) + 20) : 20;
  494. digit_current_led[3] = ((target_setting_2_value + 1) % 10) + 30;
  495. break;
  496. case WORKING_MODE_MENU_3:
  497. digit_current_led[0] = 3; // Menu 3
  498. digit_current_led[1] = 10;
  499. digit_current_led[2] = target_setting_3_value >= 9 ? (((target_setting_3_value + 1) / 10) + 20) : 20;
  500. digit_current_led[3] = ((target_setting_3_value + 1) % 10) + 30;
  501. break;
  502. default:
  503. break;
  504. }
  505.  
  506. // Color animation
  507.  
  508. color_animation_handler();
  509.  
  510. switch (current_working_mode) {
  511. case WORKING_MODE_CLOCK:
  512. digit_animation_is_preview = false;
  513. if (current_setting_3_value == DIGIT_ANIMATION_RUNNING_LED)
  514. animation_running_led_handler();
  515. else if (current_setting_3_value == DIGIT_ANIMATION_FADE)
  516. animation_fade_handler();
  517. break;
  518. case WORKING_MODE_MENU_3:
  519. digit_animation_is_preview = true;
  520. if (target_setting_3_value == DIGIT_ANIMATION_RUNNING_LED)
  521. animation_running_led_handler();
  522. else if (target_setting_3_value == DIGIT_ANIMATION_FADE)
  523. animation_fade_handler();
  524. break;
  525. default:
  526. break;
  527. }
  528.  
  529. // Choose colors
  530.  
  531. switch (current_working_mode) {
  532. case WORKING_MODE_SET_TIME_HOURS:
  533. case WORKING_MODE_SET_TIME_MINUTES:
  534. case WORKING_MODE_CLOCK:
  535. for (uint8_t i = 0; i < 4; i++) {
  536. if (current_setting_1_value == 16) {
  537. digit_color[i] = Adafruit_NeoPixel::ColorHSV(degree_hue_to_uint16_hue(i * 48 + delta_hue[i]), 255, digit_brightness[i]);
  538. if (current_setting_3_value == DIGIT_ANIMATION_FADE)
  539. digit_previous_color[i] = Adafruit_NeoPixel::ColorHSV(degree_hue_to_uint16_hue(i * 48 + delta_hue[i]), 255, digit_previous_brightness[i]);
  540. } else {
  541. digit_color[i] = nth_preset_color(current_setting_1_value, delta_hue[i], digit_brightness[i]);
  542. if (current_setting_3_value == DIGIT_ANIMATION_FADE)
  543. digit_previous_color[i] = nth_preset_color(current_setting_1_value, delta_hue[i], digit_previous_brightness[i]);
  544. }
  545. }
  546. break;
  547. case WORKING_MODE_MENU_1:
  548. case WORKING_MODE_MENU_2:
  549. case WORKING_MODE_MENU_3:
  550. for (uint8_t i = 0; i < 4; i++)
  551. if (target_setting_1_value == 16)
  552. digit_color[i] = Adafruit_NeoPixel::ColorHSV(degree_hue_to_uint16_hue(i * 48 + delta_hue[i]), 255, digit_brightness[i]);
  553. else
  554. digit_color[i] = nth_preset_color(target_setting_1_value, delta_hue[i], digit_brightness[i]);
  555. break;
  556. default:
  557. break;
  558. }
  559.  
  560. // Show digits
  561.  
  562. switch (current_working_mode) {
  563. case WORKING_MODE_SET_TIME_HOURS:
  564. case WORKING_MODE_SET_TIME_MINUTES:
  565.  
  566. if ((now_time - matrix_blinking_last_time) >= MATRIX_BLINKING_INTERVAL_MS) {
  567. matrix_blinking_last_time = now_time;
  568. matrix_blinking_state = !matrix_blinking_state;
  569. }
  570.  
  571. if (button_up.isPressed() || button_down.isPressed())
  572. matrix_blinking_state = true;
  573.  
  574. if (current_working_mode == WORKING_MODE_SET_TIME_HOURS) {
  575. matrix.setPixelColor(digit_current_led[0], matrix_blinking_state ? digit_color[0] : 0);
  576. matrix.setPixelColor(digit_current_led[1], matrix_blinking_state ? digit_color[1] : 0);
  577. matrix.setPixelColor(digit_current_led[2], digit_color[2]);
  578. matrix.setPixelColor(digit_current_led[3], digit_color[3]);
  579. } else if (current_working_mode == WORKING_MODE_SET_TIME_MINUTES) {
  580. matrix.setPixelColor(digit_current_led[0], digit_color[0]);
  581. matrix.setPixelColor(digit_current_led[1], digit_color[1]);
  582. matrix.setPixelColor(digit_current_led[2], matrix_blinking_state ? digit_color[2] : 0);
  583. matrix.setPixelColor(digit_current_led[3], matrix_blinking_state ? digit_color[3] : 0);
  584. }
  585. break;
  586. case WORKING_MODE_CLOCK:
  587. for (uint8_t i = 0; i < 4; i++) {
  588. matrix.setPixelColor(digit_current_led[i], digit_color[i]);
  589. if (current_setting_3_value == DIGIT_ANIMATION_FADE)
  590. matrix.setPixelColor(digit_previous_led[i], digit_previous_color[i]);
  591. }
  592. break;
  593. case WORKING_MODE_MENU_1:
  594. matrix.setPixelColor(digit_current_led[0], digit_color[0]);
  595. matrix.setPixelColor(digit_current_led[2], digit_color[2]);
  596. matrix.setPixelColor(digit_current_led[3], digit_color[3]);
  597. break;
  598. case WORKING_MODE_MENU_2:
  599. matrix.setPixelColor(digit_current_led[0], digit_color[0]);
  600. matrix.setPixelColor(digit_current_led[2], digit_color[2]);
  601. matrix.setPixelColor(digit_current_led[3], digit_color[3]);
  602. break;
  603. case WORKING_MODE_MENU_3:
  604. matrix.setPixelColor(digit_current_led[0], digit_color[0]);
  605. matrix.setPixelColor(digit_current_led[2], digit_color[2]);
  606. matrix.setPixelColor(digit_current_led[3], digit_color[3]);
  607. break;
  608. default:
  609. break;
  610. }
  611.  
  612. matrix.setBrightness(255);
  613. matrix.show();
  614. }
  615.  
  616. // 1Hz hardware timer
  617.  
  618. #ifdef TIME_SOURCE_MCU
  619. ISR(TIMER1_COMPA_vect) {
  620. isr_counter++;
  621.  
  622. if (isr_counter >= 60) {
  623. isr_counter = 0;
  624. isr_current_minute++;
  625. }
  626. if (isr_current_minute >= 60) {
  627. isr_current_minute = 0;
  628. isr_current_hour++;
  629. }
  630. if (isr_current_hour >= 24)
  631. isr_current_hour = 0;
  632.  
  633. dot_blinking_state = !dot_blinking_state;
  634. }
  635.  
  636. void start_blink_timer() {
  637. cli();
  638. TCCR1A = 0; // Set entire TCCR1A register to 0
  639. TCCR1B = 0; // Same for TCCR1B
  640. TCNT1 = 0; // Initialize counter value to 0
  641. // Set compare match register for 1hz increments
  642. OCR1A = 15624; // = (16*10^6) / (1*1024) - 1 (must be <65536)
  643. TCCR1B |= _BV(WGM12); // Turn on CTC mode
  644. TCCR1B |= _BV(CS12) | _BV(CS10); // Set CS12 and CS10 bits for 1024 prescaler
  645. TIMSK1 |= _BV(OCIE1A); // Enable blinking
  646. TIMSK1 |= _BV(OCIE1A);
  647. sei();
  648. }
  649.  
  650. void stop_blink_timer() {
  651. TCCR1A = 0; // Set entire TCCR1A register to 0
  652. TCCR1B = 0; // Same for TCCR1B
  653. TCNT1 = 0; // Initialize counter value to 0
  654. }
  655.  
  656. void fetch_volatile_data() {
  657. cli();
  658. current_hour = isr_current_hour;
  659. current_minute = isr_current_minute;
  660. sei();
  661. }
  662.  
  663. void set_volatile_data() {
  664. cli();
  665. isr_current_hour = current_hour;
  666. isr_current_minute = current_minute;
  667. sei();
  668. }
  669. #endif
  670.  
  671. // Setup
  672.  
  673. void setup() {
  674. button_mode.begin();
  675. button_ok.begin();
  676. button_up.begin();
  677. button_down.begin();
  678.  
  679. clock.begin();
  680. read_time();
  681. clock.set(current_hour, current_minute, 0, 3, 1, 2022, 1);
  682.  
  683. read_ram_data();
  684.  
  685. dot.begin();
  686. matrix.begin();
  687. dot.clear();
  688. dot.setBrightness(50);
  689. matrix.clear();
  690.  
  691. #ifdef TIME_SOURCE_MCU
  692. start_blink_timer();
  693. #endif
  694. }
  695.  
  696. void loop() {
  697. now_time = millis();
  698.  
  699. #ifdef TIME_SOURCE_MCU
  700. fetch_volatile_data();
  701. #else
  702. read_time();
  703. if (now_time - dot_blinking_last_time >= 1000) {
  704. dot_blinking_state = !dot_blinking_state;
  705. dot_blinking_last_time = now_time;
  706. }
  707. #endif
  708.  
  709. button_mode_handler();
  710. button_ok_handler();
  711. button_up_handler();
  712. button_down_handler();
  713. working_mode_handler();
  714. matrix_handler();
  715. dot_handler();
  716. }

Тест светодиодов

Если вам кажется что какой-то из светодиодов на плате Nixie Clock перестал работать как положено, вы можете проверить работу всех светодиодов загрузив на плату следующий код:

TestLed.ino
  1. #include <Adafruit_NeoPixel.h>
  2.  
  3. constexpr uint8_t DELAYVAL = 50;
  4.  
  5. Adafruit_NeoPixel matrix(40, 2, NEO_GRB + NEO_KHZ800);
  6. Adafruit_NeoPixel dot(1, 9, NEO_GRB + NEO_KHZ800);
  7.  
  8. void setup() {
  9. matrix.begin();
  10. dot.begin();
  11. }
  12.  
  13. void loop() {
  14. matrix.clear();
  15. dot.clear();
  16. dot.setPixelColor(0, dot.Color(0, 150, 0));
  17. dot.show();
  18. for (int i = 0; i < 10; i++) {
  19. matrix.setPixelColor(i, matrix.Color(150, 150, 0));
  20. matrix.show();
  21. delay(DELAYVAL);
  22. }
  23. for (int i = 10; i < 20; i++) {
  24. matrix.setPixelColor(i, matrix.Color(150, 0, 150));
  25. matrix.show();
  26. delay(DELAYVAL);
  27. }
  28. for (int i = 20; i < 30; i++) {
  29. matrix.setPixelColor(i, matrix.Color(0, 150, 150));
  30. matrix.show();
  31. delay(DELAYVAL);
  32. }
  33. for (int i = 30; i < 40; i++) {
  34. matrix.setPixelColor(i, matrix.Color(0, 150, 0));
  35. matrix.show();
  36. delay(DELAYVAL);
  37. }
  38. delay(10000);
  39. }

Ресурсы

Часы Nixie Clock в магазине.