"""Module to control a shift register.
A shift register is a micro-electronic device providing a series of
flip-flops, where the output of one flip-flop is connected to the input
of the next one.
Although there are different types of shifts registers, those which are
addressed by this module, are configured as serial-in, parallel-out (SIPO).
Data is shifted in serially through DIN by each cycle of the
clock DCLK and can be read in parallel from all outputs Q0, Q1, Q2, ...
simultaneously.
The highest significant bit (Q7') shifted out may be available as QOVR.
The content of the flip-flops may be cleared by DCLR.
Further, there are implementations with buffered outputs (latches).
Those may involve additional lines to copy (latch) the current content
of the flip-flops into the buffer by RCLK and to clear the buffer by
cycling RCLR.
Finally, there may be an enabling line ENA. In some devices, this enables
or disables the whole chip. This is useful e.g. for putting the device
into a deep sleep mode in power-critical applications. Upon recovery
from sleep, the content of the flip-flops may get lost.
Other implementations interpret ENA to just mute or un-mute the outputs
Q while still allowing to alter the register contents.
Finally, ENA might de-couple the serial communication DIN/DCLK. These
implementations preserve the content but pause further feeding of the
shift register while ENA is inactive.
A symbolic visualization of a shift register could look like this::
Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7
^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | |
+-----------------------+
DIN -->| |--> QOVR
DCLK -->| |<-- RCLK
DCLR -->| |<-- RCLR
ENA -->| |
+-----------------------+
Typical implementations of such devices are the
`TI SN74HC594 <https://www.ti.com/product/de-de/SN74HC594>`_,
`ST M74HC595 <https://www.st.com/en/automotive-logic-ics/m74hc595.html>`_, and
`ST HCF4094 <https://www.st.com/en/automotive-logic-ics/hcf4094.html>`_.
Note that handling the output Q0, Q1, ..., QOVR is beyond the scope of
this module.
"""
__author__ = "Oliver Maye"
__version__ = "0.1"
__all__ = ["ShiftReg"]
import logging
from .gpio import GPIO
from .module import Module
from .sysfactory import SysProvider
from .systypes import ErrorCode
[docs]
class ShiftReg( Module ):
"""Driver class for a shift register device.
The implementation provided here, does not assume a
specific size of the shift register, i.e. number of flip-flops.
Although it provides an implementation for controlling the DIN and
DCLK lines, i.e. the process of shifting bits into the register,
it is meant to be derived for more sophisticated / efficient
implementations.
Note that for all GPIO lines configured, the actual physical effect
may be inverted by using the GPIO's `inverted` configuratio parameter.
This may be necessary to semantically match the chip's implementation
and behavior.
"""
PIN_IDX_DCLR = 0
PIN_IDX_ENA = 1
PIN_IDX_RCLK = 2
PIN_IDX_RCLR = 3
PIN_IDX_DIN = 4
PIN_IDX_DCLK = 5
PIN_MAXNUM = 6
MODULE_PARAM_PREFIX = "shiftreg"
ITEM_PARAM_PREFIX = ("dclr", "enable", "rclk", "rclr", "din", "dclk")
def __init__(self):
"""Initialize the instance with defaults.
Also see: :meth:`.Params_init`
"""
self.pin = [None] * ShiftReg.PIN_MAXNUM
#
# Module API
#
[docs]
@classmethod
def Params_init(cls, paramDict):
"""Initializes configuration parameters with defaults.
General or global GPIO adjustments or defaults can be set using
the `shiftreg.gpio.*` keys. They can be pin-wise overridden by
specific e.g. `shiftreg.ena.gpio.*` settings.
The following settings are supported:
================================== ================================================================
Key name Value type, meaning and default
================================== ================================================================
shiftreg.gpio.provider Global setting: GPIO provider
shiftreg.gpio.pinNumbering Global setting: numbering scheme
shiftreg.gpio.inverted Global setting: True if low-active
shiftreg.gpio.level Global setting: initial logic level
shiftreg.din.gpio.pinNumbering DIN pin: Numbering scheme
shiftreg.din.gpio.pinDesignator DIN pin: Name or number of the pin
shiftreg.din.gpio.inverted DIN pin: True, if pin is low-active
shiftreg.din.gpio.level DIN pin: Initial logic level
shiftreg.dclk.gpio.pinNumbering DCLK pin: Numbering scheme
shiftreg.dclk.gpio.pinDesignator DCLK pin: Name or number of the pin
shiftreg.dclk.gpio.inverted DCLK pin: True, if pin is low-active
shiftreg.dclk.gpio.level DCLK pin: Initial logic level
shiftreg.dclr.gpio.pinNumbering DCLR pin: Numbering scheme
shiftreg.dclr.gpio.pinDesignator DCLR pin: Name or number of the pin
shiftreg.dclr.gpio.inverted DCLR pin: True, if pin is low-active
shiftreg.dclr.gpio.level DCLR pin: Initial logic level
shiftreg.rclk.gpio.pinNumbering RCLK pin: Numbering scheme
shiftreg.rclk.gpio.pinDesignator RCLK pin: Name or number of the pin
shiftreg.rclk.gpio.inverted RCLK pin: True, if pin is low-active
shiftreg.rclk.gpio.level RCLK pin: Initial logic level
shiftreg.rclr.gpio.pinNumbering RCLR pin: Numbering scheme
shiftreg.rclr.gpio.pinDesignator RCLR pin: Name or number of the pin
shiftreg.rclr.gpio.inverted RCLR pin: True, if pin is low-active
shiftreg.rclr.gpio.level RCLR pin: Initial logic level
shiftreg.enable.gpio.pinNumbering ENA pin: Numbering scheme
shiftreg.enable.gpio.pinDesignator ENA pin: Name or number of the pin
shiftreg.enable.gpio.inverted ENA pin: True, if pin is low-active
shiftreg.enable.gpio.level ENA pin: Initial logic level
=====================================================================================================
If the `shiftreg.*.pinDesignator` is not given, the driver
assumes, the corresponding line is not present or implemented.
Also see: :meth:`.Module.Params_init`, :meth:`.GPIO.Params_init`.
:param dict(str, object) paramDict: Dictionary of configuration settings.
:return: none
:rtype: None
"""
# Driver defaults
gpioDefaults = {}
gpioDefaults["gpio.direction"] = GPIO.DIRECTION_OUT
GPIO.Params_init( gpioDefaults )
# global defaults
for key, value in gpioDefaults.items():
itemKey = ShiftReg.MODULE_PARAM_PREFIX + "." + key
if not( itemKey in paramDict):
paramDict[itemKey] = value
# specific line configuration
for idx in range(ShiftReg.PIN_MAXNUM):
prefix = ShiftReg.MODULE_PARAM_PREFIX + "." + \
ShiftReg.ITEM_PARAM_PREFIX[idx] + "."
if( (prefix + "gpio.pinDesignator") in paramDict ):
paramDict[prefix+"gpio.direction"] = GPIO.DIRECTION_OUT
for key in gpioDefaults.keys():
itemKey = prefix + key
if not( itemKey in paramDict):
paramDict[itemKey] = paramDict[ShiftReg.MODULE_PARAM_PREFIX + "." + key]
return None
[docs]
def open(self, paramDict):
ret = ErrorCode.errOk
if not [p for p in self.pin if not p]:
ret = ErrorCode.errResourceConflict
if ret.isOk():
self.Params_init( paramDict )
for idx in range(ShiftReg.PIN_MAXNUM):
prefix = ShiftReg.MODULE_PARAM_PREFIX + "." + \
ShiftReg.ITEM_PARAM_PREFIX[idx] + "."
itemKey = prefix + "gpio.pinDesignator"
if( itemKey in paramDict ):
# Extract GPIO parameters
gpioParams = dict( [(k.replace(prefix, ""),v) for k,v in paramDict.items() if k.startswith(prefix)] )
prv = gpioParams.get("gpio.provider", SysProvider.AUTO)
self.pin[idx] = GPIO.getGPIO(prv)
ret = self.pin[idx].open(gpioParams)
if not ret.isOk():
logging.debug("ShiftReg couldn't open pin #%s (%s), error: %s.", \
ShiftReg.ITEM_PARAM_PREFIX[idx], \
gpioParams["gpio.pinDesignator"], \
ret)
break
if not ret.isOk():
# Roll-back
for idx in range(ShiftReg.PIN_MAXNUM):
if self.pin[idx]:
self.pin[idx].close()
self.pin[idx] = None
if ret.isOk():
for idx in (ShiftReg.PIN_IDX_DCLR, ShiftReg.PIN_IDX_RCLK, ShiftReg.PIN_IDX_RCLR):
if self.pin[idx]:
self.pin[idx].set( GPIO.LEVEL_LOW )
self.clearData()
self.clearLatch()
self.enable()
logging.debug('ShiftReg.open() returns: %s.', ret)
return ret
[docs]
def close(self):
ret = ErrorCode.errOk
self.disable()
for idx in range(ShiftReg.PIN_MAXNUM):
if self.pin[idx]:
err = self.pin[idx].close()
self.pin[idx] = None
if( ret.isOk() and not err.isOk() ):
ret = err
logging.debug('ShiftReg closed, return: %s.', ret)
return ret
#
# Module specific API
#
[docs]
def write(self, data, numBits=1, autoLatch=True):
"""Feed data into the first stage of the shift register.
The lowest significant number of bits as given by the `numBits`
parameter is written to DIN sequentially, starting with the
highest-significant bit, first. If, for example,
`data = 141 = 0x8d = 10001101b` and `numBits=4`, the sequence
`1-1-0-1ยด is written in that order.
For each bit written, DIN is set correspondingly and DCLK is
cycled once to logic `high`and back to logic `low`.
This implementation is GPIO based and accepts single bits to be
shifted in - at the price of dedicated GPIO lines for DIN and
DCLK. Implementations in derived classes may depend on SPI or
other mechanisms and, hence, put restrictions on what `numBits`
can be.
If `autoLatch` is `True` and depending on the underlying
hardware, the resulting content of the shift register is
latched into the buffer.
:param int data: The data to send to the shift register.
:param int numBits: Non-negative number of least-significant \
bits in 'data' to shift-in. Usually 1, 4 or multiple of 8.
:param bool autoLatch: Whether to automatically latch the result into the buffer.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
if not self.pin[ShiftReg.PIN_IDX_DIN] or not self.pin[ShiftReg.PIN_IDX_DCLK]:
ret = ErrorCode.errNotSupported
elif not isinstance( data, int ) or \
not isinstance( numBits, int ) or (numBits<0):
ret = ErrorCode.errInvalidParameter
elif numBits == 0:
# Do nothing
ret = ErrorCode.errOk
else:
mask = 1 << (numBits-1)
for _ in range(numBits):
level = GPIO.LEVEL_HIGH if (data & mask) else GPIO.LEVEL_LOW
ret = self.pin[ShiftReg.PIN_IDX_DIN].set( level )
ret = self.pin[ShiftReg.PIN_IDX_DCLK].set( GPIO.LEVEL_HIGH )
# wait for ~12 ns
ret = self.pin[ShiftReg.PIN_IDX_DCLK].set( GPIO.LEVEL_LOW )
if not ret.isOk():
break
mask >>= 1
if( ret.isOk() and autoLatch ):
# Intentionally ignore the return as operation might not be supprted
self.latch()
logging.debug('ShiftReg write(data=%02x, numBits=%d), return: %s.',
data, numBits, 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.
If `activate`is `True`, the ENA line is set to the logic `high`
level. Otherwise, it is set to logic `low`.
Remember that the physical effect may be inverted by using the
GPIO's `inverted` configuration parameter.
The actual semantic effect or meaning depends on the underlying
device. In some rare implementations, disabling puts the chip
into a deep sleep mode, which may be advantageous in power-
critical applications. However, upon recovery from sleep, the
content of the flip-flops may be lost.
More frequently, de-activating ENA just de-couples the serial
communication DIN/DCLK. These implementations preserve the
content but pause further feeding of the shift register while
ENA is inactive. This behavior is similar to the chip-select (CS)
line in SPI communication.
:param bool activate: Whether to activate (default) or deactivate the device.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
if self.pin[ShiftReg.PIN_IDX_ENA]:
level = GPIO.LEVEL_HIGH if activate else GPIO.LEVEL_LOW
ret = self.pin[ShiftReg.PIN_IDX_ENA].set( level )
else:
ret = ErrorCode.errNotSupported
logging.debug('ShiftReg ENA set to %s, return: %s.', activate, ret)
return ret
[docs]
def disable(self):
"""Disable the device, 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)
[docs]
def clearData(self):
"""Use DCLR to reset the register content.
If supported by the driven chip, clears the contents of the
flip-flops by cycling DCLR.
That is, the line is set to logic `high` and reset back to
logic `low`.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
if self.pin[ShiftReg.PIN_IDX_DCLR]:
ret = self.pin[ShiftReg.PIN_IDX_DCLR].set( GPIO.LEVEL_HIGH )
# wait for ~12 ns
ret = self.pin[ShiftReg.PIN_IDX_DCLR].set( GPIO.LEVEL_LOW )
else:
ret = ErrorCode.errNotSupported
logging.debug('ShiftReg clearData, return: %s.', ret)
return ret
[docs]
def clearLatch(self):
"""Use RCLR to reset the buffer (latch) content.
If supported by the driven chip, clears the contents of the
buffer by cycling RCLR.
That is, the line is set to logic `high` and reset back to
logic `low`.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
if self.pin[ShiftReg.PIN_IDX_RCLR]:
ret = self.pin[ShiftReg.PIN_IDX_RCLR].set( GPIO.LEVEL_HIGH )
# wait for ~12 ns
ret = self.pin[ShiftReg.PIN_IDX_RCLR].set( GPIO.LEVEL_LOW )
else:
ret = ErrorCode.errNotSupported
logging.debug('ShiftReg clearLatch, return: %s.', ret)
return ret
[docs]
def latch(self):
"""Use RCLK to copy the register flip-flop content to the buffer.
If supported by the driven chip, copies the contents of the
shift register to the buffer by cycling RCLK.
That is, the line is set to logic `high` and reset back to
logic `low`.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
if self.pin[ShiftReg.PIN_IDX_RCLK]:
ret = self.pin[ShiftReg.PIN_IDX_RCLK].set( GPIO.LEVEL_HIGH )
# wait for ~12 ns
ret = self.pin[ShiftReg.PIN_IDX_RCLK].set( GPIO.LEVEL_LOW )
else:
ret = ErrorCode.errNotSupported
logging.debug('ShiftReg latch, return: %s.', ret)
return ret
[docs]
def clear(self):
"""Clear the register and buffer content.
If supported by the underlying hardware, use DCLR and
RCLR to reset. Otherwise, the implementation may
write a number of zeroes to the register to
approximate a similar result.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
flagWrite = False
ret = self.clearData()
if ret.isOk():
ret = self.clearLatch()
if( ret == ErrorCode.errNotSupported ):
ret = self.latch()
if( ret == ErrorCode.errNotSupported ):
flagWrite = True
elif( ret == ErrorCode.errNotSupported ):
flagWrite = True
if flagWrite:
ret = self.write( 0, 32 ) # autoLatch clears the buffer
return ret