;************************************************************************** ; FILE: theremin.asm * ; CONTENTS: Junior Theremin * ; COPYRIGHT: MadLab Ltd. 2001-2003 * ; AUTHOR: James Hutchby * ; UPDATED: 10/11/03 * ;************************************************************************** list p=12C508A ; list p=12C509A ifdef __12C508A include "p12c508a.inc" endif ifdef __12C509A include "p12c509a.inc" endif ; __config _IntRC_OSC & _WDT_OFF & _MCLRE_OFF & _CP_OFF __config _IntRC_OSC & _WDT_OFF & _MCLRE_OFF & _CP_ON __idlocs h'CD10' errorlevel -302,-305 ;************************************************************************** ; * ; Specification * ; * ;************************************************************************** ; power-up self-test - all LEDs flash twice ; double beep then self-calibrates ; re-calibrates every 5s or less ; tone frequency = ~500Hz - ~4kHz ; button1 decreases range by an octave ; button2 increases range by an octave ; both buttons toggles slide/discrete mode ; sleeps after 5 minutes of inactivity, either button to wake ;************************************************************************** ; * ; Port assignments * ; * ;************************************************************************** GPIO_IN equ b'011111' ; GPIO IN status GPIO_OUT equ b'011100' ; GPIO OUT status SPEAKER_PORT equ GPIO ; speaker port SPEAKER1 equ 4 ; speaker output1 SPEAKER2 equ 5 ; speaker output2 SPEAKER_MASK equ b'110000' ; speaker mask SPEAKER_ON equ GPIO_OUT&~(1<<SPEAKER1) SPEAKER_OFF equ GPIO_OUT|(1<<SPEAKER1) BUTTON_PORT equ GPIO ; button port BUTTON1 equ 1 ; button1 BUTTON2 equ 0 ; button2 BUTTON_MASK equ b'000011' ; button mask LED_PORT equ GPIO ; LED port LED1 equ 1 ; LED #4 LED2 equ 4+1 ; LED #3 LED3 equ 0 ; LED #2 LED4 equ 4+0 ; LED #1 MUX equ 5 ; LED multiplexer LED_MASK equ b'000011' ; LED mask ;************************************************************************** ; * ; Constants and timings * ; * ;************************************************************************** CLOCK equ d'4000000' ; processor clock frequency in Hz SAMPLE1 equ d'10' ; slide sample period in ms SAMPLE2 equ d'125' ; discrete sample period in ms SLIDE_THRESHOLD equ d'6' SLIDE_UPPER equ d'250' SLIDE_LOWER equ d'50' RECALIBRATE equ d'5000' ; recalibration time in ms BEEP_PITCH equ d'75' ; beep pitch BEEP_PERIOD equ d'250' ; beep period TIMEOUT equ d'300'*d'100' ; sleep timeout period in 1/100s ;************************************************************************** ; * ; File register usage * ; * ;************************************************************************** RAM equ h'07' cblock RAM LEDS ; multiplexed LEDs buttons ; buttons pressed mode ; mode (0 = slide, 1 = discrete) ndx ; index note ; current note, -1 if silent last ; last note, -1 if silent period ; note period in cycles/16 sample:2 ; sample period in cycles/16 pulses:2 ; pulse counter base:2 ; baseline pulse count highest:2 ; highest pulse count toggle ; toggle time recal ; recalibration timer timer:2 ; sleep timer count ; scratch counter work1, work2 ; work registers endc ;************************************************************************** ; * ; Macros * ; * ;************************************************************************** routine macro label ; routine label endm table macro label ; define lookup table label addwf PCL endm entry macro value ; define table entry retlw value endm index macro label ; index lookup table call label endm jump macro label ; jump through table goto label endm tstw macro ; test w register iorlw 0 endm movff macro f1,f2 ; move file to file movfw f1 movwf f2 endm movlf macro n,f ; move literal to file movlw n movwf f endm ;-------------------------------------------------------------------------- ; reset vector ;-------------------------------------------------------------------------- org 0 movwf OSCCAL goto main_entry ;************************************************************************** ; * ; Lookup tables * ; * ;************************************************************************** table pulse_table entry d'50' entry d'55' entry d'60' entry d'65' entry d'70' entry d'75' entry d'80' entry d'85' entry 0 table period_table C1_FREQ equ d'523' ; ~523.2 Hz D1_FREQ equ d'587' ; ~587.3 Hz E1_FREQ equ d'659' ; ~659.2 Hz F1_FREQ equ d'698' ; ~698.4 Hz G1_FREQ equ d'784' ; ~783.9 Hz A2_FREQ equ d'880' ; ~879.9 Hz B2_FREQ equ d'988' ; ~987.7 Hz C2_FREQ equ d'1046' ; ~1046.4 Hz D2_FREQ equ d'1175' ; ~1174.5 Hz E2_FREQ equ d'1318' ; ~1318.4 Hz F2_FREQ equ d'1397' ; ~1396.8 Hz G2_FREQ equ d'1568' ; ~1567.8 Hz A3_FREQ equ d'1760' ; ~1759.8 Hz B3_FREQ equ d'1975' ; ~1975.3 Hz C3_FREQ equ d'2093' ; ~2092.8 Hz D3_FREQ equ d'2349' ; ~2349.1 Hz E3_FREQ equ d'2637' ; ~2636.8 Hz F3_FREQ equ d'2794' ; ~2793.6 Hz G3_FREQ equ d'3136' ; ~3135.7 Hz A4_FREQ equ d'3520' ; ~3519.7 Hz B4_FREQ equ d'3951' ; ~3950.7 Hz C4_FREQ equ d'4186' ; ~4185.6 Hz note_ macro freq entry (CLOCK/(freq*2))/d'16' endm entry d'250' note_ C1_FREQ note_ D1_FREQ note_ E1_FREQ note_ F1_FREQ note_ G1_FREQ note_ A2_FREQ note_ B2_FREQ note_ C2_FREQ note_ D2_FREQ note_ E2_FREQ note_ F2_FREQ note_ G2_FREQ note_ A3_FREQ note_ B3_FREQ note_ C3_FREQ note_ D3_FREQ note_ E3_FREQ note_ F3_FREQ note_ G3_FREQ note_ A4_FREQ note_ B4_FREQ note_ C4_FREQ table patterns_table pattern_ macro leds,repeat variable i = repeat while i > 0 entry leds i set i-1 endw endm pattern_ b'0000',1 pattern_ b'0001',1 pattern_ b'0010',1 pattern_ b'0100',1 pattern_ b'1000',1 pattern_ b'1001',1 pattern_ b'1010',1 pattern_ b'1100',2 pattern_ b'1101',2 pattern_ b'1110',2 pattern_ b'1111',3 ;************************************************************************** ; * ; Procedures * ; * ;************************************************************************** ;-------------------------------------------------------------------------- ; polls the pushbuttons, returns NZ flag set if either pushbutton pressed ;-------------------------------------------------------------------------- routine poll movff GPIO,work1 movlw GPIO_IN ; input mode tris GPIO bcf LED_PORT,MUX iorwf GPIO ; poll the buttons clrwdt comf GPIO,w movwf work2 movff work1,GPIO ; re-initialise port incf note,w movlw GPIO_OUT skpz andlw ~(1<<SPEAKER1) tris GPIO movfw work2 andlw BUTTON_MASK movwf buttons retlw 0 ;-------------------------------------------------------------------------- ; multiplexes the LEDs ;-------------------------------------------------------------------------- routine get_mux movwf LEDS do_bit macro bit,led btfsc LEDS,bit if led < 4 iorlw 1<<led else andlw ~(1<<led) endif endm movlw LED_MASK<<4 ; determine port I/O data do_bit 0,LED1 do_bit 1,LED2 do_bit 2,LED3 do_bit 3,LED4 movwf LEDS retlw 0 ;-------------------------------------------------------------------------- ; toggles the speaker and sets the LEDs ;-------------------------------------------------------------------------- do_speaker macro ; [28] movfw LEDS ; set LEDs [4] btfss LED_PORT,MUX ; [4/8] swapf LEDS,w ; [4] xorwf LED_PORT,w ; [4] andlw LED_MASK ; [4] iorlw SPEAKER_MASK ; toggle speaker output [4] xorwf SPEAKER_PORT ; [4] endm ;-------------------------------------------------------------------------- ; waits, fed with the wait in 1/100s in the w reg ;-------------------------------------------------------------------------- routine wait movwf count movlw SPEAKER_OFF ; speaker off tris GPIO bsf SPEAKER_PORT,SPEAKER1 bcf SPEAKER_PORT,SPEAKER2 wait1 movlf CLOCK/(d'100'*d'16'*d'256'),work1 wait2 do_speaker clrf work2 wait3 clrwdt ; [4] decfsz work2 ; [4] goto wait3 ; [8] decfsz work1 goto wait2 decfsz count goto wait1 clrf GPIO retlw 0 ;-------------------------------------------------------------------------- ; beeps ;-------------------------------------------------------------------------- routine beep movlw SPEAKER_ON ; speaker on tris GPIO bsf SPEAKER_PORT,SPEAKER1 bcf SPEAKER_PORT,SPEAKER2 movlf BEEP_PERIOD,work1 beep1 do_speaker ; toggle speaker output movlf BEEP_PITCH,work2 ; half-cycle delay beep2 clrwdt decfsz work2 goto beep2 decfsz work1 goto beep1 movlw SPEAKER_OFF ; speaker off tris GPIO clrf GPIO retlw 0 ;-------------------------------------------------------------------------- ; counts pulses while playing a note ;-------------------------------------------------------------------------- do_timing macro f ; timing loop [16 * f] local dot1 dot1 clrwdt ; [4] decfsz f ; [4/8] goto dot1 ; [8] nop ; [4] endm do_count macro ; [32] movfw TMR0 ; low byte of pulse counter [4] xorwf pulses+1,w ; [4] xorwf pulses+1 ; pulses+1 <= TMR0 [4] xorlw h'80' ; determine if TMR0 has rolled over [4] iorwf pulses+1,w ; [4] andlw h'80' ; [4] skpnz ; [8/4] incf pulses+0 ; increment high byte if yes [4] endm wait_speaker macro local spk1 clrwdt ; wait for toggle time spk1 movfw TMR0 subwf toggle,w andlw ~1 bnz spk1 do_speaker movfw period ; next toggle time addwf toggle endm routine count_pulses incf note,w ; get note period skpz btfsc mode,0 index period_table movwf period PERIOD1 set (SAMPLE1*CLOCK)/(d'1000'*d'16') PERIOD2 set (SAMPLE2*CLOCK)/(d'1000'*d'16') movlw high PERIOD1 ; initialise sample period btfsc mode,0 movlw high PERIOD2 movwf sample+0 movlw low PERIOD1 btfsc mode,0 movlw low PERIOD2 movwf sample+1 movfw period ; initial subtraction subwf sample+1 skpc decf sample+0 clrf pulses+0 ; clear pulse counter clrf pulses+1 incf last,w ; note playing ? bz count2 ; branch if not wait_speaker movlw d'5' ; adjust toggle time subwf toggle clrwdt ; wait for toggle time count1 movfw TMR0 subwf toggle,w andlw ~1 bnz count1 count2 incf last,w ; speaker change of state ? [4] bz count3 ; [8] incf note,w ; [4] bnz count4 ; [12] count3 movfw last andwf note,w xorlw h'ff' bz count4 ; branch if not incf note,w ; speaker on or off movlw SPEAKER_ON skpnz movlw SPEAKER_OFF tris GPIO bsf SPEAKER_PORT,SPEAKER1 bcf SPEAKER_PORT,SPEAKER2 count4 movff note,last ; [8] movlw b'00101111' ; count low-to-high transitions on RTCC pin [4] clrwdt ; no prescaling, weak pull-ups enabled [4] clrf TMR0 ; wake on pin change [4] option ; [4] clrwdt ; [4] nop ; [4] clrf TMR0 ; initialise TMR0 [4] nop ; 2 instruction cycle delay [4] nop ; after writing to TMR0 [4] ; -- start of pulse counting -- CYCLES1 equ d'112' count5 do_speaker ; toggle speaker output [28] movlw CYCLES1/d'16' ; initialise timer [4] subwf period,w ; [4] movwf work1 ; [4] do_timing work1 ; timing loop [16 * work1] do_count ; get pulses [32] nop ; [4] nop ; [4] nop ; [4] movfw period ; decrement sample period [4] subwf sample+1 ; [4] skpc ; [8/4] decf sample+0 ; [4] btfss sample+0,7 ; finished ? [8/4] goto count5 ; loop if not [8] nop ; [4] ; -- last iteration -- CYCLES2 equ d'112' do_speaker ; toggle speaker output [28] movfw period ; remainder [4] addwf sample+1 ; [4] incf sample+1,w ; initialise timer [4] movwf work1 ; ensure not zero [4] do_timing work1 ; timing loop [16 * work1] ; -- end of pulse counting -- do_count ; get final pulses [32] nop ; [4] movfw sample+1 ; [4] subwf period,w ; [4] movwf work1 ; [4] movlw CYCLES2/d'16' ; [4] subwf work1 ; [4] skpz ; [4] skpc ; [8] goto count6 do_timing work1 ; timing loop [16 * work1] count6 do_speaker ; toggle speaker output [28] incf note,w ; note playing ? [4] bz count7 ; exit if not [8] clrwdt ; count instructions, prescale RTCC by 4 [4] movlw b'00000001' ; weak pull-ups enabled, wake on pin change [4] option ; [4] nop ; [4] nop ; [4] clrf TMR0 ; initialise TMR0 [4] movff period,toggle ; toggle time movlw d'4' subwf toggle count7 retlw 0 ;-------------------------------------------------------------------------- ; power down ;-------------------------------------------------------------------------- routine power_down movlw b'111111' ; read port tris GPIO movwf GPIO nop movfw GPIO sleep ; standby mode ;-------------------------------------------------------------------------- ; main entry point ;-------------------------------------------------------------------------- routine main_entry clrf GPIO ; initialise port movlw GPIO_OUT tris GPIO clrwdt movlw b'00000000' ; weak pull-ups enabled, wake on pin change option movlw b'1111' ; flash LEDs twice call get_mux movlw d'25' call wait movlw b'0000' call get_mux movlw d'25' call wait movlw b'1111' call get_mux movlw d'25' call wait clrf LED_PORT clrf mode ; slide mode decf mode ;-------------------------------------------------------------------------- ; next mode ;-------------------------------------------------------------------------- routine next_mode movlw -1 movwf last movwf note movlw b'0000' call get_mux call beep ; double beep movlw d'10' call wait call beep next1 call poll ; wait for buttons to be released bnz next1 incf mode ; next mode bcf mode,1 call count_pulses ; baseline pulse count movff pulses+0,base+0 movff pulses+1,base+1 clrf highest+0 clrf highest+1 movlw h'ff' ; initialise recalibration timer btfsc mode,0 movlw RECALIBRATE/SAMPLE2 movwf recal ;-------------------------------------------------------------------------- ; main loop ;-------------------------------------------------------------------------- routine main_loop movlf high TIMEOUT,timer+0 ; initialise sleep timer movlf low TIMEOUT,timer+1 loop0 clrwdt call count_pulses ; count pulses movfw pulses+0 ; store highest pulse count subwf highest+0,w movwf work1 movfw pulses+1 subwf highest+1,w skpc decf work1 btfss work1,7 goto loop1 movff pulses+0,highest+0 movff pulses+1,highest+1 loop1 decfsz recal ; re-calibrate ? goto loop2 ; branch if not movff highest+0,base+0 ; new baseline movff highest+1,base+1 clrf highest+0 clrf highest+1 movlw h'ff' ; recharge recalibration timer btfsc mode,0 movlw RECALIBRATE/SAMPLE2 movwf recal loop2 movfw pulses+0 ; determine pulse count delta subwf base+0,w movwf pulses+0 movfw pulses+1 subwf base+1,w movwf pulses+1 skpc decf pulses+0 btfss pulses+0,7 ; negative delta ? goto loop3 ; branch if not movfw pulses+0 ; adjust baseline subwf base+0 movfw pulses+1 subwf base+1 skpc decf base+0 clrf pulses+0 clrf pulses+1 loop3 movlw b'0000' call get_mux btfsc mode,0 ; slide mode ? goto loop4 ; branch if not movlf -1,note movlw SLIDE_THRESHOLD ; threshold reached ? subwf pulses+1 skpc decf pulses+0 btfsc pulses+0,7 goto loop7 ; branch if not clrc rlf pulses+1 rlf pulses+0 tstf pulses+0 ; limit to single byte movlw h'ff' skpz movwf pulses+1 swapf pulses+1,w ; more LEDS on as frequency movwf work1 ; increases rlf work1,w rlf work1 movfw work1 btfsc work1,4 movlw h'0f' andlw h'0f' index patterns_table call get_mux ; multiplex LEDs movlf SLIDE_UPPER-SLIDE_LOWER,note movfw pulses+1 subwf note skpc clrf note movlw SLIDE_LOWER addwf note goto loop7 loop4 clrf ndx ; determine the note clrf work1 loop5 movfw ndx index pulse_table tstw bz loop6 subwf pulses+1 skpc decf pulses+0 btfsc pulses+0,7 goto loop6 incf ndx clrc tstf work1 skpnz setc rlf work1 incf last,w ; note playing ? bz loop5 ; branch if not wait_speaker goto loop5 loop6 swapf work1,w iorwf work1,w call get_mux ; multiplex LEDs decf ndx,w ; note movwf note incf note,w ; middle octave if not silent movlw d'7' skpz addwf note loop7 call poll ; both pushbuttons pressed ? movfw buttons xorlw BUTTON_MASK bz next_mode ; branch if yes btfss mode,0 ; discrete mode ? goto loop8 ; branch if not incf note,w ; silent ? bz loop8 ; branch if yes movlw d'7' ; octave - btfsc buttons,BUTTON1 subwf note movlw d'7' ; octave + btfsc buttons,BUTTON2 addwf note loop8 incf note,w ; silent ? bnz main_loop ; branch if not movlw SAMPLE1/d'10' btfsc mode,0 movlw SAMPLE2/d'10' subwf timer+1 skpc decf timer+0 btfsc timer+0,7 ; sleep if timeout goto power_down goto loop0 ifdef __12C508A ; org h'1ff' ; *** comment for OTP part *** ; goto main_entry endif ifdef __12C509A ; org h'3ff' ; *** comment for OTP part *** ; goto main_entry endif end