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