1. Тема дня
Разбираем:
timer clock
prescaler и auto-reload
Input Capture
Output Compare
PWM
One-Pulse mode
переполнение счётчика
аппаратный запуск ADC
синхронизацию таймеров
измерение периода и длительности импульсаГлавная мысль: если момент фронта или длительность импульса важны, событие должно фиксироваться или формироваться аппаратным таймером. Задача FreeRTOS должна обрабатывать уже готовый timestamp.
2. Зачем это нужно
Фазы светофора
Input Capture аппаратно фиксирует момент фронта:
фронт на входе
→ CNT копируется в CCR
→ ISR получает сохранённый timestamp
→ input_task анализирует интервалыДаже если ISR пришла позже, capture value соответствует моменту фронта.
HDR-подсветка и sync
Плохой вариант:
gpio_set_level(IR_GPIO, 1);
vTaskDelay(pdMS_TO_TICKS(duration_ms));
gpio_set_level(IR_GPIO, 0);Правильный:
camera sync edge
→ Input Capture / timer trigger
→ One-Pulse или Output Compare
→ аппаратный импульс подсветкиADC + DMA
TIM update/TRGO
→ ADC conversion
→ DMA bufferТак sample period равномерный и не зависит от scheduler.
3. Теория
Основные регистры
timer clock
↓
Prescaler PSC
↓
Counter CNT
├── сравнение с ARR → update/overflow
└── сравнение с CCRx → Output Compare/PWMФормулы:
f_counter = f_timer / (PSC + 1)
T_tick = 1 / f_counter
f_update = f_timer / ((PSC + 1) × (ARR + 1))Тик 1 мкс
Если f_counter = 1 MHz, то 1 tick = 1 us. Это удобно для измерения периодов 50/100 Гц и импульсов подсветки. Но для 16-bit таймера:
65536 us ≈ 65.5 msДальше нужен overflow handling или 32-bit timer.
Input Capture
При фронте:
CCRx = CNTДля периода между rising edges:
uint32_t delta_ticks = current_capture - previous_capture;Unsigned arithmetic корректно работает при одном wraparound для 32-bit counter.
Duty cycle
rising1 → falling → rising2
period = rising2 - rising1
high_time = falling - rising1
duty = high_time / periodБез float:
uint32_t duty_permille = ((uint64_t)high_ticks * 1000ULL) / period_ticks;Input filter
Цифровой фильтр таймера помогает против glitches, но добавляет задержку и ограничивает максимальную частоту. Он дополняет, но не заменяет схемотехнику.
Output Compare
Output Compare делает аппаратное событие при:
CNT == CCRxМожно установить/сбросить/инвертировать выход, вызвать interrupt или DMA request. Для периодических событий следующий compare лучше считать от предыдущего compare, а не от текущего CNT, чтобы не накапливать drift.
PWM
frequency = f_counter / (ARR + 1)
duty ≈ CCR / (ARR + 1)PWM формируется аппаратно без scheduler jitter.
One-Pulse mode
trigger
→ programmable delay
→ output active
→ programmable duration
→ output inactive
→ timer stopsПодходит для ИК-вспышки, reset pulse, camera trigger, strobe.
Preload
Preload применяет новые CCR/ARR только на update event. Это защищает от одиночного неправильного PWM-импульса при смене duty/duration в середине периода.
4. Типичные ошибки
- Измерять фронт через timestamp внутри ISR вместо Capture Register.
- Не проверить timer clock.
- Забыть PSC + 1 и ARR + 1.
- Не учитывать overflow.
- Использовать software PWM через vTaskDelay().
- Переключать capture polarity слишком медленно.
- Логировать или считать тяжёлую математику в capture callback.
- Считать digital filter заменой схемотехнике.
- Менять CCR в середине PWM без preload.
- Планировать compare от текущего CNT и накапливать drift.
5. Практическое задание
Сгенерируй PWM и измерь его через Input Capture.
- Настрой PWM:
frequency = 1 kHz
duty = 30 %- Соедини PWM output → capture input.
- Настрой capture timer:
resolution ≈ 1 MHz
capture rising and falling edges- Ожидаемые значения:
period ≈ 1000 us
high time ≈ 300 us
duty ≈ 30 %
frequency ≈ 1000 Hz- ISR передаёт только:
typedef struct {
uint32_t capture;
uint8_t edge;
} timer_capture_event_t;Task считает period/duty/frequency.
- Добавь CLI capture:
capture:
resolution=1000000 Hz
edges=120340
measurements=60170
period=999..1001 us
frequency=1000000 mHz
duty=300 permille
invalid=0
timeouts=06. Что попробовать дальше
- Добавить timeout отсутствия сигнала.
- Проверить 100 Hz, 1 kHz, 10 kHz, разные duty.
- Добавить glitch и проверить counter.
- Сделать HIL: Raspberry Pi/второй MCU генерирует сигнал, ESP32/STM32 измеряет.
- Вынести расчёт в pulse_meter_core и покрыть host unit-тестами.
Финальный чеклист по урокам 21-30
Надёжность
□ Все fault имеют source, class, action и counter. □ Есть degraded mode, видимый через CLI/MQTT/CAN. □ Watchdog не является первым recovery-механизмом. □ Перед reset сохраняется fault snapshot.
Конфигурация и OTA
□ Config имеет magic/version/size/CRC/generation. □ Есть defaults и validation. □ Есть migration старых версий. □ Есть A/B config. □ OTA подтверждается только после self-test. □ Недоступность LTE/MQTT не вызывает автоматический rollback исправной прошивки.
CI/HIL
□ Сборка идёт в pinned Docker/toolchain. □ Сохраняются ELF/MAP/sdkconfig/manifest/SHA256. □ Контролируется размер OTA slot. □ Подпись firmware отделена от PR-job. □ HIL тестирует не только happy path, но и power-cut, rollback, watchdog, fault paths.
Тестируемость
□ Core-модули не зависят от ESP-IDF/STM32 HAL. □ Время передаётся аргументом. □ Бесконечные task используют тестируемую функцию step(). □ AT-парсер тестируется на fragmentation invariance. □ Crash fuzzing превращается в regression test.
RTOS и interrupts
□ Периферия имеет одного owner task. □ ISR не ждёт mutex. □ ISR вызывает только ...FromISR() API. □ NVIC priorities согласованы с configMAX_SYSCALL_INTERRUPT_PRIORITY. □ ESP32 interrupts имеют осознанную core affinity. □ Длинная обработка переносится из ISR в task.
DMA, буферы, таймеры
□ У continuous stream есть owner, overrun detection и CLI stats. □ DMA buffers имеют явный ownership. □ STM32H7/F7 cache clean/invalidate учтены. □ Timer clock проверен по clock tree. □ Точные фронты измеряются через Input Capture. □ PWM/One-Pulse формируются аппаратно, не через vTaskDelay().
Задание
Объясните отличие ISR-entry timestamp от значения Input Capture register. Опишите тест различия под interrupt load.
Критерии самопроверки: Отделить edge time от handling time и использовать известный signal/reference; не заявлять точность только по scheduler timing.
Показать ответ автора
Capture register фиксирует hardware edge time, а ISR timestamp включает interrupt latency. Подать известный signal и дополнительную interrupt load, сравнить capture intervals с ISR-entry timing и проверить variation без тяжёлого logging внутри ISR.
Задание
Для исходного упражнения 1 kHz, 30 % PWM укажите ожидаемые period и high time, затем объясните защиту preload от output glitch при изменении duty.
Критерии самопроверки: Сохранить исходные units и values; проверить результаты измерения и update boundary, а не только наличие переключений output.
Показать ответ автора
Ожидаемый period примерно 1000 us и high time примерно 300 us. Preload откладывает новые CCR/ARR до update event и не позволяет изменению в части периода сформировать один неправильный pulse.