/************************************************************************ Multi channel radio switch decoder Copyright (c) 2002 Andy Birkett This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You may view the GNU General Public License at http://www.gnu.org/copyleft/gpl.html or write to: the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. mailto:AndyBirkett.Planes@gaspode.co.uk http://www.gaspode.co.uk ************************************************************************/ // 16F84A can be used for testing the code //#define PIC84 // Skip tx throttle channel detection for simulation it takes too long! //#define SIMULATE // Set to invert input for some unknowm receiver //#define INVERT /* 16f84: ; --------- ; <->RA2 |1 18| RA1<-> ; <->RA3 |2 17| RA0<-> ; <->RA4 |3 16| osc -xtal---||--0V ; -->CLR |4 15| osc -xtal---||--0V ; 0V |5 14| 5V ; int <->RB0 |6 13| RB7<-> int ; <->RB1 |7 12| RB6<-> int ; <->RB2 |8 11| RB5<-> int ; <->RB3 |9 10| RB4<-> int ; --------- 12C508a: ; --------- ; 5V |1 18| 0V ; elevatorB <->GP5 |2 17| GP0<-> Throttle ; elevatorA <->GP4 |3 16| GP1<-> RudderA ; rx i/p -->GP3 |4 15| GP2<-> RudderB ; --------- */ /* What a frame looks like: <--------------------- 21 ms -------------------------> 580µS pulses -- -- -- -- --- -- -- --------------- ------ ------- ---- ---- --- ------------------ -- ^ ^ ^ ^ ^ ^ | | | | | | ch 1 (aileron) ---- | | | | | | | | | | ch 2 (throttle) ------------- | | | | | | | | ch 3 (elevator) --------------------- | | | | | | ch 4 (rudder) --------------------------- | | | | ch 5 and other channels ...---------------------- | | at least 3ms gap between frames?-------------------------- Thhe 580µS pulse can vary Grahams is more like 400µS Colins is 460µS A channel is low for 410µS to 1530µS (applying full trim) A channel is low for 540µS to 1390µS (no trim) aileron full left = 410µS throttle full down = 410µS (my throttle is inverted!) elevator stick up = 410µS rudder stick left = 410µS NOTE we use aileron as rudder!!! Typical values: // NOTE my throttle is reversed to work with GWS speed controller! Throttle: stick up trim up 1530µS stick up trim centre 1390µS centre stick 920µS stick down trim centre 540µS stick down trim right 410µS Leading edge to leading edge times (these are what we use now): Mine, Colins worst case Used ( 1 lot of 35 towards centre) full trim 990 (est) 1010 n/a left / up 1120 1090 1120 1155 centre 1520 1500 1500 1500 right / down 1980 1950 1950 1915 full trim 2110 (est) 2070 n/a */ #ifdef PIC84 #include "..\compiler\cc5xfree\16f84a.h" #define CP_off |= 0x3FF8 #pragma config PWRTE=on, WDTE=off, FOSC=HS, CP_off #else #include "..\compiler\cc5xfree\12C508a.h" /* bit 0 0} bit 1 1}=> Internal RC bit 2 0=>Watchdog disabled bit 3 1=>No code protecr bit 4 0=>mclr pin NOT used */ #define NO_MCLR &= ~0x80 #define CP_off |= 0xFFE8 #define INTERNAL_RX |= 2 #pragma config INTERNAL_RX, CP_off, WDTE=off #endif /***********/ /* DEFINES */ /***********/ #define PRESCALER_VALUE 3 // A value of 3 gives a pre-scaler of 16, this means the counter will count to 4096µS #define TIMER_TICK 16 // Time for one timer tick in µs // High to High pulses longer than this are rejected #define MAX_ALLOWED_PULSE_WIDTH (2200/TIMER_TICK) // High to High pulses shorter than this are rejected #define MIN_ALLOWED_PULSE_WIDTH (900/TIMER_TICK) #define MAX_MEASURED_WIDTH (1950/TIMER_TICK - 1) // (-1 => towards centre) #define MIN_PULSE_WIDTH (1120/TIMER_TICK + 1) // (+1 => towards centre) #define MIN_THROTTLE_WIDTH (1200/TIMER_TICK) #define MAX_THROTTLE_WIDTH (1870/TIMER_TICK) #define CENTER_STICK ((1500 / TIMER_TICK)) #define WAIT_17mS 17 #define WAIT_3mS (3000/TIMER_TICK) #define WAIT_1mS (1000/TIMER_TICK) // High pulses shorter than this are rejected #define WAIT_300uS (300/TIMER_TICK) #define FALSE 0 #define TRUE 1 // The number of bad frames before we switch everything off in weve had an error // Equates to 1 seconds #define ERROR_THRESHOLD 1000000/TIMER_TICK /************/ /* VARIABLES */ /************/ #ifdef PIC84 /* outputs */ // Asignment for PIC84 #pragma bit OP_1 @ PORTB.0 #pragma bit OP_2 @ PORTB.1 #pragma bit OP_3 @ PORTB.2 #pragma bit OP_4 @ PORTB.3 #pragma bit OP_5 @ PORTA.0 /* Inputs */ #pragma bit ip_rx @ PORTB.6 // bit stream from receiver #else /* Outputs */ #pragma bit OP_1 @ GPIO.0 #pragma bit OP_2 @ GPIO.1 #pragma bit OP_3 @ GPIO.2 #pragma bit OP_4 @ GPIO.4 #pragma bit OP_5 @ GPIO.5 /* Inputs */ // NB bit 3 must be an input #pragma bit ip_rx @ GPIO.3 // bit stream from receiver #endif unsigned char nFlags; #pragma bit fFrameError @ nFlags.7 #ifdef INVERT #define ip_low ip_rx #define ip_high !ip_rx #else #define ip_low !ip_rx #define ip_high ip_rx #endif // working variables // The number of conecutive frame errors unsigned long nErrorCount; // Timer to measure gap unsigned char nGapTimer; // Counter used to hold the times of each of the channels unsigned char nCh1Time; unsigned char nCh2Time; unsigned char nCh3Time; unsigned char nCh4Time; unsigned char nCh5Time; unsigned char nCh6Time; unsigned char nCh7Time; unsigned char nCh8Time; // Measure one channel // Sets fFrameError if we saw noise // Return TRUE if there is a valid calue for this channel // otherwise FALSE static char measureChannel(char *pChannelTimer) { char nMeasuredTime; fFrameError = 0; // The input has just gone high // Start measuring time from leading edge to leading edge TMR0 = 0; // But we still want to check the input remains high for at least 300µS // So wait for input to go low while measuring time. while (ip_high) { } // The input has gone low, was it high long enough??? if (TMR0 < WAIT_300uS) { // No, throw this frame away. fFrameError = 1; return FALSE; } // Input is now low, wait for it to go high, measuring the time while (ip_low) { // If this channel is not present then we will eventually count too much if (TMR0 > MAX_ALLOWED_PULSE_WIDTH) break; } // Store measured time so it dosn't tick while we are testing it! nMeasuredTime = TMR0; if (nMeasuredTime < MIN_ALLOWED_PULSE_WIDTH) { fFrameError = 1; return FALSE; } // There may be less that 8 channels in which case Tthe signal stays low if (nMeasuredTime > MAX_ALLOWED_PULSE_WIDTH) { // We've run off the end of the freame, this channel isn't there return FALSE; } else { // We now have the time for this channel so store it *pChannelTimer = TMR0; return TRUE; } } // static char measureChannel(char *pChannelTimer) // Routine to read all 8 channels from the incoming bit stream. static void ReadChannels() { startLookingAgain: // Initally look for a 3ms gap followed by a 300µS high period // Waiting for 0 while (ip_high) { } TMR0 = 0; // Check is stays low for 3ms while (TMR0 < WAIT_3mS) { if (ip_high) { // input as gone high within 3ms goto HadError; } } // Wait for high within the next 15ms // 3 channel tx 6ms of info 14ms of gap, we've allready waited 3ms nGapTimer = WAIT_17mS; // Make count units of 1ms while (ip_low) { TMR0 = 0; while (TMR0 < WAIT_1mS) { // Prevent jitter by dropping out here if the i/p has gone high if (ip_high) break; } if (--nGapTimer == 0) goto HadError; } // while (ip_low) // We are now at the starr of the pulse train for all the channels // measure each channel while looking for errors in the frame // We assume three channels must be present // The rest can be missing if (!measureChannel(&nCh1Time)) goto HadError; if (!measureChannel(&nCh2Time)) goto HadError; if (!measureChannel(&nCh3Time)) goto HadError; measureChannel(&nCh4Time); measureChannel(&nCh5Time); measureChannel(&nCh6Time); measureChannel(&nCh7Time); measureChannel(&nCh8Time); if (!fFrameError) { // got to the end of a frame succefully return; } HadError: fFrameError = 1; goto startLookingAgain; } // void ReadChannels() void waitHalfSecond() { unsigned long nDelay; // each loop takes 10µS for (nDelay=0;nDelay < (500000/10);nDelay++); } /***************************************************************/ /* MAIN */ /***************************************************************/ void main( void) { #ifdef PIC84 // switch banks during inittialisation #pragma update_RP 1 PORTA = 0; PORTB = 0; TRISA = 0xF0; /* bit 0..3 outputs */ TRISB = 0xF0; /* bit 0..3 i/p */ #else OSCCAL = W; GPIO = 0; TRIS = 0x08; /* bit 3 i/p bits 0,1,2,4,5 o/p */ #endif OPTION = PRESCALER_VALUE; /* prescaler divide by 16 */ TMR0 = 0; #ifdef PIC84 // don't switch banks again #pragma update_RP 0 #endif nErrorCount = ERROR_THRESHOLD; nCh1Time = 0; nCh2Time = 0; nCh3Time = 0; nCh4Time = 0; nCh5Time = 0; nCh6Time = 0; nCh7Time = 0; nCh8Time = 0; // TEMP TO SPEED UP SIMULATION #ifndef SIMULATE // wait for everything to settle!!! waitHalfSecond(); waitHalfSecond(); #endif // Main loop while (1) { // Note we don't come back from read channels until we have a good frame ReadChannels(); // Now do all the calculations // and output new outputs // We only have 5 o/ps so make then channels 4-8. // We just do if channel is less than centre stick => off otherwise on for now if (nCh4Time < CENTER_STICK) OP_1 = FALSE; else OP_1 = TRUE; if (nCh5Time < CENTER_STICK) OP_2 = FALSE; else OP_2 = TRUE; if (nCh6Time < CENTER_STICK) OP_3 = FALSE; else OP_3 = TRUE; if (nCh7Time < CENTER_STICK) OP_4 = FALSE; else OP_4 = TRUE; if (nCh8Time < CENTER_STICK) OP_5 = FALSE; else OP_5 = TRUE; } // while (1) } // main