Skip to content

Commit cebe8e9

Browse files
committed
tests/extmod_hardware: Add a test for machine.PWM freq and duty.
Signed-off-by: Damien George <damien@micropython.org>
1 parent 2c80d36 commit cebe8e9

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed

tests/extmod_hardware/machine_pwm.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Test machine.PWM, frequncy and duty cycle (using machine.time_pulse_us).
2+
#
3+
# IMPORTANT: This test requires hardware connections: the PWM-output and pulse-input
4+
# pins must be wired together (see the variable `pwm_pulse_pins`).
5+
6+
import sys
7+
import time
8+
9+
try:
10+
from machine import time_pulse_us, Pin, PWM
11+
except ImportError:
12+
print("SKIP")
13+
raise SystemExit
14+
15+
import unittest
16+
17+
freq_margin_per_thousand = 0
18+
duty_margin_per_thousand = 0
19+
timing_margin_us = 5
20+
21+
# Configure pins based on the target.
22+
if "esp32" in sys.platform:
23+
pwm_pulse_pins = ((4, 5),)
24+
freq_margin_per_thousand = 2
25+
duty_margin_per_thousand = 1
26+
timing_margin_us = 20
27+
elif "esp8266" in sys.platform:
28+
pwm_pulse_pins = ((4, 5),)
29+
duty_margin_per_thousand = 3
30+
timing_margin_us = 50
31+
elif "mimxrt" in sys.platform:
32+
if "Teensy" in sys.implementation._machine:
33+
# Teensy 4.x
34+
pwm_pulse_pins = (
35+
("D0", "D1"), # FLEXPWM X and UART 1
36+
("D2", "D3"), # FLEXPWM A/B
37+
("D11", "D12"), # QTMR and MOSI/MISO of SPI 0
38+
)
39+
else:
40+
pwm_pulse_pins = (("D0", "D1"),)
41+
elif "rp2" in sys.platform:
42+
pwm_pulse_pins = (("GPIO0", "GPIO1"),)
43+
elif "samd" in sys.platform:
44+
pwm_pulse_pins = (("D0", "D1"),)
45+
else:
46+
print("Please add support for this test on this platform.")
47+
raise SystemExit
48+
49+
50+
# Test a specific frequency and duty cycle.
51+
def _test_freq_duty(self, pulse_in, pwm, freq, duty_u16):
52+
print("freq={:<5} duty_u16={:<5} :".format(freq, duty_u16), end="")
53+
54+
# Check configured freq/duty_u16 is within error bound.
55+
freq_error = abs(pwm.freq() - freq) * 1000 // freq
56+
duty_error = abs(pwm.duty_u16() - duty_u16) * 1000 // (duty_u16 or 1)
57+
print(" freq={} freq_er={}".format(pwm.freq(), freq_error), end="")
58+
print(" duty={} duty_er={}".format(pwm.duty_u16(), duty_error), end="")
59+
print(" :", end="")
60+
self.assertLessEqual(freq_error, freq_margin_per_thousand)
61+
self.assertLessEqual(duty_error, duty_margin_per_thousand)
62+
63+
# Calculate expected timing.
64+
expected_total_us = 1_000_000 // freq
65+
expected_high_us = expected_total_us * duty_u16 // 65535
66+
expected_low_us = expected_total_us - expected_high_us
67+
expected_us = (expected_low_us, expected_high_us)
68+
timeout = 2 * expected_total_us
69+
70+
# Wait for output to settle.
71+
time_pulse_us(pulse_in, 0, timeout)
72+
time_pulse_us(pulse_in, 1, timeout)
73+
74+
if duty_u16 == 0 or duty_u16 == 65535:
75+
# Expect a constant output level.
76+
no_pulse = (
77+
time_pulse_us(pulse_in, 0, timeout) < 0 and time_pulse_us(pulse_in, 1, timeout) < 0
78+
)
79+
self.assertTrue(no_pulse)
80+
if expected_high_us == 0:
81+
# Expect a constant low level.
82+
self.assertEqual(pulse_in(), 0)
83+
else:
84+
# Expect a constant high level.
85+
self.assertEqual(pulse_in(), 1)
86+
else:
87+
# Test timing of low and high pulse.
88+
n_averaging = 10
89+
for level in (0, 1):
90+
t = 0
91+
time_pulse_us(pulse_in, level, timeout)
92+
for _ in range(n_averaging):
93+
t += time_pulse_us(pulse_in, level, timeout)
94+
t //= n_averaging
95+
expected = expected_us[level]
96+
print(" level={} timing_er={}".format(level, abs(t - expected)), end="")
97+
self.assertLessEqual(abs(t - expected), timing_margin_us)
98+
99+
print()
100+
101+
102+
# Test a specific frequency with multiple duty cycles.
103+
def _test_freq(self, freq):
104+
print()
105+
self.pwm.freq(freq)
106+
for duty in (0, 10, 25, 50, 75, 90, 100):
107+
duty_u16 = duty * 65535 // 100
108+
if sys.platform == "esp32":
109+
# TODO why is this bit needed to get it working on esp32?
110+
self.pwm.init(freq=freq, duty_u16=duty_u16)
111+
time.sleep(0.1)
112+
self.pwm.duty_u16(duty_u16)
113+
_test_freq_duty(self, self.pulse_in, self.pwm, freq, duty_u16)
114+
115+
116+
# Given a set of pins, this test class will test multiple frequencies and duty cycles.
117+
class TestBase:
118+
@classmethod
119+
def setUpClass(cls):
120+
print("set up pins:", cls.pwm_pin, cls.pulse_pin)
121+
cls.pwm = PWM(cls.pwm_pin)
122+
cls.pulse_in = Pin(cls.pulse_pin, Pin.IN)
123+
124+
@classmethod
125+
def tearDownClass(cls):
126+
cls.pwm.deinit()
127+
128+
def test_freq_50(self):
129+
_test_freq(self, 50)
130+
131+
def test_freq_100(self):
132+
_test_freq(self, 100)
133+
134+
def test_freq_500(self):
135+
_test_freq(self, 500)
136+
137+
def test_freq_1000(self):
138+
_test_freq(self, 1000)
139+
140+
@unittest.skipIf(sys.platform == "esp8266", "frequency too high")
141+
def test_freq_2000(self):
142+
_test_freq(self, 2000)
143+
144+
@unittest.skipIf(sys.platform == "esp8266", "frequency too high")
145+
def test_freq_5000(self):
146+
_test_freq(self, 5000)
147+
148+
@unittest.skipIf(sys.platform == "esp8266", "frequency too high")
149+
def test_freq_10000(self):
150+
_test_freq(self, 10000)
151+
152+
153+
# Generate test classes, one for each set of pins to test.
154+
for pwm, pulse in pwm_pulse_pins:
155+
cls_name = "Test_{}_{}".format(pwm, pulse)
156+
globals()[cls_name] = type(
157+
cls_name, (TestBase, unittest.TestCase), {"pwm_pin": pwm, "pulse_pin": pulse}
158+
)
159+
160+
if __name__ == "__main__":
161+
unittest.main()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy