;*************************************************************************** ; 6 Channel Pulse Position Modulation Decoder ;*************************************************************************** ; Bruce Abbott bhabbott@paradise.net.nz ; ; for Microchip 12F629/75 (5 channels) or 16F630/76 (6 channels) ; ;============================= Description ================================= ; ; PPM DECODER: ; ; Each frame consists of 4 to 6 channels (5 to 7 pulses), followed by a sync ; gap. ; ; 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: Pulse polarity (shift) is assumed to be positive. If your Rx produces ; negative pulses, then either change the polarity of instructions that ; test the input signal (BTFSS<->BTFSC), or add an invertor to the input ; circuit! ; ; After receiving channel 6, channels 1, 3, and 5 are output simultaneously, ; followed by channels 2, 4 and 6. ; ; FILTERING: ; ; 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 and GWS use ; channel 1. If channel 1 measures less than 1.3mS or more than 1.7mS at ; startup then it is assigned to the throttle, otherwise channel 3 will be ; assumed. ; ; ============================================================================= ; Summary of Changes ; ; 2004/6/21 V0.00 - modified code from rxdec6 v0.10 ; 2005/2/11 V0.01 - Relaxed input pulse width timing ; 2005/2/27 V0.02 - bugs fixed; 51X,42X not outputting all required channels. ; - bug fixed; failsafe throttle was not set. ; - PIC16F676 PPM_in assigned to RA5 (MCLR reserved for ICSP) ; - Enabled Watchdog timer ; 2005/6/3 V0.03 - Negative 'Shift' assembly option. ; (NOTE: only required if input pulses are negative-going!) ; 2006/4/15 V0.04 - bug fixed; only channels 1~3 were active if 4 or 5 channels ; were detected. ; - Reduced startup delay from 500mS to 250mS. ; ; ----------------------------------------------------------------------------- ; #DEFINE ARM_THROTTLE ; enable if throttle arming control wanted. #DEFINE DETECT_JR ; enable if JR transmitter detection required. ;#DEFINE NEGATIVE_SHIFT ; enable if input pulses are negative going. ; NOTE: normally only required if CHANGING the ; 'shift' of your receiver, eg. for using a ; 72MHz Futaba receiver with a JR transmitter. ifdef NEGATIVE_SHIFT #DEFINE SKIP_PPM_HI btfsc GPIO,PPM_in #DEFINE SKIP_PPM_LO btfss GPIO,PPM_in else #DEFINE SKIP_PPM_HI btfss GPIO,PPM_in #DEFINE SKIP_PPM_LO btfsc GPIO,PPM_in endif #DEFINE version "0.04" ; Make sure that PROCESSOR and file are compatible with your CPU. ; 12F629 uses 12F675 defines, 16F630 uses 16F676 definitions (just don't try ; to use the A/D module...) ; Enable one of the DEFINEs below if your assembler requires it. ; MPLab users should select the device from the "Configure" menu. ;#DEFINE __12F675 ;#DEFINE __16F676 ;#DEFINE OSCAL_NO 0x70 ; set this if OSCCAL value was erased! ifdef __12F675 PROCESSOR PIC12F675 INCLUDE __CONFIG _MCLRE_OFF&_CP_OFF&_WDT_ON&_BODEN_ON&_INTRC_OSC_NOCLKOUT endif ifdef __16F676 PROCESSOR PIC16F676 INCLUDE __CONFIG _MCLRE_OFF&_CP_OFF&_WDT_ON&_INTRC_OSC_NOCLKOUT endif radix dec errorlevel 0,-305,-302 ; Bit definitions for the GPIO register and the TRIS register ifdef __16F676 #DEFINE PPM_in 5 ; pin 2 input pulse stream #DEFINE CH_1 0 ; pin 10 Channel 1 output #DEFINE CH_2 1 ; pin 9 Channel 2 output #DEFINE CH_3 2 ; pin 8 Channel 3 output #DEFINE CH_4 3 ; pin 7 Channel 4 Output #DEFINE CH_5 4 ; pin 6 Channel 5 Output #DEFINE CH_6 5 ; pin 5 Channel 6 Output #DEFINE GPIO PORTA #DEFINE TRISIO TRISA else ; 12F629/75 #DEFINE PPM_in 3 ; pin 4 input pulse stream #DEFINE CH_1 0 ; pin 7 Channel 1 output #DEFINE CH_2 1 ; pin 6 Channel 2 output #DEFINE CH_3 2 ; pin 5 Channel 3 output #DEFINE CH_4 4 ; pin 3 Channel 4 Output #DEFINE CH_5 5 ; pin 2 CHannel 5 Output #DEFINE PORTC GPIO endif #DEFINE TrisBits (1<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 __12C509 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 Channels ; number of channels in current frame BYTE NumChannels ; number of channels detected at startup BYTE GoodFrames ; No. of good frames to go ; before accepting failsafe frame. BYTE HoldFrames ; No. of bad frames to go ; before switching to failsafe. BYTE ArmFrames ; No. of low throttle frames to go ; before arming throttle. BYTE Temp1 ; general purpose storage BYTE Temp2 ; '' '' '' BYTE PPI_1 ; channel 1 in BYTE PPI_2 ; channel 2 in BYTE PPI_3 ; channel 3 in BYTE PPI_4 ; channel 4 in BYTE PPI_5 ; channel 5 in ifdef CH_6 BYTE PPI_6 ; channel 6 in endif BYTE FLS_1 ; channel 1 failsafe BYTE FLS_2 ; channel 2 failsafe BYTE FLS_3 ; channel 3 failsafe BYTE FLS_4 ; channel 4 failsafe BYTE FLS_5 ; channel 5 failsafe ifdef CH_6 BYTE FLS_6 ; channel 6 failsafe endif BYTE PPA_1 ; channel 1 average BYTE PPA_2 ; channel 2 average BYTE PPA_3 ; channel 3 average BYTE PPA_4 ; channel 4 average BYTE PPA_5 ; channel 5 average ifdef CH_6 BYTE PPA_6 ; channel 6 memory endif BYTE PPM_1 ; channel 1 memory BYTE PPM_2 ; channel 2 memory BYTE PPM_3 ; channel 3 memory BYTE PPM_4 ; channel 4 memory BYTE PPM_5 ; channel 5 memory ifdef CH_6 BYTE PPM_6 ; channel 6 memory endif BYTE PWM_1 ; channel 1 out BYTE PWM_2 ; channel 2 out BYTE PWM_3 ; channel 3 out BYTE PWM_4 ; channel 4 out BYTE PWM_5 ; channel 5 out ifdef CH_6 BYTE PWM_6 ; channel 6 out endif ;------------------------------------------------------------------------- ; flag bit assignments ;------------------------------------------------------------------------- ; #DEFINE WATCH 0 ; Watchdog timeout #DEFINE GOT_FS 1 ; failsafe frame has been captured #DEFINE BAD_FRAME 2 ; error detected in frame #DEFINE JR 3 ; JR throttle detection option #DEFINE ARMED 4 ; throttle armed ;------------------------------------------------------------------------- ; Constants ;------------------------------------------------------------------------- ; number of good frames required before accepting failsafe values. #DEFINE GOODCOUNT 10 ; number of bad frames acceptable before going to failsafe. #DEFINE HOLDCOUNT 25 ; number of consecutive low throttle frames required before arming. #DEFINE ARMCOUNT 5 ;**************************************************************************** ; Code ; ORG 0 goto ColdStart ORG 8 ifdef __16F676 dt "-RXD676-" else dt "-RXD675-" 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. bsf STATUS,RP0 call 0x3ff ; get OSCCAL value movwf OSCCAL ; set oscillator calibration movlw OptionBits movwf OPTION_REG bcf STATUS,RP0 clrwdt ; initialise I/O registers clrf GPIO ; all GPIO (PORTA) outputs low ifdef __16F676 clrf PORTC ; all PORTC outputs low (12F630/76) endif bsf STATUS,RP0 movlw TrisBits movwf TRISIO ; set GPIO (PORTA) directions ifdef __16F676 movlw 0 movwf TRISC ; set portc directions (12F630/76) endif ifdef ANSEL clrf ANSEL ; disable analog inputs (12F675/16F676) endif bcf STATUS,RP0 movlw b'00000111' movwf CMCON ; Comparator off ; 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 just started ; error: PPMcount = XX and error code in W. ; ; Error Codes ; 0 = pulse too short, too long, or next pulse too soon ; 1 = no next pulse (ie. no channel) ; 2 = good channel PRECHARGE = ((750-12)/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_HI ; signal gone low ? goto pulselo nop ; 6uS per loop decfsz PPMcount ; count down goto hiloop retlw 0 ; timed out, signal high pulselo: movlw PRECHARGE-(180/6) subwf PPMcount,W skpnc ; less than minimum pulse width ? retlw 0 movlw PRECHARGE-(690/6) subwf PPMcount,W skpc ; greater than maximum pulse width ? retlw 0 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_LO ; signal should stay low until 0.75mS retlw 0 nop ; 6uS per loop decfsz PPMcount ; count down to zero @ 0.75mS goto to750uS incf PPMcount ; count up, start at 1 to2280uS: SKIP_PPM_LO ; start of next channel pulse ? retlw 2 ; return OK nop ; 6uS per loop incfsz PPMcount ; count up to 256 @ 2.28mS goto to2280uS retlw 1 ; return error @ 2.28mS ;------------------------------------------------------------------------------- ; 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 ;---------------------------------------------------------------------------- ; Begin Channels 1,3,5 Output Pulses ;---------------------------------------------------------------------------- Start135: movlw (1< 2.56mS ? } goto in_gap ; no, continue timing } Get_Frame: movlw 128-(23000/256) ; set 23mS timeout movwf TMR0 wait_1st: clrwdt btfsc TMR0,7 ; timer reached 128 ? goto badframe SKIP_PPM_HI ; wait for start of first channel goto wait_1st clrf Channels ; no channels received yet! NO_OP 4 call GetPPM ; get first channel addlw -2 skpz goto badframe movf PPMcount,W movwf PPI_1 incf Channels call GetPPM ; get 2nd channel addlw -2 skpz goto badframe movf PPMcount,W movwf PPI_2 incf Channels call GetPPM ; get 3rd channel addlw -2 skpz goto badframe movf PPMcount,W movwf PPI_3 incf Channels call GetPPM ; get 4th channel addlw -2 skpz goto nochannel movf PPMcount,W movwf PPI_4 incf Channels call GetPPM ; get 5th channel addlw -2 skpz goto nochannel movf PPMcount,W movwf PPI_5 incf Channels ifdef CH_6 call GetPPM ; get 6th channel addlw -2 skpz goto nochannel movf PPMcount,W movwf PPI_6 incf Channels endif goto update nochannel: addlw 1 skpz ; channel timed out? goto badframe btfss Flags,GOT_FS ; got failsafe frame yet ? goto update ; no, ignore missing channels movf Channels,w subwf NumChannels ; got correct number of channels ? skpnz goto update ; yes badframe: movlw ARMCOUNT ; reset throttle arming delay movwf ArmFrames decfsz HoldFrames ; too many bad frames ? goto hold goto Failsafe hold: btfss Flags,GOT_FS ; good frame available for hold ? goto no_signal movf PPA_1,w movwf PWM_1 movf PPA_2,w movwf PWM_2 movf PPA_3,w movwf PWM_3 ; get previous averaged outputs movf PPA_4,w movwf PWM_4 movf PPA_5,w movwf PWM_5 ifdef CH_6 movf PPA_6,w movwf PWM_6 endif call Output ; create servo pulses goto no_signal ; ; Got a good frame. Average the pulse widths of this frame and the last frame. ; update: movf PPI_1,w addwf PPM_1,w ; PPA = average(this+last) movwf PPA_1 rrf PPA_1 movf PPI_2,w addwf PPM_2,w movwf PPA_2 rrf PPA_2 movf PPI_3,w addwf PPM_3,w movwf PPA_3 rrf PPA_3 movf PPI_4,w addwf PPM_4,w movwf PPA_4 rrf PPA_4 movf PPI_5,w addwf PPM_5,w movwf PPA_5 rrf PPA_5 ifdef CH_6 movf PPI_6,w addwf PPM_6,w movwf PPA_6 rrf PPA_6 endif movf PPA_1,w movwf PWM_1 ; PWM = average movf PPA_2,w movwf PWM_2 movf PPA_3,w movwf PWM_3 movf PPA_4,w movwf PWM_4 movf PPA_5,w movwf PWM_5 ifdef CH_6 movf PPA_6,w movwf PWM_6 endif btfss Flags,GOT_FS ; failsafe frame captured ? goto save_frame ; no, skip output ifdef ARM_THROTTLE btfsc Flags,ARMED ; throttle armed ? goto do_output ; yes, do normal output movlw (1300-750)/6 btfsc Flags,JR subwf PPM_1,w ; throttle < 1.3mS ? btfss Flags,JR subwf PWM_3,w skpc goto low_throttle ; yes movlw ARMCOUNT movwf ArmFrames ; no, recharge arming delay goto mod_throttle low_throttle: decfsz ArmFrames ; got enough arming frames ? goto mod_throttle bsf Flags,ARMED ; yes, arm the throttle! goto save_frame mod_throttle: btfsc Flags,JR goto mod_jr movf FLS_3,w movwf PWM_3 ; keep throttle off until armed goto do_output mod_jr: movf FLS_1,w movwf PWM_1 ; keep JR throttle off until armed endif do_output: call Output ; output servo pulses save_frame: movf PPI_1,W movwf PPM_1 movf PPI_2,W movwf PPM_2 movf PPI_3,W movwf PPM_3 ; remember this frame movf PPI_4,W movwf PPM_4 movf PPI_5,W movwf PPM_5 ifdef CH_6 movf PPI_6,W movwf PPM_6 endif movlw HOLDCOUNT ; recharge hold timeout movwf HoldFrames btfsc Flags,GOT_FS ; got failsafe frame ? goto frame_done ; yes decfsz GoodFrames ; got enough good frames ? goto frame_done ; no ; get failsafe values movf Channels,w ; remember number of channels movwf NumChannels movf PPA_1,W movwf FLS_1 movf PPA_2,W movwf FLS_2 movf PPA_3,W ; copy averaged frame to failsafe movwf FLS_3 movf PPA_4,W movwf FLS_4 movf PPA_5,W movwf FLS_5 ifdef CH_6 movf PPA_6,W movwf FLS_6 endif bsf Flags,GOT_FS ; failsafe frame captured ifdef ARM_THROTTLE ifdef DETECT_JR movlw (1300-750)/6 subwf FLS_1,w ; channel 1 < 1.3mS ? skpc goto jr_detected movlw (1700-750)/6 subwf FLS_1,w ; channel 1 >= 1.7mS ? skpc goto futaba movlw (1100-750)/6 ; low throttle in failsafe! movwf FLS_1 jr_detected: bsf Flags,JR ; JR throttle detected goto arm endif ; DETECT_JR futaba: movlw (1300-750)/6 subwf FLS_3,w ; channel 3 >= 1.3mS ? skpc goto arm movlw (1100-750)/6 ; low throttle in failsafe! movwf FLS_3 arm: bsf Flags,ARMED ; arm throttle now endif ; ARM_THROTTLE frame_done: goto wait_sync ; wait for next frame ;---------- Oscillator Calibration Subroutine -------------- org 0x3ff ifdef OSCAL_NO retlw OSCAL_NO endif END