惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
T
Threat Research - Cisco Blogs
H
Hacker News: Front Page
N
News and Events Feed by Topic
Know Your Adversary
Know Your Adversary
Cisco Talos Blog
Cisco Talos Blog
SecWiki News
SecWiki News
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
K
Kaspersky official blog
Forbes - Security
Forbes - Security
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
P
Privacy & Cybersecurity Law Blog
H
Heimdal Security Blog
Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
V
Vulnerabilities – Threatpost
T
Tenable Blog
T
Tailwind CSS Blog
P
Privacy International News Feed
WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
博客园 - Franky
Hacker News: Ask HN
Hacker News: Ask HN
Jina AI
Jina AI
C
Cybersecurity and Infrastructure Security Agency CISA
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
雷峰网
雷峰网
Vercel News
Vercel News
A
About on SuperTechFans
爱范儿
爱范儿
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
The Last Watchdog
The Last Watchdog
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 司徒正美
量子位
博客园 - 三生石上(FineUI控件)
J
Java Code Geeks
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recorded Future
Recorded Future
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Martin Fowler
Martin Fowler
Project Zero
Project Zero

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Часы из вольтметра. Версия 2026
JustJeremy ( · 2026-05-20 · via Все публикации подряд на Хабре

Уровень сложностиПростой

Время на прочтение5 мин

Охват и читатели3

Туториал

Перевод

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

В далёком 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