; ;================================================================================== ;= TITLE: DTMF.asm = ;= DATE: Started August 1999 = ;= PURPOSE: = ;= = ;= FILE TYPE: STAND ALONE MODULE = ;= = ;= HEADER FILES: equ.h, offset.s, sine.s = ;= = ;= HARDWARE: NovaTech Z8 Proto. Red Board ZPCB18 = ;= = ;= = ;= ASSEMBLER: ZiLOG ZDS / ZMASM = ;= PROGRAMMER: Bob Bongiorno = ;================================================================================== ; ; RELEASE HISTORY: ; ; Version Date Description ; 1.00 8/15/99 Proto Release ; 1.10 9/15/99 Reasonably Functional Model ; 1.20 9/28/99 Commented and OTP Burned ; ;================================================================================== ; ; ;********************************************************************************** ; I/O MAP ;********************************************************************************** ;P00 --> PWM Output ;P01 --> unused output, no connection ;P02 --> unused output, no connection ;P20 --> Row 0 ;P21 --> Row 1 ;P22 --> Row 2 ;P23 --> Row 3 ;P24 --> Col 0 ;P25 --> Col 1 ;P26 --> Col 2 ;P27 --> Col 3 ;P31 --> unused input, terminate to ground. ;P32 --> unused input, terminate to ground. ;P33 --> unused input, terminate to ground. ;********************************************************************************** ;********************************************************************************** GLOBALS ON ;Required for symbol table generation. include "equ.h" ;Equate file. ;********************************************************************************** ; Interrupt Vectors / Program Memory Address 0000 - 000Bh ;********************************************************************************** org 0000h .WORD DUMMY ;IRQ 0 , Not Used .WORD DUMMY ;IRQ 1 , Not Used .WORD DUMMY ;IRQ 2 , Not Used .WORD DUMMY ;IRQ 3 , Not Used .WORD KEY_SCAN ;IRQ 4 , Timer 0 Interrupt .WORD TIMER_1 ;IRQ 5 , Timer 1 Interrupt ;********************************************************************************** ; Start of Executed Code, 000Ch ;********************************************************************************** ; ; ;********************************************************************************** ; Initialization Begins ;********************************************************************************** DTMF_INIT: DI ;Disable interrupts. CLR P3M ;Port 3 = Digital Inputs, Port 2 = Open Drain Outputs. LD P01M,#00000100b ;Port 0 = Outputs, Stack = Internal (Emulator requirement). LD P2M,#00001111b ;Port 2 Mode: Outputs = 7,6,5 & 4, Inputs = 3,2,1 & 0 SRP #70h ;Setup to clear all RAM. Load a pointer (r15), in working LD r15,#7Eh ;register group 7, to one below it's own address of 7Fh. CLR_RAM: ;This two instruction loop, (4 bytes) clears all RAM ! CLR @r15 ;The DJNZ instruction requires a working register. DJNZ r15,CLR_RAM ;(Ports 0 and 2 outputs are cleared here as well.) ;The last register cleared is r15 itself by the last DJNZ ! DEC KEY_TEMP ;Non-key value (FF) for first time through KEY_SCAN. CLR IRQ ;Clear spurious interrupt requests. LD IMR,#00010000b ;Allow IRQ 4 (Timer 0) interrupt. LD IPR,#00001011b ;Set interrupt priority for IRQ5 > IRQ4. LD SPL,#80h ;Stack = Top of Ram + 1 (Pre-decrementing stack pointer). CLR SPH ;(A good practice during emulation.) LD PRE0,#00000001b ;T0 prescaler = 00h for divide by 64 and continuous mode. CLR T0 ;T0 counter = 00h for count of 256. LD TMR,#00000011b ;Load and enable T0 for keyscan interrupts = 16.384 mSec. EI ;Enable global interrupts. ;********************************************************************************** ; Initialization Ends ;********************************************************************************** ;********************************************************************************** ; Foreground Wait-Loop ;********************************************************************************** WAIT_HERE: ;Program loops here in the foreground NOP ;until the interrupt for Timer 0 /KEY_SCAN NOP ;comes along. (User's code could be NOP ;located here but must be tolerant of periods JR WAIT_HERE ;of inactivity while the DTMF signal is output. ;Also, any modification of the register pointer ;will require saving and restoring it in the ;following routines.) ;********************************************************************************** ; KEY_SCAN ;KEY_SCAN is an interrupt service routine driven by Timer 0. KEY_SCAN outputs the column drive ;on ports 2-4, 2-5, 2-6, and 2-7. Scan inputs are on ports 2-0, 2-1, 2-2 and 2-3. ;As each column is pulled low, the inputs are checked for an active key and debounced ;if necessary. Once a key is debounced, DTMF_OUT is called in order to output the appropriate ;frequencies. ;********************************************************************************** KEY_SCAN: SRP #WORK_REG0 ;Point to working reg. group 0. LD P2M,#00001111b ;Set Port 2 for upper nibble outputs, ;lower nibble inputs. COL_0: LD p2,#11101111b ;Column 0 driven low. CLR key_cnt ;Key 0-3. CALL GET_KEY ;This finds active key if any. JR Z,KEY_FOUND ;Jump to debounce key. COL_1: LD p2,#11011111b ;Column 1 driven low. LD key_cnt,#4 ;Key 4-7 CALL GET_KEY ; JR Z,KEY_FOUND ; COL_2: LD p2,#10111111b ;Column 2 driven low. LD key_cnt,#8 ;Key 8-11 CALL GET_KEY ; JR Z,KEY_FOUND ; COL_3: LD p2,#01111111b ;Column 3 driven low. LD key_cnt,#12 ;Key 12-15 CALL GET_KEY ; JR NZ,SCAN_EXIT ;Jump for no active key found. KEY_FOUND: ;Active key found. ADD key_cnt,row_cnt ;Add row value from GET_KEY to key base value. CP key_cnt,key_temp ;If key is same, JR Z,KEY_SAME ;go debounce it. CLR bounce ;Not same, so clear debounce counter. LD key_temp,key_cnt ;Make them the same for the next time around. JR SCAN_EXIT ;Exit with no action taken. KEY_SAME: INC bounce ;Debounce counter. CP bounce,#4 ;The same key for 4 reads ? JR NZ,SCAN_EXIT ;If not, exit with no action taken. DEBOUNCED: LD key_temp,#0FFh ;Else debounced so set key_tmp = non-key value and CLR bounce ;clear bounce counter for next key scan. JR DTMF_OUT ;Go output DTMF for key_cnt value. SCAN_EXIT: LD p2,#11111111b ;All columns inactive. IRET ;Return to WAIT_HERE loop and reenable T0 int. GET_KEY: LD temp_rows,p2 ;Input port 2 and save in temp_rows. AND temp_rows,#0Fh ;Clear for Row data only. ROW_0: CLR row_cnt ;Set for Row 0. CP temp_rows,#00001110b ;Row 0 key ? JR Z,KEY_RET ;Yes, return. ROW_1: INC row_cnt ;No. Set for Row 1. CP temp_rows,#00001101b ;Row 1 key ? JR Z,KEY_RET ;Yes, return. ROW_2: INC row_cnt ;No. Set for Row 2. CP temp_rows,#00001011b ;Row 2 key ? JR Z,KEY_RET ;Yes, return. ROW_3: INC row_cnt ;No. Set for Row 3. CP temp_rows,#00000111b ;Row 3 key ? KEY_RET: RET ;Return. ;********************************************************************************** ; DTMF_OUT ;Output DTMF signals based on the key value in key_cnt. ;Stay here till no key is active. ;Timer 0 runs continuously at the sample rate. The DTMF output is raised at start of its' cycle. ;T0 is a polled interrupt. ;Timer 1 runs as a one-shot timer whose counter is loaded with the Sine value. T1 is a ;vectored interrupt that pulls the DTMF line back down once per T0 cycle. ;T0 is the PWM frequency, T1 controls the ON time or Duty-Cycle. ;********************************************************************************** DTMF_OUT: SRP #WORK_REG1 ;Set up pointer for working register group 1. CLR TMR ;Stop timers. LD pointer_hi,#>Sine ;Load the Sine table, high byte, base address ;into pointer_hi. DTMF_OFFSET: LD offset_hi,#>offset_table ;Load offset_table base address LD offset_lo,# 0011 1100) ;Adding to C0h yields FCh. Because no carry is generated, ;we don't have to increment offset_hi, it's always 01h. ;Each key has a designated row and column frequency pair. ;Get the table increment values based upon the current key. ;We need a row frequency word and column frequency word. ;These values are the step size for "walking through" ;the Sine table. The larger the step, the higher the frequency. LD temp,#R_FREQ_HI ;Set indirect pointer to r_freq_hi register address. LDCI @temp,@offset ;r_freq_hi LDCI @temp,@offset ;r_freq_lo LDCI @temp,@offset ;c_freq_hi LDCI @temp,@offset ;c_freq_lo ;Column and row frequency offsets are now in place. ;Set-up Timer 0 and 1 to prepare for PWM / DTMF signal generation on port 0-0. DTMF_TIMERS: LD PRE0,#00000101b ;T0 prescaler = 1, continuous mode. LD T0,#ctval ;T0 is loaded with 7Dh for 8 khz sample rate (8 Mhz xtal). LD TMR,#00000011b ;Load and enable T0 for the first time. LD PRE1,#00000110b ;T1 prescaler = 1, one shot mode. LD calc_sin_value,#20 ;Load a dummy value for the first time. LD IMR,#00100000b ;Allow vectored T1 interrupt only. CLR IRQ ;Clear all pending ints. EI ;Reenable global ints. DTMF_LOOP: OR P0,#00000001b ;Raise DTMF output line, port 0-0. LD T1,calc_sin_value ;T1 counter loaded = last calc. value. OR TMR,#00001100b ;Start T1 (One shot mode.) TCM P2,#00001111b ;Check for any key still active. JR Z,DTMF_EXIT ;No active key, exit. Else, continue DTMF out. ;At some point Timer 1's interrupt will come along and set the output low. ;In the meantime, we are calculating Timer 1's counter value for the NEXT pass thru ;the DTMF_LOOP. ;The DTMF signal is the sum of 2 sine waves. The T1 value, or duty cycle, is changed ;8000 times /sec. CALCULATE_PWM executes at this rate in order to determine the next ;sum of the row and column value from the sine table. The row_inc pair is the pointer ;for the row frequency plus the previous running sums. Only the upper byte is used ;to find the next sine value. Likewise, the column_inc pair works the same way for ;the column frequency. Once we have a row and column sine value, they are added ;together. The column frequency requires a 3 db higher level with respect to the row. ;This is accomplished by shifting the column left before adding to the row. ;Because the sine table has been located with the lower byte of its address at 00, ;and because it's 256 bytes long, (00 --> FF), we don't have to be concerned with ;the upper byte of the sine table address. It is always 02. This simplifies the math ;each time will calculate the next PWM value. CALCULATE_PWM: ADD row_inc_lo,r_freq_lo ;The r_freq pair for the key is added to the running ADC row_inc_hi,r_freq_hi ;sum and stored there. The high byte, row_inc_hi is LD pointer_lo,row_inc_hi ;loaded into the sine table pointer_lo. LDC row_val,@pointer ;We now have the row value from the sine table. ADD col_inc_lo,c_freq_lo ;Now we do the same for the column. ADC col_inc_hi,c_freq_hi ; LD pointer_lo,col_inc_hi ; LDC col_val,@pointer ;We now have the column value from the sine table. RL col_val ;The column value is doubled ;by shifting left before the sum. This is to give ;the column frequencies a 3 db gain over the ;row frequencies. (This is refered to as "twist" ;in the telephone industry.) ADD row_val,col_val ;2(col_val) + row_val --> row_val . LD calc_sin_value,row_val ;The calc_sin_value is this sum. ;Save it for the next T1 load in the DTMF_LOOP. WAIT_T0_POLLED: TM IRQ,#00010000b ;Test for T0 IRQ set. JR Z,WAIT_T0_POLLED ;If not, loop back to test T0 IRQ again. AND IRQ,#11101111b ;If T0 IRQ is set, clear it and JR DTMF_LOOP ;jump to start next cycle of DTMF output. DTMF_EXIT: ;The key is no longer active. DI ;(Always disable global interrupts while modifying Int regs.) CLR TMR ;Stop both timers. CLR IRQ ;Clear all pending interrupts. LD IMR,#00010000b ;Enable T0 interrupts only. LD PRE0,#00000001b ;Set T0 prescaler for divide by 64 (00) and continuous mode. CLR T0 ;Set T0 counter for divide by 256 (00) for 16 mSec int. LD TMR,#00000011b ;Load and enable T0. CLR row_inc_lo ;Reset pointers so pure sines will be CLR row_inc_hi ;in phase. This is for column 4 only, optional CLR col_inc_lo ;keypad with optional offset table entries. CLR col_inc_hi ;This clearing is not necessary for 3 column keypad. IRET ;Return to WAIT_HERE loop and reenable T0 int. ;********************************************************************************** ; TIMER_1 Interrupt Service Routine ;********************************************************************************** TIMER_1: ;Timer 1 interrupt service. AND P0,#11111110b ;Lower DTMF output line. IRET ;Reenable Timer IRQ's and return to DTMF work. ;********************************************************************************** ; Offset Table ;********************************************************************************** org 01C0h ;Locate offset_table include "offset.S" ;offset_table. ;********************************************************************************** ; Sine Table ;********************************************************************************** org 0200h ;Locate Sine Table include "Sine.s" ;Sine table. ;********************************************************************************** ; Dummy Interrupt Service Routine ;********************************************************************************** org 03F8h ;Locate Dummy interrupt handler to just ;fit into top of memory for 1K ROM device. ;This is a recommended routine for all Z8's. ;Locate just inside available ROM space. ;Fill unused ROM locations ahead of this ;with NOP's (FF). DUMMY: ;Vector here for spurious interrupts. DI ;Disable global, CLR IMR ;potential and CLR IRQ ;all pending interrupts. JP DTMF_INIT ;Jump to cold start / initialization. ;********************************************************************************** ; Program End ;********************************************************************************** end ;================================================================================== ;==================================================================================