12. Служебные блоки: сторожевые таймеры, CRC, RTC
Три блока, которые редко разбирают подробно, но без которых серьёзное изделие не выпускают: сторож, перезапускающий зависшую программу, проверка целостности данных и часы, идущие при снятом питании.
Как это называется в документации
Оригинал Перевод independent watchdog независимый сторожевой таймер window watchdog оконный сторожевой таймер to refresh / kick / feed перезагрузить счётчик, «покормить» reload value значение перезагрузки reset flag признак причины сброса cyclic redundancy check циклический избыточный код backup domain резервный домен prescaler предделитель refresh window окно, в котором разрешено кормление early wakeup interrupt (EWI) раннее прерывание перед сбросом reset pulse on NRST импульс сброса на выводе NRST alarm будильник часов alarm mask маска будильника: поле не сравнивается binary-coded decimal (BCD) двоично-десятичный код write protection key ключ снятия защиты записи configuration mode (CNF) режим настройки регистров RTC
12.1 Зачем сторожевой таймер
Программа может зависнуть: бесконечный цикл ожидания флага, который никогда не придёт, повреждённый указатель, помеха на шине. В лаборатории вы нажмёте сброс, а изделие в поле должно перезапуститься само.
Сторожевой таймер — счётчик, который непрерывно считает вниз и при достижении нуля перезагружает камень. Программа обязана регулярно его сбрасывать («кормить»). Перестала кормить — значит зависла, и перезапуск произойдёт сам.
У STM32 таких таймеров два, и они решают разные задачи.
12.2 IWDG: независимый
Тактируется от собственного генератора LSI и работает, даже если остановилась вся остальная тактовая система. Именно это делает его надёжным: отказ PLL или кварца его не касается.
#define IWDG_KEY_RELOAD 0xAAAAu /* перезагрузить счётчик */
#define IWDG_KEY_ENABLE 0xCCCCu /* запустить */
#define IWDG_KEY_UNLOCK 0x5555u /* открыть доступ к настройкам */
void iwdg_start_2s(void)
{
IWDG->KR = IWDG_KEY_ENABLE; /* пошёл; заодно включает LSI */
IWDG->KR = IWDG_KEY_UNLOCK; /* снять защиту записи */
IWDG->PR = IWDG_PR_PR_2; /* 100b -> делитель 64 */
IWDG->RLR = 1250u; /* значение перезагрузки */
while (IWDG->SR) { } /* дождаться применения настроек — нужен идущий LSI */
IWDG->KR = IWDG_KEY_RELOAD;
}
void iwdg_feed(void)
{
IWDG->KR = IWDG_KEY_RELOAD;
}
Порядок важен. Новые PR и RLR доходят до счётчика за несколько
тактов LSI, и флаги в SR снимаются только при идущем генераторе. Ключ
0xCCCC включает LSI сам, поэтому сторожа запускают первым: если ждать
SR раньше, а LSI до этого никто не включал, цикл не кончится никогда.
До конца настройки сторож считает с тем, что стоит после сброса, —
делитель 4 и 4095, у F1 это 4096 × 4 / 40000 ≈ 0,4 секунды; на запись
двух регистров этого хватает с запасом.
Расчёт таймаута:
таймаут = (RLR + 1) × предделитель / частота LSI
= (1250 + 1) × 64 / 40000 ≈ 2 секунды
Предделитель задаётся тремя битами: 000 — деление на 4, каждый следующий шаг
удваивает, до 256. Счётчик перезагрузки 12-битный, максимум 4095. Отсюда
предельный таймаут: 4096 × 256 / 40000 ≈ 26 секунд.
Частота LSI зависит от семейства. У F1 номинал 40 кГц, у F4 и F7 —
32 кГц: тот же код даёт там (1250 + 1) × 64 / 32000 ≈ 2,5 секунды
вместо двух, а предел — 4096 × 256 / 32000 ≈ 32,8 секунды против 26,2.
И считать надо не по номиналу: если генератор окажется на четверть
быстрее, двухсекундный таймаут превращается в 1,6 секунды — кормить
сторожа надо исходя из самого короткого таймаута.
Как это выглядит во времени — счётчик сторожа, кормление и сброс после зависания:
⚠️ LSI неточен. Его частота гуляет в пределах десятков процентов в зависимости от температуры и экземпляра кристалла. Считать таймаут впритык нельзя: если по расчёту получается 2 секунды, кормить надо хотя бы раз в полсекунды. Точные границы разброса — в datasheet, раздел электрических характеристик.
⚠️ Запущенный IWDG не выключается. Ни программно, ни как-либо ещё — только сбросом камня. Это сделано намеренно: сбойный код не должен иметь возможности отключить сторожа.
12.3 WWDG: оконный
Второй сторож устроен хитрее. Он ругается не только на позднее кормление, но и на слишком раннее: есть окно, внутри которого счётчик разрешено обновлять, а вне его — сброс.
Зачем так: если программа сорвалась в цикл, который случайно проходит мимо вызова «покормить» чаще, чем надо, обычный сторож этого не заметит. Оконный — заметит, потому что нарушен ожидаемый ритм.
Тактируется WWDG от шины APB1, поэтому останавливается вместе с ней и годится скорее для контроля временных характеристик программы, чем для защиты от полного зависания. На практике чаще берут IWDG, а оконный применяют там, где важно доказать соблюдение ритма — например, в изделиях, проходящих сертификацию по функциональной безопасности.
Как считать. Счётчик 7-битный и тактируется от PCLK1 через делитель 4096
и ещё 2^WDGTB. Сброс происходит, когда падает бит T6, то есть счётчик
переходит с 0x40 на 0x3F, либо когда кормят, пока счётчик выше окна W.
Запускают с 0x7F: до сброса 64 тика.
| F103, PCLK1 36 МГц | F407, PCLK1 42 МГц | |
|---|---|---|
тик при WDGTB = 3 (÷8) |
4096 × 8 / 36 МГц = 910 мкс | 4096 × 8 / 42 МГц = 780 мкс |
| с 0x7F до сброса — 64 тика | 58,3 мс | 49,9 мс |
окно W = 0x50 открывается через 47 тиков |
42,8 мс | 36,7 мс |
Кормить надо между этими границами: на F103 — от 42,8 до 58,3 мс после прошлого раза, на F407 — от 36,7 до 49,9 мс. Ритм 45 мс подходит обоим.
#define WWDG_T0 0x7Fu /* с чего считать: до сброса 64 тика */
#define WWDG_W 0x50u /* кормить можно, когда счётчик ≤ 0x50 */
void wwdg_start(void)
{
RCC->APB1ENR |= RCC_APB1ENR_WWDGEN;
WWDG->CFR = WWDG_CFR_WDGTB | WWDG_W; /* PCLK1 / 4096 / 8, окно 0x50 */
WWDG->CR = WWDG_CR_WDGA | WWDG_T0; /* пуск: до сброса 64 тика */
}
void wwdg_feed(void) /* только когда счётчик уже ≤ WWDG_W */
{
WWDG->CR = WWDG_T0;
}
Эти две функции прогнаны на компьютере против модели счётчика. Делитель в модели идёт непрерывно и кормлением не сбрасывается — как в железе, — поэтому границы окна плавают на один тик.
| Кормим раз в | F103 | F407 |
|---|---|---|
| 30 мс | сброс «рано» на первом кормлении, 30,000 мс | сброс «рано», 30,000 мс |
| 45 мс | работает | работает |
| 50 мс | работает | сброс «поздно», 49,932 мс |
| 60 мс | сброс «поздно», 58,254 мс | сброс «поздно», 49,932 мс |
Одни и те же 0x7F и 0x50 на двух сериях дают разные окна: тик зависит от частоты шины.
⚠️ Счётчик 0x40 — это уже край. Запуск или кормление значением 0x40 оставляет до сброса один тик: модель сбросила камень через 0,910 мс. То же с окном
W= 0x40 — кормить разрешено лишь в течение последнего тика. Запускают с 0x7F, окно берут заметно ниже.
Раннее предупреждение. Бит EWI в CFR даёт прерывание, когда счётчик
доходит до 0x40, — за один тик до сброса. Кормить из него не стоит (12.7),
а вот сохранить след того, что делала программа, — самое место.
12.4 Как узнать, что был сброс от сторожа
После перезапуска полезно понять его причину: включили питание, нажали кнопку
или сработал сторож. Признаки лежат в RCC->CSR:
uint8_t reset_was_watchdog(void)
{
uint8_t flag = (RCC->CSR & RCC_CSR_IWDGRSTF) ? 1u : 0u;
RCC->CSR |= RCC_CSR_RMVF; /* сбросить признаки причин сброса */
return flag;
}
Флаги накапливаются: кнопка, сторож и программный сброс их не трогают,
очищают их только сброс по питанию и бит RMVF. Если флаги не очистить,
после следующего нажатия кнопки или программного сброса вы увидите и
старый IWDGRSTF и решите, что программа снова зависла.
Практический приём: при обнаружении сброса от сторожа записать это в журнал или резервный регистр (раздел 10). Изделие, которое молча перезагружается раз в сутки, иначе никогда не будет замечено.
Весь список причин. Внутренний сброс — от сторожа, программный, при выходе
из сна — тоже дёргает вывод NRST, поэтому флаг PINRSTF стоит почти при любом
сбросе. Кто проверит его первым, всё сочтёт нажатием кнопки. Первым проверяют
питание, а PINRSTF — последним:
/* внутренний сброс тоже дёргает вывод NRST, поэтому PINRSTF стоит почти
* при любом сбросе — его проверяют последним */
rst_t reset_cause(uint32_t csr)
{
if (csr & RCC_CSR_PORRSTF)
return RST_POWER;
#ifdef RCC_CSR_BORRSTF
if (csr & RCC_CSR_BORRSTF)
return RST_BROWNOUT;
#endif
if (csr & RCC_CSR_IWDGRSTF)
return RST_IWDG;
if (csr & RCC_CSR_WWDGRSTF)
return RST_WWDG;
if (csr & RCC_CSR_SFTRSTF)
return RST_SOFT;
if (csr & RCC_CSR_LPWRRSTF)
return RST_LOWPOWER;
if (csr & RCC_CSR_PINRSTF)
return RST_PIN;
return RST_UNKNOWN;
}
| Событие | Флаги в RCC->CSR |
Ответ |
|---|---|---|
| включили питание | PORRSTF, PINRSTF (у F4 ещё BORRSTF) |
питание |
| нажали кнопку сброса | PINRSTF |
кнопка |
| сработал IWDG | IWDGRSTF, PINRSTF |
IWDG |
| сработал WWDG | WWDGRSTF, PINRSTF |
WWDG |
NVIC_SystemReset() |
SFTRSTF, PINRSTF |
программный |
Порядок «кнопка первой» назвал бы кнопкой четыре случая из пяти. У F4 есть ещё
флаг просадки питания BORRSTF; при включении он стоит вместе с PORRSTF,
поэтому проверяется вторым.
⚠️ Отладка со сторожем. Останов на точке прерывания замораживает программу, но не сторожа — через пару секунд плата перезагрузится прямо из-под отладчика, и это выглядит крайне загадочно. Лечится битами в
DBGMCU->CR: они останавливают счётчик, пока ядро стоит на паузе.На F4 те же биты разнесены по
DBGMCU->APB1FZиDBGMCU->APB2FZ, и HAL закрывает их макросами__HAL_DBGMCU_FREEZE_IWDG()и подобными (раздел 35.5).
c DBGMCU->CR |= DBGMCU_CR_DBG_IWDG_STOP | DBGMCU_CR_DBG_WWDG_STOP;
12.5 Аппаратный CRC
Блок, считающий контрольную сумму быстрее, чем это сделал бы процессор. Используется для проверки целостности: прошивки перед запуском, пакета из линии связи, данных во внешней памяти.
uint32_t crc32_words(const uint32_t *data, uint32_t count)
{
RCC->AHBENR |= RCC_AHBENR_CRCEN; /* F1; у F4 — RCC->AHB1ENR |= RCC_AHB1ENR_CRCEN */
CRC->CR = CRC_CR_RESET; /* начать новый расчёт */
for (uint32_t i = 0; i < count; i++) {
CRC->DR = data[i]; /* записал слово — результат уже готов */
}
return CRC->DR; /* чтение того же регистра даёт результат */
}
Регистр один и тот же: запись подаёт данные, чтение забирает накопленный результат.
⚠️ У F1 и F4 вариант фиксирован — CRC-32/MPEG-2: полином
0x04C11DB7, начальное значение0xFFFFFFFF, слово идёт старшим битом вперёд, без отражений и без завершающей инверсии. Сcrc32из zlib и FCS Ethernet он не совпадёт, даже если выровнять байты. Их вариант на этом блоке тоже получают: каждое слово подают через__RBIT, а результат берут как~__RBIT(CRC->DR). Все шесть параметров CRC и таблица вариантов — раздел 43.1; если сумму проверяет другая сторона, вариант согласуют заранее.Свой полином и ширину задают у F3, F7, G0, G4, L0, L4, H7 и более новых семейств, а из F0 — только у F071, F072, F078 и F09x; у остальных F0 полином тот же, настраиваются лишь начальное значение и отражения. Если протокол требует конкретного варианта CRC-16, на F1 его придётся считать программно.
⚠️ Данные подаются словами. Массив байтов надо либо дополнять до кратности четырём, либо учитывать порядок байтов — иначе результат не сойдётся с тем, что посчитала другая сторона.
12.6 RTC: часы реального времени
Живут в резервном домене (раздел 10) и продолжают идти от батарейки, когда всё остальное обесточено. Тактируются обычно от часового кварца LSE на 32 768 Гц — число не случайное: это 2¹⁵, и делением пополам пятнадцать раз получается ровно одна секунда.
Разница между поколениями существенная:
| STM32F1 | STM32F4 | |
|---|---|---|
| что считает | 32-битный счётчик секунд | полноценный календарь |
| формат | число секунд, дату считаете сами | часы, минуты, дата, день недели в BCD |
| будильник | одно значение счётчика | два, A и B, с масками |
| дополнительно | — | периодическое пробуждение, метки времени |
У F1 придётся самому переводить счётчик секунд в дату — обычно берут готовую функцию преобразования из библиотеки времени. У F4 всё считает сам блок.
Общее для обоих: доступ к резервному домену нужно открыть битом DBP,
а после записи в регистры RTC — дождаться завершения операции. Синхронизация
занимает такты медленного генератора, то есть десятки микросекунд, и без
ожидания вы прочитаете старое значение.
Практическая мелочь: после подачи питания регистры RTC могут содержать мусор, если батарейка не стояла. Признак «часы уже настроены» обычно кладут в резервный регистр — записали туда условное число при первой настройке, и при старте проверяете его наличие.
Будильник: у F1 — число, у F4 — маски
У F1 будильник — такое же 32-битное число секунд, как и сам счётчик:
прерывание приходит, когда CNT становится равен ALR. «Разбудить через
минуту» — значит прочитать счётчик и записать в ALRH:ALRL сумму. Переход
через 2³² сумма делает сама: 0xFFFFFFF0 + 32 = 0x00000010, и будильник
сработает вовремя. Такого переполнения ждать 136 лет, но и проверять тут
нечего — беззнаковая арифметика права всегда.
Запись в ALR у F1 идёт через режим настройки, как и в сам счётчик: дождаться
RTOFF, поставить CNF, записать, снять CNF — только в этот момент запись
уходит в резервный домен — и снова дождаться RTOFF (rtc_now — чтение
счётчика, о нём ниже):
void rtc_alarm_in(uint32_t seconds)
{
PWR->CR |= PWR_CR_DBP;
RTC->CRL &= ~RTC_CRL_RSF; /* после сброса или Stop — дождаться синхронизации */
while (!(RTC->CRL & RTC_CRL_RSF)) { }
uint32_t at = f1_alarm_at(rtc_now(), seconds);
while (!(RTC->CRL & RTC_CRL_RTOFF)) { } /* прошлая запись закончилась */
RTC->CRL |= RTC_CRL_CNF; /* режим настройки */
RTC->ALRH = (uint16_t)(at >> 16);
RTC->ALRL = (uint16_t)at;
RTC->CRL &= ~(RTC_CRL_CNF | RTC_CRL_ALRF); /* выход — запись пошла; флаг сбросить */
while (!(RTC->CRL & RTC_CRL_RTOFF)) { }
RTC->CRH |= RTC_CRH_ALRIE;
EXTI->IMR |= EXTI_IMR_MR17;
EXTI->RTSR |= EXTI_RTSR_TR17;
NVIC_EnableIRQ(RTCAlarm_IRQn);
}
RSF сначала сбрасывают и только потом ждут: после сброса или выхода из Stop
он мог остаться поднятым с прошлого раза, и ожидание без сброса ничего
не гарантирует.
У F4 будильников два, A и B, и сравниваются они не с числом, а с календарём.
Каждое поле — секунды, минуты, часы, дату — можно исключить из сравнения своим
битом маски: MSK1 … MSK4, биты 7, 15, 23 и 31 регистра ALRMAR. Отсюда
расписания без всякой арифметики:
| Расписание | Не сравнивать | ALRMAR |
|---|---|---|
| каждую минуту в :30 | дату, часы, минуты | 0x80808030 |
| каждый час в :00:00 | дату, часы | 0x80800000 |
| каждый день в 07:15:00 | дату | 0x80071500 |
Собрать ALRMAR для своего расписания и времени — вкладка «RTC» в калькуляторах
приложения; там же для F1 — будильник-число с переходом через 2³² и счётчик
секунд как дата.
Значения полей — в двоично-десятичном коде: 15 минут записываются как 0x15,
а не как 0x0F. Регистры RTC у F4 закрыты от случайной записи: сначала два
ключа в WPR, потом выключить будильник и дождаться ALRAWF — без этого
запись в ALRMAR не проходит:
uint32_t alrmar(uint32_t masks, uint32_t h, uint32_t m, uint32_t s)
{
return masks | (bcd(h) << 16) | (bcd(m) << 8) | bcd(s);
}
void rtc_alarm_a(uint32_t value)
{
PWR->CR |= PWR_CR_DBP; /* доступ к резервному домену (раздел 10) */
RTC->WPR = 0xCAu; /* снять защиту записи — два ключа подряд */
RTC->WPR = 0x53u;
RTC->CR &= ~(RTC_CR_ALRAE | RTC_CR_ALRAIE);
while (!(RTC->ISR & RTC_ISR_ALRAWF)) { } /* ALRMAR можно менять */
RTC->ALRMAR = value;
RTC->ISR = ~(RTC_ISR_ALRAF | RTC_ISR_INIT) | (RTC->ISR & RTC_ISR_INIT); /* сбросить флаг, INIT не трогать */
RTC->CR |= RTC_CR_ALRAE | RTC_CR_ALRAIE;
RTC->WPR = 0xFFu; /* защиту обратно */
EXTI->IMR |= EXTI_IMR_MR17; /* будильник — линия EXTI 17 */
EXTI->RTSR |= EXTI_RTSR_TR17;
NVIC_EnableIRQ(RTC_Alarm_IRQn);
}
Флаги в ISR сбрасываются записью нуля, а единица их не трогает, — поэтому
пишут «всё единицами, кроме ALRAF». Бит INIT в том же регистре обычный:
единица в нём останавливает календарь, поэтому его значение сохраняют.
Общее у обоих — линия EXTI 17. Будильник выведен на неё, и именно через неё он будит камень из Stop (раздел 10.3): маска и передний фронт настраиваются так же, как для вывода. В обработчике сбрасывают два флага:
void RTC_Alarm_IRQHandler(void)
{
if (RTC->ISR & RTC_ISR_ALRAF) {
RTC->ISR = ~(RTC_ISR_ALRAF | RTC_ISR_INIT) | (RTC->ISR & RTC_ISR_INIT);
alarms++;
}
EXTI->PR = EXTI_PR_PR17; /* не сбросить — обработчик вызывается снова и снова */
}
Не сброшен флаг самого RTC (ALRAF, у F1 — ALRF) — сигнал на линии 17
так и остаётся поднятым, нового фронта не будет, и будильник сработает один
раз. Не сброшен флаг линии в EXTI->PR — обработчик будет вызываться без
конца. Ключи WPR в обработчике не нужны: флаг будильника защита записи
не прикрывает.
Счётчик F1 читают трижды
32-битный счётчик F1 читается двумя 16-битными половинами, а младшая раз в 65 536 с — каждые 18,2 часа — переходит через 0xFFFF. Если переход пришёлся между двумя чтениями, результат врёт на 18 часов с лишним. Модель на компьютере ставит счётчик на 0x0000FFFF и даёт ему тикнуть сразу после первого чтения:
| Как читали | Получили | На самом деле | Ошибка |
|---|---|---|---|
| старшую, потом младшую | 0x00000000 | 0x00010000 | на 65 536 с назад |
| младшую, потом старшую | 0x0001FFFF | 0x00010000 | на 65 535 с вперёд |
| старшую, младшую и старшую ещё раз | 0x00010000 | 0x00010000 | нет |
Лечение — перечитать старшую половину и повторить, если она изменилась (на компьютере вместо регистров — модель):
#define CNT_HI() RTC->CNTH
#define CNT_LO() RTC->CNTL
uint32_t rtc_now(void)
{
uint16_t hi, lo;
do {
hi = CNT_HI();
lo = CNT_LO();
} while (hi != CNT_HI());
return ((uint32_t)hi << 16) | lo;
}
Обычно это три чтения; шесть — только в тот редкий раз, когда счётчик тикнул между ними. Ошибка такого рода опасна именно редкостью: на столе её не увидеть, а прибор в поле раз в сутки с небольшим получает неверное время.
12.7 Грабли
Сторож срабатывает при отладке. Не выставлены биты DBGMCU->CR.
Таймаут посчитан впритык. LSI гуляет на десятки процентов — берите двойной запас.
Программа зависла в настройке сторожа. SR ждут до ключа 0xCCCC,
а LSI никто не включал — флаги не снимутся. Сначала 0xCCCC (12.2).
Кормление из прерывания. Соблазнительно повесить iwdg_feed на таймер —
и тем самым обессмыслить всю затею: зависшая основная программа продолжит
«кормиться» из работающего прерывания. Кормить надо из главного цикла,
и лучше — с проверкой, что все задачи отчитались.
Флаги причин сброса не очищены. Признак живёт до выключения питания и вводит в заблуждение после следующих сбросов кнопкой или программой.
Причина сброса всегда «кнопка». PINRSTF проверен первым, а он стоит
почти при любом сбросе (12.4).
WWDG сбрасывает сразу после запуска. Счётчик запущен со значением 0x40 или окно задано равным ему — до сброса один тик. Запускать с 0x7F.
WWDG сбрасывает при кормлении. Кормят раньше, чем счётчик опустился
до окна: на F103 с W = 0x50 — не раньше чем через 42,8 мс.
Настройка WWDG не переносится между сериями. Тик зависит от PCLK1: одни и те же 0x7F и 0x50 дают на F103 окно 42,8–58,3 мс, на F407 — 36,7–49,9 мс.
CRC не сброшен перед новым расчётом. Забыли CRC_CR_RESET — получите
продолжение предыдущей суммы.
Часы «сбрасываются» при перезагрузке. Значит инициализация RTC выполняется безусловно при каждом старте. Она должна выполняться один раз, а признак настройки храниться в резервном регистре.
Будильник срабатывает один раз. В обработчике не сброшен флаг самого
RTC — ALRAF у F4, ALRF у F1: сигнал на линии EXTI 17 остаётся поднятым,
и нового фронта не будет.
Обработчик будильника вызывается без конца. Не сброшен флаг линии —
EXTI->PR = EXTI_PR_PR17.
Время на F1 изредка прыгает на 18 часов. Счётчик прочитан двумя половинами без повтора, и младшая перешла через 0xFFFF между чтениями — ошибка 65 536 с. Старшую половину перечитывают (12.6).
Будильник F4 не записывается. Не сняты ключи WPR или не дождались
ALRAWF — запись в ALRMAR не проходит.
12.8 Итоги
- IWDG тактируется от собственного генератора и переживает отказ основного тактирования; запущенный, он не выключается до сброса.
- Таймаут =
(RLR + 1) × предделитель / LSI; предел около 26 секунд. LSI неточен — закладывайте двойной запас. - WWDG ловит не только позднее, но и слишком раннее кормление — контроль ритма, а не только факта жизни. Тик = 4096 × 2^WDGTB / PCLK1, запуск с 0x7F — 64 тика до сброса, кормить — только когда счётчик опустился до окна; на F103 при ÷8 и окне 0x50 это 42,8–58,3 мс.
- Причину сброса разбирают начиная с питания, а
PINRSTFпроверяют последним: внутренние сбросы тоже дёргают NRST. - Кормить сторожа из прерывания — значит обессмыслить его.
- Причину сброса читают из
RCC->CSR, и флаги надо очищать вручную. - При отладке сторожа останавливают битами
DBGMCU->CR, иначе плата перезагружается на точках останова. - Аппаратный CRC у F1 и F4 умеет только CRC-32/MPEG-2; с zlib и Ethernet он
сходится лишь через
__RBITна входе и~__RBITна выходе. - RTC у F1 — счётчик секунд, у F4 — полноценный календарь; инициализировать часы нужно однократно, признак хранить в резервном регистре.
- Будильник у F1 — число секунд в
ALR, запись через режим настройки; у F4 — два будильника с масками полей в BCD, запись после ключейWPRиALRAWF. Оба будят из Stop через EXTI 17, а в обработчике сбрасывают два флага: будильника и линии. - 32-битный счётчик F1 читают «старшая — младшая — старшая ещё раз», иначе раз в 18 часов время прыгает на 65 536 с.
Дальше: раздел 12а «RTC нового образца: доли секунды, калибровка, пробуждение, TAMP» — у G4: доли секунды, подстройка хода в ppm, таймер пробуждения и резервные регистры в блоке TAMP.