1 # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
3 # SPDX-License-Identifier: MIT
4 """PWMOut Class for NXP LPC4330"""
6 from greatfet import GreatFET
9 from microcontroller.pin import pwmOuts
11 raise RuntimeError("No PWM outputs defined for this board") from ImportError
14 # pylint: disable=unnecessary-pass
15 class PWMError(IOError):
16 """Base class for PWM errors."""
21 # pylint: enable=unnecessary-pass
23 """Pulse Width Modulation Output Class"""
25 MAX_CYCLE_LEVEL = 1024
27 def __init__(self, pin, *, frequency=750, duty_cycle=0, variable_frequency=False):
28 """This class makes use of the GreatFET One's Pattern Generator to create a
29 Simulated Pulse width modulation. The way that the Pattern Generator works is that
30 takes a pattern in the form of bytes and will repeat the output. The trick to simulate
31 PWM is to generate the correct byte pattern for the correct channel.
34 pin (Pin): CircuitPython Pin object to output to
35 duty_cycle (int) : The fraction of each pulse which is high. 16-bit
36 frequency (int) : target frequency in Hertz (32-bit)
39 PWMOut: PWMOut object.
42 PWMError: if an I/O or OS error occurs.
43 TypeError: if `channel` or `pin` types are invalid.
44 ValueError: if PWM channel does not exist.
48 if variable_frequency:
49 raise NotImplementedError("Variable Frequency is not currently supported.")
56 self._open(pin, duty_cycle, frequency)
61 def __exit__(self, t, value, traceback):
64 def _open(self, pin, duty=0, freq=500):
66 for pwmpair in pwmOuts:
68 self._channel = pwmpair[0]
71 if self._channel is None:
72 raise RuntimeError("No PWM channel found for this Pin")
75 self.duty_cycle = duty
80 self._set_enabled(True)
83 """Deinit the GreatFET One PWM."""
84 # pylint: disable=broad-except
86 if self._channel is not None:
88 self._set_enabled(False)
89 except Exception as e:
90 # due to a race condition for which I have not yet been
91 # able to find the root cause, deinit() often fails
92 # but it does not effect future usage of the pwm pin
94 "warning: failed to deinitialize pwm pin {0} due to: {1}\n".format(
95 self._channel, type(e).__name__
101 # pylint: enable=broad-except
103 def _is_deinited(self):
104 if self._pattern is None:
106 "Object has been deinitialize and can no longer "
107 "be used. Create a new object."
112 def _get_period(self):
113 return 1.0 / self._get_frequency()
115 def _set_period(self, period):
116 """Get or set the PWM's output period in seconds.
119 PWMError: if an I/O or OS error occurs.
120 TypeError: if value type is not int or float.
124 if not isinstance(period, (int, float)):
125 raise TypeError("Invalid period type, should be int or float.")
127 self._set_frequency(1.0 / period)
129 period = property(_get_period, _set_period)
131 def _get_duty_cycle(self):
132 """Get or set the PWM's output duty cycle as a ratio from 0.0 to 1.0.
135 PWMError: if an I/O or OS error occurs.
136 TypeError: if value type is not int or float.
137 ValueError: if value is out of bounds of 0.0 to 1.0.
141 return self._duty_cycle
143 def _set_duty_cycle(self, duty_cycle):
144 if not isinstance(duty_cycle, (int, float)):
145 raise TypeError("Invalid duty cycle type, should be int or float.")
147 # convert from 16-bit
148 if isinstance(duty_cycle, int):
149 duty_cycle /= 65535.0
150 if not 0.0 <= duty_cycle <= 1.0:
151 raise ValueError("Invalid duty cycle value, should be between 0.0 and 1.0.")
153 # Generate a pattern for 1024 samples of the duty cycle
154 pattern = [(1 << self._channel)] * round(PWMOut.MAX_CYCLE_LEVEL * duty_cycle)
155 pattern += [(0 << self._channel)] * round(
156 PWMOut.MAX_CYCLE_LEVEL * (1.0 - duty_cycle)
159 self._pattern = pattern
160 self._duty_cycle = duty_cycle
162 self._set_enabled(True)
164 duty_cycle = property(_get_duty_cycle, _set_duty_cycle)
166 def _get_frequency(self):
167 return self._frequency
169 def _set_frequency(self, frequency):
170 """Get or set the PWM's output frequency in Hertz.
173 PWMError: if an I/O or OS error occurs.
174 TypeError: if value type is not int or float.
178 if not isinstance(frequency, (int, float)):
179 raise TypeError("Invalid frequency type, should be int or float.")
181 # We are sending 1024 samples per second already
182 self._gf.pattern_generator.set_sample_rate(frequency * len(self._pattern))
183 self._frequency = frequency
185 frequency = property(_get_frequency, _set_frequency)
187 def _get_enabled(self):
188 enabled = self._enable
195 raise PWMError(None, 'Unknown enabled value: "%s"' % enabled)
197 def _set_enabled(self, value):
198 """Get or set the PWM's output enabled state.
201 PWMError: if an I/O or OS error occurs.
202 TypeError: if value type is not bool.
206 if not isinstance(value, bool):
207 raise TypeError("Invalid enabled type, should be string.")
212 self._gf.pattern_generator.scan_out_pattern(self._pattern)
214 self._gf.pattern_generator.stop()