;***************************************************************************
; rxdecode.asm Pulse Position Modulation Decoder for 4 Channel Rx
;***************************************************************************
; Bruce Abbott bhabbott@paradise.net.nz
;
; for Microchip PIC 12C508, 12C509, 12F629 or 12F675.
;
;============================= Description =================================
;
; PPM DECODER:
;
; Each frame consists of 4 to 8 channels (5 to 9 pulses), followed by a sync
; gap. We only decode the first 4 channels.
;
; The width of each channel is 1 to 2mS (1.5mS when sticks centered). The
; sync gap is at least 2.5mS long, and frame length is approximately 20mS.
;
; The input signal should look something like this:-
;
; |<------------------ 1 frame (~20mS) -------------------->|
; |< 1~2mS >|
; _ _ _ _ _ _
; ___________| |_______| |______| |______| |______| |__ // ____________| |____
; sync gap ch1 ch2 ch3 ch4 etc. sync gap ch1
;
; NOTE: This waveform shows positive 'shift'. If your Rx outputs negative
; 'shift', the waveform is inverted.
;
; There is just enough time to generate four decoded outputs after receiving
; channel 4, and before the end of the sync gap.
;
; GLITCH FILTER:
;
; Each channel is averaged with the value from the previous frame, reducing
; servo jitter on weak signals. If any channel in a frame is corrupted, the
; whole frame is discarded and the last good frame is used instead.
;
; FAILSAFE:
;
; On receiving a sufficient number of good frames we save it for failsafe.
; Then, if the signal is corrupted for too long, we output the failsafe frame
; instead of the last good frame.
;
; THROTTLE ARMING:
;
; When entering failsafe the throttle is cut, and it will not be restarted
; until a good signal is detected AND the throttle is manually reset. This
; should prevent the situation where motor-induced RF interference causes
; the decoder to cycle in and out of failsafe repeatedly.
;
; For this feature to work the throttle channel has to be determined. Futaba
; and Hitec tranmitters assign the throttle to channel 3. JR, Airtronics and
; GWS use channel 1.
;
; Some ESC's need to have the throttle set to maximum at startup, perhaps to
; disable the brake or to enter programming mode. Therefore, channel 1 is
; designated as throttle if it measures less than 1.3mS or more than 1.7mS at
; startup, otherwise channel 3 is assumed.
;
; The throttle channel is forced to 1.05mS during failsafe, rather than set
; to its failsafe value, in case that value was full throttle!
;
; =============================================================================
; Summary of Changes
;
; 2003/12/2 V0.5 - Initial release
;
; 2003/12/27 V0.6 - ASM Error if 'addwf PCL' not in page 0.
; - Select processor via MPLab's Device Select menu.
; 2004/1/7 V0.7 - No output until failsafe frame captured. Should now be
; compatible with JR/GWS transmitters (throttle on CH1).
;
; 2004/1/9 V0.8 - Increased range to 0.75~2.28mS, now accepts >125% throws.
; Resolution is slightly reduced (from 5uS to 6uS).
; - Failsafe and Hold frames now use averaged output values.
; - Ignore missing 4th channel (for 3 channel TX) NOT TESTED!
;
; 2004/2/15 V0.9 - Fixed bug:- OSCCAL not initialized in 12F675 code!
; - Detect throttle channel. The throttle is held OFF after
; failsafe, until re-armed manually. This prevents cyling
; into and out of failsafe due to electrical noise coming
; from the motor.
;
; 2004/6/20 V1.0 - Using TMR0 to detect loss of signal. This prevents lockup
; during a clean signal loss (no noise) which may occur if
; used in a receiver which has muting.
; - TMR0 is also used to set frame repeat time in failsafe.
; - Fixed bug:- Initial channel output values were wrong.
;
; 2004/10/24 V1.1 - Positive and Negative shift versions.
; - Ignore short glitches on signal transitions.
; - Ensure throttle is low in failsafe, even if it was maxiumum
; at startup (possible requirement for ESC programming).
;
; 2004/12/12 V1.2 - Fixed bug: JR throttle arming was disabled due to a typo!
;
; 2005/1/20 V1.3 - Now accepts pulse widths from 180uS to 700uS.
;
; 2005/11/16 V1.4 - Change channels to 5 - 8. by Y.C.Tseng
; disable the ARM_THROTTLE and DETECT_JR
;
; -----------------------------------------------------------------------------
;#DEFINE ARM_THROTTLE ; enable if throttle arming control wanted.
;#DEFINE DETECT_JR ; enable for JR/Airtronics/GWS throttle detection.
#DEFINE version "1.4"
#DEFINE Negative_Shift ; enable for Futaba/Hitec on 72MHz
; Make sure that PROCESSOR and <include> file are compatible with your CPU!
; 12C508(A) and 12C509(A) can use 12C508 definitions. 12F629 can use 12F675
; definitions (just don't try to use the A/D module...)
;#DEFINE __12C508 ; enable if processor not specified elsewhere
;#DEFINE __12F675 ; (MPLAB:- use menu <Configure/Select_Device>)
;#DEFINE NO_OSCCAL ; enable if OSCCAL value was erased!
ifdef __12C508
PROCESSOR PIC12C508
INCLUDE <P12C508.inc>
__CONFIG _MCLRE_OFF&_CP_OFF&_WDT_ON&_IntRC_OSC
else
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 CH_1 0 ; pin 7 Channel 5 output
#DEFINE CH_2 1 ; pin 6 Channel 6 output
#DEFINE CH_3 2 ; pin 5 Channel 7 output
#DEFINE PPM_in 3 ; pin 4 input pulse stream
#DEFINE CH_4 4 ; pin 3 Channel 8 Output
#DEFINE LED 5 ; pin 2 Signal Indicator LED
#DEFINE TrisBits H'FF'&~((1<<CH_1)|(1<<CH_2)|(1<<CH_3)|(1<<CH_4)|(1<<LED))
; Bits to be set with the OPTION instruction
; No wake up
; No weak pullups
; Timer 0 source internal
; Prescaler to Timer 0, divide by 256.
;
#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
BYTE HoldFrames ; No. of bad frames to go before going
; to failsafe
BYTE ArmFrames ; No. of low throttle frames to go
; before arming throttle.
BYTE Temp1
BYTE Temp2
BYTE DUMY_1
; flag values
;
#DEFINE WATCH 0 ; Watchdog timeout
#DEFINE GOT_FS 1 ; have captured failsafe frame
#DEFINE GOT_4 2 ; 4 channels found in current frame
#DEFINE DET_4 3 ; 4 channel TX detected
#DEFINE JR 4 ; JR throttle detected
#DEFINE ARMED 5 ; throttle armed
; number of consecutive good frames required at startup.
#DEFINE GOODCOUNT 10
; number of consecutive bad frames accepted without going to failsafe.
#DEFINE HOLDCOUNT 25
; number of consecutive low throttle frames required before arming.
#DEFINE ARMCOUNT 10
; macros for working with negative or positive shift
skip_PPM_high MACRO
ifdef Negative_Shift
btfsc GPIO,PPM_in
else
btfss GPIO,PPM_in
endif
ENDM
skip_PPM_low MACRO
ifdef Negative_Shift
btfss GPIO,PPM_in
else
btfsc GPIO,PPM_in
endif
ENDM
;****************************************************************************
; Code
;
ORG 0
goto ColdStart
;-------------------------- version string ----------------------------------
org 8
ifdef __12C508
dt "RXDEC508"
endif
ifdef __12F629
dt "RXDEC629"
endif
ifdef __12F675
dt "RXDEC675"
endif
dt "--V"
dt version
dt "--"
ifdef ARM_THROTTLE
dt "ARMTHROT"
endif
ifdef DETECT_JR
dt "DETECTJR"
endif
ifdef Negative_Shift
dt "NEG_SHFT"
endif
;============================================================================
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 NO_OSCCAL
movlw 0x90 ; replace with value for your PIC!
endif
movwf OSCCAL ; set oscillator calibration
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
; initialise I/O registers
clrf GPIO ; all outputs low
movlw TrisBits
ifdef __12C508
TRIS GPIO ; set I/O pin directions
else
bsf STATUS,RP0 ; register bank 1 (12F629/75)
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-15)/6) ; = 0.75mS
GetPPM:
movlw PRECHARGE ; preset count for signal high length
movwf PPMcount
movlw (30/6)
movwf Temp1
high_delay: NO_OP 2
decf PPMcount ; wait 30uS to skip short glitches
decfsz Temp1
goto high_delay
hiloop:
skip_PPM_high ; signal gone low ?
goto pulselo
nop ; 6uS per loop
decfsz PPMcount ; count down
goto hiloop
retlw 1 ; timed out, signal high
pulselo:
movlw PRECHARGE-(180/6)
subwf PPMcount,W
skpnc ; less than minimum pulse width ?
retlw 1
movlw PRECHARGE-(700/6)
subwf PPMcount,W
skpc ; greater than maximum pulse width ?
retlw 1
movlw (30/6)
movwf Temp1
low_delay: NO_OP 2
decf PPMcount ; wait 30uS to skip short glitches
decfsz Temp1
goto low_delay
to750uS:
skip_PPM_low ; 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:
skip_PPM_low ; 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
;*******************************************************************************
; Main
;*******************************************************************************
Main: btfsc Flags,WATCH ; did the watchdog timeout ?
goto Failsafe ; oops! try to keep going ...
clrf Flags ; clear all flags
movlw 250 ; wait 500mS for Rx to stabilise
call dx1k ; (signal LED is on)
movlw 250
call dx1k
Start: bsf GPIO,LED ; signal LED off
movlw (1500-750)/6
movwf PMM_1
movwf PMM_2 ; all channel outputs at midpoint
movwf PMM_3
movwf PMM_4
movwf PPM_4 ; init channel 4 (for 3 channel TX)
movlw GOODCOUNT ; set number of good frames required
movwf GoodFrames ; before saving failsafe values.
movlw HOLDCOUNT ; set number of bad frames allowed
movwf HoldFrames ; before going to failsafe.
movlw ARMCOUNT ; set number of low throttle frames
movwf ArmFrames ; before arming throttle
goto no_signal
Failsafe: movlw 1
movwf HoldFrames ; stay in failsafe until signal returns
btfss Flags,GOT_FS ; do we have good failsafe values?
goto no_signal
movf FLS_1,W
movwf PWM_1
movf FLS_2,W ; get failsafe values
movwf PWM_2
movf FLS_3,W
movwf PWM_3
movf FLS_4,W
movwf PWM_4
call Output ; output failsafe frame
bcf Flags,ARMED ; keep throttle OFF
movlw ARMCOUNT
movwf ArmFrames ; reset throttle arming delay
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
wait_gap: skip_PPM_high ; wait for a gap ]
goto time_gap ; ]
nop ; ]
clrwdt ; ]
nop ; ] 10uS per loop
decf Temp1 ; ]
skpnz ; ]
decfsz Temp2 ; timed out ? ]
goto wait_gap ; ]
goto badframe ; can't find sync gap!
time_gap: clrf PPMcount ; reset gap timer
in_gap: decf Temp1 ; }
skpnz ; }
decf Temp2 ; timed out ? }
skpnz ; }
goto badframe ; can't find sync gap! } 10uS per loop
skip_PPM_low ; still in gap ? }
goto wait_gap ; }
incfsz PPMcount ; gap > 2.56mS ? }
goto in_gap ; no, continue timing }
Get_Frame: movlw 128-(23000/256) ; set frame timeout to 23mS
movwf TMR0
wait_1st: clrwdt
btfsc TMR0,7 ; timer reached 23mS ?
goto badframe
skip_PPM_high ; wait for start of first channel
goto wait_1st
bcf Flags,GOT_4 ; channel 4 not received yet
NO_OP 3
call GetPPM ; get 1st channel
andlw 255
skpz ; process return code
goto badframe
movf PPMcount,W
movwf DUMY_1
call GetPPM ; get 2nd channel
andlw 255
skpz ; process return code
goto badframe
movf PPMcount,W
movwf DUMY_1
call GetPPM ; get 3rd channel
andlw 255
skpz ; process return code
goto badframe
movf PPMcount,W
movwf DUMY_1
call GetPPM ; get 4th channel
andlw 255
skpz ; process return code
goto badframe
movf PPMcount,W
movwf DUMY_1