В комментариях к "серверу точного времени" (https://habr.com/ru/articles/1023414) предлагали вдобавок к NTP и GPS подключить еще и DCF77, как еще один источник времени.
И я таки сделал это, хоть и в виде отдельной железки, а поскольку техника тут аналоговая - были свои нюансы.
В качестве справки:
DCF77 - это радиостанция, передающая точное время от атомных часов, собственно, это ее основное назначение.
Расположена в Европе, в Германии, неподалеку от Франкфурта. Вещает на длинных волнах на всю Европу, захватывая в том числе часть exUSSR. Передает сигнал, содержащий информацию о времени и дате, UTC+1/UTC+2 в зависимости от "летнего времени".
Также передает местную погоду и может быть использована как средство оповещения, но нас это мало касается.
Рабочая частота 77.5 кГц - поэтому и "DCF77".
Её сигнал может быть использован для автонастройки электронных часов, для чего выпускаются недорогие модули, которые можно встраивать в различные устройства, там, в Европе.
А вот у нас это всё работает довольно плохо.
Проблема простая: расстояние.
Несмотря на большую мощность передатчика и хорошее распространение длинных волн на большие расстояния - 2000 км это 2000 км.
Типовой DCF77-модуль имеет компактную магнитную антенну, которую наверное удобно размещать в типовых настольных часах:

Вообще, для понимания, для приема электромагнитных волн существует два типа антенн: электрические и магнитные, соответственно для электрической или магнитной составляющей поля.
Электрические - кусок провода, металлический штырь, рисунок на печатной плате - их размеры должны соответствовать длине волны (1/4, 1/2, 1), длина волны зависит от частоты и скорости света (примерно в метрах = 300000000 / Гц), поэтому для высокочастотных сигналов типа сотовой связи или WiFi они очень удобны.
Но для частот типа 77500 Гц длина волны 3.870км (поэтому они и длинные волны), и более-менее рабочая антенна должна иметь длину около километра (1/4 от 3.870).
Для таких частот удобнее использовать магнитные антенны: по сути катушку провода, намотанную на стержне (и иногда без него).
Там есть свои нюансы, по поводу того что катушка должна быть правильно ориентирована относительно силовых линий магнитного поля волны - но это уже детали.
Вот как раз, как в случае этого модуля: ферритовый стержень с обмотками.
Но физику не обманешь: размер имеет значение, чем больше стержень - тем выраженнее эффект.
Маленькая магнитная антенна, наверное, неплохо работает в Европе, или рядом.
В моем случае ее оказалось недостаточно для устойчивого приема сигнала: то мусор, то вообще ничего.
Тут надо сказать о том, что, собственно говоря, мы ловим?
DCF77 передает просто некоторый сигнал, который раз в секунду прерывается на определенное время. Пауза 0.1 сек - логический "0", пауза 0.2 секунды - логическая "1". Нет паузы - конец цикла. Потом передача начинается заново.
Длина одного цикла - 60 секунд, длина сообщения 58 (или 59, смотря как считать) бит.
Таблица декодирования (из Вики):

Модуль RC8000, который как раз должен это ловить, принимает сигнал, только инвертирует, вместо пауз - импульсы: по хорошему они должны быть 0.1 сек или 0.2 сек.
Он не декодирует сигнал в код, он просто принимает и фильтрует аналоговый сигнал.
И когда не может принять качественный сигнал - либо "молчит", либо начинает сыпать случайными имульсами в случайное время, потому что ловит помехи.
Тут еще одно важное отступление: на дальнее распространение радиоволн сильно влияет состояние ионосферы планеты, которое под влиянием излучения ближайшей звезды может существенно меняться.
Если коротко - ночью дальняя связь лучше, и то, что не ловится днем - может ловиться ночью. Если повезет, конечно.
В данном случае - не помогла и ночь, модуль либо молчал, либо сыпал мусором.
Оставался вариант - попробовать увеличить размер магнитной антенны.
Для этого нужно взять ферритовый стержень побольше, намотать на него катушку и подключить вместо штатной антенны.
Найти стержень побольше оказалось не так просто: современная техника высокочастотная, магнитных антенн либо уже нет, либо они маленькие, а вот такое нак надо - было в старинных советских радиоприемниках ДВ/СВ.
Пришлось найти такой, и извлечь феррит из него (ну или заказывать из Китая, где есть всё - но это долго).
Катушка на антенне - это не просто так, это часть старого доброго колебательного контура: резонансная частота контура зависит от индуктивности катушки и емкости конденсатора, а от резонанской частоты зависит, что именно будет ловить антенна.
Таким образом, надо было намотать катушку такой же индуктивности, как штатная.
К счастью, сейчас есть простые приборчики, которые могут измерять свойства различных радиоэлементов, в том числе и индуктивность: так выяснилось, что индуктивность штатной катушки на антенне - 1.34мГн.
Тут даже неважно, насколько точно она измерена в мГн - важно сделать такую же.
Для этого нужно просто намотать на стержень правильное количество витков провода, очень хорошо подходит обмоточный ПЭЛШО (провод электрический лакированный в шелковой оплетке) - ради него пришлось посетить магазинчик радиоприбабахов.
Чтобы узнать нужное количество витков - можно сделать, например, так:
Намотать 20 витков - измерить индуктивность: получится сколько-то там.
Общая индуктивнось катушки зависит от свойств стержня (которые мы не знаем), но важнее - зависит от квадрата их количества, то есть если для 20 она X, то для 2*20 будет X*2^2, для 3*20 будет X*3^2, и так далее.
В общем, получилось, что нужно чуть больше 100 витков.
Но есть проблема: чтобы попасть точно в заданную индуктивность нужно намотать точно рассчитанное количество витков, включая нецелые (причем не ошибиться в расчетах).
Сделать это не так просто, к тому же нет возможности подстроить под нужную частоту конденсатор колебательного контура, а значит нельзя компенсировать неизбежные погрешности.
Но есть древний лайхак, времен массового использования ферритовых антенн: можно взять витков чуть побольше, скажем, 120, разделить одну катушку на две, намотать каждую на отдельной бумажной втулке, соединить последовательно, а потом раздвигать их по стержню: чем больше общая длина - тем ниже будет индуктивность.
Передвигая их по стержню - подобрать оптимальную индуктивность и наилучшее качество приема.

И вот, собственно, пробуем:
У модуля RC8000 четыре вывода: VCC, GND, OUT и EN.
VCC - 3.3в, OUT - наши импульсы, EN - enable, который надо подключить к GND (почему так? а вот так, логично же: enable на 0).
Вместо штатной антенны - франкенштейн самолепный.
OUT - пока нужны просто импульсы, поэтому подключаем просто светодиодик.

Лайфхак: все видели светодиодные ленты, в т.ч. со светодиодами 2525 - мелкими квадратными. Они не слишком долговечные, некоторые светодиоды чернеют и перегорают, ленты выбрасывают и меняют - так вот, подобные светодиоды идеальные индикаторы! Они очень чувствительные, вспышки хорошо заметные, и хорошо сочетаются с 3-вольтовой логикой. Именно такой светодиод и будет индикатором импульсов
Чтобы исключить помехи при настройке - никаких работающих ESP рядом, никаких импульсных блоков питания, две батарейки по 1.5В.
Включаем, постепенно раздвигаем катушки - и вот пошел сигнал.
Раз в секунду - чуть дольше, чуть короче, а вот пауза - и снова пошли вспышки раз в секунду.
Готово, сигнал DCF77 принимается, даже на парковке днем.
Фиксируем катушки - и вот теперь можно попробовать подключить это к ESP.
И новая проблема: ESP интересна наличием WiFi, а батарейки и WiFi - вещи плохо совместимые.
Конечно, можно подключить всё через блок питания - но современные импульсные блоки очень шумят в ДВ-диапазоне (обычно всем наплевать, сейчас он почти не используется, но не в этом случае).
К счастью, нашелся старый трансформаторный блок питания - из тех, которые шли когда-то к телефонам. Он оказался на 12 вольт, но вот тут уже их можно понизить до 3.3 модулем DCDC - у него рабочая частота выше, чем у радиосигнала DCF77, эта антенна ее не ловит.
Модуль RC8000 вместе с аннтенной подключен отдельно, проводом - подальше от ESP.
Пришлось подключить к нему конденсатор на VCC и GND, побольше, без него он работать отказывался.

Сигнальный выход модуля - ко входу ESP, на котором настроена обработка прерываний.
Смысл в том, что импульсы на этом входе будут вызывать прерывания: при этом будут отмечаться времена возникновения и спада импульсов, что позволит вычислять длительнность имульсов и пауз.
В идеале DCF77 имеет строгие правила: импульсы начинают идти ровно с началом очередной минуты (атомные часы, вот это всё), и идут каждую секунду кроме 59-й.
Логический 0 представлен импульсами длительностью 100 мс, логическая единица - 200 мс, после каждого - пауза до начала следующей секунды, 900 и 800 мс соответственно.
На деле - есть шумы, которые даже при хорошем приёме немного портят время импульсов, мешая их распознавать.
В процессе отладки потребовалось анализировать поток времён импульсов-пауз, из-за ограниченного размера памяти и необходимости быстрой отработки прерывания пришлось делить значения на степени двойки (стандартная операция деления - долго, но деление на 2-4-8 делается сдвигом на N бит вправо, так быстрее).
Оказалось, это удобно - сразу избавляемся от слишком мелких различий, и тогда анализ импульсов сводится к нескольким правилам:
- если пауза была больше некоторого значения A1 - новый импльс начинает минуту.
- если импульс был меньше некоторого значения A2 - это был 0
- если был больше A2, но меньше другого значения A3 - это была 1
Если поделить так миллисекунды на 2^6 ( >> 6) и добавлять к значениям символ 'A' - лог времен начинает напоминать ДНК-код (]BNBNDOBNDOBN), можно просто сравнивать по символам: B = 0, D = 1, >Z - начало минуты, можно читать глазами из лога.
А всё что не укладывается в эту схему - считать ошибками приема.
^BOBOBNDMBNDMDMBOBOBNDMDMBOBODMBNBNDMBNBNDMBNBOBNBNBNBNDMDMBNDMBNBODMBNBOB...
Это очень помогло при отладке, когда почему-то сигнал не принимается (видно в логе - потому что начинает идти шум типа ACBBCDJ - нарушены интервалы).
Биты набираются в 64-битный аккумулятор (в обратном порядке, но какая разница, так просто удобнее), после очередной паузы A1 аккумулятор переходит в текущие данные и начинается сбор новых битов.
И если за время предыдущего сбора явных ошибок не было - по данным строится текущее время, с учетом момента начала новой минуты (передается всегда время следующей минуты, с первым импульсом после паузы она и начинается).
Для ведения полученого времени использована та же библиотека JbTime, что в NTP-сервере, с микросекундами.
И такая же библиотека раздачи NTP JbNTP - прежде всего, чтобы можно было получать время и сравнивать его с другими источниками.
.... #define INTERRUPT_PIN 13 #define READ_PIN(pin) ((GPIP(pin) ? 1 : 0)) volatile byte int_pulse; // счетчик импульсов volatile uint32_t mark_time; // отметка времени volatile uint32_t start_second; // отметка старта новой минуты volatile bool set_second; // флаг готовности установки минуты volatile bool dcf_ok; // валидность текущая volatile uint64_t dcf_data; // данные для обработки volatile uint64_t dcf_tmp; // аккумулятор данных // для отладки вспомогательное volatile byte xlog[180]; volatile byte log_cnt; // --------------------------------------------------- void ICACHE_RAM_ATTR run_interrupt(){ uint32_t tmp = micros(); // отметили микросекунды начала uint32_t diff = millis() - mark_time; // длительность предыдущей фазы mark_time = millis(); // новая метка времени byte sym = (byte)(diff >> 6) + 'A'; // уменьшаем до байта bool signal = READ_PIN(INTERRUPT_PIN); // что там у нас? if(signal) { // импульс if(sym >= 'Z'){ // начало новой минуты start_second = tmp; dcf_data = dcf_tmp; // скидываем старый буфер dcf_tmp = 0; // очищаем буфер set_second = false; if(dcf_ok && int_pulse == 58) set_second = true; // прошлая секунда считана dcf_ok = true; // считаем ОК int_pulse = 0; // битовый счетчик log_cnt = 0; } else{ int_pulse ++; // можно пробовать проверять на соответствие пар // корректные паузы - M,N,O, при этом правильнее BN, BO и DN, DM // но можно и не проверять } }else{ if(sym == 'B'){ // это 0 } else if(sym == 'D' ){ // это 1 dcf_tmp |= (uint64_t)(0x1ULL << int_pulse ); } else { // это мусор dcf_ok = false; } } // для отладки xlog[ log_cnt ] = sym; log_cnt ++; if(log_cnt > 170) log_cnt = 0; } // --------------------------------------------------- void PulseSetup(){ int_pulse = 0; mark_time = 0; dcf_data = 0; dcf_tmp = 0; set_second = false; dcf_ok = false; // для отладки log_cnt = 0; memset((void*)xlog,0,sizeof(xlog)); pinMode(INTERRUPT_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN),run_interrupt,CHANGE); } byte dcf_weights[] = {1,2,4,8,10,20,40,80}; #define CEST_OFFSET 3600*2 #define CET_OFFSET 3600 #include <RTClib.h> // --------------------------------------- void PulseLoop(){ if(set_second){ // пример // 0100011100011000010011100110000001011000010100010101100100100000 // 0000110111110100010010100000110001001000010100010001100100100000 if(dcf_data & 1ULL) return; // must be 0 if(! (dcf_data & (1ULL << 20)) ) return; // must be 1 bool sum = 0; // minute int minute = 0; for(int i = 21; i < 28; i++){ if(dcf_data & (1ULL << i )){ minute += dcf_weights[i - 21]; sum = !sum; } } if((bool)(dcf_data & (1ULL << 28 )) != sum) return; // hour sum = 0; int hour = 0; for(int i = 29; i < 35; i++){ if(dcf_data & (1ULL << i )){ hour += dcf_weights[i - 29]; sum = !sum; } } if((bool)(dcf_data & (1ULL << 35 )) != sum) return; // date sum = 0; int mday = 0; for(int i = 36; i < 42; i++){ if(dcf_data & (1ULL << i )){ mday += dcf_weights[i - 36]; sum = !sum; } } int wday = 0; for(int i = 42; i < 45; i++){ if(dcf_data & (1ULL << i )){ wday += dcf_weights[i - 42]; sum = !sum; } } int month = 0; for(int i = 45; i < 50; i++){ if(dcf_data & (1ULL << i )){ month += dcf_weights[i - 45]; sum = !sum; } } int year = 2000; for(int i = 50; i < 58; i++){ if(dcf_data & (1ULL << i )){ year += dcf_weights[i - 50]; sum = !sum; } } if((bool)(dcf_data & (1ULL << 58 )) != sum) return; DateTime now = DateTime(year, month, mday, hour, minute, 0); unsigned long dtm = now.unixtime(); bool cest = dcf_data & (1ULL << 17); bool cet = dcf_data & (1ULL << 18); if (cest && !cet){ dtm -= CEST_OFFSET; } else if (!cest && cet){ dtm -= CET_OFFSET; } else return; uint32_t usec = micros() - start_second; systime.settime(dtm, usec); systime.fresh = true; if(systime.fresh){ if(RTCSetTime(&systime)){ systime.fresh = false; } } set_second = false; } } // --------------------------------------- void setup(){ ... PulseSetup(); NTPSetup(); ... } void loop(){ ... PulseLoop(); NTPLoop(); ... }
По такой же схеме как там - тут тоже подключен модуль RTC.
Вся разница в том, что единственным источником времени будет DCF77.
И вот - пробую результат:
/sbin/ntpdate -d 192.168.1.49 ntpdig: querying 192.168.1.49 (192.168.1.49) org t1: ed92223c.60d05000 rec t2: ed92223c.9f7af640 xmt t3: ed92223c.9f7af640 dst t4: ed92223c.aecb0000 org t1: 1776788412.378179 rec t2: 1776788412.622970 xmt t3: 1776788412.622970 dst t4: 1776788412.682785 rec-org t21: 0.244792 xmt-dst t34: -0.059815 2026-04-21 19:20:12.622970 (+0300) +0.092488 +/- 0.152309 192.168.1.49 s1 no-leap
Неплохо (0.092488 - отклонение от ранее установленного), учитывая что время тут берется буквально из воздуха.
Можно запускать девайс в работу...
Но есть и минусы: например, обычная дрель-шуроповерт рядом сводит прибор с ума, индикатор моргает как ненормальный.
В общем, это нечто такое, что должно работать долго, неспешно, в деревне, постепенно синхронизируясь как бы само по себе.
Комментарии (13)

jar_ohty
22.04.2026 12:12Немного позанудствую.
Вообще, для понимания, для приема электромагнитных волн существует два типа антенн: электрические и магнитные, соответственно для электрической или магнитной составляющей поля.
Это касается укороченных антенн. Полноразмерная антенна в равной мере принимает как магнитную, так и электрическую компоненты электромагнитной волны. А электрически малые антенны да, принимают либо в чистом виде электрическое (короткий штырь, патч‑антенна) или магнитное поле (рамка или ферритовая антенна). В данном случае, так как речь идет о 77 кГц, полноразмерная антенна, конечно же, немыслима. Тем не менее, следует в словосочетание «два типа антенн» добавить слова «электрически малых».
Ну и по поводу помехоустойчивости. Подобная магнитная антенна в этом смысле очень плоха. Из соображений поддержания высокой добротности контура вход УВЧ приемника делается высокоомным, (либо сигнал снимается с небольшой катушки связи или отвода, но это не наш случай: тут контурная катушка подключена к приемнику полностью). Сопротивление контура в резонансе тоже велико. И поэтому провода, идущие к антенне и сама ее катушка отлично принимают, как электрическая антенна. То есть ловят все те электрические помехи, которые распространяются по окружающим проводам, стенной арматуре и так далее, и в том числе приходят от блока питания. Это усугубляется несимметричным входом приемника. Как надо? А надо всего-то сделать вход приемника симметричным, а антенну экранировать электростатическим экраном. Первое сделать затруднительно, когда у нас приемник — это ящик черного цвета, залитый компаундом, который нам выдали, как вещь в себе. А вот экранировать — не стоит ничего. Просто взять и обернуть стержень вместе с обмоткой медным скотчем, не замкнув его (проложив между началом и концом изолирующую прокладку или оставив щель вдоль стержня) — и этот экран соединить с землей приемника. Провода от катушки к контурному конденсатору следует также свить вместе (как в оригинале) и поместить в экранирующую оплетку. Ее же и использовать для подключения экрана на магнитной антенне. Так мы сделаем магнитную антенну почти не чувствительной к электрическим помехам. Хорошо бы заэкранировать и сам приемничек, а по питанию поставить LC-фильтр с резонансом на 77 кГц.

JBFW Автор
22.04.2026 12:12Вот, а вот это можно попробовать! )
Потому что экранировать есть чем (с какого-то перепуга давно купил медную фольгу), и помехи от всего что рядом движется - изрядно достали.А то и вовсе засунуть это всё в алюминиевую трубу и залить выходы герметиком, наглухо. Заодно решив проблему "как разместить на улице под открытым небом".
Хотя нет, тогда получится замкнутый виток...

alex21579
22.04.2026 12:12У меня часики есть настольные, иногда срабатывает подстройка. В Москве находятся, 6 этаж, окна на запад. По карте получается - самая граница приёма. Очень цифра мешает. в 2000 году они лучше работали.

JBFW Автор
22.04.2026 12:12Скорее, распространение импульсных блоков питания.
В начале 2000 еще было 100500 моделей телефонов, например, у каждой - свой уникальный разьем зарядки, свой блок питания - тяжелый, с 50-Гц трансформатором внутри. Такие же - у многих роутеров, свичей и прочей бытовой мелочи, которая была.
Сейчас везде импульсники, легкие, мощные, в каждом чайнике с экранчиком, и в зарядке для пылесоса.
У них частота преобразования как раз где-то в районе 78 кГц. И всё это фонит (но всем пофиг, т.к. ДВ/СВ давно никто не слушает)

xirahai
22.04.2026 12:12Можно попробовать улучшить прием следующим ообразом. К основной катушке на большом феррите подключить параллельно конденсатор, чтобы контур был настроен на 77.5 кГц. Он может состоять из основного конденсатора постоянной емкости, и параллельно ему подстроечный для точной настройки. А в приемник подать сигнал с катушки связи, содержащей несколько витков. Это хорошо сработает если на плате приемника нет параллельного конденсатора, то есть антенна используется как апериодическая. Если он есть, то выпаять. В любом случае там простор для экспериментов.

pvvv
22.04.2026 12:12да там и без резонанса, заявленные 50кВт мощности передатчика на расстоянии 2000км вроде должны соответствовать 2е-12 Тл магнитного поля которые на частоте 77500Гц в контуре с общей эффективной площадью 1м^2 наведут вполне измеримые 1мкВ напряжения.
можно вместо готового приёмника с непонятной чувствительностью попробовать взять медленный сигма-дельта АЦП с частотой модулятора 77500Гц, и им прямо в 0 перенести через undersampling, ads1232 например, собственных шумов ~10nV/rtHz, раз в 10 ещё усилить можно малошумящим ОУ.

AndreyDmitriev
22.04.2026 12:12У меня есть дома некоторое количество самоустанавливающихся часов, и это удобно, да, хотя и не в каждой комнате они ловят (до передатчика от моего дома примерно 450 км). Дети постоянно просыпали в школу и у меня была крамольная мысль слепить передатчик, который рано утром перебивал бы сигнал и ставил бы часы минут этак на пятнадцать-двадцать вперёд, а потом бы выключался,они бы сами возвращались обратно через некоторое время. Но до реализации не дошло — во-первых у них есть и смартофоны, а во-вторых я не рискнул не рассчитать с мощностью и переставлял бы часы и соседям до кучи (опять же гадить самоделкой в эфир — ну такое). Но мысль была.
Ds02006
Сейчас намного популярнее эмуляторы DCF77 - это приложение для смартфонов, которое берёт точное время по протоколу NTP, а выдает его в обычные проводные наушники по стандарту радиостанции DCF77, наушники нужно положить рядом с часами, которым нужно дать точное время.
igrblkv
А примеры есть?
А то гугление предлагает писать скрипты на питоне в термуксе, а не готовое приложение.
Часы у меня есть, но это не точно. А хотелось-бы точно.
jar_ohty
Вообще было бы интересно сделать маленький приемник сигнала RBU. Не как источник эталонной частоты, а именно чтобы выдавало текущее время и метки для синхронизации. Так как DCF77 принимается у нас так себе, да и наверняка доживает последние годы, а RBU вечен как
ЙосяУВБ-76.Am6er
https://play.google.com/store/apps/details?id=jp.houryo.dcf77emulator