1 """PWMOut Class for NXP LPC4330"""
3 from greatfet import GreatFET
4 from greatfet.interfaces.pattern_generator import PatternGenerator
7 from microcontroller.pin import pwmOuts
9 raise RuntimeError("No PWM outputs defined for this board")
11 from microcontroller.pin import Pin
14 # pylint: disable=unnecessary-pass
15 class PWMError(IOError):
16 """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 NotImplemented("Variable Frequency is not currently supported.")
53 self._open(pin, duty_cycle, frequency)
58 def __exit__(self, t, value, traceback):
61 def _open(self, pin, duty=0, freq=500):
63 for pwmpair in pwmOuts:
65 self._channel = pwmpair[0]
68 if self._channel is None:
69 raise RuntimeError("No PWM channel found for this Pin")
72 self.duty_cycle = duty
77 self._set_enabled(True)
80 """Deinit the GreatFET One PWM."""
81 # pylint: disable=broad-except
83 if self._channel is not None:
85 self._set_enabled(False)
86 except Exception as e:
87 # due to a race condition for which I have not yet been
88 # able to find the root cause, deinit() often fails
89 # but it does not effect future usage of the pwm pin
91 "warning: failed to deinitialize pwm pin {0} due to: {1}\n".format(
92 self._channel, type(e).__name__
98 # pylint: enable=broad-except
100 def _is_deinited(self):
101 if self._pattern is None:
103 "Object has been deinitialize and can no longer "
104 "be used. Create a new object."
109 def _get_period(self):
110 return 1.0 / self._get_frequency()
112 def _set_period(self, period):
113 """Get or set the PWM's output period in seconds.
116 PWMError: if an I/O or OS error occurs.
117 TypeError: if value type is not int or float.
121 if not isinstance(period, (int, float)):
122 raise TypeError("Invalid period type, should be int or float.")
124 self._set_frequency(1.0 / period)
126 period = property(_get_period, _set_period)
129 def _get_duty_cycle(self):
130 """Get or set the PWM's output duty cycle as a ratio from 0.0 to 1.0.
133 PWMError: if an I/O or OS error occurs.
134 TypeError: if value type is not int or float.
135 ValueError: if value is out of bounds of 0.0 to 1.0.
139 return self._duty_cycle
141 def _set_duty_cycle(self, duty_cycle):
142 if not isinstance(duty_cycle, (int, float)):
143 raise TypeError("Invalid duty cycle type, should be int or float.")
145 # convert from 16-bit
146 if isinstance(duty_cycle, int):
147 duty_cycle /= 65535.0
148 if not 0.0 <= duty_cycle <= 1.0:
149 raise ValueError("Invalid duty cycle value, should be between 0.0 and 1.0.")
151 # Generate a pattern for 1024 samples of the duty cycle
152 pattern = [(1 << self._channel)] * round(PWMOut.MAX_CYCLE_LEVEL * duty_cycle)
153 pattern += [(0 << self._channel)] * round(PWMOut.MAX_CYCLE_LEVEL * (1.0 - duty_cycle))
155 self._pattern = pattern
156 self._duty_cycle = duty_cycle
158 self._set_enabled(True)
160 duty_cycle = property(_get_duty_cycle, _set_duty_cycle)
162 def _get_frequency(self):
163 return int(PWMOut._nova.getIOpinPWMFreq(self._pwmpin).split("PWMFREQ ")[1])
165 def _set_frequency(self, frequency):
166 """Get or set the PWM's output frequency in Hertz.
169 PWMError: if an I/O or OS error occurs.
170 TypeError: if value type is not int or float.
174 if not isinstance(frequency, (int, float)):
175 raise TypeError("Invalid frequency type, should be int or float.")
177 # We are sending 1024 samples per second already
178 self._gf.pattern_generator.set_sample_rate(frequency * len(self._pattern))
180 frequency = property(_get_frequency, _set_frequency)
182 def _get_enabled(self):
183 enabled = self._enable
190 raise PWMError(None, 'Unknown enabled value: "%s"' % enabled)
192 def _set_enabled(self, value):
193 """Get or set the PWM's output enabled state.
196 PWMError: if an I/O or OS error occurs.
197 TypeError: if value type is not bool.
201 if not isinstance(value, bool):
202 raise TypeError("Invalid enabled type, should be string.")
207 self._gf.pattern_generator.scan_out_pattern(self._pattern)
209 self._gf.pattern_generator.stop()