]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/adafruit_blinka/microcontroller/nova/pwmout.py
Adafuit uses 7-bit address, shift address to make it 8-bit for binho nova writeto_the...
[Adafruit_Blinka-hackapet.git] / src / adafruit_blinka / microcontroller / nova / pwmout.py
1 import os
2 import digitalio
3
4 try:
5     from microcontroller.pin import pwmOuts
6 except ImportError:
7     raise RuntimeError("No PWM outputs defined for this board")
8
9 from microcontroller.pin import Pin
10
11 class PWMError(IOError):
12     """Base class for PWM errors."""
13     pass
14
15
16 class PWMOut(object):
17     # Nova instance
18     _nova = None
19     MAX_CYCLE_LEVEL = 1024
20
21     def __init__(self, pin, *, frequency=750, duty_cycle=0, variable_frequency=False):
22         """Instantiate a PWM object and open the sysfs PWM corresponding to the
23         specified channel and pin.
24
25         Args:
26             pin (Pin): CircuitPython Pin object to output to
27             duty_cycle (int) : The fraction of each pulse which is high. 16-bit
28             frequency (int) : target frequency in Hertz (32-bit)
29             variable_frequency (bool) : True if the frequency will change over time
30
31         Returns:
32             PWMOut: PWMOut object.
33
34         Raises:
35             PWMError: if an I/O or OS error occurs.
36             TypeError: if `channel` or `pin` types are invalid.
37             ValueError: if PWM channel does not exist.
38
39         """
40         if PWMOut._nova is None:
41             from adafruit_blinka.microcontroller.nova import Connection
42             PWMOut._nova = Connection.getInstance()
43
44         self._pwmpin = None
45         self._open(pin, duty_cycle, frequency, variable_frequency)
46
47     def __del__(self):
48         self.deinit()
49
50     def __enter__(self):
51         return self
52
53     def __exit__(self, t, value, traceback):
54         self.deinit()
55
56     def _open(self, pin, duty=0, freq=750, variable_frequency=False):
57         self._channel = None
58         for pwmpair in pwmOuts:
59             if pwmpair[1] == pin:
60                 self._channel = pwmpair[0][0]
61                 self._pwmpin = pwmpair[0][1]
62
63         self._pin = pin
64         if self._channel is None:
65             raise RuntimeError("No PWM channel found for this Pin")
66
67         PWMOut._nova.setIOpinMode(self._pwmpin, Pin.PWM)
68
69         # set frequency
70         self.frequency = freq
71         # set period
72         self._period = self._get_period()
73
74         # set duty
75         self.duty_cycle = duty
76
77         self._set_enabled(True)
78
79     def deinit(self):
80       try:
81         """Deinit the Nova PWM."""
82         if self._channel is not None:
83             #self.duty_cycle = 0
84             self._set_enabled(False) # make to disable before unexport
85
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("warning: failed to deinitialize pwm pin {0}:{1} due to: {2}\n".format(self._channel, self._pwmpin, type(e).__name__))
91       finally:
92           self._channel = None
93           self._pwmpin = None
94
95     def _is_deinited(self):
96         if self._pwmpin is None:
97             raise ValueError("Object has been deinitialize and can no longer "
98                              "be used. Create a new object.")
99
100     # Mutable properties
101
102     def _get_period(self):
103         return 1.0 / self._get_frequency()
104
105     def _set_period(self, period):
106         if not isinstance(period, (int, float)):
107             raise TypeError("Invalid period type, should be int or float.")
108
109         self._set_frequency(1.0 / period)
110
111     period = property(_get_period, _set_period)
112
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
122     def _get_duty_cycle(self):
123         duty_cycle = Pin._nova.getIOpinValue(self._pwmpin)
124
125         # Convert duty cycle to ratio from 0.0 to 1.0
126         duty_cycle = duty_cycle / PWMOut.MAX_CYCLE_LEVEL
127
128         # convert to 16-bit
129         duty_cycle = int(duty_cycle * 65535)
130         return duty_cycle
131
132     def _set_duty_cycle(self, duty_cycle):
133         if not isinstance(duty_cycle, (int, float)):
134             raise TypeError("Invalid duty cycle type, should be int or float.")
135
136         # convert from 16-bit
137         duty_cycle /= 65535.0
138         if not 0.0 <= duty_cycle <= 1.0:
139             raise ValueError("Invalid duty cycle value, should be between 0.0 and 1.0.")
140
141         # Convert duty cycle from ratio to 1024 levels
142         duty_cycle = duty_cycle * PWMOut.MAX_CYCLE_LEVEL
143
144         # Set duty cycle
145         Pin._nova.setIOpinValue(self._pwmpin, duty_cycle)
146
147     duty_cycle = property(_get_duty_cycle, _set_duty_cycle)
148     """Get or set the PWM's output duty cycle as a ratio from 0.0 to 1.0.
149
150     Raises:
151         PWMError: if an I/O or OS error occurs.
152         TypeError: if value type is not int or float.
153         ValueError: if value is out of bounds of 0.0 to 1.0.
154
155     :type: int, float
156     """
157
158     def _get_frequency(self):
159         return int(PWMOut._nova.getIOpinPWMFreq(self._pwmpin).split('PWMFREQ ')[1])
160
161     def _set_frequency(self, frequency):
162         if not isinstance(frequency, (int, float)):
163             raise TypeError("Invalid frequency type, should be int or float.")
164
165         PWMOut._nova.setIOpinPWMFreq(self._pwmpin, frequency)
166
167     frequency = property(_get_frequency, _set_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
177     def _get_enabled(self):
178         enabled = self._enable
179
180         if enabled == "1":
181             return True
182         elif enabled == "0":
183             return False
184
185         raise PWMError(None, "Unknown enabled value: \"%s\"" % enabled)
186
187     def _set_enabled(self, value):
188         if not isinstance(value, bool):
189             raise TypeError("Invalid enabled type, should be string.")
190         self._enable = value
191         if not self._enable:
192             self._set_duty_cycle(0.0)
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
202     # String representation
203
204     def __str__(self):
205         return "PWM%d, pin %s (freq=%f Hz, duty_cycle=%f%%)" % \
206             (self._pin, self._pin, self.frequency, self.duty_cycle * 100,)