Full Version : Eric Hsieh's Sine Wave Synthesizer (ASM)
avr >>SOUND & MUSIC PROJECTS >>Eric Hsieh's Sine Wave Synthesizer (ASM)


Admin5- 04-21-2006
Sine Wave Synthesizer

Introduction

Every group wants their final project to be something that will be remembered long after they're gone. Some do highly sophisticated and complex projects that entail upwards of a hundred hours to complete. Yet others go out of their way to develop something 'cool' and 'fun'. Luke and I decided that we wanted to be in this second category, because developing something that's 'cool' would also be fun to do. So in deciding what to design we tried to think of something that would catch the attention of the people in the lab. The easiest way of doing this is to create something that would make noise or play music so everyone in the lab could enjoy it. Realizing this would be the best way to go, we decided to create a synthesizer that could record and playback notes, 'teach' the user how to play a simple melody, and also play some prerecorded tunes. After all, don't you think being remembered as the group that played back the Imperial March theme from Star Wars is cooler than a paper tape reader? (no offense to those groups doing paper tape readers)

High Level Design

Seeing the synthesizer not just as a keyboard for playback but something with multiple options, we opted to use a simple state machine to section off each option. In other words, there would be states for the different options (one for single note play, record play, etc...) Since the main program would be this state machine, it was wise to have the generation of the sine wave output to be interrupt driven. This gives a lot of freedom to the main program functions because the state machine for the main program doesn't have to worry about creating these tones.

Program/Hardware Design

State Machine Design

Since our design involved the user being able to choose from several different playback option, it was obvious that the program would entail the use of a simple state machine. Each option of the synthesizer (Default or waiting, Single Note Play, Record Play, Mem Playback, LCD play, and Recorded Tunes) would correspond with one of these states. The buttons on the proto-board would be used to change from one state to another.

Proto-board button layout:

Button 0: Single Note Play
Button 1: Record Play
Button 2: Memory Playback
Button 3: LCD Play
Button 4: End Single Note Play
Button 5: End LCD Play
Button 6: Recorded Tunes

Since the synthesizer defaults to the 'default state', pushing button's 0-3 or 6 will transfer the user to that option. Pushing button 4 and 5 are only valid if the user is already in the single note play or lcd play option respectively.

Default State

The program spins through the instructions of how to use the synthesizer. At even intervals, the LCD displays text on how to select different options. The following can be seen by the user on the LCD:

0 Single Note Play
1 Record Play
2 Mem Playback
3 LCD play
6 Recorded Tunes

When button's 0-3 or 6 are pressed on the proto-board, the synthesizer changes to the state of the option selected.

Single Note Play

Once the user hits the button on the proto-board corresponding with the single note play option, the state machine enters to this 'Single Note Play' state. A tone is then generated for each button pushed on the keypad. This state can be exited once button 4 on the proto-board is pushed. The synthesizer then resets back to the default state.

Record Play

The user can play and record up to 100 notes into memory and then play them back in .25 second intervals. When a button is pushed on the keypad, the number corresponding to the button is stored in RAM. If the user enters 100 notes, the synthesizer will output a 'Recording Done' message to the LCD and return to the default state to await input. The recorded notes can then be played by selecting the memory playback option. But if the user doesn't want to enter 100 notes, they can hit the button for mem playback and the recording will cease and memory playback will start.

Mem Playback

Once the user records a certain number of notes, they can be played back in sequence. If no notes were recorded, then nothing is played.

LCD play (aka Keyboard Karaoke)

In this state, a welcome message is flashed on the LCD letting the user that they have entered the 'LCD Play' option. After a small delay, a series of characters is displayed one at a time on the LCD. These characters represent the buttons that the user should hit in order to play the notes of a song. Once the 'karaoke' lines are finished, the program will jump back to the default state and await input. At any time during the karaoke playback, the user can hit button 5 to stop LCD play and go back to the default state.

Recorded Tunes

A fun not but very useful feature, once the user enters this option, they have the opportunity to playback some of the pre-recorded songs in the program. For this project, only the Imperial March from Star Wars was hard-coded into the program and available for user playback.

Sine wave generation

In this design, we used the National Semiconductor's DAC0808 to convert 8-bit PCM data into an analog, sinusoidal signal. We calculated samples of the notes of the scale from C4-G#5 (262 Hz - 523 Hz) including the black keys and stored them in flash memory. The synthesizer can only play the first 15 notes (C4-D#5), but the microcontroller can output the full range of PCM data. The sampling rate we used was 20 kHz, which is approximately 20 times the Nyquist frequency for the highest frequency note. We used an op-amp on the output of the signal to generate a voltage from the output current and to amplify the signal. We had problems with our original op-amp (LM358) chopping of the top of the sine wave output. However, then we switched over to an audio op-amp, the LF353. This op-amp was much better because it only chopped off the very top of the signal. We corrected the problem by lowering the gain resistor on the op-amp. This method provided reasonable sine waves. In order to output the data at 20kHz, we used Timer 1 with compare match A and B. We set up the timer so that it would call the compare match B interrupt when the Timer 1 is 25. We also have the timer reset on a compare match with A, which is 26. In this way, Timer 1 counts to 25, resets, and counts to 25 in an endless loop. In the compare match B ISR, the program outputs the current sample to PORTC. At button push time, the program finds the starting address of the samples for one period of a sine wave. Several registers store the beginning address of the sine wave and the pointer to the current sample. At each interrupt, the pointer to the current sample gets incremented, and the next sample is sent to the DAC. Once the pointer reaches the end of the samples, it resets to the start of the samples for that particular note. Since sine waves are periodic, we need only to store the samples for one period.


N.B. The DAC is connected to Port C rather than Port D

Results of the Design

Given the time allotted, we were able to generate a reasonable approximation for a sine wave and allow the user to play these tones, record and playback these tones, and learn a simple song. Having a little more time, we could have added the ability to play multiple tones at the same time. All that is needed is to add more entries into the keypad lookup table to cover multiple keypresses and add code that would sum the two sets of samples from each waveform. Also, another improvement in the tone generation would be to filter the output analog signal to remove unwanted frequencies.

Looking back in retrospect, we tried to encompass a lot of features in the project and didn't focus exclusively on one area (if we had concentrated our efforts on the sine wave generation and didn't do the memory playback etc.., we could have done the multiple tone output.) Also, we would probably have used a different LCD, one with a larger display and more expanded features, since the LCD we used limited us to write 16 characters at a time. Finally, with some financial support, we could have used a keypad that actually resembled a piano keyboard and had more keys, rather than a telephone keypad.

Keypad buttons | Note Value

1 C4
2 C#4
3 D4
A D#4
4 E4
5 E#4
6 F4
B G4
7 G#4
8 A4
9 A#4
C B4
* C5
0 C#5
# D5
D D#5

Link: http://instruct1.cit.cornell.edu/courses/e...ts/s1999/hsieh/

CODE

.nolist   ;Suppress listing of include file
.include "4414def.inc" ;Define chip particulars
.list

;***** Global register variables
.def wreg =R16 ;General use working register
.def wreg2 =r17

.def timeout =R18 ;Timeout value passed to subroutine
.def lcdstat =R19 ;LCD busy/wait status
.def longtime=R20 ;Long timer for powerup
.def butnum =r21 ;final press value
.def maybe =r24
.def state =R22
.def playing =r23
.def key =r25
.def notemp =r26
.def rcdcnt =r09
.def charcnt =r01
.def butprot =r02
.def lastZL =r03
.def lastZH =r04
.def ZLtemp =r05
.def ZHtemp =r06
.def initialL=r07
.def initialH=r08
.def play1 =r10
.def notenum =r11
.def r0temp1 =r12
.def ZLtemp1 =r13
.def ZHtemp1 =r14
.def r0temp =r15
.def lttemp =r27
;***** Other equates
.equ lcdrs =PD6 ;LCD rs pin connected to PD6
.equ lcdrw =PD5 ;LCD r/w pin connected to PD5
.equ lcde =PD4 ;LCD enable pin connected to PD4

; Timer/Counter prescaler values
.equ TSTOP =0 ;Stop Timer/Counter
.equ TCK1 =1 ;Timer/Counter runs from CK
.equ TCK8 =2 ;Timer/Counter runs from CK / 8
.equ TCK64 =3 ;Timer/Counter runs from CK / 64
.equ TCK256 =4 ;Timer/Counter runs from CK / 256
.equ TCK1024 =5 ;Timer/Counter runs from CK / 1024
.equ TEXF =6 ;Timer/Counter runs from external falling edge
.equ TEXR =7 ;Timer/Counter runs from external rising edge

.dseg

rcdnts: .byte 110

.cseg

; Interrupt vectors

.org $0000
rjmp  RESET;reset entry vector
reti  
reti
reti
reti
rjmp CMP1B_MATCH
reti
rjmp t0int
reti
reti
reti
reti
reti

; Main program entry point on reset

;The following table is used to convert raw button-press high/low
;values to a sequence number. Invalid codes caused by multiple
;button presses are ignored.
keytbl: .db  0b11101110, 0b11011110, 0b10111110, 0b01111110
.db  0b11101101, 0b11011101, 0b10111101, 0b01111101
.db  0b11101011, 0b11011011, 0b10111011, 0b01111011
.db  0b11100111, 0b11010111, 0b10110111, 0b01110111

addtbl: .db  0x00, C4samtbl, 0x00, Cs4samtbl
.db  0x00, D4samtbl, 0x01, Ds4samtbl
.db  0x01, E4samtbl, 0x01, F4samtbl
.db  0x01, Fs4samtbl, 0x02, G4samtbl
.db  0x02, Gs4samtbl, 0x02, A4samtbl
.db  0x02, As4samtbl, 0x02, B4samtbl
.db  0x03, C5samtbl, 0x03, Cs5samtbl
.db  0x03, D5samtbl, 0x03, Ds5samtbl
.db  0x03, E5samtbl, 0x03, F5samtbl
.db  0x03, Fs5samtbl, 0x03, G5samtbl
.db  0x04, Gs5samtbl, 0x00, 0x00

.include "tune.inc"

reset:  ldi wreg, LOW(RAMEND);setup stack pointer
out  SPL, wreg
ldi wreg, HIGH(RAMEND)
out SPH, wreg
ldi wreg, 0x0a
out TCCR1B, wreg
ldi wreg, 0x00
out OCR1AH, wreg
out OCR1BH, wreg
ldi wreg, 0x1a
out OCR1AL, wreg
ldi wreg, 0x19
out OCR1BL, wreg
ldi wreg, 0xff
out DDRC, wreg
clr wreg
out PORTC, wreg
ldi wreg, 0x00
out TCCR1A, wreg
clr playing
;ldi wreg, 0xff;setup buttons on proto-board
;out DDRB, wreg;set PortB to be inputs
;out PINB, wreg;make sure all the buttons are up

; ************** start LCD startup ***********************************

ldi wreg,TSTOP;Timer 0 off (just in case)
out TCCR0,wreg;Stop timer
ldi wreg,0b01100010;Enable Timer 0 interrupt
out TIMSK,wreg
sei  ;Enable interrupts


;power down the LCD
ldi wreg, 0xff;LCD power connection
out DDRA, wreg;power it down after reset
ldi wreg, 0x00
out PORTA,wreg
ldi longtime,100; then Wait 1.5 second with LCD power off
ldi timeout,0;Delay 15 mS
offwait:rcall delay
dec longtime
brne offwait

;now power up LCD
ldi wreg, 0xff
out PORTA, wreg
ldi longtime,100;Wait 1.5 second for LCD power up
ldi timeout,0;Delay 15 mS  
puwait: rcall delay
dec longtime
brne puwait

; ************** end LCD startup ***********************************
;hopefully by now the LCD has rebooted


rcall lcdinit
start:;instructions sent to LCD, scrolling
rcall lcdclr
ldi ZH,high(welcome*2);first message addr to Z-ptr
ldi ZL,low(welcome*2);Init Z-pointer
rcall wrtstr
rcall bdelay
mov wreg, butprot
cpi wreg, 0x7F
brne options

default:rcall lcdclr
ldi ZH,high(rulesa*2);first message addr to Z-ptr
ldi ZL,low(rulesa*2);Init Z-pointer
rcall wrtstr
rcall bdelay
mov wreg, butprot
cpi wreg, 0x7F
brne options

rcall lcdclr
ldi ZH,high(rulesb*2);first message addr to Z-ptr
ldi ZL,low(rulesb*2);Init Z-pointer
rcall wrtstr
rcall bdelay
mov wreg, butprot
cpi wreg, 0x7F
brne options

rcall lcdclr
ldi ZH,high(rulesc*2);first message addr to Z-ptr
ldi ZL,low(rulesc*2);Init Z-pointer
rcall wrtstr
rcall bdelay
mov wreg, butprot
cpi wreg, 0x7F
brne options
 
rcall lcdclr
ldi ZH,high(rulesd*2);first message addr to Z-ptr
ldi ZL,low(rulesd*2);Init Z-pointer
rcall wrtstr
rcall bdelay
mov wreg, butprot
cpi wreg, 0x7F
brne options

rcall lcdclr
ldi ZH,high(rulese*2);first message addr to Z-ptr
ldi ZL,low(rulese*2);Init Z-pointer
rcall wrtstr
rcall bdelay
mov wreg, butprot
cpi wreg, 0x7F
brne options
rjmp default

;***** jump back to beginning if no button is pressed during scroll of rules *****

default1:rjmp default
options:mov wreg, butprot
cpi wreg, 0x7E;single node option
breq snglnde  
cpi wreg, 0x7D;record option
breq record
cpi wreg, 0x7B;memory play option
breq memplay1
cpi wreg, 0x77;LCD play option
breq LCDplay1
cpi wreg, 0x6F;exit single note option
breq endsngl1
cpi wreg, 0x5F;exit LCD play option
breq endLCD1
cpi wreg, 0x3F;recorded tune
breq rectune1
rjmp default ;treat all other pushes as nothing and loop back

;***** single note playback *****
snglnde:clr butprot

rcall lcdclr
ldi ZH,high(snglnt*2);first message addr to Z-ptr
ldi ZL,low(snglnt*2);Init Z-pointer
rcall wrtstr

ldi wreg, 0x01;set flag in flags reg denoting single node state
mov state, wreg;nothing in state so mov is ok

ser wreg
out DDRB, wreg
snglscn:rcall btdbce ;get a button push
rcall play
cpi butnum, 0xFF;no recognizable button was pushed
brne snglpsh ;branch to a single button press
clr playing ;no button is down so stop playing note
in wreg, PINA;get the button push from the proto-board
com wreg
cpi wreg, 0x10;see if the exit single note button was pushed
breq endsngl1;branch to end single note
rjmp snglscn ;loop back and scan for a button push

snglpsh:;rcall lcdclr
mov wreg, playing;get a copy of which note is playing
cpi wreg, 0x00;see if the note is still being played
brne snglscn ;if playing=0x00, then no note is being played
mov wreg, butnum;get which note is being played
;swap wreg ;shift value of note button into upper 4 bits of reg
;ori wreg, 0x0F;set lower 4 bits to F to signify that a note is being played
;swap wreg
ori wreg, 0x80
mov playing, wreg
rcall play
;mov wreg, butnum
;subi wreg, -0x30
;rcall lcdput
rjmp snglscn ;jump back and scan for another button press
;***** end single note playback *****

memplay1:rjmp memplay2
LCDplay1:rjmp LCDplay2
endsngl1:rjmp endsngl2
endLCD1:rjmp endLCD2
rectune1:rjmp rectune2
record: clr butprot
ldi wreg, 0x02;set flag in flags reg denoting record state
mov state, wreg

rcall lcdclr
ldi ZH,high(rcdply*2);first message addr to Z-ptr
ldi ZL,low(rcdply*2);Init Z-pointer
rcall wrtstr

clr rcdcnt ;count of how many notes had been played
ldi     ZL, LOW(rcdnts) ;ptr to RAM set up memory to store notes
       ldi     ZH, HIGH(rcdnts)


rcdng:
mov wreg, rcdcnt
cpi wreg, 100;limit of # of notes hit?
breq rcddone ;if so branch to done
rcall btdbce
cpi butnum, 0xFF;a keyboard button pressed?
breq rcdopt
st Z,butnum;store note into RAM
;out PORTC, ZL
inc ZL ;increment Z pointer
inc rcdcnt ;increment count
;mov wreg, butnum
;subi wreg, -0x30
;rcall lcdput

mov wreg, butnum;get which note is being played
;swap wreg ;shift value of note button into upper 4 bits of reg
;ori wreg, 0x0F;set lower 4 bits to F to signify that a note is being played
;swap wreg
ori wreg, 0x80
mov playing, wreg
rcall play

rjmp rcdng

rcdopt: clr playing
in wreg, PINA;see if user wants to exit
com wreg
cpi wreg, 0x04;user wants to memory play
breq memplay ;start memory play
rjmp rcdng ;jump back

memplay2:rjmp memplay
LCDplay2:rjmp LCDplay
endsngl2:rjmp endsngl
endLCD2:rjmp endLCD
rectune2:rjmp rectune

rcddone:clr state
clr playing
ldi ZH,high(endrcd*2);first message addr to Z-ptr
ldi ZL,low(endrcd*2);Init Z-pointer
rcall wrtstr
rcall bdelay ;delay so user can see
rjmp default

memplay:clr butprot
ldi wreg, 0x04;set flag in flags reg denoting memory playback state
mov state, wreg

rcall lcdclr
ldi ZH,high(memply*2);first message addr to Z-ptr
ldi ZL,low(memply*2);Init Z-pointer
rcall wrtstr

ldi     ZL, LOW(rcdnts) ;ptr to RAM set up memory to store notes
       ldi     ZH, HIGH(rcdnts)


rcall lcdclr
mmplyng:mov wreg, rcdcnt
cpi wreg, 0x00
breq mmdone
ld wreg, Z ;get number from memory
;subi wreg, -0x30
;rcall lcdput

;ld r0, Z
;out PORTC, ZL
;mov wreg, r0
inc ZL
;swap wreg ;move note # to upper 4 bits
;ori wreg, 0x0F;note is now playing
;swap wreg
ori wreg, 0x80
mov playing, wreg
rcall play
dec rcdcnt ;count of # of total notes stored

rcall mdelay ;delay for certain time
rjmp mmplyng

mmdone: clr state
clr playing
clr rcdcnt
rcall lcdclr
ldi ZH,high(endmem*2);first message addr to Z-ptr
ldi ZL,low(endmem*2);Init Z-pointer
rcall wrtstr
rcall bdelay ;delay so user can see
rjmp default


LCDplay:clr butprot
ldi wreg, 0x08;set flag in flags reg denoting LCD playback state
mov state, wreg

rcall lcdclr
ldi ZH,high(lcdply*2);first message addr to Z-ptr
ldi ZL,low(lcdply*2);Init Z-pointer
rcall wrtstr

rcall bdelay

rcall lcdclr
ldi ZH, high(tunea*2)
ldi ZL, low(tunea*2)

clr playing
clr charcnt
mov wreg, charcnt
cpi wreg, 15
breq lcdend1
lcdnxt: lpm
tst R0 ;See if at end of message
breq lcdend1 ;If so, next message
mov wreg, charcnt
cpi wreg,8 ;addressing changes at char #8!
brne lcdwrtit;at char 8, fix it
ldi wreg,0xC0;Set address to last 8 chars
rcall lcdcmd  
rcall sdelay
lcdwrtit:mov wreg,R0 ;Send it to the LCD
rcall lcdput
adiw ZL,1 ;Increment Z-pointer
inc charcnt ;keep track of chars on display

ldi wreg, 0x20
rcall lcdput
inc charcnt
rcall lcddelay
mov wreg, butprot
cpi wreg, 0x5F
breq lcdends1
rjmp lcdnxt

lcdend1:rcall lcdclr
ldi ZH, high(tuneb*2)
ldi ZL, low(tuneb*2)

clr playing
clr charcnt
mov wreg, charcnt
cpi wreg, 15
breq lcdend2
lcdnxt1:lpm
tst R0 ;See if at end of message
breq lcdend2 ;If so, next message
mov wreg, charcnt
cpi wreg,8 ;addressing changes at char #8!
brne lcdwrt1 ;at char 8, fix it
ldi wreg,0xC0;Set address to last 8 chars
rcall lcdcmd  
rcall sdelay
lcdwrt1:mov wreg,R0 ;Send it to the LCD
rcall lcdput
adiw ZL,1 ;Increment Z-pointer
inc charcnt ;keep track of chars on display

ldi wreg, 0x20
rcall lcdput
inc charcnt
rcall lcddelay
mov wreg, butprot
cpi wreg, 0x5F
breq lcdends
rjmp lcdnxt1

lcdend2:rcall lcdclr
ldi ZH, high(tunec*2)
ldi ZL, low(tunec*2)

clr playing
clr charcnt
mov wreg, charcnt
cpi wreg, 15
breq lcdend3
lcdnxt2:lpm
tst R0 ;See if at end of message
breq lcdend3 ;If so, next message
mov wreg, charcnt
cpi wreg,8 ;addressing changes at char #8!
brne lcdwrt2 ;at char 8, fix it
ldi wreg,0xC0;Set address to last 8 chars
rcall lcdcmd  
rcall sdelay
lcdwrt2:mov wreg,R0 ;Send it to the LCD
rcall lcdput
adiw ZL,1 ;Increment Z-pointer
inc charcnt ;keep track of chars on display

ldi wreg, 0x20
rcall lcdput
inc charcnt
rcall lcddelay
mov wreg, butprot
cpi wreg, 0x5F
breq lcdends
rjmp lcdnxt2

lcdends1:rjmp lcdends
lcdend3:rcall lcdclr
ldi ZH, high(tuned*2)
ldi ZL, low(tuned*2)

clr playing
clr charcnt
mov wreg, charcnt
cpi wreg, 15
breq lcdends
lcdnxt3:lpm
tst R0 ;See if at end of message
breq lcdends ;If so, next message
mov wreg, charcnt
cpi wreg,8 ;addressing changes at char #8!
brne lcdwrt3 ;at char 8, fix it
ldi wreg,0xC0;Set address to last 8 chars
rcall lcdcmd  
rcall sdelay
lcdwrt3:mov wreg,R0 ;Send it to the LCD
rcall lcdput
adiw ZL,1 ;Increment Z-pointer
inc charcnt ;keep track of chars on display

ldi wreg, 0x20
rcall lcdput
inc charcnt
rcall lcddelay
mov wreg, butprot
cpi wreg, 0x5F
breq lcdends
rjmp lcdnxt3

lcdends:rcall lcdclr
rjmp endLCD

rectune:clr butprot
ldi wreg, 0x10;set flag in flags reg denoting memory playback state
mov state, wreg

rcall lcdclr
ldi ZH,high(rtune*2);first message addr to Z-ptr
ldi ZL,low(rtune*2);Init Z-pointer
rcall wrtstr

ldi     ZH, HIGH(imp*2) ;ptr to RAM set up memory to store notes
       ldi     ZL, LOW(imp*2)


clr  playing
rcall lcdclr
rtplyng:lpm
mov wreg, r0
cpi wreg, 0x00
breq rtdone
mov playing, r0

rcall play
adiw ZL, 1
rcall rtdelay ;delay for certain time
rjmp rtplyng

rtdone: clr state
clr playing
rcall lcdclr
ldi ZH,high(endrt*2);first message addr to Z-ptr
ldi ZL,low(endrt*2);Init Z-pointer
rcall wrtstr
rcall bdelay ;delay so user can see
rjmp default



endsngl:clr butprot
clr state ;return to the default state

rcall lcdclr
ldi ZH,high(endsgl*2);first message addr to Z-ptr
ldi ZL,low(endsgl*2);Init Z-pointer
rcall wrtstr
rcall bdelay ;delay so user can see
rjmp default

endLCD: clr butprot
clr state ;return to the default state
rcall lcdclr
ldi ZH,high(enlcd*2);first message addr to Z-ptr
ldi ZL,low(enlcd*2);Init Z-pointer
rcall wrtstr
rcall bdelay
rjmp default

;***** write a string to the LCD *****
wrtstr: clr charcnt
nextc: lpm  ;Get next character from ROM
tst R0 ;See if at end of message
breq strend ;If so, next message
mov wreg, charcnt
cpi wreg,8 ;addressing changes at char #8!
brne wrtit ;at char 8, fix it
ldi wreg,0xC0;Set address to last 8 chars
rcall lcdcmd  
rcall sdelay
wrtit: mov wreg,R0 ;Send it to the LCD
rcall lcdput
adiw ZL,1 ;Increment Z-pointer
inc charcnt ;keep track of chars on display
rjmp nextc ;Loop for more
strend: ret
;***** end write a string to the LCD *****

;***** delay for lcdcmd to go through *****
sdelay: ldi longtime,5;Wait 1.5 second for human to read it
ldi timeout,0;Delay 15 ms  
wait1: rcall delay
dec longtime
brne wait1
ret
;***** end delay for lcdcmd to go through *****

;***** delay for .25 sec *****
mdelay: ldi longtime,25;Wait 1.5 second for human to read it
ldi timeout,0;Delay 15 ms  
mwait: rcall delay
dec longtime
brne mwait
ret

rtdelay:ldi longtime,10;Wait 1.5 second for human to read it
ldi timeout,0;Delay 15 ms  
rtwait: rcall delay
dec longtime
brne rtwait
ret

;***** delay that polls PINA for press *****
bdelay: ldi longtime,500;Wait 1.5 second for human to read it
ldi timeout,0;Delay 15 ms  
bwait: in wreg, PINA
andi wreg, 0x7F
mov butprot, wreg
cpi wreg, 0x7F
brne bdone
rcall delay
dec longtime
brne bwait
bdone: ret

;***** lcd delay *****
lcddelay:ldi longtime,7
ldi timeout, 0
lcdwat: rcall btdbce
cpi butnum, 0xFF
breq lcdskp
mov wreg, butnum
;swap wreg ;move note # to upper 4 bits
;ori wreg, 0x0F;note is now playing
;swap wreg
ori wreg, 0x80
mov playing, wreg
rcall play
rjmp lcddec
lcdskp: clr playing
in wreg, PINA
andi wreg, 0x7F
mov butprot, wreg
cpi wreg, 0x5F
brne lcddec
ret
lcddec: dec longtime
brne lcdwat
ret

;***** delay that polls keyboard for press *****
bkdelay:ldi longtime,300;Wait 1.5 second for human to read it
ldi timeout,0;Delay 15 ms  
bkwait: rcall btdbce
cpi butnum, 0xFF;no recognizable button was pushed
breq bkdec ;branch to a single button press
;mov wreg, butnum;get which note is being played
;swap wreg ;shift value of note button into upper 4 bits of reg
;ori wreg, 0x0F;set lower 4 bits to F to signify that a note is being played
ori wreg, 0x80
mov playing, wreg
ret

bkdec: clr playing
dec longtime
brne bwait
bkdone: ret


;********* button debounce ***************************
btdbce: rcall   butloop        ;read in button press

mov wreg2, butnum
; cpi wreg2, 0xFF
; breq btdbce
mov     maybe, butnum  ;make a copy of the button previously read in
butdcd: ldi lttemp,7
ldi timeout,0;Delay 15 ms  
btwait: rcall delay
dec lttemp
brne btwait

rcall   butloop
cp      maybe, butnum;butnum contains the button that was pushed
breq    btdone
rjmp    btdbce
btdone: clr maybe
ret
;********* end button debounce ***************************



;================================================
; Clear entire LCD and delay for a bit
lcdclr:
ldi wreg,1 ;Clear LCD command
rcall lcdcmd
ldi timeout,256;Delay 15 mS for clear command
rcall delay
ret

;================================================
; Initialize LCD module
lcdinit:
;cbi PORTA,0 ;Turn on LED 0
ldi wreg,0 ;Setup port pins
out PORTD,wreg;Pull all pins low
ldi wreg,0xff;All pins are outputs
out DDRD,wreg
ldi timeout,256;Wait at least 15 mS at power up
rcall delay

; LCD specs call for 3 repetitions as follows
ldi wreg,3 ;Function set
out PORTD,wreg;to 8-bit mode
nop  ;nop is data setup time
sbi PORTD,lcde;Toggle enable line
cbi PORTD,lcde

ldi timeout,256;Wait at least 15 mS
rcall delay

ldi wreg,3 ;Function set
out PORTD,wreg
nop
sbi PORTD,lcde;Toggle enable line
cbi PORTD,lcde

ldi timeout,256;Wait at least 15 ms
rcall delay

ldi wreg,3 ;Function set
out PORTD,wreg
nop
sbi PORTD,lcde;Toggle enable line
cbi PORTD,lcde

ldi timeout,256;Wait at least 15 ms
rcall delay

ldi wreg,2 ;Function set, 4 line interface
out PORTD,wreg
nop
; rcall strobe ;Toggle enable line
sbi PORTD,lcde;Toggle enable line
cbi PORTD,lcde

ldi wreg,0b11110000;Make 4 data lines inputs
out DDRD,wreg

; Finally,
; At this point, the normal 4 wire command routine can be used

ldi wreg,0b00100000;Function set, 4 wire, 1 line, 5x7 font
rcall lcdcmd

ldi wreg,0b00001100;Display on, no cursor, no blink
rcall lcdcmd

ldi wreg,0b00000110;Address increment, no scrolling
rcall lcdcmd
;sbi PORTB,0 ;Turn off LED 0
ret

;============================================
; Wait for LCD to go unbusy
lcdwait:
;cbi PORTA,1 ;Turn on LED 1
ldi wreg,0xF0;Make 4 data lines inputs
out DDRD,wreg
sbi PORTD,lcdrw;Set r/w pin to read
cbi PORTD,lcdrs;Set register select to command
waitloop:
sbi PORTD,lcde;Toggle enable line
cbi PORTD,lcde
in lcdstat,PIND;Read busy flag
;Read, and ignore lower nibble
sbi PORTD,lcde;Toggle enable line
cbi PORTD,lcde

sbrc lcdstat,3;Loop until done
rjmp waitloop
;sbi PORTA,1 ;Turn off LED 1
ret

;=============================================
; Send command in wreg to LCD
lcdcmd:
push wreg ;Save character
rcall lcdwait ;Wait for LCD to be ready
ldi wreg,0xFF;Make all port D pins outputs
out DDRD,wreg
pop wreg ;Get character back
push wreg ;Save another copy
swap wreg ;Get upper nibble
andi wreg,0x0F;Strip off upper bits
out PORTD,wreg;Put on port
nop  ;wait for data setup time
sbi PORTD,lcde;Toggle enable line
cbi PORTD,lcde

pop wreg ;Recall character
andi wreg,0x0F;Strip off upper bits
out PORTD,wreg;Put on port
nop
sbi PORTD,lcde;Toggle enable line
cbi PORTD,lcde

ldi wreg,0xF0;Make 4 data lines inputs
out DDRD,wreg
ret

;=============================================
; Send character data in wreg to LCD
lcdput:
push wreg ;Save character
rcall lcdwait ;Wait for LCD to be ready
ldi wreg,0xFF;Make all port D pins outputs
out DDRD,wreg

pop wreg ;Get character back
push wreg ;Save another copy
swap wreg ;Get upper nibble
andi wreg,0x0F;Strip off upper bits
out PORTD,wreg;Put on port
sbi PORTD,lcdrs;Register select set for data
nop
sbi PORTD,lcde;Toggle enable line
cbi PORTD,lcde

pop wreg ;Recall character
andi wreg,0x0F;Strip off upper bits
out PORTD,wreg;Put on port
sbi PORTD,lcdrs;Register select set for data
nop
sbi PORTD,lcde;Toggle enable line
cbi PORTD,lcde
;cbi PORTD,lcdrs;--

ldi wreg,0xF0;Make 4 data lines inputs
out DDRD,wreg
ret

; ******************************* Button detection ********************************

butloop:ldi     wreg, 0x0f     ;set lower four lines to output
       out     DDRB, wreg
       ldi     wreg, 0xf0     ;and turn on the pullups on the inputs
       out     PORTB, wreg
       nop                    ;Need some time for the pullups to
       nop                    ;charge the port pins
       nop
       nop
       in      wreg, PINB     ;read the high nibble
       mov     key, wreg      ;and store it (with zeros in the low nibble)


       ldi     wreg, 0xf0     ;set upper four lines to outputs
       out     DDRB, wreg
       ldi     wreg, 0x0f     ;and turn on pullups on the inputs
       out     PORTB, wreg
       
       nop                    ;As before wait for the pin to charge
       nop    
       nop
       nop
       in      wreg, PINB     ;read the low nibble
       or      key, wreg      ;combine to make key code
       
      ;At the point the raw key code should have exactly one zero each in
      ;the lower and upper nibbles. Any other number of zeros indicates
      ;either no-button pressed or multiple-button pressed.

      ;Now search the table for a match to the raw key code
      ;and exit with a button number
mov ZLtemp1, ZL
mov ZHtemp1, ZH
mov r0temp1, r0
       ldi     ZL, low(keytbl*2)      ;table pointer in FLASH
       ldi     ZH, high(keytbl*2)     ;so convert from word to byte addr
       ldi     wreg, 0
mov butnum, wreg

tbllp:  lpm                    ;get the table entry
       cp      key, r0        ;match?
       breq    butfnd
       inc     butnum         ;if not, have we exhaused the
mov wreg, butnum
       cpi     wreg, 0x10   ;table
       breq    illegal
       adiw    ZL, 1          ;if not, get the next table entry
       rjmp    tbllp

butfnd: clr key
mov ZL, ZLtemp1
mov ZH, ZHtemp1
mov r0, r0temp1
ret

illegal:ser     wreg           ;no table match means return 0xFF
mov     butnum, wreg
mov ZL, ZLtemp1
mov ZH, ZHtemp1
mov r0, r0temp1
       ret

; ******************************* Button detection end ********************************

play:   ldi notemp, 0x80
and notemp, playing
cpi notemp, 0x80
brne nopres
clr  notenum
ldi notemp, 0x7f
and notemp, playing
mov ZHtemp1, ZH
mov ZLtemp1, ZL
mov r0temp1, r0
ldi ZH,high(addtbl*2);first message addr to Z-ptr
ldi ZL,low(addtbl*2);Init Z-pointer

nextw: lpm   ;Get next word from ROM
cp notenum, notemp ;See if at end of message
breq foundn
adiw ZL, 2
inc notenum
rjmp nextw
foundn: mov notemp, r0
adiw ZL, 1
lpm
mov notenum, r0
adiw ZL, 1
lsl notenum
;rol notemp
mov initialL, notenum
mov initialH, notemp

mov ZH, ZHtemp1
mov ZL, ZLtemp1
mov r0, r0temp1
ret
nopres: clr wreg2
out PORTC, wreg2
mov ZH, ZHtemp1
mov ZL, ZLtemp1
mov r0, r0temp1
ret


;=============================================
;***** Timer 0 overflow interrupt handler
t0int: ;in save, SREG
set  ;Set T flag
ldi wreg,TSTOP;Timer 0 off
out TCCR0,wreg;Stop timer
;out SREG, save
reti  ;Done, return

CMP1B_MATCH:
;in save, SREG
;out PORTC, count
;inc count
mov ZLtemp, ZL
mov ZHtemp, ZH
mov r0temp, r0

ldi wreg2, 0x80
and wreg2, playing
cpi wreg2, 0x80
brne idle
tst play1
brne notepl
mov ZH,initialH
mov ZL,initialL
;ldi ZH, high(C5samtbl*2)
;ldi ZL, low(C5samtbl*2)
mov lastZL, ZL
mov lastZH, ZH
ldi wreg2, 0xff
mov play1, wreg2
notepl: mov ZL, lastZL
mov ZH, lastZH
lpm
out PORTC, r0
ldi wreg2, 128
cp r0, wreg2
brne cont
clr play1
cont: adiw ZL, 1
mov lastZL, ZL
mov lastZH, ZH
mov ZL, ZLtemp
mov ZH, ZHtemp
mov r0, r0temp
;out SREG, save
reti
idle: clr wreg2
out PORTC, wreg2
mov ZL, ZLtemp
mov ZH, ZHtemp
mov r0, r0temp
;out SREG, save
reti


;***** Delay n*64 microseconds using timer 0, delay time passed in timeout
; weird construction, interrupt is called like a subroutine

delay: in wreg,SREG;Save status register
push wreg
out TCNT0,timeout
clt  ;Clear T
ldi wreg,TCK256;Timer 0 prescaler, CK / 256
out TCCR0,wreg;Run timer
dwait: brtc dwait ;Wait for timer 0 interrupt to set T
pop wreg ;Restore status register
out SREG,wreg
ret

;=============================================
; text data
welcome:.db "Bootup ",0x00
rulesa: .db "1 Single Nte Ply", 0x00
rulesb: .db "2 Record Play   ", 0x00
rulesc: .db "3 Mem Playback  ", 0x00
rulesd: .db "4 LCD Play      ", 0x00
rulese: .db "5 Recorded Tune ", 0x00
snglnt: .db "Single Note Play", 0x00
endsgl: .db "Single Play Done", 0x00
rcdply: .db "Record Play ", 0x00
endrcd: .db "Recording Done", 0x00
memply: .db "Mem Playback ", 0x00
endmem: .db "Mem Plyback Done", 0x00
lcdply: .db "LCD Play", 0x00
enlcd: .db "LCD Play Done", 0x00
tunea: .db "113154 ", 0x00
tuneb: .db "1131B5 ", 0x00
tunec: .db "11*8543 ", 0x00
tuned: .db "9985B5 ", 0x00
endrt: .db "Rcrded Tune Done", 0x00
rtune:.db "Playing Tune", 0x00




Admin5- 04-21-2006

Waveform samples and prerecorded tune

CODE

C4samtbl:
  .db  138, 149, 159, 169
  .db  179, 188, 197, 206
  .db  214, 221, 228, 234
  .db  239, 244, 248, 251
  .db  253, 254, 255, 255
  .db  253, 251, 248, 245
  .db  240, 235, 229, 222
  .db  215, 207, 199, 190
  .db  180, 171, 161, 151
  .db  140, 130, 119, 109
  .db  99, 89, 79, 69
  .db  60, 52, 44, 36
  .db  29, 23, 17, 13
  .db  9, 6, 3, 2
  .db  1, 1, 2, 4
  .db  7, 11, 15, 20
  .db  26, 33, 40, 48
  .db  56, 65, 74, 84
  .db  94, 104, 114, 128
Cs4samtbl:
  .db  139, 150, 161, 171
  .db  182, 191, 201, 209
  .db  218, 225, 232, 238
  .db  243, 247, 251, 253
  .db  254, 255, 255, 253
  .db  251, 248, 243, 238
  .db  232, 226, 218, 210
  .db  202, 192, 183, 172
  .db  162, 151, 140, 129
  .db  118, 107, 96, 86
  .db  75, 66, 56, 47
  .db  39, 32, 25, 19
  .db  14, 9, 6, 3
  .db  2, 1, 1, 3
  .db  5, 8, 12, 17
  .db  23, 29, 37, 45
  .db  54, 63, 72, 83
  .db  93, 104, 115, 128
D4samtbl:
  .db  140, 151, 163, 174
  .db  185, 195, 205, 214
  .db  222, 229, 236, 242
  .db  246, 250, 253, 254
  .db  255, 254, 253, 250
  .db  246, 242, 236, 229
  .db  222, 214, 205, 195
  .db  185, 174, 163, 151
  .db  140, 129, 116, 105
  .db  93, 82, 72, 61
  .db  52, 43, 34, 27
  .db  20, 14, 10, 6
  .db  3, 2, 1, 2
  .db  3, 6, 9, 14
  .db  20, 26, 34, 42
  .db  51, 61, 71, 82
  .db  93, 104, 116, 128
Ds4samtbl:
  .db  140, 153, 165, 176
  .db  188, 198, 208, 217
  .db  226, 233, 240, 245
  .db  249, 252, 254, 255
  .db  254, 253, 250, 246
  .db  241, 234, 227, 219
  .db  210, 200, 189, 178
  .db  167, 155, 142, 130
  .db  118, 105, 93, 81
  .db  70, 59, 49, 40
  .db  31, 24, 17, 12
  .db  7, 4, 2, 1
  .db  1, 3, 6, 10
  .db  15, 21, 28, 36
  .db  45, 55, 65, 76
  .db  88, 100, 112, 128
E4samtbl:
  .db  141, 154, 167, 179
  .db  191, 202, 212, 222
  .db  230, 237, 243, 248
  .db  252, 254, 255, 255
  .db  253, 250, 245, 239
  .db  232, 224, 215, 205
  .db  194, 183, 171, 158
  .db  145, 132, 119, 106
  .db  93, 81, 69, 57
  .db  47, 37, 28, 21
  .db  14, 9, 5, 2
  .db  1, 1, 3, 5
  .db  9, 15, 21, 29
  .db  38, 48, 58, 70
  .db  82, 94, 107, 120
  .db  128, 128, 128, 128
F4samtbl:
  .db  142, 156, 169, 182
  .db  194, 206, 216, 226
  .db  234, 241, 247, 251
  .db  254, 255, 255, 253
  .db  250, 245, 239, 231
  .db  222, 213, 202, 190
  .db  178, 164, 151, 137
  .db  123, 109, 96, 82
  .db  70, 58, 47, 36
  .db  27, 19, 13, 8
  .db  4, 2, 1, 2
  .db  4, 8, 13, 20
  .db  28, 37, 47, 58
  .db  70, 83, 96, 110
  .db  128, 128, 128, 128
Fs4samtbl:
  .db  143, 157, 171, 185
  .db  198, 210, 220, 230
  .db  238, 245, 250, 253
  .db  255, 255, 253, 250
  .db  245, 238, 230, 221
  .db  210, 198, 185, 172
  .db  158, 143, 130, 114
  .db  99, 85, 71, 59
  .db  47, 36, 26, 18
  .db  12, 6, 3, 1
  .db  1, 3, 6, 11
  .db  18, 26, 35, 46
  .db  58, 70, 84, 98
  .db  112, 128, 128, 128
G4samtbl:
  .db  144, 159, 174, 188
  .db  201, 214, 224, 234
  .db  242, 248, 252, 254
  .db  255, 254, 250, 245
  .db  238, 229, 219, 208
  .db  195, 181, 167, 151
  .db  136, 120, 105, 90
  .db  75, 61, 49, 37
  .db  27, 18, 11, 6
  .db  3, 1, 2, 4
  .db  8, 14, 22, 31
  .db  42, 54, 68, 82
  .db  97, 112, 128, 128
Gs4samtbl:
  .db  145, 161, 176, 191
  .db  205, 218, 228, 238
  .db  245, 250, 254, 255
  .db  254, 251, 246, 239
  .db  229, 219, 206, 193
  .db  178, 162, 146, 130
  .db  113, 97, 81, 66
  .db  52, 40, 29, 19
  .db  12, 6, 2, 1
  .db  2, 5, 10, 17
  .db  26, 36, 48, 62
  .db  77, 92, 108, 128
A4samtbl:
  .db  145, 163, 179, 195
  .db  209, 222, 233, 242
  .db  248, 253, 255, 255
  .db  252, 247, 239, 230
  .db  218, 205, 191, 175
  .db  158, 141, 123, 106
  .db  89, 72, 57, 43
  .db  31, 21, 12, 6
  .db  2, 1, 2, 5
  .db  11, 19, 29, 41
  .db  55, 70, 86, 103
  .db  128, 128, 128, 128
As4samtbl:
  .db  147, 165, 182, 198
  .db  213, 226, 237, 245
  .db  251, 254, 255, 253
  .db  248, 241, 231, 219
  .db  205, 190, 173, 155
  .db  137, 118, 100, 82
  .db  65, 50, 36, 24
  .db  15, 7, 3, 1
  .db  2, 5, 12, 20
  .db  31, 44, 59, 75
  .db  93, 111, 128, 128
B4samtbl:
  .db  148, 167, 185, 202
  .db  217, 230, 240, 248
  .db  253, 255, 254, 250
  .db  243, 233, 220, 206
  .db  189, 171, 152, 133
  .db  113, 94, 75, 58
  .db  43, 29, 18, 9
  .db  4, 1, 2, 5
  .db  11, 21, 32, 47
  .db  63, 80, 99, 128
x:
  .db  0, 0, 0, 0
  .db  0, 0, 0, 0
C5samtbl:
  .db  149, 169, 188, 206
  .db  221, 234, 244, 251
  .db  254, 255, 251, 245
  .db  235, 223, 208, 190
  .db  171, 151, 131, 110
  .db  89, 70, 52, 37
  .db  24, 13, 6, 2
  .db  1, 4, 10, 19
  .db  32, 46, 64, 82
  .db  102, 128, 128, 128
Cs5samtbl:
  .db  150, 171, 191, 209
  .db  225, 238, 247, 253
  .db  255, 253, 248, 238
  .db  226, 210, 192, 172
  .db  151, 129, 107, 86
  .db  66, 47, 32, 19
  .db  9, 3, 1, 3
  .db  8, 17, 29, 45
  .db  63, 83, 104, 128
D5samtbl:
  .db  151, 174, 195, 213
  .db  229, 242, 250, 254
  .db  254, 250, 242, 230
  .db  214, 195, 175, 152
  .db  129, 106, 83, 62
  .db  43, 27, 15, 6
  .db  2, 1, 5, 14
  .db  26, 41, 60, 81
  .db  103, 128, 128, 128
Ds5samtbl:
  .db  153, 176, 198, 217
  .db  233, 245, 252, 255
  .db  253, 246, 234, 219
  .db  200, 178, 155, 130
  .db  105, 81, 59, 40
  .db  24, 12, 4, 1
  .db  3, 10, 21, 36
  .db  55, 76, 100, 128
E5samtbl:
  .db  154, 179, 202, 222
  .db  237, 248, 254, 255
  .db  250, 239, 225, 206
  .db  183, 159, 133, 106
  .db  81, 58, 38, 21
  .db  9, 3, 1, 5
  .db  14, 28, 47, 69
  .db  93, 128, 128, 128
F5samtbl:
  .db  156, 182, 206, 226
  .db  241, 251, 255, 253
  .db  245, 231, 213, 190
  .db  164, 137, 109, 82
  .db  58, 36, 19, 8
  .db  2, 2, 8, 20
  .db  37, 58, 83, 110
  .db  128, 128, 128, 128
Fs5samtbl:
  .db  157, 185, 210, 230
  .db  245, 253, 255, 250
  .db  238, 221, 198, 172
  .db  143, 114, 85, 59
  .db  36, 18, 6, 1
  .db  3, 11, 26, 46
  .db  70, 98, 128, 128
G5samtbl:
  .db  159, 188, 214, 234
  .db  248, 254, 254, 245
  .db  229, 208, 181, 151
  .db  120, 90, 61, 37
  .db  18, 6, 1, 4
  .db  14, 31, 54, 82
  .db  112, 128, 128, 128
Gs5samtbl:
  .db  161, 191, 218, 238
  .db  251, 255, 251, 238
  .db  218, 192, 162, 129
  .db  96, 66, 39, 19
  .db  6, 1, 5, 17
  .db  37, 63, 93, 128


imp:
.db 0x87, 0x87, 0x87, 0x87
.db 0x87, 0x87, 0x87, 0x87
.db 0x87, 0x87, 0x87, 0x87
.db 0x81, 0x81, 0x81, 0x88
.db 0x87, 0x87, 0x87, 0x87
.db 0x81, 0x81, 0x81, 0x88
.db 0x87, 0x87, 0x87, 0x87
.db 0x87, 0x87, 0x87, 0x87
.db 0x8D, 0x8D, 0x8D, 0x8D
.db 0x8D, 0x8D, 0x8D, 0x8D
.db 0x8D, 0x8D, 0x8D, 0x8D
.db 0x8E, 0x8E, 0x8E, 0x88
.db 0x85, 0x85, 0x85, 0x85
.db 0x82, 0x82, 0x82, 0x88
.db 0x87, 0x87, 0x87, 0x87
.db 0x87, 0x87, 0x87, 0x87
.db 0x93, 0x93, 0x93, 0x93
.db 0x87, 0x87, 0x87, 0x87
.db 0x87, 0x93, 0x93, 0x93
.db 0x93, 0x92, 0x92, 0x92
.db 0x91, 0x90, 0x8F, 0x90
.db 0x90, 0x90, 0x90, 0x88
.db 0x88, 0x8D, 0x8D, 0x8D
.db 0x8D, 0x8C, 0x8C, 0x8C
.db 0x8B, 0x8A, 0x89, 0x8A
.db 0x8A, 0x8A, 0x8A, 0x83
.db 0x83, 0x86, 0x86, 0x86
.db 0x86, 0x82, 0x82, 0x82
.db 0x87, 0x8A, 0x8A, 0x8A
.db 0x8A, 0x87, 0x87, 0x87
.db 0x8A, 0x8E, 0x8E, 0x8E
.db 0x8E, 0x8E, 0x8E, 0x8E
.db 0x8E, 0x93, 0x93, 0x93
.db 0x93, 0x87, 0x87, 0x87
.db 0x87, 0x87, 0x93, 0x93
.db 0x93, 0x93, 0x92, 0x92
.db 0x92, 0x91, 0x90, 0x8F
.db 0x90, 0x90, 0x90, 0x90
.db 0x88, 0x88, 0x8D, 0x8D
.db 0x8D, 0x8D, 0x8C, 0x8C
.db 0x8C, 0x8B, 0x8A, 0x89
.db 0x8A, 0x8A, 0x8A, 0x8A
.db 0x83, 0x83, 0x86, 0x86
.db 0x86, 0x86, 0x82, 0x82
.db 0x82, 0x8A, 0x87, 0x87
.db 0x87, 0x87, 0x82, 0x82
.db 0x82, 0x8A, 0x87, 0x87
.db 0x87, 0x87, 0x87, 0x87
.db 0x00, 0x00, 0x00, 0x00


Forumer™ is Voted #1 Free Forum Hosting provider
Build your own community today with the largest message board hosting company.