;*****************************************************************************************
; Copyright © [09/12/1998] Scenix Semiconductor, Inc. All rights reserved.
;
; Scenix Semiconductor, Inc. assumes no responsibility or liability for
; the use of this [product, application, software, any of these products].
; Scenix Semiconductor conveys no license, implicitly or otherwise, under
; any intellectual property rights.
; Information contained in this publication regarding (e.g.: application,
; implementation) and the like is intended through suggestion only and may
; be superseded by updates. Scenix Semiconductor makes no representation
; or warranties with respect to the accuracy or use of these information,
; or infringement of patents arising from such use or otherwise.
;*****************************************************************************************
;
; Filename:
;		dtmf_gen_20.src
;
; Authors:
;		Chris Fogelklou
;		Applications Engineer	
;		Scenix Semiconductor, Inc.
;
; Revision:
;		1.3.5
;
; Target Part(s):
;		SX28AC/DP, Datecode AB9929AA
; 		SX52BD/PQ, Datecode AB0004AC
;
; Default Oscillator Frequency:
;		50MHz
;
; Assemblers:
;		SXKey28L Version 1.09 for SX18/20/28AC
;		SXKey52 Version 1.19 for SX48/52BD
;		SASM Version 1.45.2
;
; Date Written:
;		Dec 9, 1998
;
; Program Description:
;
;		DTMF generation utilizing 2 artificial sine wave generators in conjunction
;		and summed internally. The program dials a pre-defined number string.
;
; Interface Pins:
;
;       I/O Count:	3
;
;	dtmfPFMPin	equ	ra.0	; DTMF output (summed PFM output)
;	dtmfLedPin	equ	rb.0	; LED output, flashing when sending DTMF tones
;	dtmfHook	equ	rb.4	; output, connect to the phone line (1=disconnect/0=connect)
;
; Revision History:
;
;		1.0   Fixed bug in uppercase routine.  Changed 'ret' to retp. (98/12/14)
;		1.1   Added stack extend directive.  Put more functions in 
;		      subroutines for easy expansion.  Operation remains the same. (98/12/14)
;		1.2   Changed 'Delaynms' subroutine to 'delay10nMs' for longer delays. (98/12/14)
;		1.3   Made high frequency amplitude 1.25 times greater than low 
;		      frequency amplitude. (Called "Twist") (99/01/04)
;		1.3.1 Made "twist" code at end of sine generators more efficient. (99/01/04)
;		1.3.2 Documentation!  Documentation!  Documentation (99/01/22)
;		1.3.3 Stephen Holland
;		      Updated device directives for all SX packages
;		      Modified for use with Scenix SX28-52 Demo Board (99/10/11)
;		1.3.4 Stephen Holland
;		      Modified for use with Scenix SX28-52 Demo Board and SX V.23 Modem
;		      Board - chosen by the define, SX_Modem (99/10/18)
;		1.3.5 CFT/AEH - Rewritten according to VP guide 1.02, added 'hook' (00/07/12)
;		       								  
; Virtual Peripherals:
;
;		VP Scheduler
;		VP Pulse Frequency Modulation (PFM)
;		VP 16 bit timer 
;
;*****************************************************************************************
; Target SX
; Uncomment one of the following lines to choose the SX18AC, SX20AC, SX28AC, 
; SX48BD, or SX52BD.
;*****************************************************************************************
;SX18_20
;SX28
SX48_52

;*****************************************************************************************
; Assembler Used
; Uncomment the following line if using the Parallax SX-Key assembler. SASM assembler
; enabled by default.
;*****************************************************************************************
;SX_Key

;*****************************************************************************************
; Runnable Demo?
; Uncomment the following line to enable the main program, creating a runnable demo
;*****************************************************************************************
;DEMO

	;*********************************************************************************
	; Assembler directives:
	;	high speed external osc, turbo mode, 8-level stack, and extended option reg.
	;
	;	SX18/20/28 - 4 pages of program memory and 8 banks of RAM enabled by default.
	;	SX48/52 - 8 pages of program memory and 16 banks of RAM enabled by default.
	;
	;*********************************************************************************

IFDEF SX_Key 				;SX-Key Directives
  IFDEF SX18_20				;SX18AC or SX20AC device directives for SX-Key
		device	SX18L,oschs2,turbo,stackx_optionx
  ENDIF
  IFDEF SX28				;SX28AC device directives for SX-Key		
		device	SX28L,oschs2,turbo,stackx_optionx
  ENDIF
  IFDEF SX48_52				;SX48/52/BD device directives for SX-Key
		device	oschs2
  ENDIF
		freq	50_000_000
ELSE					;SASM Directives
  IFDEF SX18_20				;SX18AC or SX20AC device directives for SASM
		device	SX18,oschs2,turbo,stackx,optionx
  ENDIF
  IFDEF SX28				;SX28AC device directives for SASM
		device	SX28,oschs2,turbo,stackx,optionx
  ENDIF
  IFDEF SX48_52				;SX48BD or SX52BD device directives for SASM
		device	SX52,oschs2
  ENDIF
ENDIF

		id	'DTMFG_13'	; Version = 1.3.2
		reset	resetEntry	; set reset vector

;*****************************************************************************************
; Macros
;*****************************************************************************************
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;
	;	To support compatibility between source code written for the SX28 and the SX52,
	;	use macros.
	;
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

	;*********************************************************************************
	; Macro: _bank
	; Sets the bank appropriately for all revisions of SX.
	;
	; This is required since the bank instruction has only a 3-bit operand, it cannot
	; be used to access all 16 banks of the SX48/52. For this reason, FSR.7
	; (SX48/52bd production release) needs to be set appropriately, depending
	; on the bank address being accessed.
	;
	; So, instead of using the bank instruction to switch between banks, use _bank instead.
	; 
	;*********************************************************************************

_bank	macro	1
	bank	\1

	IFDEF SX48_52
	    IF \1 & %10000000		;SX48BD and SX52BD (production release) bank instruction 
		setb	fsr.7		;modifies FSR bits 4,5 and 6. FSR.7 needs to be set by software.
	    ELSE
		clrb	fsr.7
	    ENDIF
	ENDIF
	endm


	;*****************************************************************************************
	; Macros for SX28/52 Compatibility
	;*****************************************************************************************
	;*********************************************************************************
	; Macro: _mode
	; Sets the MODE register appropriately for all revisions of SX.
	;
	; This is required since the MODE (or MOV M,#) instruction has only a 4-bit operand. 
	; The SX18/20/28AC use only 4 bits of the MODE register, however the SX48/52BD have 
	; the added ability of reading or writing some of the MODE registers, and therefore use
	; 5-bits of the MODE register. The  MOV M,W instruction modifies all 8-bits of the 
	; MODE register, so this instruction must be used on the SX48/52BD to make sure the MODE
	; register is written with the correct value. This macro fixes this.
	;
	; So, instead of using the MODE or MOV M,# instructions to load the M register, use
	;  _mode instead.
	; 
	;*********************************************************************************
_mode	macro	1
	IFDEF SX48_52
  expand
		mov	w,#\1		;loads the M register correctly for the SX48BD and SX52BD
		mov	m,w
  noexpand
	ELSE
  expand
		mov	m,#\1		;loads the M register correctly for the SX18AC, SX20AC
  noexpand					;and SX28AC
	ENDIF
	endm

	;*****************************************************************************************
	; INCP/DECP macros for incrementing/decrementing pointers to RAM
	; used to compensate for incompatibilities between SX28 and SX52
	;*****************************************************************************************

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;
	;   	To support compatibility between source code written for the SX28 and the SX52,
	;	use macros.  This macro compensates for the fact that RAM banks are contiguous in
	;	the SX52, but separated by 0x20 in the SX18/28.
	;
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

INCP	macro	1
		inc	\1
	IFNDEF	SX48_52
		setb	\1.4		; If SX18,20 or SX28, keep bit 4 of the pointer = 1
	ENDIF				; to jump from $1f to $30, etc.
endm
		
DECP	macro	1
	IFDEF	SX48_52
		dec	\1
	ELSE
		clrb	\1.4		; If SX18,20 or SX28, forces rollover to next bank
		dec	\1		; if it rolls over.  (Skips banks with bit 4 = 0)
		setb	\1.4		; Eg:  $30 --> $20 --> $1f --> $1f
	ENDIF				; AND: $31 --> $21 --> $20 --> $30
endm

	;*****************************************************************************************
	; Error generating macros
	; Used to generate an error message if the label is unintentionally moved into the 
	; second half of a page.  Use for lookup tables.
	;*****************************************************************************************

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;
	;   	Surround lookup tables with the tableStart and tableEnd macros.  An error will
	;	be generated on assembly if part of the table is in the second half of a page.
	;	
	;	Example:
	;		lookupTable1
	;			add	pc,w	; Add w register to program counter
	;		tableStart
	;			retw	0
	;			retw	4
	;			retw	8
	;			retw	4
	;		tableEnd
	;
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

tableStart	macro 0			; Generates an error message if code that MUST be in
					; the first half of a page is moved into the second half.
	if $ & $100
		ERROR  'Must be located in the first half of a page.'
	endif
endm

tableEnd	macro 0			; Generates an error message if code that MUST be in
					; the first half of a page is moved into the second half.
	if $ & $100
		ERROR  'Must be located in the first half of a page.'
	endif
endm

;*****************************************************************************************
; Data Memory address definitions
; These definitions ensure the proper address is used for banks 0 - 7 for 2K SX devices
; (SX18/20/28) and 4K SX devices (SX48/52). 
;*****************************************************************************************
IFDEF SX48_52

global_org	=	$0A
bank0_org	=	$00
bank1_org	=	$10
bank2_org	=	$20
bank3_org	=	$30
bank4_org	=	$40
bank5_org	=	$50
bank6_org	=	$60
bank7_org	=	$70

ELSE

global_org	=	$08
bank0_org	=	$10
bank1_org	=	$30
bank2_org	=	$50
bank3_org	=	$70
bank4_org	=	$90
bank5_org	=	$B0
bank6_org	=	$D0
bank7_org	=	$F0

ENDIF
;*****************************************************************************************
; Global Register definitions
; NOTE: Global data memory starts at $0A on SX48/52 and $08 on SX18/20/28.
;*****************************************************************************************

		org	global_org

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;
	;   	Use only these defined label types for global registers.  If an extra temporary 
	;	register is required, adhere to these label types.  For instance, if two temporary
	;	registers are required for the Interrupt Service Routine, use the labels isrTemp0
	;	and isrTemp1.
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

flags0		equ	global_org + 0  ; stores bit-wise operators like flags 
					; and function-enabling bits (semaphores)
	;VP_BEGIN: DTMF Generation

	dtmfgGenEn	equ	flags0.0	; Signifies whether or not DTMF output is enabled
	dtmfgTimerFlag	equ	flags0.1	; Flags a rollover of the dtmfgTimers.

	;VP_END: DTMF Generation

flags1		equ	global_org + 1  ; stores bit-wise operators like flags 
					; and function-enabling bits (semaphores)
localTemp0	equ	global_org + 2	; temporary storage register
					; Used by first level of nesting
					; Never guaranteed to maintain data
localTemp1	equ	global_org + 3	; temporary storage register	
					; Used by second level of nesting
					; or when a routine needs more than one 
					; temporary global register.	
localTemp2	equ	global_org + 4	; temporary storage register
					; Used by third level of nesting or by
					; main loop routines that need a loop 
					; counter, etc.
isrTemp0	equ	global_org + 5	; Interrupt Service Routine's temp register.  
					; Don't use this register in the mainline.

;*****************************************************************************************
; RAM Bank Register definitions
;*****************************************************************************************

	;*********************************************************************************
	; Bank 0
	;*********************************************************************************
		org     bank0_org
bank0		=	$

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	; 	- Avoid using bank0 in programs written for SX48/52.
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

	;*********************************************************************************
	; Bank 1
	;*********************************************************************************
		org     bank1_org
bank1		=	$
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;
	;	Tip 1:
	;	Indicate which Virtual Peripherals a portion of source code or declaration belongs
	;	to with a ;VP_BEGIN: VirtualPeripheralName and VP_END: comment.
	;
	;	Tip 2:
	;	All RAM location declaration names should be
	;   	- left justified
	;	- less than 2 tabs in length
	;	- written in hungarian notation
	;	- prefixed by a truncated version of the Virtual Peripheral's name
	;	
	;	Examples:
	;
	;	;VP_BEGIN: RS232 Transmit
	;	rs232TxBank	=       $	;UART Transmit bank
	;	rs232TxHigh	ds      1	;hi byte to transmit
	;	rs232TxLow	ds      1	;low byte to transmit
	;	rs232TxCount	ds      1	;number of bits sent
	;	rs232TxDivide	ds      1	;xmit timing (/16) counter
	;	;VP_END: RS232 Transmit
	;
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

;VP_BEGIN: VP Scheduler
isrMultiplex	ds	1	; The isrMultiplex register is used to switch to a new
;VP_END: VP Scheduler		; execution thread on each pass of the ISR.


;VP_BEGIN: DTMF Generation
		
dtmfgSinGenBank	=	$
dtmfgFreqAccHi	ds	1		; 
dtmfgFreqAccLo	ds	1		; 16-bit accumulator which decides when to increment the sine wave

dtmfgFreqAccHi2	ds	1		; 
dtmfgFreqAccLo2	ds	1		; 16-bit accumulator which decides when to increment the sine wave

dtmfgFreqCntHi	ds	1		; freq_count = Frequency * 6.83671552
dtmfgFreqCntLo	ds	1		; 16-bit counter which decides which frequency for the sine wave

dtmfgFreqCntHi2 ds	1		; freq_count = Frequency * 6.83671552
dtmfgFreqCntLo2	ds	1		; 16-bit counter which decides which frequency for the sine wave

dtmfgCurrSin	ds	1		; The current value of the imitation sin wave
dtmfgSinVel	ds	1		; The velocity of the sin wave

dtmfgCurrSin2	ds	1		; The current value of the imitation sin wave
dtmfgSinVel2	ds	1		; The velocity of the sin wave

dtmfgSin2Tmp	ds	1		; This register is used to do a temporary shift/add register

dtmfgPFMBank	=	$
dtmfgPFM0Acc	ds	1		; PFM accumulator
dtmfgPFM0	ds	1		; current PFM output

;VP_END: DTMF Generation

	;*********************************************************************************
	; Bank 2
	;*********************************************************************************
		org	bank2_org
bank2		=	$

;VP_BEGIN: DTMF Generation

dtmfgTimers	=	$
dtmfgTimerLo	ds	1
dtmfgTimerHi	ds	1

dtmfgDialerBank	=	$
dtmfgByte	ds	1

;VP_END: DTMF Generation

	;*********************************************************************************
	; Bank 3
	;*********************************************************************************
		org	bank3_org
bank3		=	$
	;*********************************************************************************
	; Bank 4
	;*********************************************************************************
		org	bank4_org
bank4		=	$
	;*********************************************************************************
	; Bank 5
	;*********************************************************************************
		org	bank5_org
bank5		=	$
	;*********************************************************************************
	; Bank 6
	;*********************************************************************************
		org	bank6_org
bank6		=	$
	;*********************************************************************************
	; Bank 7
	;*********************************************************************************
		org	bank7_org
bank7		=	$

IFDEF SX48_52
	;*********************************************************************************
	; Bank 8
	;*********************************************************************************
		org	$80	;bank 8 address on SX52
bank8		=	$
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	; 	- This extra memory is not available in the SX18/28, so don't use it for Virtual
	;	  Peripherals written for both platforms.
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	;*********************************************************************************
	; Bank 9
	;*********************************************************************************
		org	$90	;bank 9 address on SX52
bank9		=	$
	;*********************************************************************************
	; Bank A
	;*********************************************************************************
		org	$A0	;bank A address on SX52
bankA		=	$
	;*********************************************************************************
	; Bank B
	;*********************************************************************************
		org	$B0	;bank B address on SX52
bankB		=	$
	;*********************************************************************************
	; Bank C
	;*********************************************************************************
		org	$C0	;bank C address on SX52
bankC		=	$
	;*********************************************************************************
	; Bank D
	;*********************************************************************************
		org	$D0	;bank D address on SX52
bankD		=	$
	;*********************************************************************************
	; Bank E
	;*********************************************************************************
		org	$E0	;bank E address on SX52
bankE		=	$
	;*********************************************************************************
	; Bank F
	;*********************************************************************************
		org	$F0	;bank F address on SX52
bankF		=	$
ENDIF
	;*********************************************************************************
	; Pin Definitions:
	;*********************************************************************************

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	; 	- Store all initialization constants for the I/O in the same area, so
	;	  pins can be easily moved around.
	;	- Pin definitions should follow the same format guidelines as RAM definitions
	;		- Left justified
	;		- No underscores.  Indicate word separation with capital letters
	;		- Less that 2 tabs in length
	;		- Indicate the Virtual Peripheral the pin is used for
	;		- All pin definitions for a specific VP must have the same prefix
	;	- Only use symbolic names to access a pin/port in the source code.
	;	- Example:
	;		; VP_BEGIN: RS232 Transmit
	;			rs232TxPin	equ	ra.3
	;		; VP_END: RS232 Transmit
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

	;VP_BEGIN: DTMF Generation
	dtmfPFMPin	equ	ra.0			; DTMF output (summed PFM output)
	;VP_END: DTMF Generation

RA_latch	equ	%00000110		;SX18/20/28/48/52 port A latch init
RA_DDIR		equ	%11111010		;SX18/20/28/48/52 port A DDIR value
RA_LVL		equ	%00000000		;SX18/20/28/48/52 port A LVL value
RA_PLP		equ	%11111111		;SX18/20/28/48/52 port A PLP value

;VP_BEGIN: DTMF Generation
dtmfLedPin	equ	rb.0			; LED output, flashing when sending DTMF tones
dtmfHook	equ	rb.4			; connect to the phone line (1=disconnect/0=connect)
;VP_END: DTMF Generation

RB_latch	equ	%11000000		;SX18/20/28/48/52 port B latch init
RB_DDIR		equ	%01101010		;SX18/20/28/48/52 port B DDIR value
RB_ST		equ	%11111111		;SX18/20/28/48/52 port B ST value
RB_LVL		equ	%00000000		;SX18/20/28/48/52 port B LVL value
RB_PLP		equ	%11111111		;SX18/20/28/48/52 port B PLP value


IFNDEF	SX18_20	; There is no C port on SX18/20
RC_latch	equ	%00000000		;SX18/20/28/48/52 port C latch init
RC_DDIR		equ	%11111111		;SX18/20/28/48/52 port C DDIR value
RC_ST		equ	%11111111		;SX18/20/28/48/52 port C ST value
RC_LVL		equ	%00000000		;SX18/20/28/48/52 port C LVL value
RC_PLP		equ	%11111111		;SX18/20/28/48/52 port C PLP value

IFDEF SX48_52	;SX48BD/52BD Port initialization values
RD_latch	equ	%00000000		;SX48/52 port D latch init
RD_DDIR		equ	%11111111		;SX48/52 port D DDIR value
RD_ST		equ	%11111111		;SX48/52 port D ST value
RD_LVL		equ	%00000000		;SX48/52 port D LVL value
RD_PLP		equ	%11111111		;SX48/52 port D PLP value

RE_latch	equ	%00000000		;SX48/52 port E latch init
RE_DDIR		equ	%11111111		;SX48/52 port E DDIR value
RE_ST		equ	%11111111		;SX48/52 port E ST value
RE_LVL		equ	%00000000		;SX48/52 port E LVL value
RE_PLP		equ	%11111111		;SX48/52 port E PLP value
ENDIF	;(SX18_20)
ENDIF	;(SX48_52)

;*****************************************************************************************
; Program constants
;*****************************************************************************************

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;	To calculate the interrupt period in cycles:
	;	- First, choose the desired interrupt frequency
	;		- Should be a multiple of each Virtual Peripherals sampling frequency.
	;		- Example:  19200kHz UART sampling rate * 16 = 307.200kHz
	;	- Next, choose the desired oscillator frequency.
	;		- 50MHz, for example.
	;	- Next calculate the 
	;	- Perform the calculation intPeriod = (cyclesPerSecond / interruptFrequency)
	;				  		= (50MHz / 307.2kHz)
	;						= 162.7604
	;	- Round intPeriod to the nearest integer:
	;						= 163
	;	- Now calculate your actual interrupt rate:
	;						= cyclesPerSecond / intPeriod
	;						= 50MHz / 163
	;						= 306.748kHz
	;	- This interrupt frequency will be the timebase for all of the Virtual 
	;	  Peripherals
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

intPeriod	=	81	; Gives an interrupt period at 50MHz of (81 * (1/50000000)s) = 1.62us
				; Which gives an interrupt frequency of (1/1.62us)Hz = 617.28kHz

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;	- Include all calculations for Virtual Peripheral constants for any sample 
	;	  rate.
	;	- Relate all Virtual Peripheral constants to the sample rate of the Virtual
	;	  Peripheral.
	;	- Example:
	;		; VP_BEGIN: 5ms Timer
	;		TIMER_DIV_CONST	equ 192	; This constant = timer sample rate/200Hz = 192
	;		; VP_END: 5ms Timer
	;
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

;VP_BEGIN: DTMF Generation

;*********************************************************************************
; Equates for DTMF tone frequencies
;*********************************************************************************

DTMF_FREQ697HI	equ	$012	; DTMF Frequency 697 Hz
DTMF_FREQ697LO	equ	$09d

DTMF_FREQ770HI	equ	$014	; DTMF Frequency 770 Hz
DTMF_FREQ770LO	equ	$090

DTMF_FREQ852HI	equ	$016	; DTMF Frequency 852 Hz
DTMF_FREQ852LO	equ	$0c0

DTMF_FREQ941HI	equ	$019	; DTMF Frequency 941 Hz
DTMF_FREQ941LO	equ	$021

DTMF_FREQ1209HI	equ	$020	; DTMF Frequency 1209 Hz
DTMF_FREQ1209LO	equ	$049

DTMF_FREQ1336HI	equ	$023	; DTMF Frequency 1336 Hz
DTMF_FREQ1336LO	equ	$0ad

DTMF_FREQ1477HI	equ	$027	; DTMF Frequency 1477 Hz
DTMF_FREQ1477LO	equ	$071

DTMF_FREQ1633HI	equ	$02b	; DTMF Frequency
DTMF_FREQ1633LO equ	$09c


DTMF_TONE_TIME	equ	8	; Signal duration x 10ms
DTMF_QUIET_TIME	equ	5	; Inter digit interval x 10ms

;VP_END: DTMF Generation

;-------------------------------------------------------------------------------------
IFDEF SX48_52
	;*********************************************************************************
	; SX48BD/52BD Mode addresses
	; *On SX48BD/52BD, most registers addressed via mode are read and write, with the
	; exception of CMP and WKPND which do an exchange with W.
	;*********************************************************************************
; Timer (read) addresses
TCPL_R		equ	$00		;Read Timer Capture register low byte
TCPH_R		equ	$01		;Read Timer Capture register high byte
TR2CML_R	equ	$02		;Read Timer R2 low byte
TR2CMH_R	equ	$03		;Read Timer R2 high byte
TR1CML_R	equ	$04		;Read Timer R1 low byte
TR1CMH_R	equ	$05		;Read Timer R1 high byte
TCNTB_R		equ	$06		;Read Timer control register B
TCNTA_R		equ	$07		;Read Timer control register A

; Exchange addresses
CMP		equ	$08		;Exchange Comparator enable/status register with W
WKPND		equ	$09		;Exchange MIWU/RB Interrupts pending with W

; Port setup (read) addresses
WKED_R		equ	$0A		;Read MIWU/RB Interrupt edge setup, 0 = falling, 1 = rising
WKEN_R		equ	$0B		;Read MIWU/RB Interrupt edge setup, 0 = enabled, 1 = disabled
ST_R		equ	$0C		;Read Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
LVL_R		equ	$0D		;Read Port Level setup, 0 = CMOS, 1 = TTL
PLP_R		equ	$0E		;Read Port Weak Pullup setup, 0 = enabled, 1 = disabled
DDIR_R		equ	$0F		;Read Port Direction

; Timer (write) addresses
TR2CML_W	equ	$12		;Write Timer R2 low byte
TR2CMH_W	equ	$13		;Write Timer R2 high byte
TR1CML_W	equ	$14		;Write Timer R1 low byte
TR1CMH_W	equ	$15 		;Write Timer R1 high byte
TCNTB_W		equ	$16		;Write Timer control register B
TCNTA_W		equ	$17		;Write Timer control register A

; Port setup (write) addresses
WKED_W		equ	$1A		;Write MIWU/RB Interrupt edge setup, 0 = falling, 1 = rising
WKEN_W		equ	$1B		;Write MIWU/RB Interrupt edge setup, 0 = enabled, 1 = disabled
ST_W		equ	$1C		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
LVL_W		equ	$1D		;Write Port Level setup, 0 = CMOS, 1 = TTL
PLP_W		equ	$1E		;Write Port Weak Pullup setup, 0 = enabled, 1 = disabled
DDIR_W		equ	$1F		;Write Port Direction

ELSE

	;*********************************************************************************
	; SX18AC/20AC/28AC Mode addresses
	; *On SX18/20/28, all registers addressed via mode are write only, with the exception of
	; CMP and WKPND which do an exchange with W.
	;*********************************************************************************
; Exchange addresses
CMP		equ	$08		;Exchange Comparator enable/status register with W
WKPND		equ	$09		;Exchange MIWU/RB Interrupts pending with W

; Port setup (read) addresses
WKED_W		equ	$0A		;Write MIWU/RB Interrupt edge setup, 0 = falling, 1 = rising
WKEN_W		equ	$0B		;Write MIWU/RB Interrupt edge setup, 0 = enabled, 1 = disabled
ST_W		equ	$0C		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
LVL_W		equ	$0D		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
PLP_W		equ	$0E		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
DDIR_W		equ	$0F		;Write Port Direction

ENDIF	;(SX48_52)


;*****************************************************************************************
; Program memory ORG defines
;*****************************************************************************************

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	; 	- Place a table at the top of the source with the starting addresses of all of
	;	  the components of the program.
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

INTERRUPT_ORG		equ	$0	; Interrupt must always start at location zero
RESET_ENTRY_ORG		equ	$1FB	; The program will jump here on reset.
SUBROUTINES_ORG		equ	$200	; The subroutines are in this location
STRINGS_ORG		equ	$300	; The strings are in location $300
PAGE3_ORG		equ	$400	; Page 3 is empty
MAIN_PROGRAM_ORG	equ	$600	; The main program is in the last page of program memory.










;****************************** Beginning of program space *******************************
	org	INTERRUPT_ORG			; First location in program memory.
;*****************************************************************************************
;------------------------------------------------------------------------------
; Interrupt Service Routine
;------------------------------------------------------------------------------
; Note: The interrupt code must always originate at address $0.
;
; Interrupt Frequency = OSC Frequency / ((-retiw value)*RTCC Prescaler)  For example:
; With a retiw value of -163, a prescaler of 1, and an oscillator frequency of 50MHz,
; this Interrupt Routine runs every 3.26us.
;------------------------------------------------------------------------------
ISR					;3	The interrupt service routine...

;------------------------------------------------------------------------------
;VP_BEGIN: VP Scheduler

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	; - Schedule tasks in the Interrupt Service Routine
	;	- Produces a FAR smaller worst-case cycle time count, and enables a larger number
	;	  of VP's to run simultaneously.  Also produces "empty" slots that future VP's 
	;	  can be copied and pasted into easily.
	;	- Determine how often your tasks need to run.  (9600bps UART can run well at a 
	;	  sampling rate of only 38400Hz, so don't run it faster than this.)
	;	- Strategically place each "module" into the threads of the Scheduler.  If a 
	;	  module must be run more often, just call it's module at double the rate or
	;	  quadruple the rate, etc.…
	;	- Split complicated Virtual Peripherals into several modules, keeping the 
	;	  high-speed portions of the Virtual Peripherals as small and quick as possible, 
	;	  and run the more complicated, slower processing part of the Virtual Peripheral 
	;	  at a lower rate.
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	;------------------------------------------------------------------------------
	; Virtual Peripheral Scheduler:  up to 16 individual threads, each running at
	;				   the interrupt rate/16.  Change the table
	;				   to modify the rate at which each of the threads
	;				   is run.
	;
	;	Input variable(s): 		isrMultiplex: variable used to choose threads
	;	Output variable(s): 		None, executes the next thread
	;	Variable(s) affected: 		isrMultiplex
	;	Flag(s) affected: 		None
	;	Program Cycles:			10 cycles (turbo mode SX52)
	;------------------------------------------------------------------------------
	_bank	isrMultiplex		;1 (2)
	inc	isrMultiplex		;1		; toggle interrupt rates
	mov	w,isrMultiplex		;1
; The code between the tableBegin and tableEnd statements MUST be 
; completely within the first half of a page.  The routines 
; it is jumping to must be in the same page as this table.
tableStart				; Start all tables with this macro.
	jmp	pc+w			;3
	jmp	isrThread1		;3,10 + 4/56 + 7 = 21/73 cycles. isrThread1 runs the DTMF Generation
	jmp	isrThread13		;3,10 + 14/16 + 3/4 + 7 = 34/37 cycles. isrThread13 runs the PFM and 16 bit timer
	jmp	isrThread3		; --- these are not used. ---
	jmp	isrThread4		;
	jmp	isrThread1		;
	jmp	isrThread5		;
	jmp	isrThread6		;
	jmp	isrThread7		;
	jmp	isrThread1		;
	jmp	isrThread8		;
	jmp	isrThread9		;
	jmp	isrThread10		;
	jmp	isrThread1		;
	jmp	isrThread11		;
	jmp	isrThread12		;
	jmp	isrThread13		;
tableEnd				; End all tables with this macro.
;VP_END: VP Scheduler

;------------------------------------------------------------------------------
;VP_BEGIN: VP Scheduler
; ISR TASKS
;------------------------------------------------------------------------------
isrThread1		; Serviced at ISR rate / 4
;------------------------------------------------------------------------------
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	; 	The sample rate of this section of code is the isr rate / 4, because it is jumped
	;	to in every 4th entry in the VP Schedulers table.  To increase the
	;	sample rate, put more calls to this thread in the Scheduler's jump table.
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?

;VP_BEGIN: DTMF Generation
;------------------------------------------------------------------------------
; Virtual Peripheral: DTMF Generation
;
; These routines (dtmfSineGen1 and dtmfSineGen2) generate a synthetic sine
; waves with values ranging from -32 to 32.  Frequency is specified by the counter.
; To set the frequency, put this value into the 16-bit dtmfgFreqCntxx register:
; dtmfgFreqCnt = FREQUENCY * 6.83671552 (@50MHz) 
;
; The routine dtmfSumSines then summs the 2 sine frequencies and applies a DC offset.
; The Pulse Frequency Modulation VP then outputs the signal on the next pass of
; the ISR.
;
;       Output variable(s) :	dtmfgPFM0
;       Variable(s) affected :	dtmfgFreqAccLo, dtmfgFreqAccLo2, dtmfgFreqAccHi, dtmfgFreqAccHi2
;       Flag(s) affected :	none
;	Program cycles:		9 worst case
;	Variable Length?	Yes.
;------------------------------------------------------------------------------
dtmfGeneration

		sb	dtmfgGenEn		;1
		jmp	dtmfSineGenOut		;3 :Exit if DTMF generation not enabled

dtmfSineGen1
		_bank	dtmfgSinGenBank		;1 (2)
		mov	w,dtmfgFreqCntLo	;1 ; advance sine at frequency
		add	dtmfgFreqAccLo,w	;1
		sb	c			;1
		jmp	:noCarry		;3 ; if lower byte rolls over 
		inc	dtmfgFreqAccHi		;1 ; carry over to upper dtmfgByte
		sb	z			;1
		jmp	:noCarry		;3 ; if carry causes roll-over
		mov	w,dtmfgFreqCntHi	;1 ; then add freq counter to accumulator
		mov	dtmfgFreqAccHi,w	;1 ; (which should be zero so move will work)
		jmp	:changeSin		;3 : and update sine wave
:noCarry					;  = To here in DTMF generation part: 9/13 cycles
		mov	w,dtmfgFreqCntHi	;1 ; add the upper dtmfgBytes of the accumulators
		add	dtmfgFreqAccHi,w	;1
		sb	c			;1
		jmp	:noChange		;3
:changeSin					;  = To here in DTMF generation part: 13/17 cycles
		mov	w,++dtmfgSinVel		;1 ; if the sine wave
		sb	dtmfgCurrSin.7		;1 ; is positive, decelerate
		mov	w,--dtmfgSinVel		;1 ; it.  Otherwise, accelerate it.
		mov	dtmfgSinVel,w		;1
		add	dtmfgCurrSin,w		;1 ; add the velocity to sin
:noChange					;  = To here in DTMF generation part: 15/22 cycles

dtmfSineGen2
		mov	w,dtmfgFreqCntLo2	;1 ;advance sine at frequency
		add	dtmfgFreqAccLo2,w	;1
		sb	c			;1
		jmp	:noCarry		;3 ; if lower dtmfgByte rolls over
		inc	dtmfgFreqAccHi2		;1 ; carry over to upper dtmfgByte
		sb	z			;1
		jmp	:noCarry		;3 ; if carry causes roll-over
		mov	w,dtmfgFreqCntHi2	;1 ; then add freq counter to accumulator
		mov	dtmfgFreqAccHi2,w	;1 ; (which should be zero so move will work)
		jmp	:changeSin		;3 ; and update sine wave
:noCarry					

		mov	w,dtmfgFreqCntHi2	;1 ; add the upper dtmfgBytes of the accumulators
		add	dtmfgFreqAccHi2,w	;1 
		sb	c			;1
		jmp	:noChange		;3
:changeSin
		mov	w,++dtmfgSinVel2	;1 ; if the sine wave
		sb	dtmfgCurrSin2.7		;1 ; is positive, decelerate it
		mov	w,--dtmfgSinVel2	;1 ; it.  Otherwise, accelerate it.
		mov	dtmfgSinVel2,w		;1	
		add	dtmfgCurrSin2,w		;1 ; add the velocity to sin
:noChange					;  = dtmfSineGen2 takes: 12/18 cycles
						;  = So far in DTMF generation = sinGen1+sinGen2=
dtmfSumSines					;  = 15/22 + 12/18 = 27/40

		mov	w,dtmfgCurrSin2		;1 ; mov sin2 into dtmfgPFM0
		mov	dtmfgPFM0,w		;1 
		mov	dtmfgSin2Tmp,w		;1 ; mov the high_frequency sin wave's current value
		clrb	c			;1 ; clear Carry flag
		snb	dtmfgSin2Tmp.7		;1 ; divide temporary register by four by shifting right
		setb	c			;1 ; set Carry flag
		rr	dtmfgSin2Tmp		;1
		clrb	c			;1 ; clear Carry flag
		snb	dtmfgSin2Tmp.7		;1
		setb	c			;1 ; set Carry flag
		mov	w,>>dtmfgSin2Tmp	;1 
		add	dtmfgPFM0,w		;1 ; (1.25)(sin2) = sin2 + (0.25)(sin2)
		mov	w,dtmfgCurrSin		;1 ; add the value of SIN into the PFM output
		add	dtmfgPFM0,w		;1 ; for result = dtmfgPFM0 = 1.25*sin2 + 1*sin

		mov	w,#128			;1 ; put dtmfgPFM0 in the middle of the output range
		add	dtmfgPFM0,w		;1 : dtmfSumSines takes 16cycles
dtmfSineGenOut					;  = DTMF generation part takes: 4/(27/40+16) = 4/56 cycles

;VP_END: DTMF Generation

	jmp	isrOut			;7 cycles until mainline program resumes execution
						
;------------------------------------------------------------------------------
isrThread2		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread3		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread4		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread5		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread6		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread7		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread8		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread9		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread10		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread11		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread12		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------
	jmp	isrOut			;7 cycles until mainline program resumes execution

;------------------------------------------------------------------------------
isrThread13		; Serviced at ISR rate / 16
;------------------------------------------------------------------------------

	
;VP_BEGIN: Pulse Frequency Modulation (PFM)
;------------------------------------------------------------------------------
; Virtual Peripheral: Pulse Frequency Modulation (PFM)
;
; This outputs the current value of dtmfgPFM0 to the dtmfPFMPin.  This generates an analog
; voltage at dtmfPFMPin after filtering.
;
;       Output variable(s) :	dtmfgPFM0Acc
;       Variable(s) affected :	dtmfgPFM0Acc
;       Flag(s) affected :	dtmfPFMPin
;	Program cycles:		9 worst case
;	Variable Length?	Yes.
;------------------------------------------------------------------------------
dtmfPfmOut
		_bank	dtmfgPFMBank		;1 (2)
		mov	w,dtmfgPFM0		;1 ; add the PWM output to the accumulator
		add	dtmfgPFM0Acc,w		;1
		snb	c			;1
		jmp	:carry			;3 ; if there was no carry,  
		clrb	dtmfPFMPin		;1 ; then clear the dtmfPFMPin
		skip				;1
:carry		setb	dtmfPFMPin	  	;1 ; otherwise set the dtmfPFMPin
						;  = PFM part = 8/9 cycles

;VP_END: Pulse Frequency Modulation (PFM)



;VP_BEGIN: 16-bit Timer
;------------------------------------------------------------------------------
; Virtual Peripheral: 16-bit Timer
;
; The timer will tick at the interrupt rate (intPeriod/clockspeed)  
; To set up the dtmfgTimers, move in FFFFh - (value that corresponds to the time.)  
; Example: For 1ms = 1ms*clockspeed/intPeriod. For intPeriod=163 and f=50MHz:
; 1ms/3.26us = 306 dec = 132 hex; so move in $FFFF - $0132 = $FECD into Timer
;
;       Output variable(s) :	dtmfgTimerFlag
;       Variable(s) affected :	dtmfgTimerLo, dtmfgTimerHi
;       Flag(s) affected :	dtmfgTimerFlag
;	Program cycles:		7 worst case
;	Variable Length?	Yes.
;------------------------------------------------------------------------------

timer		_bank	dtmfgTimers		;1/2
		inc	dtmfgTimerLo		;1 ;adjust timer's accumulator
		snb	z			;1
		inc	dtmfgTimerHi		;1 ; (timer = 16 bits long)        
		snb	z			;1
		setb	dtmfgTimerFlag		;1 : Ends delay10nMs when set

:timerOut					; = 6/7 cycles

						; total 8/9 + 6/7 = 14/16 cycles 
;VP_END: 16-bit Timer


	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	; 	The final thread in the ISR Multi-Threader must load the isrMultiplex 
	; 	register with a value of 255, so it will roll-over to 0 on the next ISR.
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	bank	isrMultiplex		;1/2
	mov	w,#255			;1 Reload isrMultiplex so isrThread1 will be run
	mov	isrMultiplex,w		;1 on next interrupt.

	jmp	isrOut			;7
;VP_END: VP Scheduler
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
isrOut
;------------------------------------------------------------------------------
	mov	w,#-intPeriod	;1	; return and add -intPeriod to the RTCC
	retiw			;3	; using the retiw instruction.

;------------------------------------------------------------------------------










;*****************************************************************************************
org	RESET_ENTRY_ORG
;*****************************************************************************************
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;	The main program operation should be easy to find, so place it at the end of the 
	;	program code.  This means that if the first page is used for anything other than 
	;	main program source code, a resetEntry must be placed in the first page, along 
	;	with a 'page' instruction and a 'jump' instruction to the beginning of the 
	;	main program, wherever that may be.
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
;------------------------------------------------------------------------------
resetEntry					; Program starts here on power-up
	page	_resetEntry
	jmp	_resetEntry
;------------------------------------------------------------------------------







	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;	ORG statements should use predefined labels rather than literal values.
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
org	SUBROUTINES_ORG

;*****************************************************************************************
; Subroutines
;*****************************************************************************************

;VP_BEGIN: DTMF Generation
	
	;*********************************************************************************
	; VP: DTMF Generation
	;
	; Function: delay10nMs
	; This subroutine delays 'w'*10 milliseconds. 
	; 
	; Inputs:
	;	w - # of milliseconds to delay for.
	; Outputs:
	; 	none
	; Registers affected:
	;	localTemp0, dtmfgTimerHi, dtmfgTimerLo, dtmfgTimerFlag
	; Functions Called:
	;       none
	;*********************************************************************************
delay10nMs	mov	localTemp0,w
		_bank	dtmfgTimers
:loop		clrb	dtmfgTimerFlag		; This loop delays for 10ms
		mov	w,#$0f4
		mov	dtmfgTimerHi,w
		mov	w,#$004
		mov	dtmfgTimerLo,w
:loop2		sb	dtmfgTimerFlag
		jmp	:loop2
		dec	localTemp0		; do it w-1 times.
		sz
		jmp	:loop	
		clrb	dtmfgTimerFlag
		retp

	;*********************************************************************************
	; VP: DTMF Generation
	;
	; Function: disableOutput
	; Disables the outputs. Loads DC value into PFM and disables the output switch.
	;
	; Inputs:
	;	none
	; Outputs:
	; 	none
	; Registers affected:
	;	dtmfgPFM0
	; Functions Called:
	;       none
	;*********************************************************************************

disableOutput	_bank	dtmfgPFMBank
		mov	w,#00			; put 2.5V DC on PFM output pin
		mov	dtmfgPFM0,w
		clrb	dtmfgGenEn
		retp

;VP_END: DTMF Generation

;*****************************************************************************************
org	STRINGS_ORG		; This label defines where strings are kept in program space.
;*****************************************************************************************
;------------------------------------------------------------------------------
; Put String Data Here
;------------------------------------------------------------------------------

; Example:
;_hello          dw      13,10,'UART Demo',0

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	; 	- Routines that use location-dependant data, such as in example below, should
	;	  use a LABEL rather than a literal value as their input.  Example:
	;	  instead of
	;		mov     m,#3		 ; move upper nybble of address of strings into m
	;	  use
	;		mov	m,#STRINGS_ORG>>8; move upper nybble of address of strings into m
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?


;VP_BEGIN: DTMF Generation

;*****************************************************************************************
; String data (for RS-232 output) and tables
;*****************************************************************************************
_dialString	dw	'210-1500',0
_0_		dw	DTMF_FREQ941HI, DTMF_FREQ941LO, DTMF_FREQ1336HI, DTMF_FREQ1336LO
_1_		dw	DTMF_FREQ697HI, DTMF_FREQ697LO, DTMF_FREQ1209HI, DTMF_FREQ1209LO
_2_		dw	DTMF_FREQ697HI, DTMF_FREQ697LO, DTMF_FREQ1336HI, DTMF_FREQ1336LO
_3_		dw	DTMF_FREQ697HI, DTMF_FREQ697LO, DTMF_FREQ1477HI, DTMF_FREQ1477LO
_4_		dw	DTMF_FREQ770HI, DTMF_FREQ770LO, DTMF_FREQ1209HI, DTMF_FREQ1209LO
_5_		dw	DTMF_FREQ770HI, DTMF_FREQ770LO, DTMF_FREQ1336HI, DTMF_FREQ1336LO
_6_		dw	DTMF_FREQ770HI, DTMF_FREQ770LO, DTMF_FREQ1477HI, DTMF_FREQ1477LO
_7_		dw	DTMF_FREQ852HI, DTMF_FREQ852LO, DTMF_FREQ1209HI, DTMF_FREQ1209LO
_8_		dw	DTMF_FREQ852HI, DTMF_FREQ852LO, DTMF_FREQ1336HI, DTMF_FREQ1336LO
_9_		dw	DTMF_FREQ852HI, DTMF_FREQ852LO, DTMF_FREQ1477HI, DTMF_FREQ1477LO
_star_		dw	DTMF_FREQ941HI, DTMF_FREQ941LO, DTMF_FREQ1209HI, DTMF_FREQ1209LO
_pound_		dw	DTMF_FREQ941HI, DTMF_FREQ941LO, DTMF_FREQ1477HI, DTMF_FREQ1477LO

;VP_END: DTMF Generation


;*****************************************************************************************
org	PAGE3_ORG
;*****************************************************************************************
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;	To ensure that several Virtual Peripherals, when pasted together, do not cross 
	;	a page boundary without the integrator's knowledge, put an ORG statement and one 
	;	instruction at every page boundary.  This will generate an error if a pasted 
	;	subroutine moves another subroutine to a page boundary.  
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	

;------------------------------------------------------------------------------
;VP_BEGIN: DTMF Generation
;
dtmfDigit2Index
;		Description:
;			This subroutine converts a digit from 0-9 or a '*' or a '#' to a table lookup
;			index which can be used by the dtmfLoadFreqs subroutine.  To use this routine,
;			pass it a value in the 'dtmfgByte' register.  No invalid digits are used (A, B, C, or D).
;
;		Global Ram Destroyed:
;			none
;
;		Inputs:
;			dtmfgByte - the ascii value to be sent
;
;		Outputs:
;			dtmfgByte - returns with ascii dtmfgByte or $FF if an error has occured
;
;------------------------------------------------------------------------------

		_bank	dtmfgDialerBank
		mov	w,#'9'^$FF		; if the character is above 9, then error (get another char)
		add	w,dtmfgByte
		snb	c			;1
		jmp	:error

		mov	w,#'*'
		mov	w,dtmfgByte-w
		snb	z			;1
		jmp	:star

		mov	w,#'#'
		mov	w,dtmfgByte-w
		snb	z			;1
		jmp	:pound

		mov	w,#'0'
		mov	w,dtmfgByte-w
		sb	c
		jmp	:error
		mov	w,#'0'
		sub	dtmfgByte,w		; convert to decimal number
		jmp	:gotIt

:star		mov	w,#10
		mov	dtmfgByte,w
		jmp	:gotIt
:pound		mov	w,#11
		mov	dtmfgByte,w
		
:gotIt		retp
:error
		mov	w,#$0FF
		mov	dtmfgByte,w
		retp

;VP_END: DTMF Generation


;------------------------------------------------------------------------------
;VP_BEGIN: DTMF Generation
;
dtmfLoadFreqs
;		Description:
;			This subroutine loads the frequencies using a table lookup approach. The index
;			into the table is passed in the dtmfgByte register.  The DTMF table must be in the
;			range of page STRINGS_ORG. ($300)
;
;		Global Ram Destroyed:
;			localTemp0
;
;		Inputs:
;			dtmfgByte - the index to the lookup table
;
;		Outputs:
;			dtmfgByte - returns with index into DTMF frequency constant lookup table
;
;------------------------------------------------------------------------------

		_bank	dtmfgDialerBank
		mov	w,#$0FF
		mov	w,dtmfgByte-w
		snb	z			;1 ;verify "not valid digit" ($FF)
		jmp	:endLoadIt
		clrb	c			;1 ; clear Carry flag
		rl	dtmfgByte		; all valid digits have MSB=0; don't have to clrb c again
		rl	dtmfgByte		; multiply dtmfgByte by 4 to get offset
						; (each digit in the dialString is 4 words)
		mov	w,#_0_			; address of first digit
		add	dtmfgByte,w		; add in the offset of the first digit
		mov	w,#4			; number of words in each digit
		mov	localTemp0,w		; counter; words left
		mov	w,#dtmfgFreqCntHi
		mov	fsr,w			;
		_bank	dtmfgDialerBank		;

:dtmfLoadLoop	_mode	STRINGS_ORG>>8		; 
		mov	w,dtmfgByte
		iread				; get the value from the table
		_bank	dtmfgSinGenBank		; and load it into the frequency 
		mov	indf,w			; register
		_bank	dtmfgDialerBank
		inc	dtmfgByte
		inc	fsr
		decsz	localTemp0
		jmp	:dtmfLoadLoop		; when all 4 values have been loaded,
:endLoadIt	retp				; return

;VP_END: DTMF Generation

	
;------------------------------------------------------------------------------
;VP_BEGIN: DTMF Generation
;
dtmfDialIt
;		Description:
;			This subroutine puts out whatever frequencies were loaded 
;			for DTMF_TONE_TIME * 10ms, and ends with DTMF_QUIET_TIME *10ms 
;			silence.
;
;		Global Ram Destroyed:
;			localTemp0
;
;		Inputs:
;			dtmfgByte
;
;		Outputs:
;			none
;
;------------------------------------------------------------------------------

		_bank	dtmfgDialerBank
		mov	w,#$0FF
		mov	w,dtmfgByte-w
		snb	z			;1
		jmp	:dialItDone
	
		call	dtmfInit		; initialize sinus values

		setb	dtmfLedPin
		mov	w,#DTMF_TONE_TIME	; delay DTMF_TONE_TIME x10ms
		call	@delay10nMs		; 
		clrb	dtmfgGenEn		; stop the tone and
		clrb	dtmfLedPin
		mov	w,#DTMF_QUIET_TIME	; delay DTMF_QUIET_TIME x 10ms
		call	@delay10nMs		; 

:dialItDone	retp


;VP_END: DTMF Generation


;------------------------------------------------------------------------------
;VP_BEGIN: DTMF Generation
;
dtmfInit
;		Description:
;			Initialises the sinus frequencies with correct values
;
;		Global Ram Destroyed:
;			localTemp0
;
;		Inputs:
;			none
;
;		Outputs:
;			dtmfgCurrSin, dtmfgCurrSin2
;			dtmfgSinVel, dtmfgSinVel2
;
;------------------------------------------------------------------------------

		_bank	dtmfgSinGenBank
		mov	w,#-4			; use these values to start the wave at close to zero crossing.
		mov	dtmfgCurrSin,w 
		mov	dtmfgCurrSin2,w
	
		mov	w,#-8			; use these values to start the wave at close to zero crossing.
		mov	dtmfgSinVel,w
		mov	dtmfgSinVel2,w

		retp

;VP_END: DTMF Generation



;*****************************************************************************************
org	MAIN_PROGRAM_ORG
;*****************************************************************************************



;------------------------------------------------------------------------------
; RESET VECTOR 
;------------------------------------------------------------------------------

	;------------------------------------------------------------------------------
	; Program execution begins here on power-up or after a reset
	;------------------------------------------------------------------------------

_resetEntry
	;------------------------------------------------------------------------------
	; Initialize all port configuration
	;------------------------------------------------------------------------------

		_mode	ST_W			;point MODE to write ST register
		mov     w,#RB_ST            	;Setup RB Schmitt Trigger, 0 = enabled, 1 = disabled
		mov	!rb,w		
IFNDEF 	SX18_20
		mov     w,#RC_ST            	;Setup RC Schmitt Trigger, 0 = enabled, 1 = disabled
		mov	!rc,w	
ENDIF 	;(SX18_20)
IFDEF 	SX48_52
		mov     w,#RD_ST            	;Setup RD Schmitt Trigger, 0 = enabled, 1 = disabled
		mov	!rd,w		
		mov     w,#RE_ST            	;Setup RE Schmitt Trigger, 0 = enabled, 1 = disabled
		mov	!re,w		
ENDIF 	;(SX48_52)
		_mode	LVL_W			;point MODE to write LVL register
		mov     w,#RA_LVL            	;Setup RA CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!ra,w		 
		mov     w,#RB_LVL            	;Setup RB CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!rb,w		
IFNDEF 	SX18_20
		mov     w,#RC_LVL            	;Setup RC CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!rc,w	
ENDIF 	;(SX18_20)
IFDEF 	SX48_52
		mov     w,#RD_LVL            	;Setup RD CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!rd,w		
		mov     w,#RE_LVL            	;Setup RE CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!re,w		
ENDIF	;(SX48_52)
		_mode	PLP_W			;point MODE to write PLP register
		mov     w,#RA_PLP            	;Setup RA Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!ra,w		 
		mov     w,#RB_PLP            	;Setup RB Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!rb,w		
IFNDEF 	SX18_20
		mov     w,#RC_PLP            	;Setup RC Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!rc,w	
ENDIF	;(SX18_20)
IFDEF 	SX48_52
		mov     w,#RD_PLP            	;Setup RD Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!rd,w		
		mov     w,#RE_PLP            	;Setup RE Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!re,w		
ENDIF	;(SX48_52)
		_mode	DDIR_W			;point MODE to write DDIR register
		mov	w,#RA_DDIR		;Setup RA Direction register, 0 = output, 1 = input		
		mov	!ra,w	
		mov	w,#RB_DDIR		;Setup RB Direction register, 0 = output, 1 = input
		mov	!rb,w			
IFNDEF 	SX18_20
		mov	w,#RC_DDIR		;Setup RC Direction register, 0 = output, 1 = input
		mov	!rc,w			
ENDIF	;(SX18_20)
IFDEF SX48_52
		mov	w,#RD_DDIR		;Setup RD Direction register, 0 = output, 1 = input
		mov	!rd,w			
		mov	w,#RE_DDIR		;Setup RE Direction register, 0 = output, 1 = input
		mov	!re,w			
ENDIF	;(SX48_52)
		mov     w,#RA_latch          	;Initialize RA data latch
		mov     ra,w		
		mov     w,#RB_latch         	;Initialize RB data latch
		mov     rb,w		
IFNDEF 	SX18_20
		mov     w,#RC_latch          	;Initialize RC data latch
		mov     rc,w		
ENDIF	;(SX18_20)
IFDEF SX48_52
		mov     w,#RD_latch         	;Initialize RD data latch
		mov     rd,w			
		mov     w,#RE_latch         	;Initialize RE data latch
		mov     re,w			
ENDIF	;(SX48_52)


	;------------------------------------------------------------------------------
	; Clear all Data RAM locations
	;------------------------------------------------------------------------------

zeroRam
IFDEF SX48_52   				;SX48/52 RAM clear routine
		mov	w,#$0a			;reset all ram starting at $0A
		mov	fsr,w
:zeroRam	clr	indf			;clear using indirect addressing
		incsz	fsr			;repeat until done
		jmp	:zeroRam

		_bank	bank0			;clear bank 0 registers
		clr	$10
		clr	$11
		clr	$12
		clr	$13
		clr	$14
		clr	$15
		clr	$16
		clr	$17
		clr	$18
		clr	$19
		clr	$1a
		clr	$1b
		clr	$1c
		clr	$1d
		clr	$1e
		clr	$1f

ELSE	;(SX48_52)				;SX18/20/28 RAM clear routine
		clr	fsr			;reset all ram banks
:zeroRam	sb	fsr.4			;are we on low half of bank?
		setb	fsr.3			;If so, don't touch regs 0-7
		clr	indf			;clear using indirect addressing
		incsz	fsr			;repeat until done
		jmp	:zeroRam
ENDIF	;(SX48_52)
	;------------------------------------------------------------------------------
	; Initialize program/VP registers
	;------------------------------------------------------------------------------

;VP_BEGIN: DTMF Generation
		call	@dtmfInit			; Program starts here on power up
		call	@disableOutput
;VP_END: DTMF Generation

	;------------------------------------------------------------------------------
	; Setup and enable RTCC interrupt, WREG register, RTCC/WDT prescaler
	;------------------------------------------------------------------------------

	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
	; Virtual Peripheral Guidelines Tip:
	;
	;	The suggested default values for the option register are:
	;	- Bit 7 set to 0: location $01 addresses the W register (WREG)
	;	- Bit 3 set to 1: Prescaler assigned to WatchDog Timer
	;
	;	If a routine must change the value of the option register (for example, to access
	;	the RTCC register directly), then it should restore the default value for the 
	;	option register before exiting.
	;
	;?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?


RTCC_ON		=	%10000000	;Enables RTCC at address $01 (RTW hi)
					;*WREG at address $01 (RTW lo) by default
RTCC_ID		=	%01000000	;Disables RTCC edge interrupt (RTE_IE hi)
					;*RTCC edge interrupt (RTE_IE lo) enabled by default
RTCC_INC_EXT	=	%00100000	;Sets RTCC increment on RTCC pin transition (RTS hi)
					;*RTCC increment on internal instruction (RTS lo) is default
RTCC_FE		=	%00010000	;Sets RTCC to increment on falling edge (RTE_ES hi)
					;*RTCC to increment on rising edge (RTE_ES lo) is default
RTCC_PS_ON	=	%00000000	;Assigns prescaler to RTCC (PSA lo)
RTCC_PS_OFF	=	%00001000	;Assigns prescaler to WDT (PSA hi)
PS_000		=	%00000000	;RTCC = 1:2, WDT = 1:1
PS_001		=	%00000001	;RTCC = 1:4, WDT = 1:2
PS_010		=	%00000010	;RTCC = 1:8, WDT = 1:4
PS_011		=	%00000011	;RTCC = 1:16, WDT = 1:8
PS_100		=	%00000100	;RTCC = 1:32, WDT = 1:16
PS_101		=	%00000101	;RTCC = 1:64, WDT = 1:32
PS_110		=	%00000110	;RTCC = 1:128, WDT = 1:64
PS_111		=	%00000111	;RTCC = 1:256, WDT = 1:128

OPTIONSETUP	equ	RTCC_ON | RTCC_FE | RTCC_PS_OFF	; the default option setup for this program.
		mov	w,#OPTIONSETUP		; setup option register for RTCC interrupts enabled 
		mov	!option,w		; and prescaler assigned to WDT.
		jmp	@mainLoop

;------------------------------------------------------------------------------
; MAIN PROGRAM CODE 
;------------------------------------------------------------------------------


mainLoop
IFDEF	DEMO
	;*********************************************************************************
	; Send DTMF dial String
	;******************************************************************
:dialString	_bank	dtmfgDialerBank
 		mov	w,#_dialString		; store String address
 		mov	localTemp1,w		; store String address

		clrb	dtmfHook		; go off-hook
		mov	w,#$64
		call	@delay10nMs		; and delay for 1 seconds.
		
:loop		
		_mode	STRINGS_ORG>>8		; read next character
		mov	w,localTemp1		; with indirect addressing 
		iread				; using the mode register
		test	w			; are we at the last char?
		snb	z			;1
		jmp	:dialDone		;yes, we're done
		_bank	dtmfgDialerBank
		mov	dtmfgByte,w
		setb	dtmfgGenEn		; enable 
		call	@dtmfDigit2Index	; convert the ascii digit to an 
						; index value
		call	@dtmfLoadFreqs		; load the frequency registers

		call	@dtmfDialIt		; dial the number for 100ms and return.
		inc	localTemp1		; point to next character
		jmp	:loop			; loop until done
:dialDone
		jmp	$			;halt

ENDIF	;(DEMO)
	jmp	mainLoop





;*****************************************************************************************
END		;End of program code
;*****************************************************************************************
