1. Тема дня
Сегодня разбираем четыре разных понятия времени:
monotonic time:
сколько времени прошло внутри текущего запуска
UTC / wall clock:
календарное время: дата, часы, минуты, секунды
hardware capture time:
точный момент внешнего фронта относительно таймера
local civil time:
UTC с часовым поясом и правилами перехода времениА также:
SNTP/NTP synchronization
instant step и smooth adjustment
PPS
clock offset
clock drift
holdover
timestamp quality
wraparound
синхронизация ESP32, STM32, камеры и HIL-стендаГлавная мысль: Для timeout, debounce, watchdog и измерения периода используется только monotonic time. UTC нужен для журналов и сопоставления событий между устройствами, но он может быть скорректирован вперёд или назад.
2. Зачем это нужно в твоих проектах
Для детектирования фаз светофора событие изменения фазы должно содержать не только календарное время, но и локальный монотонный timestamp:
phase=GREEN
mono_us=128493820
utc=2026-07-18T06:42:31.438217Z
time_quality=SYNCED
sequence=817Если NTP скорректировал календарные часы назад, sequence и mono_us всё равно сохранят правильный порядок событий. Для EC25, MQTT и TCP нужны два разных вида времени:
monotonic:
timeout AT-команды;
reconnect backoff;
ожидание регистрации;
watchdog modem_task;
RTT запроса.
UTC:
timestamp телеметрии;
время SMS;
TLS certificate validation;
журнал ошибок;
сопоставление событий на сервере.Для камеры и HDR-подсветки нужны:
frame_sync_capture_ticks
frame_sequence
mono_us
exposure_mode
IR pulse duration
optional UTCДля HIL на Raspberry Pi важно различать:
DUT local monotonic time
Raspberry Pi monotonic time
UTC Raspberry Pi
UTC DUT3. Теория
2.3.1 3.1. Monotonic time и wall clock решают разные задачи
Monotonic time должен:
- никогда не идти назад;
- увеличиваться с постоянной шкалой;
- не зависеть от NTP;
- не зависеть от timezone;
- использоваться для всех интервалов.Примеры:
uint64_t started_us;
uint64_t timeout_us;
uint64_t last_progress_us;
uint64_t debounce_started_us;
if ((now_us - started_us) >= timeout_us) {
timeout();
}UTC отвечает на вопрос: “Какое сейчас календарное время?”. Он может измениться после NTP, восстановления RTC или ручной коррекции. Поэтому UTC нельзя использовать для timeout, debounce, watchdog или reconnect-backoff. Правило:
duration / timeout / debounce / watchdog:
monotonic
calendar / telemetry / logs:
UTC2.3.2 3.2. Timestamp должен создаваться у источника события
Плохо:
GPIO edge
→ queue
→ phase task
→ MQTT task
→ timestamp добавлен при publishПравильно:
GPIO edge:
hardware capture timestamp
phase confirmed:
monotonic timestamp подтверждения
MQTT publish:
передаёт уже существующий timestampДля события можно хранить два времени:
typedef struct {
uint64_t observed_mono_us;
uint64_t confirmed_mono_us;
} phase_timing_t;2.3.3 3.3. Структура timestamp
typedef enum {
TIME_QUALITY_UNSYNCED = 0,
TIME_QUALITY_ESTIMATED,
TIME_QUALITY_SYNCED,
TIME_QUALITY_HOLDOVER,
TIME_QUALITY_INVALID,
} time_quality_t;
typedef struct {
uint64_t mono_us;
int64_t utc_us;
bool utc_valid;
time_quality_t quality;
uint32_t sync_generation;
uint32_t boot_id;
} event_timestamp_t;Смысл:
mono_us:
основной timestamp и порядок событий
utc_us:
привязка к UTC, если она известна
quality:
насколько UTC заслуживает доверия
sync_generation:
какая синхронизация использована
boot_id:
отличает одинаковые mono_us после reboot2.3.4 3.4. Качество времени
UNSYNCED:
устройство только загрузилось, NTP/RTC ещё нет.
ESTIMATED:
время восстановлено из RTC или сохранённого значения.
SYNCED:
получен свежий NTP/SNTP/GNSS-источник.
HOLDOVER:
источник потерян, но устройство продолжает считать время локальным генератором.
INVALID:
время явно неверно или противоречит другим источникам.2.3.5 3.5. Связь monotonic и UTC через anchor
typedef struct {
uint64_t anchor_mono_us;
int64_t anchor_utc_us;
int32_t rate_ppb;
uint32_t generation;
time_quality_t quality;
bool valid;
} time_anchor_t;Базовая модель:
utc_us = anchor_utc_us + (mono_us - anchor_mono_us)С учётом drift:
delta_us = mono_us - anchor_mono_us
correction_us = delta_us × rate_ppb / 1 000 000 000
utc_us = anchor_utc_us + delta_us + correction_usrate_ppb:
+20 000 ppb = +20 ppm
-10 000 ppb = -10 ppm2.3.6 3.6. Offset и drift
Offset — ошибка положения часов прямо сейчас. Drift — ошибка скорости часов. Пример 20 ppm:
20 микросекунд ошибки за секунду
1.2 миллисекунды за минуту
72 миллисекунды за час
1.728 секунды за сутки2.3.7 3.7. Step и smooth correction
Step мгновенно переставляет UTC. Smooth/slew постепенно ускоряет или замедляет системные часы. Для production удобно:
первый sync после загрузки:
step, если UTC ещё не использовался
небольшая последующая ошибка:
smooth
очень большая ошибка:
fault event + controlled stepTimeout всё равно остаётся на monotonic clock.
2.3.8 3.8. PPS
PPS даёт точную границу секунды, но не говорит номер секунды. Для absolute UTC нужен GNSS/NMEA/UBX, RTC, NTP или другой источник календарной секунды.
PPS pin
→ timer Input Capture
→ capture_ticks
→ PPS event
→ time discipline task4. Типичные ошибки
1. Использовать UTC для timeout.
2. Добавлять timestamp при MQTT publish.
3. Считать любое calendar time синхронизированным.
4. Не хранить boot_id.
5. Считать PPS полноценным UTC.
6. Резко исправлять monotonic clock.
7. Игнорировать drift в holdover.
8. Смешивать ms, us, ticks и RTOS ticks.
9. Не учитывать wraparound.
10. Применять timezone внутри протокола.
11. Считать NTP доказательством микросекундной точности.5. Практическое задание на 30-60 минут
Создай TIME_POLICY.md:
# Time architecture policy
1. All timeouts use monotonic time.
2. UTC is never used for duration measurement.
3. Hardware events are timestamped at their source.
4. Every event contains boot_id and sequence.
5. UTC timestamps include time quality.
6. SNTP corrections never change monotonic time.
7. After loss of synchronization, state becomes HOLDOVER.
8. Local timezone is applied only for display.
9. Units are encoded in variable and field names.
10. Wraparound behavior is covered by tests.Добавь API:
typedef enum {
TIME_QUALITY_UNSYNCED = 0,
TIME_QUALITY_ESTIMATED,
TIME_QUALITY_SYNCED,
TIME_QUALITY_HOLDOVER,
TIME_QUALITY_INVALID,
} time_quality_t;
typedef struct {
uint64_t mono_us;
int64_t utc_us;
uint32_t boot_id;
uint32_t sync_generation;
time_quality_t quality;
bool utc_valid;
} event_timestamp_t;
void time_service_init(uint32_t boot_id);
uint64_t time_service_mono_us(void);
void time_service_apply_sync(uint64_t mono_us,
int64_t utc_us,
time_quality_t quality);
event_timestamp_t time_service_timestamp(void);
event_timestamp_t time_service_from_mono(uint64_t mono_us);
void time_service_mark_holdover(void);Добавь в phase_event_t:
typedef struct {
uint32_t sequence;
phase_t phase;
uint8_t mask;
event_timestamp_t observed;
event_timestamp_t confirmed;
} phase_event_t;CLI-команда:
time statusПример вывода:
time:
quality=SYNCED
boot_id=42
sync_generation=7
mono_us=1834291203
utc=2026-07-18T06:42:31.438217Z
last_sync_age=1842s
offset_last=-1240us
drift_estimate=+18.4ppm
uncertainty=4200us
source=SNTP
smooth_sync=yes6. Что почитать или попробовать дальше
- ESP-IDF System Time, ESP Timer и ESP-NETIF SNTP Service.
- STM32 AN4759 по RTC, subsecond, synchronization и smooth calibration.
- STM32 AN4013 по timer synchronization, master/slave timers и TRGO.
Короткий итог
hardware event
→ hardware capture / monotonic timestamp
→ event sequence + boot_id
→ time_service
→ optional UTC mapping + quality
→ queue / MQTT / CAN / logЗадание
NTP перевёл UTC назад во время AT command timeout. Назовите clock для timeout и поля event, сохраняющие порядок при correction и reboot.
Критерии самопроверки: Объяснить, почему UTC correction не продлевает и не обращает timeout; отделить порядок внутри boot от calendar correlation между устройствами.
Показать ответ автора
Использовать monotonic time для timeout. Сохранить monotonic timestamp источника и sequence, добавить boot_id для различения запусков и прикреплять UTC mapping с явными quality/sync_generation вместо замены ordering clock.
Задание
Устройство потеряло synchronization source после исправной sync. Определите time quality и влияние drift на uncertainty в holdover.
Критерии самопроверки: Не приравнивать formatted calendar value к проверенной accuracy. Объяснить изменение evidence при возврате synchronization.
Показать ответ автора
Сообщать HOLDOVER, а не свежий SYNCED source. Продолжать локальный monotonic timing и оценивать UTC от последнего anchor; uncertainty должна учитывать elapsed time и oscillator drift, а last synchronization age сохраняться.