]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/adafruit_blinka/microcontroller/nxp_lpc4330/pwmout.py
08a31b772f99af2e5322e812ee05526fa4a64e9a
[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     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 NotImplemented("Variable Frequency is not currently supported.")
49
50         self._pattern = None
51         self._channel = None
52         self._enable = False
53         self._open(pin, duty_cycle, frequency)
54
55     def __enter__(self):
56         return self
57
58     def __exit__(self, t, value, traceback):
59         self.deinit()
60
61     def _open(self, pin, duty=0, freq=500):
62         self._channel = None
63         for pwmpair in pwmOuts:
64             if pwmpair[1] == pin:
65                 self._channel = pwmpair[0]
66
67         self._pin = pin
68         if self._channel is None:
69             raise RuntimeError("No PWM channel found for this Pin")
70
71         # set duty
72         self.duty_cycle = duty
73
74         # set frequency
75         self.frequency = freq
76
77         self._set_enabled(True)
78
79     def deinit(self):
80         """Deinit the GreatFET One PWM."""
81         # pylint: disable=broad-except
82         try:
83             if self._channel is not None:
84                 # self.duty_cycle = 0
85                 self._set_enabled(False)
86         except Exception as e:
87             # due to a race condition for which I have not yet been
88             # able to find the root cause, deinit() often fails
89             # but it does not effect future usage of the pwm pin
90             print(
91                 "warning: failed to deinitialize pwm pin {0} due to: {1}\n".format(
92                     self._channel, type(e).__name__
93                 )
94             )
95         finally:
96             self._pattern = None
97             self._channel = None
98         # pylint: enable=broad-except
99
100     def _is_deinited(self):
101         if self._pattern is None:
102             raise ValueError(
103                 "Object has been deinitialize and can no longer "
104                 "be used. Create a new object."
105             )
106
107     # Mutable properties
108
109     def _get_period(self):
110         return 1.0 / self._get_frequency()
111
112     def _set_period(self, period):
113         """Get or set the PWM's output period in seconds.
114
115         Raises:
116             PWMError: if an I/O or OS error occurs.
117             TypeError: if value type is not int or float.
118
119         :type: int, float
120         """
121         if not isinstance(period, (int, float)):
122             raise TypeError("Invalid period type, should be int or float.")
123
124         self._set_frequency(1.0 / period)
125
126     period = property(_get_period, _set_period)
127
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(PWMOut.MAX_CYCLE_LEVEL * (1.0 - duty_cycle))
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 int(PWMOut._nova.getIOpinPWMFreq(self._pwmpin).split("PWMFREQ ")[1])
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
180     frequency = property(_get_frequency, _set_frequency)
181
182     def _get_enabled(self):
183         enabled = self._enable
184
185         if enabled == "1":
186             return True
187         if enabled == "0":
188             return False
189
190         raise PWMError(None, 'Unknown enabled value: "%s"' % enabled)
191
192     def _set_enabled(self, value):
193         """Get or set the PWM's output enabled state.
194
195         Raises:
196             PWMError: if an I/O or OS error occurs.
197             TypeError: if value type is not bool.
198
199         :type: bool
200         """
201         if not isinstance(value, bool):
202             raise TypeError("Invalid enabled type, should be string.")
203         self._enable = value
204         if self._gf:
205             if self._enable:
206                 if self._pattern:
207                     self._gf.pattern_generator.scan_out_pattern(self._pattern)
208             else:
209                 self._gf.pattern_generator.stop()