;
; $Header: C:\\ham\\evm/RCS/p3d.asm,v 1.4 2000/10/05 16:59:04 dbraun Exp dbraun $
;
; $Log: p3d.asm,v $
; Revision 1.4  2000/10/05 16:59:04  dbraun
; Works!  Problem is that adjusting Rx phase messes up Tx phase.
; Problem also exists in g3ruh.asm.
;
; Revision 1.3  2000/10/05 16:17:53  dbraun
; Added more SPYs.  Realized true cause of problems.
;
; Revision 1.2  2000/10/04 13:57:20  dbraun
; Should work, but doesn't :-(
;
; Revision 1.1  2000/10/02 17:04:06  dbraun
; Initial revision
;
;
; Initialized from:
; C:\\ham\\evm/RCS/g3ruh.asm
; Revision 1.9  2000/08/03 19:17:47  dbraun
;

	page	132,79
	opt	rc
	opt	mu
	title	'G3RUH-style P3D telemetry demodulator'

;***************************************************************
;* This program is derived from:
;*  G3RUH.ASM -- 9600 bit/s G3RUH modem			       *
;*							       *
;* 9600 bit/s two level PAM demodulation with fixed rate       *
;* sampling and adaptive equalizer.			       *
;*							       *
;* PAM demodulation is based on some ideas presented in book   *
;*	Lee, E., A., Messerschmitt, D., G.:		       *
;*	"Digital Communication",			       *
;*	Kluwer, 1988					       *
;*							       *
;* Symbol synchronization is base on the article	       *
;*	Mueller, K., H., Mller, M.:			       *
;*	"Timing Recovery in Digital Synchronous Data           *
;*	Receivers",                                            *
;*	IEEE Trans. on Comm., Vol. COM-23, No. 5, May 1976     *
;*							       *
;* This module uses registers as follows:		       *
;*  r0 - general purpose (temporary use)		       *
;*  r1 - general purpose (temporary use)		       *
;*  r2 - sample buffer pointer				       *
;*  r3 - pointer into buffer for xmit data  (NA1DB)
;*  r4 - output filter pointer				       *
;*  r6 - general purpose (temporary use)		       *
;*							       *
;* Copyright (C) 1993-1996 by Alef Null. All rights reserved.  *
;* Author(s): Jarkko Vuori, OH2LNS			       *
;* Modification(s):					       *
;*	01.01.1996  Added support for both left and right      *
;*		    channels				       *
;*	22.03.1996  Modified for the new SPY program	       *
;***************************************************************

; User controllable parameters
SPY	set	0	    ; Set to 1 for SPY mode.
SpyS2  set	0	    ; Spy on the s2 variable.
SpyS1  set	0	    ; Spy on the s1 variable.
SpyAgc set	0	    ; Spy on AGC gain
SpyD1 set	0	    ; Spy on RX data signal, post-decision
SpyRxData set	0	    ; Spy on final RX data bits
SpyTxSig  set	0	    ; Spy on TX data signal, post-scrambler
SpyEyeFilt  set	0	    ; Spy on DCD eye filter variable

OLevel	set	0.10	    ; output signal level (in rms volts)
RxGain  set     9.0        ; Codec input gain in dB, set to match radio.
RightAudioChannel set  0    ; left codec channel (0)/right codec channel (1)
DoOutput  set   1          ; Set to non-zero if we should "transmit" data.
			; If set to 0, we will transmit all 1 (mark) bits.
			; Note: The PTT line is ever actually activated.
SCIBaudRate    set  9600 ; The SCI baud rate is reset only if necessary.
SendFixedMessage  set  1   ; If set, a canned message will be transmitted,
			   ; and the SCI RX data will be left alone.

	nolist
	include 'leonid'
	include 'ioequlc'
	list


; Program parameters
buflen	equ	256				    ; codec sample buffer length

fs	equ	48000.0 			    ; sampling frequency
fd	equ	9600.0				    ; decision rate
I	equ	2				    ; interpolation factor
D	equ	5				    ; decimation factor 48k*(I/D)=19.2k

; AGC
refLev	equ	0.6				    ; reference level for AGC
agcN	equ	15				    ; agc analyse block length
agcGain equ	100.0				    ; agc integrator gain

; symbol synchro
rwfc0	equ	0.99				    ; random walk filter coefficient (carrier off)
rwfc1	equ	0.3				    ; random walk filter coefficient (carrier on)

; DCD
DCDFil	equ	0.02				    ; decision error IIR LPF coefficient
DCDN	equ	@cvi(0.3*fd+.5) 		    ; delay (in s) after DCD off

; G3RUH specific
poly	equ	$10800				    ; G3RUH,K9NG scrambler polynomial (x^17 + x^12 + 1)

; flags
car	equ	1				    ; carrier on/off

; ASCII constants
LF      equ 10
CR      equ 13

; macro for DCD output handling
; O2 for both channels
carled	macro	mode
	b\mode	#13,x:$ffe4   ; N1OWU: was bit 2, I made bit 13 to match bpsk1200
	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



; first order high-pass IIR filter
; input in a, output in a
; frq is -3dB point (fc/fs)
hpass	macro	acc,frq,store
c1	set	-1.0/@cos(6.28319*frq)*(1.0-@sin(6.28319*frq))
	move		    acc,x0
	move		    #>(1.0-c1)/2.0,y0
	mpyr	x0,y0,acc   x:<store,y0
	move		    acc,x:<store
	sub	y0,acc	    x:<store+1,x0
	move		    #>c1,y0
	macr	-x0,y0,acc
	move		    acc,x:<store+1
	endm


	org	P:user_code

	move		    #buffer+2,r7	    ; codec sample buffer ptr
	move		    #buflen*4-1,m7

	move		    #buffer,r2		    ; sample buffer read ptr
	move		    #4-1,n2
	move		    #buflen*4-1,m2

	if RightAudioChannel
	move		    #buffer+(buflen*2)+2,r3 ; sample buffer write ptr
	else
	move		    #buffer+(buflen*2)+1,r3 ; sample buffer write ptr
	endif

	move		    #4,n3
	move		    #buflen*4-1,m3

	move		    #filtx,r4		    ; transmit filter ptr
	move		    #oftaps-1,m4


; fs = 48 kHz, mike input, line output.
; N1OWU: Attenuate the unused output channel.  The Olevel define
; controls the output level by scaling the output data.
	if RightAudioChannel
	  ctrlcd	1,r2,buflen,MIC,RxGain,RxGain,LINEO|HEADP,94.5,0.0
	else
	  ctrlcd	1,r2,buflen,MIC,RxGain,RxGain,LINEO|HEADP,0.0,94.5
	endif

	opencd	fs/1000.0,NOHPF
	

	; Disconnect the SCI Tx data signal, so we can control it directly.
	if !SPY
	  bclr #12,X:m_scr   ; disable SCI Tx interrupts		  
	  bclr #1,X:m_pcc    ; disconnect the SCI TxD from port C
	  bset #1,X:m_pcddr  ; make the former TxD pin an output
	endif

	; If desired, set the SCI baud rate to 9600 so we can get
	; 9600 baud xmit data from the serial port.  Remember that
	; the RX data is asynchronous, and does not use the SCI at all!

	; If needed, set new baud rate (code copied from LEONID).
	; Only if we are actually reading data from the SCI
	if DoOutput&&!SendFixedMessage&&!SPY
	 jclr #0,X:m_ssr,*  ;wait until any existing data is sent out	  
	 movep #(xtal+2*16*SCIBaudRate)/(2*2*16*SCIBaudRate)-1,X:m_sccr
	endif								  


; N1OWU:  Activate the relay that switches the analog I/O
; to the 9600 baud FSK port.  It's connected to bit 9 of port B.

	if @def('N1OWU')
	  bset	#9,x:$ffe2    ; Make bit 9 an output
	  bset	#9,x:$ffe4 
	endif


; Main Loop:
; wait for D samples
loop	waitblk r2,buflen,D
	if RightAudioChannel
	move		    (r2)+
	endif

	move		    #buflen*4-1,m0
	move		    #-4,n0

; calculate even phase
	move		    x:<eadr,r6
	move		    r2,r0
	clr	a
	move		    x:(r0)+n0,x0  y:(r6)+,y0
	rep	#iftaps-1
	mac	x0,y0,a     x:(r0)+n0,x0  y:(r6)+,y0
	macr	x0,y0,a     #ocoeffs,r1

; high-pass filter the signal (to reject CS4215 offset)
	hpass	a,5.0/(2.0*fd),hpf		    ; fc = 5 Hz

; AGC
	move	a,x0
	move		    y:<agc,y0
	mpy	x0,y0,a
	rep	#8
	asl	a
	move		    a,y:<s2

	if SPY&&SpyS2
	  jsr	<SpyA
	endif

; calculate two first phases of the output filter
	clr	b	    (r2)+
	move		    x:(r1)+,x0	  y:(r4)+,y0
	rep	#oftaps-1
	mac	x0,y0,b     x:(r1)+,x0	  y:(r4)+,y0
	macr	x0,y0,b
	move		    b,y:(r3)+n3
	move		      (r2)+n2

	clr	b	    (r2)+
	move		    x:(r1)+,x0	  y:(r4)+,y0
	rep	#oftaps-1
	mac	x0,y0,b     x:(r1)+,x0	  y:(r4)+,y0
	macr	x0,y0,b
	move		    b,y:(r3)+n3
	move		      (r2)+n2

; calculate odd phase
	move		    x:<oadr,r6
	move		    r2,r0
	clr	a
	move		    x:(r0)+n0,x0  y:(r6)+,y0
	rep	#iftaps-1
	mac	x0,y0,a     x:(r0)+n0,x0  y:(r6)+,y0
	macr	x0,y0,a

; high-pass filter the signal (to reject CS4215 offset)
	hpass	a,5.0/(2.0*fd),hpf		    ; fc = 5 Hz

; AGC
	move	a,x0
	move		    y:<agc,y0
	mpy	x0,y0,a
	rep	#8
	asl	a
	move		    a,y:<s1

	if SPY&&SpyS1
	  jsr	<SpyA
	endif

; make decision (for symbol synchro)
	tst	a	    #refLev,x0
	tpl	x0,a
	move		    #-refLev,x0
	tmi	x0,a

	move		    y:<d1,x0		    ; update decisions
	move		    x0,y:<d3
	move		    a,y:<d1

; calculate three last phases of the output filter
	clr	b	    (r2)+
	move		    x:(r1)+,x0	  y:(r4)+,y0
	rep	#oftaps-1
	mac	x0,y0,b     x:(r1)+,x0	  y:(r4)+,y0
	macr	x0,y0,b
	move		    b,y:(r3)+n3
	move		      (r2)+n2

	clr	b	    (r2)+
	move		    x:(r1)+,x0	  y:(r4)+,y0
	rep	#oftaps-1
	mac	x0,y0,b     x:(r1)+,x0	  y:(r4)+,y0
	macr	x0,y0,b
	move		    b,y:(r3)+n3
	move		      (r2)+n2

	clr	b	    (r2)+
	move		    x:(r1)+,x0	  y:(r4)+,y0
	rep	#oftaps-1
	mac	x0,y0,b     x:(r1)+,x0	  y:(r4)+,y0
	macr	x0,y0,b     (r4)-
	move		    b,y:(r3)+n3
	move		      (r2)+n2

; AGC control
	move		    y:<s1,a
	if RightAudioChannel
	abs	a	    (r2)-
	else
	abs	a
	endif
	move		    y:<agcmax,x0
	cmp	x0,a	    #>1,x1
	tlo	x0,a
	move		    a,y:<agcmax

	move		    y:<agcn,a		    ; if one block searched
	sub	x1,a	    #>agcN,x1
	move		    a,y:<agcn
	jne	<_agc

	move		    x1,y:<agcn		    ; calculate error and filter it
	clr	b	    y:<agcmax,a
	move		    b,y:<agcmax
	move		    #refLev+refLev/3,b

	sub	a,b	    #>@pow(2,-5)*agcGain/(fs/agcN),x1  ; rectangular integration
	move	b,x0
	move		    y:<agc,a
	macr	x0,x1,a
	abs	a
	move		    a,y:<agc
_agc
	if SPY&&SpyAgc
	  move		    y:<agc,a
	  jsr	<SpyA
	endif

; Symbol synchro
	move		    y:<d1,a		    ; z = (d3 - d1) * s2
	move		    y:<d3,x0
	sub	x0,a	    y:<s2,x0
	move	a,x1
	mpyr	x0,x1,a     y:<rwf,x0
	move	a,x1

	move		    y:<rwfilt,b 	    ; filter the zero crossing
	macr	x0,x1,b     #0.3,x0

	cmpm	x0,b				    ; check if limits reached
	jlt	<_sync2
	tst	b
	jpl	_sync1
	jsr	<retard
	clr	b
	jmp	_sync2
_sync1	jsr	<advance
	clr	b
_sync2	move		    b,y:<rwfilt
_sync3

; unscramble symbol, result in C
	move		    y:<d1,a		    ; databit to C

	if SPY&&SpyD1
	  jsr	<SpyA
	endif

	asl	a
	move		    x:<usrem,b1
	jcc	<unscram
	move		    #>(poly<<1)|1,x0
	eor	x0,b
unscram lsr	b
	move		    b1,x:<usrem

; make symbol decision (with NRZ-S decoding), databit in C, result in C
	rol	b	    y:<prvrsym,x0
	eor	x0,b	    b1,y:<prvrsym
	not	b
	lsr	b

; Send bit directly out TxD line
; Light the yellow LED when a non-mark (i.e. 0) bit is present.
	.if <cs>
	  if !SPY
	    bset #1,X:m_pcd
	  endif
	  if SPY&&SpyRxData
	    move    #0.5,a
	    jsr	<SpyA
	  endif
	  YellowLED clr
	.else
	  if !SPY
	    bclr #1,X:m_pcd
	  endif
	  if SPY&&SpyRxData
	    move    #-0.5,a
	    jsr	<SpyA
	  endif
	  YellowLED set
	.endi

; Get next bit to be sent (in C flag)
	if 	DoOutput
	  jsr  	AsyncEncode
	else
	  ; Send a continuous 1  (mark) bit.
          ori #$01,ccr    ; Set carry
	endif

_gb1	rol	a	    y:<prvxsym,x0	    ; NRZ-S coding
	not	a
	eor	x0,a
	ror	a	    a1,y:<prvxsym

	clr	a				    ; scrambler, databit in C flag, result in a (-1 or 1)
	rol	a	    x:<srem,x0
	eor	x0,a	    #-OLevel,x1
	lsr	a	    #>poly,x0
	jcc	<_gb2
	eor	x0,a
_gb2	move		    a1,x:<srem
	move		    #OLevel,a
	tcc	x1,a

_out1	move		    a,y:(r4)	    ; put bit to output filter

	if SPY&&SpyTxSig
	  jsr	<SpyA
	endif

; calculate eye opening
	move		    y:<d1,b
	move		    y:<s1,x0
	sub	x0,b
	abs	b	    #DCDFil,x1

; filter it (with first order IIR filter)
	move	b,x0
	mpy	x0,x1,b     #(1.0-DCDFil),x0
	move		    y:<eyefilt,x1
	macr	x0,x1,b
	move		    b,y:<eyefilt

; and make decision if carrier detected
	jset	#car,x:<flag,_caron
	move		    #0.16+0.03,x0	    ; check if carrier appeared
	cmp	x0,b	    #>DCDN,x0
	jgt	_car2

	move		    x0,y:<DCDn

	move		    #rwfc1,x0
	move		    x0,y:<rwf
	carled	set
	bset	#car,x:<flag
	jmp	<_car2

_caron	move		    #0.16-0.03,x0	    ; check if carrier disappeared
	cmp	x0,b	    y:<DCDn,b
	jlt	_car2

	move		    #>1,x0		    ; no carrier after DCDN symbols
	sub	x0,b	    #rwfc0,x0
	move		    b,y:<DCDn
	jne	_car2

	move		    x0,y:<rwf
	carled	clr
	bclr	#car,x:<flag
_car2
	if SPY&&SpyEyeFilt
	  move		    y:<eyefilt,a
	  jsr	<SpyA
	endif

; Have the orange LED indicate the sate of the Tx data.

	btst	#0,x:$ffe5   ; Port C data register
	.if <cs>
	  OrangeLED clr
	.else
	  OrangeLED set
	.endi


; all this again, sigh!
	jmp	<loop


; retard sampling
retard	move		    x:<eadr,a		    ; select previous filter tap set
	move		    #>iftaps,x0
	sub	x0,a	    #>icoeffs,x0
	cmp	x0,a	    #>ifbnks*iftaps,x0
	jhs	<_adv1
	move		    (r2)+
	add	x0,a	    (r2)+n2		    ; jumped over the first taps set
_adv1	add	x0,a	    a,x:<eadr		    ; store those new tap set addresses
	move		    a,x:<oadr
	rts

; advance sampling
advance move		    x:<eadr,a		    ; select next filter tap set
	move		    #>iftaps,x0
	add	x0,a	    #>icoeffs+ifbnks*iftaps,x0
	cmp	x0,a	    #>ifbnks*iftaps,x0
	jlo	<_ret1
	move		    (r2)-
	sub	x0,a	    (r2)-n2		    ; jumped over the first taps set
_ret1	add	x0,a	    a,x:<eadr		    ; store those new tap set addresses
	move		    a,x:<oadr
	rts


; Return the next bit to be xmitted, either from the SCI input
; data or from a canned message.

AsyncEncode
        move X:AsyncEncodeBitCount,b
        tst b		X:AsyncEncodeShiftReg,a1
        .if <eq>
	  ; Get a new char, and return a zero async start bit.
          jsr <ReadChar
          .if <cc>
            move #>9,b   ; Send (after start bit) 8 data + 1 stop bits.
            move b,X:AsyncEncodeBitCount
            bset #8,a1    ; Set the stop bit
            move a1,X:AsyncEncodeShiftReg
            andi #$FE,ccr    ; Clear carry
	    rts     ;carry = next bit to be transmitted
          .endi
	  ; If C=1, there is no new data, and we will keep
	  ; sending a stop bit (1) until new data arrives.
        .else
	  ; Return next bit of current char.
          move #>1,x0
          sub x0,b
          lsr a1 b,X:AsyncEncodeBitCount
          move a1,X:AsyncEncodeShiftReg
	  rts     ;carry = next bit to be transmitted
        .endi

ReadChar
      if SendFixedMessage
        move #$1FFFF,m0
        move X:FixedMessagePtr,r0
        clr a
        move Y:(r0)+,x0
        eor x0,a
        .if <eq>
          move #FixedMessage,r0
          nop
          move Y:(r0)+,x0
        .endi
        move r0,X:FixedMessagePtr
        andi #$FE,ccr    ; Clear carry
      else
        if SPY
          ori #$01,ccr    ; Set carry: No character
	else
	  lookc 0
	endif
      endif
      .if <cc>
	move x0,a1
	;andi #$FE,ccr    ; Clear carry
      .endi
      rts     ;carry = 0 if new character has been read from the terminal
	      ;a1 = new character


        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 0
        jcs <Spy_end
        move #>'S',a
        cmp x0,a
        jne <Spy_end
        move #>'P',x0
        putc
        move #>0,x0      ; send a 0 meaning non-complex data
        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

        org X:user_data
        org Y:user_data

	org	X:

n	dc	8

hpf	ds	2				    ; data storage for the input DC-cancellation IIR filter

eadr	dc	icoeffs 			    ; input polyphase filter even bank pointer
oadr	dc	icoeffs+ifbnks*iftaps		    ; input polyphase filter odd  bank pointer

srem	ds	1
usrem	ds	1

flag	dc	0

	include 'filtx.asm'

AsyncEncodeShiftReg dc 0
AsyncEncodeBitCount dc 0

        if SendFixedMessage
FixedMessagePtr dc FixedMessage
        endif


LastX = *

	org	Y:

s1	ds	1
d1	ds	1
s2	ds	1
d3	ds	1

rwfilt	dc	0
eyefilt dc	0
DCDn	dc	DCDN
rwf	dc	rwfc0

prvrsym ds	1				    ; previous received symbol
prvxsym ds	1				    ; previous xmitted symbol

agc	dc	@pow(2,-7)
agcn	dc	agcN				    ; agc block counter
agcmax	dc	0

filtx	dsm	oftaps

	include 'filtr.asm'

	if SendFixedMessage
          opt nops
FixedMessage
             dc '##############################################',CR,LF
             dc 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()',CR,LF
             dc 'P3D Launch Telemetry decoder program by NA1DB',CR,LF
             dc 0
	endif

LastY = *

        if @cvi(LastX)>=@cvi(LastY)
          org L:LastX
        else
          org L:LastY
        endif

        if SPY
SpySave dc 0,0
SpyCount dc 0
SpyTmp   ds 1
        endif

buffer	dsm	buflen*4

	end
