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
13 # pylint: disable=unnecessary-pass
14 class PWMError(IOError):
15 """Base class for PWM errors."""
20 # pylint: enable=unnecessary-pass
22 """Pulse Width Modulation Output Class"""
24 MAX_CYCLE_LEVEL = 1024
26 def __init__(self, pin, *, frequency=750, duty_cycle=0, variable_frequency=False):
27 """This class makes use of the GreatFET One's Pattern Generator to create a
28 Simulated Pulse width modulation. The way that the Pattern Generator works is that
29 takes a pattern in the form of bytes and will repeat the output. The trick to simulate
30 PWM is to generate the correct byte pattern for the correct channel.
33 pin (Pin): CircuitPython Pin object to output to
34 duty_cycle (int) : The fraction of each pulse which is high. 16-bit
35 frequency (int) : target frequency in Hertz (32-bit)
38 PWMOut: PWMOut object.
41 PWMError: if an I/O or OS error occurs.
42 TypeError: if `channel` or `pin` types are invalid.
43 ValueError: if PWM channel does not exist.
47 if variable_frequency:
48 raise NotImplementedError("Variable Frequency is not currently supported.")
55 self._open(pin, duty_cycle, frequency)
60 def __exit__(self, t, value, traceback):
63 def _open(self, pin, duty=0, freq=500):
65 for pwmpair in pwmOuts:
67 self._channel = pwmpair[0]
70 if self._channel is None:
71 raise RuntimeError("No PWM channel found for this Pin")
74 self.duty_cycle = duty
79 self._set_enabled(True)
82 """Deinit the GreatFET One PWM."""
83 # pylint: disable=broad-except
85 if self._channel is not None:
87 self._set_enabled(False)
88 except Exception as e:
89 # due to a race condition for which I have not yet been
90 # able to find the root cause, deinit() often fails
91 # but it does not effect future usage of the pwm pin
93 "warning: failed to deinitialize pwm pin {0} due to: {1}\n".format(
94 self._channel, type(e).__name__
100 # pylint: enable=broad-except
102 def _is_deinited(self):
103 if self._pattern is None:
105 "Object has been deinitialize and can no longer "
106 "be used. Create a new object."
111 def _get_period(self):
112 return 1.0 / self._get_frequency()
114 def _set_period(self, period):
115 """Get or set the PWM's output period in seconds.
118 PWMError: if an I/O or OS error occurs.
119 TypeError: if value type is not int or float.
123 if not isinstance(period, (int, float)):
124 raise TypeError("Invalid period type, should be int or float.")
126 self._set_frequency(1.0 / period)
128 period = property(_get_period, _set_period)
130 def _get_duty_cycle(self):
131 """Get or set the PWM's output duty cycle as a ratio from 0.0 to 1.0.
134 PWMError: if an I/O or OS error occurs.
135 TypeError: if value type is not int or float.
136 ValueError: if value is out of bounds of 0.0 to 1.0.
140 return self._duty_cycle
142 def _set_duty_cycle(self, duty_cycle):
143 if not isinstance(duty_cycle, (int, float)):
144 raise TypeError("Invalid duty cycle type, should be int or float.")
146 # convert from 16-bit
147 if isinstance(duty_cycle, int):
148 duty_cycle /= 65535.0
149 if not 0.0 <= duty_cycle <= 1.0:
150 raise ValueError("Invalid duty cycle value, should be between 0.0 and 1.0.")
152 # Generate a pattern for 1024 samples of the duty cycle
153 pattern = [(1 << self._channel)] * round(PWMOut.MAX_CYCLE_LEVEL * duty_cycle)
154 pattern += [(0 << self._channel)] * round(
155 PWMOut.MAX_CYCLE_LEVEL * (1.0 - duty_cycle)
158 self._pattern = pattern
159 self._duty_cycle = duty_cycle
161 self._set_enabled(True)
163 duty_cycle = property(_get_duty_cycle, _set_duty_cycle)
165 def _get_frequency(self):
166 return self._frequency
168 def _set_frequency(self, frequency):
169 """Get or set the PWM's output frequency in Hertz.
172 PWMError: if an I/O or OS error occurs.
173 TypeError: if value type is not int or float.
177 if not isinstance(frequency, (int, float)):
178 raise TypeError("Invalid frequency type, should be int or float.")
180 # We are sending 1024 samples per second already
181 self._gf.pattern_generator.set_sample_rate(frequency * len(self._pattern))
182 self._frequency = frequency
184 frequency = property(_get_frequency, _set_frequency)
186 def _get_enabled(self):
187 enabled = self._enable
194 raise PWMError(None, 'Unknown enabled value: "%s"' % enabled)
196 def _set_enabled(self, value):
197 """Get or set the PWM's output enabled state.
200 PWMError: if an I/O or OS error occurs.
201 TypeError: if value type is not bool.
205 if not isinstance(value, bool):
206 raise TypeError("Invalid enabled type, should be string.")
211 self._gf.pattern_generator.scan_out_pattern(self._pattern)
213 self._gf.pattern_generator.stop()