Overview

This project is designed to display a scrolling text message on two 8x8 LED matrices, using an Atmel AT Mega16 CMOS IC chip, with a custom designed program and font burned into it's program and EEPROM memory, respectively.

Video

Can't play the video? Try the Flash Version.

The Software

This project uses the Atmel Assembler Language, which is based on a Reduced Instruction Set Computing architecture. This RISC design allows for faster processing and execution of commands

The software for the final product is designed to, as efficiently as possible, display a scrolling message using two 8x8 LED matrices.

The program first reads all the relevant character data, as defined by the "Msg" string in program memory, saved in EEPROM, to SRAM, in the order in which it is to be displayed. Next, the program serially reads each bit out on pin PA2, using PA3 to trigger the shift registers clock pulse input.

Once all the required data is read into the shift registers, the program sets PA0 high, telling the shift registers to refresh their pin outputs. Finally, the program sets the next pin in PortB low, which displays the next row of LEDs to the display.

The program cycles through this 8 times, to display a single character on each display. The program does this hundreds of times a second, making it look to the human eye that the circuit is constantly showing the message, when it is really just flashing through each row. The program uses timer interrupts to display each character long enough for a human to see, waiting a few seconds to allow a person to understand what is being displayed, before scrolling the message to the next position.

For a look at the code, scroll down to the Code section.

The Hardware

ATMega16 Microcontroller

The microcontroller being used in this project is the Atmel ATMega16 CMOS IC. This chip is capable of speeds up to 8 MHz. However, for the uses of this project, it is only running at 1 MHz. But since most commands require only 1 cycle to complete, this still gives the program plenty of time to run commands and complete calculations, as well as load and send data.

This chip can be programmed using both C and the Atmel Assembly Language. For the purposes of this project, the RISC design of the Assembly language was used.

HEF4749B Shift Register

In this project, two HEF4749B CMOS IC shift register chips were used. The use of these chips allows for the easy scaling up of the number of LED matrices in the system, with only minor changes to the program. If the project were designed without the use of these chips, the size of the display would be limited to 2 LED matrices, and would require another ATMega16 chip for the addition of every display, as well as even more complicated programming, for the syncing of the message across chips and displays.

Bicolour LED Matrix

This project uses two 8x8 bicolour LED matrices (shown above), to create a rectangular display on which text and other graphics can be displayed on. Due to the circuit board size limitations, only the red colour capability of the display is being used.

ISP

A late addition to the circuit design was the capability to re-program the ATmega16 while it is still within the circuit. A large factor in this decision was because of the 40 pins on the ATmega. This makes it extremely hard to take the chip in and out of both the circuit, and the programming board, without causing damage to the chip or the circuit.

However, this also presented an opportunity to become familiar with the In System Programmer, which allows for the designer to reprogram the message that is displayed, or the character design that is to be used in the display of the message, since the ISP can reprogram the flash program memory, where the program is stored, as well as the EEPROM, where all the character data (the font) is stored.

PCB

Above is a photo of the final circuit board mask used to create the Scrolling Message Board. The mask was created using the freeware version of the EAGLE layout design software. However, due to the limitations imposed on the freeware version, the mask pictured above is actually composed of two different masks. In building, both masks were printed onto separate pieces of transfer paper, which were then lined up before transferring to toner onto a copper clad board.

After burning the circuit design onto the board, and cleaning off the extra toner, all the necessary holes were drilled using a 1/32' bit, to allow for easy installation of the parts. The final board is shown below.

However, this original design does have some mistakes in it, which have been corrected on the actual message board. This first error was that a connection between the ATmega16 and the shift registers was not made; causing the shift registers to never display the information on the LED matrices. Additionally, 4 connections between each matrix and its corresponding shift register were mistakenly routed to the wrong pins on the display, causing the top half of the display to be inactive.

The Code

;==================================================================================================
;PROJECT:
;	This project is designed to display a scrolling text message across two 8x8 LED matrices, 
;	based on a string that is stored in program memory. All of the character data is stored in 
;	EEPROM. Upon initialization, the program stores all the required glyph data, in order, in 
;	SRAM, and cycles through each byte, temporarily showing each column on the displays. However,  
;	due to the principle of persistence of vision, it appears to the human eye that the full
;	display is constantly on.
;
;AUTHOR: Jack Gettings
;DATE: April 5, 2011
;==================================================================================================

;===INCLUDES=======================================================================================
.include	"m16def.inc"
.include	"../clockPrescaler.inc"
;===DEFINES========================================================================================
.def	temp		=R16
.def	blocks		=R17
.def	index		=R19
.def	byte		=R20
.def	ptr		=R21
.def	anode		=R22
.def	offset		=R23
.def	length		=R24
.def	mask		=R25
;===EQUATES========================================================================================
.equ	data		= 0
.equ	latch		= 1
.equ	enable		= 2
.equ	clock		= 3
.equ	leds		= 2
.equ	shiftDDR	= DDRA
.equ	shiftPort	= PORTA
.equ	rowDDR		= DDRB
.equ	rowPort		= PORTB
;===VECTOR=JUMP=TABLE==============================================================================
.org	0x00
	rjmp	Reset					;Reset
.org	OVF1addr
	rjmp	OVF1isr					;Timer Overflow Enable

;==================================================================================================
;===EEPROM=DATA====================================================================================
;==================================================================================================
.eseg
.org	0x00
;===LOOK=UP=TABLE=OF=GLYPH=DATA====================================================================
;Due to the size limit of EEPROM (512 bytes), there is a limit to the # of glyphs that can be stored / used:
;
;						512 / 8 = 64
;
;Therefore, only 64 glyphs can be stored, since each glyph requires 8 bytes. To make the most use of this space, 
;a 64 section of the ASCII table, from space, to the capital letter Z.

LUT:
.db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00	;Space (ASCII 32)
.db 0x00, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x00, 0x00	;!
.db 0x00, 0x20, 0xC0, 0x20, 0xC0, 0x00, 0x00, 0x00	;"
.db 0x00, 0x24, 0x7E, 0x24, 0x7E, 0x24, 0x00, 0x00	;#
.db 0x00, 0x20, 0x54, 0xFE, 0x54, 0x08, 0x00, 0x00	;$
.db 0x00, 0x44, 0x08, 0x10, 0x20, 0x44, 0x00, 0x00	;%
.db 0x00, 0x7C, 0x92, 0x9A, 0x66, 0x0E, 0x00, 0x00	;&
.db 0x00, 0x00, 0x00, 0x20, 0xC0, 0x00, 0x00, 0x00	;' (apostrophe)
.db 0x00, 0x38, 0x44, 0x82, 0x00, 0x00, 0x00, 0x00	;(
.db 0x00, 0x00, 0x00, 0x82, 0x44, 0x38, 0x00, 0x00	;)
.db 0x00, 0x00, 0x50, 0x20, 0x50, 0x00, 0x00, 0x00	;*
.db 0x00, 0x10, 0x10, 0x7C, 0x10, 0x10, 0x00, 0x00	;+
.db 0x00, 0x00, 0x00, 0x00, 0x02, 0x0C, 0x00, 0x00	;, (comma)
.db 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00	;- (dash)
.db 0x00, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00	;. (period)
.db 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00	;/
.db 0x00, 0xFE, 0x8A, 0x92, 0xA2, 0xFE, 0x00, 0x00	;0
.db 0x00, 0x22, 0x42, 0xFE, 0x02, 0x02, 0x00, 0x00	;1
.db 0x00, 0x42, 0x86, 0x8A, 0x92, 0x62, 0x00, 0x00	;2
.db 0x00, 0x82, 0x92, 0x92, 0x92, 0xFE, 0x00, 0x00	;3
.db 0x00, 0xF0, 0x10, 0x10, 0x10, 0xFE, 0x00, 0x00	;4
.db 0x00, 0xF2, 0x92, 0x92, 0x92, 0x9E, 0x00, 0x00	;5
.db 0x00, 0xFE, 0x92, 0x92, 0x92, 0x1E, 0x00, 0x00	;6
.db 0x00, 0x80, 0x80, 0x80, 0x80, 0xFE, 0x00, 0x00	;7
.db 0x00, 0xFE, 0x92, 0x92, 0x92, 0xFE, 0x00, 0x00	;8
.db 0x00, 0xF0, 0x90, 0x90, 0x90, 0xFE, 0x00, 0x00	;9
.db 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00	;: (colon)
.db 0x00, 0x00, 0x02, 0x64, 0x00, 0x00, 0x00, 0x00	;; (semi-colon)
.db 0x00, 0x10, 0x28, 0x44, 0x82, 0x00, 0x00, 0x00	;<
.db 0x00, 0x24, 0x24, 0x24, 0x24, 0x24, 0x00, 0x00	;=
.db 0x00, 0x82, 0x44, 0x28, 0x10, 0x00, 0x00, 0x00	;>
.db 0x00, 0x40, 0x80, 0x8A, 0x90, 0x60, 0x00, 0x00	;?
.db 0x00, 0x7C, 0xBA, 0xAA, 0xBA, 0x78, 0x00, 0x00	;@
.db 0x00, 0x7E, 0x90, 0x90, 0x90, 0x7E, 0x00, 0x00	;A
.db 0x00, 0xFE, 0x92, 0x92, 0x92, 0x6E, 0x00, 0x00	;B
.db 0x00, 0x7C, 0x82, 0x82, 0x82, 0x44, 0x00, 0x00	;C
.db 0x00, 0xFE, 0x82, 0x82, 0x82, 0x7C, 0x00, 0x00	;D
.db 0x00, 0xFE, 0x92, 0x92, 0x92, 0x82, 0x00, 0x00	;E
.db 0x00, 0xFE, 0x90, 0x90, 0x90, 0x80, 0x00, 0x00	;F
.db 0x00, 0x7C, 0x82, 0x82, 0x8A, 0x4C, 0x00, 0x00	;G
.db 0x00, 0xFE, 0x10, 0x10, 0x10, 0xFE, 0x00, 0x00	;H
.db 0x00, 0x82, 0x82, 0xFE, 0x82, 0x82, 0x00, 0x00	;I
.db 0x00, 0x04, 0X82, 0x82, 0xFC, 0x80, 0x00, 0x00	;J
.db 0x00, 0xFE, 0x10, 0x28, 0x44, 0x82, 0x00, 0x00	;K
.db 0x00, 0xFE, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00	;L
.db 0x00, 0xFE, 0x40, 0x20, 0x40, 0xFE, 0x00, 0x00	;M
.db 0x00, 0xFE, 0x20, 0x10, 0x08, 0xFE, 0x00, 0x00	;N
.db 0x00, 0x7C, 0x82, 0x82, 0x82, 0x7C, 0x00, 0x00	;O
.db 0x00, 0xFE, 0x90, 0x90, 0x90, 0x60, 0x00, 0x00	;P
.db 0x00, 0x7C, 0x82, 0x8A, 0x7C, 0x02, 0x00, 0x00	;Q
.db 0x00, 0xFE, 0x90, 0x98, 0x94, 0x62, 0x00, 0x00	;R
.db 0x00, 0x62, 0x92, 0x92, 0x92, 0x8C, 0x00, 0x00	;S
.db 0x00, 0x80, 0x80, 0xFE, 0x80, 0x80, 0x00, 0x00	;T
.db 0x00, 0xFC, 0x02, 0x02, 0x02, 0xFC, 0x00, 0x00	;U
.db 0x00, 0xF0, 0x0C, 0x02, 0x0C, 0xF0, 0x00, 0x00	;V
.db 0x00, 0xFE, 0x02, 0x1E, 0x02, 0xFE, 0x00, 0x00	;W
.db 0x00, 0xC6, 0x28, 0x10, 0x28, 0xC6, 0x00, 0x00	;X
.db 0x00, 0xE0, 0x10, 0x1E, 0x10, 0xE0, 0x00, 0x00	;Y
.db 0x00, 0x86, 0x8A, 0x92, 0xA2, 0xC2, 0x00, 0x00	;Z
LUTEnd:

;==================================================================================================
;===PROGRAM=DATA===================================================================================
;==================================================================================================
.cseg
;===POSSIBLE=MESSAGES==============================================================================
Msg:
;.db		"  HELLO WORLD!  "
.db		"  J. GETTINGS   "
;.db		"  TEI4M   "
;.db		"  J.G.  "
;.db		"  0123456789  "
MsgEnd:
;===PROGRAM=START==================================================================================
Reset:
	ldi		temp, high(RAMEND)		;Establish the stack pointer to the end of SRAM
	out		SPH, temp
	ldi		temp, low(RAMEND)
	out		SPL, temp

	rcall		loadSRAM			;load relevant character data from EEPROM to SRAM

	ldi		YH, high(SRAM_START)		;point to start of SRAM buffer
	ldi		YL, low(SRAM_START)
	movw		X, Y				;position X to the beginning of the SRAM buffer

	rcall		InitPorts			;initialize ports for output
	rcall		InitTimer1Int			;initialize timer interrupts
	sbi		shiftPort, enable		;enable shift registers to receive serial data input
	ldi		blocks, leds			;register # of LED Matrices in use
	sub		length, blocks			;reduce length to accommodate blank padding at end
	clr		ptr				;point to the first character in the message
	sei						;enable global interrupts

Scroll:
	cli						;clear interrupts --> prevent an interrupt during the display cycle
	rcall		ShowFrame			;load the full frame
	sei						;has a timer interrupt occurred?
	rjmp		Scroll				;display the next frame

ShowFrame:
	movw		Z, X				;Point Z to the location of the start of the data to be displayed
	ldi		anode, 0x80						
	
NextColumn:
	cbi		shiftPort, latch		;prevent latching to prevent data from being displayed on pins
								;of the Shift Register(s)
	rcall		LoadRegisters			;load all bits into shift registers
	rcall		Store

	sbiw		Z, 7
	clc						;prepare for next row (Clear C flag to allow for comparison)
	ror		anode				;next row
	brne		NextColumn			;do the next row if not done
	ret						;frame complete - all 8 rows entered

LoadRegisters:
	ld		byte, Z				;read next byte from SRAM

WriteByte:
	ldi		index, 0x08			;1 byte = 8 bits

NextBit:
	cbi		shiftPort, data			;assume 0 is bit value
	cbi		shiftPort, clock		;prepare for a rising edge
	rol		byte				;C flag used to test bit
	brcc		RegisterReady			;is bit value actually 0?
	sbi		shiftPort, data			;if not: set bit to 1

RegisterReady:
	sbi		shiftPort, clock		;trigger rising edge to load bit
	dec		index				;one less bit to load
	brne		NextBit				;are all 8 bits loaded?
	dec		blocks				;one less character to load
	breq		Loaded				;are all bits loaded
	adiw		Z, 8				;if not: position for next byte in SRAM
	rjmp		LoadRegisters			;load the next byte

Loaded:
	ldi		blocks, leds			;pass the # of displays to the "blocks" register
	ret						;return

Store:
	clr		temp				;prepare to zero all rows
	out		rowPort, temp			;go
	sbi		shiftPort, latch		;register the bits to store (pins)
	out		rowPort, anode			;activate the current row
	rcall		Delay				;leave on display long enough to see
	ret

;---TIMER1-OVERFLOW-INTERRUPT-SERVICE-ROUTINE------------------------------------------------------
OVF1isr:
	cli						;suspend interrupts
	adiw		X, 1				;smooth scroll
	inc		ptr				;advance base to next character
	cp		ptr, length			;does X point to the end of the character?
	brne		NextFrame
	movw		X, Y
	clr		ptr

NextFrame:
	ldi		anode, 0x08			;restart at row 1
	sei
	reti
;===LOAD=EEPROM=CHARACTER=DATA=TO=SRAM=============================================================
LoadSRAM:
	ldi		ZH, high(Msg<<1)		;establish pointer to beginning of message
	ldi		ZL, low(Msg<<1)
	mov		temp, ZL			;determine the length of the message
	ldi		length, low(MsgEnd<<1)		;obtain the end address of the message
	sub		length, temp			;the # of characters is the difference
	dec		length				;reduce the length by the # of matrix blocks to clean up scrolling
	dec		length
	lsl		length				;multiply by 8 to determine the # of bytes to be loaded
	lsl		length
	lsl		length

	clr		index				;index of the current character
	ldi		XH, high(LUT)			;establish pointer to the beginning of EEPROM data
	ldi		XL, low(LUT)
	ldi		YH, high(SRAM_START)		;establish pointer to beginning of SRAM
	ldi		YL, low(SRAM_START)
ContinueLoading:
	ldi		mask, 8				;use mask as a temporary counter
	rcall		LoadCharacter
	inc		index
	cp		index, length
	brne		ContinueLoading
	ret

LoadCharacter:
	lpm		offset, Z+			;get the next character to be displayed
	ldi		temp, ' '			;load space (ASCII 32) into temp -> start of LUT Data
	sub		offset, temp			;determine offset of character in the LUT
	ldi		XH, high(LUT)			;position X to the beginning of the LUT
	ldi		XL, low(LUT)
	lsl		offset				;multiply by 8
	lsl		offset
	lsl		offset
	brcc		IndexSet
	inc		XH				;accomodate possible overflow

IndexSet:
	add		XL, offset

NextByte:
	rcall		GetEEPROM
	st		Y+, byte
	dec		mask
	brne		NextByte
	ret

GetEEPROM:
	out		EEARH, XH			;initialize EEPROM address registers
	out		EEARL, XL			;set up address in EE address register
EEPROMRead:
	sbic		EECR, EERE			;wait for previous EE read to finish
	rjmp		EEPROMRead
	sbi		EECR, EERE
	in		byte, EEDR			;read data from EE data register
	adiw	X, 1
	ret
;===INITIALIZATION=SUBROUTINES=====================================================================
InitTimer1Int:
	ldi		temp, clk
	out		TCCR1B, temp
	ldi		temp, 1<<TOIE1
	out		TIMSK, temp
	ret

InitPorts:
	ser		temp
	out		shiftDDR, temp
	out		rowDDR, temp
	clr		temp
	out		rowPORT, temp
	ret

;===DELAY=LOOP=====================================================================================
Delay:
	push	R17					;push register values onto the stack to be stored
	push	R18
	push	R19

; ============================= 
;    delay loop generator 
;     1000 cycles:
; ----------------------------- 
; delaying 999 cycles:
          ldi  R17, $03
WGLOOP0:  ldi  R18, $6E
WGLOOP1:  dec  R18
          brne WGLOOP1
          dec  R17
          brne WGLOOP0
; ----------------------------- 
; delaying 1 cycle:
          nop
; ============================= 

	pop	R19					;restore register data from the stack
	pop	R18
	pop	R17
	ret