Embedded HandbookПриложения
Все разделы › XXV. Мини-проекты
STM32F103 · уровень средний · 7 мин чтения
Открыть в интерактивном справочнике

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.

Цепочка RTC F103: кварц 32 768 Гц, делитель PRL, пропуск импульсов CAL, 32-битный счётчик секунд и будильник


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 живёт в своём домене и тактируется от кварца, а не от шины. Поэтому записи в его регистры идут через «шлюз»:

  1. дождаться RTOFF = 1 — прошлая запись закончена;
  2. CNF = 1 — режим настройки;
  3. записать PRL, CNT или ALR (каждый — две 16-битные половины);
  4. CNF = 0 — запись уходит в домен RTC;
  5. снова дождаться 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 с

Уход часов за 30 суток для кварцев +37 и −25 ppm: без коррекции и с подобранными PRL и CAL

Как измерить ошибку кварца. Бит 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 Что улучшить


198.8 Грабли

Время сбрасывается при каждой перепрошивке. Не проверяется метка в BKP_DR1: прошивка каждый раз заново настраивает RTC.

Запись в RTC «не проходит». Не дождались RTOFF, не включили CNF или не разрешили DBP — регистры домена защищены от записи.

Раз в 18 часов время прыгает на 65 536 секунд. Старшая и младшая половины счётчика прочитаны по разные стороны переноса.

Часы отстают, а CAL не помогает. CAL только замедляет. Уменьшить делитель.

Будильник сработал один раз. После срабатывания его никто не завёл заново.

Светодиод на PC13 светит тускло или мешает кварцу. Выводы PC13–PC15 питаются от домена RTC и слабые: не быстрее 2 МГц при 30 пФ и не источник тока. Светодиод — только от питания к выводу, зажигать нулём (как на BluePill и в прошивке).

Часы идут от батарейки, но после смены батарейки — с нуля. Батарейку меняют при включённом основном питании — домен не обесточится.


198.9 Итоги

Дальше: раздел 198а «Мини-проект: датчик на батарейке — год на одной CR2032» — часы работают от сети; теперь прибор, который живёт от батарейки.

Открыть в интерактивном справочнике