Source code for philander.mux

"""Module to control multiplexer chips.

Usually, multiplexer / de-multiplexer connect one *common* line `X` to
one of four or one of eight other lines `Y0, Y1, ...`.
As this connection can be used both ways, those Y-lines are often called
input / output or I/O channels.

The selection is made by two (1:4) or three (1:8) control lines
`A, B, ...`, which can be interpreted as a bit pattern.
Moreover, there may be an enable or disable line to have no connection
at all. A general pin/logic scheme of a 3:8 de-/multiplexer could look
like this::

         Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7
          |  |  |  |  |  |  |  |
        +-----------------------+
    A --|                       |
    B --|          *            |-- ENA
    C --|                       |
        +-----------------------+
                   |
                   X             
             
Typical implementations of such devices are the
`TI SN74HCS237 <https://www.ti.com/product/de-de/SN74HCS237>`_,
`ST M74HC4851 <https://www.st.com/en/switches-and-multiplexers/m74hc4851.html>`_, and
`ST HCF4051 <https://www.st.com/en/switches-and-multiplexers/hcf4051.html>`_.

Variants of this chip family work as de-multiplexer or decoder, only:
Lacking a *common* line, the I/O lines are operated in *output* only.
This can be used to drive exactly one out of four or eight lines high or
low. An example of this sort of device is the
`TI SN74LVC138 <https://www.ti.com/product/de-de/SN74LVC138A>`_,

Note that the core intention of this module is to control the `A, B, C`
selector bits and the `ENA` enabling/disabling/inhibiting line, if
present.
However, manipulating the common `X` and I/O `Y` lines is beyond the
scope of this driver.
"""
__author__ = "Oliver Maye"
__version__ = "0.1"
__all__ = ["Mux"]

import logging

from .gpio import GPIO
from .module import Module
from .systypes import ErrorCode


[docs] class Mux( Module ): """Generic driver class for a de-/multiplexer device. """ MAXNUM_BITS = 4 MODULE_PARAM_PREFIX = "mux." def __init__(self): """Initialize the instance with defaults. The size of the multiplexer, i.e. the number of control bits `A, B, C, ...` is implicitly defined by the number of GPIO configurations passed to :meth:`.open`. Also see: :meth:`.Params_init` """ self.bit = list() self.ena = None self.maxValue = 0 # # Module API #
[docs] @classmethod def Params_init(cls, paramDict): """Initializes configuration parameters with defaults. Note that general or global GPIO adjustments or defaults can be set using the `mux.gpio.*` keys. They apply to both, the bit control lines as well as the enable line. They can be pin-wise overridden by specific e.g. `mux.bit0.gpio.*` settings. The following settings are supported: ================================== ================================================================ Key name Value type, meaning and default ================================== ================================================================ mux.gpio.pinNumbering Global setting: numbering scheme mux.gpio.inverted Global setting: True if low-active mux.gpio.level Global setting: initial logic level mux.bit[0...3].gpio.pinNumbering Bit pin: Numbering scheme mux.bit[0...3].gpio.pinDesignator Bit pin: Name or number of the pin mux.bit[0...3].gpio.inverted Bit pin: True, if pin is low-active mux.bit[0...3].gpio.level Bit pin: Initial logic level mux.enable.gpio.pinNumbering ENA pin: Numbering scheme mux.enable.gpio.pinDesignator ENA pin: Name or number of the pin mux.enable.gpio.inverted ENA pin: True, if pin is low-active mux.enable.gpio.level ENA pin: Initial logic level ===================================================================================================== The number of `mux.bit0.pinDesignator`, `mux.bit1.pinDesignator` etc. settings implicitly defines the size of the multiplexer, i.e. the number of control bits. For safety reasons, a 4 bit multiplexer (1:16) is supported, at maximum. If the `mux.enable.pinDesignator` is not given, the driver assumes, there is no enable/disable line. Also see: :meth:`.Module.Params_init`, :meth:`.GPIO.Params_init`. """ # Driver defaults gpioDefaults = {} gpioDefaults["gpio.direction"] = GPIO.DIRECTION_OUT GPIO.Params_init( gpioDefaults ) # Mux global defaults for key, value in gpioDefaults.items(): tempKey = Mux.MODULE_PARAM_PREFIX + key if not( tempKey in paramDict): paramDict[tempKey] = value # Specific bit configurations prefixDefault = Mux.MODULE_PARAM_PREFIX for idx in range(Mux.MAXNUM_BITS): prefix = Mux.MODULE_PARAM_PREFIX + "bit" + str(idx) + "." tempKey = prefix + "gpio.pinDesignator" if( tempKey in paramDict ): paramDict[prefix+"gpio.direction"] = GPIO.DIRECTION_OUT for key in gpioDefaults.keys(): tempKey = prefix + key if not( tempKey in paramDict): paramDict[tempKey] = paramDict[prefixDefault+key] else: break # ENA line configuration prefix = Mux.MODULE_PARAM_PREFIX + "enable." tempKey = prefix + "gpio.pinDesignator" if( tempKey in paramDict ): paramDict[prefix+"gpio.direction"] = GPIO.DIRECTION_OUT for key in gpioDefaults.keys(): tempKey = prefix + key if not( tempKey in paramDict): paramDict[tempKey] = paramDict[prefixDefault+key] # This class defaults # defaults = { # Mux.PARAM_PREFIX + "key" : False, # } # for key, value in defaults.items(): # if not key in paramDict: # paramDict[key] = value return None
[docs] def open(self, paramDict): ret = ErrorCode.errOk if self.bit or self.ena: ret = ErrorCode.errResourceConflict else: Mux.Params_init( paramDict ) for idx in range(Mux.MAXNUM_BITS): prefix = Mux.MODULE_PARAM_PREFIX + "bit" + str(idx) + "." tempKey = prefix + "gpio.pinDesignator" if( tempKey in paramDict ): # Extract GPIO parameters gpioParams = dict( [(k.replace(prefix, ""),v) for k,v in paramDict.items() if k.startswith(prefix)] ) pin = GPIO.getGPIO() ret = pin.open(gpioParams) if( ret == ErrorCode.errOk ): self.bit.append(pin) else: logging.debug("Mux couldn't open pin #%d (%s), error: %s.", \ idx, gpioParams["gpio.pinDesignator"], ret) for pin in self.bit: pin.close() self.bit.clear() break else: break if( (ret == ErrorCode.errOk) and not self.bit ): ret = ErrorCode.errFewData if( ret == ErrorCode.errOk ): prefix = Mux.MODULE_PARAM_PREFIX + "enable." tempKey = prefix + "gpio.pinDesignator" if( tempKey in paramDict ): # Extract GPIO parameters gpioParams = dict( [(k.replace(prefix, ""),v) for k,v in paramDict.items() if k.startswith(prefix)] ) self.ena = GPIO.getGPIO() ret = self.ena.open(gpioParams) if( ret != ErrorCode.errOk ): logging.debug("Mux couldn't open enable line (%s), error: %s.", \ gpioParams["gpio.pinDesignator"], ret) self.ena = None for pin in self.bit: pin.close() self.bit.clear() if( ret == ErrorCode.errOk ): self.maxValue = (1 << len(self.bit)) - 1 logging.debug('Mux.open() returns: %s.', ret) return ret
[docs] def close(self): ret = ErrorCode.errOk self.disable() for pin in self.bit: err = pin.close() if( (ret==ErrorCode.errOk) and (err!=ErrorCode.errOk)): ret = err self.bit.clear() if self.ena: err = self.ena.close() if( (ret==ErrorCode.errOk) and (err!=ErrorCode.errOk)): ret = err self.ena = None self.maxValue = 0 logging.debug('Mux closed, return: %s.', ret) return ret
# # Mux specific API #
[docs] def select(self, number, automute=False): """Switch to the channel with the given, zero-based number. Depending on the size of the multiplexer, the least-significant bits of the given number are used to set the control lines `A`, `B`, `C` etc. That directly determines, which of the I/O channels `Y0`, `Y1`, etc. is connected to the common line `X`. If the `automute` flag is set, the device is first disabled. Then, the channel is selected and finally, the device is enabled, again. Note that this will always leave the device in an enabled state! :param int number: Zero-based index number of the channel to select. :param bool automute: Flag, if the device should be disabled for switching. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ ret = ErrorCode.errOk if self.bit: if automute: self.enable(False) acc = int(number) for pin in self.bit: level = GPIO.LEVEL_HIGH if (acc & 1) else GPIO.LEVEL_LOW err = pin.set( level ) if( (ret==ErrorCode.errOk) and (err!=ErrorCode.errOk)): ret = err acc = acc >> 1 if automute: self.enable(True) else: ret = ErrorCode.errNotInited logging.debug('Mux selected %d, return: %s.', number, ret) return ret
[docs] def enable(self, activate=True): """Set the ENA line to activate the device as given. If supported by the driven chip, enables or disables the devices, as given by the `activate` parameter. Note that the actual effect on the physical line may be inverted by using the GPIO's `inverted` option. This may be necessary to semantically match the chip's `ENA` line meaning and behavior. :param bool activate: Whether to activate (default) or deactivate the multiplexer. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ ret = ErrorCode.errOk if self.ena: level = GPIO.LEVEL_HIGH if activate else GPIO.LEVEL_LOW ret = self.ena.set( level ) else: ret = ErrorCode.errNotSupported logging.debug('Mux ENA set to %s, return: %s.', activate, ret) return ret
[docs] def disable(self): """Disable the multiplexer, if supported by the underlying chip. Also see: :meth:`.enable`. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ return self.enable(False)