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
 
  13 # pylint: disable=unnecessary-pass
 
  14 class PWMError(IOError):
 
  15     """Base class for PWM errors."""
 
  20 # pylint: enable=unnecessary-pass
 
  22     """Pulse Width Modulation Output Class"""
 
  24     MAX_CYCLE_LEVEL = 1024
 
  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.
 
  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)
 
  38             PWMOut: PWMOut object.
 
  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.
 
  47         if variable_frequency:
 
  48             raise NotImplementedError("Variable Frequency is not currently supported.")
 
  55         self._open(pin, duty_cycle, frequency)
 
  60     def __exit__(self, t, value, traceback):
 
  63     def _open(self, pin, duty=0, freq=500):
 
  65         for pwmpair in pwmOuts:
 
  67                 self._channel = pwmpair[0]
 
  70         if self._channel is None:
 
  71             raise RuntimeError("No PWM channel found for this Pin")
 
  74         self.duty_cycle = duty
 
  79         self._set_enabled(True)
 
  82         """Deinit the GreatFET One PWM."""
 
  83         # pylint: disable=broad-except
 
  85             if self._channel is not None:
 
  87                 self._set_enabled(False)
 
  88         except Exception as e:
 
  89             # due to a race condition for which I have not yet been
 
  90             # able to find the root cause, deinit() often fails
 
  91             # but it does not effect future usage of the pwm pin
 
  93                 "warning: failed to deinitialize pwm pin {0} due to: {1}\n".format(
 
  94                     self._channel, type(e).__name__
 
 100         # pylint: enable=broad-except
 
 102     def _is_deinited(self):
 
 103         if self._pattern is None:
 
 105                 "Object has been deinitialize and can no longer "
 
 106                 "be used. Create a new object."
 
 111     def _get_period(self):
 
 112         return 1.0 / self._get_frequency()
 
 114     def _set_period(self, period):
 
 115         """Get or set the PWM's output period in seconds.
 
 118             PWMError: if an I/O or OS error occurs.
 
 119             TypeError: if value type is not int or float.
 
 123         if not isinstance(period, (int, float)):
 
 124             raise TypeError("Invalid period type, should be int or float.")
 
 126         self._set_frequency(1.0 / period)
 
 128     period = property(_get_period, _set_period)
 
 130     def _get_duty_cycle(self):
 
 131         """Get or set the PWM's output duty cycle as a ratio from 0.0 to 1.0.
 
 134            PWMError: if an I/O or OS error occurs.
 
 135            TypeError: if value type is not int or float.
 
 136            ValueError: if value is out of bounds of 0.0 to 1.0.
 
 140         return self._duty_cycle
 
 142     def _set_duty_cycle(self, duty_cycle):
 
 143         if not isinstance(duty_cycle, (int, float)):
 
 144             raise TypeError("Invalid duty cycle type, should be int or float.")
 
 146         # convert from 16-bit
 
 147         if isinstance(duty_cycle, int):
 
 148             duty_cycle /= 65535.0
 
 149         if not 0.0 <= duty_cycle <= 1.0:
 
 150             raise ValueError("Invalid duty cycle value, should be between 0.0 and 1.0.")
 
 152         # Generate a pattern for 1024 samples of the duty cycle
 
 153         pattern = [(1 << self._channel)] * round(PWMOut.MAX_CYCLE_LEVEL * duty_cycle)
 
 154         pattern += [(0 << self._channel)] * round(
 
 155             PWMOut.MAX_CYCLE_LEVEL * (1.0 - duty_cycle)
 
 158         self._pattern = pattern
 
 159         self._duty_cycle = duty_cycle
 
 161             self._set_enabled(True)
 
 163     duty_cycle = property(_get_duty_cycle, _set_duty_cycle)
 
 165     def _get_frequency(self):
 
 166         return self._frequency
 
 168     def _set_frequency(self, frequency):
 
 169         """Get or set the PWM's output frequency in Hertz.
 
 172             PWMError: if an I/O or OS error occurs.
 
 173             TypeError: if value type is not int or float.
 
 177         if not isinstance(frequency, (int, float)):
 
 178             raise TypeError("Invalid frequency type, should be int or float.")
 
 180         # We are sending 1024 samples per second already
 
 181         self._gf.pattern_generator.set_sample_rate(frequency * len(self._pattern))
 
 182         self._frequency = frequency
 
 184     frequency = property(_get_frequency, _set_frequency)
 
 186     def _get_enabled(self):
 
 187         enabled = self._enable
 
 194         raise PWMError(None, 'Unknown enabled value: "%s"' % enabled)
 
 196     def _set_enabled(self, value):
 
 197         """Get or set the PWM's output enabled state.
 
 200             PWMError: if an I/O or OS error occurs.
 
 201             TypeError: if value type is not bool.
 
 205         if not isinstance(value, bool):
 
 206             raise TypeError("Invalid enabled type, should be string.")
 
 211                     self._gf.pattern_generator.scan_out_pattern(self._pattern)
 
 213                 self._gf.pattern_generator.stop()