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