A Place Where No Dreams Come True...

Current Topic: There are many types of LED Displays and Sensors. From discrete, single color, LED indicators to numeric, alpha and graphic displays to specialized narrow-band wavelength optical emitters.

Implementing A 7-Segment Multi-Digit Display Using Background Processing.

Background processing. What does that mean. Well the majority of the work is implemented as an Interrupt service routine that operates in the background as a 'Driver'.

This is necessary for two reasons. First - The AVR processor has only a limited amount of (combined) current that it can deliver in total through its various I/O pins. The number of total segments that a multi-digit display needs exceeds the total available by the number of digits. Basically the Arduino only has the power available to drive one digit at a time. That's where multiplexing comes in. Which needs synchronization. Which needs a timer. Which is best implemented as interrupt code operating in the background. Second - And of utmost importance is that the code that does the the actual scanning (multiplexing) of the digits be hidden and abstracted from upper layers of code so that the hardware is isolated from the application and the rule of 'Only One Digit' at a time can be enforced.

Creating A Proper Interface Between Hardware And Software.

To demonstrate a reasonable background interface to an 8-digit 7-segment LED array consider a display with each common (anode or cathode) port output pins and the remaining segments (decimal-point and colon) to seperate port output pins. Using a set of compile time '#define' statements code can be generated which is 'steered' by the preprocessor definitions making it easily adaptable to different hardware configurations.

To accomplish this a hardware timer is used. The subsequent timer interrupt is used to advance a digit in the multiplexed array and using a shared buffer which represents the segment configuration (one byte for each digit). Thus: No matter what is in the segment array only one digit at a time is energized. Additionally the timer value is buffered which is useful as a 'timer tick' for regular code processing. Perhaps a delay counter or clock value.

Here's where the hidden part comes in. Important to any interface is limiting access to interface buffers. This is done through the interface methods exposed to upper layer code. A.K.A. A set of low level functions that provide the actual interface so that upper code has no idea where the actual data is. The other benefit is that the interface validates the access making sure that it is within the context of the interface (limits).

;-----------------------------------------------------------------------------
; file: multiled.S
;
; Smegduino Multi-Digit 7-Segment LED Display multiplexing example.
;
;  A simple hardware manipulation demonstration including a reasonably
;   efficient Interrupt Service Routine to manage the hardware in the
;   background with little impact on foreground linear processing. There is
;   also limited configuration support for Timer-1(A) used to generate the
;   actual strobe (multiplex) interrupt.
;
; Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com
;
;  All Smegware software is free; you can redistribute it and/or modify
;  it under the terms of the GNU General Public License as published by
;  the Free Software Foundation; either version 2 of the License, or
;  (at your option) any later version.
;
;  This software is distributed in the hope that it will be useful,
;  but WITHOUT ANY WARRANTY; without even the implied warranty of
;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;  GNU General Public License for more details.
;
; In General - This code multiplexes a seven-segment multi-digit numeric
;  LED display. It can support common-anode as well as common-cathode
;  displays by updating the strobe array. It is not only a very efficient
;  method but necessary on the Arduino since the current draw for all the
;  LEDs (8 x 8) would exceed the maximum power (current) capabilities.
; Note: This code is written exclusively for the mega256 configuration and
;  its specific port definition but is easily reconfigured.
;
; Circuit:
;  The digit strobe is on PORTA where PA7 = digit-7 ... PA0 = digit-0 an is
;   by default set for Active-Low (common-cathode).
;
;  The segments are connected to PORTC and are Active-High (driven anode).
;  Where: PC7 = segment-C
;         PC6 = segment-DP (decimal point)
;         PC5 = segment-A
;         PC4 = segment-E
;         PC3 = segment-D
;         PC2 = segment-G
;         PC1 = segment-B
;         PC0 = segment-F
;         _____
;  And:  |  A  |
;       F|     |B
;        |_____|
;        |  G  |
;       E|     |C
;        |_____|
;           D   -(DP)
;
; Additionally: Each anode segment is limited through a 680-ohm resistor.
;-----------------------------------------------------------------------------
;
; History...
;
;   $Source$
;   $Author$
; $Revision$
;
; $Log$
;
;------------------------------------------------------------------------------
#define __SFR_OFFSET 0
#include <avr/io.h>
;-----------------------------------------------------------------------------
; Convenience macros.
#define RTEMP R0
#define RZERO R1
#define RL R18
#define RH R19
#define RPARAM2L R22
#define RPARAM2H R23
#define RPARAM1L R24
#define RPARAM1H R25
#define RPARAM1 RPARAM1L
#define RPARAM2 RPARAM2L

#define COUNTL RPARAM1L
#define COUNTH RPARAM1H
#define COUNT  COUNTLxx
;-----------------------------------------------------------------------------
; TCCRnA register bit definitions.
.equ BIT_WGM0,   0B00000001
.equ BIT_WGM1,   0B00000010
.equ BIT_COMC0,  0B00000100
.equ BIT_COMC1,  0B00001000
.equ BIT_COMB0,  0B00010000
.equ BIT_COMB1,  0B00100000
.equ BIT_COMA0,  0B01000000
.equ BIT_COMA1,  0B10000000
; TCCRnB register bit definitions.
.equ BIT_CS0,    0B00000001
.equ BIT_CS1,    0B00000010
.equ BIT_CS2,    0B00000100
.equ BIT_WGM2,   0B00001000
.equ BIT_WGM3,   0B00010000
.equ BIT_ICES,   0B01000000
.equ BIT_ICNC,   0B10000000
; TCCRnC register bit definitions.
.equ BIT_FOCC,   0B00100000
.equ BIT_FOCB,   0B01000000
.equ BIT_FOCA,   0B10000000
; TIMSKn register bit definitions.
.equ BIT_TOIE,   0B00000001
.equ BIT_OCIEA,  0B00000010
.equ BIT_OCIEB,  0B00000100
.equ BIT_OCIEC,  0B00001000
.equ BIT_ICIE,   0B00100000
; TIFRn register bit definitions.
.equ BIT_TOV,    0B00000001
.equ BIT_OCFA,   0B00000010
.equ BIT_OCFB,   0B00000100
.equ BIT_OCFC,   0B00001000
.equ BIT_ICF,    0B00100000
; Timer1 register definitions.
.equ REG_TIFR1,  0X16
.equ REG_TIMSK1, 0X6F
.equ REG_TCCR1A, 0X80
.equ REG_TCCR1B, 0X81
.equ REG_TCCR1C, 0X82
.equ REG_TCNT1L, 0X84
.equ REG_TCNT1H, 0X85
.equ REG_ICR1L,  0X86
.equ REG_ICR1H,  0x87
.equ REG_OCR1AL, 0X88
.equ REG_OCR1AH, 0X89
;-----------------------------------------------------------------------------
; LED digit sync active low.
.equ REG_PORTA, 0X02
.equ REG_DDRA,  0X01
.equ REG_PINA,  0X00
; LED segment source active high.
.equ REG_PORTC, 0X08
.equ REG_DDRC,  0X07
.equ REG_PINC,  0X06
;-----------------------------------------------------------------------------
; Interrupt tick (count) register - 32-bits.
.lcomm timer1_count,4               ; Timer1 compare A interrupt clock tick.

; LED digit counter.
.lcomm digcnt,1                     ; Strobe digit vector.

; LED segment array.
.lcomm led_segments,8               ; Digit segment data.

; LED digit strobe array in PROM - Active-low (common-cathode).
.text
strobe: .byte 0X7F,0XBF,0XDF,0XEF,0XF7,0XFB,0XFD,0XFE

;-----------------------------------------------------------------------------
; LED interface port initialization.
.global led_init_multiseg
led_init_multiseg:
    LDI     RL,0X00                 ; Initial - No Segments - Active-high.
    OUT     REG_PORTC,RL            ; Set Segments.
    LDI     RL,0XFF                 ; Initial - No Digits - Active-low.
    OUT     REG_PORTA,RL            ; Set Digits.
    OUT     REG_DDRA,RL             ; Output port.
    OUT     REG_DDRC,RL             ; Output port.
    RET
;-----------------------------------------------------------------------------
; LED interface port initialization.
.global led_set_digit
led_set_digit:
    LDI     ZL,lo8(led_segments)    ; Segment variable data
    LDI     ZH,hi8(led_segments)    ;  address.
    ANDI    RPARAM1L,7              ; Limit range to protect local data.
    ADD     ZL,RPARAM1L             ; Point to next digit segments
    ADC     ZH,RZERO                ;  address.
    ST      Z,RPARAM2L              ; Segment data.
    RET
;-----------------------------------------------------------------------------
; Set timer 1 to CTC mode 2.
.global timer1_clear_compare
timer1_clear_compare:
    LDI     RL,0                    ; Mode 4 - CTC - No pin output. 
    STS     REG_TCCR1A,RL
    LDI     RL,(BIT_CS0 | BIT_WGM2) ; Mode 4 - CTC - No prescaler.
    STS     REG_TCCR1B,RL 
    RET
;-----------------------------------------------------------------------------
; Set timer 1 counter register.
.global timer1_set_count
timer1_set_count:
    IN      RL,SREG                 ; Preserve status register.
    CLI                             ; Required for 16-bit register coherency.
    STS     REG_TCNT1H,COUNTH       ; Update timer counter register
    STS     REG_TCNT1L,COUNTL       ;  with 16 bit count.
    OUT     SREG,RL                 ; Restore original processor state.
    RET
;-----------------------------------------------------------------------------
; Set timer 1 compare A register.
.global timer1_set_compare_a
timer1_set_compare_a:
    IN      RL,SREG                 ; Preserve status register.
    CLI                             ; Required for 16-bit register coherency.
    STS     REG_OCR1AH,COUNTH       ; Update compare capture register
    STS     REG_OCR1AL,COUNTL       ;  with 16 bit count.
    OUT     SREG,RL                 ; Restore original processor state.
    RET
;-----------------------------------------------------------------------------
; Enable timer 1 compare A interrupt enable.
.global timer1_enable_ocie1a
timer1_enable_ocie1a:
    LDS     RL,REG_TIMSK1           ; Current contents.
    SBR     RL,BIT_OCIEA            ; Set output compare A compare bit.
    STS     REG_TIMSK1,RL           ; Enable interrupt.
    RET
;-----------------------------------------------------------------------------
; Timer 1 get tick count (32-bit).
.global timer1_get_tick
timer1_get_tick:
    LDS     RPARAM2L,timer1_count   ;
    LDS     RPARAM2H,timer1_count+1 ;
    LDS     RPARAM1L,timer1_count+2 ;
    LDS     RPARAM1H,timer1_count+3 ;
    RET
;-----------------------------------------------------------------------------
; Timer 1 set tick count (32-bit) Needed when using as a clock.
.global timer1_set_tick
timer1_set_tick:
    IN      RL,SREG                 ; Preserve status register.
    CLI                             ; Required for 32-bit register coherency.
    STS     timer1_count+0,RPARAM2L ;
    STS     timer1_count+1,RPARAM2H ;
    STS     timer1_count+2,RPARAM1L ;
    STS     timer1_count+3,RPARAM1H ;
    OUT     SREG,RL                 ; Restore original processor state.
    RET
;-----------------------------------------------------------------------------
; Timer 1 Output-Compare-A interrupt service routine.
.global TIMER1_COMPA_vect
TIMER1_COMPA_vect:
    PUSH    ZL                      ; Preserve linear state.
    IN      ZL,SREG                 ;
    PUSH    ZL                      ; Preserve Status register.
    PUSH    ZH                      ; Preserve linear state.
    PUSH    RL                      ;
    PUSH    RZERO                   ;
; Update Tick counter.
    CLR     RZERO                   ; Force.
    LDS     ZL,timer1_count         ; Tick count low-byte.
    LDS     ZH,timer1_count+1       ; Tick count high-byte.
    ADIW    ZL,1                    ; Increment timer Tick counter
    STS     timer1_count,ZL         ; Update Tick count.
    STS     timer1_count+1,ZH       ;
    LDS     RL,timer1_count+2       ; Tick count higher-byte.
    ADC     RL,RZERO                ;  32-bit operation.
    STS     timer1_count+2,RL       ;
    LDS     RL,timer1_count+3       ; Tick count highest-byte.
    ADC     RL,RZERO                ;  32-bit operation.
    STS     timer1_count+3,RL       ;
; Strobe next digit.
    LDI     ZL,lo8(strobe)          ; Digit strobe constants
    LDI     ZH,hi8(strobe)          ;  address.
    LDS     RL,digcnt               ; Current digit.
    ADD     ZL,RL                   ; Point to current digit
    ADC     ZH,RZERO                ;  vector.
    OUT     REG_PORTC,RZERO         ; Void current digit segments.
    LPM     RZERO,Z                 ; Next digit strobe.
    OUT     REG_PORTA,RZERO         ; Update digit strobe.
; Set digit segments.
    CLR     RZERO                   ; Force.
    LDI     ZL,lo8(led_segments)    ; Segment variable data
    LDI     ZH,hi8(led_segments)    ;  address.
    ADD     ZL,RL                   ; Point to current digit.
    ADC     ZH,RZERO                ; Next digit segments.
    LD      RZERO,Z                 ; Next digit.
    OUT     REG_PORTC,RZERO         ; Update digit segments.
; Calculate digit for next strobe.
    INC     RL                      ; Point to next digit.
    CPI     RL,8                    ; Check overflow.
    BRCS    .no_roll                ; Still valid.
    CLR     RL                      ; Roll over (clip at 8).
.no_roll:
    STS     digcnt,RL               ; Cache next digit.
; Restore and return.
    POP     RZERO                   ; Restore pre-interrupt state.
    POP     RL                      ;
    POP     ZH                      ;
    POP     ZL                      ;
    OUT     SREG,ZL                 ; Restore status Register.
    POP     ZL                      ;
    RETI                            ; Re-Enter linear processing state.
;-----------------------------------------------------------------------------
;end: multiled.S

Creating A C/C++ Interface.

For the compiler to use code written strictly in Assembly language a set of conditions must be met. First... The Assembly code must export (make global) the objects necessary. Second... A definition (header) file must properly 'prototype' the interface so that the compiler understands the data (types) passed between the calls (code). Third... The Assembly code must understand how the compiler passes data (operands) to Assembly code. And. What resources must be saved by the Assembly code so that it does not clobber the compiler generated code when it returns.

//----------------------------------------------------------------------------
// file:multiled.h
//
//----------------------------------------------------------------------------

extern "C" {
#include <stdint.h>
extern void timer1_clear_compare(void);
extern void timer1_set_count(uint16_t);
extern void timer1_set_compare_a(uint16_t);
extern void timer1_enable_ocie1a(void);
extern void timer1_ocie1a_int(void);
extern uint32_t timer1_get_tick(void);
extern void led_set_digit(uint8_t,uint8_t);
extern void led_init_multiseg(void);
extern void led_set_digit(uint8_t,uint8_t);
}

//----------------------------------------------------------------------------
// end:multiled.h

A Possible Application Use Of This Driver.

Setting the timer at ~500 Hz and using the timer tick (count) as a clock it is easy to display elapsed seconds in 1000s with a range of about 100 days. It is also suitable for a timer input to a delay function.

//-----------------------------------------------------------------------------
// file: display.ino
//
// Smegduino LED display demonstration.
//
//  A simple interface for a multi-digit 7-segment multiplexed LED display.
//
// Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//          _____
// where:  |  A  |
//        F|     |B
//         |_____|
//         |  G  |
//        E|     |C
//         |_____|
//            D    (DP)
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//-----------------------------------------------------------------------------

#include <multiled.h>

//-----------------------------------------------------------------------------
// Define string constants in program memory (PROM).

static const char STR_PU_PU[] PROGMEM = "%u %u";

//-----------------------------------------------------------------------------
// Define LED segment placememnts for this implememtation.

#define SEGMENT_NONE 0X00
#define SEGMENT_A 0X20
#define SEGMENT_B 0X02
#define SEGMENT_C 0X80
#define SEGMENT_D 0X08
#define SEGMENT_E 0X10
#define SEGMENT_F 0X01
#define SEGMENT_G 0X04
#define SEGMENT_DP 0X40

#define NUMBER_ZERO (SEGMENT_A | SEGMENT_B | SEGMENT_C | SEGMENT_D | SEGMENT_E | SEGMENT_F)
#define NUMBER_ONE (SEGMENT_B | SEGMENT_C)
#define NUMBER_TWO (SEGMENT_A | SEGMENT_B | SEGMENT_D | SEGMENT_E | SEGMENT_G)
#define NUMBER_THREE (SEGMENT_A | SEGMENT_B | SEGMENT_C | SEGMENT_D | SEGMENT_G)
#define NUMBER_FOUR ( SEGMENT_B | SEGMENT_C | SEGMENT_F | SEGMENT_G)
#define NUMBER_FIVE ( SEGMENT_A | SEGMENT_C | SEGMENT_D | SEGMENT_F | SEGMENT_G)
#define NUMBER_SIX ( SEGMENT_C | SEGMENT_D | SEGMENT_E | SEGMENT_F | SEGMENT_G)
#define NUMBER_SEVEN ( SEGMENT_A | SEGMENT_B | SEGMENT_C)
#define NUMBER_EIGHT ( SEGMENT_A | SEGMENT_B | SEGMENT_C | SEGMENT_D | SEGMENT_E | SEGMENT_F | SEGMENT_G)
#define NUMBER_NINE ( SEGMENT_A | SEGMENT_B | SEGMENT_C | SEGMENT_F | SEGMENT_G)
#define NUMBER_MINUS (SEGMENT_G)

//-----------------------------------------------------------------------------

static uint8_t const segments[10] PROGMEM = {
  NUMBER_ZERO,
  NUMBER_ONE,
  NUMBER_TWO,
  NUMBER_THREE,
  NUMBER_FOUR,
  NUMBER_FIVE,
  NUMBER_SIX,
  NUMBER_SEVEN,
  NUMBER_EIGHT,
  NUMBER_NINE
};

#define DPY_MODE_DEFAULT 0
#define DPY_MODE_CLOCK 1
#define DPY_MODE_ACCEL 2
#define DPY_MODE_TICK 3
#define DPY_MODE_RATE 4

static uint8_t _mode = DPY_MODE_DEFAULT;
static uint32_t _clock = 0;

//-----------------------------------------------------------------------------

void display_init(void)
{
  uint8_t i;

  led_init_multiseg();
  timer1_clear_compare();
  timer1_set_compare_a(15990); // ~1ms = .001/sec (this mega2560).
  for(i = 0; i < 8; i++)
  {
    led_set_digit(i, SEGMENT_NONE);
  }
  timer1_enable_ocie1a();
}

//-----------------------------------------------------------------------------

void display_set_clock(uint32_t clk)
{
  timer1_set_tick(clk);
  _clock = 0;
}

//-----------------------------------------------------------------------------

void display_set_mode(uint8_t mode)
{
  _mode = mode;
}

//-----------------------------------------------------------------------------

void display_set_off(void)
{
  _mode = DPY_MODE_DEFAULT;
}

//-----------------------------------------------------------------------------

void display_update_tick(void)
{
  uint8_t i;
  uint32_t tick = timer1_get_tick() / 10;

  for(i = 0; i < 8; i++)
  {
    led_set_digit(i, pgm_read_byte(&segments[tick % 10]));
    tick /= 10;
  }
}

//-----------------------------------------------------------------------------

#define ACCEL_1G 8192
#define ACCEL_GAIN 100

void display_update_accel(void)
{
  int16_t n;

  n = (((float)mpu_get_accel_x() / ACCEL_1G) * ACCEL_GAIN);
  if(n < 0)
  {
    n = 0 - n;
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(6, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(7, pgm_read_byte_near(&segments[n / 10]));

  n = (((float)mpu_get_accel_y() / ACCEL_1G) * ACCEL_GAIN);
  if(n < 0)
  {
    led_set_digit(5, NUMBER_MINUS | SEGMENT_DP);
    n = 0 - n;
  }
  else
  {
    led_set_digit(5, SEGMENT_DP);
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(3, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(4, pgm_read_byte_near(&segments[n / 10]));

  n = (((float)mpu_get_accel_z() / ACCEL_1G) * ACCEL_GAIN);
  if(n < 0)
  {
    led_set_digit(2, NUMBER_MINUS | SEGMENT_DP);
    n = 0 - n;
  }
  else
  {
    led_set_digit(2, SEGMENT_DP);
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(0, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(1, pgm_read_byte_near(&segments[n / 10]));
}

//-----------------------------------------------------------------------------

//#define RATE_SCALE 12976
#define RATE_1DEG 131
#define RATE_GAIN 10

void display_update_rate(void)
{
  int16_t n;

  n = (((float)mpu_get_rate_x() / RATE_1DEG) * RATE_GAIN);
  if(n < 0)
  {
    n = 0 - n;
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(6, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(7, pgm_read_byte_near(&segments[n / 10]));

  n = (((float)mpu_get_rate_y() / RATE_1DEG) * RATE_GAIN);
  if(n < 0)
  {
    led_set_digit(5, NUMBER_MINUS | SEGMENT_DP);
    n = 0 - n;
  }
  else
  {
    led_set_digit(5, SEGMENT_DP);
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(3, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(4, pgm_read_byte_near(&segments[n / 10]));

  n = (((float)mpu_get_rate_z() / RATE_1DEG) * RATE_GAIN);
  if(n < 0)
  {
    led_set_digit(2, NUMBER_MINUS | SEGMENT_DP);
    n = 0 - n;
  }
  else
  {
    led_set_digit(2, SEGMENT_DP);
  }
  if(n > 99)
  {
    // Saturate at ~1-G.
    n = 99;
  }
  led_set_digit(0, pgm_read_byte_near(&segments[n % 10]));
  led_set_digit(1, pgm_read_byte_near(&segments[n / 10]));
}

//-----------------------------------------------------------------------------

void display_update_clock(void)
{
  uint16_t t;
  uint32_t tick = timer1_get_tick() / 100; // tenths of seconds.

  if(tick > _clock)
  {
    _clock = tick;
    led_set_digit(0, pgm_read_byte_near(&segments[tick % 10]));
    tick /= 10;
    t = tick % 60;
    led_set_digit(1, pgm_read_byte_near(&segments[t % 10]) | SEGMENT_DP);
    led_set_digit(2, pgm_read_byte_near(&segments[t / 10]));
    tick /= 60;
    t = tick % 60;
    led_set_digit(3, pgm_read_byte_near(&segments[t % 10]) | SEGMENT_DP);
    led_set_digit(4, pgm_read_byte_near(&segments[t / 10]));
    tick /= 60;
    t = tick % 24;
    led_set_digit(5, pgm_read_byte_near(&segments[t % 10]) | SEGMENT_DP);
    led_set_digit(6, pgm_read_byte_near(&segments[t / 10]));
    tick /= 24;
    led_set_digit(7, pgm_read_byte_near(&segments[tick % 10]) | SEGMENT_DP);
  }
}

//-----------------------------------------------------------------------------

void display_update(void)
{
  switch(_mode)
  {
    case DPY_MODE_CLOCK:
      display_update_clock();
      break;

    case DPY_MODE_ACCEL:
      display_update_accel();
      break;

    case DPY_MODE_TICK:
      display_update_tick();
      break;

    case DPY_MODE_RATE:
      display_update_rate();
      break;

    default:
      break;
  }
}

//-----------------------------------------------------------------------------

void display(void)
{
  while(1)
  {
    display_update();
    pause();
  }
}

//-----------------------------------------------------------------------------

void display_set_literal(void)
{
  led_set_digit(0, NUMBER_ZERO);
}

//-----------------------------------------------------------------------------
//end: display.ino

13124