//LVC2 //Low Voltage Cutoff program for twin-engined plane (comparing two separate packs, common ground) //Simple data logging //Voltage thresholds set for 5 A123 lithium cells; 'normal' Lithium Polymer cells require different cutoff //LVC program can have three thresholds but one is usually the most practical //The thresholds cause throttle to cut until acknowledged by pilot (who does so by also cutting thottle) //Threshold 3 keeps cutting throttle (and keeps requiring acknowledgement) every time lowest pack reaches min //Thresholds 1 and 2 (disabled) require only one acknowledgement each (allows voltages to then drift up and down) //Thresholds 1 and 2 are intended for early warnings if desired //The program determines the throttle position and pack voltages every ~20ms (in sync with the RX pulse) //Every 7 seconds the lowest pack voltages (since last write) are stored in memory //This gives about 15minutes flight recording //Multiply ADC values by 0.074 to convert to measured volts (4 * 4.95/1023 * 13/3.40) //4 = scaling to fit 8bit, 4.95 = ref voltage, 4.95/1023 = ADC conversion, 13/3.4 = measured resistor divider effect //Flight data is only overwritten before each flight once both packs are connected //Written for PIC12F683 (8 pin) / BoostC compiler //Personal and non-commercial use encouraged //Copyright 2007 - David Theunissen - See www.flyelectric.ukgateway.net //PREPROCESSOR section ======================================================== #include //Generic device 'include'; actual device set in compiler (Settings/Target) #pragma DATA _CONFIG, _FCMEN_OFF & _IESO_OFF & _BOD_ON & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTOSCIO //Core configuration options //VARIABLES section =========================================================== #define ESC gpio.1 //GP1 (Pin6) is Controller #define RX gpio.3 //GP3 (Pin4) is Receiver #define LED gpio.5 //GP5 (Pin2) is LED typedef unsigned char u8; //Defines u8 as an unsigned char (8bits) typedef unsigned short u16; //Defines u16 as an unsigned short (16bits) u16 packvalue; //Used to determine individual pack ADC values u16 div1; //The value of resistor dividers (eg: div1 is pack 1) u16 div2; u8 k; //Counter for multiple pack readings u16 pack1; //The value of pack 1, 2 ... u16 pack2; u16 lowpack; //Value of lowest pack after comparisons u16 level3; //3rd warning threshold bit ack; //Pilot acknowledgement of warnings //u16 level1; //1st warning threshold (disabled) //u16 level2; //2nd warning threshold (disabled) //bit warn1; //Stores 1st warning given (disabled) //bit warn2; //Stores 2nd warning given (disabled) u16 pulse_good; //Duration of last 'known good' pulse from RX u16 pulse_new; //Duration of 'latest' pulse from RX signed short pulse_diff; //Difference between new and old pulse lengths (16bit signed) bit jitter; //Store whether jitter or glitch is being detected (1=yes 0=no) u8 j; //Counter to avoid 'over-controlling' jitter/glitch detection u16 m_timer; //Memory Timer - timebase for writes to memory u8 m_count; //Memory Counter - prevents over-writes u8 t_timer; //Throttle Timer - times how long throttle is high u16 t_pos; //Throttle Position (stick position) u8 highest_t; //Highest throttle position between writes to memory u8 lowest1; //Lowest pack1 voltages between writes to memory u8 lowest2; //Lowest pack2 voltages between writes to memory u8 p; //Used for short time delays and iterations //FUNCTIONS section =========================================================== void write_data() //Function to write to EEPROM flash memory { intcon.7 = 0; //Disable all interrupts if not done elsewhere pir1.7 = 0; //EEIF: Clear this bit which gets set after each write eecon2 = 0x55; //Required before writing to memory eecon2 = 0xAA; //Required before writing to memory eecon1.1 = 1; //WR: Writes data eedata to address eeadr while(eecon1.1==1); //Wait for write to complete (becomes 0 when complete) eeadr++; //Increment the address by 1 for next write (8 bit 0-255) //intcon.7 = 1; //Enable interrupts if appropriate } //----------------------------------------------------------------------------- void esc_rx() //Function to mirror RX input to ESC output { //Time duration of each 'high' pulse //Timer1 with 1:1 pre-scaler and 4MHz clock should count once every 1us (1000 for a 1ms pulse) //Pulses normally go high for between 1 and 2ms every 20ms or so //Futaba FF9 generates 1312 at low throttle/low trim and 2572 at high throttle (1.0 - ~2.0ms on scope) tmr1h=0; //Initialise Timer1 registers tmr1l=0; pir1.0=0; //TMR1IF 'interrupt' flag (overflows) while (RX==0); //Wait for RX to finish being low t1con.0 = 1; //Start Timer1 while (RX==1); //Wait for RX to finish being high t1con.0 = 0; //Stop Timer1 //Check for jitter //The counter divides the pulse into ~1000 steps //A 3 step jitter has been observed (eg: 1316 then 1319) //This may confuse some ESCs so is filtered out below //One 'click' on FF9 digital trim is 6 steps (eg: 1316 becomes 1322) //Timer1 has two 8bit registers that need to be merged to enable comparisons MAKESHORT(pulse_new, tmr1l, tmr1h);//'pulse_new' now represents duration of pulse //Determine the difference between last 'good' pulse and latest new one pulse_diff = pulse_new - pulse_good; //Set jitter flag if change in pulse is just jitter (ie: changes by 3 steps or less) if (pulse_diff >= -3 && pulse_diff < 0) jitter=1; //-3 to 0 else if (pulse_diff > 0 && pulse_diff <= 3) jitter=1; //0 to +3 else jitter=0; //To avoid being 'over-protective', reset flag after 10 consecutive 'detections' //This prevents the device from holding the ESC at one level for more than ~0.2sec if (jitter==1 && j<10) j++; //Increment jitter counter up to 10 in a row else if (jitter==1 && j>=10) { jitter=0; //Allow new pulse through after 10 consecutive 'failures' j=0; //Reset counter } else j=0; //Reset counter after each 'good' pulse //De-glitch if pulses are too long or short if (pulse_new > 3000) jitter=1; //>2.3ms if (pulse_new < 1000) jitter=1; //<0.76ms //Make ESC go high for same duration //To 'play back' the same duration, set timer registers to values that cause them to overflow //8bit timer registers overflow after reaching '255' so deduct the high pulse count (above) from 255 if (jitter==0) //If there is NO jitter/glitch - replay new signal { tmr1l = 255 - tmr1l; //Set the Timer1 registers to overflow after same duration as measured tmr1h = 255 - tmr1h; pir1.0 = 0; //Reset overflow flag ESC=1; //Set ESC high to play back same duration t1con.0 = 1; //Start Timer1 while(pir1.0==0); //pir1.0 flag becomes 1 when timer overflows t1con.0 = 0; //Stop Timer1 ESC = 0; pulse_good = pulse_new; //Store this latest 'good' pulse } else //If there IS jitter/glitch - replay last 'good' pulse { LOBYTE(tmr1l, pulse_good); //Restore previous 'good' pulse HIBYTE(tmr1h, pulse_good); tmr1l = 255 - tmr1l; //Set the Timer1 registers to overflow tmr1h = 255 - tmr1h; pir1.0 = 0; //Reset overflow flag ESC=1; //Set ESC high to play back same duration t1con.0 = 1; //Start Timer1 while(pir1.0==0); //pir1.0 flag becomes 1 when timer overflows t1con.0 = 0; //Stop Timer1 ESC = 0; } } //----------------------------------------------------------------------------- void run_adc() //Function to run ADC conversions { //Run ADC for(p=0; p<1; p++); //Delay for acquisition capacitor to u8ge (1 = ~14uS) pir1.6 = 0; //Clear the ADC Complete flag adcon0.1 = 1; //GO: Start ADC acquisition while(pir1.6==0); //Wait for conversion to complete (become 1) //Store ADC value in 'packvalue' (special BoostC statement to create the 10bit value by merging two registers) MAKESHORT(packvalue, adresl, adresh); } //----------------------------------------------------------------------------- void pack_values() //Function to trigger ADC conversions and find pack values { //adcon0 structure... //1 Right justify ADC values //0 Use Vdd as reference //00 Not used //10 Input channel (CHS0) is AN2 (THIS PART CHANGES BELOW) //0 ADC capture (GO) not started //1 ADC enabled //Determine 1st pack value adcon0 = 0b10001001; //Makes AN2 channel active run_adc(); //Run ADC conversion div1 = packvalue; //Copy result to appropriate register //Determine 2nd pack value adcon0 = 0b10001101; //Makes AN3 channel active run_adc(); //Run ADC conversion div2 = packvalue; //Copy result to appropriate register } //----------------------------------------------------------------------------- void find_lowest() //Averages 4 measurements and finds lowest pack value { pack1 = 0; //Initialise pack2 = 0; for(k=0; k<4; k++) //Measure pack values 'n' times to smooth results (don't go over 64) { pack_values(); //Determine pack values (resistor dividers actually) pack1 += div1; //Sum each iteration for each pack pack2 += div2; } //Divide by 16 to average results (/4) and use less space/speed up writing to EEPROM memory (/4) //A single ADC value can be as high as 966 so needs dividing by 4 to fit into an 8bit memory location pack1 = pack1>>4; pack2 = pack2>>4; //Find lowest pack (start with pack 1 and then compare against others) lowpack = pack1; if (pack253 && tmr2<73 && lowpack>level3) ack=1; //Accept as acknowledgement if pulse width count is between 0.8 and 1.1ms // and voltage has risen above minimum //Purpose of a range is to reduce likelihood of count being a glitch tmr2 = 0; //Initialise Timer2 } ack=0; //Reset the acknowledgement flag } //----------------------------------------------------------------------------- void level_checks() //Checks voltage thresholds to decide when to warn pilot / cut throttle { /* Levels 1 and 2 disabled if (lowpack>4; //Divide by 16 to reduce size for writing to memory if (t_pos>140) //Throttle ranges 82-160 so 140 is ~75% { if (t_timer<10) //Requires throttle to have been high for over 220ms (10 * ~22ms) { t_timer ++; //Increment timer each time this is checked (ie: every ~220ms) } else //Throttle has been high long enough to avoid 'surge' voltage drops { level_checks(); //Check voltage thresholds and take appropriate action } } else //When the thottle is not high (no special delay) { t_timer = 0; //Reset the 'throttle high' timer level_checks(); //Check voltage thresholds and take appropriate action } //Log 256 data records at regular intervals if (m_timer<325) //This 'if' section captures the highest/lowest values between writes //It also establishes timing of writes to memory //325=~7 seconds (assuming 21.5ms pulse length) = 15min recording { if (t_pos>highest_t) //Record highest throttle position between writes { LOBYTE(highest_t, t_pos);//'t_pos' is 16bit but divide by 16 above fits bottom 8 } if (pack1