;***************************************************************************
;   trainer.asm           4 Channel Trainer Cord
;***************************************************************************
;            Bruce Abbott   bhabbott@paradise.net.nz 
;
;        for Microchip PIC 12C508/9 or 12F629/75.
;
;=============================== Description =================================
;
;The Problem:
; 
; Hitec/Futaba, JR/GWS, and Sanwa/Airtronics have different channel assignments. 
;
;The Solution:
;
; Use a PIC to capture, store, and regenerate channels in the correct order.
;
;=============================================================================
;                             Summary of Changes
;
; 2004/10/16 V0.0 - Created from rxdecode.asm 
; 2005/01/22 V0.1 - Separate inputs and outputs for JR Futaba.
; 2008/04/21 V0.2 - Inverted output for JR
; 2008/04/25 V0.3 - Airtronics option 
;
; -----------------------------------------------------------------------------

#DEFINE version  "0.3"

; Enable ONE of the three options below!

;#DEFINE GWS  1	 ; GWS 	
#DEFINE JR   1  ; JR 	    
;#DEFINE AIR  1	 ; Airtronics	
		
;#DEFINE OSC_CAL 0x70	; enable if OSCCAL value was erased!

; Make sure that PROCESSOR and Include files are compatible with your CPU!
; 12C508(A) and 12C509(A) can use 12C508 definitions. In MPLAB, you should
; select the menu option <Configure/Select Device>. Otherwise, enable _one_ 
; of the following defines...  

;#DEFINE __12C508    ; Also for 12C509
;#DEFINE __12F629	 
;#DEFINE __12F675

	ifdef	  __12C508
	PROCESSOR PIC12C508
        INCLUDE   <P12C508.inc>
	__CONFIG  _MCLRE_OFF&_CP_OFF&_WDT_ON&_IntRC_OSC	
	endif

	ifdef	  __12F629
        PROCESSOR PIC12F629
        INCLUDE   <P12F629.inc>
	__CONFIG  _MCLRE_OFF&_CP_OFF&_WDT_ON&_BODEN_ON&_INTRC_OSC_NOCLKOUT
	endif

	ifdef	  __12F675
        PROCESSOR PIC12F675
        INCLUDE   <P12F675.inc>
	__CONFIG  _MCLRE_OFF&_CP_OFF&_WDT_ON&_BODEN_ON&_INTRC_OSC_NOCLKOUT
	endif

        radix     dec

	errorlevel 0,-305,-302


; Bit definitions for the GPIO register and the TRIS register

#DEFINE ft_out   0    ; GP0/DAT  pin 7   Futaba/Hitec output
#DEFINE jr_out	 1    ; GP1/CLK  pin 6   JR/GWS/Airtronics output  
#DEFINE SW_1     2    ; GP2      pin 5   direction switch 
#DEFINE jr_in	 3    ; GP3/MCLR pin 4   JR/GWS/Airtronics input 
#DEFINE ft_in    4    ; GP4      pin 3   Futaba/Hitec input
#DEFINE LED	 5    ; GP5      pin 2   Status LED

#DEFINE TrisBits H'FF'&~((1<<jr_out)|(1<<ft_out)|(1<<LED)) ; outputs

;--------------------------------------------------------------------------
;             Bits to be set with the OPTION instruction
;--------------------------------------------------------------------------
; bit             function                state
;------   -------------------------   --------------
;  7        Weak Pullups (12F6xx)       1=inactive 
;  6        Weak Pullups (12C50x)       1=inactive
;  5          Tmr0 clock source         0=internal
;  4          Tmr0 clock edge           0=low->high        
;  3         Prescaler assign           0=timer
;  2          }
;  1          }prescaler divide 111=1:256 
;  0          }

;                    76543210
#DEFINE OptionBits B'11000111' 


; =========================================================================
; Macro for generating short time delays
;
NO_OP           MACRO   count
NO_OP_COUNT     SET     count
                WHILE   NO_OP_COUNT>1
		goto	$+1		; 2 clocks
NO_OP_COUNT     SET     NO_OP_COUNT-2
                ENDW
		IF	NO_OP_COUNT
		nop			; 1 clock
		ENDIF
                ENDM

;===========================================================================
; Macro to create offsets for variables in RAM
;
		ifdef	__12C508
ByteAddr	SET	7
		else
ByteAddr	SET	32		; user RAM starts here
		endif

BYTE            MACRO     ByteName
ByteName        EQU       ByteAddr
ByteAddr	SET       ByteAddr+1
                ENDM

; ==========================================================================
;                 RAM Variable Definitions  
;
        BYTE	Flags		; various boolean flags            

        BYTE	PPMcount	; pulse length. 1~255 = 0.75~2.28mS  

        BYTE	PPM_1		; channel 1 in        
        BYTE	PPM_2		; channel 2 in
	BYTE	PPM_3		; channel 3 in
	BYTE	PPM_4		; channel 4 in

        BYTE	PPO_1		; channel 1 out       
        BYTE	PPO_2		; channel 2 out
	BYTE	PPO_3		; channel 3 out
	BYTE	PPO_4		; channel 4 out

	BYTE	Temp1
	BYTE	Temp2



; flag values
;
#DEFINE WATCH	0		; Watchdog timeout

;****************************************************************************
;                                Code
;
	ORG	0
	goto	ColdStart

;-------------------------- version string ----------------------------------

	org	8		
	ifdef	JR
	dt	"TRNJR"
	endif
	ifdef	AIR
	dt	"TRAIR"
	endif
	ifdef	GWS
	dt	"TRGWS"
	endif

	ifdef	__12C508
	dt	"508"
	endif
	ifdef	__12F629
	dt	"629"	
	endif
	ifdef	__12F675
	dt	"675"
	endif

	dt	"--V"
	dt	version
	dt	"--"

;============================================================================

ColdStart:		
	bcf	Flags,WATCH
	btfss	STATUS,NOT_TO		; copy Watchdog timeout flag
	bsf	Flags,WATCH

; get oscillator calibration value and use it to fine-tune clock frequency.
; 12C508/9 has value in W at startup, 12F629/75 gets it from RETLW at 0x3ff.

	ifdef	__12C508
	ifdef	OSC_CAL		
	movlw	OSC_CAL			
	endif
	movwf	OSCCAL			; set oscillator calibration value 
	else 
	bsf	STATUS,RP0		; register bank 1 (12F629/75) 
	call	0x3ff			; get OSCCAL value
	movwf	OSCCAL			; set oscillator calibration 
        bcf	STATUS, RP0		; register bank 0 
        endif
                      
	ifdef	__12C508
        clrwdt                    
        movlw	OptionBits  
        OPTION                    	
	else
        clrwdt                    
	bsf	STATUS,RP0	; register bank 1 (12F629/75)
        movlw	OptionBits
        movwf	OPTION_REG
	bcf	STATUS,RP0	; register bank 0
        clrwdt
	endif


; initialise I/O registers 

	ifdef	__12C508
	ifdef	GWS
	movlw	1<<ft_out
	else
	movlw	(1<<ft_out)|(1<<jr_out)
	endif
        movwf	GPIO			; initialize outputs
        movlw	TrisBits
        TRIS    GPIO			; set I/O pin directions
	else
        clrf	GPIO			; all outputs low
	bsf	STATUS,RP0		; register bank 1 (12F629/75)
        movlw	TrisBits
        movwf	TRISIO			; set I/O pin directions
	ifdef	ANSEL
	clrf	ANSEL			; disable analog inputs (12F675)
	endif
	bcf	STATUS,RP0		; register bank 0
	movlw	b'00000111'		      
        movwf	CMCON			; Comparator off
	endif

; CPU specific stuff done, now we can start the main program!

        goto	Main		
							                               
;----------------------------------------------------------------------------
; GetPPM:             Get time to next PPM pulse	
;---------------------------------------------------------------------------- 
;
; input:	PPM signal has just gone high. 
; output:	PPMcount = Pulse Width * 6uS, next pulse has started
; error:	PPMcount = XX and error code in W.
;
; Error Codes
;		0 = good channel
;		1 = pulse too short, too long, or next pulse too soon 
;		2 = no next pulse (ie. no channel)

PRECHARGE = ((750-12)/6)		; = 0.75mS - (setup time)

Get_J:	
		movlw	PRECHARGE	; preset count for signal high length
		movwf	PPMcount
hiloop:	
		btfss	GPIO,jr_in	; signal gone low ?
		goto	pulselo
		nop			; 6uS per loop
		decfsz	PPMcount	; count down
		goto	hiloop
		retlw	1		; timed out, signal high
pulselo:
		movlw	PRECHARGE-(200/6)	
		subwf	PPMcount,W	
		skpnc			; less than minimum pulse width ?
		retlw	1
		movlw   PRECHARGE-(600/6)
		subwf   PPMcount,W
		skpc			; greater than maximum pulse width ?
		retlw	1
to750uS:	
		btfsc	GPIO,jr_in	; signal should stay low until 0.75mS
		retlw	1
		nop			; 6uS per loop
		decfsz  PPMcount	; count down to zero @ 0.75mS
		goto    to750uS
		incf	PPMcount	; count up, start at 1
to2280uS:
		btfsc	GPIO,jr_in	; start of next channel pulse ?
		retlw	0		; return OK
		nop			; 6uS per loop
		incfsz  PPMcount	; count up to 256 @ 2.28mS
		goto	to2280uS
		retlw	2		; return timeout error @ 2.28mS		
	
;------------------------------------------------------------------------------
;                     Get PPM on Futaba/Hitec input
;------------------------------------------------------------------------------
Get_F:	
		movlw	PRECHARGE	; preset count for signal high length
		movwf	PPMcount
lo_loop:	
		btfsc	GPIO,ft_in	; signal gone low ?
		goto	pulse_hi
		nop			; 6uS per loop
		decfsz	PPMcount	; count down
		goto	lo_loop
		retlw	1		; timed out, signal high
pulse_hi:
		movlw	PRECHARGE-(200/6)	
		subwf	PPMcount,W	
		skpnc			; less than minimum pulse width ?
		retlw	1
		movlw   PRECHARGE-(600/6)
		subwf   PPMcount,W
		skpc			; greater than maximum pulse width ?
		retlw	1
to_750uS:	
		btfss	GPIO,ft_in	; signal should stay low until 0.75mS
		retlw	1
		nop			; 6uS per loop
		decfsz  PPMcount	; count down to zero @ 0.75mS
		goto    to_750uS
		incf	PPMcount	; count up, start at 1
to_2280uS:
		btfss	GPIO,ft_in	; start of next channel pulse ?
		retlw	0		; return OK
		nop			; 6uS per loop
		incfsz  PPMcount	; count up to 256 @ 2.28mS
		goto	to_2280uS
		retlw	2		; return timeout error @ 2.28mS		
	

;------------------------------------------------------------------------------
;                    300uS Pulse on JR/GWS Output
;------------------------------------------------------------------------------
;
Pulse_J:
		ifdef	GWS
		bsf	GPIO,jr_out	; Positive pulse for GWS 
		else
		bcf	GPIO,jr_out	; Negative pulse for JR etc.
		endif
		movlw	(300-6)/6
		movwf	Temp1
_pulse:		nop
		clrwdt
		nop
		decfsz	Temp1
		goto	_pulse
		ifdef	GWS
		bcf	GPIO,jr_out
		else
		bsf	GPIO,jr_out
		endif
		retlw	0


;------------------------------------------------------------------------------
;                   300uS Pulse on Futaba/Hitec Output
;------------------------------------------------------------------------------
Pulse_F:
		bcf	GPIO,ft_out
		movlw	(300-6)/6
		movwf	Temp1
_lopulse:	nop
		clrwdt
		nop
		decfsz	Temp1
		goto	_lopulse
		bsf	GPIO,ft_out
		retlw	0


;------------------------------------------------------------------------------
;                     Delay Timer for PPM Output
;------------------------------------------------------------------------------
;
; input: W=delay count, 1 to 255 = 0.75 to 2.28mS 
;
; Total time = 300uS pulse + 450uS fixed + 6~1530uS variable  
;
;
Servo_Delay:	movwf	Temp2
		movlw	(450-6)/6	
		movwf	Temp1
_ds1:		nop
		clrwdt			; avoid watchdog timeout
		nop
		decfsz	Temp1		; wait 0.45mS  
		goto	_ds1
_ds2:		nop
		clrwdt			; avoid watchdog timeout
		nop
		decfsz	Temp2		; wait 0.006~1.53mS
		goto	_ds2		
		retlw	0

;-------------------------------------------------------------------------------
;                    Millisecond Delay Timer 
;-------------------------------------------------------------------------------
; Input: W = number of milliseconds to wait (max 256mS)
;

dx1k:		movwf	Temp1
_dx1k1:		movlw	(1000-5)/5
		movwf	Temp2
_dx1k2:		clrwdt			; avoid watchdog timeout
		nop
		decfsz	Temp2		; wait 1mS
		goto	_dx1k2
		decfsz	Temp1		
		goto	_dx1k1
		retlw	0


;*******************************************************************************
;				   Main
;*******************************************************************************		


Main:		btfsc	Flags,WATCH	; did the watchdog timeout ?
        	goto    no_signal	; oops! try to keep going ...

		clrf	Flags		; clear all flags

		movlw	250		; wait 500mS for power to stabilise 
		call	dx1k		; (signal LED is on)
		movlw	250
		call	dx1k		; wait 500mS with signal LED on

no_signal:	bsf	GPIO,LED	; signal LED off
		
wait_sync:	clrwdt			; we're still sane, no reset please!

		clrf	Temp1
		movlw	9		; set 'gap detect' timeout to 23mS
		movwf	Temp2
		
		btfss	GPIO,SW_1	; direction switch on ?
		goto	wait_jr

wait_ft:	btfsc	GPIO,ft_in	; wait for a gap	]
		goto	time_ft		; 			]
		nop			;			]
		clrwdt			;			]
		nop			;			] 10uS per loop
		decf	Temp1		;			]
		skpnz			;			]
		decfsz	Temp2		; timed out ?		]
		goto	wait_ft		;			] 
		goto	badframe	; can't find sync gap!	 		
	
time_ft:	clrf	PPMcount	; reset gap timer 	

in_ft:		decf	Temp1		;			}
		skpnz			;			}
		decf	Temp2		; timed out ?		}
		skpnz			;			} 
		goto	badframe	; can't find sync gap!	} 10uS per loop
		btfss	GPIO,ft_in	; still in gap ?	}
		goto	wait_ft		; 			}
		incfsz	PPMcount	; gap > 2.56mS ?	}
		goto	in_ft		; no, continue timing	}

Get_ft:		movlw	128-(23000/256)	; set frame timeout to 23mS
		movwf	TMR0
wait_ft1:	clrwdt
		btfsc	TMR0,7		; timer reached 23mS ?
		goto	badframe	
		btfsc	GPIO,ft_in	; wait for start of first channel
		goto	wait_ft1
		NO_OP	9				
		call	Get_F		; get first channel
		andlw	255
		skpz    		; process return code
		goto	badframe
		movf	PPMcount,W
		movwf	PPM_1
		call	Get_F		; get 2nd channel
		andlw	255
		skpz   		
		goto	badframe
		movf	PPMcount,W
		movwf	PPM_2
		call	Get_F		; get 3rd channel
		andlw	255
		skpz   		
		goto	badframe
		movf	PPMcount,W
		movwf	PPM_3
		call	Get_F		; get 4th channel
		andlw	255
		skpz   		
		goto	badframe
		movf	PPMcount,W
		movwf	PPM_4
	
update:		bcf	GPIO,LED	; signal LED on 
;
; Got a good frame. Re-order output channels  
;
		ifdef	AIR
F2A:		movf	PPM_1,W		; Futaba    Airtronics
		movwf	PPO_2		;   1   ->  2 (ailerons)   
		movf	PPM_2,W
		movwf	PPO_1		;   2   ->  1 (elevator)  
		movf	PPM_3,W
		movwf	PPO_3		;   3   ->  3 (throttle)  
		movf	PPM_4,W
		movwf	PPO_4		;   4   ->  4 (rudder)  
		else
F2J:		movf	PPM_1,W		; Futaba     JR/GWS
		movwf	PPO_2		;   1   ->  2 (ailerons)   
		movf	PPM_2,W
		movwf	PPO_3		;   2   ->  3 (elevator)  
		movf	PPM_3,W
		movwf	PPO_1		;   3   ->  1 (throttle)  
		movf	PPM_4,W
		movwf	PPO_4		;   4   ->  4 (rudder)  
		endif
;
; Send output to JR/GWS/Airtronics transmitter
;
		call	Pulse_J		; start channel 1
		movf	PPO_1,W		
		call	Servo_Delay	
		call	Pulse_J		; start channel 2
		movf	PPO_2,W		
		call	Servo_Delay	
		call	Pulse_J		; start channel 3
		movf	PPO_3,W		
		call	Servo_Delay	
		call	Pulse_J		; start channel 4
		movf	PPO_4,W		
		call	Servo_Delay	
		call	Pulse_J		; end channel 4

		goto	wait_sync

wait_jr:	btfss	GPIO,jr_in	; wait for a gap	]
		goto	time_jr		; 			]
		nop			;			]
		clrwdt			;			]
		nop			;			] 10uS per loop
		decf	Temp1		;			]
		skpnz			;			]
		decfsz	Temp2		; timed out ?		]
		goto	wait_jr		;			] 
		goto	badframe	; can't find sync gap!	 		
	
time_jr:	clrf	PPMcount	; reset gap timer 	

in_jr:		decf	Temp1		;			}
		skpnz			;			}
		decf	Temp2		; timed out ?		}
		skpnz			;			} 
		goto	badframe	; can't find sync gap!	} 10uS per loop
		btfsc	GPIO,jr_in	; still in gap ?	}
		goto	wait_jr		; 			}
		incfsz	PPMcount	; gap > 2.56mS ?	}
		goto	in_jr		; no, continue timing	}

Get_jr:		movlw	128-(25000/256)	; set frame timeout to 25mS
		movwf	TMR0
wait_jr1:	clrwdt
		btfsc	TMR0,7		; timer reached 25mS ?
		goto	badframe	
		btfss	GPIO,jr_in	; wait for start of first channel
		goto	wait_jr1
		NO_OP	9				
		call	Get_J		; get first channel
		andlw	255
		skpz    		; process return code
		goto	badframe
		movf	PPMcount,W
		movwf	PPM_1
		call	Get_J		; get 2nd channel
		andlw	255
		skpz   		
		goto	badframe
		movf	PPMcount,W
		movwf	PPM_2
		call	Get_J		; get 3rd channel
		andlw	255
		skpz   		
		goto	badframe
		movf	PPMcount,W
		movwf	PPM_3
		call	Get_J		; get 4th channel
		andlw	255
		skpz   		
		goto	badframe
		movf	PPMcount,W
		movwf	PPM_4

		bcf	GPIO,LED	; signal LED on 
;
; Got a good frame. Re-order output channels  
;
		ifdef	AIR
A2F:		movf	PPM_1,W		; Airtronics   Futaba/Hitec
		movwf	PPO_2		;     1    ->  2 (elevator)   
		movf	PPM_2,W
		movwf	PPO_1		;     2    ->  1 (ailerons)  
		movf	PPM_3,W
		movwf	PPO_3		;     3    ->  3 (elevator)  
		movf	PPM_4,W
		movwf	PPO_4		;     4    ->  4 (rudder)
		else
J2F:		movf	PPM_1,W		;   JR/GWS     Futaba/Hitec
		movwf	PPO_3		;     1    ->  3 (throttle)   
		movf	PPM_2,W
		movwf	PPO_1		;     2    ->  1 (ailerons)  
		movf	PPM_3,W
		movwf	PPO_2		;     3    ->  2 (elevator)  
		movf	PPM_4,W
		movwf	PPO_4		;     4    ->  4 (rudder)  
		endif
;
; Send output to Hitec/Futaba transmitter
;
		call	Pulse_F		; start channel 1
		movf	PPO_1,W		
		call	Servo_Delay	
		call	Pulse_F		; start channel 2
		movf	PPO_2,W		
		call	Servo_Delay	
		call	Pulse_F		; start channel 3
		movf	PPO_3,W		
		call	Servo_Delay	
		call	Pulse_F		; start channel 4
		movf	PPO_4,W		
		call	Servo_Delay	
		call	Pulse_F		; end channel 4

		goto	wait_sync	; wait for next frame


		
badframe:	clrwdt
		movlw	128-((23000-9000)/256); timer reached 9mS ?
		subwf	TMR0,w
		skpc			; skip other channels
		goto	badframe
		goto	no_signal	






;---------- Oscillator Calibration Subroutine (12F629/75 only) --------------

		ifdef	__12F675
		org	0x3ff
		ifdef	OSC_CAL
		retlw	OSC_CAL
		else
		retlw	0x70
		endif	
		endif

		END


