; dtmf.asm: PWM DTMF dialer ; ; Copyright 1992, 1995 Eric Smith ; ; dtmf.asm is free software; you can redistribute it and/or modify it under the ; terms of the GNU General Public License version 2 as published by the Free ; Software Foundation. Note that I am not granting permission to redistribute ; or modify dtmf.asm under the terms of any later version of the General Public ; License. ; ; This program is distributed in the hope that it will be useful (or at least ; amusing), but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General ; Public License for more details. ; ; You should have received a copy of the GNU General Public License along with ; this program (in the file "COPYING"); if not, write to the Free Software ; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ; ; $Header: /usr/home/kolwynia/eric/pic/dtmf/RCS/dtmf.asm,v 1.16 1995/06/06 18:07:48 eric Exp eric $ device PIC16C54,HS_OSC,WDT_OFF,PROTECT_OFF f equ 1 ; for destination argument w equ 0 ; for destination argument ind equ 0 ; used for indirect through FSR rtcc equ 1 ; real time clock/counter pc equ 2 ; program counter fsr equ 4 ; file select register (index register) status equ 3 ; status register: cf equ 0 ; bit 0 = carry flag dcf equ 1 ; bit 1 = digit carry flag zf equ 2 ; bit 2 = zero flag pdf equ 3 ; bit 3 = power down flag tof equ 4 ; bit 4 = time out flag ; bits 5,6,7 do not apply to PIC16C54 nothing equ 0 ; used to return nothing from subroutines porta equ 5 ;I/O port RA: pabit0 equ 0 ; bit 0 = unused led equ 1 ; bit 1 = low turns on LED pwm2 equ 2 ; bit 2 = PWM speaker output 1 pwm3 equ 3 ; bit 3 = PWM speaker output 2 portb equ 6 ;I/O port RB: colm0 equ 0 ; bit 0 = keypad column 0 colm1 equ 1 ; bit 1 = keypad column 1 colm2 equ 2 ; bit 2 = keypad column 2 colm3 equ 3 ; bit 3 = keypad column 3 row0 equ 4 ; bit 4 = keypad row 0 row1 equ 5 ; bit 5 = keypad row 1 row2 equ 6 ; bit 6 = keypad row 2 row3 equ 7 ; bit 7 = keypad row 3 ; ; Where it's at... ; ram equ 08h ; start of usable RAM body equ 000h ; start of program memory freep0 set 0 ; free words of page zero freep1 set 0 ; free words of page one org ram ; start of RAM one: ds 1 ; holds the value 1 for incrementing W zero: ds 1 ; holds the value 0 for two's-complementing W c4f: ds 1 ; holds the value 04fh for SIN scaling row: ds 2 ; row sine phase (fixed point) colm ds 2 ; column sine phase (fixed point) rowfrq: ds 2 ; row increment (fixed point) colmfrq: ds 2 ; column increment (fixed point) iter: ds 2 ; iteration count for DTMF burst in sample ; periods newsamp: ds 1 ; sample value being calculated oldsamp: ds 1 ; last sample (currently being output) digidx: ds 1 ; index into phone number digit: ds 1 ; current digit temp: ds 1 kcol: ds 1 ; key scan column krow: ds 1 ; key scan row freeram equ 020h-. org body ; start of ROM if .-000h error "loop0 is not at 000h" endif loop0: call incrow ; 25 call waste6 ; 6 call inccol ; 15 call calccol ; 18 call pulse ; 28 ; LOOP is the entry point to the DTMF burst generator. LOOP1 falls into it. ; LOOP can't be called as a subroutine (main line only), ; outputs one DTMF burst. The two's complement of the duration of the ; burst in sample intervals is stored in ITER. When the loop completes, ; it jumps to NXTDIG (from INCPHAS, see the comments there). loop0a: nop ; 1 loop: bsf porta,pwm3 ; 1 bsf porta,pwm2 ; 1 movf newsamp,w ; 1 movwf oldsamp ; 1 andlw 070h ; 1 movwf pc ; 2 incrow: goto incrowx inccol: goto inccolx flash: goto flashx pause: goto pausex ds 0 freep0 set freep0+0 if .-010h error "loop1 is not at 010h" endif loop1: call incrow ; 25 call waste8 ; 8 call inccol ; 15 call pulse ; 28 loop1b: nop ; 1 loop1a: movf colm+1,w ; 1 btfsc colm+1,6 ; 1 is it the 2nd half of a half cycle? subwf zero,w ; 1 then complement to effect a subtract from 64 andlw 07fh ; 1 reduce to 6 bits for 90 degrees call sinetbl ; 6 and get amplitude btfsc colm+1,7 ; 1 is amplitude supposed to be negative? subwf c4f,w ; 1 change polarity of sine amplitude addwf newsamp ; 1 rrf newsamp ; 1 goto loop ; 9 ds 1 freep0 set freep0+1 if .-020h error "loop2 is not at 020h" endif loop2: call incrow ; 25 call waste7 ; 7 call pulse ; 28 call inccol ; 15 goto loop1a ; 25 waste8: nop ; 1 waste7: nop ; 1 waste6: nop ; 1 waste5: nop ; 1 waste4: retlw nothing ; 4 (includes call) ds 6 freep0 set freep0+6 if .-030h error "loop3 is not at 030h" endif loop3: call inccol ; 15 nop ; 1 call pulse ; 28 call incrow ; 25 call waste6 ; 6 goto loop1a ; 25 ds 10 freep0 set freep0+10 if .-040h error "loop4 is not at 040h" endif loop4: call pulse ; 28 call incrow ; 25 call waste6 ; 6 call inccol ; 15 goto loop1b ; 26 ; ; DTMF keypad frequency matrix: ; ; : : : : : ; : 1209 : 1336 : 1477 : 1633 : ; ........!_______!_______!_______!_______! ; ! ! ! ! ! ; 697 ! 1 ! 2 ! 3 ! A ! ; ........!_______!_______!_______!_______! ; ! ! ! ! ! ; 770 ! 4 ! 5 ! 6 ! B ! ; ........!_______!_______!_______!_______! ; ! ! ! ! ! ; 852 ! 7 ! 8 ! 9 ! C ! ; ........!_______!_______!_______!_______! ; ! ! ! ! ! ; 941 ! * ! 0 ! # ! D ! ; ........!_______!_______!_______!_______! ; These equates are used to make the row and column tables easier to read. ; ; These equates define the phase increments necessary to generate sine ; waves of the appropriate frequencies for DTMF tones. The values are ; computed with the formula 256 * Tsamp * Freq. The 256 accounts ; for the effective domain of the sine function (256 steps = 2 * PI ; radians). Sure would be nice to have an assembler with floating point! frq697 equ 2284 ; @int(65536.0*50e-6*697.0) frq770 equ 2523 ; @int(65536.0*50e-6*770.0) frq852 equ 2792 ; @int(65536.0*50e-6*852.0) frq941 equ 3084 ; @int(65536.0*50e-6*941.0) frq1209 equ 3962 ; @int(65536.0*50e-6*1209.0) frq1336 equ 4378 ; @int(65536.0*50e-6*1336.0) frq1477 equ 4840 ; @int(65536.0*50e-6*1477.0) frq1633 equ 5351 ; @int(65536.0*50e-6*1633.0) ; Tables to set the row and column frequencies for a key. ; The values in these tables are increment sizes for stepping through the ; sine table. rowtabl: addwf pc retw frq697&0ffh,frq770&0ffh,frq852&0ffh,frq941&0ffh rowtabh: addwf pc retw frq697>>8,frq770>>8,frq852>>8,frq941>>8 coltabl: addwf pc retw frq1209&0ffh,frq1336&0ffh,frq1477&0ffh,frq1633&0ffh coltabh: addwf pc retw frq1209>>8,frq1336>>8,frq1477>>8,frq1633>>8 ; Output a pulse of 0 to 15 cycles. ; Enter with the pulse width (in cycles) in W and with the PWM pin ; set to the high state. ; ; 28 cycles (including call) ; ; W=0 ends pulse on 22th cycle after beginning of call ; W=15 ends pulse on 7th cycle after beginning of call pulse: movf oldsamp,w ; 1 andlw 00fh ; 1 addwf pc ; 2 calculate first half of delay nop ; delay... nop nop nop nop nop nop nop nop nop nop nop nop nop nop bcf porta,pwm2 ; 1 set PWM output to negative side bcf porta,pwm3 ; 1 xorlw 0fh ; 1 compute remaining delay addwf pc ; 2 calculate second half of delay nop ; delay... nop nop nop nop nop nop nop nop nop nop nop nop nop nop retlw nothing ; 2 ; This is a sine wave for 360 degrees per 256 table entries, scaled by ; 127, such that 1.0 = $7F hex. ; ; Only 65 locations (0 to 90 degrees in steps of 2pi/256) are used in order ; to save space, as a full sine table would take up all of zero page. ; ; The values are signed fractions, i.e., s.fffffff ; All values in the table are positive since it is for the first ; quadrant. ; 6 cycles (including call) ;sinetbl: addwf pc ; retw 000h,003h,006h,009h,00ch,010h,013h,016h ; retw 019h,01ch,01fh,022h,025h,028h,02bh,02eh ; retw 031h,033h,036h,039h,03ch,03fh,041h,044h ; retw 047h,049h,04ch,04eh,051h,053h,055h,058h ; retw 05ah,05ch,05eh,060h,062h,064h,066h,068h ; retw 06ah,06bh,06dh,06fh,070h,071h,073h,074h ; retw 075h,076h,078h,079h,07ah,07ah,07bh,07ch ; retw 07dh,07dh,07eh,07eh,07eh,07fh,07fh,07fh ; retw 07fh sinetbl: addwf pc retw 40,41,42,43,44,45,46,47 retw 48,49,49,50,51,52,53,54 retw 55,56,57,58,58,59,60,61 retw 62,62,63,64,65,65,66,67 retw 68,68,69,70,70,71,71,72 retw 72,73,73,74,74,75,75,76 retw 76,76,77,77,77,78,78,78 retw 78,78,79,79,79,79,79,79 retw 79 if 0 ; This is how we would do it if we had floating point and repeat: pi equ 3.141592653 sinetbl: addwf pc angle set 0 rept 65 retlw _fix(127.0*_sin(_float(angle)*pi/128.0)+0.5) angle set angle+1 endm endif ; GETDIG is the actual phone number we want to dial. ; ; If I'd get macros working we could have a nice macro to define a ; phone number. ; ; mapping: 0 1 2 3 4 5 6 7 8 9 * # ; d 0 1 2 4 5 6 8 9 a c e ; ; f indicates the end of the number ; ; Currently the code assumes that phone numbers are four bytes each, which ; is long enough for seven digit local numbers. getdig: addwf pc retw 04ah,0a8h,000h,00fh ; 499-7111 (WWV) retw 055h,050h,010h,01fh ; 555-1212 (information) retw 012h,045h,068h,09fh ; 234-5678 (test) retw 012h,045h,068h,09fh ; 234-5678 (test) retw 012h,045h,068h,09fh ; 234-5678 (test) retw 012h,045h,068h,09fh ; 234-5678 (test) ;maxdig equ 2*((.-getdig)-1) maxdig equ 7 ; CALCCOL is the longest subroutine that doesn't have to entirely reside in ; page zero, so we position it to begin at 0ffh in order to free as much of ; page zero as possible. freep0 set freep0+(0ffh-.) ds 0ffh-. org 0ffh ; CALCCOL computes the column tone sample, and adds in the previously ; computed row tone sample. ; 18 cycles (including call) calccol: movf colm+1,w ; 1 btfsc colm+1,6 ; 1 is it the 2nd half of a half cycle? subwf zero,w ; 1 then complement to effect a subtract from 64 andlw 07fh ; 1 reduce to 6 bits for 90 degrees call sinetbl ; 6 and get amplitude btfsc colm+1,7 ; 1 is amplitude supposed to be negative? subwf c4f,w ; 1 change polarity of sine amplitude addwf newsamp ; 1 rrf newsamp ; 1 retlw nothing ; 2 ; INCCOL updates the sine wave phase for the column tone, and tests ; for completion of the tone. ; ; There is a great ugliness in the way this subroutine deals with ; the tone completion. INCCOL is called from the main line code ; in one of five places (LOOP0, LOOP1, LOOP2, LOOP3, or LOOP4). Since we ; know we are called by main, if the tone is over we just jump to ; NXTDIG, disregarding the return address on the stack (and leaving ; it there). This saves some cycles in the main line tone loop, to ; maximize the available range of PWM duty cycles. Kudos to Howard! ; 15 cycles (including call to extender) inccolx: movf colmfrq,w ; 1 add low byte of colmfrq to low byte of colm addwf colm ; 1 movf colmfrq+1,w ; 1 add high byte of colmfrq to high byte of colm btfsc status,cf ; 1 addwf one,w ; 1 alternate: incf colm+1 addwf colm+1 ; 1 incf iter ; 1 increment iteration count btfsc status,zf ; 1 incfsz iter+1 ; 1 retlw nothing ; 2 bcf porta,pwm2 ; turn off pwm outputs bsf porta,pwm3 goto nxtdig ; ; INCROW is responsible for updating the sine wave phase for ; the row tone, and calculating the sample. ; 25 cycles (including call to extender) ; We could add up to five additional cycles to INCROW and adjust the delays ; in LOOP[0-4]. This could be used to look for key press/release, etc. incrowx: movf rowfrq,w ; 1 add low byte of rowfrq to low byte of row addwf row ; 1 movf rowfrq+1,w ; 1 add high byte of rowfrq to high byte of row btfsc status,cf ; 1 addwf one,w ; 1 alternate: incf row+1 addwf row+1 ; 1 movf row+1,w ; 1 btfsc row+1,6 ; 1 is it the 2nd half of a half cycle? subwf zero,w ; 1 then complement to effect a subtract from 64 andlw 07fh ; 1 reduce to 6 bits for 90 degrees call sinetbl ; 6 and get amplitude btfsc row+1,7 ; 1 is amplitude supposed to be negative? subwf c4f,w ; 1 change polarity of sine amplitude movwf newsamp ; 1 retlw nothing ; 2 ;======================== PROGRAM ENTRY POINT ========================= ; Initialize the hardware reset: ; clrwdt ; reset watchdog timer ; movlw 3fh ; external edge to timer ; option ; high to low edge for timer ; prescaler assigned to watchdog ; prescaler divide by 128 for now movlw 1 ; initialize the value of one to allow W to be movwf one ; incremented by using "addwf one,w" movlw 0 ; initialize the value of zero so we can movwf zero ; calculate the two's complement of W using ; "subwf zero,w" movlw 04fh movwf c4f bcf porta,pwm2 ; turn off PWM output bsf porta,pwm3 bsf porta,led ; turn off LED movlw 0f0h ;porta: tris porta ; bit 0 = output (unused) ; bit 1 = output (LED) ; bit 2 = output (PWM speaker) ; bit 3 = output (sync pulse for debug***) ; bits 4-7 = inputs (no pins on 16C54) movlw 0ffh ;drive port b output pins high movwf portb movlw 30h ; portb: tris portb ; bit 0 = output (column 0) not used ; bit 1 = output (column 1) ; bit 2 = output (column 2) ; bit 3 = output (column 3) ; bit 4 = input (row 0) ; bit 5 = input (row 1) ; bit 6 = output (row 2) not used ; bit 7 = output (row 3) not used ;======================== MAIN LOOP ========================= main: movlw 0 call flash if 0 ; should be ifdef test clrf colmfrq clrf colmfrq+1 movlw 07h nxtdig: incf digit movf digit,w andlw 07h movwf digit btfsc digit,2 goto nxtd1 call rowtabl movwf rowfrq movf digit,w call rowtabh movwf rowfrq+1 goto nxtd2 nxtd1: andlw 03h call coltabl movwf rowfrq movf digit,w andlw 03h call coltabh movwf rowfrq+1 dur1: equ 65000 ; duration 3.25 S nxtd2: movlw (-dur1)&0ffh movwf iter movlw ((-dur1)>>8)&0ffh movwf iter+1 goto loop else keyscan: movlw 010h movwf krow ks1 movlw 002h movwf kcol ks2 movf kcol,w xorlw 0ffh movwf portb movf krow,w andwf portb btfsc status,zf goto ks9 bcf status,cf rlf kcol,f btfss kcol,4 goto ks2 bcf status,cf rlf krow,f btfss krow,6 goto ks1 goto keyscan ; at this point, row is 10h or 20h, and column is 2, 4, or 8 ks9: movlw 10h xorwf krow,f ; row is now 00h or 30h rlf kcol,f ; col is now 04h, 08h, or 10h rlf kcol,f ; 08h, 10h, or 20h movlw 30h andwf kcol,w ; col (in w) is 00h, 10h, or 20h addwf krow,w ; now 00h, 10h, 20h, 30h, 40h, or 50h movwf digidx decf digidx,f nxtdig: clrf rowfrq ; assume we're going to generate a delay clrf rowfrq+1 clrf colmfrq clrf colmfrq+1 ; clrf colm ; set initial row and column phase ; clrf row ; clrf colm+1 ; clrf row+1 incf digidx ; goto nxtd1 ; goto main nxtd1: bcf status,cf rrf digidx,w ; shift it right. if it's odd, pause btfsc status,cf goto nxtd3 movwf temp bcf status,cf rrf temp,w call getdig ; get a pair of digits movwf digit btfss digidx,1 ; get the particular digit swapf digit movf digit,w ; if it's an f, we're done andlw 0fh xorlw 0fh btfsc status,zf goto main ; movf digit,w ; andlw 00fh ; call flash movf digit,w andlw 3 ; get the column frequency call coltabl movwf colmfrq movf digit,w andlw 3 call coltabh movwf colmfrq+1 rrf digit ; now shift digit right two bits rrf digit,w andlw 3 movwf digit call rowtabl movwf rowfrq movf digit,w call rowtabh movwf rowfrq+1 dur1: equ 3000 ; duration 150 mS nxtd2: movlw (-dur1)&0ffh movwf iter movlw ((-dur1)>>8)&0ffh movwf iter+1 goto loop dur2: equ 6000 ; duration 300 mS nxtd3: movlw (-dur2)&0ffh movwf iter movlw ((-dur2)>>8)&0ffh movwf iter+1 goto loop endif flashx: andlw 0fh addwf one,w movwf temp flash1: bcf porta,led call pause bsf porta,led call pause decfsz temp goto flash1 movlw 8 movwf temp flash2: call pause decfsz temp goto flash2 pausex: movlw 0ffh movwf temp+1 movwf temp+2 pause1: decfsz temp+1 goto pause1 decfsz temp+2 goto pause1 retlw nothing ;===================== HARDWARE VECTORS ===================== freep1 set freep1+(01ffh-.) org 01ffh ; reset vector of PIC16C54 goto reset ; start of program end