]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/adafruit_blinka/microcontroller/nxp_lpc4330/pwmout.py
Black formatted
[Adafruit_Blinka-hackapet.git] / src / adafruit_blinka / microcontroller / nxp_lpc4330 / pwmout.py
1 """PWMOut Class for NXP LPC4330"""
2
3 from greatfet import GreatFET
4 from greatfet.interfaces.pattern_generator import PatternGenerator
5
6 try:
7     from microcontroller.pin import pwmOuts
8 except ImportError:
9     raise RuntimeError("No PWM outputs defined for this board")
10
11 from microcontroller.pin import Pin
12
13
14 # pylint: disable=unnecessary-pass
15 class PWMError(IOError):
16     """Base class for PWM errors."""
17
18     pass
19
20
21 # pylint: enable=unnecessary-pass
22 class PWMOut:
23     """Pulse Width Modulation Output Class"""
24
25     MAX_CYCLE_LEVEL = 1024
26
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.
32
33         Args:
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)
37
38         Returns:
39             PWMOut: PWMOut object.
40
41         Raises:
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.
45         """
46         self._gf = GreatFET()
47
48         if variable_frequency:
49             raise NotImplemented("Variable Frequency is not currently supported.")
50
51         self._pattern = None
52         self._channel = None
53         self._enable = False
54         self._open(pin, duty_cycle, frequency)
55
56     def __enter__(self):
57         return self
58
59     def __exit__(self, t, value, traceback):
60         self.deinit()
61
62     def _open(self, pin, duty=0, freq=500):
63         self._channel = None
64         for pwmpair in pwmOuts:
65             if pwmpair[1] == pin:
66                 self._channel = pwmpair[0]
67
68         self._pin = pin
69         if self._channel is None:
70             raise RuntimeError("No PWM channel found for this Pin")
71
72         # set duty
73         self.duty_cycle = duty
74
75         # set frequency
76         self.frequency = freq
77
78         self._set_enabled(True)
79
80     def deinit(self):
81         """Deinit the GreatFET One PWM."""
82         # pylint: disable=broad-except
83         try:
84             if self._channel is not None:
85                 # self.duty_cycle = 0
86                 self._set_enabled(False)
87         except Exception as e:
88             # due to a race condition for which I have not yet been
89             # able to find the root cause, deinit() often fails
90             # but it does not effect future usage of the pwm pin
91             print(
92                 "warning: failed to deinitialize pwm pin {0} due to: {1}\n".format(
93                     self._channel, type(e).__name__
94                 )
95             )
96         finally:
97             self._pattern = None
98             self._channel = None
99         # pylint: enable=broad-except
100
101     def _is_deinited(self):
102         if self._pattern is None:
103             raise ValueError(
104                 "Object has been deinitialize and can no longer "
105                 "be used. Create a new object."
106             )
107
108     # Mutable properties
109
110     def _get_period(self):
111         return 1.0 / self._get_frequency()
112
113     def _set_period(self, period):
114         """Get or set the PWM's output period in seconds.
115
116         Raises:
117             PWMError: if an I/O or OS error occurs.
118             TypeError: if value type is not int or float.
119
120         :type: int, float
121         """
122         if not isinstance(period, (int, float)):
123             raise TypeError("Invalid period type, should be int or float.")
124
125         self._set_frequency(1.0 / period)
126
127     period = property(_get_period, _set_period)
128
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.
131
132         Raises:
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.
136
137         :type: int, float
138         """
139         return self._duty_cycle
140
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.")
144
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.")
150
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(
154             PWMOut.MAX_CYCLE_LEVEL * (1.0 - duty_cycle)
155         )
156
157         self._pattern = pattern
158         self._duty_cycle = duty_cycle
159         if self._enable:
160             self._set_enabled(True)
161
162     duty_cycle = property(_get_duty_cycle, _set_duty_cycle)
163
164     def _get_frequency(self):
165         return int(PWMOut._nova.getIOpinPWMFreq(self._pwmpin).split("PWMFREQ ")[1])
166
167     def _set_frequency(self, frequency):
168         """Get or set the PWM's output frequency in Hertz.
169
170         Raises:
171             PWMError: if an I/O or OS error occurs.
172             TypeError: if value type is not int or float.
173
174         :type: int, float
175         """
176         if not isinstance(frequency, (int, float)):
177             raise TypeError("Invalid frequency type, should be int or float.")
178
179         # We are sending 1024 samples per second already
180         self._gf.pattern_generator.set_sample_rate(frequency * len(self._pattern))
181
182     frequency = property(_get_frequency, _set_frequency)
183
184     def _get_enabled(self):
185         enabled = self._enable
186
187         if enabled == "1":
188             return True
189         if enabled == "0":
190             return False
191
192         raise PWMError(None, 'Unknown enabled value: "%s"' % enabled)
193
194     def _set_enabled(self, value):
195         """Get or set the PWM's output enabled state.
196
197         Raises:
198             PWMError: if an I/O or OS error occurs.
199             TypeError: if value type is not bool.
200
201         :type: bool
202         """
203         if not isinstance(value, bool):
204             raise TypeError("Invalid enabled type, should be string.")
205         self._enable = value
206         if self._gf:
207             if self._enable:
208                 if self._pattern:
209                     self._gf.pattern_generator.scan_out_pattern(self._pattern)
210             else:
211                 self._gf.pattern_generator.stop()