;file: code.asm ;******Digital Beat Tracker ************************************ ; Kenneth Liu & Chris Chan ; .nolist .include "8535def.inc" .list ;***** register variables .def save =r1 ;saves the SREG in ISRs .def reload =r2 ;timer 0 interval .def lcdstat =r3 .def stopflag=r4 ;flag for stopped '0' if not stopped .def maxavgL =r7 ;maxavg regs .def maxavgH =r8 ; .def thresL =r12 ;threshold regs .def thresH =r13 ; .def AnaLo =r24 ;A to D result .def AnaHi =r25 ; ;******* Below Regs aren't used after mode selection ;******* so OK to use same regs for division and mult code .def key =r5 ;holds raw press value .def Temp =r16 ;temporary register .def count30 =r26 ;30 msec counter .def butnum =r27 ;final press value .def state =r18 ;debouncer state machine variable .def press =r20 ;contains debounced press value '0' if no keypressed .def maybe =r21 ;******* .def timeout =r19 ;Timeout value in mSec passed to subroutine .def charcnt =r23 ;Char position on the display .def chr =r16 ;a variable character to print ;***** Division Subroutine Register Variables .def drem8u =r15 ;remainder .def dres8u =r16 ;result .def dd8u =r16 ;dividend .def dv8u =r17 ;divisor .def dcnt8u =r18 ;loop counter ;***** Division Subroutine Register Variables .def drem16uL=r14 .def drem16uH=r15 .def dres16uL=r16 .def dres16uH=r17 .def dd16uL =r16 .def dd16uH =r17 .def dv16uL =r18 .def dv16uH =r19 .def dcnt16u =r20 ;***** Multiplication Subroutine Register Variables .def mc16uL =r16 ;multiplicand low byte .def mc16uH =r17 ;multiplicand high byte .def mp16uL =r18 ;multiplier low byte .def mp16uH =r19 ;multiplier high byte .def m16u0 =r18 ;result byte 0 (LSB) .def m16u1 =r19 ;result byte 1 .def m16u2 =r20 ;result byte 2 .def m16u3 =r21 ;result byte 3 (MSB) .def mcnt16u =r22 ;loop counter ;***** 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 e pin connected to PD4 .equ azero ='0' ;0x30 ascii '0' .equ btrk =13 ;'A' on keypad .equ stop =15 ;'C' on keypad .equ clearmax=16 ''D' on keypad ;*************************************** .dseg ;define voltage/resistance string sample1: .byte 2 ;inverted ADCL sample sample2: .byte 2 ;smoothed/averaged sample runavg: .byte 2 ;running average value answer: .byte 6 rstate: .byte 2 ;RAM storage for keybd scanner rmaybe: .byte 2 ; rpress: .byte 2 ; rtimeo: .byte 2 ; ;*************************************** ;***** Initialization .cseg .org $0000 rjmp RESET ;reset entry vector reti reti reti reti reti reti reti reti rjmp t0int ;timer 0 overflow reti reti reti reti reti reti reti ;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, 0b11101101, 0b11101011, 0b11100111 .db 0b11011110, 0b11011101, 0b11011011, 0b11010111 .db 0b10111110, 0b10111101, 0b10111011, 0b10110111 .db 0b01111110, 0b01111101, 0b01111011, 0b01110111 ;*************************** RESET: ldi Temp, LOW(RAMEND) ;setup stack pointer out SPL, Temp ldi Temp, HIGH(RAMEND) out SPH, Temp ;set up timer 0 for 1 mSec ticks ldi temp, 3 ;prescale timer by 64 out TCCR0, temp ldi temp,256-62 ;preload timer since mov reload, temp out TCNT0, Reload ;62.5 x (64x.25) microSec = 1.0 mSec. ;setup PORTB to all outputs to drive LEDS ser temp out DDRB, temp ;set up analog converter to read channel zero ldi temp, 0 out ADMUX, temp ldi Temp, 0b00000001 ; enable timer1a comp match interrupt out TIMSK, Temp ; and timer 0 int clr count30 ;reset keyboard debouncer vars clr press clr maybe ldi state, 1 ;initially in State 1 for debounce state machine clr stopflag ;initially not stopped clr maxavgL ;clear maxavg and thres regs clr maxavgH ; clr thresL ; clr thresH ; ;clear the contents of 'samples' in RAM ldi ZL, LOW(sample1) ldi ZH, HIGH(sample1) clr AnaLo st Z+,AnaLo clr AnaHi st Z+,AnaHi ldi ZL, LOW(sample2) ldi ZH, HIGH(sample2) st Z+,AnaLo st Z+,AnaHi ldi ZL, LOW(runavg) ldi ZH, HIGH(runavg) st Z+,AnaLo st Z+,AnaHi sei ;Enable interrupts ;***************************************************** ;new reboot LCD and clear the display main: rcall lcdinit ;Initialize LCD module prompt: rcall lcdclr ;Clear LCD screen ;prompt user for mode selection clr charcnt ;zero the character count ldi chr, 0x20 ;load var char with first printable ldi ZH,high(text1*2); ldi ZL,low(text1*2) ;Init Z-pointer rcall inflsh ;display string on LCD from FLASH ;**********finished clearing LCD and displaying mode selection prompt ;wait for selection from keypad choose: ldi ZL, LOW(rpress) ldi ZH, HIGH(rpress) ld r0, Z mov press, r0 tst press ;wait for a keypress breq choose cpi butnum, btrk brne choose rcall lcdclr clr charcnt ;zero the character count ldi chr, 0x20 ;load var char with first printable ldi ZH,high(text2*2); ldi ZL,low(text2*2) ;Init Z-pointer rcall inflsh ;display string on LCD from FLASH ;******************************************************************* t1on: clr count30 clr press clr maybe ldi state, 1 ldi Temp,0b0000000 ;disable timer0 out TIMSK, Temp ldi temp, 0b11000101 ;start A to D conversion out ADCSR, temp swait: in temp, ADCSR ;wait for A to D done andi temp, 0b01000000;by checking ADSC bit brne swait ;this bit is cleared by the AtoD hardware await: in temp, ADCSR ;wait for A to D done andi temp, 0b01000000;by checking ADSC bit brne await ;this bit is cleared by the AtoD hardware in AnaLo, ADCL ;read the voltage in AnaHi, ADCH lsl AnaLo ;double precision shift (mult by 4) rol AnaHi ;right twice lsl AnaLo rol AnaHi ; push AnaLo ; push AnaHi ; lsr AnaHi ;divide by 4 for testing ; ror AnaLo ; lsr AnaHi ; ror AnaLo ; out PORTB, AnaLo ;output to test DAC ; pop AnaHi ; pop AnaLo ;*********************************************************** ; Inverting Code for Final Project ; Range of ADC is 0-4096 => halfway point is 2048 ldi r17, Low(2048) ldi r18, High(2048) cpi AnaHi, 0b1000 ; 2048 in binary = 100000000000 => upper 8 bits = 1000 brge noinvert ; Skip inverting if great than or equal to 2048 sub r17, AnaLo ; Invert by (2048 - ADC) + 2048 sbc r18, AnaHi subi r17, -Low(2048) sbci r18, -High(2048) rjmp _inverted noinvert: ; Averaging filter to smooth values: add previous sample to new sample and divide by 2 mov r17, AnaLo mov r18, AnaHi _inverted:ldi ZL, Low(sample1); load previous inverted sample into Y ldi ZH, High(sample1) ld r0,Z ; load previous into YL mov YL, r0 st Z+, r17 ; store new inverted value (r17,r18) into memory ld r0,Z ; load previous into YH mov YH, r0 st Z+, r18 ; store new inverted value half r18 add r17, YL ; add old sample to previous sample adc r18, YH lsr r18 ; divide sum by 2 ror r17 ; Take first order derivative (Abs Value of difference between samples) ldi ZL, Low(sample2); load previous smoothed sample ldi ZH, High(sample2) ld r0,Z ; put previous into YL mov YL, r0 st Z+, r17 ; store new smoothed value into memory ld r0,Z ; put other half of previous sample into YH mov YH, r0 st Z+, r18 ; store other half of new smoothed value sub r17, YL ; subtract (New sample - Old sample) sbc r18, YH brpl noinvert2 ; if negative, complement to rectify com r17 com r18 ldi temp, 1 add r17,temp clr temp adc r18,temp noinvert2: ;************************************************************ ; Running Average Code for Final Project ; Uses 24/16 Bit unsigned division code and 16x16 unsigned multiplication code ; Formula for running average = (old running avg)*(Coeff) + (new sample)*(1-Coeff) ; Currently setting Coeff = .975 ; Note: Since we cannot do decimal, we use 975 to represent .975 ; So our formula is: new running avg=(old running avg)*(Coeff)/1000 + (new term)*(1000 - Coeff)/1000 ; --basically scale by 1000 ; assume current rectified ADC value in r17, r18 mov r16, r17 ; setup operands for (new term)*(1000-Coeff) mov r17, r18 ; ldi r18, Low(25) ; (1-Coeff)=(1000-975)=25 ldi r19, High(25) ; rcall mpy16u ; result in r18,r19,r20 mov r17, r18 ; move product (r18, r19, r20) into dividend mov r16, r19 ; setup divide operands mov r18, r20 ; ldi r19, Low(1000) ; move 1000 into divisor ldi r20, High(1000) ; rcall div16s ; divide by 1000 (result in r17 (lowest byte),r16 (upper byte)) push r16 ; push answer onto stack for addition later push r17 ldi ZL, Low(runavg) ; load old average from RAM and multiply by Coeff ldi ZH, High(runavg) ld r0,Z+ mov r16, r0 ; setup operands for (old running avg)*(Coeff) ld r0,Z ; mov r17, r0 ; ldi r18, Low(975) ; ldi r19, High(975) ; rcall mpy16u ; multiply (result in r18,r19,r20) mov r17, r18 ; move product (r18, r19, r20) into dividend mov r16, r19 ; setup divide operands mov r18, r20 ; ldi r19, Low(1000) ; move 1000 into divisor ldi r20, High(1000) ; rcall div16s ; divide by 1000 (result in r17 (lowest byte),r16 (upper byte)) pop r18 ; retrieve (new term)*(1000-Coeff)/1000 result pop r19 ; add r17, r18 ; and add to (old avg)*(Coeff)/1000 result adc r16, r19 ; ldi ZL, Low(runavg) ; store new calculated average into memory ldi ZH, High(runavg) st Z+, r17 ; store low byte then the high byte st Z, r16 ;*************TEST CODE begin**************** push r16 push r17 lsr r16 ;divide by 16 for testing ror r17 lsr r16 ror r17 lsr r16 ror r17 lsr r16 ror r17 ; out PORTB, r17 ;output to test DAC pop r17 pop r16 ;*************TEST CODE end**************** push r16 push r17 sub r17, maxavgL ; compare current sample value to maxavg sbc r16, maxavgH brlt no_update pop r17 pop r16 mov maxavgL, r17 mov maxavgH, r16 rjmp no_updatecont no_update: pop r17 pop r16 no_updatecont: ;*************TEST CODE begin**************** push maxavgL push maxavgH lsr maxavgH;divide by 16 for testing ror maxavgL lsr maxavgH ror maxavgL lsr maxavgH ror maxavgL lsr maxavgH ror maxavgL ; out PORTB, maxavgL ;output to test DAC pop maxavgH pop maxavgL ;*************TEST CODE end**************** ;threshold is 0.6*Maxavg (Can change percentage to what works best) push maxavgL push maxavgH push r16 ;divide routine kills r16,r17 so store push r17 mov r16, maxavgL ; setup operands mov r17, maxavgH ; ldi r18, Low(5) ; *3 ldi r19, High(5) ; rcall mpy16u ; result in r18,r19,r20 mov maxavgL,r18 mov maxavgH,r19 mov dd16uL, maxavgL mov dd16uH, maxavgH ldi dv16uL, 8 ;/5 clr dv16uH rcall div16u mov thresL, dres16uL mov thresH, dres16uH ;*************TEST CODE begin**************** push thresL push thresH lsr thresH;divide by 16 for testing ror thresL lsr thresH ror thresL lsr thresH ror thresL lsr thresH ror thresL ; out PORTB, thresL ;output to test DAC pop thresH pop thresL ;*************TEST CODE end**************** ;*************TEST CODE begin**************** push r16 push r17 lsr r16 ;divide by 16 for testing ror r17 lsr r16 ror r17 lsr r16 ror r17 lsr r16 ror r17 ; out PORTB, r17 ;output to test DAC pop r17 pop r16 ;*************TEST CODE end**************** pop r17 ;recall current value from stack pop r16 sub r17, thresL ; compare current sample value to threshold (0.666*maxavg) sbc r16, thresH brlt _nostrobe ;strobe if current value is bigger than threshold clr temp ;strobe out PORTB, temp rjmp _endstrobe _nostrobe: ldi temp, 0xff ;turn off strobe (LEDS) out PORTB, temp _endstrobe: pop maxavgH pop maxavgL ldi ZL, LOW(rpress) ldi ZH, HIGH(rpress) ld r0, Z mov press, r0 tst press ;was a key pressed? breq _clearmaxcont ;if not don't check for STOP cpi butnum, stop ;is STOP pushed? brne _setstopcont clr stopflag ;set STOP flag com stopflag _setstopcont: cpi butnum, clearmax;check to see if CLEARMAX pushed? brne _clearmaxcont clr maxavgH ;clear maxavg regs clr maxavgL clr count30 ;clear pushbutton variables clr maybe ; clr press ; ldi state, 1 ; _clearmaxcont: tst stopflag ;test to see if STOP was pushed (stopflag=0xff) brne jprompt ;goto prompt if STOP ;else keep running beat tracker ldi temp, 0b11000101;start another A to D conversion out ADCSR, temp rjmp swait ; go back to waiting for A/D to finish and repeat jprompt:rjmp RESET ;*******************SUBROUTINE CODE begin*************************** ;******************************************************************* ;Debouncer State machine for dispatching keybd scanner ;call this subroutine every 30 msecs ;uses registers state, press, maybe and butnum (from keybd scanner) ;pressed is the final value of the button press (butnum) states: cpi state, 2 ; check if in State 2 breq state2c cpi state, 3 ; check if in State 3 breq state3c cpi state, 4 ; check if in State 4 breq state4c state1: ldi state, 1 cpi butnum, 0 ; Is the button pressed? brne state2 ; If is pressed, goto state 2 clr press ; If not, still in state 1, press=0 rcall loop ; call keybd ret state2: ldi state, 2 mov maybe, butnum rcall loop ; call keybd ret state2c:cp butnum,maybe ; butnum = maybe? breq state3 ; if yes goto state 3 rcall loop ; else call keybd ldi state, 1 ; and return to state 1 ret state3: ldi state, 3 mov press, butnum ; press = butnum rcall loop ; call keybd ret state3c:cp butnum,press ; button = press? brne state4 ; if not goto state 4 rcall loop ; if yes call keybd ret state4: ldi state, 4 rcall loop ; call keybd ret state4c:cp butnum, press ; button = press? brne state1 ; if not goto state 1 rcall loop ; call keybd ret ;Code for Reading the touchpad (PORTC) ;****************************************************** loop: ldi temp, 0x0f ;set lower four lines to output out DDRC, temp ldi temp, 0xf0 ;and turn on the pullups on the inputs out PORTC, temp nop ;Need some time for the pullups to nop ;charge the port pins nop nop in temp, PINC ;read the high nibble mov key, temp ;and store it (with zeros in the low nibble) ldi temp, 0xf0 ;set upper four lines to outputs out DDRC, temp ldi temp, 0x0f ;and turn on pullups on the inputs out PORTC, temp nop ;As before wait for the pin to charge nop nop nop in temp, PINC ;read the low nibble or key, temp ;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 ldi ZL, low(keytbl*2) ;table pointer in FLASH ldi ZH, high(keytbl*2) ;so convert from word to byte addr ldi butnum, 0 tbllp: lpm ;get the table entry cp key, r0 ;match? breq foundit inc butnum ;if not, have we exhaused the cpi butnum, 0x10 ;table breq illegal adiw ZL, 1 ;if not, get the next table entry rjmp tbllp foundit:subi butnum, -1 ;add one for display com butnum ;invert to drive LEDs ; out PORTB, butnum ;display button number com butnum ;reinvert back to normal ret illegal:ser temp ;no table match means show ;out PORTB, temp ;a zero clr butnum ret ;********** LCD STUFF ************** ;================================================ ; Clear entire LCD and delay for a bit lcdclr: ldi temp,1 ;Clear LCD command rcall lcdcmd ldi timeout,3 ;Delay 3 mS for clear command rcall delay ret ;================================================ ; Initialize LCD module lcdinit:cbi PORTB,0 ;Turn on LED 0 ldi temp,0 ;Setup port pins out PORTD,temp ;Pull all pins low ldi temp,0xff ;All pins are outputs out DDRD,temp ldi timeout,15 ;Wait at least 15 mS at power up rcall delay ; LCD specs call for 3 repetitions as follows: ;first rep ldi temp,3 ;Function set out PORTD,temp ;to 8-bit mode nop ;nop is data setup time sbi PORTD,lcde ;Toggle enable line nop cbi PORTD,lcde ldi timeout,15 ;Wait at least 15 mS rcall delay ;second rep ldi temp,3 ;Function set out PORTD,temp nop sbi PORTD,lcde ;Toggle enable line nop cbi PORTD,lcde ldi timeout,15 ;Wait at least 15 ms rcall delay ;third rep ldi temp,3 ;Function set out PORTD,temp nop sbi PORTD,lcde ;Toggle enable line nop cbi PORTD,lcde ldi timeout,15 ;Wait at least 15 ms rcall delay ;Now change to 4-wire interface mode ldi temp,2 ;Function set, 4 wire databus out PORTD,temp nop sbi PORTD,lcde ;Toggle enable line nop cbi PORTD,lcde ; Finally, at this point, ; the normal 4 wire command routine (lcdcmd) can be used ldi temp,0b00100000 ;Function set, 4 wire, 1 line, 5x7 font rcall lcdcmd ldi temp,0b00001100 ;Display on, no cursor, no blink rcall lcdcmd ldi temp,0b00000110 ;Address increment, no scrolling rcall lcdcmd sbi PORTB,0 ;Turn off LED 0 ret ;============================================ ; Wait for LCD to go unbusy lcdwait:cbi PORTB,1 ;Turn on LED 1 ldi temp,0xF0 ;Make 4 data lines inputs out DDRD,temp sbi PORTD,lcdrw ;Set r/w pin to read cbi PORTD,lcdrs ;Set register select to command waitloop: sbi PORTD,lcde ;Toggle enable line nop cbi PORTD,lcde in lcdstat,PIND ;Read busy flag ;Read, and ignore lower nibble sbi PORTD,lcde ;Toggle enable line nop cbi PORTD,lcde sbrc lcdstat,3 ;Loop until done rjmp waitloop sbi PORTB,1 ;Turn off LED 1 ret ;============================================= ; Send command in temp to LCD lcdcmd: push temp ;Save character rcall lcdwait ldi temp,0xFF ;Make all port D pins outputs out DDRD,temp pop temp ;Get character back push temp ;Save another copy swap temp ;Get upper nibble andi temp,0x0F ;Strip off upper bits out PORTD,temp ;Put on port nop ;wait for data setup time sbi PORTD,lcde ;Toggle enable line nop cbi PORTD,lcde pop temp ;Recall character andi temp,0x0F ;Strip off upper bits out PORTD,temp ;Put on port nop sbi PORTD,lcde ;Toggle enable line nop cbi PORTD,lcde ret ;============================================= ; Send character data in temp to LCD lcdput: push temp ;Save character rcall lcdwait ldi temp,0xFF ;Make all port D pins outputs out DDRD,temp pop temp ;Get character back push temp ;Save another copy swap temp ;Get upper nibble andi temp,0x0F ;Strip off upper bits out PORTD,temp ;Put on port sbi PORTD,lcdrs ;Register select set for data nop sbi PORTD,lcde ;Toggle enable line nop cbi PORTD,lcde pop temp ;Recall character andi temp,0x0F ;Strip off upper bits out PORTD,temp ;Put on port sbi PORTD,lcdrs ;Register select set for data nop sbi PORTD,lcde ;Toggle enable line nop cbi PORTD,lcde ret ;============================================= ; subroutine waits for time equal to value in register timeout ;the register 'timeout' should be loaded before the call delay: tst timeout brne delay ret ;============================================= ;****************************************** ;Subroutine for writing to LCD when string is in FLASH inflsh: nextc: lpm ;r0 ;Get next character from flash tst r0 ;See if at end of message breq end0 ;If so, next message cpi charcnt,8 ;addressing changes at char #8! brne wrtit ;at char 8, fix the addressing ldi temp,0xC0 ;Set address to last 8 chars rcall lcdcmd wrtit: mov temp,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 end0: ldi timeout,255 ;Delay 1/2 sec so human can read it rcall delay ldi timeout,255 ; rcall delay ret inRAM: nextc1: ld r0, Z ;Get next character from RAM tst r0 ;See if at end of message breq end1 ;If so, next message cpi charcnt,8 ;addressing changes at char #8! brne wrtit1 ;at char 8, fix the addressing ldi temp,0xC0 ;Set address to last 8 chars rcall lcdcmd wrtit1: mov temp,r0 ;Send it to the LCD rcall lcdput adiw ZL,1 ;Increment Z-pointer inc charcnt ;keep track of chars on display rjmp nextc1 ;Loop for more end1: ldi timeout,255 ;Delay 1/2 sec so human can read it rcall delay ldi timeout,255 ; rcall delay ret ;*************************ISR's********************************************** ;***** Timer 0 overflow interrupt handler ;timer 0 ISR (timer-zero overflow) ;Enters every 1.0 mSec t0int: in save, SREG out TCNT0, Reload ; keeps clock ticking at 1 mSec push r18 ; push r19 push r20 push r21 push ZL push ZH ; push r16 ldi ZL, LOW(rmaybe) ldi ZH, HIGH(rmaybe) ld r0, Z mov maybe, r0 ldi ZL, LOW(rstate) ldi ZH, HIGH(rstate) ld r0, Z mov state, r0 ldi ZL, LOW(rpress) ldi ZH, HIGH(rpress) ld r0, Z mov press, r0 ldi ZL, LOW(rtimeo) ldi ZH, HIGH(rtimeo) ld r0, Z mov timeout, r0 dec timeout ;subtract another mSec ldi ZL, LOW(rtimeo) ldi ZH, HIGH(rtimeo) st Z, timeout cpi count30,30 ;check for 30msec counter brne _t0cont ;if haven't reached 30 yet, increment counter clr count30 ;if reached 30msec clear counter rcall states ;and call debouncer state machine subroutine rjmp _t0end _t0cont:inc count30 _t0end: out SREG, save ldi ZL, LOW(rmaybe) ldi ZH, HIGH(rmaybe) st Z, maybe ldi ZL, LOW(rstate) ldi ZH, HIGH(rstate) st Z, state ldi ZL, LOW(rpress) ldi ZH, HIGH(rpress) st Z, press ; pop r16 pop ZH pop ZL pop r21 pop r20 ; pop r19 pop r18 reti ;back to backgound tasks .include "math.asm" ;**************************************** ;define fixed strings to be transmitted from flash- zero terminated text1: .db "Press A to start", 0x00 text2: .db "Beat Track Mode", 0x00