From: Melissa LeBlanc-Williams Date: Mon, 25 Nov 2019 22:42:33 +0000 (-0800) Subject: Merge pull request #170 from hamishmb/fix-thread-safety X-Git-Tag: 3.0.2 X-Git-Url: https://git.ayoreis.com/Adafruit_Blinka-hackapet.git/commitdiff_plain/5d1689e9657a53c655171b8373de0f145eeba0a3?hp=76ca3e6a3c4c2f2695d96bdb0ee2db66d3f0f32c Merge pull request #170 from hamishmb/fix-thread-safety Add a lock for thread safety when the I2C class is used in a with statement --- diff --git a/src/adafruit_blinka/board/beaglebone_pocketbeagle.py b/src/adafruit_blinka/board/beaglebone_pocketbeagle.py index a56e5f7..fc0bb3a 100644 --- a/src/adafruit_blinka/board/beaglebone_pocketbeagle.py +++ b/src/adafruit_blinka/board/beaglebone_pocketbeagle.py @@ -40,10 +40,10 @@ P1_29 = pin.P1_29 # GPIO3_21 - GPIO_117 P1_30 = pin.P1_30 # UART0_TXD - GPIO_43 P1_31 = pin.P1_31 # GPIO3_18 - GPIO_114 P1_32 = pin.P1_32 # UART0_RXD - GPIO_42 -P1_33 = pin.P1_33 # GPIO3_15 - GPIO_111 +P1_33 = pin.P1_33 # GPIO3_15 - GPIO_111 - EHRPWM0B (ehrpwm.0:1) P1_34 = pin.P1_34 # GPIO0_26 - GPIO_26 P1_35 = pin.P1_35 # GPIO2_24 - GPIO_88 -P1_36 = pin.P1_36 # EHRPWM0A - GPIO_110 +P1_36 = pin.P1_36 # EHRPWM0A - GPIO_110 - EHRPWM0A (ehrpwm.0:0) P2_1 = pin.P2_1 # EHRPWM1A - GPIO_50 diff --git a/src/adafruit_blinka/microcontroller/am335x/pin.py b/src/adafruit_blinka/microcontroller/am335x/pin.py index e73457d..ca9d027 100644 --- a/src/adafruit_blinka/microcontroller/am335x/pin.py +++ b/src/adafruit_blinka/microcontroller/am335x/pin.py @@ -343,3 +343,10 @@ i2cPorts = ( (1, I2C1_SCL, I2C1_SDA), (2, I2C2_SCL, I2C2_SDA), ) + +PWM1 = P1_36 +PWM2 = P1_33 +PWM3 = P2_1 +PWM4 = P2_3 + +pwmOuts = ( ((0, 0), PWM1), ((0, 1), PWM2), ((2, 0), PWM3), ((4, 1), PWM4) ) diff --git a/src/adafruit_blinka/microcontroller/am335x/sysfs_pwmout.py b/src/adafruit_blinka/microcontroller/am335x/sysfs_pwmout.py new file mode 100644 index 0000000..70bab33 --- /dev/null +++ b/src/adafruit_blinka/microcontroller/am335x/sysfs_pwmout.py @@ -0,0 +1,287 @@ +# 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" + _unexport_path = "unexport" + _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.deinit() + + def __enter__(self): + return self + + def __exit__(self, t, value, traceback): + self.deinit() + + 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._channel,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) + + # 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 + + self._set_enabled(True) + + def deinit(self): + try: + """Deinit the sysfs PWM.""" + channel_path = os.path.join(self._sysfs_path, self._channel_path.format(self._channel)) + pin_path = os.path.join(channel_path, self._pin_path.format(self._channel,self._pwmpin)) + + if self._channel is not None: + #self.duty_cycle = 0 + self._set_enabled(False) # make to disable before unexport + try: + #unexport_path = os.path.join(channel_path, self._unexport_path) + with open(os.path.join(channel_path, self._unexport_path), "w") as f_unexport: + f_unexport.write("%d\n" % self._pwmpin) + except IOError as e: + raise PWMError(e.errno, "Unexporting PWM pin: " + e.strerror) + except Exception as e: + # due to a race condition for which I have not yet been + # able to find the root cause, deinit() often fails + # but it does not effect future usage of the pwm pin + print("warning: failed to deinitialize pwm pin {0}:{1} due to: {2}\n".format(self._channel, self._pwmpin, type(e).__name__)) + finally: + self._channel = None + self._pwmpin = None + + def _is_deinited(self): + if self._pwmpin is None: + raise ValueError("Object has been deinitialize and can no longer " + "be used. Create a new object.") + + def _write_pin_attr(self, attr, value): + # Make sure the pin is active + self._is_deinited() + + path = os.path.join( + self._sysfs_path, + self._channel_path.format(self._channel), + self._pin_path.format(self._channel,self._pwmpin), + attr) + + with open(path, 'w') as f_attr: + f_attr.write(value + "\n") + + def _read_pin_attr(self, attr): + # Make sure the pin is active + self._is_deinited() + + path = os.path.join( + self._sysfs_path, + self._channel_path.format(self._channel), + self._pin_path.format(self._channel,self._pwmpin), + attr) + + with open(path, 'r') as f_attr: + return f_attr.read().strip() + + # Mutable properties + + def _get_period(self): + period_ns = self._read_pin_attr(self._pin_period_path) + try: + period_ns = int(period_ns) + 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) + + period = property(_get_period, _set_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): + duty_cycle_ns = self._read_pin_attr(self._pin_duty_cycle_path) + try: + duty_cycle_ns = int(duty_cycle_ns) + 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): + if not isinstance(duty_cycle, (int, float)): + raise TypeError("Invalid duty cycle type, should be int or float.") + + # convert from 16-bit + duty_cycle /= 65535.0 + if 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/ft232h/spi.py b/src/adafruit_blinka/microcontroller/ft232h/spi.py index 2d42ae0..a260e72 100644 --- a/src/adafruit_blinka/microcontroller/ft232h/spi.py +++ b/src/adafruit_blinka/microcontroller/ft232h/spi.py @@ -17,7 +17,12 @@ class SPI: def init(self, baudrate=100000, polarity=0, phase=0, bits=8, firstbit=MSB, sck=None, mosi=None, miso=None): self._port.set_frequency(baudrate) + # FTDI device can only support mode 0 and mode 2 + # due to the limitation of MPSSE engine. + # This means CPHA must = 0 self._port._cpol = polarity + if phase != 0: + raise ValueError("Only SPI phase 0 is supported by FT232H.") self._port._cpha = phase @property diff --git a/src/adafruit_blinka/microcontroller/generic_linux/spi.py b/src/adafruit_blinka/microcontroller/generic_linux/spi.py index a93c46b..f2ffa82 100755 --- a/src/adafruit_blinka/microcontroller/generic_linux/spi.py +++ b/src/adafruit_blinka/microcontroller/generic_linux/spi.py @@ -33,15 +33,8 @@ class SPI: self.chip = detector.chip def set_no_cs(self): - # Linux SPI driver for AM33XX chip in BeagleBone and PocketBeagle - # does not support setting SPI_NO_CS mode bit (issue #104) - if not self.chip.AM33XX and not self.chip.IMX8MX and not self.chip.SAMA5 \ - and not self.chip.APQ8016 and not self.chip.T210 and not self.chip.T186 \ - and not self.chip.T194 and not self.chip.SUN8I: - try: - self._spi.no_cs = True # this doesn't work but try anyways - except AttributeError: - pass + # No kernel seems to support this, so we're just going to pass + pass @property def frequency(self): diff --git a/src/busio.py b/src/busio.py index 73c88ef..f71871b 100755 --- a/src/busio.py +++ b/src/busio.py @@ -33,7 +33,7 @@ class I2C(Lockable): self._i2c = _I2C(portId, mode=_I2C.MASTER, baudrate=frequency) break else: - raise NotImplementedError( + raise ValueError( "No Hardware I2C on (scl,sda)={}\nValid I2C ports: {}".format((scl, sda), i2cPorts) ) @@ -101,7 +101,7 @@ class SPI(Lockable): self._pins = (portSck, portMosi, portMiso) break else: - raise NotImplementedError( + raise ValueError( "No Hardware SPI on (SCLK, MOSI, MISO)={}\nValid SPI ports:{}". format((clock, MOSI, MISO), spiPorts)) @@ -236,7 +236,7 @@ class UART(Lockable): ) break else: - raise NotImplementedError( + raise ValueError( "No Hardware UART on (tx,rx)={}\nValid UART ports: {}".format((tx, rx), uartPorts) ) diff --git a/src/pulseio.py b/src/pulseio.py index 42524fc..bb8a04b 100644 --- a/src/pulseio.py +++ b/src/pulseio.py @@ -6,3 +6,5 @@ if detector.board.any_coral_board: from adafruit_blinka.microcontroller.generic_linux.sysfs_pwmout import PWMOut if detector.board.any_giant_board: from adafruit_blinka.microcontroller.generic_linux.sysfs_pwmout import PWMOut +if detector.board.any_beaglebone: + from adafruit_blinka.microcontroller.am335x.sysfs_pwmout import PWMOut