[Return to top]

Z80 LED matrix Driver assembly source file
[Back]


Download this file [Download]
; This code was written to test some LED matrix displays that were bought
; cheap at a radio rally. The cards have 240 LEDs in an 8x30 matrix driven
; by 8 row lines and two 4 bit decoders driving the 30 columns. (column 15
; and column 31 = all LEDs off.)

; The displays were driven by the uPF IO/M Z80 PIO the address map of which
; is below. Change these for your own system.

; One interesting routine in this is rand_8 which generates a maximal length
; 8 bit pseudo random sequence. Read the comments to see how it works.

; uPF Address map

MPortA:		EQU	00h	;Main board 8255 port A
MPortB:		EQU	01h	;Main board 8255 port B
MPortC:		EQU	02h	;Main board 8255 port C
MPPCNT:		EQU	03h	;Main board 8255 control

MCTC0:		EQU	40h	;Main board CTC Channel 0
MCTC1:		EQU	41h	;Main board CTC Channel 1
MCTC2:		EQU	42h	;Main board CTC Channel 2
MCTC3:		EQU	43h	;Main board CTC Channel 3

MPIODA:		EQU	80h	;Main board PIO Port A Data
MPIOCA:		EQU	82h	;Main board PIO Port A Control
MPIODB:		EQU	81h	;Main board PIO Port B Data
MPIOCB:		EQU	83h	;Main board PIO Port B Control

PIODA:		EQU	68h	;IOM PIO Port A Data
PIOCA:		EQU	6Ah	;IOM PIO Port A Control
PIODB:		EQU	69h	;IOM PIO Port B Data
PIOCB:		EQU	6Bh	;IOM PIO Port B Control

URTDA:		EQU	60h	;IOM 8251 Data Port
URTCNT:		EQU	61h	;IOM 8251 Control Port

CTC0:		EQU	64h	;IOM CTC Channel 0
CTC1:		EQU	65h	;IOM CTC Channel 1
CTC2:		EQU	66h	;IOM CTC Channel 2
CTC3:		EQU	67h	;IOM CTC Channel 3

DIPSW:		EQU	6Ch	;IOM Dip SW

PPortA:		EQU	7Ch	;Programmer 8255 port A
PPortB:		EQU	7Dh	;Programmer 8255 port B
PPortC:		EQU	7Eh	;Programmer 8255 port C
EPPCNT:		EQU	7Fh	;Programmer 8255 control

PPortD:		EQU	70h	;Programmer Vpp select & control

	ORG	1800h

	LD	A,#0Fh		; control word
	OUT	(PIOCA),A	; port A o/p
	OUT	(PIOCB),A	; port B o/p

	LD	A,#0Fh		; set OFF address
	LD	(pat_adr),A	; save it
	CALL	byte_out	; out to display

	LD	A,#0Ch		; set scans per call
	LD	(s_count),A	; save it

	LD	A,#29		; scroll end byte
	LD	(s_end),A	; save it

	LD	A,#00h		; start from 0
	LD	(pat_adr),A	; save address
	LD	(index),A	; save index byte
	LD	(s_start),A	; clear scroll start
	LD	(1+s_start),A	; clear scroll start (high byte)

; scrolling buffer

main:
	LD	HL,pattern	; point to bitmap
	ADD	A,L		; add low
	LD	L,A		; copy back
	LD	A,#0		; clear byte
	ADC	A,H		; add high byte + carry
	LD	H,A		; save back
	LD	(pointer),HL	; save it
	CALL	scan_disp	; go scan the display
	LD	A,(index)	; get index
	INC	A		; next
	LD	(index),A	; save new
	JR	NZ,main		; loop

; flash 'Reboot' 4 times slowly

	LD	HL,d_buffer	; point to display buffer
	LD	(pointer),HL	; save it

	LD	HL,xpattern	; point to pattern
	LD	DE,d_buffer	; point to buffer
	LD	BC,30		; 30 bytes to do
	LDIR			; copy block

	LD	A,#80h		; scan count
	LD	(s_count),A	; save it

	LD	A,#04h		; flash count
	LD	(d_count),A	; save it

	LD	HL,d_buffer	; point to bitmap 'Reboot'

	CALL	flash_it	; flash display

	LD	A,#08h		; set scans per call
	LD	(s_count),A	; save it

	LD	A,#01h		; scroll direction (right)
	LD	(s_dir),A	; save it

	CALL	clear_dis	; scroll clear display

; scroll up test

	CALL	b_clear		; clear buffer

	LD	HL,p_up		; point to pattern
	LD	DE,p_buffer	; point to buffer
	LD	BC,30		; 30 bytes to do
	LDIR			; copy block

	LD	A,#18h		; set scans per call
	LD	(s_count),A	; save it

	LD	A,#40h		; scroll count
	LD	(d_count),A	; save it

	LD	A,#29		; scroll end byte
	LD	(s_end),A	; save it
	LD	A,#0		; start from 0
	LD	(s_start),A	; clear scroll start
	LD	A,#01h		; scroll direction (up)
	LD	(s_dir),A	; save it

scroll_up:
	CALL	scan_disp	; go scan the display

	CALL	scroll		; go scroll display

	LD	A,(d_count)	; get scroll count
	DEC	A		; next
	LD	(d_count),A	; save new
	JR	NZ,scroll_up	; loop if not done

; scroll down test

	LD	HL,p_down	; point to pattern
	LD	DE,p_buffer	; point to buffer
	LD	BC,30		; 30 bytes to do
	LDIR			; copy block

	LD	A,#40h		; scroll count
	LD	(d_count),A	; save it

	LD	A,#00h		; scroll direction (up)
	LD	(s_dir),A	; save it

scroll_dn:
	CALL	scan_disp	; go scan the display

	CALL	scroll		; go scroll display

	LD	A,(d_count)	; get scroll count
	DEC	A		; next
	LD	(d_count),A	; save new
	JR	NZ,scroll_dn	; loop if not done

; split scroll test

	LD	HL,p_right	; point to pattern
	LD	DE,p_buffer	; point to buffer
	LD	BC,30		; 30 bytes to do
	LDIR			; copy block

	LD	A,#40h		; scroll count
	LD	(d_count),A	; save it

scroll_sp:
	CALL	scan_disp	; go scan the display

	LD	A,#14		; scroll end byte
	LD	(s_end),A	; save it
	LD	A,#00h		; start from 0
	LD	(s_start),A	; clear scroll start
	LD	(s_dir),A	; and scroll direction

	CALL	scroll		; go scroll display

	LD	A,#29		; scroll end byte
	LD	(s_end),A	; save it
	LD	A,#15		; start from 15
	LD	(s_start),A	; clear scroll start
	LD	A,#01h		; scroll direction (up)
	LD	(s_dir),A	; save it

	CALL	scroll		; go scroll display

	LD	A,(d_count)	; get scroll count
	DEC	A		; next
	LD	(d_count),A	; save new
	JR	NZ,scroll_sp	; loop if not done

; scroll column at a time

	LD	HL,p_numb	; point to pattern
	LD	DE,p_buffer	; point to buffer
	LD	BC,30		; 30 bytes to do
	LDIR			; copy block

	LD	A,#00h		; scroll direction (down)
	LD	(s_dir),A	; save it

	CALL	s_column	; scroll display 1 column at a time

	LD	A,#80h		; scan count
	LD	(s_count),A	; save it

	LD	A,#04h		; flash count
	LD	(d_count),A	; save it

	LD	HL,d_buffer	; point to display buffer

	CALL	flash_it	; flash display

; random # test

; don't just dump the word in, put it in p_buffer .....

	LD	HL,p_rand	; point to pattern
	LD	DE,p_buffer	; point to buffer
	LD	BC,30		; 30 bytes to do
	LDIR			; copy block

; ..... and scroll it in

	LD	A,#01h		; scroll direction (up)
	LD	(s_dir),A	; save it

	CALL	s_column	; scroll display 1 column at a time

; we now have 'Random' in the display

	LD	A,#04h		; scans
	LD	(b_count),A	; save it
rand_loop:
	LD	A,#04h		; set scans per call
	LD	(s_count),A	; save it

	LD	A,#F0h		; random count (240 LEDs to do)
	LD	(d_count),A	; save it
random:
	CALL	rand_8		; get random byte
	DEC	A		; change 1-255 to 0-254
	LD	B,#0		; clear B
	SRL	A		; bit into carry
	RL	B		; bit into B
	SRL	A		; bit into carry
	RL	B		; bit into B
	SRL	A		; bit into carry
	RL	B		; bit into B

	CP	#30		; compare with 30
	JR	NC,random	; skip if not within display

; A now has column #, B has bit #

	LD	HL,d_buffer	; point to display
	ADD	A,L		; add to pointer low
	LD	L,A		; copy back to pointer
	LD	A,#0		; clear A
	ADC	A,H		; add carry to H
	LD	H,A		; back to pointer

	INC	B		; bit is now 1-8
	SCF			; set carry
	LD	A,#0		; clear A
set_bit:
	RLA			; move bit
	DJNZ	set_bit		; loop until in position

	XOR	(HL)		; xor in byte
	LD	(HL),A		; save into buffer

	CALL	scan_disp	; go scan the display

	LD	A,(d_count)	; get scan count
	DEC	A		; next
	LD	(d_count),A	; save new
	JR	NZ,random	; loop if not done

; flash 'Random' 4 times slowly

	LD	A,#80h		; scan count
	LD	(s_count),A	; save it

	LD	A,#04h		; flash count
	LD	(d_count),A	; save it

	LD	HL,d_buffer	; point to display buffer

	CALL	flash_it	; flash display

	LD	A,(b_count)	; get scroll count
	DEC	A		; next
	LD	(b_count),A	; save new
	JR	NZ,rand_loop	; loop if not done

	LD	A,#08h		; set scans per call
	LD	(s_count),A	; save it

	LD	A,#00h		; scroll direction (left)
	LD	(s_dir),A	; save it

	CALL	clear_dis	; scroll clear display

	LD	A,(index)	; get index
	JP	main		; loop

; end of main loop .. now the subroutines

; scroll clear display in direction (s_dir)
; left/right 0/1

clear_dis:
	LD	A,#30		; column count
	LD	(b_count),A	; save it
clear_lp:
	LD	HL,(pointer)	; pointer to buffer
	LD	BC,29		; 29 bytes to do

	LD	A,(s_dir)	; get direction
	BIT	0,A		; test direction bit
	JR	Z,clear_l	; do scroll clear left

; this bit scrolls the display 1 column right

	LD	DE,29		; offset to buffer end
	ADD	HL,DE		; add to pointer
	LD	D,H		; copy high byte
	LD	E,L		; copy low byte
	DEC	HL		; -1
	LDDR			; copy block
	JR	clr_end		; go do end loop

; this bit scrolls the display 1 column left

clear_l:
	LD	D,H		; copy high byte
	LD	E,L		; copy low byte
	INC	HL		; +1
	LDIR			; copy block
clr_end:
	XOR	A		; clear A
	LD	(HL),A		; clear end byte

	CALL	scan_disp	; go scan the display

	LD	A,(b_count)	; get column count
	DEC	A		; next
	LD	(b_count),A	; save new
	JR	NZ,clear_lp	; loop if not done

	RET

; scroll display down 1 column at a time in direction (s_dir)

s_column:
	LD	A,#04h		; scans
	LD	(s_count),A	; save it

	LD	A,#30		; column count
	LD	(b_count),A	; save it

scroll_col:
	LD	A,#08		; bit count
	LD	(d_count),A	; save it

scroll_bit:
	CALL	scan_disp	; go scan the display

	LD	A,(b_count)	; get column count
	NEG			; make -ve
	ADD	A,#30		; add 30 (range 0-29)

	LD	(s_end),A	; save as end
	LD	(s_start),A	; save as start

	CALL	scroll		; go scroll display

	LD	A,(d_count)	; get bit count
	DEC	A		; next
	LD	(d_count),A	; save new
	JR	NZ,scroll_bit	; loop if not done

	LD	A,(b_count)	; get column count
	DEC	A		; next
	LD	(b_count),A	; save new
	JR	NZ,scroll_col	; loop if not done

	RET			; done

; scroll display in direction (s_dir) from (s_start) to (s_end) inclusive.
; no error checking!

scroll:
	LD	HL,d_buffer	; point to display buffer
	LD	DE,(s_start)	; get start #
	ADD	HL,DE		; add to pointer

	LD	DE,30		; get bytes to p_buffer buffer
	EX	DE,HL		; exchange pointer and distance
	ADD	HL,DE		; add to give p_buffer pointer in HL

	LD	A,(s_start)	; get start byte #
	LD	B,A		; copy it
	LD	A,(s_end)	; get end byte #
	INC	A		; +1 to give total bytes to do
	SUB	B		; subtract start gives bytes to do
	LD	B,A		; copy to loop counter

	LD	A,(s_dir)	; get direction byte
	BIT	0,A		; test up/down bit
	JR	NZ,rotate_u	; do up if bit = 1

; rotates B bytes one bit downward. uses d_buffer and p_buffer

rotate_d:
	LD	A,(HL)		; get byte from pattern
	SRL	A		; rotate
	LD	A,(DE)		; get byte from display
	RRA			; rotate carry in
	LD	(DE),A		; save it back
	LD	A,(HL)		; get byte from pattern back
	RRA			; rotate carry in
	LD	(HL),A		; save back
	INC	HL		; increment source
	INC	DE		; increment destination
	DJNZ	rotate_d	; loop if not done

	RET			; done

; rotates B bytes one bit upward. uses d_buffer and p_buffer

rotate_u:
	LD	A,(HL)		; get byte from pattern
	SLA	A		; rotate carry out
	LD	A,(DE)		; get byte from display
	RLA			; rotate carry in
	LD	(DE),A		; save it
	LD	A,(HL)		; get byte from pattern back
	RLA			; rotate carry in
	LD	(HL),A		; save back
	INC	HL		; increment source
	INC	DE		; increment destination
	DJNZ	rotate_u	; loop if not done

	RET			; done

; clears the pattern buffer

b_clear:
	LD	HL,d_buffer	; pointer to buffer
	LD	D,H		; copy high byte
	LD	E,L		; copy low byte
	INC	DE		; +1
	XOR	A		; clear A
	LD	(HL),A		; clear first byte
	LD	BC,59		; 59 bytes to do
	LDIR			; clear block
	RET			; done

; flash display.
; HL points to the massage to flash
; (d_count) is the number of flashes
; (s_count) is the number of scans per flash

flash_it:
	LD	(f_point),HL	; save flash pattern pointer
flash:
	LD	(pointer),HL	; save it
	CALL	scan_disp	; go scan the display

	LD	HL,ypattern	; point to bitmap '      '
	LD	(pointer),HL	; save it
	CALL	scan_disp	; go scan the display

	LD	HL,(f_point)	; point to flash pattern

	LD	A,(d_count)	; get display count
	DEC	A		; next
	LD	(d_count),A	; save new
	JR	NZ,flash	; loop if not done

	LD	(pointer),HL	; save it
	CALL	scan_disp	; go scan the display
	RET			; done


; scans the display (s_count) times using the bitamp at (pointer)

scan_disp:
	LD	A,(s_count)	; get count
	LD	B,A		; copy to loop counter
scan_p:
	LD	HL,(pointer)	; set pointer to bitmap
scan_it:
	CALL	byte_out	; out to display

	INC	HL		; next bitmap byte
	LD	A,(pat_adr)	; get address
	INC	A		; + 1
	LD	(pat_adr),A	; save it
	AND	#0Fh		; mask low address
	CP	#0Fh		; compare with OFF address
	JR	NZ,scan_it	; skip if not xFh

	LD	A,(pat_adr)	; get address
	INC	A		; + 1
	AND	#1Fh		; mask address bits
	LD	(pat_adr),A	; save it
	JR	NZ,scan_it	; skip reset pointer if not at start

	DJNZ	scan_p		; do it all again

	RET			; done

; outputs byte from (HL) to display column (pat_adr).
; the display is blanked before the byte is sent by setting the address
; to 0Fh which turns off all the column drivers.

byte_out:
	LD	A,#0Fh		; OFF address
	OUT	(PIODB),A	; display off
	LD	A,(HL)		; get byte
	OUT	(PIODA),A	; byte to display
	LD	A,(pat_adr)	; get address
	OUT	(PIODB),A	; display on
	RET

; returns pseudo random 8 bit number in A. Only affects A.
; (r_seed) is the byte from which the number is generated and MUST be
; initialised to a non zero value or this function will always return
; zero.

rand_8:
	LD	A,(r_seed)	; get seed
	AND	#B8h		; mask non feedback bits
	SCF			; set carry
	JP	PO,no_clr	; skip clear if odd
	CCF			; complement carry (clear it)
no_clr:
	LD	A,(r_seed)	; get seed back
	RLA			; rotate carry into byte
	LD	(r_seed),A	; save back for next prn
	RET			; done

r_seed:
	DB	1		; prng seed byte (must not be zero)
s_start:	
	DS	2		; scroll start byte (0-29)
s_end:
	DS	1		; scroll end byte (0-29)
s_dir:
	DS	1		; scroll direction (0/1 = down/up)
b_count:
	DS	1		; display buffer size
d_count:
	DS	1		; display flash count
s_count:
	DS	1		; scans per call to scan_disp
pat_adr:
	DS	1		; pattern address
index:
	DS	2		; index offset
pointer:
	DS	2		; pointer into bitmap
f_point:
	DS	2		; pointer to flash pattern
pattern:
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	00h,00h,00h,00h,00h,00h		;' '

	DB	64h,92h,92h,92h,4Ch,00h		;'S'
	DB	30h,0Ah,0Ah,0Ah,3Ch,00h		;'y'
	DB	12h,2Ah,2Ah,2Ah,04h,00h		;'s'
	DB	20h,FCh,22h,02h,04h,00h		;'t'
	DB	1Ch,2Ah,2Ah,2Ah,18h,00h		;'e'
	DB	3Eh,20h,18h,20h,1Eh,00h		;'m'
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	3Eh,10h,20h,20h,10h,00h		;'r'
	DB	1Ch,2Ah,2Ah,2Ah,18h,00h		;'e'
	DB	04h,2Ah,2Ah,2Ah,3Eh,00h		;'a'
	DB	1Ch,22h,22h,12h,FEh,00h		;'d'
	DB	30h,0Ah,0Ah,0Ah,3Ch,00h		;'y'
	DB	06h,06h,00h,00h,00h,00h		;'.'
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	FEh,FEh,06h,06h,06h,06h,00h	;'L'
	DB	FEh,FEh,D6h,D6h,D6h,C6h,00h	;'E'
	DB	FEh,FEh,C6h,C6h,7Ch,38h,00h	;'D'
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	3Eh,20h,18h,20h,1Eh,00h		;'m'
	DB	3Ch,02h,02h,04h,3Eh,00h		;'u'
	DB	00h,82h,FEh,02h,00h,00h		;'l'
	DB	20h,FCh,22h,02h,04h,00h		;'t'
	DB	00h,22h,BEh,02h,00h,00h		;'i'
	DB	3Eh,28h,28h,28h,10h,00h		;'p'
	DB	00h,82h,FEh,02h,00h,00h		;'l'
	DB	1Ch,2Ah,2Ah,2Ah,18h,00h		;'e'
	DB	22h,14h,08h,14h,22h,00h		;'x'
	DB	1Ch,2Ah,2Ah,2Ah,18h,00h		;'e'
	DB	1Ch,22h,22h,12h,FEh,00h		;'d'
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	3Eh,20h,18h,20h,1Eh,00h		;'m'
	DB	04h,2Ah,2Ah,2Ah,3Eh,00h		;'a'
	DB	20h,FCh,22h,02h,04h,00h		;'t'
	DB	3Eh,10h,20h,20h,10h,00h		;'r'
	DB	00h,22h,BEh,02h,00h,00h		;'i'
	DB	22h,14h,08h,14h,22h,00h		;'x'
	DB	06h,06h,00h,00h,00h,00h		;'.'

ypattern:
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	00h,00h,00h,00h,00h,00h		;' '
	DB	00h,00h,00h,00h,00h,00h		;' '

	DB	00h				; pad byte(s)

p_right:
	DB	64h,92h,92h,92h,4Ch,00h		;'S'
	DB	3Eh,28h,28h,28h,10h,00h		;'p'
	DB	00h,82h,FEh,02h,00h,00h		;'l'
	DB	00h,22h,BEh,02h,00h,00h		;'i'
	DB	20h,FCh,22h,02h,04h,00h		;'t'

xpattern:
	DB	00h,FEh,98h,94h,62h		;'R'
	DB	00h,1Ch,2Ah,2Ah,18h		;'e'
	DB	00h,FEh,12h,22h,1Ch		;'b'
	DB	00h,1Ch,22h,22h,1Ch		;'o'
	DB	00h,1Ch,22h,22h,1Ch		;'o'
	DB	20h,FCh,22h,04h,00h		;'t'

p_up:
	DB	20h,40h,FEh,40h,20h		;' '
	DB	00h,00h,00h,00h,00h		;' '
	DB	00h,FCh,02h,02h,FCh		;'U'
	DB	00h,FEh,90h,90h,60h		;'P'
	DB	00h,00h,00h,00h,00h		;' '
	DB	20h,40h,FEh,40h,20h		;' '

p_down:
	DB	08h,04h,FEh,04h,08h		;' '
	DB	00h,FEh,82h,82h,7Ch		;'D'
	DB	00h,1Ch,22h,1Ch,00h		;'o'
	DB	3Ch,02h,0Ch,02h,3Ch		;'w'
	DB	00h,1Eh,20h,1Eh,00h		;'n'
	DB	08h,04h,FEh,04h,08h		;' '

p_rand:
	DB	00h,7Eh,58h,54h,22h		;'R'
	DB	04h,2Ah,2Ah,1Eh,00h		;'a'
	DB	0Eh,10h,0Eh,00h			;'n'
	DB	0Ch,12h,0Ah,7Eh,00h		;'d'
	DB	0Ch,12h,12h,0Ch,00h		;'o'
	DB	1Eh,10h,0Ch,10h,0Eh,00h		;'m'

p_numb:
	DB	00h,22h,7Eh,02h,00h		;'1'
	DB	1Ch,2Ah,4Ah,04h,00h		;'6'
	DB	18h,28h,7Eh,08h,00h		;'4'
	DB	20h,52h,54h,38h,00h		;'9'
	DB	74h,52h,52h,4Ch,00h		;'5'
	DB	2Ch,52h,52h,2Ch,00h		;'8'

d_buffer:
	DS	30				; display part
p_buffer:
	DS	30				; +30 bytes for effects

	END

e-mail me [e-mail]
Last page update: 28th April, 2002.