1. Today’s topic

We examine:

text
timer clock
prescaler and auto-reload
Input Capture
Output Compare
PWM
One-Pulse mode
counter overflow
hardware ADC triggering
timer synchronization
period and pulse-duration measurement

The central idea: when edge timing or pulse duration matters, a hardware timer should capture or generate the event. A FreeRTOS task should process an already recorded timestamp.

2. Why this matters

Traffic-light phases

Input Capture records the edge time in hardware:

text
input edge
→ CNT copied into CCR
→ ISR receives the saved timestamp
→ input_task analyzes intervals

Even if the ISR runs later, the capture value corresponds to the edge time.

HDR illumination and sync

Poor approach:

c
gpio_set_level(IR_GPIO, 1);
vTaskDelay(pdMS_TO_TICKS(duration_ms));
gpio_set_level(IR_GPIO, 0);

Correct approach:

text
camera sync edge
→ Input Capture / timer trigger
→ One-Pulse or Output Compare
→ hardware illumination pulse

ADC + DMA

text
TIM update/TRGO
→ ADC conversion
→ DMA buffer

This keeps the sample period uniform and independent of the scheduler.

3. Theory

Main registers

text
timer clock
    ↓
Prescaler PSC
    ↓
Counter CNT
    ├── comparison with ARR → update/overflow
    └── comparison with CCRx → Output Compare/PWM

Formulae:

text
f_counter = f_timer / (PSC + 1)
T_tick = 1 / f_counter
f_update = f_timer / ((PSC + 1) × (ARR + 1))

A 1 microsecond tick

If f_counter = 1 MHz, then 1 tick = 1 us. This is convenient for measuring 50/100 Hz periods and illumination pulses. But for a 16-bit timer:

text
65536 us ≈ 65.5 ms

Beyond that, overflow handling or a 32-bit timer is required.

Input Capture

At an edge:

text
CCRx = CNT

For the period between rising edges:

c
uint32_t delta_ticks = current_capture - previous_capture;

Unsigned arithmetic works correctly across one wraparound for a 32-bit counter.

Duty cycle

text
rising1 → falling → rising2
period = rising2 - rising1
high_time = falling - rising1
duty = high_time / period

Without float:

c
uint32_t duty_permille = ((uint64_t)high_ticks * 1000ULL) / period_ticks;

Input filter

The timer’s digital filter helps against glitches, but adds delay and limits the maximum frequency. It complements circuit design rather than replacing it.

Output Compare

Output Compare generates a hardware event when:

text
CNT == CCRx

It can set/reset/toggle an output, raise an interrupt, or request DMA. For periodic events, calculate the next compare from the previous compare rather than the current CNT to avoid accumulating drift.

PWM

text
frequency = f_counter / (ARR + 1)
duty ≈ CCR / (ARR + 1)

PWM is generated in hardware without scheduler jitter.

One-Pulse mode

text
trigger
→ programmable delay
→ output active
→ programmable duration
→ output inactive
→ timer stops

Suitable for an IR flash, reset pulse, camera trigger, or strobe.

Preload

Preload applies new CCR/ARR values only at the update event. This prevents a single incorrect PWM pulse when duty/duration changes in the middle of a period.

4. Common mistakes

  • Measuring an edge with a timestamp inside ISR rather than the Capture Register.
  • Not checking the timer clock.
  • Forgetting PSC + 1 and ARR + 1.
  • Not accounting for overflow.
  • Using software PWM through vTaskDelay().
  • Switching capture polarity too slowly.
  • Logging or doing heavy mathematics in a capture callback.
  • Treating the digital filter as a substitute for circuit design.
  • Changing CCR mid-PWM without preload.
  • Scheduling compare from the current CNT and accumulating drift.

5. Practical assignment

Generate PWM and measure it through Input Capture.

  1. Configure PWM:
text
frequency = 1 kHz
duty = 30 %
  1. Connect PWM output → capture input.
  2. Configure the capture timer:
text
resolution ≈ 1 MHz
capture rising and falling edges
  1. Expected values:
text
period ≈ 1000 us
high time ≈ 300 us
duty ≈ 30 %
frequency ≈ 1000 Hz
  1. The ISR passes only:
c
typedef struct {
    uint32_t capture;
    uint8_t edge;
} timer_capture_event_t;

The task calculates period/duty/frequency.

  1. Add a capture CLI:
text
capture:
resolution=1000000 Hz
edges=120340
measurements=60170
period=999..1001 us
frequency=1000000 mHz
duty=300 permille
invalid=0
timeouts=0

6. What to try next

  • Add a missing-signal timeout.
  • Test 100 Hz, 1 kHz, 10 kHz, and different duty values.
  • Inject a glitch and inspect the counter.
  • Create HIL: Raspberry Pi/a second MCU generates the signal, ESP32/STM32 measures it.
  • Extract the calculation into pulse_meter_core and cover it with host unit tests.

Final checklist for lessons 21-30

Reliability

□ Every fault has source, class, action, and counter. □ Degraded mode is visible through CLI/MQTT/CAN. □ Watchdog is not the first recovery mechanism. □ A fault snapshot is saved before reset.

Configuration and OTA

□ Config has magic/version/size/CRC/generation. □ Defaults and validation exist. □ Old versions can be migrated. □ A/B config exists. □ OTA is confirmed only after self-test. □ LTE/MQTT unavailability does not automatically roll back healthy firmware.

CI/HIL

□ Builds use a pinned Docker/toolchain. □ ELF/MAP/sdkconfig/manifest/SHA256 are retained. □ OTA slot size is controlled. □ Firmware signing is separate from the PR-job. □ HIL tests not only the happy path but also power-cut, rollback, watchdog, and fault paths.

Testability

□ Core modules do not depend on ESP-IDF/STM32 HAL. □ Time is passed as an argument. □ Infinite tasks use a testable step() function. □ The AT parser is tested for fragmentation invariance. □ Fuzzing crashes become regression tests.

RTOS and interrupts

□ A peripheral has a single owner task. □ ISR does not wait for a mutex. □ ISR calls only ...FromISR() API. □ NVIC priorities agree with configMAX_SYSCALL_INTERRUPT_PRIORITY. □ ESP32 interrupts have deliberate core affinity. □ Long processing moves from ISR into a task.

DMA, buffers, and timers

□ A continuous stream has an owner, overrun detection, and CLI stats. □ DMA buffers have explicit ownership. □ STM32H7/F7 cache clean/invalidate are accounted for. □ Timer clock is checked against the clock tree. □ Precise edges are measured through Input Capture. □ PWM/One-Pulse are generated in hardware, not through vTaskDelay().

Exercise

Explain why an ISR-entry timestamp is not equivalent to an Input Capture register value. Describe a test that exposes the difference under interrupt load.

Self-check criteria: Separate edge time from handling time and use a known signal/reference; do not claim accuracy from scheduler timing alone.

Show the supplied answer

The capture register latches the hardware edge time, while an ISR timestamp includes interrupt latency. Apply a known signal and additional interrupt load, compare recorded capture intervals with ISR-entry timing, and inspect variation without heavy logging inside ISR.

Exercise

For the source’s 1 kHz, 30 % PWM exercise, state the expected period and high time, then explain how preload prevents an output glitch during a duty update.

Self-check criteria: Keep the source units and values; verify both measurement results and the update boundary rather than merely observing that output toggles.

Show the supplied answer

The expected period is approximately 1000 us and high time approximately 300 us. Preload defers the new CCR/ARR values until the update event, preventing a partial-period change from producing one malformed pulse.