8000 tests/extmod_hardware: Add a test for machine.PWM freq and duty. · micropython/micropython@cebe8e9 · GitHub
[go: up one dir, main page]

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, 8000 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)
0