Skip to content

Instantly share code, notes, and snippets.

@martinohanlon
Last active August 12, 2017 21:05
Show Gist options
  • Save martinohanlon/23a8a67bc3c68988fbb492b3d5d42ca5 to your computer and use it in GitHub Desktop.
Save martinohanlon/23a8a67bc3c68988fbb492b3d5d42ca5 to your computer and use it in GitHub Desktop.
4 digit 7 segment display
from gpiozero import LEDCollection, LEDBoard, OutputDeviceError, DigitalOutputDevice
from gpiozero.threads import GPIOThread
from gpiozero.exc import OutputDeviceError
from itertools import cycle
from time import sleep
class SevenSegmentDisplay(LEDBoard):
"""
Extends :class:`LEDBoard` for a 7 segment LED display
7 segment displays have either 7 or 8 pins, 7 pins for the digit display
and an optional 8th pin for a decimal point. 7 segment displays
typically have either a common anode or common cathode pin, when
using a common anode display 'active_high' should be set to False.
Instances of this class can be used to display characters or control
individual leds on the display. For example::
from gpiozero import SevenSegmentDisplay
seven = SevenSegmentDisplay(1,2,3,4,5,6,7,8,active_high=False)
seven.display("7")
:param int \*pins:
Specify the GPIO pins that the 7 segment display is attached to.
Pins should be in the LED segment order A,B,C,D,E,F,G,decimal_point
(the decimal_point is optional).
:param bool pwm:
If ``True``, construct :class:`PWMLED` instances for each pin. If
``False`` (the default), construct regular :class:`LED` instances. This
parameter can only be specified as a keyword parameter.
:param bool active_high:
If ``True`` (the default), the :meth:`on` method will set all the
associated pins to HIGH. If ``False``, the :meth:`on` method will set
all pins to LOW (the :meth:`off` method always does the opposite). This
parameter can only be specified as a keyword parameter.
:param bool initial_value:
If ``False`` (the default), all LEDs will be off initially. If
``None``, each device will be left in whatever state the pin is found
in when configured for output (warning: this can be on). If ``True``,
the device will be switched on initially. This parameter can only be
specified as a keyword parameter.
"""
def __init__(self, *pins, **kwargs):
# 7 segment displays must have 7 or 8 pins
if len(pins) == 7:
self._has_decimal_point = False
elif len(pins) == 8:
self._has_decimal_point = True
else:
raise ValueError('SevenSegmentDisplay must have 7 or 8 pins')
# Don't allow 7 segments to contain collections
for pin in pins:
assert not isinstance(pin, LEDCollection)
pwm = kwargs.pop('pwm', False)
active_high = kwargs.pop('active_high', True)
initial_value = kwargs.pop('initial_value', False)
if kwargs:
raise TypeError('unexpected keyword argument: %s' % kwargs.popitem()[0])
self._layouts = {
'1': (False, True, True, False, False, False, False),
'2': (True, True, False, True, True, False, True),
'3': (True, True, True, True, False, False, True),
'4': (False, True, True, False, False, True, True),
'5': (True, False, True, True, False, True, True),
'6': (True, False, True, True, True, True, True),
'7': (True, True, True, False, False, False, False),
'8': (True, True, True, True, True, True, True),
'9': (True, True, True, True, False, True, True),
'0': (True, True, True, True, True, True, False),
'A': (True, True, True, False, True, True, True),
'B': (False, False, True, True, True, True, True),
'C': (True, False, False, True, True, True, False),
'D': (False, True, True, True, True, False, True),
'E': (True, False, False, True, True, True, True),
'F': (True, False, False, False, True, True, True),
'G': (True, False, True, True, True, True, False),
'H': (False, True, True, False, True, True, True),
'I': (False, False, False, False, True, True, False),
'J': (False, True, True, True, True, False, False),
'K': (True, False, True, False, True, True, True),
'L': (False, False, False, True, True, True, False),
'M': (True, False, True, False, True, False, False),
'N': (True, True, True, False, True, True, False),
'O': (True, True, True, True, True, True, False),
'P': (True, True, False, False, True, True, True),
'Q': (True, True, False, True, False, True, True),
'R': (True, True, False, False, True, True, False),
'S': (True, False, True, True, False, True, True),
'T': (False, False, False, True, True, True, True),
'U': (False, False, True, True, True, False, False),
'V': (False, True, True, True, True, True, False),
'W': (False, True, False, True, False, True, False),
'X': (False, True, True, False, True, True, True),
'Y': (False, True, True, True, False, True, True),
'Z': (True, True, False, True, True, False, True),
'-': (False, False, False, False, False, False, True),
' ': (False, False, False, False, False, False, False),
'=': (False, False, False, True, False, False, True)
}
super(SevenSegmentDisplay, self).__init__(*pins, pwm=pwm, active_high=active_high, initial_value=initial_value)
def display(self, char):
"""
Display a character on the 7 segment display
:param string char:
A single character to be displayed
"""
char = str(char).upper()
if len(char) > 1:
raise ValueError('only a single character can be displayed')
if char not in self._layouts:
raise ValueError('there is no layout for character - %s' % char)
layout = self._layouts[char]
for led in range(7):
self[led].value = layout[led]
def display_hex(self, hexnumber):
"""
Display a hex number (0-F) on the 7 segment display
:param int hexnumber:
The number to be displayed in hex
"""
self.display(hex(hexnumber)[2:])
@property
def decimal_point(self):
"""
Represents the status of the decimal point led
"""
# does the 7seg display have a decimal point (i.e pin 8)
if self.has_decimal_point:
return self[7].value
else:
raise OutputDeviceError('there is no 8th pin for the decimal point')
@decimal_point.setter
def decimal_point(self, value):
"""
Sets the status of the decimal point led
"""
if self.has_decimal_point:
self[7].value = value
else:
raise OutputDeviceError('there is no 8th pin for the decimal point')
@property
def has_decimal_point(self):
"""
Represents whether the seven segment display has a decimal point
"""
return self._has_decimal_point
def set_char_layout(self, char, layout):
"""
Create or update a custom character layout, which can be used with the
`display` method.
:param string char:
A single character to be displayed
:param tuple layout:
A 7 bool tuple of LED values in the segment order A, B, C, D, E, F, G
"""
char = str(char).upper()
if len(char) != 1:
raise ValueError('only a single character can be used in a layout')
if len(layout) != 7:
raise ValueError('a character layout must have 7 segments')
self._layouts[char] = layout
class MultiSevenSegmentDisplay(SevenSegmentDisplay):
"""
Extends :class:`SevenSegmentDisplay` for a multi digit 7 segment LED display
7 segment displays have either 7 or 8 pins, 7 pins for the digit display
and an optional 8th pin for a decimal point. 7 segment displays
typically have either a common anode or common cathode pin, when
using a common anode display 'active_high' should be set to False.
Multi digit 7 segment displays have additional pins for each digit which
when set low for common anode or high for common cathode will light that
digit.
Different values are displayed on different digits by multiplexing the display.
Instances of this class can be used to display numbers, messages or control
individual leds and digits on the display. For example::
from gpiozero import MultiSevenSegmentDisplay
multi_seven = MultiSevenSegmentDisplay((1,2,3,4,5,6,7,8)(9,10,11,12))
multi_seven.display("leds")
:param tuple led_pins:
Specify the GPIO pins that the 7 segment display's leds are attached to.
Pins should be in the LED segment order A,B,C,D,E,F,G,decimal_point
(the decimal_point is optional).
:param tuple digit_pins:
Specify the GPIO pins that the 7 segment display's digits are attached to.
:param bool pwm:
If ``True``, construct :class:`PWMLED` instances for each LED pin. If
``False`` (the default), construct regular :class:`LED` instances. This
parameter can only be specified as a keyword parameter.
:param bool active_high:
If ``True`` (the default), the :meth:`on` method will set all the
associated pins to HIGH. If ``False``, the :meth:`on` method will set
all pins to LOW (the :meth:`off` method always does the opposite). This
parameter can only be specified as a keyword parameter.
:param bool initial_value:
If ``False`` (the default), all LEDs and digits will be off initially. If
``None``, each device will be left in whatever state the pin is found
in when configured for output (warning: this can be on). If ``True``,
the device will be switched on initially. This parameter can only be
specified as a keyword parameter.
"""
def __init__(self, led_pins, digit_pins, **kwargs):
pwm = kwargs.pop('pwm', False)
active_high = kwargs.pop('active_high', True)
initial_value = kwargs.pop('initial_value', False)
if kwargs:
raise TypeError('unexpected keyword argument: %s' % kwargs.popitem()[0])
if len(digit_pins) < 2:
raise ValueError('a MultiSevenSegmentDisplay must have more than 1 digit')
self.digits = []
for digit_pin in digit_pins:
self.digits.append(DigitalOutputDevice(digit_pin, active_high=not active_high))
self._display_thread = None
super(MultiSevenSegmentDisplay, self).__init__(*led_pins, pwm=pwm, active_high=active_high)
if initial_value:
self.on()
def display(self, message, align_left=True, refresh_delay=0.007):
"""
Display a message on the multi 7 segment display
:param string message:
The message to be displayed. If the 7 segment display has
a decimal points, they will be used in replacement for
full stops in the message.
:param bool align_left:
If 'True' (the default) the message will be aligned to the left
on the display. If 'False' the message will be aligned to the
right.
:param float refresh_delay:
The time in seconds that each digit will be turned on when
multiplexing the display.
The defalut of 0.007 was chosen by trial and error, short enough
so there was no flicker, long enough to still be bright, this
value may need to be modified depending on the display.
"""
self._stop_display()
message = self._format_message(str(message), align_left)
self._display_thread = GPIOThread(
target=self._display, args=(message, align_left, refresh_delay)
)
self._display_thread.start()
def _display(self, message, align_left, refresh_delay):
for digit in cycle(range(len(self.digits))):
#if we are at the start of the digits, go to the start of the message
if digit == 0:
i = 0
#display the digit
super(MultiSevenSegmentDisplay, self).display(message[i])
#is the next digit a full stop?
if self.has_decimal_point:
if message[i+1:i+2] == ".":
self.decimal_point = True
i += 1
else:
self.decimal_point = False
#turn the digit on and wait
self.digits[digit].on()
if self._display_thread.stopping.wait(refresh_delay):
break
self.digits[digit].off()
i += 1
def _format_message(self, message, align_left):
#add spaces to decimal points if needed
if self.has_decimal_point:
output = ""
for i in range(len(message)):
if message[i] == '.' and (message[i-1] == '.' or i == 0):
output += ' '
output += message[i]
message = output
#validate the messages length
if (self.has_decimal_point and len(message) - message.count('.') > len(self.digits)) or (not self.has_decimal_point and len(message) > len(self.digits)):
raise ValueError('the message is too long: %s' % message)
#align the message
max_len = len(self.digits) + message.count(".") if self.has_decimal_point else len(self.digits)
message = message.ljust(max_len) if align_left else message.rjust(max_len)
return message
def _stop_display(self):
if self._display_thread:
self._display_thread.stop()
self._display_thread = None
def on(self):
"""
Turns on all LEDs on all digits.
"""
self._stop_display()
super(MultiSevenSegmentDisplay, self).on()
for digit in self.digits:
digit.on()
def off(self):
"""
Turns off all LEDs on all digits
"""
self._stop_display()
super(MultiSevenSegmentDisplay, self).off()
for digit in self.digits:
digit.off()
@property
def value(self):
"""
Represents the value of the displays pins, returning a tuple of
2 tuples for LED values and digit values.
"""
digits_value = []
for digit in self.digits:
digits_value.append(digit.value)
leds_value = []
for led in self:
leds_value.append(led.value)
return((tuple(leds_value), tuple(digits_value)))
@value.setter
def value(self, value):
"""
Sets the value of the displays pins, passing a tuple of
2 tuples for LED values and digit values.
e.g. ``((A, B, C, D, E, F, G, decimal_point),
(1, 2, 3, 4))``
"""
self._stop_display()
leds_value = value[0]
digits_value = value[1]
if len(digits_value) != len(self.digits):
raise ValueError('expected %s digit values' % len(self.digits))
for i in range(len(self.digits)):
self.digits[i].value = digits_value[i]
for i in range(len(leds_value)):
self[i].value = leds_value[i]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment