Small LED dot matrix sign

Small LED dot matrix sign

This (5 rows by 28 columns) scrolling LED dot matrix digital sign was designed and built during November 2000 as a hobby and was my second ever microcontroller project. After building and programming it I left it working continuously for about 21 months, after that it was shelved and I only powered it occasionally when somebody wished to have a look at it.

Further down in this article are the schematic and its firmware source code.

I had hard time trying to take a nice photo of the sign while it was showing a message. In person it looks much nicer, the display looks red and not so orange/pink as in the photo, and the slight brightness difference between the LEDs rows is never as noticeable.

I wished to make it as small as possible but using only locally available parts, to keep it cheap. SMD parts are a rarity here and only transistors were available in SMD packaging. Only 89c52, 89c2051 and some PICs microcontrollers were locally available. I decided to use the AT89c2051 by Atmel and combined it with a 4017 CMOS IC and some transistors to make a demultiplexer that could drive all the LEDs. The resulting display is bright enough to be clearly visible in well lit indoor environments.

There was no way to fit a full sized (through-hole) MAX232 with all its caps so I decided to make a not so fully compliant but still reliable transistor based RS232 to 5V level converter. This converter was inspired by an schematic in some book or manual by Intel, I can't remember what the title of that book was.

In this photo, you can see the sign with its serial cable attached.

The memory where the messages are stored is a 28c08 serial eeprom, it can store 2048 bytes and is all available to the displayed message and its control characters.

The PCBs were made with double sided phenolic board. Here you can see a phenolic board that is 9 years old, somehow it looks aged and it also smells like old stuff. I think it looked nicer when new. I used that "press and peel blue" stuff for toner transfer which I don't like because it shrinks when heated. That caused a few problems with component fitting in this project. Now I use plain magazine paper and is much easier to get almost perfect results at first attempt. It is also way cheaper and can be readily obtained. After etching I tested the tracks and found that some weren't conducting current, so I soldered a thin wires along the offending traces. That is noticeable in some of the photos.

The PCBs were designed in the free DOS based Tango PCB Student Edition where I simply hand routed all traces following my pencil drawn schematic. Despite that method of PCB designing there were no mistakes at all and the little sign started to work at the first attempt.



Firmware

I programmed this sign in MCS-51 assembly but I didn't write that many comments, and after so long without working with 8051s I had a bit of trouble to understand it. The buffer is shared by the screen and the serial transfer code for writing the EEPROM, so the screen shows a lot of garbage when programming a new message. It can also be seen from the code that the characters are of variable width. An odd number indicates the last byte of a character. It uses less than 50% of the uC program memory, so a lot of functions and effects could be added. The only effect available in this code is variable message speed, which can be controlled by adding curly opening and closing brackets in the message.

The function FINDCHR simply subtracts 31 (space ASCII code -1) from the ASCII code of the character to be moved to the display buffer and then decreases B for each odd byte starting from the program address pointed by the CRAC label. This allows to find the data for any character of different widths. As it is inefficient it limits the max scrolling speed of the sign message, but the maximum scroll speed possible with this simple algorithm is plenty fast.

The commands supported by the serial port routines are: 52, which returns a character that allows a Windows computer program to identify the device, 53 followed by address high and low bytes and 16 data bytes writes the 16 bytes directly to the EEPROM, 54 followed by address high and low bytes returns 16 bytes from the memory. Quite simple ain't it?


$MOD51
$TITLE(PANTALLA DMX)
; ROLANDO CALLA Z / LPB / 4-11-2K 28-11-2K
;
;*********** DEF
SCL EQU P3.3
SDA EQU P3.2
SCK EQU P3.4
RST EQU P3.7
DIRH EQU 9 ;0000 00XX
DIRL EQU 8 ;XXXX XXXX
BUF EQU 30H
BUE EQU 40H
SCRLL EQU 0AH
SCBUF EQU 16
SCBUE EQU 29 + SCBUF ; 1:9 / 2:19 /3:30
SCLSP EQU 0BH
SCLSP2 EQU 0CH
G1 EQU P1.0
G2 EQU P1.1
G3 EQU P1.2
GN EQU 0DH
;^********** DEF
;
;
ORG 0
CLR G1
CLR G2
CLR G3
;ACALL DELAY
;
;
;*********** I2C INIT
SETB SDA
SETB SCL
MOV DIRL,#0
MOV DIRH,#0
MOV SP,#5FH
MOV SCRLL,#1
;^********** I2C INIT
;
;
AJMP INIT
;
;
;*********** GRAL
DELAY:
MOV R6,#0FFH
MOV R7,#0FFH
DLY:
DJNZ R7,DLY
DJNZ R6,DLY
RET
;^********** GRAL
;
;
;*********** SER INTV
ORG 23H
CLR ES
JBC TI,TED
CLR RI
;CHK IF >X<
CLR C
MOV A,SBUF
MOV B,A
ADD A,#0ABH
JC TED
MOV A,B
SUBB A,#52H
JB OV,TED
MOV B,#4
MUL AB
MOV DPTR,#SERVICES
JMP @A+DPTR
;
SERVICES:
ACALL DEVID ;[52] ID DEL DISPOSITIVO
AJMP WAITTI
ACALL RCV16EPR ;[53] SER >> 16 OCTETOS >> EEPROM
AJMP WAITTI
ACALL EPR16SND ;[54] EEPROM >> 16 OCTETOS >> SER
AJMP WAITTI
WAITTI: JNB TI,WAITTI
CLR TI
;
TED:
SETB ES
RETI
;^********** SER INTV
;
;
;*********** SER INIT
INIT:
CLR RST
MOV GN,#1
MOV SCLSP2,#4
MOV TMOD,#00100000B
MOV TH1,#0FDH
MOV TL1,#0FDH
SETB TR1
SETB EA
SETB ES
MOV SCON,#01010000B
MOV SBUF,#0A3H ;ID
JNB TI,$
CLR TI
ACALL START
ACALL SLVW
ACALL STOP
;^********** SER INIT
;
;
MOV R0,#(SCBUF-1);
FILL:
MOV @R0,#00H
INC R0
CJNE R0,#(SCBUE),FILL
;
;
FILL2:
MOV @R0,#0FFH
INC R0
CJNE R0,#(SCBUE+6),FILL2
;
;
MAIN: ; MAIN
DJNZ SCLSP,SHOW
MOV SCLSP,SCLSP2
ACALL SCROLL
SHOW:
ACALL SHOWSCR
JMP MAIN
;
;
;*********** PANTALLA
SCROLL:
DJNZ SCRLL,NRC
AJMP RC
NRC:
MOV R0,#(SCBUF-1)
MOV R1,#SCBUF
SL:
XCH A,@R1
XCH A,@R0
INC R1
INC R0
CJNE R0,#(SCBUE+6),SL
MOV (SCBUF-1),#0
RET
;
RC:
ACALL GETB
CJNE R5,#254,RCCNT
MOV DIRL,#0
MOV DIRH,#0
ACALL START
ACALL SLVW
ACALL STOP
ACALL GETB
RCCNT:
CJNE R5,#253,RCCNT2
DEC SCLSP2
AJMP RC
RCCNT2:
CJNE R5,#252,RCCNT3
INC SCLSP2
AJMP RC
RCCNT3:
ACALL FINDCHR
ACALL ADDCHR
AJMP NRC
;
FINDCHR:
MOV A,R5
CLR C
SUBB A,#1FH
MOV B,A
MOV R0,#0
MOV DPTR,#CRAC
;
FCL:
MOV A,R0
MOVC A,@A+DPTR
JNB ACC.0,NOEND
DEC B
NOEND:
INC R0
INC B
DJNZ B, FCL
MOV R5,0
RET
;
ADDCHR:
MOV SCRLL,#1
MOV R0,#(SCBUE+1)
MOV DPTR,#CRAC
ADCH:
MOV A,R5
MOVC A,@A+DPTR
CPL A
MOV @R0,A
CPL A
INC R0
INC R5
INC SCRLL
JNB ACC.0,ADCH
MOV @R0,#0FFH
RET
;
SHOWSCR:
MOV GN,#1
MOV R0,#(SCBUF-1)
GS: ;GROUP SET
INC R0
MOV A,@R0
ANL A,#11111000B
ORL A,GN
MOV @R0,A
CJNE R0,#(SCBUF+9),GCNT1
MOV A,GN
RL A
MOV GN,A
GCNT1:
CJNE R0,#(SCBUF+19),GCNT2
MOV A,GN
RL A
MOV GN,A
GCNT2:
CJNE R0,#SCBUE,GS
MOV R0,#(SCBUF-1)
SS:
INC R0
MOV P1,@R0
ACALL COLDLY
MOV A,P1
ORL A,#11111000B
MOV P1,A
CLR SCK
SETB SCK
CJNE R0,#SCBUE,SS
RET
;
COLDLY:
MOV R7,#0FFH
CDLY:
DJNZ R7,CDLY
MOV R7,#010H
CDLY2:
DJNZ R7,CDLY2
RET
;^********** PANTALLA
;
;
;*********** COMANDOS
DEVID:
MOV SBUF,#0A2H
RET
;
RCV16EPR:
PUSH 0
PUSH 1
ORL P1,#11111000B
JNB RI,$
CLR RI
MOV DIRH,SBUF
JNB RI,$
CLR RI
MOV DIRL,SBUF
MOV R0,#BUF
R16EL:
JNB RI,$
MOV @R0,SBUF
CLR RI
INC R0
CJNE R0,#BUE,R16EL
ACALL W16B
MOV SBUF,#0A4H
POP 1
POP 0
MOV DIRL,#0
MOV DIRH,#0
ACALL START
ACALL SLVW
ACALL STOP
MOV SCLSP2,#4
RET
;
EPR16SND:
PUSH 0
PUSH 1
ORL P1,#11111000B
JNB RI,$
CLR RI
MOV DIRH,SBUF
JNB RI,$
CLR RI
MOV DIRL,SBUF
ACALL R16B
MOV R0,#BUF
E16SL:
CLR TI
MOV SBUF,@R0
JNB TI,$
INC R0
CJNE R0,#BUE,E16SL
POP 1
POP 0
MOV DIRL,#0
MOV DIRH,#0
ACALL START
ACALL SLVW
ACALL STOP
MOV SCLSP2,#4
RET
;^********** COMANDOS
;
;
;*********** I2C
W16B:
MOV R0,#BUF
ACALL START
ACALL SLVW
W16L:
MOV 5,@R0
ACALL SNDBYTE
INC R0
CJNE R0,#BUE,W16L
ACALL STOP
ACALL TWR
RET
;
R16B:
ACALL START
ACALL SLVW
ACALL STOP
MOV R0,#BUF
R16L:
ACALL GETB
MOV @R0,5
INC R0
CJNE R0,#BUE,R16L
RET
;
GETB:
ACALL START
ACALL SLVR
ACALL RCVBYTE
ACALL STOP
RET
;
START:
SETB SDA
SETB SCL
JNB SDA, START
JNB SCL, START
NOP
CLR SDA
nop
nop
nop
nop
nop
CLR SCL
RET
;
STOP:
CLR SDA
NOP
NOP
SETB SCL
NOP
NOP
NOP
NOP
NOP
SETB SDA
NOP
NOP
NOP
NOP
NOP
NOP
NOP
RET
;
SLVR:
MOV R5,#10100001B
MOV A,DIRH
RL A
ORL A,R5
MOV R5,A
ACALL SNDBYTE
RET
;
SLVW:
MOV R5,#10100000B
MOV A,DIRH
RL A
ORL A,R5
MOV R5,A
ACALL SNDBYTE
MOV R5,DIRL
ACALL SNDBYTE
RET
;
SNDBYTE:
MOV R4,#8
MOV A,R5
SNL:
RLC A
MOV SDA,C
NOP
SETB SCL
NOP
NOP
NOP
NOP
CLR SCL
DJNZ R4,SNL
ACALL WACK
RET
;
RCVBYTE:
SETB SDA
MOV R4,#8
CLR SCL
RNL:
NOP
NOP
NOP
SETB SCL
NOP
NOP
MOV C,SDA
RLC A
CLR SCL
DJNZ R4,RNL
MOV R5,A
RET
;
WACK:
SETB SDA
NOP
NOP
SETB SCL
NOP
NOP
NOP
NOP
WAL:
MOV C,SDA
JC WAL
CLR SCL
RET
;
TWR:
MOV R6,#0EH
MOV R7,#0FFH
TWL:
DJNZ R7,TWL
DJNZ R6,TWL
RET
;^********** I2C
;
;
;*********** GRAL
;^********** GRAL
;
;
;*********** CARACTERES
CRAC:
DB 1H
; [ESPACIO]
DB 001H
; !
DB 0E9H
; "
DB 0C0H,000H,0C1H
; #
DB 050H,0F8H,050H,0F8H,051H
; $
DB 048H,0A8H,0F8H,0A8H,091H
; %
DB 0C8H,0D0H,020H,058H,099H
; &
DB 050H,0A8H,068H,010H,029H
; '
DB 0C1H
; (
DB 070H,089H
; )
DB 088H,071H
; *
DB 0A8H,070H,0F8H,070H,0A9H
; +
DB 020H,070H,021H
; ,
DB 008H,011H
; -
DB 020H,020H,021H
; .
DB 009H
; /
DB 008H,010H,020H,040H,081H
; 0
DB 070H,088H,088H,071H
; 1
DB 048H,0F8H,009H
; 2
DB 048H,098H,0A8H,049H
; 3
DB 088H,0A8H,0A8H,051H
; 4
DB 030H,050H,0F8H,011H
; 5
DB 0E8H,0A8H,0A8H,091H
; 6
DB 070H,0A8H,0A8H,091H
; 7
DB 080H,098H,0A0H,0C0H,081H
; 8
DB 050H,0A8H,0A8H,051H
; 9
DB 040H,0A8H,0A8H,071H
; :
DB 051H
; ;
DB 008H,051H
; <
DB 020H,050H,089H
; =
DB 050H,050H,051H
; >
DB 088H,050H,021H
; ?
DB 040H,080H,0B8H,041H
; @
DB 070H,0A8H,0D8H,0B0H,071H
; A
DB 078H,0A0H,0A0H,079H
; B
DB 0F8H,0A8H,0A8H,051H
; C
DB 070H,088H,088H,089H
; D
DB 0F8H,088H,088H,071H
; E
DB 0F8H,0A8H,0A8H,089H
; F
DB 0F8H,0A0H,0A0H,081H
; G
DB 070H,088H,0A8H,0B9H
; H
DB 0F8H,020H,020H,0F9H
; I
DB 088H,0F8H,089H
; J
DB 010H,008H,008H,0F1H
; K
DB 0F8H,020H,050H,089H
; L
DB 0F8H,008H,009H
; M
DB 0F8H,040H,020H,040H,0F9H
; N
DB 0F8H,040H,020H,0F9H
; O
DB 070H,088H,088H,071H
; P
DB 0F8H,0A0H,0A0H,041H
; Q
DB 070H,088H,098H,069H
; R
DB 0F8H,0A0H,0B0H,049H
; S
DB 048H,0A8H,0A8H,091H
; T
DB 080H,0F8H,081H
; U
DB 0F0H,008H,008H,0F1H
; V
DB 0E0H,010H,008H,010H,0E1H
; W
DB 0F8H,010H,020H,010H,0F9H
; X
DB 088H,050H,020H,050H,089H
; Y
DB 080H,040H,038H,040H,081H
; Z
DB 088H,098H,0A8H,0C8H,089H
; [
DB 0F8H,089H
; \
DB 080H,040H,020H,010H,009H
; ]
DB 088H,0F9H
; ^
DB 020H,040H,080H,040H,021H
; _
DB 008H,008H,008H,009H
; Ñ
DB 038H,0A0H,090H,039H
;^********** CARACTERES
;
END

To generate the character's codes I created a simple Delphi application where it is possible to draw the characters by clicking the pixels and then clicking a button the hex codes are generated automatically and all that remains to do is to copy and paste.

The software to store messages in the sign is also made with Delphi.



Schematics

This is the original, pencil-drawn schematic for this mini sign. Click for a larger version.



Conclusion

Given that some of the parts are no longer available, namely the "5405M7" dot matrix display modules for which I couldn't find not even its datasheet, this can hardly be reproduced exactly as it is shown here. Also, with newer and smaller parts this can be done much more simple and smaller. I may add more information and analysis or how-it-works on this thing if there is any visitor interest.

Video (Update)

I've uploaded a short video showing the sign working. In this video the LEDs of the sign look purple. That is not accurate, the photo at the top of the article is much closer. All the photos above were taken with a Sony DSC-P43. The video was recorded with a Panasonic FZ35. Looks like the FZ is more sensitive to near IR light which I guess is emitted by the LEDs. The video is compressed 20X from the camera motion jpeg output.

Bookmark and Share