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.
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 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.
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.
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.
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.
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.
;================================================================================================== ;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