12а. RTC нового образца: доли секунды, калибровка, пробуждение, TAMP
Раздел 12.6 разобрал часы F1 — счётчик секунд — и календарь F4 с двумя будильниками. Но у часов F4 и более новых серий есть и то, что там осталось за кадром: доли секунды, сдвиг хода на долю секунды, подстройка частоты кварца в миллионных долях (ppm) и таймер пробуждения. У G4 к этому добавился отдельный блок TAMP: входы вмешательства, внутренние источники тревоги и 32 резервных регистра. Здесь — всё это на G474: формулы, расчёт по ним и код по регистрам.
Опоры: справочное руководство RM0440 (G4), главы 35 «RTC» и 36 «TAMP»,
регистр RCC_BDCR и раздел PWR о пробуждении; заголовки stm32g474xx.h,
stm32g431xx.h и stm32f407xx.h и библиотека HAL для G4 из паков Keil.
Расчёты сделаны моделью на ПК и сверены с формулами RM0440; код собран
armclang для G474 и G431. QEMU часы G4 не моделирует — на железе не
проверено.
Как это называется в документации
Оригинал Перевод sub second register (SSR) регистр долей секунды: счётчик синхронного делителя asynchronous, synchronous prescaler (PREDIV_A, PREDIV_S) асинхронный и синхронный делители shift control (SHIFTR), ADD1S, SUBFS сдвиг часов на долю секунды назад или вперёд smooth digital calibration (CALR), CALP, CALM гладкая цифровая калибровка: вставить или выбросить импульсы wakeup timer (WUT), WUCKSEL таймер пробуждения и выбор его такта alarm sub second mask (MASKSS) сколько младших бит SS сравнивает будильник shadow registers, BYPSHAD теневые копии календаря; BYPSHAD — читать мимо них timestamp (TSE, ITSE, TAMPTS) метка времени: календарь, записанный в момент события tamper, internal tamper вмешательство: внешний вход или внутренний источник backup registers (TAMP_BKPxR) резервные регистры no erase (TAMPxNOER) не стирать резервные регистры при вмешательстве
12а.1 Что нового по сравнению с F4
Доли секунды, сдвиг, гладкая калибровка и таймер пробуждения есть уже
у F407: в его заголовке — регистры SSR, SHIFTR, CALR и WUTR. У G474
тот же набор, но блок перестроен:
| F407 | G474 | |
|---|---|---|
| режим настройки и флаги | в одном ISR (0x0C) |
ICSR (0x0C) — настройка, SR (0x50) — флаги |
| сброс флага | нуль в ISR, сохранив INIT (раздел 12.6) |
единица в SCR (0x5C) |
| калибровка | грубая CALIBR (0x18) и гладкая CALR (0x3C) |
только гладкая CALR (0x28) |
| вмешательство | TAFCR (0x40) внутри RTC |
отдельный блок TAMP: 3 входа и 4 внутренних источника |
| резервные регистры | RTC_BKP0R…BKP19R: 20 штук, 80 байт |
TAMP_BKP0R…BKP31R: 32 штуки, 128 байт |
У G431 резервных регистров 16 — 64 байта: RM0440 даёт 32 устройствам
категорий 3 и 4 и 16 — категории 2, заголовки пака это подтверждают.
Код с F4 для резервных регистров на G4 не соберётся: RTC->BKP0R там нет,
есть TAMP->BKP0R.
Включение по регистрам. У G4 у RTC свой такт шины — бит RTCAPBEN,
а источник такта выбирается в RCC_BDCR один раз: сменить RTCSEL потом
можно только сбросом резервного домена или после отказа LSE:
void rtc_lse_on(void)
{
RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN | RCC_APB1ENR1_RTCAPBEN; /* у G4 у RTC свой такт шины */
PWR->CR1 |= PWR_CR1_DBP; /* доступ к резервному домену */
RCC->BDCR |= RCC_BDCR_LSEON;
while (!(RCC->BDCR & RCC_BDCR_LSERDY)) { }
RCC->BDCR |= RCC_BDCR_RTCSEL_0 | RCC_BDCR_RTCEN; /* 01 — LSE; сменить потом — только сбросом домена */
}
Ключи защиты записи в RTC_WPR те же, что у F4: 0xCA и 0x53. В примерах
ниже их пишет rtc_unlock, а rtc_lock возвращает защиту записью 0xFF.
12а.2 Два делителя и доли секунды
Секунду из 32 768 Гц делают два делителя подряд (RM0440, 35.3.4):
ck_apre = 32 768 / (PREDIV_A + 1), 1 Гц = ck_apre / (PREDIV_S + 1).
Асинхронный PREDIV_A — 7 бит, синхронный PREDIV_S — 15 бит; общий
делитель — от 1 до 2²² = 4 194 304. Синхронный — это и есть счётчик SS
в регистре RTC_SSR: он считает вниз от PREDIV_S до нуля с тактом ck_apre.
Отсюда доля секунды:
доля = (PREDIV_S − SS) / (PREDIV_S + 1).
При делителях по умолчанию (127 и 255) SS = 191 — это 0,25 с, SS = 127 — 0,5 с, SS = 0 — 0,99609 с. Шаг — 1 / (PREDIV_S + 1), и его можно измельчить, перенеся деление в синхронный делитель:
PREDIV_A |
PREDIV_S |
ck_apre | Шаг долей | CALP |
|---|---|---|---|---|
| 127 | 255 | 256 Гц | 3906,25 мкс | можно |
| 31 | 1023 | 1024 Гц | 976,56 мкс | можно |
| 7 | 4095 | 4096 Гц | 244,14 мкс | можно |
| 3 | 8191 | 8192 Гц | 122,07 мкс | можно |
| 0 | 32767 | 32768 Гц | 30,52 мкс | нельзя |
Цена мелкого шага — ток: чем быстрее ck_apre, тем выше может быть
потребление часов, и RM0440 советует держать PREDIV_A большим (цифр тока
руководство не даёт). Второе ограничение — калибровка: при PREDIV_A меньше 3
CALP не действует (12а.3). Поэтому 3 и 8191 — самый мелкий шаг, 122 мкс,
при полном диапазоне калибровки. Ещё два условия RM0440: будильник по
секундам требует PREDIV_S не меньше 3, а подстройка по сети 50 или 60 Гц
(REFCKON) — делителей по умолчанию.
Делители пишутся в режиме настройки, и RM0440 требует записать PRER двумя
отдельными обращениями (HAL для G4 пишет одним):
void rtc_prescalers(uint32_t a, uint32_t s)
{
rtc_unlock();
RTC->ICSR |= RTC_ICSR_INIT; /* календарь стоит */
while (!(RTC->ICSR & RTC_ICSR_INITF)) { }
RTC->PRER = s; /* RM0440: две раздельные записи */
RTC->PRER = s | (a << RTC_PRER_PREDIV_A_Pos);
RTC->ICSR &= ~RTC_ICSR_INIT; /* пошёл через 4 такта RTCCLK */
rtc_lock();
}
INITF поднимается примерно через 2 такта RTCCLK — 61,0 мкс, а после снятия
INIT календарь трогается через 4 такта — 122,1 мкс.
Сдвиг на долю секунды
Если часы надо подогнать под точное время — от GPS, сервера времени, другого
прибора, — останавливать календарь не нужно. Запись в RTC_SHIFTR
прибавляет SUBFS к счётчику SS, а он считает вниз — часы отстают на
SUBFS / (PREDIV_S + 1) с. С битом ADD1S к этому прибавляется целая секунда,
и часы уходят вперёд на 1 − SUBFS / (PREDIV_S + 1) с:
| Нужно | PREDIV_S |
SHIFTR |
Вышло |
|---|---|---|---|
| назад на 0,1 с | 255 | 0x0000001A | −0,101562 с |
| назад на 0,1 с | 8191 | 0x00000333 | −0,099976 с |
| вперёд на 0,25 с | 255 | 0x800000C0 | +0,250000 с |
| вперёд на 0,25 с | 8191 | 0x80001800 | +0,250000 с |
Модель счётчика на ПК — SS идёт вниз, сдвиг прибавляет к нему SUBFS — через
три секунды хода даёт ту же разницу, что и формула. Точность сдвига —
шаг долей: при 255 назад на 0,1 с не выходит, ошибка 1,56 мс.
int rtc_shift(uint32_t add1s, uint32_t subfs)
{
if (RTC->SSR & 0x8000u) /* SS[15] = 1 — новый сдвиг переполнит SS */
return -1;
rtc_unlock();
while (RTC->ICSR & RTC_ICSR_SHPF) { }
RTC->SHIFTR = (add1s ? RTC_SHIFTR_ADD1S : 0u) | subfs;
rtc_lock();
return 0;
}
После сдвига SS бывает больше PREDIV_S, и тогда, по RM0440, верное время
на секунду меньше, чем в TR. Формула доли при этом просто даёт
отрицательное число — считайте её со знаком:
int32_t rtc_ms(uint32_t ss, uint32_t prediv_s)
{
return ((int32_t)prediv_s - (int32_t)ss) * 1000 / ((int32_t)prediv_s + 1);
}
12а.3 Гладкая калибровка: ppm вместо подстроечного конденсатора
Часовой кварц редко даёт ровно 32 768 Гц. Ошибка в +20 ppm — это 1,728 с
в сутки и 51,8 с за 30 суток, в −35 ppm — 3,024 с в сутки и 90,7 с за месяц.
Регистр RTC_CALR исправляет это без подстройки кварца: за цикл из 2²⁰
импульсов — 32 с — выбрасывает CALM импульсов (0…511), а бит CALP
добавляет 512 (RM0440, 35.3.13):
FCAL = F × [1 + (CALP × 512 − CALM) / (2²⁰ + CALM − CALP × 512)].
Диапазон — от −487,1 до +488,5 ppm, шаг — 0,9537 ppm. Подбор: CALM −
512 × CALP ≈ 2²⁰ × ошибку. Для +20 ppm это 20,97 — CALM = 21; для −35 ppm
это −36,70 — ставим CALP и CALM = 512 − 37 = 475. Модель на ПК перебирает
все 1024 сочетания и выбирает лучшее:
| Кварц | Цикл | CALR |
Остаток | Уход в сутки |
|---|---|---|---|---|
| +20 ppm | 32 с | 0x0015 | −0,027 ppm | 0,002 с |
| +20 ppm | 16 с | 0x2014 | +0,926 ppm | 0,080 с |
| +20 ppm | 8 с | 0x4014 | +0,926 ppm | 0,080 с |
| −35 ppm | 32 с | 0x81DB | +0,286 ppm | 0,025 с |
| −35 ppm | 16 с | 0xA1DC | −0,668 ppm | 0,058 с |
| −35 ppm | 8 с | 0xC1DC | −0,668 ppm | 0,058 с |
Короткий цикл (CALW16, CALW8) нужен, чтобы быстрее проверить результат,
но он огрубляет шаг: младший бит CALM залипает в нуле при 16 с, два
младших — при 8 с. Проверяют ход по выходу CALIB на RTC_OUT1 (PC13):
при делителях по умолчанию это 512 Гц при COSEL = 0 или 1 Гц при
COSEL = 1. Если мерить ровно цикл,
ошибка измерения — полтакта за окно: 0,477 ppm за 32 с, 0,954 ppm за 16 с,
1,907 ppm за 8 с.
Выброшенные импульсы разбросаны по циклу: по RM0440, бит CALM[0] убирает
импульс при cal_cnt = 0x80000, CALM[1] — при 0x40000 и 0xC0000 и так
далее до CALM[8] — 256 импульсов вида 0xXX800. Модель на ПК раскладывает их
по этим правилам: при любом CALM на любом начале цикла отставание от
равномерного — не больше 1,89 импульса. Поэтому часы калиброваны и на
коротких отрезках, а не только раз в 32 с. Простой подсчёт импульсов
за цикл расходится с формулой RM не больше чем на 0,24 ppm — это меньше шага.
CALR можно менять на ходу, но только когда RECALPF = 0: пока прошлое
значение не принято, регистр заблокирован. Новое вступает в силу за три
такта ck_apre — 11,72 мс при 256 Гц:
void rtc_calibrate(uint32_t calr)
{
rtc_unlock();
while (RTC->ICSR & RTC_ICSR_RECALPF) { } /* прошлая запись ещё не принята */
RTC->CALR = calr;
rtc_lock();
}
12а.4 Таймер пробуждения и будильник с долями секунды
Будильник из раздела 12.6 привязан к календарю, а для «проснуться через
N» есть отдельный 16-битный таймер WUT: он считает вниз от значения
RTC_WUTR, раз в WUT + 1 тактов поднимает WUTF и перезагружается сам.
Такт выбирает WUCKSEL (RM0440, 35.3.7):
WUCKSEL |
Такт | Шаг | Период |
|---|---|---|---|
| 000 | RTCCLK / 16 | 488,28 мкс | от 0,488 мс до 32 с |
| 001 | RTCCLK / 8 | 244,14 мкс | от 0,244 мс до 16 с |
| 010 | RTCCLK / 4 | 122,07 мкс | от 0,122 мс до 8 с |
| 011 | RTCCLK / 2 | 61,04 мкс | от 0,122 мс до 4 с |
| 10x | ck_spre, 1 Гц | 1 с | от 1 до 65 536 с — 18,2 ч |
| 11x | ck_spre и ещё 2¹⁶ | 1 с | от 65 537 до 131 072 с — 36,4 ч |
При RTCCLK / 2 значение WUT = 0 запрещено, поэтому минимум — два такта. При 11x к счётчику добавляется 2¹⁶: таймер становится 17-битным. Выбор по периоду — самый мелкий шаг, в который период влезает:
| Период | WUCKSEL |
WUT |
Получится |
|---|---|---|---|
| 1 мс | 011 | 15 (0x000F) | 0,977 мс, −2,344 % |
| 100 мс | 011 | 1637 (0x0665) | 99,976 мс, −0,024 % |
| 1 с | 011 | 16383 (0x3FFF) | ровно 1 с |
| 10 мин | 100 | 599 (0x0257) | ровно 600 с |
| 24 ч | 110 | 20863 (0x517F) | ровно 86 400 с |
Секунду можно взять и от ck_spre с WUT = 0 — тот же такт, что ведёт календарь. Миллисекунда ровно не получается: у кварца на 32 768 Гц нет целого числа тактов в 1 мс.
void rtc_wakeup(uint32_t wucksel, uint32_t wut)
{
rtc_unlock();
RTC->CR &= ~(RTC_CR_WUTE | RTC_CR_WUTIE);
while (!(RTC->ICSR & RTC_ICSR_WUTWF)) { } /* WUTR и WUCKSEL можно менять */
RTC->WUTR = wut;
RTC->CR = (RTC->CR & ~RTC_CR_WUCKSEL) | wucksel;
RTC->SCR = RTC_SCR_CWUTF;
RTC->CR |= RTC_CR_WUTIE | RTC_CR_WUTE;
rtc_lock();
EXTI->IMR1 |= EXTI_IMR1_IM20; /* из Stop будит линия EXTI 20 */
EXTI->RTSR1 |= EXTI_RTSR1_RT20;
NVIC_EnableIRQ(RTC_WKUP_IRQn);
}
void RTC_WKUP_IRQHandler(void)
{
if (RTC->SR & RTC_SR_WUTF) {
RTC->SCR = RTC_SCR_CWUTF; /* единица в SCR — без ключей и без INIT */
wakeups++;
}
EXTI->PR1 = EXTI_PR1_PIF20;
}
Будить из Stop через таймер — линия EXTI 20 по переднему фронту (RM0440,
6.3.10), будильник — линия 17. Первый WUTF приходит не через WUT + 1
тактов, а между WUT и WUT + 1 после WUTE: для 10 минут — между 599 и 600 с,
а при ck_spre с WUT = 0 — в любой момент первой секунды.
Будильник с долями секунды
Рядом с RTC_ALRMAR из раздела 12.6 у G4 стоит RTC_ALRMASSR: значение
SS и MASKSS — сколько его младших бит сравнивать. Если все поля
ALRMAR замаскированы, одна только маска даёт периодический будильник.
Модель на ПК при PREDIV_S = 255:
MASKSS |
Сравниваются | Срабатывает |
|---|---|---|
| 0 | ничего, срабатывает на смене секунды | раз в 1 с |
| 1 | SS[0] | раз в 7,812 мс |
| 6 | SS[5:0] | раз в 0,25 с |
| 7 | SS[6:0] | раз в 0,5 с |
| 8 | SS[7:0] | раз в 1 с |
| 15 | все 15 бит | раз в 1 с |
Будильник «каждый день в 07:15:00,500» — это ALRMAR = 0x80071500 из
раздела 12.6 и ALRMASSR = 0x0F00007F: SS = 127 — середина секунды.
Запись — как у F4, после ALRAWF, только флаг сбрасывается единицей
в SCR:
void rtc_alarm_a_ss(uint32_t alrmar, uint32_t maskss, uint32_t ss)
{
rtc_unlock();
RTC->CR &= ~(RTC_CR_ALRAE | RTC_CR_ALRAIE);
while (!(RTC->ICSR & RTC_ICSR_ALRAWF)) { }
RTC->ALRMAR = alrmar;
RTC->ALRMASSR = (maskss << RTC_ALRMASSR_MASKSS_Pos) | ss;
RTC->SCR = RTC_SCR_CALRAF;
RTC->CR |= RTC_CR_ALRAE | RTC_CR_ALRAIE;
rtc_lock();
}
12а.5 Чтение календаря: теневые регистры и BYPSHAD
RTC_SSR, RTC_TR и RTC_DR по умолчанию читаются из теневых копий:
календарь всё время копируется туда и поднимает RSF. Правила
RM0440 (35.3.9):
- шина APB должна быть не медленнее 7 × RTCCLK — 229 376 Гц при LSE;
иначе RM требует
BYPSHAD= 1; - чтение
SSRилиTRзамораживает старшие копии до чтенияDR— читать по порядкуSSR,TR,DR, и тогда три значения согласованы; - в Stop и Standby копирование не идёт, после выхода копии обновятся
не сразу, а в пределах 4 тактов RTCCLK — 122,1 мкс. Поэтому после пробуждения
RSFсбрасывают и ждут снова:
void rtc_resync(void)
{
rtc_unlock();
RTC->ICSR &= ~RTC_ICSR_RSF;
rtc_lock();
while (!(RTC->ICSR & RTC_ICSR_RSF)) { }
}
С BYPSHAD = 1 ждать RSF не нужно — значения берутся прямо из счётчиков,
но и согласованности никто не обещает: такт может прийти между чтениями.
Модель на ПК ставит часы на 12:00:00 с SS = 0 — последний такт секунды —
и даёт тикнуть сразу после первого чтения:
| Как читали | Показано | На самом деле | Ошибка |
|---|---|---|---|
TR, потом SSR |
12:00:00,000 | 12:00:01,000 | −1 с |
SSR, потом TR |
12:00:01,996 | 12:00:01,000 | +0,996 с |
SSR, TR, DR и SSR ещё раз |
12:00:01,000 | 12:00:01,000 | нет |
Лечение — то же, что для счётчика F1 в разделе 12.6: перечитать младший регистр и повторить, если он изменился. RM0440 так и советует — сравнить два чтения младшего регистра календаря. В редком случае с тактом между чтениями это восемь обращений вместо четырёх:
void rtc_read(rtc_now_t *t)
{
do {
t->ss = RTC->SSR;
t->tr = RTC->TR;
t->dr = RTC->DR;
} while (t->ss != RTC->SSR);
}
12а.6 TAMP: вмешательство, метка времени, резервные регистры
У G474 32 резервных регистра TAMP_BKP0R…BKP31R — с 0x4000 2500
по 0x4000 257C. Их не сбрасывает ни системный сброс, ни выход из Standby,
и они питаются от VBAT. Запись требует DBP, как и всё в резервном
домене (раздел 10.5):
void bkp_write(uint32_t i, uint32_t v)
{
(&TAMP->BKP0R)[i] = v; /* нужен DBP; пока стоит флаг вмешательства — не запишется */
}
armclang превращает её в одну запись по адресу 0x4000 2500 + 4 × i:
bkp_write: MOVW r2,#0x2500
MOVT r2,#0x4000
STR r1,[r2,r0,LSL #2]
BX lr
Вмешательство стирает их. По умолчанию любое событие TAMP обнуляет все
резервные регистры, и они держатся в нуле, пока стоит хоть один флаг
вмешательства. Не стирать можно по входу: TAMPxNOER или TAMPxMSK
в TAMP_CR2 — в заголовке эти биты называются TAMP1NOERASE и TAMP1MF.
Ещё регистры стираются при снятии защиты чтения флеш с уровня 1 на 0.
Источников вмешательства семь. Внешних входов три — TAMP_IN1…TAMP_IN3;
внутренних четыре: сбой LSE, сбой HSE, переполнение календаря (31 декабря
99-го года, 23:59:59 — дальше календарь стоит) и заводское чтение ST.
После сброса резервного домена TAMP_CR1 = 0xFFFF 0000 — биты
ITAMP3E…ITAMP6E уже стоят.
Внешний вход работает двумя способами:
- по фронту (
TAMPFLT= 0) — подтяжка выключена; RM0440 советует сразу после включения прочитать уровень вывода: если активный уровень уже есть, событие придёт немедленно; - по уровню с фильтром — вход подтягивается перед каждой выборкой
(1…8 тактов RTCCLK,
TAMPPRCH), опрашивается с частотой от 1 до 128 Гц (TAMPFREQ), событие — после 2, 4 или 8 выборок подряд (TAMPFLT). Задержка — от 2 выборок при 128 Гц, 0,0156 с, до 8 выборок при 1 Гц — 8 с.
Детектор работает и при снятом VDD, поэтому вход должен быть надёжно
притянут к неактивному уровню — иначе регистры сотрёт помеха. Пример:
при вскрытии крышка корпуса замыкает TAMP_IN1 (PC13) на землю, 4 выборки при 16 Гц —
0,25 с:
void tamp_in1_setup(void)
{
TAMP->FLTCR = (2u << TAMP_FLTCR_TAMPFLT_Pos) | (4u << TAMP_FLTCR_TAMPFREQ_Pos);
TAMP->CR2 &= ~(TAMP_CR2_TAMP1NOERASE | TAMP_CR2_TAMP1MF | TAMP_CR2_TAMP1TRG); /* низкий уровень — вскрытие */
TAMP->IER |= TAMP_IER_TAMP1IE;
TAMP->CR1 |= TAMP_CR1_TAMP1E;
rtc_unlock();
RTC->CR |= RTC_CR_TAMPTS; /* метка времени на вмешательство, даже при TSE = 0 */
rtc_lock();
EXTI->IMR1 |= EXTI_IMR1_IM19; /* вмешательство и метка — линия EXTI 19 */
EXTI->RTSR1 |= EXTI_RTSR1_RT19;
NVIC_EnableIRQ(RTC_TAMP_LSECSS_IRQn);
}
Метка времени — календарь, записанный в RTC_TSSSR, RTC_TSTR
и RTC_TSDR в момент события. Событий три: фронт на RTC_TS (бит TSE),
переход на батарейку (ITSE) и вмешательство (TAMPTS). TSF поднимается
через 2 такта ck_apre — 7,81 мс при 256 Гц; второе событие при стоящем
TSF поднимает TSOVF, а в регистрах остаётся первое. Года в метке нет:
в RTC_TSDR только день недели, месяц и число. И главное — сброс TSF
обнуляет метку, поэтому сначала прочитать, потом сбросить:
void RTC_TAMP_LSECSS_IRQHandler(void)
{
if (TAMP->SR & TAMP_SR_TAMP1F) {
if (RTC->SR & RTC_SR_TSF) { /* сначала прочитать: сброс TSF обнуляет метку */
ts_ss = RTC->TSSSR;
ts_time = RTC->TSTR;
ts_date = RTC->TSDR;
RTC->SCR = RTC_SCR_CTSF;
}
TAMP->SCR = TAMP_SCR_CTAMP1F; /* пока флаг стоит, резервные регистры держатся в нуле */
tampers++;
}
EXTI->PR1 = EXTI_PR1_PIF19;
}
На PC13 сидят сразу RTC_OUT1, RTC_TS и TAMP_IN1, и эти функции
работают во всех режимах сна и от батарейки.
12а.7 Грабли
Регистры RTC не отвечают. У G4 у часов свой такт шины: не включён
RTCAPBEN в RCC_APB1ENR1.
Источник такта не меняется. RTCSEL выбирается один раз; сменить —
только сбросом резервного домена (BDRST), а он стирает и часы.
CALP не действует. PREDIV_A меньше 3 — тогда RM0440 считает CALP
равным нулю. Самый мелкий шаг с полной калибровкой — делители 3 и 8191.
Запись в CALR пропала. Стоял RECALPF: регистр заблокирован, пока
прошлое значение не принято.
Нечётный CALM не работает. Включён CALW16 (или CALW8): младшие биты
залипают в нуле, шаг — 1,907 ppm (или 3,815 ppm).
Будильник по секундам молчит. PREDIV_S меньше 3, а MSK1 = 0.
После пробуждения время старое. В Stop теневые копии не обновляются:
сбросить RSF и дождаться его.
Время изредка прыгает на секунду. BYPSHAD = 1, а SSR и TR прочитаны
без повтора — такт пришёлся между чтениями.
Первое пробуждение раньше срока. Первый WUTF — между WUT и WUT + 1
тактами; при ck_spre и WUT = 0 — в любой момент первой секунды.
WUTR не записывается. Не снят WUTE или не дождались WUTWF.
Сдвиг не сработал. Стоял SHPF (прошлый сдвиг идёт), SS[15] = 1 или
включён REFCKON — с подстройкой по сети сдвиг несовместим.
Флаг сбрасывают как на F4. Нуль в RTC_SR ничего не делает — у G4 флаги
гасит единица в RTC_SCR.
Резервный регистр не пишется. Нет DBP — или стоит флаг вмешательства:
пока он не сброшен, регистры держатся в нуле.
Резервные регистры стёрлись сами. Сработал вход вмешательства (висит в воздухе, по фронту включён при активном уровне), внутренний источник или снята защита чтения с уровня 1 на 0.
Метка времени пустая. TSF сброшен раньше, чем прочитаны TSTR
и TSDR, — сброс их обнуляет.
Код резервных регистров с F4 не собирается. У G4 они в TAMP, а не
в RTC: TAMP->BKP0R.
12а.8 Итоги
- Секунда = 32 768 / ((PREDIV_A + 1) × (PREDIV_S + 1)); доля — (PREDIV_S − SS) / (PREDIV_S + 1); шаг от 3906,25 мкс (127 и 255) до 30,52 мкс (0 и 32767); с полной калибровкой — до 122,07 мкс (3 и 8191).
SHIFTRдвигает часы на долю секунды без остановки: назад на SUBFS / (PREDIV_S + 1), сADD1S— вперёд на 1 − SUBFS / (PREDIV_S + 1).- Гладкая калибровка: от −487,1 до +488,5 ppm с шагом 0,9537 ppm за цикл 32 с;
+20 ppm —
CALR= 0x0015, −35 ppm — 0x81DB, остаток меньше 0,3 ppm против 1,728 и 3,024 с в сутки без калибровки. - Таймер пробуждения: от 0,122 мс до 36,4 ч; 10 мин — ck_spre и WUT = 599,
сутки —
WUCKSEL110 и WUT = 20863; будит из Stop через EXTI 20. MASKSSдобавляет к будильнику доли секунды и даёт периоды короче секунды: 0,25 с приMASKSS= 6 иPREDIV_S= 255.- Календарь читают по порядку
SSR,TR,DR; после Stop ждутRSF; сBYPSHAD= 1 перечитываютSSR— иначе ошибка до секунды. - TAMP у G474: 3 входа, 4 внутренних источника, 32 резервных регистра
(у G431 — 16, у F407 — 20 внутри RTC); вмешательство стирает их и пишет
метку времени без года; флаги RTC и TAMP гасит единица в
SCR.
Полные исходники
rtc12a.c — RTC и TAMP у STM32G474 по регистрам.
/* 12а: RTC и TAMP у STM32G474 по регистрам — LSE, делители, гладкая калибровка, сдвиг на доли
* секунды, таймер пробуждения, будильник с субсекундами, чтение календаря, резервные регистры
* TAMP, вход вмешательства с меткой времени. Собирается armclang: -DSTM32G474xx (и -DSTM32G431xx —
* у него резервных регистров 16). Числа — RM0440, главы 35 и 36; проверка — _verify/rtc12a_check.py. */
#include <stdint.h>
#include <stddef.h>
#define WUCKSEL_DIV2 3u /* 011: RTCCLK / 2, шаг 61 мкс */
#define WUCKSEL_1HZ 4u /* 10x: ck_spre, обычно 1 Гц */
#define WUCKSEL_1HZ_17 6u /* 11x: ck_spre, к WUT добавляется 2^16 */
#define CALR_FAST_20PPM 21u /* кварц спешит на 20 ppm: замаскировать 21 импульс из 2^20 */
#define CALR_SLOW_35PPM (RTC_CALR_CALP | 475u) /* отстаёт на 35 ppm: +512 − 475 = +37 импульсов */
_Static_assert(RTC_BASE == 0x40002800u && TAMP_BASE == 0x40002400u, "RTC и TAMP — соседние блоки на APB1");
_Static_assert(offsetof(RTC_TypeDef, SSR) == 0x08u && offsetof(RTC_TypeDef, ICSR) == 0x0Cu
&& offsetof(RTC_TypeDef, PRER) == 0x10u && offsetof(RTC_TypeDef, WUTR) == 0x14u
&& offsetof(RTC_TypeDef, CR) == 0x18u && offsetof(RTC_TypeDef, WPR) == 0x24u
&& offsetof(RTC_TypeDef, CALR) == 0x28u && offsetof(RTC_TypeDef, SHIFTR) == 0x2Cu
&& offsetof(RTC_TypeDef, TSSSR) == 0x38u && offsetof(RTC_TypeDef, ALRMASSR) == 0x44u
&& offsetof(RTC_TypeDef, SR) == 0x50u && offsetof(RTC_TypeDef, SCR) == 0x5Cu,
"смещения регистров RTC — по таблице 336 RM0440");
_Static_assert(offsetof(TAMP_TypeDef, BKP0R) == 0x100u && RTC_TAMP_NB == 3u && RTC_TAMP_INT_NB == 4u,
"резервные регистры с 0x100; три внешних входа и четыре внутренних источника");
#if defined(STM32G474xx)
_Static_assert(RTC_BACKUP_NB == 32u && offsetof(TAMP_TypeDef, BKP31R) == 0x17Cu, "у G474 — 32 регистра");
#elif defined(STM32G431xx)
_Static_assert(RTC_BACKUP_NB == 16u && offsetof(TAMP_TypeDef, BKP15R) == 0x13Cu, "у G431 — 16 регистров");
#endif
_Static_assert(RTC_PRER_PREDIV_A_Pos == 16u && RTC_PRER_PREDIV_A_Msk == (0x7Fu << 16)
&& RTC_PRER_PREDIV_S_Msk == 0x7FFFu, "PREDIV_A — 7 бит с 16-го, PREDIV_S — 15 бит");
_Static_assert(RTC_SSR_SS_Msk == 0xFFFFu && RTC_ALRMASSR_SS_Msk == 0x7FFFu && RTC_ALRMASSR_MASKSS_Pos == 24u,
"SS — 16 бит (15-й — переполнение после сдвига), в будильнике — 15, MASKSS с 24-го бита");
_Static_assert(RTC_CALR_CALP == (1u << 15) && RTC_CALR_CALW8 == (1u << 14) && RTC_CALR_CALW16 == (1u << 13)
&& RTC_CALR_CALM_Msk == 0x1FFu, "CALP, CALW8, CALW16 — биты 15, 14, 13; CALM — 9 бит");
_Static_assert(CALR_FAST_20PPM == 0x0015u && CALR_SLOW_35PPM == 0x81DBu, "CALR для +20 и −35 ppm");
_Static_assert(RTC_SHIFTR_ADD1S == (1u << 31) && RTC_SHIFTR_SUBFS_Msk == 0x7FFFu, "ADD1S — бит 31, SUBFS — 15 бит");
_Static_assert(RTC_CR_WUCKSEL_Msk == 7u && RTC_WUTR_WUT_Msk == 0xFFFFu && RTC_CR_WUTE == (1u << 10)
&& RTC_CR_WUTIE == (1u << 14) && RTC_CR_BYPSHAD == (1u << 5),
"WUCKSEL — 3 бита, WUT — 16, WUTE — бит 10, WUTIE — 14, BYPSHAD — 5");
_Static_assert(RTC_ICSR_WUTWF == (1u << 2) && RTC_ICSR_SHPF == (1u << 3) && RTC_ICSR_RSF == (1u << 5)
&& RTC_ICSR_INITF == (1u << 6) && RTC_ICSR_INIT == (1u << 7) && RTC_ICSR_RECALPF == (1u << 16),
"флаги ICSR");
_Static_assert(RTC_SR_WUTF == (1u << 2) && RTC_SCR_CWUTF == RTC_SR_WUTF && RTC_SCR_CTSF == RTC_SR_TSF,
"флаг в SR и бит его сброса в SCR — на одном месте");
_Static_assert(TAMP_FLTCR_TAMPFREQ_Pos == 0u && TAMP_FLTCR_TAMPFLT_Pos == 3u && TAMP_FLTCR_TAMPPRCH_Pos == 5u
&& TAMP_FLTCR_TAMPPUDIS == (1u << 7), "поля TAMP_FLTCR");
_Static_assert(TAMP_CR2_TAMP1NOERASE == 1u && TAMP_CR2_TAMP1MF == (1u << 16) && TAMP_CR2_TAMP1TRG == (1u << 24),
"TAMP1NOER и TAMP1MSK из RM в заголовке называются TAMP1NOERASE и TAMP1MF");
_Static_assert(RCC_BDCR_RTCSEL_0 == (1u << 8) && RCC_BDCR_RTCEN == (1u << 15) && RCC_APB1ENR1_RTCAPBEN == (1u << 10)
&& PWR_CR1_DBP == (1u << 8), "RTCSEL = 01 — LSE; RTCEN; такт шины RTC; DBP");
_Static_assert(EXTI_IMR1_IM20 == (1u << 20) && RTC_WKUP_IRQn == 3 && RTC_TAMP_LSECSS_IRQn == 2,
"таймер пробуждения — линия EXTI 20");
volatile uint32_t wakeups, tampers;
volatile uint32_t ts_time, ts_date, ts_ss;
static void rtc_unlock(void)
{
RTC->WPR = 0xCAu; /* ключи те же, что у F4 */
RTC->WPR = 0x53u;
}
static void rtc_lock(void)
{
RTC->WPR = 0xFFu;
}
void rtc_lse_on(void)
{
RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN | RCC_APB1ENR1_RTCAPBEN; /* у G4 у RTC свой такт шины */
PWR->CR1 |= PWR_CR1_DBP; /* доступ к резервному домену */
RCC->BDCR |= RCC_BDCR_LSEON;
while (!(RCC->BDCR & RCC_BDCR_LSERDY)) { }
RCC->BDCR |= RCC_BDCR_RTCSEL_0 | RCC_BDCR_RTCEN; /* 01 — LSE; сменить потом — только сбросом домена */
}
/* 1 Гц = 32768 / ((a + 1) × (s + 1)); шаг долей секунды — 1 / (s + 1) */
void rtc_prescalers(uint32_t a, uint32_t s)
{
rtc_unlock();
RTC->ICSR |= RTC_ICSR_INIT; /* календарь стоит */
while (!(RTC->ICSR & RTC_ICSR_INITF)) { }
RTC->PRER = s; /* RM0440: две раздельные записи */
RTC->PRER = s | (a << RTC_PRER_PREDIV_A_Pos);
RTC->ICSR &= ~RTC_ICSR_INIT; /* пошёл через 4 такта RTCCLK */
rtc_lock();
}
void rtc_calibrate(uint32_t calr)
{
rtc_unlock();
while (RTC->ICSR & RTC_ICSR_RECALPF) { } /* прошлая запись ещё не принята */
RTC->CALR = calr;
rtc_lock();
}
/* назад на subfs / (PREDIV_S + 1) с; с add1s — вперёд на 1 − subfs / (PREDIV_S + 1) */
int rtc_shift(uint32_t add1s, uint32_t subfs)
{
if (RTC->SSR & 0x8000u) /* SS[15] = 1 — новый сдвиг переполнит SS */
return -1;
rtc_unlock();
while (RTC->ICSR & RTC_ICSR_SHPF) { }
RTC->SHIFTR = (add1s ? RTC_SHIFTR_ADD1S : 0u) | subfs;
rtc_lock();
return 0;
}
void rtc_wakeup(uint32_t wucksel, uint32_t wut)
{
rtc_unlock();
RTC->CR &= ~(RTC_CR_WUTE | RTC_CR_WUTIE);
while (!(RTC->ICSR & RTC_ICSR_WUTWF)) { } /* WUTR и WUCKSEL можно менять */
RTC->WUTR = wut;
RTC->CR = (RTC->CR & ~RTC_CR_WUCKSEL) | wucksel;
RTC->SCR = RTC_SCR_CWUTF;
RTC->CR |= RTC_CR_WUTIE | RTC_CR_WUTE;
rtc_lock();
EXTI->IMR1 |= EXTI_IMR1_IM20; /* из Stop будит линия EXTI 20 */
EXTI->RTSR1 |= EXTI_RTSR1_RT20;
NVIC_EnableIRQ(RTC_WKUP_IRQn);
}
void RTC_WKUP_IRQHandler(void)
{
if (RTC->SR & RTC_SR_WUTF) {
RTC->SCR = RTC_SCR_CWUTF; /* единица в SCR — без ключей и без INIT */
wakeups++;
}
EXTI->PR1 = EXTI_PR1_PIF20;
}
/* будильник A: календарь как в разделе 12 плюс доли секунды — сравниваются SS[maskss−1:0] */
void rtc_alarm_a_ss(uint32_t alrmar, uint32_t maskss, uint32_t ss)
{
rtc_unlock();
RTC->CR &= ~(RTC_CR_ALRAE | RTC_CR_ALRAIE);
while (!(RTC->ICSR & RTC_ICSR_ALRAWF)) { }
RTC->ALRMAR = alrmar;
RTC->ALRMASSR = (maskss << RTC_ALRMASSR_MASKSS_Pos) | ss;
RTC->SCR = RTC_SCR_CALRAF;
RTC->CR |= RTC_CR_ALRAE | RTC_CR_ALRAIE;
rtc_lock();
}
typedef struct {
uint32_t ss, tr, dr;
} rtc_now_t;
/* BYPSHAD = 1: SSR прочитан дважды одинаковым — между чтениями календарь не менялся */
void rtc_read(rtc_now_t *t)
{
do {
t->ss = RTC->SSR;
t->tr = RTC->TR;
t->dr = RTC->DR;
} while (t->ss != RTC->SSR);
}
/* BYPSHAD = 0, после выхода из Stop: теневые регистры ещё старые */
void rtc_resync(void)
{
rtc_unlock();
RTC->ICSR &= ~RTC_ICSR_RSF;
rtc_lock();
while (!(RTC->ICSR & RTC_ICSR_RSF)) { }
}
/* доля секунды в миллисекундах; после сдвига SS бывает больше PREDIV_S — доля отрицательная */
int32_t rtc_ms(uint32_t ss, uint32_t prediv_s)
{
return ((int32_t)prediv_s - (int32_t)ss) * 1000 / ((int32_t)prediv_s + 1);
}
void bkp_write(uint32_t i, uint32_t v)
{
(&TAMP->BKP0R)[i] = v; /* нужен DBP; пока стоит флаг вмешательства — не запишется */
}
uint32_t bkp_read(uint32_t i)
{
return (&TAMP->BKP0R)[i];
}
/* TAMP_IN1 (PC13): крышка замыкает вход на землю; 4 выборки подряд при 16 Гц — 0,25 с;
* резервные регистры стираются, метка времени пишется */
void tamp_in1_setup(void)
{
TAMP->FLTCR = (2u << TAMP_FLTCR_TAMPFLT_Pos) | (4u << TAMP_FLTCR_TAMPFREQ_Pos);
TAMP->CR2 &= ~(TAMP_CR2_TAMP1NOERASE | TAMP_CR2_TAMP1MF | TAMP_CR2_TAMP1TRG); /* низкий уровень — вскрытие */
TAMP->IER |= TAMP_IER_TAMP1IE;
TAMP->CR1 |= TAMP_CR1_TAMP1E;
rtc_unlock();
RTC->CR |= RTC_CR_TAMPTS; /* метка времени на вмешательство, даже при TSE = 0 */
rtc_lock();
EXTI->IMR1 |= EXTI_IMR1_IM19; /* вмешательство и метка — линия EXTI 19 */
EXTI->RTSR1 |= EXTI_RTSR1_RT19;
NVIC_EnableIRQ(RTC_TAMP_LSECSS_IRQn);
}
void RTC_TAMP_LSECSS_IRQHandler(void)
{
if (TAMP->SR & TAMP_SR_TAMP1F) {
if (RTC->SR & RTC_SR_TSF) { /* сначала прочитать: сброс TSF обнуляет метку */
ts_ss = RTC->TSSSR;
ts_time = RTC->TSTR;
ts_date = RTC->TSDR;
RTC->SCR = RTC_SCR_CTSF;
}
TAMP->SCR = TAMP_SCR_CTAMP1F; /* пока флаг стоит, резервные регистры держатся в нуле */
tampers++;
}
EXTI->PR1 = EXTI_PR1_PIF19;
}
Дальше: раздел 12б «Самопроверка прошивки: ОЗУ, Flash, такты и ядро во время работы» — марш ОЗУ, CRC образа против эталона, проверка частоты, регистров ядра и стека.