From: Melissa LeBlanc-Williams Date: Tue, 3 Dec 2019 17:45:31 +0000 (-0800) Subject: Merge pull request #191 from binhollc/master X-Git-Tag: 3.1.0 X-Git-Url: https://git.ayoreis.com/Adafruit_Blinka-hackapet.git/commitdiff_plain/4376a296bdc5eca8e1c47d51219dbe173b217cd5?hp=a8662a122f5985fc07bac4046d450dcb5e1854f6 Merge pull request #191 from binhollc/master Add Binho Nova integration --- diff --git a/requirements.txt b/requirements.txt index d9d85f6..0ddfbe8 100755 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ rpi_ws281x>=4.0.0; platform_machine=='armv7l' or platform_machine=='armv6l' spidev>=3.4; sys_platform == 'linux' sysv_ipc; sys_platform == 'linux' pyftdi>=0.30.0 +binho-host-adapter>=0.1.4 \ No newline at end of file diff --git a/src/adafruit_blinka/board/binho_nova.py b/src/adafruit_blinka/board/binho_nova.py new file mode 100644 index 0000000..0dfe4a4 --- /dev/null +++ b/src/adafruit_blinka/board/binho_nova.py @@ -0,0 +1,20 @@ +from adafruit_blinka.microcontroller.nova import pin + +IO0 = pin.IO0 +IO1 = pin.IO1 +IO2 = pin.IO2 +IO3 = pin.IO3 +IO4 = pin.IO4 + +TX = IO4 +RX = IO3 + +SDA = pin.SDA +SCL = pin.SCL + +SCK = pin.SCK +SCLK = pin.SCLK +MOSI = pin.MOSI +MISO = pin.MISO +SS0 = pin.SS0 +SS1 = pin.SS1 diff --git a/src/adafruit_blinka/microcontroller/nova/__init__.py b/src/adafruit_blinka/microcontroller/nova/__init__.py new file mode 100644 index 0000000..12ccf0c --- /dev/null +++ b/src/adafruit_blinka/microcontroller/nova/__init__.py @@ -0,0 +1,24 @@ +class Connection: + __instance = None + @staticmethod + def getInstance(): + """ Static access method. """ + if Connection.__instance is None: + Connection() + return Connection.__instance + + def __init__(self): + """ Virtually private constructor. """ + if Connection.__instance is not None: + raise Exception("This class is a singleton!") + + from binhoHostAdapter import binhoHostAdapter + from binhoHostAdapter import binhoUtilities + + utilities = binhoUtilities.binhoUtilities() + devices = utilities.listAvailableDevices() + + if len(devices) > 0: + Connection.__instance = binhoHostAdapter.binhoHostAdapter(devices[0]) + else: + raise RuntimeError('No Binho Nova found!') diff --git a/src/adafruit_blinka/microcontroller/nova/i2c.py b/src/adafruit_blinka/microcontroller/nova/i2c.py new file mode 100644 index 0000000..7f08062 --- /dev/null +++ b/src/adafruit_blinka/microcontroller/nova/i2c.py @@ -0,0 +1,75 @@ +class I2C: + + def __init__(self): + from adafruit_blinka.microcontroller.nova import Connection + self._nova = Connection.getInstance() + self._nova.setNumericalBase(10) + self._nova.setOperationMode(0, "I2C") + self._nova.setPullUpStateI2C(0, "EN") + self._nova.setClockI2C(0, 400000) + + def scan(self): + + scanResults = [] + + for i in range(8, 121): + result = self._nova.scanAddrI2C(0, i<<1) + + resp = result.split(" ") + + if resp[3] == 'OK': + scanResults.append(i) + + return scanResults + + def writeto(self, address, buffer, *, start=0, end=None, stop=True): + + end = end if end else len(buffer) + + self._nova.startI2C(0, address<<1) + + for i in range(start, end): + self._nova.writeByteI2C(0, buffer[i]) + + if stop: + self._nova.endI2C(0) + else: + self._nova.endI2C(0, True) + + def readfrom_into(self, address, buffer, *, start=0, end=None, stop=True): + + end = end if end else len(buffer) + + result = self._nova.readBytesI2C(0, address<<1, len(buffer[start:end])) + + if result != "-NG": + resp = result.split(" ") + + for i in range(len(buffer[start:end])): + buffer[start+i] = int(resp[2+i]) + else: + raise RuntimeError("Received error response from Binho Nova, result = " + result) + + def writeto_then_readfrom(self, address, buffer_out, buffer_in, *, + out_start=0, out_end=None, + in_start=0, in_end=None, stop=False): + + out_end = out_end if out_end else len(buffer_out) + in_end = in_end if in_end else len(buffer_in) + + self._nova.startI2C(0, address<<1) + + for i in range(out_start, out_end): + self._nova.writeByteI2C(0, buffer_out[i]) + + self._nova.endI2C(0, True) + + result = self._nova.readBytesI2C(0, address<<1, len(buffer_in[in_start:in_end])) + + if result != "-NG": + resp = result.split(" ") + + for i in range(len(buffer_in[in_start:in_end])): + buffer_in[in_start+i] = int(resp[2+i]) + else: + raise RuntimeError("Received error response from Binho Nova, result = " + result) diff --git a/src/adafruit_blinka/microcontroller/nova/pin.py b/src/adafruit_blinka/microcontroller/nova/pin.py new file mode 100644 index 0000000..235e4ec --- /dev/null +++ b/src/adafruit_blinka/microcontroller/nova/pin.py @@ -0,0 +1,72 @@ +class Pin: + """A basic Pin class for use with Binho Nova.""" + + IN = 'DIN' + OUT = 'DOUT' + AIN = 'AIN' + AOUT = 'AOUT' + PWM = 'PWM' + LOW = 0 + HIGH = 1 + + _nova = None + + def __init__(self, pin_id=None): + if not Pin._nova: + from adafruit_blinka.microcontroller.nova import Connection + Pin._nova = Connection.getInstance() + # check if pin is valid + if pin_id > 4: + raise ValueError("Invalid pin {}.".format(pin_id)) + + self.id = pin_id + + def init(self, mode=IN, pull=None): + if self.id is None: + raise RuntimeError("Can not init a None type pin.") + # Nova does't have configurable internal pulls for + if pull: + raise ValueError("Internal pull up/down not currently supported.") + Pin._nova.setIOpinMode(self.id, mode) + + def value(self, val=None): + if self.id is None: + raise RuntimeError("Can not access a None type pin.") + # read + if val is None: + return int(Pin._nova.getIOpinValue(self.id).split('VALUE ')[1]) + # write + if val in (self.LOW, self.HIGH): + Pin._nova.setIOpinValue(self.id, val) + else: + raise RuntimeError("Invalid value for pin") + +# create pin instances for each pin +IO0 = Pin(0) +IO1 = Pin(1) +IO2 = Pin(2) +IO3 = Pin(3) +IO4 = Pin(4) + +SCL = IO2 +SDA = IO0 +SCK = SCLK = IO3 +MOSI = IO4 +MISO = IO2 +SS0 = IO0 +SS1 = IO1 + +PWM0 = IO0 +# No PWM support on IO1 +PWM2 = IO2 +PWM3 = IO3 +PWM4 = IO4 + +# orderd as (channel, pin), id +pwmOuts = (((1, 0), PWM0), ((1, 2), PWM2), ((1, 3), PWM3), ((1, 4), PWM4)) + +UART1_TX = IO4 +UART1_RX = IO3 + +# ordered as uartId, txId, rxId +uartPorts = ((0, UART1_TX, UART1_RX), ) diff --git a/src/adafruit_blinka/microcontroller/nova/pwmout.py b/src/adafruit_blinka/microcontroller/nova/pwmout.py new file mode 100644 index 0000000..6af2de8 --- /dev/null +++ b/src/adafruit_blinka/microcontroller/nova/pwmout.py @@ -0,0 +1,205 @@ + +try: + from microcontroller.pin import pwmOuts +except ImportError: + raise RuntimeError("No PWM outputs defined for this board") + +from microcontroller.pin import Pin + +class PWMError(IOError): + """Base class for PWM errors.""" + pass + + +class PWMOut(object): + # Nova instance + _nova = None + MAX_CYCLE_LEVEL = 1024 + + def __init__(self, pin, *, frequency=750, 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. + + """ + if PWMOut._nova is None: + from adafruit_blinka.microcontroller.nova import Connection + PWMOut._nova = Connection.getInstance() + + PWMOut._nova.setOperationMode(0, 'IO') + 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=750, 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") + + PWMOut._nova.setIOpinMode(self._pwmpin, Pin.PWM) + + # set frequency + self.frequency = freq + # set period + self._period = self._get_period() + + # set duty + self.duty_cycle = duty + + self._set_enabled(True) + + def deinit(self): + try: + """Deinit the Nova PWM.""" + if self._channel is not None: + #self.duty_cycle = 0 + self._set_enabled(False) # make to disable before unexport + + 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.") + + # Mutable properties + + def _get_period(self): + return 1.0 / self._get_frequency() + + def _set_period(self, period): + if not isinstance(period, (int, float)): + raise TypeError("Invalid period type, should be int or float.") + + self._set_frequency(1.0 / 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 = Pin._nova.getIOpinValue(self._pwmpin) + + # Convert duty cycle to ratio from 0.0 to 1.0 + duty_cycle = duty_cycle / PWMOut.MAX_CYCLE_LEVEL + + # 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 1024 levels + duty_cycle = duty_cycle * PWMOut.MAX_CYCLE_LEVEL + + # Set duty cycle + Pin._nova.setIOpinValue(self._pwmpin, duty_cycle) + + 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 int(PWMOut._nova.getIOpinPWMFreq(self._pwmpin).split('PWMFREQ ')[1]) + + def _set_frequency(self, frequency): + if not isinstance(frequency, (int, float)): + raise TypeError("Invalid frequency type, should be int or float.") + + PWMOut._nova.setIOpinPWMFreq(self._pwmpin, 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._enable + + 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._enable = value + if not self._enable: + self._set_duty_cycle(0.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._pin, self._pin, self.frequency, self.duty_cycle * 100,) diff --git a/src/adafruit_blinka/microcontroller/nova/spi.py b/src/adafruit_blinka/microcontroller/nova/spi.py new file mode 100644 index 0000000..3926922 --- /dev/null +++ b/src/adafruit_blinka/microcontroller/nova/spi.py @@ -0,0 +1,74 @@ +class SPI: + MSB = 0 + PAYLOAD_MAX_LENGTH = 64 + + def __init__(self, clock): + from adafruit_blinka.microcontroller.nova import Connection + self._nova = Connection.getInstance() + self._nova.setNumericalBase(10) + self._nova.setOperationMode(0, 'SPI') + self._nova.setClockSPI(0, clock) + self._nova.setModeSPI(0, 0) + self._nova.setIOpinMode(0, 'DOUT') + self._nova.setIOpinMode(1, 'DOUT') + self._nova.beginSPI(0) + + # Cpol and Cpha set by mode + # Mode Cpol Cpha + # 0 0 0 + # 1 0 1 + # 2 1 0 + # 3 1 1 + + def init(self, baudrate=100000, polarity=0, phase=0, bits=8, + firstbit=MSB, sck=None, mosi=None, miso=None): + #print("baudrate: " + str(baudrate)) + #print("mode: " + str((polarity<<1) | (phase))) + self._nova.setClockSPI(0, baudrate) + self._nova.setModeSPI(0, (polarity<<1) | (phase)) + + @staticmethod + def get_received_data(lineOutput): + return (lineOutput.split('RXD ')[1]) + + @property + def frequency(self): + return self._nova.getClockSPI(0).split('CLK ')[1] + + def write(self, buf, start=0, end=None): + end = end if end else len(buf) + chunks, rest = divmod(end - start, self.PAYLOAD_MAX_LENGTH) + for i in range(chunks): + chunk_start = start + i * self.PAYLOAD_MAX_LENGTH + chunk_end = chunk_start + self.PAYLOAD_MAX_LENGTH + buffer_data = buf[chunk_start:chunk_end] + self._nova.clearBuffer(0) + self._nova.writeToBuffer(0, 0, buffer_data) + self._nova.transferBufferSPI(0, chunk_end - chunk_start + 1) + if rest: + buffer_data = buf[-1*rest:] + self._nova.clearBuffer(0) + self._nova.writeToBuffer(0, 0, buffer_data) + self._nova.transferBufferSPI(0, rest) + + def readinto(self, buf, start=0, end=None, write_value=0): + end = end if end else len(buf) + for i in range(start, end): + buf[start+i] = int(self.get_received_data(self._nova.transferSPI(0, write_value))) + + def write_readinto(self, buffer_out, buffer_in, out_start=0, out_end=None, in_start=0, in_end=None): + out_end = out_end if out_end else len(buffer_out) + in_end = in_end if in_end else len(buffer_in) + readlen = in_end-in_start + writelen = out_end-out_start + if readlen > writelen: + # resize out and pad with 0's + tmp = bytearray(buffer_out) + tmp.extend([0] * (readlen - len(buffer_out))) + buffer_out = tmp + i = 0 + for data_out in buffer_out: + data_in = int(self.get_received_data(self._nova.transferSPI(0, data_out))) + if i < readlen: + buffer_in[in_start+i] = data_in + i += 1 diff --git a/src/adafruit_blinka/microcontroller/nova/uart.py b/src/adafruit_blinka/microcontroller/nova/uart.py new file mode 100644 index 0000000..253d7a7 --- /dev/null +++ b/src/adafruit_blinka/microcontroller/nova/uart.py @@ -0,0 +1,65 @@ +import time + +class UART(): + ESCAPE_SEQUENCE = "+++UART0" + def __init__(self, + portid, + baudrate=9600, + bits=8, + parity=None, + stop=1, + timeout=1000, + read_buf_len=None, + flow=None): + from adafruit_blinka.microcontroller.nova import Connection + self._nova = Connection.getInstance() + + self._id = portid + self._baudrate = baudrate + self._parity = parity + self._bits = bits + self._stop = stop + self._timeout = timeout + + if flow is not None: # default 0 + raise NotImplementedError( + "Parameter '{}' unsupported on Binho Nova".format( + "flow")) + + self._nova.setOperationMode(self._id, 'UART') + self._nova.setBaudRateUART(self._id, baudrate) + self._nova.setDataBitsUART(self._id, bits) + self._nova.setParityUART(self._id, parity) + self._nova.setStopBitsUART(self._id, stop) + self._nova.setEscapeSequenceUART(self._id, UART.ESCAPE_SEQUENCE) + self._nova.beginBridgeUART(self._id) + + def deinit(self): + self._nova.writeBridgeUART(UART.ESCAPE_SEQUENCE) + self._nova.stopBridgeUART(UART.ESCAPE_SEQUENCE) + + def read(self, nbytes=None): + if nbytes is None: + return None + data = bytearray() + for i in range(nbytes): + data.append(ord(self._nova.readBridgeUART())) + return data + + def readinto(self, buf, nbytes=None): + if nbytes is None: + return None + for i in range(nbytes): + buf.append(ord(self._nova.readBridgeUART())) + return buf + + def readline(self): + out = self._nova.readBridgeUART() + line = out + while out != '\r': + out = self._nova.readBridgeUART() + line += out + return line + + def write(self, buf): + return self._nova.writeBridgeUART(buf) diff --git a/src/board.py b/src/board.py index 1a4362c..ef2f086 100755 --- a/src/board.py +++ b/src/board.py @@ -106,6 +106,9 @@ elif board_id == ap_board.DRAGONBOARD_410C: elif board_id == ap_board.FTDI_FT232H: from adafruit_blinka.board.ftdi_ft232h import * +elif board_id == ap_board.BINHO_NOVA: + from adafruit_blinka.board.binho_nova import * + elif "sphinx" in sys.modules: pass diff --git a/src/busio.py b/src/busio.py index ede1e6c..74cfa89 100755 --- a/src/busio.py +++ b/src/busio.py @@ -23,6 +23,10 @@ class I2C(Lockable): from adafruit_blinka.microcontroller.ft232h.i2c import I2C self._i2c = I2C() return + elif detector.board.binho_nova: + from adafruit_blinka.microcontroller.nova.i2c import I2C + self._i2c = I2C() + return elif detector.board.any_embedded_linux: from adafruit_blinka.microcontroller.generic_linux.i2c import I2C as _I2C else: @@ -91,6 +95,12 @@ class SPI(Lockable): self._spi = _SPI() self._pins = (SCK, MOSI, MISO) return + elif detector.board.binho_nova: + from adafruit_blinka.microcontroller.nova.spi import SPI as _SPI + from adafruit_blinka.microcontroller.nova.pin import SCK, MOSI, MISO + self._spi = _SPI(clock) + self._pins = (SCK, MOSI, MISO) + return elif detector.board.any_embedded_linux: from adafruit_blinka.microcontroller.generic_linux.spi import SPI as _SPI else: @@ -145,6 +155,9 @@ class SPI(Lockable): elif detector.board.ftdi_ft232h: from adafruit_blinka.microcontroller.ft232h.spi import SPI as _SPI from adafruit_blinka.microcontroller.ft232h.pin import Pin + elif detector.board.binho_nova: + from adafruit_blinka.microcontroller.nova.spi import SPI as _SPI + from adafruit_blinka.microcontroller.nova.pin import Pin else: from machine import SPI as _SPI from machine import Pin @@ -204,9 +217,15 @@ class UART(Lockable): flow=None): if detector.board.any_embedded_linux: raise RuntimeError('busio.UART not supported on this platform. Please use pyserial instead.') + elif detector.board.binho_nova: + from adafruit_blinka.microcontroller.nova.uart import UART as _UART else: from machine import UART as _UART - from microcontroller.pin import uartPorts + + if detector.board.binho_nova: + from adafruit_blinka.microcontroller.nova.pin import uartPorts + else: + from microcontroller.pin import uartPorts self.baudrate = baudrate @@ -244,6 +263,8 @@ class UART(Lockable): ) def deinit(self): + if detector.board.binho_nova: + self._uart.deinit() self._uart = None def read(self, nbytes=None): diff --git a/src/digitalio.py b/src/digitalio.py index c02c4d5..ed497cc 100755 --- a/src/digitalio.py +++ b/src/digitalio.py @@ -35,6 +35,8 @@ elif detector.chip.IMX8MX: from adafruit_blinka.microcontroller.nxp_imx8m.pin import Pin elif detector.board.ftdi_ft232h: from adafruit_blinka.microcontroller.ft232h.pin import Pin +elif detector.board.binho_nova: + from adafruit_blinka.microcontroller.nova.pin import Pin elif detector.chip.STM32: from machine import Pin from adafruit_blinka import Enum, ContextManaged diff --git a/src/microcontroller/__init__.py b/src/microcontroller/__init__.py index 5550543..5d5f094 100755 --- a/src/microcontroller/__init__.py +++ b/src/microcontroller/__init__.py @@ -50,5 +50,7 @@ elif chip_id == ap_chip.APQ8016: from adafruit_blinka.microcontroller.snapdragon.apq8016.pin import * elif chip_id == ap_chip.IMX8MX: from adafruit_blinka.microcontroller.nxp_imx8m import * +elif chip_id == ap_chip.BINHO: + from adafruit_blinka.microcontroller.nova import * else: raise NotImplementedError("Microcontroller not supported:", chip_id) diff --git a/src/microcontroller/pin.py b/src/microcontroller/pin.py index 475156d..d6e18d8 100755 --- a/src/microcontroller/pin.py +++ b/src/microcontroller/pin.py @@ -34,5 +34,7 @@ elif chip_id == ap_chip.IMX8MX: from adafruit_blinka.microcontroller.nxp_imx8m.pin import * elif chip_id == ap_chip.FT232H: from adafruit_blinka.microcontroller.ft232h.pin import * +elif chip_id == ap_chip.BINHO: + from adafruit_blinka.microcontroller.nova.pin import * else: raise NotImplementedError("Microcontroller not supported: ", chip_id) diff --git a/src/pulseio.py b/src/pulseio.py index bb8a04b..239a4ba 100644 --- a/src/pulseio.py +++ b/src/pulseio.py @@ -8,3 +8,5 @@ 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 +if detector.board.binho_nova: + from adafruit_blinka.microcontroller.nova.pwmout import PWMOut