Using DL2416 intelligent 16-segment led displays with AVR

I have a project in mind where I need an old-school display, and got some of these awesome looking retro things (The actual part number is DL2416T, manufactured by Siemens):

You can still get them from ebay, most are probably extracted from old equipment. Be aware, that there is unit to unit variation in brightness, and if you need to stack more than one, then you want to get them with same brightness code if possible. Brightness code is a single letter printed on the side of the unit like this:

I ordered 4 displays, 3 were with 'E', one with 'D'. The D unit is just slightly less bright than E units.

Obviously I wanted to check these puppies out in action, and took a chance to also test out cheap AVR-s (or clones perhaps) that I ordered off ebay a while ago. So I wired up a simple test rig on solderless breadboard (click on the diagram to see it better):

As you can see, these displays are from pre-SPI era, and need a lot of I/O even in very minimal configuration without cursors and flashing display. Fortunately 8 of the 12 I/O lines can be reused for other functions when displays are not being written to. Capacitors are non-critical, 10n-100n are ok, and the setup may function without these. I used 8MHz ceramic resonator as clock source, just so that I dont have to figure out the new fuse settings.

In case you are wondering, I have a nonstandard programming connector that also passes on +5V from USB to device end, making in convenient for powering small temporary projects like this.

The displays cannot keep up with AVR running at 8MHz, I just added few delays into display write function, one of them may be unnecessary.


The sample code should be pretty clear, the Display class has a full display length buffer for easily creating scrolling displays. The refresh() method copies the entire buffer to display units, using write() to send each character out. The address manipulation in write() is to revert the address bits (because the display unit addresses characters from right to left) and to pull the CS of correct display low.

The putc() method displays a character, and if the display is already full then scrolls the display left to make room for the new character. putc() also converts any lowercase ASCII characters to upper case, because the DL2614 only has uppecase character set.

#include <avr/io.h> #include <util/delay.h> #include <string.h> class Display { uint8_t buf[8]; uint8_t cp; // next character address in buf void write(uint8_t adr,uint8_t data) { if (adr>3) adr=(adr&3)|4; // two lowest bits are adr, next two are chipsels else adr=(adr&3)|8; adr=adr^3; // the display addresses characters from right to left PORTB=(PORTB&0xf0)|adr|0x0c; // apply address, both CS high PORTD=data|0x80; // apply data, WR high PORTB=(PORTB&0xf0)|adr; // appropriate CS low _delay_us(1); PORTD=data&0x7f; // WR low _delay_us(1); PORTD=data|0x80; // WR high PORTB|=0x0c; // both CS high } void refresh(void) { for (uint8_t i=0;i<sizeof(buf);i++) write(i,buf[i]); } public: Display() { clear(); } void clear(void) { memset(buf,' ',sizeof(buf)); cp=0; refresh(); } void putc(char c) { uint8_t i; if (cp>=sizeof(buf)) { for (i=0;i<sizeof(buf)-1;i++) buf[i]=buf[i+1]; cp=i; } if (c>='a' && c<='z') c=c-('a'-'A'); buf[cp++]=c; } void puts(const char* s) { while (s && *s) putc(*s++); refresh(); } }; // creating a global instance like that works fine, except that // when the constructor is called the I/O is not yet set up, so // the display is not cleared Display display; /* I/O configuration ----------------- I/O pin direction DDR PORT PC0 unused output 1 0 PC1 unused output 1 0 PC2 unused output 1 0 PC3 unused output 1 0 PC4 unused output 1 0 PC5 unused output 1 0 PD0 D0 output 1 0 PD1 D1 output 1 0 PD2 D2 output 1 0 PD3 D3 output 1 0 PD4 D4 output 1 0 PD5 D5 output 1 0 PD6 D6 output 1 0 PD7 /WR output 1 1 PB0 A0 output 1 0 PB1 A1 output 1 0 PB2 /CE1 output 1 1 PB3 /CE2 output 1 1 PB4 unused input,pullup 0 1 PB5 unused input,pullup 0 1 */ int main(void) { MCUSR=0; MCUCR=0; // I/O directions DDRC=0x3f; DDRD=0xff; DDRB=0x0f; // initial state PORTC=0x00; PORTD=0x80; PORTB=0x3c; // display.clear(); display.puts("zigzaggy"); while (1); }

Build and flash it

$ avr-g++ -I. -I.. -g -mmcu=atmega328 -Os -fpack-struct -fshort-enums -funsigned-bitfields -funsigned-char -Wall \ -fno-exceptions -DF_CPU=8000000UL -I.. -c display_demo.cpp -o display_demo.o $ avr-g++ -Wl,-Map,display_demo.map -mmcu=atmega328 -o display_demo.elf display_demo.o text data bss dec hex filename 508 10 9 527 20f display_demo.elf avr-objcopy: --change-section-lma .eeprom=0x00000000 never used $ avrdude -P usb -B 10 -c usbtiny -p m328p -U lfuse:w:0xE6:m -U hfuse:w:0xDC:m \ -U efuse:w:0x07:m -U lock:w:0x3F:m -U flash:w:display_demo.hex -U eeprom:w:display_demo.eep

And there you have it

If you did everything right, and the ancient silicon still chooces right, then you should be seeing this now:

Copyright © Madis Kaal 2000-