]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/adafruit_blinka/microcontroller/nxp_lpc4330/pwmout.py
i2c support
[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
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 NotImplementedError("Variable Frequency is not currently supported.")
50
51         self._pattern = None
52         self._channel = None
53         self._enable = False
54         self._frequency = 500
55         self._duty_cycle = 0
56         self._open(pin, duty_cycle, frequency)
57
58     def __enter__(self):
59         return self
60
61     def __exit__(self, t, value, traceback):
62         self.deinit()
63
64     def _open(self, pin, duty=0, freq=500):
65         self._channel = None
66         for pwmpair in pwmOuts:
67             if pwmpair[1] == pin:
68                 self._channel = pwmpair[0]
69
70         self._pin = pin
71         if self._channel is None:
72             raise RuntimeError("No PWM channel found for this Pin")
73
74         # set duty
75         self.duty_cycle = duty
76
77         # set frequency
78         self.frequency = freq
79
80         self._set_enabled(True)
81
82     def deinit(self):
83         """Deinit the GreatFET One PWM."""
84         # pylint: disable=broad-except
85         try:
86             if self._channel is not None:
87                 # self.duty_cycle = 0
88                 self._set_enabled(False)
89         except Exception as e:
90             # due to a race condition for which I have not yet been
91             # able to find the root cause, deinit() often fails
92             # but it does not effect future usage of the pwm pin
93             print(
94                 "warning: failed to deinitialize pwm pin {0} due to: {1}\n".format(
95                     self._channel, type(e).__name__
96                 )
97             )
98         finally:
99             self._pattern = None
100             self._channel = None
101         # pylint: enable=broad-except
102
103     def _is_deinited(self):
104         if self._pattern is None:
105             raise ValueError(
106                 "Object has been deinitialize and can no longer "
107                 "be used. Create a new object."
108             )
109
110     # Mutable properties
111
112     def _get_period(self):
113         return 1.0 / self._get_frequency()
114
115     def _set_period(self, period):
116         """Get or set the PWM's output period in seconds.
117
118         Raises:
119             PWMError: if an I/O or OS error occurs.
120             TypeError: if value type is not int or float.
121
122         :type: int, float
123         """
124         if not isinstance(period, (int, float)):
125             raise TypeError("Invalid period type, should be int or float.")
126
127         self._set_frequency(1.0 / period)
128
129     period = property(_get_period, _set_period)
130
131     def _get_duty_cycle(self):
132         """Get or set the PWM's output duty cycle as a ratio from 0.0 to 1.0.
133
134         Raises:
135            PWMError: if an I/O or OS error occurs.
136            TypeError: if value type is not int or float.
137            ValueError: if value is out of bounds of 0.0 to 1.0.
138
139         :type: int, float
140         """
141         return self._duty_cycle
142
143     def _set_duty_cycle(self, duty_cycle):
144         if not isinstance(duty_cycle, (int, float)):
145             raise TypeError("Invalid duty cycle type, should be int or float.")
146
147         # convert from 16-bit
148         if isinstance(duty_cycle, int):
149             duty_cycle /= 65535.0
150         if not 0.0 <= duty_cycle <= 1.0:
151             raise ValueError("Invalid duty cycle value, should be between 0.0 and 1.0.")
152
153         # Generate a pattern for 1024 samples of the duty cycle
154         pattern = [(1 << self._channel)] * round(PWMOut.MAX_CYCLE_LEVEL * duty_cycle)
155         pattern += [(0 << self._channel)] * round(
156             PWMOut.MAX_CYCLE_LEVEL * (1.0 - duty_cycle)
157         )
158
159         self._pattern = pattern
160         self._duty_cycle = duty_cycle
161         if self._enable:
162             self._set_enabled(True)
163
164     duty_cycle = property(_get_duty_cycle, _set_duty_cycle)
165
166     def _get_frequency(self):
167         return self._frequency
168
169     def _set_frequency(self, frequency):
170         """Get or set the PWM's output frequency in Hertz.
171
172         Raises:
173             PWMError: if an I/O or OS error occurs.
174             TypeError: if value type is not int or float.
175
176         :type: int, float
177         """
178         if not isinstance(frequency, (int, float)):
179             raise TypeError("Invalid frequency type, should be int or float.")
180
181         # We are sending 1024 samples per second already
182         self._gf.pattern_generator.set_sample_rate(frequency * len(self._pattern))
183         self._frequency = frequency
184
185     frequency = property(_get_frequency, _set_frequency)
186
187     def _get_enabled(self):
188         enabled = self._enable
189
190         if enabled == "1":
191             return True
192         if enabled == "0":
193             return False
194
195         raise PWMError(None, 'Unknown enabled value: "%s"' % enabled)
196
197     def _set_enabled(self, value):
198         """Get or set the PWM's output enabled state.
199
200         Raises:
201             PWMError: if an I/O or OS error occurs.
202             TypeError: if value type is not bool.
203
204         :type: bool
205         """
206         if not isinstance(value, bool):
207             raise TypeError("Invalid enabled type, should be string.")
208         self._enable = value
209         if self._gf:
210             if self._enable:
211                 if self._pattern:
212                     self._gf.pattern_generator.scan_out_pattern(self._pattern)
213             else:
214                 self._gf.pattern_generator.stop()