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