TUCoPS :: Phreaking Boxes - Silver, White :: dtmfpic.asm

PWM DTMF Dialer for 16C54 PIC

; 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

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH