Seven Segment Display Drivers

Summary

Driver Classes

Simple numeric (decimal and hexademcimal) drivers for a single seven-segment display, requiring seven GPIO pins. During initialisation the user must supply a list of exactly seven GPIO pins, using the numeric pin identifier (e.g. 16 for GPIO Pin 16). Each entry in the list corresponds to the a single segment as shown in Figure 1; with the first list entry referring to segment 'a', and the last list entry to segment 'g'. Physically, each LED segment is also assumed to be laid out in the standard pattern, shown below in Figure 1.

Illustration of a Seven Segment Display

Figure 1: Assumed Layout of the Seven Segment Display

Warning

Once the constructor has been called, no further changes to the pin entries are possible. If changes are required, the object must be destroyed, and then re-created from the class definition. Also note that the display driver will also assume exclusive use of the relevant GPIO pins whilst the driver object is in scope.

To display a character, the display method of the class is used: passing in an integer in the range 0..9 representing the number to show on the seven segment. Not that by default the display method assumes that GPIO pins must be held low for the segment to display: i.e. the behaviour normally used by common anode seven-segment displays. If you need the requested GPIO pin to be held high to display a segment, pass in True to the inverted parameter of the display method.

Note

The drivers will only display characters in the specified range, and will raise a ValueError exception if the requested character is not in an appropriate range.

Common Anode and Common Cathode Implementations

The display methods of the SegDisplay and the SegHexDisplay have an (optional) argument pin_on for the sense of the GPIO pins. This argument is defined as

PIN_ON_SENSE = {"HIGH", "LOW"}

When the GPIO pins need to be set 'high' ('1') for the device input to turn on, the pin_on input need to be set to HIGH. This typical behaviour for a common anode display, and also the normal library defined.

Alternatively, if the GPIO pins need to be set 'low' ('0') for the device input to turn on, then the pin_on argument must be sent to LOW. This is also the typical behaviour for common cathode devices.

Class and Package Diagrams

The classes provided for the seven segment displays are described in the following sections. Logically the classes within the lbutils.drivers package for these drivers are organised as follows

Seven Segment Package Structure

Tested Implementations

This version is written for MicroPython 3.4, and has been tested on:

  • Raspberry Pi Pico H/W

Examples

Examples Folder

  • examples/drivers/seven_segment_example.py
  • examples/drivers/seven_segment_hex_example.py

WokWi

lbutils.drivers.SegDisplay

Simple (decimal) numeric driver for a seven-segment display, requiring seven GPIO pins.

Note: This driver will only display characters in the range '0' to '9', and will raise a ValueError exception if the requested character is not in an appropriate range.

Source code in lbutils/drivers/seven_segment.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
class SegDisplay:
    """Simple (decimal) numeric driver for a seven-segment display, requiring
    seven GPIO pins.

    **Note:** This driver will only display characters in the range '0' to '9',
    and will raise a `ValueError` exception if the requested character is not in
    an appropriate range.
    """

    _char_list = [
        [False, False, False, False, False, False, True],
        [True, False, False, True, True, True, True],
        [False, False, True, False, False, True, False],
        [False, False, False, False, True, True, False],
        [True, False, False, True, True, False, False],
        [False, True, False, False, True, False, False],
        [False, True, False, False, False, False, False],
        [False, False, False, True, True, True, True],
        [False, False, False, False, False, False, False],
        [False, False, False, True, True, False, False],
    ]
    """Defines how characters are rendered, from zero ('0') in the first entry
    to nine ('9') as the last entry.

    Note that pins which are listed here as `False` will be *on* using the
    default options to the `display` method.
    """

    def __init__(self, gpio_request: list) -> None:
        """Initialise a seven-segment display, using the user supplied list of
        GPIO pins in `gpio_request` as reference for pins to drive.

        This class also assume a common anode seven-segment display by default,
        and so will assume that pulling a GPIO pin *low* will turn the relevant
        segment *on*. If you need to modify this behaviour, see the `inverted`
        parameter for the `display` method.

        !!! Note
            This list of entries in the `gpio_request` *must* be **exactly**
            seven entries long, or the class will throw a `ValueError` in the
            constructor.

        Parameters
        ----------

        gpio_request: list
            The pin-ordered list of GPIO pins to use for the segment positions
            'a' (as the first entry in the list) to 'g' (as the last entry in
            the list).

            **Note**: The `SegDisplay` class will also attempt to create the
            underlying GPIO object for each of the entries in the list. If
            the GPIO pins need to be initialised first, this must be done
            *before* calling this constructor.

        Raises
        ------

        ValueError
            The `gpio_request` is empty, or does not have exactly
            seven elements in the list.
        """
        self.pin_list = []

        if (gpio_request is None) or (not gpio_request):
            msg = "The GPIO Request List is empty"
            raise ValueError(msg)
        elif len(gpio_request) != DISPLAY_SEGMENTS:
            msg = "The GPIO Request List must be EXACTLY seven entries long"
            raise ValueError(msg)
        else:
            for segment in range(7):
                self.pin_list.append(Pin(gpio_request[segment], Pin.OUT))

    def display(self, character: int, pin_on: PIN_ON_SENSE = PIN_ON_SENSE.LOW) -> None:
        """Display the given `character` on the seven-segment display, using the
        `_char_list` as a guide for which pins to turn on or off. By default the
        `display` method will use the entries in the `_char_list` directly: if
        you need to invert the 'normal' sense, set the `inverted` parameter to
        `True`.

        Parameters
        ----------

        character: int
            The value to be displayed on the seven segment display, which must be
            between zero ('0') and nine ('9')
        pin_on: PIN_ON_SENSE, optional
            When set to `"HIGH"` the GPIO pins need to be set 'high' ('1') for
            the device segment to turn on (the typical behaviour for a common
            anode display). When set to `"LOW"` the GPIO pins need to be set
            'low' ('0') for the device segment to turn on (the typical behaviour
            for a common cathode display.

        Raises
        ------

        IndexError
            The `character` is not in a range that can be displayed.
        """
        # For a character in the valid range...
        if 0 <= character <= NUM_CHARACTERS:
            if pin_on == PIN_ON_SENSE.LOW:
                # ... if the request is to display in the non-inverted form, then
                # select the row in `_char_list` corresponding to the character to
                # be displayed and then set in turn each of the GPIO pins corresponding
                # to the segment values either high or low depending on the column
                # value in `_char_list` for that segment value
                for pin in range(7):
                    self.pin_list[pin].value(self._char_list[character][pin])
            else:
                # ... if the request is to display in the inverted form, then
                # select the row in `_char_list` corresponding to the character to
                # be displayed and then set in turn each of the GPIO pins corresponding
                # to the segment values either high or low depending on the *inverse* of
                # the column value in `_char_list` for that segment value
                for pin in range(7):
                    self.pin_list[pin].value(not self._char_list[character][pin])
        else:
            msg = ("The display character must be between zero ('0') and nine ('9')",)
            raise IndexError(msg)

Functions

__init__

__init__(gpio_request: list) -> None

Initialise a seven-segment display, using the user supplied list of GPIO pins in gpio_request as reference for pins to drive.

This class also assume a common anode seven-segment display by default, and so will assume that pulling a GPIO pin low will turn the relevant segment on. If you need to modify this behaviour, see the inverted parameter for the display method.

Note

This list of entries in the gpio_request must be exactly seven entries long, or the class will throw a ValueError in the constructor.

Parameters:

  • gpio_request (list) –

    The pin-ordered list of GPIO pins to use for the segment positions 'a' (as the first entry in the list) to 'g' (as the last entry in the list).

    Note: The SegDisplay class will also attempt to create the underlying GPIO object for each of the entries in the list. If the GPIO pins need to be initialised first, this must be done before calling this constructor.

Raises:

  • ValueError

    The gpio_request is empty, or does not have exactly seven elements in the list.

Source code in lbutils/drivers/seven_segment.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def __init__(self, gpio_request: list) -> None:
    """Initialise a seven-segment display, using the user supplied list of
    GPIO pins in `gpio_request` as reference for pins to drive.

    This class also assume a common anode seven-segment display by default,
    and so will assume that pulling a GPIO pin *low* will turn the relevant
    segment *on*. If you need to modify this behaviour, see the `inverted`
    parameter for the `display` method.

    !!! Note
        This list of entries in the `gpio_request` *must* be **exactly**
        seven entries long, or the class will throw a `ValueError` in the
        constructor.

    Parameters
    ----------

    gpio_request: list
        The pin-ordered list of GPIO pins to use for the segment positions
        'a' (as the first entry in the list) to 'g' (as the last entry in
        the list).

        **Note**: The `SegDisplay` class will also attempt to create the
        underlying GPIO object for each of the entries in the list. If
        the GPIO pins need to be initialised first, this must be done
        *before* calling this constructor.

    Raises
    ------

    ValueError
        The `gpio_request` is empty, or does not have exactly
        seven elements in the list.
    """
    self.pin_list = []

    if (gpio_request is None) or (not gpio_request):
        msg = "The GPIO Request List is empty"
        raise ValueError(msg)
    elif len(gpio_request) != DISPLAY_SEGMENTS:
        msg = "The GPIO Request List must be EXACTLY seven entries long"
        raise ValueError(msg)
    else:
        for segment in range(7):
            self.pin_list.append(Pin(gpio_request[segment], Pin.OUT))

display

display(
    character: int, pin_on: PIN_ON_SENSE = PIN_ON_SENSE.LOW
) -> None

Display the given character on the seven-segment display, using the _char_list as a guide for which pins to turn on or off. By default the display method will use the entries in the _char_list directly: if you need to invert the 'normal' sense, set the inverted parameter to True.

Parameters:

  • character (int) –

    The value to be displayed on the seven segment display, which must be between zero ('0') and nine ('9')

  • pin_on (PIN_ON_SENSE) –

    When set to "HIGH" the GPIO pins need to be set 'high' ('1') for the device segment to turn on (the typical behaviour for a common anode display). When set to "LOW" the GPIO pins need to be set 'low' ('0') for the device segment to turn on (the typical behaviour for a common cathode display.

Raises:

  • IndexError

    The character is not in a range that can be displayed.

Source code in lbutils/drivers/seven_segment.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
def display(self, character: int, pin_on: PIN_ON_SENSE = PIN_ON_SENSE.LOW) -> None:
    """Display the given `character` on the seven-segment display, using the
    `_char_list` as a guide for which pins to turn on or off. By default the
    `display` method will use the entries in the `_char_list` directly: if
    you need to invert the 'normal' sense, set the `inverted` parameter to
    `True`.

    Parameters
    ----------

    character: int
        The value to be displayed on the seven segment display, which must be
        between zero ('0') and nine ('9')
    pin_on: PIN_ON_SENSE, optional
        When set to `"HIGH"` the GPIO pins need to be set 'high' ('1') for
        the device segment to turn on (the typical behaviour for a common
        anode display). When set to `"LOW"` the GPIO pins need to be set
        'low' ('0') for the device segment to turn on (the typical behaviour
        for a common cathode display.

    Raises
    ------

    IndexError
        The `character` is not in a range that can be displayed.
    """
    # For a character in the valid range...
    if 0 <= character <= NUM_CHARACTERS:
        if pin_on == PIN_ON_SENSE.LOW:
            # ... if the request is to display in the non-inverted form, then
            # select the row in `_char_list` corresponding to the character to
            # be displayed and then set in turn each of the GPIO pins corresponding
            # to the segment values either high or low depending on the column
            # value in `_char_list` for that segment value
            for pin in range(7):
                self.pin_list[pin].value(self._char_list[character][pin])
        else:
            # ... if the request is to display in the inverted form, then
            # select the row in `_char_list` corresponding to the character to
            # be displayed and then set in turn each of the GPIO pins corresponding
            # to the segment values either high or low depending on the *inverse* of
            # the column value in `_char_list` for that segment value
            for pin in range(7):
                self.pin_list[pin].value(not self._char_list[character][pin])
    else:
        msg = ("The display character must be between zero ('0') and nine ('9')",)
        raise IndexError(msg)

lbutils.drivers.SegHexDisplay

Simple hexadecimal driver for a seven-segment display, requiring seven GPIO pins.

Note: This driver will only display characters in the range '0' to 'F', and will raise a ValueError exception if the requested character is not in an appropriate range.

Source code in lbutils/drivers/seven_segment_hex.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
class SegHexDisplay:
    """Simple hexadecimal driver for a seven-segment display, requiring seven
    GPIO pins.

    **Note:** This driver will only display characters in the range '0' to
    'F', and will raise a `ValueError` exception if the requested character
    is not in an appropriate range.
    """

    _char_list = [
        [False, False, False, False, False, False, True],
        [True, False, False, True, True, True, True],
        [False, False, True, False, False, True, False],
        [False, False, False, False, True, True, False],
        [True, False, False, True, True, False, False],
        [False, True, False, False, True, False, False],
        [False, True, False, False, False, False, False],
        [False, False, False, True, True, True, True],
        [False, False, False, False, False, False, False],
        [False, False, False, True, True, False, False],
        [False, False, False, True, False, False, False],
        [True, True, False, False, False, False, False],
        [False, True, True, False, False, False, True],
        [True, False, False, False, False, True, False],
        [False, True, True, False, False, False, False],
        [False, True, True, True, False, False, False],
    ]
    """Defines how characters are rendered, from zero ('0') in the first entry
    to nine ('9') as the last entry.

    Note that pins which are listed here as `False` will be _on_ using the
    default options to the `display` method.
    """

    def __init__(self, gpio_request: list) -> None:
        """Initialise a seven-segment display, using the user supplied list of
        GPIO pins in `gpio_request` as reference for pins to drive.

        This class also assume a common anode seven-segment display by default,
        and so will assume that pulling a GPIO pin _low_ will turn the relevant
        segment _on_. If you need to modify this behaviour, see the `inverted`
        parameter for the `display` method.

        !!! Note
            This list of entries in the `gpio_request` _must_ be **exactly**
            seven entries long, or the class will throw a `ValueError` in the
            constructor.

        Parameters
        ----------

        gpio_request: list
            The pin-ordered list of GPIO pins to use for the segment positions
            'a' (as the first entry in the list) to 'g' (as the last entry
            in the list).

            **NOTE**: The `SegDisplay` class will also attempt to create the
            underlying GPIO object for each of the entries in the list. If
            the GPIO pins need to be initialised first, this must be done
            _before_ calling this constructor.

        Raises
        ------

        ValueError
            The `gpio_request` is empty, or does not have exactly
            seven elements in the list.
        """
        self.pin_list = []

        if (gpio_request is None) or (not gpio_request):
            msg = "The GPIO Request List is empty"
            raise ValueError(msg)
        elif len(gpio_request) != DISPLAY_SEGMENTS:
            msg = "The GPIO Request List must be EXACTLY seven entries long"
            raise ValueError(msg)
        else:
            for segment in range(7):
                self.pin_list.append(Pin(gpio_request[segment], Pin.OUT))

    def display(self, character: int, pin_on: PIN_ON_SENSE = PIN_ON_SENSE.LOW) -> None:
        """Display the given `character` on the seven-segment display, using the
        `_char_list` as a guide for which pins to turn on or off. By default the
        `display` method will use the entries in the `_char_list` directly: if
        you need to invert the 'normal' sense, set the `inverted` parameter to
        `True`.

        Parameters
        ----------

        character: int or str
            The value to be displayed on the seven segment display. The value
            must be either a `str` or an `int`, and will be interpreted as
            follows:

            `int`: The value must be between zero ('0') and sixteen decimal
            ('F'), and will be interpreted as a single, hexadecimal digit.

            `str`: The value will be interpreted directly as a hexadecimal digit,
            and must be in the range `[0..F]`.

            If the type does not conform to the above, then a `TypeError` will be
            raised.
        pin_on: PIN_ON_SENSE, optional
            When set to `"HIGH"` the GPIO pins need to be set 'high' ('1') for
            the device segment to turn on (the typical behaviour for a common
            anode display). When set to `"LOW"` the GPIO pins need to be set
            'low' ('0') for the device segment to turn on (the typical behaviour
            for a common cathode display.

        Raises
        ------

        IndexError
            The `character` is not in a range that can be displayed.
        TypeError
            The `character` is not either an `int` or a `str`
        """
        # Convert a decimal integer in the range [0..15], and then display
        if isinstance(character, int):
            # For a character in the valid range...
            if 0 <= character <= NUM_CHARACTERS:
                if pin_on == PIN_ON_SENSE.LOW:
                    # ... if the request is to display in the non-inverted form, then
                    # select the row in `_char_list` corresponding to the character to
                    # be displayed and then set in turn each of the GPIO pins corresponding
                    # to the segment values either high or low depending on the column
                    # value in `_char_list` for that segment value
                    for pin in range(7):
                        self.pin_list[pin].value(self._char_list[character][pin])
                else:
                    # ... if the request is to display in the inverted form, then
                    # select the row in `_char_list` corresponding to the character to
                    # be displayed and then set in turn each of the GPIO pins corresponding
                    # to the segment values either high or low depending on the _inverse_ of
                    # the column value in `_char_list` for that segment value
                    for pin in range(7):
                        self.pin_list[pin].value(not self._char_list[character][pin])
            else:
                msg = (
                    "The display character must be between zero ('0') and sixteen ('F')",
                )
                raise IndexError(msg)

        # Convert a string integer in the range [0..F], and then display
        elif isinstance(character, str):
            # Normalise the character by converting to upper case
            normalised_character = character.upper()

            # Check if this normalise character is a valid hexadecimal digit...
            if normalised_character in ASCII_HEX_DIGITS:
                # ... if so, convert the hexadecimal string to an integer, so we can use
                # this as the index for the character lookup
                _char_list_index = int(normalised_character, 16)

                if pin_on == PIN_ON_SENSE.LOW:
                    # If the request is to display in the non-inverted form, then
                    # select the row in `_char_list` corresponding to the `_char_list_index` to
                    # be displayed and then set in turn each of the GPIO pins corresponding
                    # to the segment values either high or low depending on the column
                    # value in `_char_list` for that segment value
                    for pin in range(7):
                        self.pin_list[pin].value(self._char_list[_char_list_index][pin])

                else:
                    # If the request is to display in the inverted form, then
                    # select the row in `_char_list` corresponding to the `_char_list_index` to
                    # be displayed and then set in turn each of the GPIO pins corresponding
                    # to the segment values either high or low depending on the _inverse_ of
                    # the column value in `_char_list` for that segment value
                    for pin in range(7):
                        self.pin_list[pin].value(
                            not self._char_list[_char_list_index][pin],
                        )
            else:
                msg = ("The display character must be a string between '0' and 'F'",)
                raise IndexError(msg)

        # If we can't convert the input `character`, raise an exception
        else:
            msg = (
                "The 'character' parameter must either be an integer ('int') or a"
                " string ('str') type.",
            )
            raise TypeError(msg)

Functions

__init__

__init__(gpio_request: list) -> None

Initialise a seven-segment display, using the user supplied list of GPIO pins in gpio_request as reference for pins to drive.

This class also assume a common anode seven-segment display by default, and so will assume that pulling a GPIO pin low will turn the relevant segment on. If you need to modify this behaviour, see the inverted parameter for the display method.

Note

This list of entries in the gpio_request must be exactly seven entries long, or the class will throw a ValueError in the constructor.

Parameters:

  • gpio_request (list) –

    The pin-ordered list of GPIO pins to use for the segment positions 'a' (as the first entry in the list) to 'g' (as the last entry in the list).

    NOTE: The SegDisplay class will also attempt to create the underlying GPIO object for each of the entries in the list. If the GPIO pins need to be initialised first, this must be done before calling this constructor.

Raises:

  • ValueError

    The gpio_request is empty, or does not have exactly seven elements in the list.

Source code in lbutils/drivers/seven_segment_hex.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
def __init__(self, gpio_request: list) -> None:
    """Initialise a seven-segment display, using the user supplied list of
    GPIO pins in `gpio_request` as reference for pins to drive.

    This class also assume a common anode seven-segment display by default,
    and so will assume that pulling a GPIO pin _low_ will turn the relevant
    segment _on_. If you need to modify this behaviour, see the `inverted`
    parameter for the `display` method.

    !!! Note
        This list of entries in the `gpio_request` _must_ be **exactly**
        seven entries long, or the class will throw a `ValueError` in the
        constructor.

    Parameters
    ----------

    gpio_request: list
        The pin-ordered list of GPIO pins to use for the segment positions
        'a' (as the first entry in the list) to 'g' (as the last entry
        in the list).

        **NOTE**: The `SegDisplay` class will also attempt to create the
        underlying GPIO object for each of the entries in the list. If
        the GPIO pins need to be initialised first, this must be done
        _before_ calling this constructor.

    Raises
    ------

    ValueError
        The `gpio_request` is empty, or does not have exactly
        seven elements in the list.
    """
    self.pin_list = []

    if (gpio_request is None) or (not gpio_request):
        msg = "The GPIO Request List is empty"
        raise ValueError(msg)
    elif len(gpio_request) != DISPLAY_SEGMENTS:
        msg = "The GPIO Request List must be EXACTLY seven entries long"
        raise ValueError(msg)
    else:
        for segment in range(7):
            self.pin_list.append(Pin(gpio_request[segment], Pin.OUT))

display

display(
    character: int, pin_on: PIN_ON_SENSE = PIN_ON_SENSE.LOW
) -> None

Display the given character on the seven-segment display, using the _char_list as a guide for which pins to turn on or off. By default the display method will use the entries in the _char_list directly: if you need to invert the 'normal' sense, set the inverted parameter to True.

Parameters:

  • character (int) –

    The value to be displayed on the seven segment display. The value must be either a str or an int, and will be interpreted as follows:

    int: The value must be between zero ('0') and sixteen decimal ('F'), and will be interpreted as a single, hexadecimal digit.

    str: The value will be interpreted directly as a hexadecimal digit, and must be in the range [0..F].

    If the type does not conform to the above, then a TypeError will be raised.

  • pin_on (PIN_ON_SENSE) –

    When set to "HIGH" the GPIO pins need to be set 'high' ('1') for the device segment to turn on (the typical behaviour for a common anode display). When set to "LOW" the GPIO pins need to be set 'low' ('0') for the device segment to turn on (the typical behaviour for a common cathode display.

Raises:

  • IndexError

    The character is not in a range that can be displayed.

  • TypeError

    The character is not either an int or a str

Source code in lbutils/drivers/seven_segment_hex.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def display(self, character: int, pin_on: PIN_ON_SENSE = PIN_ON_SENSE.LOW) -> None:
    """Display the given `character` on the seven-segment display, using the
    `_char_list` as a guide for which pins to turn on or off. By default the
    `display` method will use the entries in the `_char_list` directly: if
    you need to invert the 'normal' sense, set the `inverted` parameter to
    `True`.

    Parameters
    ----------

    character: int or str
        The value to be displayed on the seven segment display. The value
        must be either a `str` or an `int`, and will be interpreted as
        follows:

        `int`: The value must be between zero ('0') and sixteen decimal
        ('F'), and will be interpreted as a single, hexadecimal digit.

        `str`: The value will be interpreted directly as a hexadecimal digit,
        and must be in the range `[0..F]`.

        If the type does not conform to the above, then a `TypeError` will be
        raised.
    pin_on: PIN_ON_SENSE, optional
        When set to `"HIGH"` the GPIO pins need to be set 'high' ('1') for
        the device segment to turn on (the typical behaviour for a common
        anode display). When set to `"LOW"` the GPIO pins need to be set
        'low' ('0') for the device segment to turn on (the typical behaviour
        for a common cathode display.

    Raises
    ------

    IndexError
        The `character` is not in a range that can be displayed.
    TypeError
        The `character` is not either an `int` or a `str`
    """
    # Convert a decimal integer in the range [0..15], and then display
    if isinstance(character, int):
        # For a character in the valid range...
        if 0 <= character <= NUM_CHARACTERS:
            if pin_on == PIN_ON_SENSE.LOW:
                # ... if the request is to display in the non-inverted form, then
                # select the row in `_char_list` corresponding to the character to
                # be displayed and then set in turn each of the GPIO pins corresponding
                # to the segment values either high or low depending on the column
                # value in `_char_list` for that segment value
                for pin in range(7):
                    self.pin_list[pin].value(self._char_list[character][pin])
            else:
                # ... if the request is to display in the inverted form, then
                # select the row in `_char_list` corresponding to the character to
                # be displayed and then set in turn each of the GPIO pins corresponding
                # to the segment values either high or low depending on the _inverse_ of
                # the column value in `_char_list` for that segment value
                for pin in range(7):
                    self.pin_list[pin].value(not self._char_list[character][pin])
        else:
            msg = (
                "The display character must be between zero ('0') and sixteen ('F')",
            )
            raise IndexError(msg)

    # Convert a string integer in the range [0..F], and then display
    elif isinstance(character, str):
        # Normalise the character by converting to upper case
        normalised_character = character.upper()

        # Check if this normalise character is a valid hexadecimal digit...
        if normalised_character in ASCII_HEX_DIGITS:
            # ... if so, convert the hexadecimal string to an integer, so we can use
            # this as the index for the character lookup
            _char_list_index = int(normalised_character, 16)

            if pin_on == PIN_ON_SENSE.LOW:
                # If the request is to display in the non-inverted form, then
                # select the row in `_char_list` corresponding to the `_char_list_index` to
                # be displayed and then set in turn each of the GPIO pins corresponding
                # to the segment values either high or low depending on the column
                # value in `_char_list` for that segment value
                for pin in range(7):
                    self.pin_list[pin].value(self._char_list[_char_list_index][pin])

            else:
                # If the request is to display in the inverted form, then
                # select the row in `_char_list` corresponding to the `_char_list_index` to
                # be displayed and then set in turn each of the GPIO pins corresponding
                # to the segment values either high or low depending on the _inverse_ of
                # the column value in `_char_list` for that segment value
                for pin in range(7):
                    self.pin_list[pin].value(
                        not self._char_list[_char_list_index][pin],
                    )
        else:
            msg = ("The display character must be a string between '0' and 'F'",)
            raise IndexError(msg)

    # If we can't convert the input `character`, raise an exception
    else:
        msg = (
            "The 'character' parameter must either be an integer ('int') or a"
            " string ('str') type.",
        )
        raise TypeError(msg)