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