;
; $Header: E:/HAM/EVM/RCS/fsk1200.asm 1.5 1997/10/23 13:40:14 dbraun Exp $
;
; $Log: fsk1200.asm $
; Revision 1.5  1997/10/23 13:40:14  dbraun
; Added blinking of Orange LED for received serial port data.
;
; Revision 1.4  1997/10/21 07:24:09  dbraun
; Yellow LED now shows status of SCI TXD line.
;
; Revision 1.3  1997/01/31 16:02:51  dbraun
; Added runtime-patchable setup of all KISS parameters.
;
; Revision 1.2  1996/12/18 08:48:17  dbraun
; Adjusted audio levels for IC821, and added Rx/Tx audio monitor
; output to right channel.
;
; Revision 1.1  1996/12/02 22:54:54  dbraun
; Initial revision
;
;

;file: FSK1200.ASM
;FSK modem for 1200 bps (1200Hz and 2200Hz tones)
;(c) 1995 Pawel Jalocha, SP9VRC
;Free license is given for radio-amateur usage _only_.
;This version of 21st July 1995

        opt mu
        nolist
	include 'leonid'
	list
        title '1200bps FSK modem by SP9VRC'

DCDLED  macro mode      ; DCD LED clr/set/chg
	b\mode #13,X:$FFE4
	endm

UpLED   macro mode      ;UP LED
        b\mode #1,X:$FFE4
        endm

DownLED macro mode      ;DOWN LED
        b\mode #2,X:$FFE4
        endm

YellowLED macro mode    ;CAT line (a yellow LED connected)
        b\mode #3,X:$FFE4
        endm

OrangeLED macro mode    ; (an orange LED connected)
        b\mode #14,X:$FFE4
        endm


SampleFreq      equ 9600.0

BufLen  equ     64      ;sample buffer length
BatchLen equ     8      ;processing batch length (must be > 1)
BatchLenLog equ  3      ;BatchLen = 2 ^ BatchLenLog

DCrise     equ  256     ;DC rise time in samples
			;the larger, the slower the DC follows
RMSrise    equ  256     ;RMS rise time in samples
			;the larger, the slower the RMS follows

AGCenable  equ 0        ;automatic adjustment of the input amplification
AGCstereo  equ 0        ;take both or only the left channel for the AGC
AGCalert   equ 1        ;indicate too low or too high audio levels with the LEDs

AGChold  equ  512       ;AGC hold time in samples
AGCfall  equ  128       ;AGC fall time in samples/AGC unit (1.5dB) -  default

  if AGCenable
RMSmin   equ 0.050      ;minimum and maximum RMS values for the front-end AGC
  else
RMSmin   equ 0.010      ;without AGC enabled, the AGCmin and max values serve
			;only to determine when to flash the LEDs.
  endif

RMSmax   equ 0.200      ;do not make these closer than by a factor of 2
                        ;the RMSes are indeed MSes that without the square root

BitFreq  equ 2*1200.0/SampleFreq        ;1200 bits per second
BitLen   equ @cvi(2.0/BitFreq)          ;bit-length in samples
CarFreq  equ 2*1700.0/SampleFreq        ;center freq. = 1700 Hz
CarDev   equ 2*0500.0/SampleFreq        ;deviation = +/- 500 Hz

RxLevelFollow equ 1.0/32    ;Rx Levels and DCD tracking speed (counts per bit)
RxDCDindicate equ 1         ;indicate DCD status with the red LED
RxClockFollow equ 1.0/512   ;Rx bit-clock recovery speed (counts per sample)
                            ;too much is too good...

RxDelayLevelProc equ BitLen*8
RxDelayBitProc   equ BitLen*16+1        ;"1" corrects bit-sample timing

SPY equ 0               ;for spying with SPY.EXE (disables the KISS protocol)

LowFreq = (1700.0-800.0)/SampleFreq     ;receive/transmit filters edges (-6dB)
UppFreq = (1700.0+800.0)/SampleFreq
PassFilterLen equ 64                    ;receive/transmit filters length
                                        ;longer length make sharper filters
                                        ;but introduces longer delays

HardLimiter equ 0                       ;hard limit the signal after the filter
                                        ;and then filter the signal again
                                        ;before FM demodulator
                                        ;if this is present the InternalAGC
                                        ;makes little sense.

InternalAGC equ 1       ;somehow helps (or substitutes) the CODEC-level AGC.

AutoTune equ 1          ;bit threshold automatic adjustment

SmoothTransmit equ 0    ;smooth transmit data before applying to the carrier

RxGain		equ 9.0   ;Line In (initial, if AGC enabled) gain
			  ;Ideal for IC821 9600-baud-mode data output

TxAttenuate     equ 4.5   ;Attenuation factor for Tx audio
MonitorAttenuate     equ 10.5   ;Attenuation factor for Rx/Tx monitor audio

DefTxDelay    equ 20    ;default value for the txdelay (20 => 200 ms)
DefTxTail     equ 3     ;default value for the txtail (3 => 30 ms)
DefTxDuplex   equ 0     ;1 = full duplex mode 
                        ;0 = simplex mode (for normal VHF packet)
DefSlotTime   equ 5	;default value of the slottime, in 10ms units.
DefPersist   equ 63	;default value of the persistence, in 0-255 units.

SciTimerRate equ baud   ;interrupt rate of the SCI timer. This we need
                        ;for proper scaling of txdelay and txtail.
                        ;It is 19200 unless you have messed up the LEONID
                        ;or the SCI itself or if the new LEONID version
                        ;has changed the data/timer rate.

;*************************************************************************
;The actuall code (internal/external program RAM)

        LOMEM P:$0000
        HIMEM P:$1FFF

        org p:user_code

	andi #%11110011,mr      ;scaling bits = 00

        move #$FFFF,m1           ;r1 to address the coeff. of all filters
                                 ;or any other linear structures

        move #Buffer,r2          ;for us to address the input and output samples
        move #<4-1,n2
	move #<BufLen*4-1,m2

        move #RxBitFilterTap,r4    ;r4 to address the inp/out bit filter taps
        move #<(TxBitFilterTap-RxBitFilterTap),n4
        move #<BitFilterLen-1,m4

        move #InpFilterTap,r5    ;r5 to address the inp/out pass filter taps
        move #<(OutFilterTap-InpFilterTap),n5
        move #<PassFilterLen-1,m5

        move #RxProcDelay,r6     ;r6 for the rx process delay
        move #RxDelayBitProc-1,m6
        move #RxDelayLevelProc,n6

        move #Buffer+2,r7        ;r7 for the CODEC's interrupt routine
        move #<BufLen*4-1,m7

        move #BitFreq,x0        ;initialize the bit-sync filter
        jsr <IQ
        move ab,L:<RxSyncFreq
        move ab,L:<RxSyncPhase

        if !SPY
        move #KISSctrl,a1        ;switch serial interface to KISS mode
        move #PTTctrl,b1
        opensc
        endif

; N1OWU:  Activate the relay that switches the analog I/O
; to the so-called 9600 baud FSK port.  It's connected to bit 9 of port B.
; This makes sense for the IC821 radio, which takes line-level Tx audio.

	bset	#9,x:$ffe4 

; N1OWU: Set up the KISS parameters.

      if !SPY

	move	x:<txdelay,x0	    ; set kiss-txdelay from txdelay
	move    #>(SciTimerRate/100),x1	    ; time scale parameter
	mpy	x0,x1,a
	asr	a		    ; integer multiply correction
	move    a0,p:kiss_pars      ; product in low order word

        move    x:<persist,a1         ;set kiss-persistence
        move	a1,p:kiss_pars+1       ; no scaling needed

	move	x:<slottime,x0	    ; set kiss-slottime from slottime
	move    #>(SciTimerRate/100),x1	    ; time scale parameter
	mpy	x0,x1,a
	asr	a
	move    a0,p:kiss_pars+2
        
	move	x:<txtail,x0	    ; set kiss-txtail from txtail
	move    #>(SciTimerRate/100),x1	    ; time scale parameter
	mpy	x0,x1,a
	asr	a
	move    a0,p:kiss_pars+3
        
        move	x:<txduplex,a1         ;set kiss-txduplex
        move	a1,p:kiss_pars+4       ;boolean parameter; no scaling needed
      endif


      if EVM56K         ;for EVM56002 use MIC input
        ctrlcd  1,r2,BufLen,MIC,RxGain,RxGain,LINEO|HEADP,TxAttenuate,MonitorAttenuate
      else              ;for DSPCARD4 use LINE input
        ctrlcd  1,r2,BufLen,LINEI,RxGain,RxGain,LINEO|HEADP,TxAttenuate,TxAttenuate
      endif
        opencd SampleFreq/1000.0,HPF     ;start taking samples at given rate

BatchLoop
	waitblk r2,BufLen,BatchLen      ;wait till enough samples for one batch
					;the following code should use r2
					;for addressing the samples
					;r7,m7 must not be used: SSI interrupts
					;r3,m3 must not be used: SCI interrupts and LEONID code
				
				;compute DC levels
	clr a  r2,x1            ;clear sums, save r2
	clr b  X:(r2)+,x0
	if BatchLen>1
	.loop #BatchLen-1       ;average samples with the batch
	  add x0,a X:(r2)+n2,x0
	  add x0,b X:(r2)+,x0
	.endl
	endif
	add x0,a X:(r2)+n2,x0
	add x0,b
	move x1,r2              ;restore r2

	if BatchLenLog>0
	.loop #BatchLenLog      ;scale the average
	  asr a
	  asr b
	.endl           ;now: a = left DC, b = right DC
	endif

	rnd a #(1.0-1.0/DCrise*BatchLen),y0           ;futher DC filter
	rnd b #1.0/DCrise*BatchLen,y1
	move Y:<DCleft,x0
	mpy x0,y0,a  a,x0
	macr x0,y1,a  Y:<DCright,x0
	mpy x0,y0,b  b,x0
	macr x0,y1,b  a,Y:<DCleft
	move b,Y:<DCright
			;now subtract the DCs from the data
	move a,y0
	move b,y1  X:(r2),a
	move r2,x1      ;save r2
	.loop #BatchLen
	  sub y0,a
	  move a,X:(r2)+
	  move X:(r2),b
	  sub y1,b  X:(r2+n2),a
	  move b,X:(r2)+n2
	.endl
	move x1,r2              ;restore r2

				;sum up the RMSes of both channels
	clr a  r2,x1            ;clear sums, save r2
	clr b  X:(r2)+,x0
	if BatchLen>1
	.loop #BatchLen-1       ;sum up the signal squares
	  mac x0,x0,a X:(r2)+n2,x0
	  mac x0,x0,b X:(r2)+,x0
	.endl
	endif
	mac x0,x0,a X:(r2)+n2,x0
	mac x0,x0,b
	move x1,r2              ;restore r2

	if BatchLenLog>0
	.loop #BatchLenLog      ;divide by BatchLen
	  asr a
	  asr b
	.endl           ;now: a = left RMS, b = right RMS
	endif
	
	rnd a #(1.0-1.0/RMSrise*BatchLen),y0  ; filter these RMSes to get smoother rise/fall
	rnd b #1.0/RMSrise*BatchLen,y1
	move X:<RMSleft,x0
	mpy x0,y0,a  a,x0
	macr x0,y1,a  X:<RMSright,x0
	mpy x0,y0,b  b,x0
	macr x0,y1,b  a,X:<RMSleft
	move b,X:<RMSright

CheckRMSmin                     ;are the RMSes below the minimum required ?
	move #RMSmin,x0
	cmp x0,a
	jcc <CheckRMSmax
	if AGCstereo
	  cmp x0,b
	  jcc <CheckRMSmax
	endif
GainUp      
	if AGCenable
				;if so, increase the CODEC's input gain
	  move X:<AGCcount,a      ;decrement the timeout
	  move #>BatchLen,x0
	  sub x0,a  #>AGCfall,x0
	  move a,X:<AGCcount
	  jgt <CheckRMS_OK        ;leave if not yet zero
	  clr a  x0,X:AGCcount
	  move Y:(r2),a1          ;get the CODEC's input control word
	  move #>$0F0F00,x0
	  and x0,a                ;extract the gain
	  cmp x0,a  #>$010100,x0  ;already maximum ?
	  jeq <Low_RMS_Alert          ;if so flash the red LED
	  add x0,a  #>$F0F000,x0  ;if not, increment the gain by 1
	  move a1,x1
	  move Y:(r2),a1          ;and reload all the control words
	  and x0,a  n2,x0         ;in the output buffer
	  or x1,a  #<4,n2         ;make n2=4 for a moment
	  .loop #BufLen
	    move a1,Y:(r2)+n2
	  .endl
	  move x0,n2              ;restore n2
	  move #0.7071,y0         ;increase the RMSes to follow the gain
	  move X:RMSleft,x0       ;increase faster
	  mpyr x0,y0,a  
	  asl a  X:<RMSright,x0
	  mpyr x0,y0,a a,X:<RMSleft
	  asl a
	  move a,X:<RMSright
	else
  	  jmp <Low_RMS_Alert          ;if so flash the LED
	endif

	jmp <CheckRMS_OK

CheckRMSmax                     ;are the RMSes above the given maximum
	move #>AGChold,x0       ;initialize the AGC hold count-down
	move x0,X:<AGCcount
	move #RMSmax,x0
	cmp x0,a                ;compare left and right RMS
	if AGCstereo
	  jcc <GainDown
	  cmp x0,b
	endif  
	jcs <CheckRMS_OK
GainDown 
	if AGCenable
                      		  ;if the RMSes are too high
	  clr a                   ;decrease the CODEC's input gain
	  move Y:(r2),a1          ;get the CODEC's input control word
	  move #>$0F0F00,x0
	  and x0,a  #>$010100,x0  ;extract the gain bits
	  sub x0,a  #>$F0F000,x0  ;attempt to decrease the gain
	  jcs <High_RMS_Alert          ;jump if overflow
	  move a1,x1
	  move Y:(r2),a1          ;reload all the input control words
	  and x0,a  n2,x0         ;in the buffer with the new input gain
	  or x1,a  #<4,n2         ;n2=4 for a moment
	  .loop #BufLen
	    move a1,Y:(r2)+n2
	  .endl
	  move x0,n2              ;restore n2
	  move #0.7071,y0         ;decrease the RMSes to follow expected
	  move X:<RMSleft,x0       ;gain reduction faster
	  mpyr x0,y0,a  X:<RMSright,x0
	  mpyr x0,y0,a a,X:<RMSleft
	  move a,X:<RMSright
	endif  ; if AGCenable is 0, fall into High_RMS_Alert
  
High_RMS_Alert
        if AGCalert
          UpLED set
        endif
	jmp <CheckRMSend

Low_RMS_Alert
        if AGCalert
          DownLED set
        endif
	jmp <CheckRMSend

CheckRMS_OK
        if AGCalert
          UpLED clr
          DownLED clr
        endif
CheckRMSend
	
Process

	.loop #BatchLen
;Receiver part
          move X:<RxCarPhase,x0  ;get the receiver carrier phase
	  move X:<RxCarFreq,a    ;advance the phase
	  add x0,a
	  move a1,X:<RxCarPhase
	  jsr <IQ               ;compute I and Q (modifies a,b,x,y,r0,m0,n0)
          move a,x1             ;save cosine
          move b,y1             ;save sine

          move X:(r2)+,a        ;get input sample from the left channel
          move a,X:<RxMonitor   ;stash away sample to send to speaker

          if HardLimiter
          move (r5)-n5          ;r5 = pre-filter tap
          move a,Y:(r5)
          move #PassFilterI,r1 ;apply the pre-filter
          nop
          clr a  X:(r1)+,x0 Y:(r5)+,y0
          .loop #PassFilterLen-1
            mac x0,y0,a  X:(r1)+,x0  Y:(r5)+,y0
          .endl
          mac x0,y0,a  #$7FFFFF,x0
          move #<$80,a                  ;hard limit, a = -1.0
          tpl x0,a                      ;unless a was non-negative - then a = +1.0
          move (r5)+n5
          endif

          move a,Y:(r5)         ;place the sample in the input tap
          move #PassFilterI,r1 ;apply the I-filter
          nop
          clr a  X:(r1)+,x0 Y:(r5)+,y0
          .loop #PassFilterLen-1
            mac x0,y0,a  X:(r1)+,x0  Y:(r5)+,y0
	  .endl
          macr x0,y0,a  #PassFilterQ,r1 ;apply the Q-filter
          nop
          clr b  X:(r1)+,x0 Y:(r5)+,y0
          .loop #PassFilterLen-1
            mac x0,y0,b  X:(r1)+,x0  Y:(r5)+,y0
	  .endl
          macr x0,y0,b a,x0
          move b,y0

;          if HardLimiter
;          tst a  #$7FFFFF,x0
;          move #<$80,a                  ;hard limit, a = -1.0
;          tpl x0,a                      ;unless a was non-negative - then a = +1.0
;          tst b
;          move #<$80,b
;          tpl x0,b
;          move a,x0
;          move b,y0
;          endif

          mpy x0,x1,a
          macr y0,y1,a          ;a = demodulated I
          mpy x0,y1,b
          macr -x1,y0,b         ;b = demodulated Q

          move          a,y0
          mpy y0,y0,b   b,y1
          move          X:<RxI,x0
          mpy x0,y1,a   y0,X:<RxI
          mac x0,x0,b   X:<RxQ,x1
          macr -x1,y0,a y1,X:<RxQ  ;a = FM demodulator output
          asr a x1,x0              ;or phase shift between two consecutive I-Q samples
          mac x0,x0,b  y1,y0
          mac y0,y0,b
          asr b
          rnd b                 ;b = amplitude square, = reference for FM output

          if InternalAGC
                                ;average (smooth) the amplitude
          move #1.0-1.0/64,y0   ;a good question is why isn't it smooth yet ?
          move #1.0/64,y1
          move X:<RxAmpl,x0
          mpy x0,y0,b  b,x0
          macr x0,y1,b
          move b,X:<RxAmpl

                        ;scale the output to make it amplitude independent
          move #>$000010,x0     ;minimum reference value
          cmp x0,b
          tlt x0,b
          move b,x0             ;x0 = reference, a = FM out.
          cmpm x0,a             ;a larger than reference ?
          jge <OverRef
          abs a  a2,x1          ;make a positive, save sign in x1
          andi #$FE,ccr         ;"standard" division
          rep #24
            div x0,a
          add x0,a
          move a0,a
          jclr #7,x1,ScaleDone    ;restore the sign
          neg a
          jmp <ScaleDone
OverRef   move #>$7FFFFF,a1       ;a bit tricky...
          move a,x1
          move x1,a
ScaleDone

          endif

          move #RxBitFilter,r1    ;aply the bit-filter
          move a,Y:(r4)         ;put new data sample into the bit-filter tap
          clr a  X:(r1)+,x0 Y:(r4)+,y0  ;filter the Tx data tap
          .loop #BitFilterLen-1
            mac x0,y0,a  X:(r1)+,x0 Y:(r4)+,y0
          .endl
          mac x0,y0,a        ;a=filtered rx data
;          rep #2
          asr a

          if SPY
          move a,X:<RxDebug
          jsr <SpyA
          endif

          move X:<RxDemodOut,x0         ;differenciate to remove mis-tune
          sub x0,a  a,X:<RxDemodOut     ;and save this sample
          rep #3                        ;scale up
            asl a
          move a,x0                     ;square
          mpyr x0,x0,b
          move X:<RxDemodSqr,x0         ;differenciate again to remove
          sub x0,b  b,X:<RxDemodSqr     ;the DC introduced by squaring
          rep #2                        ;scale up again
            asl b

;          if SPY
;          move X:<RxUppLev,a     ;optimal threshold: (lower+upper)/2
;          move X:<RxLowLev,x0
;          add x0,a  X:<RxDemodOut,x1
;          asr a
;          sub x1,a
;          move a,X:<RxDebug     ;** DEBUG **
;          jsr <SpyA
;          endif
                                        ;advance the sync. phase
          move L:<RxSyncPhase,x         ;x1=I, x0=Q
          move L:<RxSyncFreq,y
          mpy x1,y1,a                           ;compute I
          macr -x0,y0,a
          mpy x1,y0,a  a,x1                     ;save I in x1
          macr x0,y1,a  #1.0-RxClockFollow,y0   ;compute Q
          move a,x0                             ;save Q in x0
          mpyr x1,y0,a  #RxClockFollow,y1       ;mult. by decay factor
          mpyr x0,y0,a  a,x1
          move b,x0                             ;add the exciting input to Q
          macr x0,y1,a  Y:<RxSyncPhase,b
          move a,x0
;          move b,X:<RxDebug     ;** DEBUG ** for listenning to the filter output
          tst b  x,L:<RxSyncPhase   ;check for non-positive -> positive transition
          jgt <CheckInterBit
          tst a
          jle <HandleTx         ;jump if not a desired transition
          jset #23,x1,HandleTx  ;jump if I-part negative

;            move X:<RxDemodOut,a
            move X:(r6+n6),a      ;get delayed data
            tst a  #<RxLowLev,r1  ;below or above zero ?
            jlt UpdLevelRMS
              move (r1)+
              move (r1)+
UpdLevelRMS move #1.0-RxLevelFollow,y0   ;update the level
            move #RxLevelFollow,y1
            move X:(r1),x0
            mpy x0,y0,b  a,x0
            macr x0,y1,b
            sub b,a b,X:(r1)+     ;subtract average level
                                  ;compute absolute deviation
            abs a X:(r1),x0       ;update the average deviation
            mpy x0,y0,b  a,x0
            macr x0,y1,b
            move b,X:(r1)

            if AutoTune
            move X:<RxUppLev,a     ;optimal threshold: (lower+upper)/2
            move X:<RxLowLev,x0
            add x0,a  X:<RxDemodOut,x1
            asr a     X:(r6),x0    ;get the sample from the processing queue
            sub x0,a  X:<RxBit,x0  ;subtract threshold from data
                                   ;bit #23,a1 is the data bit (but beware...)
            else
            move X:<RxDemodOut,a
            move X:<RxBit,x0
            endif

            eor x0,a  a,X:<RxBit   ;compare with the previous sample
            jclr #23,a1,BitOut     ;level change ? jump if not

BitOut      not a               ;invert the bit (as for NRZ-S coding)
            rol a               ;carry = received bit
            if !SPY
            putbit              ;pass the bit to the HDLC handler
            endif
            jmp <HandleTx

CheckInterBit
          tst a                 ;positive -> non-positive transition ?
          jgt <HandleTx
          jclr #23,x1,HandleTx  ;I-part negative ? jump if not
;            move X:<RxDemodOut,a
;            move a,X:<RxInterBit
HandleTx

          move X:<RxDemodOut,a  ;still the Rx: put the sample into proc. queue
          move a,X:(r6)-        ;and advance the pointer

          move (r4)+n4          ;switch r4 to the tx bit-filter tap
          move (r5)+n5          ;switch r5 to the output tap

;Transmitter part
;at this point we should generate a new output data sample
          clr a                 ;zero-sample when tx is off
          jclr #0,X:<TxRxState,TxAudioSample

          move X:<TxBitFreq,a   ;advance the bit phase
          move X:<TxBitPhase,x0
          add x0,a
          move a1,X:<TxBitPhase
          jes <NewTxBit

            if SmoothTransmit
            clr a
            jmp <TxSample
            endif

TxNoFlip    move X:<TxBit,a
            jmp <TxSample
NewTxBit
          if SPY
          jmp <TxFlip
          else
;          move #$FFFF,m0        ;getbit needs this (it uses r0 implicitely)
          getbit                ;get next bit to be sent
          endif
          jeq <TxFlip
          jcs <TxNoFlip
TxFlip    move X:<TxBit,a       ;flip if bit=0 or idle pattern
          neg a                 ;would be nice to interpolate here...
          move a,X:<TxBit       ;but it's not that important for lower rates
TxSample                        ;a = new data sample (+1 or -1)
          if SmoothTransmit
          move #TxBitFilter,r1    ;aply the bit-filter
          move a,Y:(r4)         ;put new data sample into the bit-filter tap
          clr a  X:(r1)+,x0 Y:(r4)+,y0  ;filter the Tx data tap
          .loop #BitFilterLen-1
            mac x0,y0,a  X:(r1)+,x0 Y:(r4)+,y0
          .endl
          macr x0,y0,a        ;a=filtered data (+/- 1)
          endif

          move a,x0
          move #CarDev,y0
          mpyr x0,y0,a #CarFreq,x0  ;a = instant carrier deviation
          add x0,a
          move a,X:<TxCarFreq   ;update the instant carrier frequency
          move X:<TxCarPhase,x0 ;get the carrier phase
          add x0,a              ;and advance it
          move a1,X:<TxCarPhase
          jsr <IQ               ;compute the sin/cos (modifies a,b,x,y,r0,m0,n0)
TxAudioSample
          move #PassFilterI,r1 ;apply the I-filter
          move a,Y:(r5)         ;put output into the filter tap
          clr a  X:(r1)+,x0 Y:(r5)+,y0
          .loop #PassFilterLen-1
            mac x0,y0,a  X:(r1)+,x0  Y:(r5)+,y0
	  .endl
          macr x0,y0,a          ;a = filtered output
          asr a
          move a,Y:(r2)+        ;put it to the output (left channel)
          if SPY
           move X:<RxDebug,a ;** DEBUG **
          else
           move X:<RxMonitor,x0
           add x0,a		;mix the Tx and Rx audio for monitoring
          endif
          move a,Y:(r2)+        ;the right (speaker/headphone) channel
	  move (r2)+
          move (r5)-n5          ;switch r5 back to the input tap
          move (r5)-            ;advance the input/output tap pointer
          move (r4)-n4          ;switch r4 back to the rx bit-filter tap
          move (r4)-            ;advance the bit filter tap pointer
        .endl
                ;DCD decision based on eye-opening
        move X:<RxLowLev,x0             ;see the difference between 0 and 1 levels
        move X:<RxUppLev,a
        sub x0,a X:<RxLowMS,x1
        abs a X:<RxUppMS,b              ;a = |upp-low|
        add x1,b  a,x1                  ;b = MS(low)+MS(upp)
        if HardLimiter
          asl b
        endif
;        asl b                  ;add more "asl b" if the DCD is too sensitive
;        asl b
        move X:<RxSyncPhase,x0  ;sum-up the energy in the clock-sync filter
        mpy x0,x0,a  Y:<RxSyncPhase,x0
        mac x0,x0,a
        jset #1,X:<TxRxState,CheckCarOff
          asl b    #>$000020,x0
          cmp x1,b              ;eye open ?
          jge <CheckCarEnd
          cmp x0,a              ;enough clock signal ?
          jlt <CheckCarEnd
          if !SPY
          caron
          endif
          bset #1,X:<TxRxState
          if RxDCDindicate
            DCDLED set
          endif
          jmp <CheckCarEnd
CheckCarOff
        cmp x1,b   #>$000010,x0
        jge <SetCarOff
        cmp x0,a
        jge <CheckCarEnd
SetCarOff
        if !SPY
        caroff
        endif
        bclr #1,X:<TxRxState
        if RxDCDindicate
          DCDLED clr
        endif
CheckCarEnd

; N1OWU:  Sample the SCI Tx data line and turn on/off the yellow LED
; accordingly, to show when data is being sent out the serial port.
; (This ought to be done in Leonid...)

	btst	#1,x:$ffe5   ; Port C data register
	.if <cs>
	  YellowLED clr
	.else
	  YellowLED set
	.endi

; Do the same for the Rx data line and the orange LED.

	btst	#0,x:$ffe5   ; Port C data register
	.if <cs>
	  OrangeLED clr
	.else
	  OrangeLED set
	.endi


	jmp     <BatchLoop

PI      equ     3.14159265358979323846
EX      equ     2.718281745911

;this routine computes a cosine/sine pair using the sine ROM
;with a second order (linear+quadrature) approximation between table points
IQ                              ;x0 = angle ( -1 = -PI, +1 = +PI)
        move #>$80,x1   ;shift out 8 most significant bits
        mpy x0,x1,a  #>$FF,x0
        ori #%00000011,mr       ;disable interrupts
        move x0,m0
        and x0,a     #>$100,x0
        or x0,a      #<$40,n0
        ori #%00000100,omr      ;enable the sine ROM table
        move a1,r0      ;put the 8 most significant bits into r0 with offset = $100
        move a0,y0      ;save the remaining bits in y0
        jclr #23,y0,SinTable
          move (r0)+
SinTable
        move Y:(r0+n0),x0       ;x0 = coarse cosine
        move Y:(r0),x1          ;x1 = coarse sine
        mpyr x1,y0,a  #PI/256.0,y1
        tfr x0,a  a,x1
        macr -x1,y1,a           ;a = fine cosine
        mpyr x0,y0,b  Y:(r0),x1
        andi #%11111011,omr     ;disable the sine ROM table
        tfr x1,b  b,x1
        macr x1,y1,b  #PI*PI/2.0/65536.0,y1  ;b = fine sine
        mpyr y0,y0,a  a,x0
        andi #%11111100,mr      ;enable interrupts
        move a,y0
        mpyr y0,y1,a
        tfr x0,a  a,y1
        macr -x0,y1,a  b,x1     ;a = super fine cosine
        macr -x1,y1,b           ;b = super fine sine
        rts                     ;x,y are modified
                                ;r0,m0,n0 are modified
                                ;maximum error is about 0.7E-6
                                ;execution time 4+64+4 clock cycles
                                ;including "jsr <IQ" and "rts"
;enable/disable interrupts must be there if interrupt routines
;are accesing RAM at Y:$100..$1FF (like CODEC input/output buffers)

; KISS control frame handling - called by LEONID when a KISS control-type
; frame is received with non-standard parameters
KISSctrl rts

; transmitter PTT control - a routine called by LEONID at the right moments.
PTTctrl jcc PTT_off
        bset #0,X:$FFE4
        bset #0,X:<TxRxState
        rts
PTT_off bclr #0,X:$FFE4
        bclr #0,X:<TxRxState
        rts

        if SPY

SpyA    move a10,L:<SpySave
        move a2,X:<SpySave+1
        move x0,Y:<SpySave+1
        move x1,Y:<SpyCount
        move X:<SpyCount,a
        tst a
        jne <Spy_copy

Spy_check
        lookc
        jcs <Spy_end
        move #>'S',a
        cmp x0,a
        jne <Spy_end
        move #>'P',x0
        putc
        move #>512,a
Spy_copy
        move #>1,x0
        sub x0,a
        move a,X:<SpyCount

        move X:<SpySave,a
	rep	#8
	lsr	a
	move	a1,x0
	putc
        move X:<SpySave,a
	rep	#16
	lsr	a
	move	a1,x0
        putc

Spy_end move L:<SpySave,a10
        move X:<SpySave+1,a2
        move Y:<SpySave+1,x0
        move Y:<SpyCount,x1
        rts

        endif

LastP = *

;*************************************************************************
;Internal data RAM

        LOMEM X:$0000,Y:$0000,L:$0000
        HIMEM X:$00FF,Y:$00FF,L:$00FF

        org L:user_data

        if SPY
SpySave dc 0,0
SpyCount dc 0
        endif

RxSyncFreq dc 0
RxSyncPhase dc 0
LastL = *

        org X:LastL
        org Y:LastL

        org Y:
DCleft  dc 0            ;DC bias for both channels
DCright dc 0
	
        org X:
RMSleft  dc 0           ;RMS (energy) for both channels
RMSright dc 0

	org X:
AGCcount dc 0           ;counter for the AGC hold-off

        org X:
TxCarFreq  dc CarFreq   ;transmitter carrier frequency and phase
TxCarPhase dc 0

TxBitFreq  dc BitFreq   ;transmitter bit rate and phase
TxBitPhase dc 0

TxRxState  dc 0         ;bit #0: 1 = transmitter on, 0 = off
                        ;bit #1: 1 = channel busy, 0 = channel free
TxBit      dc $7FFFFF

        org X:
RxCarFreq   dc CarFreq
RxCarPhase  dc 0

RxI dc 0        ;most recent I-Q vector
RxQ dc 0
RxAmpl dc 0     ;amplitude (mean square) of the FM demod. output

RxDemodOut  dc 0
RxDemodSqr  dc 0

RxBit       dc 0
RxInterBit  dc 0

RxLowLev    dc 0        ;keeps levels and deviations for the received symbols
RxLowMS     dc 0
RxUppLev    dc 0
RxUppMS     dc 0

RxDebug     dc 0.25     ;for debug purposes only
RxMonitor   dc 0.25     ;for speaker output

txdelay dc DefTxDelay	;runtime-patchable
txtail dc DefTxTail	;runtime-patchable
txduplex dc DefTxDuplex	;runtime-patchable
slottime dc DefSlotTime	;runtime-patchable
persist dc DefPersist	;runtime-patchable


;PI      equ     3.141592654

BitFilterLen equ 16

        org X:
TxBitFilter

time = -@cvf(BitFilterLen/2)+0.5
count   set 0
        dup BitFilterLen
Filter = @cos(PI*time/@cvf(BitFilterLen))
time = time+1.0
        dc  Filter*Filter
count   set count+1
        endm

RxBitFilter

sigma = @cvf(BitFilterLen)/2.0/6.0

time = -@cvf(BitFilterLen/2)+0.5
count   set 0
        dup BitFilterLen
Filter = @pow(EX,-time*time/(2.0*sigma*sigma))
time = time+1.0
        dc Filter
count   set count+1
        endm

;time = -@cvf(BitFilterLen/2)+0.5
;count   set 0
;        dup BitFilterLen
;Filter = 0.75*@cos(PI*time/@cvf(BitFilterLen))+0.25*@cos(3.0*PI*time/@cvf(BitFilterLen))
;time = time+1.0
;        dc  Filter
;count   set count+1
;        endm

;*************************************************************************
;External data RAM

        if EVM56K
          LOMEM X:$0100,Y:$0100,L:$0100
          HIMEM X:$3FFF,Y:$3FFF,L:$3FFF
        else
          LOMEM X:$0100,Y:$0100,L:$0100
          HIMEM X:$1FFF,Y:$3FFF,L:$1FFF
        endif

      if EVM56K
        org L:$2000
      else
        org L:$200
      endif

Buffer   dsm BufLen*4   ;CODEC's input/output buffer
LastL = *

; DB: Put the external X data right after the P (program) data.
; The dl.exe program does not like to load data into X space above 2000...

        org X:@cvs(X,LastP)

PassFilterI  dsm  PassFilterLen

PassFilterQ  dsm  PassFilterLen

sigma = @cvf(PassFilterLen)/2.0/3.0

	org	X:PassFilterI

time = -@cvf(PassFilterLen/2)+0.5
count   set 0
        dup PassFilterLen
Window = @pow(EX,-time*time/(2.0*sigma*sigma))
Filter = (@sin(2.0*PI*time*UppFreq)-@sin(2.0*PI*time*LowFreq))/(PI*time)
time = time+1.0
        dc  Window*Filter
count   set count+1
        endm


	org	X:PassFilterQ

time = -@cvf(PassFilterLen/2)+0.5
count   set 0
        dup PassFilterLen
Window = @pow(EX,-time*time/(2.0*sigma*sigma))
Filter = (-@cos(2.0*PI*time*UppFreq)+@cos(2.0*PI*time*LowFreq))/(PI*time)
time = time+1.0
        dc  Window*Filter
count   set count+1
        endm

        org X:
RxProcDelay dsm RxDelayBitProc

        org Y:LastL
        if HardLimiter
InpPreFilterTap  dsm PassFilterLen
        endif
InpFilterTap  dsm PassFilterLen
OutFilterTap  dsm PassFilterLen

RxBitFilterTap dsm BitFilterLen
TxBitFilterTap dsm BitFilterLen

	end

;*************************************************************************

