// Simple Galloping Ghost recoder for DigiSpark ATTiny85 board and DRV8838 bridge - Phil_G // For Tobe GG actuator, genuine Rand or MM Birdcage actuator, or rudder-only Adams clone // Rate 2-14 pulses per second controlled by elevator channel or default 7cps (ELEV_DFLT) if no elevator channel present // Mark-space between 30%-70% controlled by rudder channel // No specific GG throttle, use conventional ESC or throttle servo for IC #define rudder_ch 0 // receiver rudder channel connected to P0 (ATTiny physical pin 5) Set PCMSK to match #define elev_ch 2 // receiver elevator channel connected to P2 (ATTiny physical pin 7) #define DRV8838 1 // LED and bridge drive, output to GG H-bridge #define PW_FAST 86 // to match PIC recoder values #define PW_SLOW 270 // to match PIC recoder values #define RUDD_DFLT 1500 // neutral rudder on power-up #define ELEV_DFLT 1350 // default rate for Adams rudder-only if elevator signal not present volatile unsigned long timer_ch1_R, timer_ch2_E; // all timer variables are unsigned long volatile int pulse_time_R = RUDD_DFLT, pulse_time_E = ELEV_DFLT; volatile byte ch1Rwas = 0, ch2Ewas = 0; // previous hi/lo state int rudder_time = RUDD_DFLT, elevator_time = ELEV_DFLT; // measured channel pulses from rx unsigned long rudder_marksp = 0, elev_rate = 0, settle_time; unsigned long bridge_cycle = 0; // total cycle time in ms unsigned long bridge_left = 0, bridge_right = 0; // bridge left/right times in ms void setup() { pinMode(rudder_ch, INPUT_PULLUP); // pullups in case rx isnt connected pinMode(elev_ch, INPUT_PULLUP); // avoids picking up hash pinMode(DRV8838, OUTPUT); // o/p to DRV8838 timer_ch1_R = 0; timer_ch2_E = 0; GIMSK = (1 << PCIE); // Enable Pin Change Interrupts PCMSK = (1 << rudder_ch) | (1 << elev_ch); // Enable interrupts for rx channel inputs sei(); settle_time = micros() + 500000; // 500 milliseconds to allow rx outputs to settle on power-up } void loop() { if (micros() < settle_time) { pulse_time_R = RUDD_DFLT; pulse_time_E = ELEV_DFLT; } cli(); rudder_time = pulse_time_R; elevator_time = pulse_time_E; // do an atomic copy in the quickest way sei(); rudder_time = constrain(rudder_time, 1000, 2000); elevator_time = constrain(elevator_time, 1000, 2000); rudder_marksp = map(rudder_time, 1000, 2000, 27, 73); // generous map to 30% - 70% bridge_cycle = map(elevator_time, 1000, 2000, PW_FAST, PW_SLOW); // 86ms=12hz to 270ms=3.7hz, matches PIC recoder bridge_left = bridge_cycle * rudder_marksp / 100; bridge_right = bridge_cycle - bridge_left; // waggle the bridge left & right digitalWrite(DRV8838, HIGH); delay(bridge_left); digitalWrite(DRV8838, LOW); delay(bridge_right); } ISR(PCINT0_vect) // read the receiver channels into timer_ch1_R & timer_ch2_E { if (PINB & (1 << rudder_ch)) { // Rx ch 1 if (ch1Rwas == 0) { timer_ch1_R = micros(); ch1Rwas = 1; } } else { if (ch1Rwas == 1) { pulse_time_R = ((volatile int)micros() - timer_ch1_R); ch1Rwas = 0; } } if (PINB & (1 << elev_ch)) { // Rx ch 2 if (ch2Ewas == 0) { timer_ch2_E = micros(); ch2Ewas = 1; } } else { if (ch2Ewas == 1) { pulse_time_E = ((volatile int)micros() - timer_ch2_E); ch2Ewas = 0; } } }