1 """PWMOut Class for NXP LPC4330"""
 
   3 from greatfet import GreatFET
 
   6     from microcontroller.pin import pwmOuts
 
   8     raise RuntimeError("No PWM outputs defined for this board") from ImportError
 
  10 # pylint: disable=unnecessary-pass
 
  11 class PWMError(IOError):
 
  12     """Base class for PWM errors."""
 
  17 # pylint: enable=unnecessary-pass
 
  19     """Pulse Width Modulation Output Class"""
 
  21     MAX_CYCLE_LEVEL = 1024
 
  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.
 
  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)
 
  35             PWMOut: PWMOut object.
 
  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.
 
  44         if variable_frequency:
 
  45             raise NotImplementedError("Variable Frequency is not currently supported.")
 
  52         self._open(pin, duty_cycle, frequency)
 
  57     def __exit__(self, t, value, traceback):
 
  60     def _open(self, pin, duty=0, freq=500):
 
  62         for pwmpair in pwmOuts:
 
  64                 self._channel = pwmpair[0]
 
  67         if self._channel is None:
 
  68             raise RuntimeError("No PWM channel found for this Pin")
 
  71         self.duty_cycle = duty
 
  76         self._set_enabled(True)
 
  79         """Deinit the GreatFET One PWM."""
 
  80         # pylint: disable=broad-except
 
  82             if self._channel is not None:
 
  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
 
  90                 "warning: failed to deinitialize pwm pin {0} due to: {1}\n".format(
 
  91                     self._channel, type(e).__name__
 
  97         # pylint: enable=broad-except
 
  99     def _is_deinited(self):
 
 100         if self._pattern is None:
 
 102                 "Object has been deinitialize and can no longer "
 
 103                 "be used. Create a new object."
 
 108     def _get_period(self):
 
 109         return 1.0 / self._get_frequency()
 
 111     def _set_period(self, period):
 
 112         """Get or set the PWM's output period in seconds.
 
 115             PWMError: if an I/O or OS error occurs.
 
 116             TypeError: if value type is not int or float.
 
 120         if not isinstance(period, (int, float)):
 
 121             raise TypeError("Invalid period type, should be int or float.")
 
 123         self._set_frequency(1.0 / period)
 
 125     period = property(_get_period, _set_period)
 
 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.
 
 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.
 
 137         return self._duty_cycle
 
 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.")
 
 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.")
 
 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)
 
 155         self._pattern = pattern
 
 156         self._duty_cycle = duty_cycle
 
 158             self._set_enabled(True)
 
 160     duty_cycle = property(_get_duty_cycle, _set_duty_cycle)
 
 162     def _get_frequency(self):
 
 163         return self._frequency
 
 165     def _set_frequency(self, frequency):
 
 166         """Get or set the PWM's output frequency in Hertz.
 
 169             PWMError: if an I/O or OS error occurs.
 
 170             TypeError: if value type is not int or float.
 
 174         if not isinstance(frequency, (int, float)):
 
 175             raise TypeError("Invalid frequency type, should be int or float.")
 
 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
 
 181     frequency = property(_get_frequency, _set_frequency)
 
 183     def _get_enabled(self):
 
 184         enabled = self._enable
 
 191         raise PWMError(None, 'Unknown enabled value: "%s"' % enabled)
 
 193     def _set_enabled(self, value):
 
 194         """Get or set the PWM's output enabled state.
 
 197             PWMError: if an I/O or OS error occurs.
 
 198             TypeError: if value type is not bool.
 
 202         if not isinstance(value, bool):
 
 203             raise TypeError("Invalid enabled type, should be string.")
 
 208                     self._gf.pattern_generator.scan_out_pattern(self._pattern)
 
 210                 self._gf.pattern_generator.stop()