1 """PWMOut Class for NXP LPC4330"""
3 from greatfet import GreatFET
6 from microcontroller.pin import pwmOuts
8 raise RuntimeError("No PWM outputs defined for this board")
10 # pylint: disable=unnecessary-pass
11 class PWMError(IOError):
12 """Base class for PWM errors."""
17 # pylint: enable=unnecessary-pass
19 """Pulse Width Modulation Output Class"""
21 MAX_CYCLE_LEVEL = 1024
23 def __init__(self, pin, *, frequency=750, duty_cycle=0, variable_frequency=False):
24 """This class makes use of the GreatFET One's Pattern Generator to create a
25 Simulated Pulse width modulation. The way that the Pattern Generator works is that
26 takes a pattern in the form of bytes and will repeat the output. The trick to simulate
27 PWM is to generate the correct byte pattern for the correct channel.
30 pin (Pin): CircuitPython Pin object to output to
31 duty_cycle (int) : The fraction of each pulse which is high. 16-bit
32 frequency (int) : target frequency in Hertz (32-bit)
35 PWMOut: PWMOut object.
38 PWMError: if an I/O or OS error occurs.
39 TypeError: if `channel` or `pin` types are invalid.
40 ValueError: if PWM channel does not exist.
44 if variable_frequency:
45 raise NotImplementedError("Variable Frequency is not currently supported.")
52 self._open(pin, duty_cycle, frequency)
57 def __exit__(self, t, value, traceback):
60 def _open(self, pin, duty=0, freq=500):
62 for pwmpair in pwmOuts:
64 self._channel = pwmpair[0]
67 if self._channel is None:
68 raise RuntimeError("No PWM channel found for this Pin")
71 self.duty_cycle = duty
76 self._set_enabled(True)
79 """Deinit the GreatFET One PWM."""
80 # pylint: disable=broad-except
82 if self._channel is not None:
84 self._set_enabled(False)
85 except Exception as e:
86 # due to a race condition for which I have not yet been
87 # able to find the root cause, deinit() often fails
88 # but it does not effect future usage of the pwm pin
90 "warning: failed to deinitialize pwm pin {0} due to: {1}\n".format(
91 self._channel, type(e).__name__
97 # pylint: enable=broad-except
99 def _is_deinited(self):
100 if self._pattern is None:
102 "Object has been deinitialize and can no longer "
103 "be used. Create a new object."
108 def _get_period(self):
109 return 1.0 / self._get_frequency()
111 def _set_period(self, period):
112 """Get or set the PWM's output period in seconds.
115 PWMError: if an I/O or OS error occurs.
116 TypeError: if value type is not int or float.
120 if not isinstance(period, (int, float)):
121 raise TypeError("Invalid period type, should be int or float.")
123 self._set_frequency(1.0 / period)
125 period = property(_get_period, _set_period)
127 def _get_duty_cycle(self):
128 """Get or set the PWM's output duty cycle as a ratio from 0.0 to 1.0.
131 PWMError: if an I/O or OS error occurs.
132 TypeError: if value type is not int or float.
133 ValueError: if value is out of bounds of 0.0 to 1.0.
137 return self._duty_cycle
139 def _set_duty_cycle(self, duty_cycle):
140 if not isinstance(duty_cycle, (int, float)):
141 raise TypeError("Invalid duty cycle type, should be int or float.")
143 # convert from 16-bit
144 if isinstance(duty_cycle, int):
145 duty_cycle /= 65535.0
146 if not 0.0 <= duty_cycle <= 1.0:
147 raise ValueError("Invalid duty cycle value, should be between 0.0 and 1.0.")
149 # Generate a pattern for 1024 samples of the duty cycle
150 pattern = [(1 << self._channel)] * round(PWMOut.MAX_CYCLE_LEVEL * duty_cycle)
151 pattern += [(0 << self._channel)] * round(
152 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 self._frequency
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))
179 self._frequency = frequency
181 frequency = property(_get_frequency, _set_frequency)
183 def _get_enabled(self):
184 enabled = self._enable
191 raise PWMError(None, 'Unknown enabled value: "%s"' % enabled)
193 def _set_enabled(self, value):
194 """Get or set the PWM's output enabled state.
197 PWMError: if an I/O or OS error occurs.
198 TypeError: if value type is not bool.
202 if not isinstance(value, bool):
203 raise TypeError("Invalid enabled type, should be string.")
208 self._gf.pattern_generator.scan_out_pattern(self._pattern)
210 self._gf.pattern_generator.stop()