📚 Lesson: Measuring Period and Duty Cycle Using Input PWM Mode (Multi‑Channel Capture)
🎯 Learning Objectives
By the end of this lesson, students will be able to:
-
Explain how Input PWM Mode differs from basic input capture.
- Configure a timer with two capture channels to measure:
- Period
- High‑time
- Duty cycle
-
Understand how the timer internally routes TI1/TI2 signals.
-
Implement a dual‑channel capture ISR.
- Apply this to real‑world signals (PWM, servo pulses, tachometers).
1️⃣ Why Input PWM Mode Exists?
In the previous lesson, we captured only the period by measuring rising‑to‑rising (or falling-to-falling) edges. But many real signals encode information in duty cycle, not just frequency:
- RC servo control pulses
- Tachometer outputs
- PWM‑encoded sensors
- Motor controllers
- Communication protocols using pulse width
To measure duty cycle, you need:
-
Period (
rising → rising) -
High time (
rising → falling)
You could do this manually by interrupting on both rising and falling edges and checking the GPIO Pin, but STM32 timers give you something better:
Input PWM Mode = two capture channels working together on one input pin
- Channel 1 captures rising edges → period
- Channel 2 captures falling edges → high time
Both channels are synchronized by hardware.
2️⃣ How Input PWM Mode Works (Block Diagram)
A simple conceptual diagram:
flowchart LR
A[External PWM Signal] --> B(TI1 Input)
B --> C[IC1
Rising Edge Capture
→ CCR1]
B --> D[IC2
Falling Edge Capture
→ CCR2]
C --> E[Period Measurement]
D --> F[High‑Time Measurement]
E --> G[Duty Cycle Calculation]
F --> G
Timer hardware automatically:
-
Routes TI1 to both channels
-
Applies opposite edge polarity
-
Computes period and duty cycle with minimal software overhead.
3️⃣ Register Configuration Overview
For a timer with at least two channels (TIM2, TIM3, TIM1, TIM15, etc.):
Step 1 - Configure GPIO as AF for TIMx_CH1
- Example for
PA6 (TIM3_CH1):
GPIO_InitAlternateF(GPIOA, 6, 1); // AF1 for TIM3_CH1
Step 2 — Configure Timer Base
Timer_Init(TIM3, 40, 0); // 1 MHz tick = 1 µs resolution, assuming 40[MHz] BUS speed
Step 3 — Configure Input PWM Mode
This is the key part.
Channel 1: map to TI1, capture rising edges
TIM3->CCMR1 &= ~TIM_CCMR1_CC1S_Msk; TIM3->CCMR1 |= TIM_CCMR1_CC1S_0; // CC1 mapped to TI1 TIM3->CCER &= ~TIM_CCER_CC1P; // Rising edge
Channel 2: map to TI1, capture falling edges
TIM3->CCMR1 &= ~TIM_CCMR1_CC2S_Msk; TIM3->CCMR1 |= TIM_CCMR1_CC2S_1; // CC2 mapped to TI1 TIM3->CCER |= TIM_CCER_CC2P; // Falling edge
Step 4 — Enable both captures
TIM3->CCER |= TIM_CCER_CC1E | TIM_CCER_CC2E;
Step 5 — Enable interrupts
TIM3->DIER |= TIM_DIER_CC1IE | TIM_DIER_CC2IE; NVIC_EnableIRQ(TIM3_IRQn);
Step 6 — Start timer
Timer_SetEnable(TIM3, 1);
4️⃣ ISR Logic: Extracting Period and Duty Cycle
volatile uint16_t lastRise = 0;
volatile uint16_t lastFall = 0;
volatile uint16_t period = 0;
volatile uint16_t highTime = 0;
void TIM3_IRQHandler(void)
{
// Rising edge → CCR1
if (TIM3->SR & TIM_SR_CC1IF)
{
TIM3->SR &= ~TIM_SR_CC1IF;
uint16_t now = TIM3->CCR1;
period = now - lastRise;
lastRise = now;
}
// Falling edge → CCR2
if (TIM3->SR & TIM_SR_CC2IF)
{
TIM3->SR &= ~TIM_SR_CC2IF;
uint16_t now = TIM3->CCR2;
highTime = now - lastRise; // time from rise → fall
}
}
Duty Cycle Calculation
float duty = (highTime * 100.0f) / period;
5️⃣ Can we interrupt only on the falling edge?
Activity:
Modify the Input PWM Mode configuration so that only the falling edge (CC2) generates an interrupt.
-
Verify that
CCR1still updates on rising edges -
Compute high‑time and period using only the
CC2 interrupt -
Compare the results with the dual‑interrupt version
-
Discuss advantages and limitations