
Всем привет!
Перебирая старые запасы на работе, я наткнулся на вакуумные индикаторы, среди них были несколько ИВ-11 и ИВ-6. Выкидывать такую винтажную красоту было жалко, родилась идея сделать стильные настольные часы, но с современной начинкой.

В интернете полно схем на трансформаторах или микросхемах вроде К176ИЕ18, К176ИЕ13, но найти их сейчас сложно, а мотать трансформатор — долго, да еще и проволоку искать.
Поэтому было принято решение сделать все с доступными компонентами.
Требования к питанию индикаторов:
Для ИВ-11: накал — 1,5 В, анодное напряжение — около 50В для динамической индикации.
Для ИВ-6: накал желательно 1,2 В (можно просто добавить резистор в цепь накала).
Поиск по маркетплейсам привёл к простому и дешёвому решению:
XL6009 (повышающий DC‑DC) — для получения стабильных 45 В из 5 В USB.
MP2307 (понижающий DC‑DC) — для точной регулировки напряжения накала 1,5 В
В качестве «мозга» проекта я выбрал STM32F401CC на плате Black Pill.
Во‑первых, она просто валялась без дела.
Во‑вторых, у него есть аппаратный RTC, что избавляет от покупки отдельного модуля DS3231.
Управлять индикаторами будем с помощью 8-канального высоковольтного транзисторного драйвера верхнего ключа TD62783AP. Если заказываете с AliExpress — берите китайский аналог KID65783AP. Оригинальный TD62783AP там часто подделывают, и он может не работать.

С компонентами определились - пора проектировать плату. Схему и печатную плату я собрал в Altium Designer.


Как-то так, отправляем гербер в поднебесную, теперь осталась только ждать.
А пока ждем можно и написать прошивку, использую STM32CubeIDE. За основу я взял библиотеку segment_lcd с сайта MicroTechnics, она будет перебирать сегменты, в код добавил много комментарий, если кто то захочет в нем ковыряться.
Код целиком
#include "main.h" #include "segment_lcd.h" #include "stdio.h" #include "button.h" /* Private variables ---------------------------------------------------------*/ RTC_HandleTypeDef hrtc; // Идентификатор RTC TIM_HandleTypeDef htim3; TIM_HandleTypeDef htim10; // Идентификатор для обработки кнопок RTC_TimeTypeDef sTime = {0}; // Структура для хранения времени RTC_DateTypeDef DateToUpdate = {0}; // Структура для хранения даты /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); // Настройка тактирования мк static void MX_GPIO_Init(void); // Инициализация портов static void MX_RTC_Init(void); // Инициализация RTC static void MX_TIM10_Init(void); // Инициализация таймера кнопок int main(void) { HAL_Init(); // Сброс периферийных устройств SystemClock_Config(); // Настройка системной тактовой частоты // Периферия MX_GPIO_Init(); // Настройка пинов MX_RTC_Init(); // Настройка часов MX_TIM10_Init(); // Настройка таймера для кнопок HAL_PWR_EnableBkUpAccess(); // Разблокируем доступ к резервной области //КАЛИБРОВКА============================================================= // HAL_RTCEx_SetSmoothCalib(&hrtc, // RTC_SMOOTHCALIB_PERIOD_32SEC, // RTC_SMOOTHCALIB_PLUSPULSES_SET, // 270); //======================================================================= HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // Получаем текущее время из RTC HAL_TIM_Base_Start_IT(&htim10); // Запускаем таймер кнопок в режиме прерываний while (1) { HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // Получаем текущее время из RTC HAL_RTC_GetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN); // Получаем текущую дату из RTC BUTTON_Process(); if (BUTTON_GetAction(BUTTON_SETTINGS) == BUTTON_SHORT_PRESS) // КОРОТКОЕ НАЖАТИЕ: увеличение минут { sTime.Minutes++; // Увеличиваем минуты на 1 sTime.Seconds = 0; // Сбрасываем секунды в 0 if(sTime.Minutes >=60) // Если достигли 60 минут { sTime.Seconds = 0; // Сбрасываем секунды в 0 sTime.Minutes = 0; // Сбрасываем минуты в 0 } HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // Записываем новое время в RTC } if (BUTTON_GetAction(BUTTON_SETTINGS) == BUTTON_LONG_PRESS) // ДЛИННОЕ НАЖАТИЕ: увеличение часов { sTime.Hours++; // Увеличиваем часы на 1 if(sTime.Hours >=24) // Если достигли 24 часов { sTime.Hours = 0; // Сбрасываем часы в 0 } HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // Записываем новое время в RTC } BUTTON_ResetActions(); // Сбрасываем флаги действий кнопок // Обновление 7-сегментного индикатора SEG_LCD_Process(); // Динамическая индикация (опрос разрядов) HAL_Delay(1); // Небольшая задержка для стабильности (также земедляят перебор // Формирование строки для вывода на индикатор char str[DIGITS_NUM + 2]; // Буфер для строки (4 символа + точка + запас) // Мигание точки с периодом 2 секунды (проверка четности секунд) if(sTime.Seconds % 2 == 0) // Четные секунды - показываем точку { snprintf(str, DIGITS_NUM +2, "%02d.%02d", sTime.Hours, sTime.Minutes ); // Формат: "ЧЧ.ММ" (например "12.30") } else // Нечетные секунды - без точки { snprintf(str, DIGITS_NUM +2, "%02d%02d", sTime.Hours, sTime.Minutes ); } // Отправляем строку в драйвер индикатора SEG_LCD_WriteString(str); } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // Включаем тактирование блока питания и доступ к резервной области __HAL_RCC_PWR_CLK_ENABLE(); // Включаем тактирование питания // HAL_PWR_EnableBkUpAccess(); // Разблокируем домен резервного питания __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2); // Настраиваем осцилляторы __HAL_RCC_RTC_ENABLE(); // Включаем тактирование RTC // Настройка генераторов HSE и LSE RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; // Внешний кварц 25 МГц RCC_OscInitStruct.LSEState = RCC_LSE_ON; // Внешний кварц 32.768 кГц для RTC RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // Включаем PLL RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // Источник PLL - HSE RCC_OscInitStruct.PLL.PLLM = 25; // Делитель: 25 МГц / 25 = 1 МГц RCC_OscInitStruct.PLL.PLLN = 168; // Множитель: 1 МГц * 168 = 168 МГц RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // Делитель для системной шины: 168/2 = 84 МГц RCC_OscInitStruct.PLL.PLLQ = 4; // Делитель для USB/SDIO if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } // Настройка делителей для шин AHB/APB RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // Источник SYSCLK - PLL RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // HCLK = SYSCLK (84 МГц) RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // PCLK1 = HCLK/2 (42 МГц) RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // PCLK2 = HCLK (84 МГц) if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_RTC_Init(void) { RTC_TimeTypeDef sTime = {0}; // Временная структура для начальной установки RTC_DateTypeDef sDate = {0}; // Временная структура для начальной установки hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; // 24-часовой формат hrtc.Init.AsynchPrediv = 127; // Асинхронный делитель (для LSE) hrtc.Init.SynchPrediv = 255; // Синхронный делитель hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; // Отключаем выход RTC hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; if (HAL_RTC_Init(&hrtc) != HAL_OK) { Error_Handler(); } HAL_PWR_EnableBkUpAccess(); // Разблокируем доступ к резервной области //КАЛИБРОВКА============================================================= HAL_RTCEx_SetSmoothCalib(&hrtc, RTC_SMOOTHCALIB_PERIOD_32SEC, RTC_SMOOTHCALIB_PLUSPULSES_RESET, -142); //136 //======================================================================= if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0x32F2) // Проверяем маркер инициализации в backup-регистре { // Первый запуск - устанавливаем корректное время и дату sTime.Hours = 12; sTime.Minutes = 0; sTime.Seconds = 0; sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE; sTime.StoreOperation = RTC_STOREOPERATION_RESET; HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN); sDate.WeekDay = RTC_WEEKDAY_THURSDAY; sDate.Month = RTC_MONTH_FEBRUARY; sDate.Date = 12; sDate.Year = 26; // 2026 год HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN); // Сохраняем маркер инициализации HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0x32F2); } } static void MX_TIM10_Init(void) { htim10.Instance = TIM10; htim10.Init.Prescaler = 8400-1; // 84 МГц / 8400 = 10 кГц htim10.Init.CounterMode = TIM_COUNTERMODE_UP; htim10.Init.Period = 10; htim10.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim10.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim10) != HAL_OK) { Error_Handler(); } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // Включаем тактирование портов __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOH_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); // Устанавливаем начальное состояние выходов (все выключены) HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15 |GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8|GPIO_PIN_12|GPIO_PIN_10|GPIO_PIN_11, GPIO_PIN_RESET); // Настройка пина кнопки (PA2) как вход GPIO_InitStruct.Pin = GPIO_PIN_2; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; // Без подтяжки (если есть внешняя) HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // Настройка пинов сегментов и разрядов на PORTB как выходы GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15 |GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // Двухтактный выход GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // Низкая скорость (для индикаторов) HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Настройка пинов сегментов на PORTA как выходы GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_12|GPIO_PIN_10|GPIO_PIN_11; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == htim10.Instance) // Если прерывание от таймера 10 - обрабатываем кнопки { BUTTON_TimerProcess(); } } void Error_Handler(void) { __disable_irq(); while (1) { } }
Важный момент в коде, это калибровка, по скольку производитель не удосуживался ставить качественные кварцевые резонаторы часы часто спешат или отстают.
HAL_RTCEx_SetSmoothCalib(&hrtc, RTC_SMOOTHCALIB_PERIOD_32SEC, RTC_SMOOTHCALIB_PLUSPULSES_SET, 270);
Кнопка всего одна, длительное нажатие регулирует часы, короткое минуты. Прошиваем, собираем на макете и вроде все работает, В поддельных stm32 часто не работает RTC этот момент тоже стоит проверить.

Пока занимался кодом пришли платы:

Вот они, черненькие, красивые, переносим компоненты из купленных модулей на плату, в репозитории (ссылка ниже) есть фото какие компоненты с плат куда запаивать. Сначала dc-dc преобразователи, проверяем напряжение и в последнею очередь паяем МК, предварительно залив на него прошивку. Так же не забываем про индикаторы.

Теперь нужен корпус. Я перенёс 3D-модель платы в Fusion 360 - так проще точно соблюдать отступы под отверстия, кнопку и разъём Type-C.
В корпусе предусмотрено:
Отверстие под кнопку.
Вырез под USB Type-C.
Отверстия для крепления платы.


Осталось собрать все в кучу и готово!

Ночью смотрятся тоже весьма не плохо.

Спасибо за внимание! Все файлы проекта (Gerber, схема в Altium, исходники прошивки, 3D-модель корпуса и HEX-файл) лежат в открытом репозитории на GitHub - ссылка в конце статьи.
Если у вас есть вопросы - пишите в комментариях, постараюсь ответить.
Комментарии (36)

siealex
06.04.2026 14:33ИВ-6 накал 1.05 В +/- 5%.

Nozyl Автор
06.04.2026 14:33В разных справочниках написано по разному, в любом случае диапазон допустимых значений 1,0 - 1,35 В.
К тому-же это еще зависит от износа лампы, мне например попадались ИВ-11 которые ни в какую не хотели работать если напряжение накала меньше 1,8 В.

S_WW
06.04.2026 14:33
Схема сделана на AVR, ATmega16, пинание 12 Вольт, step-up тактируется самим процессором, он же регулирует напряжение ламп. Лампы ИВ-11, разделительная ИВ-15. Есть возможность регулировать яркость, причём вечером в заданное время он сохраняет текущее напряжение в EEprom и загружает из него ночное напряжение. А утром сохраняет ночное и устанавливает дневное. Время смены напряжения можно задавать, причём в субботу и воскресенье он устанавливает дневное напряжение на час позже. Коррекция задаётся в секундах в сутки, ход подстраивается программно по алгоритму Брезенхама. Корпус из бука выточен на станке ЧПУ.

S_WW
06.04.2026 14:33

Nozyl Автор
06.04.2026 14:33Интересно, можно чуть подробнее ? Какие напряжения используется в режиме день/ночь и что за микросхема перед каждой лампой ?

S_WW
06.04.2026 14:33В часах есть кнопки для регулировки яркости. При нажатии процессор увеличивает или уменьшает скважность ШИМ, которая идёт на step-up. Пользователь визуально устанавливает удобную ему яркость. Но ночью дневная яркость слишком велика, спать невозможно, поэтому я сделал переключение на дневную/ночную. AVR удобны тем, что там есть встроенный EEprom, где можно хранить установленные пользователем уровни яркости, времена переключения, а также текущие день, месяц, год и величину коррекции - чтобы не устанавливать их заново при выключении питания.
Для резервного питания я сначала поставил ионистор на 2 Фарады. Поначалу он неплохо работал, держал время до 5 часов. Но через полгода он вдруг сдох. Оказалось, что их срок службы всего 1000 часов. И вообще, это не конденсатор, а нечто среднее между конденсатором и аккумулятором. Поэтому пришлось поставить держатель для батарейки.
На сегменты подаю напряжение ключом UDN2982, а на сетки через сборку PHC2300:
Да, ещё. После долгих сомнений соединил все накалы последовательно и подключил их к питанию 6 Вольт, 5 Вольт для процессора получаются из 6-ти пропусканием через диод LL4148 - дёшево и сердито. Визуально разности в яркости не видно вообще, даже при минимальной яркости.

Vilos
06.04.2026 14:33Я с ионисторами ни разу не сталкивался. "1000 часов" про которые вы пишете - это в смысле время которое они могут быть под напряжением? Ну тогда напрашивается следующая схема: поскольку они нужны только в те моменты когда отключено электропитание, то заряд копить и удерживать на конденсаторе большой ёмкости, а когда питание отрубается, то заряд перетекает на ионистор с конденсатора и тот уже отдает питание в прибор.....не судите строго, я не очень знаком с ионистором, но мне кажется такой вариант его использования жизнеспособен, поскольку эти 1000 часов будут меньше расходоваться на нем.

VT100
06.04.2026 14:33Спасибо за отсутствие бесяче-попсячей подсветки баллонов.
Нити накала было бы неплохо сместить “под землю”, для надёжного гашения цифр.

Daddy_Cool
06.04.2026 14:33Красота! Но позвольте... неприкрытые лампы выглядят небезопасным пижонством (как и в ламповых усилках), кошка пробежала, хвостиком махнула... Можно бы сетку... или клетку.
Типа такой, но более редкую


S_WW
06.04.2026 14:33Ну, если честно, цель создания таких девайсов (как и ламповых усилков) - именно пижонство. (В хорошем смысле :-) И как раз поэтому лампы выставляют для всеобщего обозрения.

Naevus
06.04.2026 14:33кошка пробежала, хвостиком махнула... Можно бы сетку... или клетку.
Не все кошки согласятся жить в клетке

dimone73
06.04.2026 14:33А какой ресурс у сиих ламп? Ну поскольку он у всех ламп невысок. А тут с завода то 50000 часов хотя бы на 5 лет есть? Не говоря про остатки....

Nozyl Автор
06.04.2026 14:33Ресурс 50000 часов подразумевают что в за этот период яркость не будет деградировать, у меня подобные часы только на AVR работают уже почти 10 лет, но яркость на них уже меньше некуда.

dimone73
06.04.2026 14:33Нашел паспорт. 10000 до снижения яркости до определенного порога. Но в общем не много.

YMA
06.04.2026 14:33Просто вопрос всем... А обещанные 80000 часов для неонок - реально? Или так, теория? Если на них сделать? (не обычные nixie clock, а матричные часы)

trikot
06.04.2026 14:33Ну зачем же этот убогий способ изготовления корпуса применять... Понимаю, что напечатать это быстро, но смотрится изделие так себе. Такие корпуса только для каких-то промизделий годятся. Проявите фантазию, используйте другие материалы, результат будет радовать долгие годы.

Prohard
06.04.2026 14:33Для ИВ-11 напряжение сетки максимум 30 вольт, а в этом устройстве 45 вольт. Часы на ИВ-11 выпускались с 1976 по 1979 год, а затем перешли на динамическую индикацию.

Nozyl Автор
06.04.2026 14:33Согласно документации для работы в импульсном режиме необходимо 50-70В. От 45В тоже работает.

Kobagugi
06.04.2026 14:33А не думали сделать динамическую регулировку яркости через фоторезистор? Лампы-то выгорают со временем, жалко гонять их на сотку глубокой ночью

Nozyl Автор
06.04.2026 14:33Сперва решил сделать "Базовую" версию, если будет свободное время в дальнейшем улучшать.

S_WW
06.04.2026 14:33Сколько я видел девайсов с регулировкой через фоторезистор - все они регулируют очень плохо. Линейность глаза не совпадает с линейностью фоторезистора, поэтому лучше сделать ручную регулировку. Тогда юзер будет ставить такую яркость, какая ему нравится.

PlainSoul
06.04.2026 14:33
О, подержите мое пиво. Тоже сделал часы на лампах ив...не помню какие так как было это в 2016 году. С того момента работают идеально. 10 лет уже прошло. Яркость ламп регулируется. С краю платы с правой стороны можно заметить фототранзистор. Микроконтролер Stm32l (какой конкретно уже не помню) его измеряет и регулирует dc/dc преобразователь. Лампы так же отключаются на ночь с 10 вечера до 6 утра. Регулировка прекрасно работает, глаза никак это не замечают.
Фишка моих часов,как кто то уже предлагал в данной теме, это использования атомного стандарта частоты для синхронизации. Я до такого не дошел, так как на то время csac стоили 3 тысячи баксов, хотя планировал. Зато часы построенны на базе высокостабильного, термостатированного кварцевого генератора с очень низким энерго потреблением. Это устройство в позолоченном корпусе в центре часов. Изначально этот модуль синхронизируется по gnss для получения времени. Модуль gnss вклеен в заднюю стенку и поэтому на фото не видно. Так же в часах есть батарейка литий ионная, которая на фото видна под основной платой. Идея в том, чтобы не обесточивать модуль синхронизации при отключении питания от кабеля usb. Лампы при этом отключаются через 10 секунд после исчезновения внешнего питания, так как потребляют больше всего они. А так батарейки хватает на несколько часов.

NickDoom
Ну что, всё правильно. За точность отвечает точное, за дизайн — красивое.
Можно и автокалибровку по GPS/PPS сделать, и по температурному датчику (кварц такой кварц), можно хоть внешний точный генератор поставить (сейчас чуть ли не рубидий уже в чипдипах продаётся).
Но это всё внутри коробочки. А за красоту отвечают лампы :)
Nozyl Автор
Интересное предложение, можно поставить какой нибудь NEO-6M, место на плате позволяет, и стоит он пару баксов.
vrtmn
А может какую-нибудь ESP8266 и по NTP синкать? А то внутри помещения GPS сигнал не осбо ловиться может
Nozyl Автор
Смущает что точку доступа и пароль нужно зашивать в код.
Или как вариант, подключение по RS232, с приложением на каком нибудь питоне для первичной настройки, вот это можно попробовать.
vrtmn
Нет, не нужно
WiFiManager позволяет настраивать подключение к WiFi
https://github.com/zhouhan0126/WIFIMANAGER-ESP32
При первом запуске создается точка доступа, к ней нужно подключиться и там уже настроить WiFi - данные сохранятся
Я в своих часах (не nixie) так и сделал
https://github.com/orangetrialtruck/stc_diyclock/blob/master/docs/nmea/ESP8266_ntp/ESP8266_ntp.ino
NickDoom
…а ему много и не надо — раз в месяц принял строку со временем, распарсил и поправил и время, и фактор коррекции…
А по PPS, если хватит шустрости, можно корректировать поправку от термодатчика. Если спутники хотя бы пару минут присылают хороший PPS — можно успеть измерить свой кварц и сопоставить его враньё с температурой.
Kobagugi
ESP8266 сюда просится идеально. Один раз настроил синхронизацию по NTP, и забыл про эти кнопки настройки времени как страшный сон)