Нашли в сети статью одного автора о том, как он усовершенствовал свою версию часов из вольтметра — перевели её для вас.

В далёком 2019 году я собрал свои первые простые часы с вольтметром, получилась вот такая штука:

Первая версия. Корпус, к слову, из вишни
Первая версия. Корпус, к слову, из вишни

Как вы поняли из названия, в этих часах для отображения времени используются аналоговые вольтметры, а не привычные всем циферблаты. Идея не моя — в сети множество подобных проектов и инструкций, кто во что горазд. Тогда я просто сделал такие часы в деревянном корпусе и поставил их себе на рабочий стол. 

Потом со временем понял, что таких поделок много, и выглядят они плюс-минус как моя. То есть коряво и довольно кустарно. Так что я решил сделать что-то красивое и современное, и задокументировать весь процесс создания. Он под катом.

Разработка началась с создания грубого макета в программе для 3D-моделирования. Я использовал Rhino3D.

Макет нового дизайна
Макет нового дизайна

Для новой версии часов я использовал три обычных панельных вольтметра, купленных на Amazon (ссылка, около 9 долларов за штуку). Я их разобрал, тщательно измерил циферблаты, а затем распечатал на самоклеящейся бумаге новые наклейки. Если вам нужны мои шаблоны в PDF — они здесь.

Новые наклейки
Новые наклейки

Обратите внимание, что на новом часовом циферблате 13 делений (от 0 до 12) а на минутном и секундном — 61 деление (от 00 до 60). Это потому, что я хотел реализовать непрерывное движение каждой стрелки. Иными словами — в 11:30 часовая стрелка должна была не просто стоять на отметке 11, а двигаться к двенадцатому делению, даже если бы она его не достигла.

Помимо кучи других проблем, у дешевых вольтметров Baomain 65C5, которые я купил, откровенно уродский пластиковый фланец. Я подумал, что будет круто его скрыть и использовать декоративный утопленный узор, чтобы передняя панель выглядела поинтереснее. Благодаря этой детали я смог вырезать переднюю и заднюю панели на станке с ЧПУ, а не делать корпус целиком вручную (как я сделал в первой, вишнёвой версии).

Получилось куда красивее
Получилось куда красивее

В качестве основного материала для корпуса я в этот раз взял кленовую доску, которую распиливают, строгают и обрабатывают обычными инструментами, а затем доводят до совершенства на станке с ЧПУ:

Обработанные передняя и задняя панели
Обработанные передняя и задняя панели

Что делать, если станка с ЧПУ у вас нет? Не печальтесь — проще всего будет собрать панель из двух склеенных частей, вырезав каждую из них по распечатанному (бумажному) шаблону. Чтобы идеально выровнять изгибы, можно использовать шлифовальный станок.

А вот изогнутая боковая стенка потребовала немного иного подхода. Чтобы добиться цельного вида, мне надо было согнуть плоский деревянный лист по шаблону. Для соблюдения нужного радиуса без использования станка для сгибания паром я решил сделать ряд надрезов с внутренней стороны. 

Вот так боковушки сгибаются вручную
Вот так боковушки сгибаются вручную

Дерево надо сначала было увлажнить, а затем зажать его и дать высохнуть. Через пару дней я приклеил изогнутую боковую стенку к передней и задней панелям, ещё один шаблон, вырезанный из куска фанеры, помог добиться точного прилегания без необходимости возиться полдня с зажимами и ремнями с храповым механизмом:

Склеиваем корпус часов с помощью внешнего шаблона (из фанеры)
Склеиваем корпус часов с помощью внешнего шаблона (из фанеры)

В общем, вот так выглядит готовая деталь после шлифовки и покрытия нитроцеллюлозным лаком:

Вроде, круто вышло.

Что внутри

Внутрянка, само собой, будет вам гораздо менее интересна, чем корпус. Но я всё равно расскажу.

На сборку ушло около часа: я взял почтенный микроконтроллер AVR128DB28, запитал его от сетевого адаптера и подключил к кварцевому резонатору на 8 МГц (ECS-80-18-4X-CKM). Если что, кварцевый резонатор на 32,768 кГц тоже подойдет. Панели подключены к трем цифровым выводам (PC0, PC1, PC2). Два входных контакта (PD6 и PD7) я соединил с двумя небольшими кнопками на задней панели — они нужны для установки времени.

Схема
Схема

Обратите внимание, что для управления счетчиками не нужны цифро-аналоговые преобразователи или какие-либо другие дополнительные компоненты. Я просто использую относительно высокочастотную последовательность однобитных цифровых импульсов. Всё остальное делает инерция счетчика (ну, и индуктивность катушки электромагнита), приводя его в промежуточное положение, пропорциональное скважности сигнала, управляемой программным обеспечением.

Вот код.

Скрытый текст
/*

  Meter clock, version 2
  ----------------------

  Context: https://lcamtuf.substack.com/p/a-nicer-voltmeter-clock

  MCU: AVR128DA28

  Pinout: PC0, PC1, PC2 - PWM outputs to meters (other side to gnd)
          PA0, PA1      - 8 MHz crystal + 18 pF caps to gnd
          PD6, PD7      - time adjustment buttons (other side to gnd)

  The only other MCU connections are power supply pins and the UPDI
  programming header.

  Meter faces: https://lcamtuf.coredump.cx/soft/embedded/meter_clock2.pdf

  Complaints to: <lcamtuf@coredump.cx>

 */

#define F_CPU 8000000

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

/* User-friendly typedefs */

typedef int8_t   s8;
typedef uint8_t  u8;
typedef int16_t  s16;
typedef uint16_t u16;
typedef int32_t  s32;
typedef uint32_t u32;

/* Configure clock. External 8 MHz crystal on PA0, PA1 */

static void setup_clock() {

  CCP = 0xd8;                       /* Unlock register access   */
  CLKCTRL.XOSCHFCTRLA = 0b10000001; /* Enable external clock    */

  while(!(CLKCTRL.MCLKSTATUS & 0b10000));

  CCP = 0xd8;                       /* Unlock register access   */
  CLKCTRL.MCLKCTRLA = 3;            /* Switch to external clock */

  while(!(CLKCTRL.MCLKSTATUS & 1));

}

/* Configure ports. */

static void setup_ports() {

  PORTA.DIR = 0b11111111;
  PORTC.DIR = 0b11111111;
  PORTD.DIR = 0b00111111; /* PA6, PA7: buttons */
  PORTF.DIR = 0b11111111;

  /* Pull-up for buttons */
  PORTD.PIN6CTRL = 0b00001000;
  PORTD.PIN7CTRL = 0b00001000;

  /* Slew rate limit for PWM output */
  PORTC.PORTCTRL = 1;

}

/* Configure tick update interrupt to run at 10 Hz */

static void setup_timer() {

  TCA0.SINGLE.INTCTRL  = 0b00000001; /* OVF interrupt every PER cycles     */
  TCA0.SINGLE.CTRLA    = 0b00001100; /* Clock prescaler / 256 (31.250 kHz) */
  TCA0.SINGLE.PER      = 3125 - 1;   /* Effective frequency 10 Hz          */
  TCA0.SINGLE.CTRLA   |= 1;          /* Timer enable                       */

}

/* Update time, handling wrap-around. */

static volatile u8  cur_hr, cur_min;   /* Hour (0-11) and minute (0-59)     */
static volatile u16 cur_secx10;        /* Tenth of a second counter (0-599) */

ISR(TCA0_OVF_vect) {

  cur_secx10++;

  if (cur_secx10 == 600) {

    cur_secx10 = 0;
    cur_min++;

    if (cur_min == 60) {

      cur_min = 0;
      cur_hr++;

      if (cur_hr == 12)  cur_hr = 0;

    }

  }

  TCA0.SINGLE.INTFLAGS = 1; /* Acknowledge interrupt */

}

/* Main entry point */

int main(void) {

  setup_clock();
  setup_ports();
  setup_timer();
  sei();

  /* Main PWM loop. Synchronous counter running from 0 to 599 at several hundred kHz. */

  u16 duty_ctr = 0; /* PWM counter        */
  u8  key_last = 0; /* Previous key state */

  u16 adj_minx10, adj_hrx10, adj_secx10; /* Computed duty cycles for meters */

  while (1) {

    /* Compute duty cycles. The minute gauge has divisions from 0 to 60. One minute corresponds
       to a duty cycle step of 10, so we multiply the minute counter accordingly, and then add
       another value between 0 and 9 based on the state of the second counter.

       For the hour gauge, we have divisions from 0 to 12, and one hour corresponds to an
       increment of 50. We multiply the hour counter by 50 and add another 0-49 depending on
       the minute counter.

       This is also where you can incorporate fudge factors if the meters aren't precise. */

    if (!duty_ctr) {
      adj_minx10 = cur_min * 10 + cur_secx10 / 60,
      adj_hrx10  = cur_hr  * 50 + adj_minx10 / 12,
      adj_secx10 = cur_secx10;
    }

    /* PWM actuation */

    u8 pcval = 0;

    if (adj_secx10 > duty_ctr) pcval  = 0b100;
    if (adj_minx10 > duty_ctr) pcval |= 0b010;
    if (adj_hrx10  > duty_ctr) pcval |= 0b001;

    PORTC.OUT = pcval;
    duty_ctr++;

    /* After 600 cycles, we reset the PWM counter and check for keypresses. */

    if (duty_ctr == 600) {

      duty_ctr = 0;

      u8 pd = (PORTD.IN >> 6);

      /* Both inputs are high: reset state and continue */
      if (pd == 0b11) { key_last = 0; continue; }

      /* At least one button pressed. If this is a continuation of a previous keypress, bail out. */
      if (key_last) continue;
      key_last = 1;

      /* Key 1 advances the minute dial while zeroing the seconds. */
      if (!(pd & 0b10)) {
        cur_secx10 = 0;
        if (++cur_min == 60) cur_min = 0;
      }

      /* Key 0 advances the hour dial without messing up any of the other dials. */
      if (!(pd & 0b01)) {
        if (++cur_hr == 12) cur_hr = 0;
      }

    }

  }

}

Основная идея заключается в том, чтобы с помощью прерывания таймера, синхронизированного с кварцевым резонатором, продвигать счётчик с частотой 10 Гц. После этого основной цикл обработки событий вычисляет соответствующий коэффициент заполнения и вручную переключает выходные контакты. Несмотря на то, что в микросхеме есть аппаратный модуль ШИМ, задача настолько проста, что использование схемы ШИМ ничего нам не даст.

А вот видео с «перелистыванием», снятое примерно в 11:59:59

Комментарии (19)


  1. Arhammon
    20.05.2026 11:19

    Иногда ретро бывает перебор - первая версия на "современных" аналоговых головках выглядит намного читабельнее. Особенно важно учитывая экзотический способ обозначения времени.


    1. KN_Dima
      20.05.2026 11:19

      Красивое.
      Но совершенно нефункциональное...


  1. JeikiS
    20.05.2026 11:19

    А где флайбэк диоды? Понимаю что в данной ситуации, видимо, обратная ЭДС настолько слаба, что затвор транзистора в авр-ке вывозит морщась, но надо, по уму. А корпус конечно бомба! Респектую!


  1. S-trace
    20.05.2026 11:19

    Обратите внимание, что на новом часовом циферблате 13 делений (от 0 до 12) а на минутном и секундном — 61 деление (от 00 до 60).

    Про 60-ю секунду координации (когда время становится 23:59:60) я слышал (автор же, кажется, нет (судя по коду)).

    А вот что он хотел сказать этой високосной минутой? В какой момент время может принять значение hh:60:ss?


    1. Darkness_Paladin
      20.05.2026 11:19

      Вы не поняли задумку автора. Тринадцатое часовое деление ("12") никогда не достигается: в течение 11го часа (от 11:00 до 11:59 и от 23:00 до 23:59) часовая стрелка ползёт от "11" к "12", а потом, в 12 часов, она падает на ноль и ползёт уже от нуля к часу.

      С минутной стрелкой та же тема. Метка "60" нужна, чтоб ползти к ней в последнюю минуту часа, а когда минута кончится, стрелка упадёт на ноль.

      Про 60-ю секунду координации (когда время становится 23:59:60) я слышал

      Эту лишнюю секунду добавляют к суткам 30 июня или 31 декабря по решению Международной Службы Вращения Земли раз в несколько лет без календарной привязки, с целью синхронизации Единого Времени с реальным вращением Земли.

      В часах без синхронизации через NTP состояние "23:59:60" не предусмотрено, ибо в стандартных сутках секунд в точности 60х60х24=86400 штук, не больше и не меньше.


      1. DarkTiger
        20.05.2026 11:19

        Точно! Понял, что свербело, пока статью читал: подсознательно ждал, когда же он до синхронизации по NTP дойдет.


  1. Wlas
    20.05.2026 11:19

    Я бы на одном индикаторе сделал, чтобы было три шкалы, как на старых стрелочных мультиметрах, и чтобы раз в секунду, например они по очереди показывали часы, минуты секунды и загорался индикатор на неонке, ну ладно, жёлтом светодиоде, который бы отображал, что сейчас выводится. Или, как вариант, ещё хуже, шкалу на три части поделить, часы, минуты, секунды, тогда и индикатор не нужен. Это же не время смотреть, а удивлять непосвящённых), так что важно, чтобы стрелка дергалась, а не удобство


    1. MaFrance351
      20.05.2026 11:19

      А я как-то думал на советском цифровом милливольтметре на ГРИ сделать. Всего-то двенадцатибитный ЦАП нужен и ОУ.


  1. trikot
    20.05.2026 11:19

    Такие часы давным давно сделал мой друг из Питера Валерий Афанасьев. Кому интересно, загляните на его сайт. https://va-steam.ru/index.php
    Такие часы давным давно сделал мой друг из Питера Валерий Афанасьев. Кому интересно, загляните на его сайт. https://va-steam.ru/index.php


  1. stalker_316
    20.05.2026 11:19

    Можно ещё на тахометре и спидометре от авто сделать подобное...


    1. Vsevo10d
      20.05.2026 11:19

      Помню, мне было 20 с чем-то, и я (впервые на Земле, конечно же!) придумал, что прикольно было бы сделать на автомобиле задний дворник, синхронизированный со спидометром, ну и имеющий соответствующую наклейку на стекло.


      1. randomsimplenumber
        20.05.2026 11:19

        И чтобы стекло очистить, нужно пару раз газонуть в пол?


  1. FFLY
    20.05.2026 11:19

    Покупал такой готовый набор, плата, микроконтроллер с программой и обвязка. Предполагалось подключать к вольтметрам-831


  1. MaFrance351
    20.05.2026 11:19

    Пожалуй, самый антуражный вариант:

    (отсюда)


  1. Darkness_Paladin
    20.05.2026 11:19

    Дизайн забавный (хотя часы из шапки, с квадратыми головками, мне нравятся больше) -- но зачем в 26м году электронику часов на раритетной АВРке собирать? ИМХО, сейчас делать часы, не имеющие вайфая и потому не умеющие синхронизироваться с NTP -- как-то не айс. Да и ценник у AVR128DB28, мягко говоря, конский -- за цену этого камня можно взять esp8266 с вайфаем, и ещё останется на пиво.


  1. mishkin79
    20.05.2026 11:19

    Из кубика Рубика сделайте ещё) Можно капельный полив и модулируя стробоскопический эффект - цифры отображать. Или экзистенциальные часы - посадить дерево и с мыслью "когда оно вырастет мне точно пипец" пойти почитать книгу, или наконец заняться делами)


  1. coden12
    20.05.2026 11:19

    в корпусе индукционного счётчика где-то видел


  1. Zhabrozavr
    20.05.2026 11:19

    Макет выглядит отлично, деревянный корпус как будто не очень сочетается с приборами.


  1. butsan
    20.05.2026 11:19

    Плюсанул бы за фото - всё чисто и аккуратно. Кармы не хватает.