36б. Run-time self-test: core, RAM, flash, clock
Sections 36 and 36а are about how a person hunts for a fault: with a debugger, a multimeter and an oscilloscope. In the field there is no person nearby. A device that controls a heater or a drive must notice by itself that a RAM cell has gone bad, a bit in flash memory has flipped or the crystal has drifted — and go to a safe state before the error reaches an output. For household appliances such checks are required by the IEC 60730 standard for class B software. The text of the standard is not on the disk, so there are no clause numbers and no tolerances from it here — only the checks themselves, each one calculated or run locally.
ST's self-test library (X-CUBE-CLASSB) is not on this machine either — neither in the STM32Cube archives nor in the Keil packs. The code in this section is written from scratch: a March C- test over RAM, a CRC of the flash image and a comparison of the HSE clock with LSE or LSI. What is already covered in other sections is not repeated here: the watchdog — 12.1–12.4, the CRC block and its variant — 12.5 and 43.1, the image CRC in a bootloader — 48, the clock security system (CSS) and HSI trimming against LSE — 6.8 and 6.9, timer capture — 13.6, the safe state — 129.7, the watchdog in a product — 204.6.
References: reference manuals RM0008 (F1) and RM0440 (G4), datasheets STM32F103x8/B and STM32G474 (DS12288), the Cortex-M3 core guide (DUI0552A) — PDFs from the Keil packs. The RAM fault model is run on a PC; the code is built with armclang for F103 and G474 and run in QEMU on a Cortex-M3.
Terms used in the documentation
Term Meaning self-test, start-up / run-time test self-check at start-up / during operation March test, March C- a marching RAM test stuck-at fault (SAF) a bit stuck at 0 or 1 transition fault (TF) a bit that does not change in one direction address decoder fault (AF) an error of the address decoder coupling fault (CF) cells coupled: a write to one corrupts another transparent test a test after which the contents are unchanged reference signature the expected signature (here — the image CRC) clock security system (CSS) the hardware monitor of the HSE clock safe state the state in which the device can do no harm
36б.1 What to check and when
The checks fall into two groups. At start-up everything is checked in full: nothing is running yet, and the RAM contents may be erased. During operation — in pieces, one step per pass of the main loop, so that the application tasks do not stall.
The order at start-up is:
- The watchdog first. If the check itself hangs, the device will restart anyway. A full RAM march on the F103 takes at least 1.9 ms (36б.3), and the watchdog timeout can be set up to 26 seconds (section 12).
- Core registers. Alternating patterns are written to every register R0–R12, the stack pointers and the link register and read back; the condition flags are checked. Such a test is written in assembler: the compiler decides which registers to use, and "all registers" cannot be checked from C. It is not built here — a description without numbers.
- All of RAM with a March C- test (36б.2). The march erases the contents,
so it runs in the start-up code before
.dataand.bssare filled. - CRC of the whole image against the reference written after the build (36б.5).
- Clock: HSE against an independent oscillator (36б.6).
On the G474 part of the RAM checks itself: according to the datasheet the first 32 KB of SRAM
and all of CCM SRAM (32 KB) have hardware parity — one bit per byte, and on
an error a non-maskable interrupt NMI. RM0440 explicitly explains it by
"class B" requirements. It is enabled with the option bit SRAM_PE. Parity
catches a flipped bit on read, but not an address error: if the address hit
the wrong cell, that cell holds data with its own correct parity.
Therefore the march is needed even where parity exists.
36б.2 RAM: which faults the march catches
A march is several passes over all words in a row, up (⇑) or down (⇓) the addresses; in each word a pass performs the same reads and writes. March C-:
⇕(w0); ⇑(r0,w1); ⇑(r1,w0); ⇓(r0,w1); ⇓(r1,w0); ⇕(r0)
w0 — write zeros, r1 — read and expect ones, ⇕ — either
order. Per word this gives 1 + 2 + 2 + 2 + 2 + 1 = 10 accesses, in total
10N for N words. The lighter March X — ⇕(w0); ⇑(r0,w1); ⇓(r1,w0); ⇕(r0),
6N. The "simple" test written most often — write 0x55555555 to
all words, read them, then the same with 0xAAAAAAAA — is 4N.
Which faults each test catches is shown by the model march36c.py: a memory of
32 words of 32 bits, exactly one fault is injected into it, the initial
contents are random, the tests are word-oriented — like the C code. The fault classes
are the generally accepted memory models: stuck-at, transition, address decoder
fault and three kinds of coupling between cells.
| Fault | Instances | March C- | March X | 0x55/0xAA |
|---|---|---|---|---|
| bit stuck at 0 or 1 | 256 | 100 % | 100 % | 100 % |
| bit does not go 0→1 or 1→0 | 256 | 100 % | 100 % | 76 % |
| two addresses select one cell | 992 | 100 % | 100 % | 0 % |
| a transition in one word flips a bit in another | 400 | 100 % | 100 % | 37 % |
| a transition in one word sets a bit in another to 0 or 1 | 800 | 100 % | 57 % | 19 % |
| while a bit in one word is in state s, a bit in another is held | 800 | 100 % | 80 % | 68 % |
| coupling of two bits of the same word | 2000 | 60 % | 60 % | 49 % |
On good memory all three tests stay silent. The "simple" test does not see an address decoder fault at all: it writes the same value to all words, and it does not matter which cell the write landed in — the same value is read back. It makes transitions once each and only in the direction set by the initial contents. March X misses 43 % of the "transition sets a bit" couplings: it has only one transition per traversal direction — up 0→1, down 1→0 — and some combinations of "which transition, on which side of the victim, to which value" it never tries. March C- has both transitions in both directions.
Coupling of two bits of the same word cannot be seen with a solid background (all zeros, all ones):
the bits of a word are always written the same way. A model with six
backgrounds — 00000000, 55555555, 33333333, 0F0F0F0F, 00FF00FF,
0000FFFF — catches it in 100 % of cases, but costs six times more. Here
a solid background is chosen to save time; if coupling inside a word matters, the march
is repeated with backgrounds.
36б.3 March C- in C
uint32_t march_c_minus(volatile uint32_t *p, uint32_t n)
{
uint32_t i, err = 0u;
for (i = 0u; i < n; i++) { p[i] = 0u; } /* ⇕(w0) */
for (i = 0u; i < n; i++) { err |= p[i]; p[i] = ~0u; } /* ⇑(r0,w1) */
for (i = 0u; i < n; i++) { err |= ~p[i]; p[i] = 0u; } /* ⇑(r1,w0) */
for (i = n; i-- > 0u;) { err |= p[i]; p[i] = ~0u; } /* ⇓(r0,w1) */
for (i = n; i-- > 0u;) { err |= ~p[i]; p[i] = 0u; } /* ⇓(r1,w0) */
for (i = 0u; i < n; i++) { err |= p[i]; } /* ⇕(r0) */
return err;
}
volatile forces the compiler to really read and write every
word: without it the compiler may drop a write that it immediately reads back.
Errors are not checked with a branch on every word, but accumulated in err
with OR: the loop is shorter, and the result shows which bits failed. Zero means
the memory is good.
The file selftest36c.c is built with armclang (-Os -Wall -Wextra) for F103 and for
G474 without warnings; _Static_assert checks the offsets of the CRC
and timer registers against RM0008 and RM0440. The ⇑(r0,w1) loop in the F103 listing:
LDR r4,[r2,#0]
SUBS r3,#1
ORR r0,r0,r4
STR lr,[r2],#4
BNE {pc}-0xc ; 0x28
Five instructions per word: read, counter, error accumulation, write with pointer advance, branch. Over the six march loops this gives 3 + 5 + 5 + 5 + 5 + 4 = 27 instructions per word; the G474 listing is the same. Each instruction takes at least one clock cycle, so the full march at start-up:
| Chip | RAM | Words | Instructions | At least |
|---|---|---|---|---|
| F103, 72 MHz | 20 KB | 5120 | 138240 | 1.9 ms |
| G474, 170 MHz | 128 KB | 32768 | 884736 | 5.2 ms |
This is a lower bound: a branch costs more than one cycle, and flash wait states add more (73.2). The exact time comes from the cycle counter on the board (73.1) — not measured here. In QEMU March C- over 256 words of good RAM returned zero.
36б.4 During operation: a transparent test in chunks
During operation RAM is in use and must not be erased. A transparent test takes a chunk, saves it, runs the march over it and puts the contents back:
uint32_t ram_step(volatile uint32_t *base, uint32_t words)
{
uint32_t n = CHUNK_WORDS, i, err, primask;
volatile uint32_t *p = base + ram_pos;
if (ram_pos + n > words) { n = words - ram_pos; }
primask = __get_PRIMASK();
__disable_irq(); /* никто не должен увидеть кусок, пока в нём нули и единицы */
for (i = 0u; i < n; i++) { saved[i] = p[i]; }
err = march_c_minus(p, n);
for (i = 0u; i < n; i++) { p[i] = saved[i]; }
__set_PRIMASK(primask);
ram_pos = (ram_pos + n >= words) ? 0u : ram_pos + n - 1u;
return err;
}
Three rules. Interrupts are disabled for the whole chunk: a handler
that reads a variable from the chunk in the middle of the march will get zero or
0xFFFFFFFF. PRIMASK is restored, not enabled: if
the function was called with interrupts already disabled, they stay disabled — in QEMU,
after a call with interrupts disabled, PRIMASK stayed equal to 1. The saved[] buffer
and the stack are not part of the tested area — the march would erase them together
with the return address.
A chunk is CHUNK_WORDS = 16 words. According to the listing, each word of the chunk takes
27 march instructions and 10 for saving and restoring: 592 instructions with interrupts
disabled, at least 8.2 µs at 72 MHz. For all that time the response to an interrupt
may be delayed — that is the price of the transparent test, and the chunk size is
chosen by the permissible latency.
Chunks overlap by one word. Coupling between neighbouring words at the junction of two
chunks is not visible to either of them: the aggressor is in one, the victim in the other.
The model runs the same order as ram_step() over an area of 64 words:
| Partitioning | Chunk starts | Neighbouring-word coupling (756) | Any-two-words coupling (300) |
|---|---|---|---|
| no overlap | 0 16 32 48 | 95 % | 24 % |
| 1-word overlap | 0 15 30 45 60 | 100 % | 27 % |
| the whole march at once | — | — | 100 % |
The contents after a full round in the model are unchanged. The same chunk order —
0, 15, 30, 45, 60 — was produced by the ram_step() code in QEMU, and all 64 words after the round
matched the copy. The last column is the limit of a transparent test: coupling
between distant words rarely falls into one chunk. Only the
full march at start-up catches it, so it is not replaced by a run-time check.
A full round over the 20 KB of the F103 is 342 calls of ram_step().
36б.5 Flash: image CRC with the reference at the end
RM0008 describes the purpose of the CRC block exactly like this: a program signature calculated at run time is compared with a reference calculated at build time and stored at a known location. The location is right after the image. Its address is given by the linker script:
__image_end = LOADADDR(.data) + SIZEOF(.data); /* здесь будет слово CRC */
After the build the script crc_image36c.py reads the .bin, calculates CRC-32/MPEG-2
the same way as the CRC block (words least significant byte first, each word fed into the calculation
most significant bit first, initial value 0xFFFFFFFF, no reflection; 12.5, 43.1), and appends
the result as a word at the end. It also checks the file length against __image_end:
if it does not match, the wrong file or the wrong boundaries were built.
At start-up flash_check_all() runs the whole image through the block and compares
the result with image[words]. During operation — 256 words per call:
int flash_step(const uint32_t *image, uint32_t words)
{
static uint32_t pos;
uint32_t i = pos, end = pos + FLASH_STEP_WORDS;
if (i == 0u) { crc_reset(); }
if (end > words) { end = words; }
for (; i < end; i++) { crc_feed(image[i]); }
if (i < words) { pos = i; return 0; }
pos = 0u;
return (crc_read() == image[words]) ? 1 : -1;
}
The feed loop is 4 instructions per word; the block itself spends 4 AHB bus cycles per word (RM0008, RM0440). The 64 KB flash of the F103 is 64 steps, the 512 KB of the G474 is 512 steps.
QEMU has no CRC block, so the run substitutes a software calculation of the same
variant. An image of 379 words passed the check at start-up and step by step (two
steps). The CRC over the image together with the reference word gave 0x00000000 —
a property of CRC without final inversion: the remainder of "data plus their own
CRC" is zero. Then one bit in the image constants was flipped —
and both the start-up check and the step-by-step check reported a failure.
The G474 flash has ECC: according to RM0440 it detects and corrects a single error in a double word by itself, and detects two. This does not make the image CRC unnecessary: a partially written or foreign image is written with correct ECC codes and does not look like an error to ECC.
36б.6 Clock and the reaction to a failure
CSS catches only a stopped HSE: according to RM0440, on HSE failure the system switches to HSI16, the PLL fed from HSE is turned off, and an NMI arrives. A crystal of the wrong frequency or an error in the PLL setup is a healthy clock for CSS. Therefore the frequency is compared with an independent oscillator: a timer counts bus clock cycles (from HSE through the PLL) between edges of LSE or LSI.
On the G474 the 32-bit TIM5 is convenient for this (datasheet): according to table 270 of RM0440 its
TI1 input is connected inside the chip to LSI (TISEL = 1) and to LSE (TISEL = 2),
no wire is needed. Capture happens on every 8th edge:
uint32_t ticks_per_8(uint32_t tisel)
{
uint32_t c1 = 0u, cap = 0u, k, t;
RCC->APB1ENR1 |= RCC_APB1ENR1_TIM5EN;
TIM5->TISEL = tisel;
TIM5->PSC = 0u;
TIM5->ARR = 0xFFFFFFFFu; /* счётчик 32-битный: разность не переполнится */
TIM5->CCMR1 = TIM_CCMR1_CC1S_0 | TIM_CCMR1_IC1PSC; /* вход TI1, захват на каждом 8-м фронте */
TIM5->CCER = TIM_CCER_CC1E;
TIM5->CR1 = TIM_CR1_CEN;
for (k = 0u; k < 3u; k++) { /* первый захват отбрасываем, берём разность двух следующих */
for (t = 0u; (TIM5->SR & TIM_SR_CC1IF) == 0u; t++) {
if (t > 2000000u) { return 0u; } /* опора молчит: это тоже отказ */
}
cap = TIM5->CCR1; /* чтение CCR1 снимает CC1IF */
if (k == 1u) { c1 = cap; }
}
return cap - c1;
}
The count over 8 periods = 8 × f timer / f reference. The window is all the counts
a healthy HSE gives at any permissible reference value, plus
one for quantization. LSI on the G474 is 29.5…34 kHz over the whole
temperature range according to the datasheet, on the F103 30…60 kHz. LSE is 32768 Hz; RM0440 gives
the accuracy of a watch crystal as "a few tens of ppm", and the ±1 % window around
nominal is this section's choice, with a large margin. Run of calc36c.py:
| Failure | G474, LSE ±1 % | G474, LSI | F103, LSI |
|---|---|---|---|
| healthy: count window | 41091…41925 | 39999…46103 | 9599…19201 |
| HSE lost, running from HSI | 3906 — caught | 3765…4339 — caught | 1067…2133 — caught |
| frequency ×0.5 | caught | caught | may be missed |
| frequency ×0.9 | caught | may be missed | may be missed |
| frequency ×0.995 | not caught | may be missed | may be missed |
| frequency ×1.02 | caught | may be missed | may be missed |
| frequency ×1.5 | caught | caught | may be missed |
| frequency ×4 | caught | caught | may be missed |
"May be missed" means that for some permissible LSI the count of the faulty clock falls inside the window. The conclusion is simple: LSI catches only gross failures. The LSI window on the G474 is ±7.1 %, on the F103 ±33.3 %: for the F103 that is almost only the loss of HSE, which CSS sees anyway. An accurate frequency check uses LSE.
On the F103 there is no internal path from LSI to a timer in medium density (F103C8,
F103CB): according to RM0008 the TIM5 CH4 input is connected to LSI by the TIM5CH4_IREMAP bit
only in high-density, XL and connectivity line chips. On the F103C8 the reference
is brought in by wire — the LSE clock divided by 64, through the TAMPER pin (6.8).
The F1 timers are 16-bit (13.1), and the "×4" row is not a typo: see
36б.7.
What to do on a failure. Any check that did not match leads to one place: switch off the power outputs, record the reason where it survives a reset (a backup register, a non-initialized RAM area), and stop feeding the watchdog (129.7). A watchdog clocked from LSI (12.2) restarts the chip even if HSE itself is broken. After the reset the device reads the reset cause flags (12.4) and its own record: if the failure repeats, it stays in the safe state instead of restarting in a loop. The watchdog is fed only at the end of a main-loop pass in which all self-test steps returned "healthy".
36б.7 Pitfalls
The march erased the stack it was running on. The full March C- at start-up
runs before .data and .bss are filled and does not touch its own stack; during operation
the stack and the saved[] buffer are excluded from the tested area. Otherwise the march erases
the return address — and you get a HardFault instead of a report.
A chunk was tested with interrupts enabled. A handler read
a counter in the middle of the march and saw 0xFFFFFFFF. Interrupts are disabled for the whole
chunk, from saving to restoring.
NMI arrives even with interrupts disabled. PRIMASK blocks
only exceptions with configurable priority (DUI0552A); NMI and HardFault
get through. The NMI handler — and that is where CSS and the G474 parity error arrive —
must not touch the tested area.
Someone else uses the CRC block. Between flash_step() steps the block
holds an intermediate result. If a communication protocol calculates its own checksum on it
(and on the G474 also reconfigures the polynomial, 43.1), the pass ends with a false
failure. The block is given to the self-test entirely, and the protocol gets a software CRC.
The reference was calculated over the wrong boundaries. The script got a .hex instead of
a .bin, or the file was taken from a different build. That is why crc_image36c.py
checks the length against __image_end and refuses if the length is not a multiple of
four.
The clock is "OK" by LSI, but the crystal is wrong. The LSI window on the F103 is ±33 %: it lets through both ×0.5 and ×1.5. LSI is used only to catch loss of HSE; accuracy is checked against LSE.
The 16-bit counter wrapped around. On the F103 all timers are 16-bit: for a healthy clock that is enough (no more than 19201), but at a frequency above ×3.41 the difference is computed modulo 65536. At ×4 the count 38400…76800 becomes 38400…65535 and 0…11264 — and the lower piece falls into the window. The G474 has a 32-bit TIM5.
Parity was enabled, but RAM was not cleared. RM0440 advises writing all of RAM at the start of the program: reading a cell that has not been written yet may cause a parity error and an NMI.
36б.8 Summary
- At start-up — everything in full: the watchdog first, core registers (assembler), RAM with a march, CRC of the whole image, clock. During operation — in pieces in every pass of the main loop; the watchdog is fed only if everything matched.
- March C- is 10 accesses per word; in the model it caught 100 % of stuck-at,
transition, address and inter-word coupling faults. The
0x55/0xAAtest (4N) does not see address faults at all (0 %), March X (6N) misses 43 % of "transition sets a bit" couplings. - Coupling of bits inside a word is caught by a solid background in 60 % of cases; six backgrounds give 100 % at six times the time.
- From the armclang listing: 27 instructions per march word; the full march takes at least 1.9 ms on the 20 KB of the F103 and 5.2 ms on the 128 KB of the G474.
- A transparent chunk of 16 words is 592 instructions with interrupts disabled, at least 8.2 µs at 72 MHz. A one-word overlap raises coverage of neighbouring-word coupling from 95 to 100 %; coupling between distant words is caught only by the full march at start-up.
- Image CRC: the reference is appended by a script after the build at
__image_end; in QEMU an image of 379 words passed, the CRC together with the reference is zero, one flipped bit is a failure. The run-time step is 256 words, 4 instructions per word. - Clock: TIM5 of the G474 counts clock cycles between 8 edges of LSE or LSI. The LSE window of ±1 % catches a 2 % drift; the LSI window (±7.1 % on the G474, ±33.3 % on the F103) — only gross failures.
- On a failure — switch outputs off, record the reason, do not feed the watchdog; a repeated failure after reset — stay in the safe state.
Full sources
selftest36c.c — run-time self-test: RAM with a March C- test, flash memory CRC, clock.
/* Раздел 36б: самопроверка во время работы — ОЗУ маршем March C-, флеш — CRC образа, частота — захватом таймера.
* Собирается armclang для STM32F103 (-DSTM32F10X_MD, заголовок stm32f10x.h) и STM32G474 (-DSTM32G474xx, stm32g4xx.h)
* без предупреждений; для прогона в QEMU (-DSELFTEST_SOFT_CRC) блок CRC заменяется программным расчётом того же варианта. */
#include <stdint.h>
#include <stddef.h>
/* ── ОЗУ ─────────────────────────────────────────────────────────────── */
#define CHUNK_WORDS 16u /* кусок прозрачного теста: слов за один вызов */
_Static_assert(CHUNK_WORDS >= 2u, "перекрытие в одно слово требует куска хотя бы из двух слов");
/* March C-: {⇕(w0); ⇑(r0,w1); ⇑(r1,w0); ⇓(r0,w1); ⇓(r1,w0); ⇕(r0)} — 10 обращений на слово.
* Возвращает 0, если память исправна; иначе — биты, которые прочитались не так. */
uint32_t march_c_minus(volatile uint32_t *p, uint32_t n)
{
uint32_t i, err = 0u;
for (i = 0u; i < n; i++) { p[i] = 0u; } /* ⇕(w0) */
for (i = 0u; i < n; i++) { err |= p[i]; p[i] = ~0u; } /* ⇑(r0,w1) */
for (i = 0u; i < n; i++) { err |= ~p[i]; p[i] = 0u; } /* ⇑(r1,w0) */
for (i = n; i-- > 0u;) { err |= p[i]; p[i] = ~0u; } /* ⇓(r0,w1) */
for (i = n; i-- > 0u;) { err |= ~p[i]; p[i] = 0u; } /* ⇓(r1,w0) */
for (i = 0u; i < n; i++) { err |= p[i]; } /* ⇕(r0) */
return err;
}
static uint32_t saved[CHUNK_WORDS]; /* копия куска; сам буфер в проверяемую область не входит */
static uint32_t ram_pos; /* начало следующего куска */
/* Один шаг прозрачного теста: сохранить кусок, прогнать March C-, вернуть содержимое.
* Соседние куски перекрываются на одно слово — так ловится связь соседних слов на стыке. */
uint32_t ram_step(volatile uint32_t *base, uint32_t words)
{
uint32_t n = CHUNK_WORDS, i, err, primask;
volatile uint32_t *p = base + ram_pos;
if (ram_pos + n > words) { n = words - ram_pos; }
primask = __get_PRIMASK();
__disable_irq(); /* никто не должен увидеть кусок, пока в нём нули и единицы */
for (i = 0u; i < n; i++) { saved[i] = p[i]; }
err = march_c_minus(p, n);
for (i = 0u; i < n; i++) { p[i] = saved[i]; }
__set_PRIMASK(primask);
ram_pos = (ram_pos + n >= words) ? 0u : ram_pos + n - 1u;
return err;
}
uint32_t ram_next_chunk(void) { return ram_pos; }
/* ── Флеш: CRC-32/MPEG-2 (полином 0x04C11DB7, начало 0xFFFFFFFF, без отражений) ── */
#ifdef SELFTEST_SOFT_CRC
void crc_reset(void); /* прогон в QEMU: блока CRC там нет, расчёт программный */
void crc_feed(uint32_t w);
uint32_t crc_read(void);
#else
_Static_assert(offsetof(CRC_TypeDef, DR) == 0x00u && offsetof(CRC_TypeDef, CR) == 0x08u,
"CRC_DR и CRC_CR по RM0008 и RM0440 — смещения 0x00 и 0x08");
static void crc_reset(void)
{
#if defined(STM32G474xx)
RCC->AHB1ENR |= RCC_AHB1ENR_CRCEN;
#else
RCC->AHBENR |= RCC_AHBENR_CRCEN;
#endif
CRC->CR = CRC_CR_RESET; /* к 0xFFFFFFFF; у G4 после сброса тот же вариант, что у F1 */
}
static void crc_feed(uint32_t w) { CRC->DR = w; }
static uint32_t crc_read(void) { return CRC->DR; }
#endif
#define FLASH_STEP_WORDS 256u /* слов CRC за один шаг в работе */
/* При старте: CRC всего образа words слов сравнивается с эталоном, лежащим сразу за образом. */
int flash_check_all(const uint32_t *image, uint32_t words)
{
uint32_t i;
crc_reset();
for (i = 0u; i < words; i++) { crc_feed(image[i]); }
return crc_read() == image[words];
}
/* В работе — по FLASH_STEP_WORDS слов за вызов. 0 — идёт, 1 — проход сошёлся, −1 — отказ.
* Блок CRC между шагами никто другой трогать не должен: его состояние — наш промежуточный итог. */
int flash_step(const uint32_t *image, uint32_t words)
{
static uint32_t pos;
uint32_t i = pos, end = pos + FLASH_STEP_WORDS;
if (i == 0u) { crc_reset(); }
if (end > words) { end = words; }
for (; i < end; i++) { crc_feed(image[i]); }
if (i < words) { pos = i; return 0; }
pos = 0u;
return (crc_read() == image[words]) ? 1 : -1;
}
/* ── Частота (G474): 32-битный TIM5 считает такты шины между фронтами LSE или LSI ── */
#if defined(STM32G474xx)
_Static_assert(offsetof(TIM_TypeDef, TISEL) == 0x5Cu, "TIMx_TISEL по RM0440 — смещение 0x5C");
#define TISEL_LSI 1u /* таблица 270 RM0440: у TIM5 tim_ti1_in1 — LSI */
#define TISEL_LSE 2u /* tim_ti1_in2 — LSE */
/* Сколько тактов таймера уложилось в 8 периодов опорной частоты; 0 — опора не пришла. */
uint32_t ticks_per_8(uint32_t tisel)
{
uint32_t c1 = 0u, cap = 0u, k, t;
RCC->APB1ENR1 |= RCC_APB1ENR1_TIM5EN;
TIM5->TISEL = tisel;
TIM5->PSC = 0u;
TIM5->ARR = 0xFFFFFFFFu; /* счётчик 32-битный: разность не переполнится */
TIM5->CCMR1 = TIM_CCMR1_CC1S_0 | TIM_CCMR1_IC1PSC; /* вход TI1, захват на каждом 8-м фронте */
TIM5->CCER = TIM_CCER_CC1E;
TIM5->CR1 = TIM_CR1_CEN;
for (k = 0u; k < 3u; k++) { /* первый захват отбрасываем, берём разность двух следующих */
for (t = 0u; (TIM5->SR & TIM_SR_CC1IF) == 0u; t++) {
if (t > 2000000u) { return 0u; } /* опора молчит: это тоже отказ */
}
cap = TIM5->CCR1; /* чтение CCR1 снимает CC1IF */
if (k == 1u) { c1 = cap; }
}
return cap - c1;
}
int clock_ok(uint32_t ticks, uint32_t lo, uint32_t hi) { return ticks >= lo && ticks <= hi; }
#endif
Next: section 36в "Noise-robust firmware: traps in free Flash, empty vectors, program flow monitoring" — traps in free flash memory, a common vector handler and monitoring of the main loop flow.