198. Мини-проект: часы на RTC с будильником и коррекцией хода
Третий прибор — часы: время идёт без питания от батарейки, есть будильник, а ход подстраивается под конкретный кварц. RTC у F103 проще, чем у поздних серий: это просто 32-битный счётчик секунд. Календарь, будильник на «каждый день в 7:30» и коррекция хода — забота программы.
Как в разделах 196 и 197, логика (clock.c) отделена от платформы (plat_f103.c):
календарь прогнан на 20 000 случайных моментов и сверен с календарём Python,
коррекция — на ошибках кварца от −60 до +150 ppm. Файлы — _verify/proj198/,
сверяет _verify/proj198_check.py.
Как это называется в документации
Оригинал Перевод backup domain, VBAT домен резервного питания, вывод батарейки LSE (32.768 kHz) внешний часовой кварц RTC prescaler (PRL), counter (CNT), alarm (ALR) делитель, счётчик, будильник RTC RTC calibration (CAL) коррекция хода: пропуск импульсов backup data register (BKP_DRx) регистр, который переживает сброс ppm (parts per million) миллионная доля: 1 ppm ≈ 0,0864 с в сутки
198.1 Что делает прибор
| Вывод | Назначение |
|---|---|
| PC14, PC15 | кварц 32 768 Гц |
| VBAT | батарейка CR2032 — держит RTC и регистры BKP без основного питания |
| PA9, PA10 | консоль USART1, 115 200 бод |
| PC13 | светодиод платы — будильник сработал |
Команды: get — время; set 2026-09-21 12:34:56 — установить; alarm 07:30 —
будильник каждый день; trim +37.0 — ввести измеренную ошибку кварца в ppm.
198.2 Время — одно число
Счётчик RTC — 32 бита секунд. Отсчёт от 2000-01-01 00:00:00 (суббота) даёт
запас до 2136-02-07 06:28:15; от 1970 года он кончился бы в 2106-м. Дата из
числа и обратно — функции cal_from_secs и cal_to_secs: деление на 86 400,
перебор лет с учётом високосных, перебор месяцев.
Проверка: 20 000 случайных моментов от 2000 до 2135 года плюс секунда после «28 февраля 23:59:59» для каждого года — ни одного расхождения с календарём Python ни в дате, ни в дне недели; обратное преобразование возвращает то же число.
Почему не хранить дату в регистрах по полям? Тогда пришлось бы переносить разряды самим: 59 секунд → минута, 31 декабря → год. С одним числом переносы делает счётчик, а арифметика «через 8 часов», «сколько прошло» — просто вычитание.
198.3 Регистры RTC: писать через режим настройки
RTC живёт в своём домене и тактируется от кварца, а не от шины. Поэтому записи в его регистры идут через «шлюз»:
- дождаться
RTOFF = 1— прошлая запись закончена; CNF = 1— режим настройки;- записать
PRL,CNTилиALR(каждый — две 16-битные половины); CNF = 0— запись уходит в домен RTC;- снова дождаться
RTOFF = 1.
После сброса процессора, пока тактирование шины не синхронизировалось с RTC, читать
нельзя: сбросить RSF и дождаться, пока блок поднимет его сам. До всего этого —
разрешить запись в домен резервного питания: PWR_CR.DBP = 1.
Первое включение или села батарейка? Прошивка кладёт в BKP_DR1 метку 0xA5A5.
Регистры BKP питаются от VBAT и переживают сброс. Метки нет — домен сбрасывается,
запускается кварц, ставится делитель 32 767 (32 768 / (32 767 + 1) = 1 Гц) и
счётчик 0. Метка есть — время идёт, трогать ничего нельзя.
Чтение 32 бит из двух половин. Между чтением CNTH и CNTL счётчик может
перейти через 0xFFFF → 0x10000. Поэтому старшая половина читается дважды: не
совпала — перечитать.
198.4 Коррекция хода: только замедлить
Часовой кварц не точен: ±20 ppm — обычное дело, это 1,7 секунды в сутки или
почти минута в месяц. В F103 есть коррекция: CAL = сколько импульсов кварца из
каждых 2²⁰ выбросить. Шаг — 10⁶ / 2²⁰ = 0,954 ppm, предел — 127 шагов,
121 ppm. Но только в одну сторону: выбросить импульс можно, добавить — нет.
Приём: сменить делитель. Делитель 32 766 вместо 32 767 ускоряет часы на
30,5 ppm; дальше CAL замедляет их до точности. Для кварца, спешащего больше
чем на 121 ppm, наоборот — 32 768. Программа берёт делитель, ближайший к штатному,
при котором CAL дотягивает до полшага:
| Кварц | Без коррекции | PRL | CAL | Остаток | За месяц |
|---|---|---|---|---|---|
| +150 ppm | +13,0 с/сутки | 32 768 | 125 | +0,26 ppm | +0,66 с |
| +130 ppm | +11,2 с/сутки | 32 768 | 104 | +0,29 ppm | +0,74 с |
| +90 ppm | +7,8 с/сутки | 32 767 | 94 | +0,35 ppm | +0,90 с |
| +37 ppm | +3,2 с/сутки | 32 767 | 39 | −0,19 ppm | −0,50 с |
| +5 ppm | +0,4 с/сутки | 32 767 | 5 | +0,23 ppm | +0,60 с |
| −10 ppm | −0,9 с/сутки | 32 766 | 22 | −0,46 ppm | −1,20 с |
| −25 ppm | −2,2 с/сутки | 32 766 | 6 | −0,20 ppm | −0,53 с |
| −40 ppm | −3,5 с/сутки | 32 765 | 22 | +0,06 ppm | +0,14 с |
| −60 ppm | −5,2 с/сутки | 32 765 | 1 | +0,08 ppm | +0,21 с |
Как измерить ошибку кварца. Бит CCO в BKP_RTCCR выводит частоту кварца,
делённую на 64 (512 Гц), на вывод TAMPER (PC13; на время измерения светодиод
там не нужен) — её меряют частотомером. Опора частотомера должна быть на порядок
точнее измеряемого: для ±20 ppm — не хуже ±2 ppm. Частотомер из раздела 197 этого
не даёт: его кварц 8 МГц сам ошибается на ±10…30 ppm (раздел 174), и прибор покажет
разность двух кварцев, а «коррекция» по нему может ухудшить ход. Годится прибор
с термокомпенсированным генератором или секундная метка приёмника спутниковой
навигации. Второй способ — без приборов: поставить время,
через неделю сравнить с точными часами; 1 секунда за 7 суток — 1,65 ppm.
Коррекция остаётся в домене резервного питания: PRL — в RTC, CAL — в BKP_RTCCR.
198.5 Будильник: одно сравнение
Регистр ALR сравнивается со счётчиком; совпали — флаг ALRF. Будильник «каждый
день в 7:30» — это ближайший момент 7:30 строго после текущего; после срабатывания
прошивка заводит следующий — на сутки вперёд. Прогон:
| Сейчас | Будильник | Сработает |
|---|---|---|
| 2026-09-21 07:29:59 | 07:30 | через 1 с |
| 2026-09-21 07:29:59 | 07:29 | 2026-09-22 07:29:00 — завтра |
| 2026-09-21 07:29:59 | 00:00 | 2026-09-22 00:00:00 |
| 2028-02-28 23:00:00 | 06:00 | 2028-02-29 06:00:00 — високосный день |
Будильник — тоже число в домене RTC, он переживает сброс процессора. Проверять флаг можно в прерывании (линия EXTI 17) — тогда будильник будит процессор даже из режима Stop (раздел 10).
198.6 Консоль и прошивка
| Команда | Ответ |
|---|---|
get |
2026-09-21 07:29:59 пн |
set 2026-09-21 12:34:56 |
ok 2026-09-21 12:34:56 пн |
set 2027-02-29 00:00:00 |
? дата: … — 2027 не високосный |
alarm 07:30 |
будильник 2026-09-22 07:30:00 вт |
trim +37.0 |
PRL=32767 CAL=039 |
trim +250.0 |
? вне диапазона коррекции |
Сборка arm-none-eabi-gcc -Os для Cortex-M3 — 5356 байт Flash, переменных
в ОЗУ нет (всё на стеке). Из них 2220 байт (41 %) — программная арифметика
double из библиотеки компилятора для подбора коррекции: сложение и вычитание,
умножение и деление, сравнение.
198.7 Что улучшить
- Подбор коррекции в целых числах: ошибка в сотых долях ppm, сравнение
в
int32_t— минус пара килобайт. - Сон между событиями: Stop с пробуждением по будильнику и по приёму USART — часы от батарейки месяцами.
- Часовой пояс и летнее время — в прошивке хранить UTC, пояс — отдельным регистром BKP.
- Точное время по сети (SNTP, раздел 156.3) — через сетевой модуль, своей сети у BluePill нет; раз в сутки, а расхождение между синхронизациями заодно даёт ошибку кварца.
198.8 Грабли
Время сбрасывается при каждой перепрошивке. Не проверяется метка в BKP_DR1:
прошивка каждый раз заново настраивает RTC.
Запись в RTC «не проходит». Не дождались RTOFF, не включили CNF или
не разрешили DBP — регистры домена защищены от записи.
Раз в 18 часов время прыгает на 65 536 секунд. Старшая и младшая половины счётчика прочитаны по разные стороны переноса.
Часы отстают, а CAL не помогает. CAL только замедляет. Уменьшить делитель.
Будильник сработал один раз. После срабатывания его никто не завёл заново.
Светодиод на PC13 светит тускло или мешает кварцу. Выводы PC13–PC15 питаются от домена RTC и слабые: не быстрее 2 МГц при 30 пФ и не источник тока. Светодиод — только от питания к выводу, зажигать нулём (как на BluePill и в прошивке).
Часы идут от батарейки, но после смены батарейки — с нуля. Батарейку меняют при включённом основном питании — домен не обесточится.
198.9 Итоги
- Время — 32-битный счётчик секунд от 2000 года, до 2136-го; календарь — программой, сверен на 20 000 моментов.
- Запись в RTC — через
RTOFFиCNF; чтение 32 бит — с перепроверкой старшей половины. CALтолько замедляет (до 121 ppm шагом 0,954); отстающим часам уменьшают делитель: 32 766 — +30,5 ppm. Остаток — меньше 0,5 ppm, около секунды в месяц.- Будильник — ближайший момент строго после текущего; после срабатывания — заново.
- Прошивка BluePill — 5356 байт, из них 2220 —
doubleв подборе коррекции.
Дальше: раздел 198а «Мини-проект: датчик на батарейке — год на одной CR2032» — часы работают от сети; теперь прибор, который живёт от батарейки.