Мне захотелось сделать себе простые самодельные часики на основе микроконтроллера STM32 и какого-то графического дисплея. И, пока я изучал эту тему, обнаружилась интересная фишка встроенного часового модуля STM32: в нём можно подстраивать скорость хода часов с небольшим шагом, чтобы компенсировать погрешность кварцевого генератора.
Мне сразу же вспомнилась функция цифровой настройки хода (ЦНХ) в моих старых часах Камертон 2-52, которые, к сожалению, уже давно не работают.
Суть этой функции очень простая: пользователь определяет, на сколько секунд в день уходит время в часах, и заносит это значение в часы. В результате погрешность часов можно снизить до нескольких секунд в год (в идеале). Я решил сделать такую функцию в своих часах, но для этого нужно было разобраться с отдельным модулем в RTC, который называется «плавная калибровка» (smooth calibration).
Калибровать часы можно специальным оборудованием, если оно у вас есть. Например, есть статья «Точное саратовское время», в которой автор использовал для калибровки самодельный частотомер с привязкой ко времени из модуля GPS навигации.
Я же хочу сделать простой интерфейс как на главной картинке этой статьи: чтобы можно было ввести количество секунд, скажем от -9.9 до +9.9 для уменьшения или увеличения скорости хода часов на заданное количество секунд в день. Это не даст идеальную компенсацию, потому что уход времени нужно будет определять «на глазок», сверяясь с другими часами чисто визуально. Об этом мы ещё поговорим. Но мне важнее простота, понятность и минимум усилий со стороны пользователя, поэтому такого интерфейса будет достаточно.
Про модуль RTC smooth calibration
Основная информация по часовому модулю STM32 описана в документе AN3371 Using the hardware real-time clock (RTC). Нужный нам модуль RTC smooth calibration стоит перед всеми делителями частоты, а на его вход подаётся частота с генератора на часовом кварце 32768 Гц.
Этот модуль имеет две функции. Во-первых, он может добавлять ровно 512 импульсов за рабочий период. Эта функция включается параметром, который обычно обозначается CALP (можно читать как «калибровка в плюс»). Мы можем либо добавить 512 импульсов к входной частоте (CALP = 1), либо ничего не добавлять (CALP = 0).
Во-вторых, мы можем вычитать импульсы из входной частоты. Эта функция имеет параметр CALM (типа «калибровка в минус»), который задаёт количество импульсов для удаления и может иметь значение от 0 до 511. Указанное количество импульсов вычитается за тот же рабочий период.
В качестве рабочего периода можно выбрать 8, 16 или 32 секунды. По умолчанию используется 32 секунды и этот период даёт наименьший шаг подстройки времени за сутки, что нам очень важно. Поэтому будем всё считать именно для периода 32 секунды. Причём, насколько я понимаю, эти 32 секунды отсчитываются не от нашего кварцевого генератора 32768 Гц, а от внутреннего RC-генератора на 32 кГц, который не особо стабилен по температуре. Но это мы никак не можем изменить.
Комбинируя значения CALP и CALM, мы можем либо вычитать от 0 до 511 импульсов, либо добавлять от 0 до 512 импульсов за 32 секунды, тем самым замедляя или ускоряя скорость «тиканья» наших часов.
Рассчитываем секунды
В документе AN3371 указан такой диапазон калибровки часов: от -487.1 ppm до +488.5 ppm с шагом примерно 0.954 ppm. Это не особо понятные цифры, но дальше они станут понятнее, когда мы получим нужные нам секунды.
В этом же документе показана формула для расчёта эффективной выходной частоты на основе входной и значений CALP и CALM:
В этой формуле можно сразу обозначить количество корректирующих импульсов N = CALP x 512 – CALM. Если наши часы спешат, то нам нужно вычесть импульсы, поэтому CALP будет равен 0. Если же наши часы отстают, тогда импульсы нужно добавлять и CALP будет равен 1. Тогда формула выше станет проще (FRTCCLK я заменю на F):
FCAL = F x [ 1 + N / (220 - N) ].
Здесь число 220 = 32 x 32768 — это количество импульсов стандартной часовой частоты за период 32 секунды. Это всё, что я могу сказать про данную формулу. Честно говоря, я не особо понимаю, как у разработчиков эта формула получилась и как из неё вывести формулу для расчёта CALM на основе ухода времени за сутки. Поэтому я вывел свою формулу более простым и понятным путём.
За один такт генератора наши часы отсчитывают 1 / 32768 секунды, поэтому за 32 секунды мы добавим время N / 32768 секунд. В сутках 24 x 60 x 60 / 32 = 2700 периодов по 32 секунды, поэтому за сутки будет добавляться время:
dT = 2700 x N / 32768 = 675 x N / 8192.
Подставляя сюда N = CALP x 512 – CALM, получим формулу для CALM:
CALM = CALP x 512 - 8192 / 675 x dT.
Это то, что нам нужно. Например, если часы спешат на 1.7 секунд в день, значит, нужно вычитать время, поэтому dT = -1.7, а CALP = 0. Тогда значение CALM = 20.632 или 21, так как нам нужны только целые числа. Или, например, наши часы отстают на 2.1 секунду в день, тогда CALP = 1 и dT = 2.1, получаем CALM = 486.514 или 487. Вроде бы тут всё логично и понятно.
А теперь давайте подставим в формулу для dT значения N = 1 (минимальный шаг), N = 511 (максимум для вычитания импульсов) и N = 512 (максимум для добавления импульсов). Мы получим числа 0.0824 с, 42.105 с и 42.188 с — это, соответственно, минимальный шаг корректировки времени, максимальное время в минус и максимальное время в плюс за сутки. Если эти значения поделить на 86400 (количество секунд в сутках) и умножить на 1000000, то получим значения 0.9537 ppm, -487.3 ppm и +488.3 ppm. Эти значения немного отличаются от указанных в AN3371 значений 0.954 ppm, -487.1 ppm и +488.5 ppm. Почему они отличаются? Я не знаю:) Но для практических расчётов моя формула хорошо работает.
Проверка на практике
Для проверки расчётов я использовал модуль с микроконтроллером STM32G031F8P6 с алиэкспресса.
На этом модуле уже есть миниатюрный часовой кварц с конденсаторами, поэтому нужно было только добавить бесперебойное питание (аккумуляторную батарею) и модуль USB-UART для записи времени и данных для калибровки.
Я не стал заморачиваться с синхронизацией по точному времени NTP сервера, поскольку я не стремлюсь к идеальной компенсации погрешности кварца. Для этого стоило бы использовать время GPS или какой-то высокоточный частотомер. Моя цель это дать пользователю возможность ручной подстройки скорости хода на основе распространённых источников синхронизации, например, времени компьютера или смартфона, которые автоматически синхронизируют время по сети.
Поэтому я просто записывал в микроконтроллер время компьютера, а также значения CALP и CALM. После этого я ждал больше суток, считывал время из микроконтроллера, сравнивал его со временем компьютера, а потом разность во времени пересчитывал на уход времени за 24 часа по простой пропорции. Для всего этого я создал небольшую программу, которая отправляла команды в микроконтроллер через COM-порт.
Таким образом, я смог проверить на практике применимость полученной нами формулы для CALM и dT.
Для начала я определил, какая погрешность часов без корректировки. После синхронизации времени с компьютером я ждал 3 дня, а потом сверился с компьютером. Оказалось, что часы в микроконтроллере отстают на 3.7 секунд в сутки. Погрешность приличная, ведь это почти 2 минуты в месяц (здесь и далее я будут принимать за месяц 30 дней). Я попробовал то же самое сделать для второго такого же модуля и там часы спешили на 3.1 секунду в сутки. Это странно, потому что обычно кварцы имеют разброс +/-20 ppm, что должно соответствовать +/-1.7 с в сутки. Но не будем углубляться в вопрос, почему погрешность такая высокая, так как статья не об этом.
Далее я проводил эксперименты только с первым модулем, в котором время отставало. Для компенсации отставания на 3.7 секунд в день я получил расчётные значения CALP = 1 и CALM = 467. Загрузил их в микроконтроллер, синхронизировал время с компьютером и через три дня посчитал новое время ухода. Теперь часы шли вперёд примерно на 0.3 секунды в день или 9 секунд в месяц. На мой взгляд, это очень даже хорошая компенсация.
Дальше я проверил на практике компенсацию времени на +/-10 с и +/-30 с, а также выставлял максимально возможные значения компенсации [CALP = 0, CALM = 511] и [CALP = 1, CALM = 0]. Все полученные значения ухода времени совпали с расчётными с учётом изначальной погрешности часов -3.7 секунд в сутки. Правда тут я уже ждал немного больше суток, а не 3 дня.
Тут надо уточнить важную деталь. Я могу сравнивать время лишь визуально с точностью до секунды, поэтому для повышения точности расчёта погрешности надо ждать хотя бы 5-10 дней после синхронизации. Иначе нет смысла говорить о десятых долях секунды. Но мне не хотелось тратить столько времени для проверки, так что все значения получались с погрешностью минимум в полсекунды. Думаю, что для проверки формулы этого вполне достаточно. Пользователь наручных или настольных часов будет в той же самой ситуации: миллисекунды визуально не получится сравнивать, всё нужно будет определять «на глазок» и синхронизировать время нажатием кнопки «Установить время» в нужный момент.
Делаем ещё точнее
Теперь рассмотрим другой вопрос. Мы определили, что минимальный шаг корректировки составляет 0.0824 с в сутки или 2.47 с в месяц. Это достаточно много. Можно ли его как-то уменьшить?
Аппаратный модуль не даёт такой возможности, но мы можем программно включать и отключать компенсацию в любой момент. Таким образом, мы можем снизить минимальный шаг корректировки времени за сутки, но только одновременно вместе с максимально возможным временем компенсации.
Например, мы можем уменьшить минимальный шаг до 0.01 с в сутки (0.3 с в месяц), если будем включать компенсацию только на 3600 / 8.24 ≈ 437 секунд каждый час. Другими словами, можно в начале каждого часа записывать нужные значения CALP и CALM, а спустя 437 секунд (7 минут и 17 секунд) отключать компенсацию записью [CALP = 0, CALM = 0]. И так повторять каждый час.
Данный алгоритм реализовать несложно, но для этого нужно каждую секунду считывать время из аппаратных регистров. Это не проблема, если часы ежесекундно выводят время на дисплей.
Тогда наша формула для расчёта CALM станет ещё проще:
CALM = CALP x 512 - 100 x dT.
Правда теперь можно компенсировать уход времени только от -5.11 до +5.12 секунд в сутки. Если этого недостаточно, то можно будет сделать минимальный шаг 0.02 с в сутки, активируя компенсацию импульсов на время 3600 / 4.12 ≈ 874 секунды каждый час.
Я попробовал добавить такой алгоритм в работу часов и он вполне хорошо работает. Правда, разумеется, вряд ли получится «на глазок» определить погрешность с точностью до 0.01 секунды, поэтому для ручной подстройки такая точность мало полезна. Только если ждать накопления погрешности 2-3 недели и вычислять среднее значение за этот период, тогда мы получим сотые доли секунд. Но это достаточно долго.
Хотя пользователь может сначала установить приблизительную компенсацию, а потом просто изменять её на минимальный шаг в плюс или минус, добиваясь более точного хода часов. Это также займёт много времени, но такая фишка может быть кому-то полезной. Такой функционал похож на подкручивание какого-то винтика в механических часах, чтобы менять частоту колебаний маятника.
Про температурную компенсацию
Возможность быстрого изменения значения CALP и CALM может быть также использована для температурной компенсации погрешности отсчёта времени. Всё-таки частота генератора плавает при изменении температуры и это тоже можно попытаться компенсировать. Про это есть информация в документе AN2604 и там даже есть типичный график температурной зависимости для кварцевого резонатора.
Но кроме резонатора у нас есть ещё нагрузочные конденсаторы, да и вход микроконтроллера имеет какую-то ёмкость. Всё это нужно учитывать вместе, поэтому для калибровки придётся снимать температурную зависимость, охлаждая и нагревая устройство и измеряя отклонение частоты точным прибором. На данный момент я не планирую делать температурную компенсацию, поэтому данный вопрос раскрывать не буду.
Заключение
Теперь я хочу реализовать в своих часиках именно алгоритм с включением и отключением компенсации каждый час для снижения минимального шага подстройки. Также можно будет добавить где-то счётчик дней (с десятыми долями), прошедших от последней установки времени. С ним будет проще считать количество секунд для компенсации за сутки, потому что не нужно будет самому высчитывать количество прошедших дней.
Пока я ещё не доработал схему и код часов. Могу только показать один из вариантов дисплея отображения времени на своих часах.
Буду дальше экспериментировать с индикацией времени, разными дисплеями и программами (возможно даже игры будут), а когда будет конечная версия моих часиков — неизвестно:)
Надеюсь, я нигде не ошибся в своих выводах и информация статьи будет полезна всем разработчикам. В любом случае, ваши комментарии и дальнейшие эксперименты помогут уточнить информацию.