Last active
August 12, 2017 21:05
-
-
Save martinohanlon/23a8a67bc3c68988fbb492b3d5d42ca5 to your computer and use it in GitHub Desktop.
4 digit 7 segment display
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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