From: ladyada Date: Sun, 12 May 2019 20:28:25 +0000 (-0400) Subject: add a periphery-based sysfs pwmout object - tested with LED and servo X-Git-Tag: 1.3.1^2 X-Git-Url: https://git.ayoreis.com/Adafruit_Blinka-hackapet.git/commitdiff_plain/0b9e819a6206fe0ea87673d27fc6e3293f3e1809?hp=-c add a periphery-based sysfs pwmout object - tested with LED and servo --- 0b9e819a6206fe0ea87673d27fc6e3293f3e1809 diff --git a/src/adafruit_blinka/board/coral_edge_tpu.py b/src/adafruit_blinka/board/coral_edge_tpu.py index 4f296ef..f7b27a6 100644 --- a/src/adafruit_blinka/board/coral_edge_tpu.py +++ b/src/adafruit_blinka/board/coral_edge_tpu.py @@ -5,6 +5,9 @@ from adafruit_blinka.microcontroller.nxp_imx8m import pin SDA = pin.I2C2_SDA SCL = pin.I2C2_SCL +PWM1 = pin.PWM1 +PWM2 = pin.PWM2 +PWM3 = pin.PWM3 GPIO_P13 = pin.GPIO6 GPIO_P16 = pin.GPIO73 diff --git a/src/adafruit_blinka/microcontroller/generic_linux/sysfs_pwmout.py b/src/adafruit_blinka/microcontroller/generic_linux/sysfs_pwmout.py new file mode 100644 index 0000000..c966b41 --- /dev/null +++ b/src/adafruit_blinka/microcontroller/generic_linux/sysfs_pwmout.py @@ -0,0 +1,254 @@ +# Much code from https://github.com/vsergeev/python-periphery/blob/master/periphery/pwm.py +# Copyright (c) 2015-2016 vsergeev / Ivan (Vanya) A. Sergeev +# License: MIT + +import os +import digitalio + +try: + from microcontroller.pin import pwmOuts +except ImportError: + raise RuntimeError("No PWM outputs defined for this board") + +class PWMError(IOError): + """Base class for PWM errors.""" + pass + + +class PWMOut(object): + # Sysfs paths + _sysfs_path = "/sys/class/pwm/" + _channel_path = "pwmchip{}" + + # Channel paths + _export_path = "export" + _pin_path = "pwm{}" + + # Pin attribute paths + _pin_period_path = "period" + _pin_duty_cycle_path = "duty_cycle" + _pin_polarity_path = "polarity" + _pin_enable_path = "enable" + + def __init__(self, pin, *, frequency=500, duty_cycle=0, variable_frequency=False): + """Instantiate a PWM object and open the sysfs PWM corresponding to the + specified channel and pin. + + Args: + pin (Pin): CircuitPython Pin object to output to + duty_cycle (int) : The fraction of each pulse which is high. 16-bit + frequency (int) : target frequency in Hertz (32-bit) + variable_frequency (bool) : True if the frequency will change over time + + Returns: + PWMOut: PWMOut object. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if `channel` or `pin` types are invalid. + ValueError: if PWM channel does not exist. + + """ + + self._pwmpin = None + self._open(pin, duty_cycle, frequency, variable_frequency) + + def __del__(self): + self.close() + + def __enter__(self): + return self + + def __exit__(self, t, value, traceback): + self.close() + + def _open(self, pin, duty=0, freq=500, variable_frequency=False): + self._channel = None + for pwmpair in pwmOuts: + if pwmpair[1] == pin: + self._channel = pwmpair[0][0] + self._pwmpin = pwmpair[0][1] + + self._pin = pin + if self._channel is None: + raise RuntimeError("No PWM channel found for this Pin") + + channel_path = os.path.join(self._sysfs_path, self._channel_path.format(self._channel)) + if not os.path.isdir(channel_path): + raise ValueError("PWM channel does not exist, check that the required modules are loaded.") + + pin_path = os.path.join(channel_path, self._pin_path.format(self._pwmpin)) + if not os.path.isdir(pin_path): + try: + with open(os.path.join(channel_path, self._export_path), "w") as f_export: + f_export.write("%d\n" % self._pwmpin) + except IOError as e: + raise PWMError(e.errno, "Exporting PWM pin: " + e.strerror) + + self._set_enabled(True) + + # Look up the period, for fast duty cycle updates + self._period = self._get_period() + + # set frequency + self.frequency = freq + # set duty + self.duty_cycle = duty + + def close(self): + """Close the sysfs PWM.""" + self._channel = None + self._pwmpin = None + + def _write_pin_attr(self, attr, value): + path = os.path.join( + self._sysfs_path, + self._channel_path.format(self._channel), + self._pin_path.format(self._pwmpin), + attr) + + with open(path, 'w') as f_attr: + #print(value, path) + f_attr.write(value + "\n") + + def _read_pin_attr(self, attr): + path = os.path.join( + self._sysfs_path, + self._channel_path.format(self._channel), + self._pin_path.format(self._pwmpin), + attr) + + with open(path, 'r') as f_attr: + return f_attr.read().strip() + + # Mutable properties + + def _get_period(self): + try: + period_ns = int(self._read_pin_attr(self._pin_period_path)) + except ValueError: + raise PWMError(None, "Unknown period value: \"%s\"" % period_ns) + + # Convert period from nanoseconds to seconds + period = period_ns / 1e9 + + # Update our cached period + self._period = period + + return period + + def _set_period(self, period): + if not isinstance(period, (int, float)): + raise TypeError("Invalid period type, should be int or float.") + + # Convert period from seconds to integer nanoseconds + period_ns = int(period * 1e9) + + self._write_pin_attr(self._pin_period_path, "{}".format(period_ns)) + + # Update our cached period + self._period = float(period) + + """Get or set the PWM's output period in seconds. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not int or float. + + :type: int, float + """ + + def _get_duty_cycle(self): + try: + duty_cycle_ns = int(self._read_pin_attr(self._pin_duty_cycle_path)) + except ValueError: + raise PWMError(None, "Unknown duty cycle value: \"%s\"" % duty_cycle_ns) + + # Convert duty cycle from nanoseconds to seconds + duty_cycle = duty_cycle_ns / 1e9 + + # Convert duty cycle to ratio from 0.0 to 1.0 + duty_cycle = duty_cycle / self._period + + # convert to 16-bit + duty_cycle = int(duty_cycle * 65535) + + return duty_cycle + + def _set_duty_cycle(self, duty_cycle): + # convert from 16-bit + duty_cycle /= 65535 + + if not isinstance(duty_cycle, (int, float)): + raise TypeError("Invalid duty cycle type, should be int or float.") + elif not 0.0 <= duty_cycle <= 1.0: + raise ValueError("Invalid duty cycle value, should be between 0.0 and 1.0.") + + # Convert duty cycle from ratio to seconds + duty_cycle = duty_cycle * self._period + + # Convert duty cycle from seconds to integer nanoseconds + duty_cycle_ns = int(duty_cycle * 1e9) + + self._write_pin_attr(self._pin_duty_cycle_path, "{}".format(duty_cycle_ns)) + + duty_cycle = property(_get_duty_cycle, _set_duty_cycle) + """Get or set the PWM's output duty cycle as a ratio from 0.0 to 1.0. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not int or float. + ValueError: if value is out of bounds of 0.0 to 1.0. + + :type: int, float + """ + + def _get_frequency(self): + return 1.0 / self._get_period() + + def _set_frequency(self, frequency): + if not isinstance(frequency, (int, float)): + raise TypeError("Invalid frequency type, should be int or float.") + + self._set_period(1.0 / frequency) + + frequency = property(_get_frequency, _set_frequency) + """Get or set the PWM's output frequency in Hertz. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not int or float. + + :type: int, float + """ + + def _get_enabled(self): + enabled = self._read_pin_attr(self._pin_enable_path) + + if enabled == "1": + return True + elif enabled == "0": + return False + + raise PWMError(None, "Unknown enabled value: \"%s\"" % enabled) + + def _set_enabled(self, value): + if not isinstance(value, bool): + raise TypeError("Invalid enabled type, should be string.") + + self._write_pin_attr(self._pin_enable_path, "1" if value else "0") + + """Get or set the PWM's output enabled state. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not bool. + + :type: bool + """ + + # String representation + + def __str__(self): + return "PWM%d, pin %s (freq=%f Hz, duty_cycle=%f%%)" % \ + (self._channel, self._pin, self.frequency, self.duty_cycle * 100,) diff --git a/src/adafruit_blinka/microcontroller/nxp_imx8m/pin.py b/src/adafruit_blinka/microcontroller/nxp_imx8m/pin.py index 305149a..98eca0e 100644 --- a/src/adafruit_blinka/microcontroller/nxp_imx8m/pin.py +++ b/src/adafruit_blinka/microcontroller/nxp_imx8m/pin.py @@ -7,6 +7,10 @@ I2C3_SCL = Pin(146) # GPIO5_IO18 I2C3_SDA = Pin(147) # GPIO5_IO19 +PWM1 = Pin((0, 1)) # GPIO1_IO01 +PWM2 = Pin((0, 13)) # GPIO1_IO13 +PWM3 = Pin((0, 14)) # GPIO1_IO14 + GPIO6 = Pin((0, 6)) # GPIO1_IO6 GPIO7 = Pin((0, 7)) # GPIO1_IO7 GPIO8 = Pin((0, 8)) # GPIO1_IO8 @@ -24,6 +28,8 @@ ECSPI1_SS0 = Pin(133) # GPIO5_IO9 i2cPorts = ( (1, I2C2_SCL, I2C2_SDA), (2, I2C3_SCL, I2C3_SDA),) # ordered as spiId, sckId, mosiId, misoId spiPorts = ( (32766, ECSPI1_SCLK, ECSPI1_MOSI, ECSPI1_MISO), ) +# SysFS pwm outputs, pwm channel and pin in first tuple +pwmOuts = ( ((0, 0), PWM1), ((1, 0), PWM2), ((2, 0), PWM3), ) # UART1_TXD/RXD on /dev/ttymxc0 # UART3_TXD/RXD not available (?) diff --git a/src/pulseio.py b/src/pulseio.py index 2d3f0b5..76d0b91 100644 --- a/src/pulseio.py +++ b/src/pulseio.py @@ -2,3 +2,5 @@ from adafruit_blinka.agnostic import detector if detector.board.any_raspberry_pi: from adafruit_blinka.microcontroller.bcm283x.pulseio.PulseIn import PulseIn +if detector.board.any_coral_board: + from adafruit_blinka.microcontroller.generic_linux.sysfs_pwmout import PWMOut