1 # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
 
   3 # SPDX-License-Identifier: MIT
 
   4 """PWMOut Class for NXP LPC4330"""
 
   6 from greatfet import GreatFET
 
   9     from microcontroller.pin import pwmOuts
 
  11     raise RuntimeError("No PWM outputs defined for this board") from ImportError
 
  14 # pylint: disable=unnecessary-pass
 
  15 class PWMError(IOError):
 
  16     """Base class for PWM errors."""
 
  21 # pylint: enable=unnecessary-pass
 
  23     """Pulse Width Modulation Output Class"""
 
  25     MAX_CYCLE_LEVEL = 1024
 
  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.
 
  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)
 
  39             PWMOut: PWMOut object.
 
  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.
 
  48         if variable_frequency:
 
  49             raise NotImplementedError("Variable Frequency is not currently supported.")
 
  56         self._open(pin, duty_cycle, frequency)
 
  61     def __exit__(self, t, value, traceback):
 
  64     def _open(self, pin, duty=0, freq=500):
 
  66         for pwmpair in pwmOuts:
 
  68                 self._channel = pwmpair[0]
 
  71         if self._channel is None:
 
  72             raise RuntimeError("No PWM channel found for this Pin")
 
  75         self.duty_cycle = duty
 
  80         self._set_enabled(True)
 
  83         """Deinit the GreatFET One PWM."""
 
  84         # pylint: disable=broad-except
 
  86             if self._channel is not None:
 
  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
 
  94                 "warning: failed to deinitialize pwm pin {0} due to: {1}\n".format(
 
  95                     self._channel, type(e).__name__
 
 101         # pylint: enable=broad-except
 
 103     def _is_deinited(self):
 
 104         if self._pattern is None:
 
 106                 "Object has been deinitialize and can no longer "
 
 107                 "be used. Create a new object."
 
 112     def _get_period(self):
 
 113         return 1.0 / self._get_frequency()
 
 115     def _set_period(self, period):
 
 116         """Get or set the PWM's output period in seconds.
 
 119             PWMError: if an I/O or OS error occurs.
 
 120             TypeError: if value type is not int or float.
 
 124         if not isinstance(period, (int, float)):
 
 125             raise TypeError("Invalid period type, should be int or float.")
 
 127         self._set_frequency(1.0 / period)
 
 129     period = property(_get_period, _set_period)
 
 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.
 
 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.
 
 141         return self._duty_cycle
 
 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.")
 
 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.")
 
 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)
 
 159         self._pattern = pattern
 
 160         self._duty_cycle = duty_cycle
 
 162             self._set_enabled(True)
 
 164     duty_cycle = property(_get_duty_cycle, _set_duty_cycle)
 
 166     def _get_frequency(self):
 
 167         return self._frequency
 
 169     def _set_frequency(self, frequency):
 
 170         """Get or set the PWM's output frequency in Hertz.
 
 173             PWMError: if an I/O or OS error occurs.
 
 174             TypeError: if value type is not int or float.
 
 178         if not isinstance(frequency, (int, float)):
 
 179             raise TypeError("Invalid frequency type, should be int or float.")
 
 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
 
 185     frequency = property(_get_frequency, _set_frequency)
 
 187     def _get_enabled(self):
 
 188         enabled = self._enable
 
 195         raise PWMError(None, 'Unknown enabled value: "%s"' % enabled)
 
 197     def _set_enabled(self, value):
 
 198         """Get or set the PWM's output enabled state.
 
 201             PWMError: if an I/O or OS error occurs.
 
 202             TypeError: if value type is not bool.
 
 206         if not isinstance(value, bool):
 
 207             raise TypeError("Invalid enabled type, should be string.")
 
 212                     self._gf.pattern_generator.scan_out_pattern(self._pattern)
 
 214                 self._gf.pattern_generator.stop()