Measuring PWM
Objectives
Section titled “Objectives”By the end of this lesson you will:
- Measure PWM’s period, frequency, and duty cycle from a waveform
- Compare a measured duty cycle against the commanded value, and explain any mismatch
Before you start
Section titled “Before you start”- You can already set up an oscilloscope for a stable trace (lesson Oscilloscope basics), and time a signal with a logic analyzer (lesson Capturing a first digital signal)
- A TESAIoT Dev Kit flashed with the QWA309 Header I/O Test example, a two-channel oscilloscope (or logic analyzer), and a multimeter
- The lab’s optional section uses the AIoT in Action course’s MicroPython firmware, which has a
led.brightness()command. If you do not have it, skip that section.
See it work first
Section titled “See it work first”The AIoT in Action course has an example, 03_led_brightness.py,
that dims an LED with led.brightness(pct) and reads it back with led.duty(). In that example, there is a test that commands on() followed by toggle().
The LED has gone dark, but duty() still answers the same number. The comment in the code sums it up: “duty() = what was commanded, not what is measured.”
This is exactly this lesson’s question: does the number the program reports match the signal actually coming out of the pin? We answer with measuring instruments, not by trusting the number on screen.
Concepts
Section titled “Concepts”1. PWM’s three numbers
Section titled “1. PWM’s three numbers”PWM (pulse-width modulation) is a square wave with a fixed period, whose proportion of time spent at 1 can be adjusted.
Period T = time from one rising edge to the nextFrequency f = 1 / TDuty cycle D = t_high / TAverage voltage V_avg = D × V_high (when a load or filter circuit averages the signal)Example: T = 2 ms, t_high = 0.5 ms, on a 3.3 V pin
f = 1 / 2 ms = 500 Hz D = 0.5 / 2 = 25% V_avg = 0.25 × 3.3 V = 0.825 VAn LED driven by PWM fast enough appears to the eye as an average brightness (generally, above about 100 Hz most people no longer see flicker, though a phone camera may still show banding). A multimeter in DC voltage mode also reads the average, as long as the period is much shorter than the meter’s own averaging time. At a low frequency like 25 Hz, the number will bounce around instead.
2. Hardware PWM versus software PWM
Section titled “2. Hardware PWM versus software PWM”PWM from a hardware timer. A counter counts up on a clock of frequency f_clk, restarting after reaching N; the pin is 1 while the count is still below a compare value.
f_PWM = f_clk / N D = compare / N Duty resolution = 1 / NExample: f_clk = 1 MHz, N = 1000 gives 1 kHz PWM with duty adjustable in steps of 0.1%. Wanting 20 kHz from a 100 MHz clock gives N = 5000, with 0.02% resolution. The register detail (whether you load N or N − 1) varies by hardware — on the PSoC, this block is called TCPWM; check the PDL documentation alongside it. The TESAIoT Dev Kit’s SDK notes that the RGB LED’s dimming path wires its pin to a TCPWM0 signal, while the AIoT in Action course notes that an LED with no hardware PWM path gets only a short pulse of about 12 ms, then goes dark, without holding its brightness.
PWM from software. The program writes the pin to 1, delays, writes it to 0, delays, and loops. The header test program’s PWM Out button works this way (writing P13.3 = 1, P13.4 = 0, delaying 20 ms, then swapping, for 50 cycles, per the example’s code). The advantage is that any pin works; the disadvantage is that the time taken by other instructions and interrupts leaks into the rhythm, so the period comes out slightly longer than set and can jitter, and the CPU must not do anything else in the meantime.
A complementary pair. Driving a motor with an H-bridge uses two signals that are phase-inverted from each other, and these two signals must never both be 1 at the same time. Otherwise the top and bottom transistors would both conduct at once, shorting the supply (shoot-through). Real systems leave a brief dead time where both lines are 0 before switching.
3. Why a measured value can differ from the commanded one
Section titled “3. Why a measured value can differ from the commanded one”| Cause | Symptom | Example |
|---|---|---|
| Counter resolution | Duty gets rounded to a step | N = 100, commanding 33.3% actually gives 33% |
| An off-by-one count | The period or duty is always off by one step | Loading N instead of N − 1 into the register |
| Software PWM | The period comes out longer than set, and jitters | Set 40 ms, measure 40.1 ms |
| Reversed polarity | The measured duty is 100% minus the commanded value | An active-low LED commanded 20% lights for 80% of the time |
| A number in the program that is not a measurement | The program reports the last commanded value | duty() still answers 100 after toggle() has turned the LED off |
| The pin is not connected to the hardware’s PWM | You get a short pulse then nothing, or no signal at all | An LED with no PWM path |
| Measurement method | The pulse width comes out wrong when the edge is slow | Measuring at the 10% level instead of the 50% level of the voltage |
How to measure accurately:
- Measure pulse width at the 50% level of the voltage (most instruments’ automatic measurement uses this level)
- Measure several periods and average, or use the instrument’s statistics (mean, min, max) — the min and max tell you how much jitter there is
- Time resolution must be finer than the duty resolution you want to check — to see a 0.1% step of a 1 kHz PWM (1 µs), you need to measure time more finely than 1 µs
Worked example
Section titled “Worked example”Problem: measure the PWM from the PWM Out button, and compare it against what the code commands (half-period 20 ms, 50 cycles, P13.3 and P13.4 phase-inverted). The numbers below are sample data.
- The commanded value: t_high = 20 ms, T = 40 ms, f = 25 Hz, D = 50%.
- Connect. Channel 1 at P13.3, channel 2 at P13.4, both probe grounds at GND. Set 10 ms/div, 1 V/div, trigger on channel 1’s rising edge at 1.65 V, mode Normal.
- Measure channel 1 (averaged over 10 periods): T = 40.10 ms, t_high = 20.05 ms.
f = 1 / 40.10 ms = 24.94 HzD = 20.05 / 40.10 = 50.0%The period is 0.10 ms (0.25%) longer than commanded- Explain. The duty matches the commanded value, but the period is a little longer, spread evenly across both halves — the signature of software PWM, where the time taken by the pin-write instructions and the loop leaks into every half-period.
- Check the complementary pair. Zoom in on an edge where the two channels swap, and check whether there is any interval where both lines read 1. In the example’s code, one pin is written to 1 just one instruction before the other is written to 0. If your instrument is fine enough, you may see an extremely brief overlap at one edge, every cycle. This is exactly why a real system must design dead time in hardware, not rely on instruction ordering.
- Count cycles. Channel 1 must show exactly 50 pulses.
Practice
Section titled “Practice”- A PWM has a 2 ms period and a 0.5 ms pulse width. Find its frequency, duty, and average voltage on a 3.3 V pin.
- A 20 kHz PWM with 30% duty. Find its period and pulse width.
- A timer uses a 10 MHz clock, and you want 10 kHz PWM. How many counts per period do you need? What compare value gives 33% duty, and how fine can duty be adjusted?
- An LED is wired active-low (it lights when the pin is 0). The program commands 20% duty on the pin. For what percentage of the time is the LED lit?
- A software PWM is set for a 40.0 ms period, but measures at 40.4 ms. What is the percent error, and what is the real frequency?
- A timer has N = 100, and the program commands 12.34%. What duty will the hardware likely actually give?
- A 3.3 V pin drives 40% duty PWM. What should a DC-voltage-mode multimeter read, roughly, if the frequency is high enough?
Solution
Section titled “Solution”- f = 500 Hz, D = 25%, V_avg = 0.825 V
- T = 1 / 20 kHz = 50 µs. Pulse width = 0.30 × 50 µs = 15 µs.
- N = 10 MHz / 10 kHz = 1000. Compare value = 330. Resolution = 1 / 1000 = 0.1%.
- The LED lights when the pin is 0, which is 100% − 20% = 80% of the time.
- (40.4 − 40.0) / 40.0 = 1%. Frequency = 1 / 40.4 ms = 24.75 Hz.
- 12 steps out of 100 is 12% (if the code truncates), off from the commanded value by 0.34 percentage points.
- 0.40 × 3.3 V = 1.32 V.
Check your understanding
Section titled “Check your understanding”Answer at least 4 of the 5 questions in quiz.yaml correctly.
Part A: PWM Out on P13.3 and P13.4. Follow the worked example with your own real data, using an oscilloscope (or a logic analyzer at 100 kHz if you have no scope).
Part B: PWM3 Out on P15.2 and P15.3. Press the PWM3 Out button — the program drives the same kind of complementary pair on pins shared with the ADC. Measure the same way, and compare against Part A.
| Commanded | P13.3 | P13.4 | P15.2 | P15.3 | |
|---|---|---|---|---|---|
| Average period | 40 ms | ||||
| Frequency | 25 Hz | ||||
| t_high | 20 ms | ||||
| Duty | 50% | ||||
| Jitter (max T − min T) | 0 | ||||
| Ever both 1 with its partner? | never |
Part C: a multimeter and PWM. Measure P13.3 in DC voltage mode while PWM Out is running. Record the number you see, and explain why it bounces around near 1.65 V instead of holding steady.
Optional: LED PWM (if you have the MicroPython firmware and a safely accessible test point)
- Open the board’s schematic. Find whether the RGB LED’s pin, or a resistor connected to it, has a point you can safely probe. If not, stop here — never probe a chip’s own pin.
- If there is one, command
led.brightness(10), thenled.brightness(50), thenled.brightness(90), one at a time, and measure frequency and duty at that point. - Read
led.duty()after each one, and compare it against what you measured. Remember to account for polarity (does the LED light when the pin is 1 or 0?) before drawing a conclusion.
Going further
Section titled “Going further”The next module begins with Building a circuit on a breadboard, where we build a larger circuit correctly the first time, checking it with every instrument we now know how to use, before ever applying power.
Reflect
Section titled “Reflect”Which number in your own program reports “what was commanded” that you have previously read as “what actually happened” — and which instrument would you use to prove it?
References
Section titled “References”- AIoT in Action: examples/s03/03_led_brightness.py
- Oscilloscope (Wikipedia)
- Pulse-width modulation (Wikipedia)
- SDK: cm33/io/04_gpio_led_button.c (the RGB LED’s pin and its TCPWM path)
- Code of QWA309 Header I/O Test (Developer Hub, commit 372d0d8)
Review questions
Answer on your own first, then open the answer.
-
A PWM signal has a 2 ms period and a 0.5 ms pulse width. What are its frequency and duty cycle? (Objective 1)
- 500 Hz และ 25%
- 2 kHz และ 25%
- 500 Hz และ 75%
- 250 Hz และ 50%
Show answer
Answer: A. 500 Hz และ 25%
f = 1 / 2 ms = 500 Hz และ D = 0.5 / 2 = 25%
-
A 3.3 V pin drives 40% duty PWM at high frequency into an averaging filter. What is the average voltage? (Objective 1)
- 1.32 V
- 1.98 V
- 3.3 V
- 0.40 V
Show answer
Answer: A. 1.32 V
V_avg = D × V_high = 0.40 × 3.3 V = 1.32 V ส่วน 1.98 V คือกรณี duty 60%
-
Which practice gives the most accurate PWM duty cycle measurement? (Objective 1)
- วัดคาบเวลาเดียวที่ระดับ 10% ของแรงดัน
- วัดความกว้างพัลส์ที่ระดับ 50% ของแรงดัน และเฉลี่ยหลายคาบเวลา
- อ่านค่าจากฟังก์ชัน duty() ของโปรแกรม
- ใช้มัลติมิเตอร์โหมดความต่อเนื่อง
Show answer
Answer: B. วัดความกว้างพัลส์ที่ระดับ 50% ของแรงดัน และเฉลี่ยหลายคาบเวลา
ระดับ 50% ลดผลของขอบที่ช้า และการเฉลี่ยหลายคาบเวลาลดผลของความละเอียดเวลาและ jitter ค่าจากโปรแกรมคือค่าที่สั่ง ไม่ใช่ค่าที่วัด
-
The program commands 30% but the pin measures 70%, and the two always add up to 100%. What is the most likely cause? (Objective 2)
- ความละเอียดของตัวนับไม่พอ
- ขั้วของสัญญาณกลับ เช่น ขาออกตั้งเป็น active-low หรือวัดขาที่เป็นคู่กลับเฟส
- ความถี่ของ PWM สูงเกินไป
- ออสซิลโลสโคปเสีย
Show answer
Answer: B. ขั้วของสัญญาณกลับ เช่น ขาออกตั้งเป็น active-low หรือวัดขาที่เป็นคู่กลับเฟส
D ที่วัดได้ = 100% − D ที่สั่ง คือรูปแบบของขั้วกลับ ความละเอียดของตัวนับทำให้คลาดเป็นขั้นเล็ก ๆ ไม่ได้กลับค่าทั้งหมด
-
After on() then toggle(), the LED is off but duty() still returns 100. Why? (Objective 2)
- หลอดเสีย
- duty() รายงานค่าที่สั่งครั้งล่าสุด ไม่ได้วัดสถานะจริงของขา
- toggle() ไม่ทำงาน
- PWM ของฮาร์ดแวร์ยังทำงานอยู่ที่ 100%
Show answer
Answer: B. duty() รายงานค่าที่สั่งครั้งล่าสุด ไม่ได้วัดสถานะจริงของขา
ตัวอย่าง 03_led_brightness.py ของหลักสูตร AIoT in Action สาธิตเรื่องนี้ไว้ ตัวเลขในโปรแกรมคือสิ่งที่สั่ง การจะรู้ว่าขาทำอะไรจริงต้องวัดด้วยเครื่องมือ
Cite this lesson
If you teach from this lesson or reuse it in slides or documents, credit it with the text below. If you changed it, add (adapted) after the title.
"Measuring PWM" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0
Thai attribution: "วัด PWM" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
Content is licensed CC BY-NC 4.0. Reuse it non-commercially and credit the Thai Embedded Systems Association (TESA) every time. · How to cite TESA