Dieser Artikel beschreibt, wie man mit einer Graupner MX-16(R) (oder Verwandten) ein Spektrum Bind-n-Fly (R) Gerät steuern kann. Thema ist nur die elektronische Verbindung und die Übersetzung der Übertragungs-Protokolle mit Hilfe eines Mikrokontrollers. Die Programmierung der Fernsteuerung für den Einsatz mit dem Spektrum(R)-Modell spreche ich hier nicht an.
Manchmal muss der Mensch auch bei Regenwetter seinen inneren Modellpiloten rauslassen können. Häufig lässt sich der Juckreiz mit ein wenig Simulatorfliegen erfolgreich besänftigen. Bei stärkerem Reiz kommt aber der Moment, da muss ein richtiges Modell ran.
Für diese Momente hat Blade den NanoCPX gemacht. Das ist soweit ganz schön, nur sind leider die ganzen Blade Helis mit Spektrum-Empfängern ausgerüstet. Zum Fliegen brauche ich also eine Spektrum-Fernsteuerung. Die gibts von 89CHF an aufwärts, aber wenn man wenigstens eine Gas- und Pitchkurve programmieren möchte, sind schon gegen 300CHF fällig. Ausserdem hab ich schon genug Kram, ich brauche nicht noch eine Fernsteuerung.
Die Lösung
Es handelt sich um ein Sender-Modul, welches ein konventionelles PPM-Signal, wie es an der Lehrer-Schüler-Buchse der meisten Fernsteuerungen anliegt, in das digitale serielle Protokoll übersetzt, mit dem ein Spektrum-Sendemodul gesteuert werden kann. Damit kann ich nun mit meiner eigenen Fernsteuerung jedes Modell mit Spektrum-Empfänger steuern.
Man nehme
- ein Spektrum-Sendemodul aus einer Spektrum-Billig-Funke
es gehen auch die Mickymaus-Sender, alle haben vergleichbare Sendemodule - ein Arduino, praktischerweise gleich ein 3.3V Modell, zb. Arduino Pro Mini
dann kann man die Spannungsversorgung für Sendemodul und Arduino brauchen - einen Spannungsregler 3,3V
falls man der Funke nur 5V zur Verfügung hat, ist ein LowDrop-Regler nötig - allenfalls einen Pegelwandler zur Aufbereitung der schwächlichen Graupner-Signallevel
und ein bisschen Elektronik-Kleinkram, natürlich.
Man lese
- Build your own DSM2 transmitter module (its working!)
wo alles entstanden ist – ein ewig langer Thread, aber wenn man durch ist, hat man die Mittel zum Nachbau in der Hand. - Graupner MX 16 Hott – Spektrum Erweiterung, T8FG Super Umbau Spektrum
das selbe auf deutsch, für zwei verschiedene Funken, aber im Prinzip das Gleiche. Wenn man sich die Anmeldeprozedur antun will, gibts dort eine noch aktuellere Software. - Turnigy ER 9X DSM2 mod, 9x Full Mod DSM2/X
weitere Info, Pin-Out vom Spektrum-TX-Modul
ausführlich und lange, wenn dann der Kopf dreht, ist man bereit, mit dem Nachbau zu beginnen.
Man versuche
erst mal das ganze auf dem Breadboard zum laufen zu bringen. Wenn der Arduino einfach nicht synchronisieren will, liegts vermutlich daran, dass Graupner bei der MX-16 (und scheinbar auch bei ähnlichen Verwandten) relativ niedrige Signalpegel ausgibt, welche der Arduino nicht korrekt auswerten kann. Man kann mit mehr (siehe MX12 und Flugsimulator) oder weniger (siehe SBUS Signal Inverter) Aufwand das Signal aufbereiten. Bei der Aufbereitung wird das Signal invertiert, dies kann im Arduino-Sketch aber kompensiert werden, indem entweder auf der steigenden oder fallenden Flanke getriggert wird (siehe Zeile 127/128). Am einfachsten versucht man einfach, welche Version funktioniert, alternativ schaut sich der entsprechend ausgerüstete Tinkerer das Signal einfach mit dem Oszilloskop an.
Man verbinde
das ganze nach einem detaillierten Schaltplan, den man sich leider gemäss den vorhandenen Komponenten selber aus den Fingern saugen muss. Es gibt zu viele mögliche Kombinationen von Komponenten, als dass man einen allgemeingültigen Schaltplan zeichnen könnte. (Mal ganz abgesehen davon, dass ich das sowieso nur für den Hausgebrauch kann… :) Das hier ist nur eine Doku, kein Step by Step Bericht.
Man programmiere
den Arduino mit folgendem Programm. Hier steht eine ziemlich fortgeschrittene Version der Software, welche das Einlesen des Signals mit hardware-nahen Routinen erledigt. Um den Code zu verstehen, muss man sich ein wenig mit den Atmel-Interrupt-Handlern auseinandersetzen, dafür läuft die Software zuverlässiger, als wenn man das selbe mit den Möglichkeiten der Arduino-IDE erledigen würde.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* PPM to DSM Sends DSM2 signal using low power MLP4DSM Spektrum TX module Based on the code by daniel_arg, modifications by AS aka c2po. Minor tweaks by MH, ChrisR LED an: Bind Mode LED schnell: Fehler, kein PPM LED langsam: OK, PPM liegt an und wird decodiert v.3.03 taster statt jumper für binding v.3.01 anpassung pulslänge für graupner v1.07 June 2011 */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
/* 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 3 of the License, or (at your option) 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 should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ // --- User defines ------------------------------------------------------------------------------------------------- #define MAX_CHANNELS 6 // maximum number of channels we can decode, don't increase this above 8 #define DSM2_CHANNELS 6 // max number of DSM2 Channels transmitted. Some tranceiver modules // can't handle mor than six channels. Don't increase this above 8 // ------------------------------------------------------------------------------------------------------------------ #define NODEBUG // Change this to #define DEBUG if needed #if F_CPU == 16000000 #define TICKS_PER_uS 2 // number of timer ticks per 1 microsecond with prescaler = 8 and CPU 8MHz #elif F_CPU == 8000000 #define TICKS_PER_uS 1 // number of timer ticks per 1 microsecond with prescaler = 8 and CPU 8MHz #else #error This version supports only 16 and 8MHz processors #endif typedef enum { NULL_ST = -1, NOT_SYNCHED, ACQUIRING, READY, FAILSAFE } State_t; #define MIN_IN_PULSE ( 750 * TICKS_PER_uS) // valid pulse must be at least 750us #define MAX_IN_PULSE (2250 * TICKS_PER_uS) // valid pulse must be less than 2250us #define SYNC_GAP_LEN (5000 * TICKS_PER_uS) // we assume a space at least 5000us is sync #define VALID_FRAMES 10 // must have this many consecutive valid frames to transition to the ready state. #define BINDING_PIN 4 // Pin used to bind #define GREEN_LED 13 // Pin used for board LED #define SLOW 1000*2 // LED slow flash interval in ms #define FAST 150*2 // LED fast flash interval in ms static int Pulses[MAX_CHANNELS + 1]; // array holding channel pulses width value in microseconds // Pulses[0] holds the sync pulse length for debugging static int Failsafe[MAX_CHANNELS + 1]; // array holding channel fail safe values static byte ChannelNum; // number of channels detected so far in the frame (first channel is 1) static byte ChannelCnt; // the total number of channels detected in a complete frame static State_t State; // this will be one of the following states: Null, Not_Synched, Acquiring, Ready, Failsafe static State_t PPM_State; static byte stateCount; // counts the number of times this state has been repeated static byte DSM2_Header[2] = {0x00,0x00}; // DSM2 header bytes static byte DSM2_Channel[] = {0x00,0xAA,0x05,0xFF,0x09,0xFF,0x0D,0xFF,0x13,0x54,0x14,0xAA,0x19,0xaa,0x1d,0xaa}; // DSM2 init data. // Number of DSM2 channels transmitted is defined by DSM2_CHANNELS!!! static byte DSM2_Sent = 0; static byte ChanIndex[] = {1,2,3,4,5,6,7,8};// PPM to DSM2 Channel Mapping Table static byte count; static int pulse; static int flash_duration ; // flashing LED duration calc /* ---------- ---------- ---------- Sync ---------- ---------- ---------- */ static void processSync() { // sync pulse was detected so reset the channel to first and update the system state Pulses[0] = ICR1 / TICKS_PER_uS; // save the sync pulse duration for debugging if(State == READY) { if( ChannelNum != ChannelCnt) // if the number of channels is unstable, go into failsafe State = FAILSAFE; } else { if(State == NOT_SYNCHED) { State = ACQUIRING; // this is the first sync pulse, we need one more to fill the channel data array stateCount = 0; } else { if( State == ACQUIRING) { if(++stateCount > VALID_FRAMES) { State = READY; // this is the second sync and all channel data is ok so flag that channel data is valid ChannelCnt = ChannelNum; // save the number of channels detected } } else if( State == FAILSAFE) { if(ChannelNum == ChannelCnt) // did we get good pulses on all channels? State = READY; } } } ChannelNum = 0; // reset the channel counter } /* ---------- ---------- ---------- Interrupt ---------- ---------- ---------- */ ISR(TIMER1_OVF_vect) { if(State == READY) { State = FAILSAFE; // use fail safe values if signal lost ChannelNum = 0; // reset the channel count } } ISR(TIMER1_CAPT_vect) { // we want to measure the time to the end of the pulse TCNT1 = 0; // reset the counter if(ICR1 >= SYNC_GAP_LEN) // is the space between pulses big enough to be the SYNC processSync(); else if(ChannelNum < MAX_CHANNELS) { // check if its a valid channel pulse and save it if((ICR1 >= MIN_IN_PULSE) && (ICR1 <= MAX_IN_PULSE)) // check for valid channel data Pulses[++ChannelNum] = ICR1 / TICKS_PER_uS; // store pulse length as microseconds else if(State == READY) { State = FAILSAFE; // use fail safe values if input data invalid ChannelNum = 0; // reset the channel count } } } /* ---------- ---------- ---------- Class ---------- ---------- ---------- */ class PPM_Decode { public: PPM_Decode() { // Constructor // empty } void begin() { pinMode(8, INPUT); // Timer1 interrupt handler uses pin 8 as input, do not change it ChannelNum = 0; State = NOT_SYNCHED; TCCR1A = 0x00; // COM1A1=0, COM1A0=0 => Disconnect Pin OC1 from Timer/Counter 1 // PWM11=0, PWM10=0 => PWM Operation disabled // TCCR1B = (1<<ICES1) | (1<<CS11); // capture using rising edge, prescaler = 8 TCCR1B = (1<<CS11); // capture using falling edge, prescaler = 8 // 8MHz clock with prescaler 8 means TCNT1 increments every 1 uS TIMSK1 = _BV(ICIE1)|_BV (TOIE1); // enable input capture and overflow interrupts for timer 1 for(byte ch = 1; ch <= MAX_CHANNELS; ch++) { Failsafe[ch] = Pulses[ch] = 1500; // set midpoint as default values for pulses and failsafe } Failsafe[1] = Pulses[1] = 1100; // set channel 1 failsafe pulse width to min throttle } State_t getState() { return State; } byte getChannelCnt() { return ChannelCnt; } void setFailsafe(byte ch, int value) { // pulse width to use if invalid data, value of 0 uses last valid data if((ch > 0) && (ch <= MAX_CHANNELS)) Failsafe[ch] = value; } void setFailsafe() { // setFailsafe with no arguments sets failsafe for all channels to their current values if(State == READY) // useful to capture current tx settings as failsafe values for(byte ch = 1; ch <= MAX_CHANNELS; ch++) Failsafe[ch] = Pulses[ch]; } int getChannelData(uint8_t channel) { // this is the access function for channel data int result = 0; // default value if(channel <= MAX_CHANNELS) { if((State == FAILSAFE) && (Failsafe[channel] > 0 )) result = Failsafe[channel]; // return the channels failsafe value if set and State is Failsafe else if((State == READY) || (State == FAILSAFE)) { cli(); // disable interrupts result = Pulses[channel]; // return the last valid pulse width for this channel sei(); // enable interrupts } } return result; } }; PPM_Decode Receiver = PPM_Decode(); void setup() { delay(100); #ifdef DEBUG Serial.begin(115200); // print values on the screen #else Serial.begin(125000); // closest speed for DSM2 module, otherwise it won't work #endif Receiver.begin(); pinMode(BINDING_PIN, INPUT); // set binding pin digitalWrite(BINDING_PIN, HIGH); // turn on internal pullup resistor on binding switch pinMode(GREEN_LED, OUTPUT); // define LED pin delay(100); count = 50; while(Receiver.getState() != READY && count-- > 0) // wait 5 sec or until PPM data is stable and ready delay(100); } void loop() { PPM_State = Receiver.getState() ; // local copy of receiver state if(digitalRead(BINDING_PIN) == LOW) { // pullup ist aktiviert, eingang geht auf GND: für jumper: HIGH, für taster: LOW DSM2_Header[0] = 0x80; // set bind bit in header DSM2_Header[1] = 0x00; // second header byte digitalWrite(GREEN_LED, HIGH); // turn on the binding LED } else { // normal operation DSM2_Header[0] = 0x00; // set normal ops header DSM2_Header[1] = 0x00; // second header byte if(PPM_State == READY) // if PPM is up and running flash_duration = SLOW ; // flash LED slow else flash_duration = FAST ; // else flash LED fast if(millis()%(flash_duration) < flash_duration/2) // flash LED routine digitalWrite(GREEN_LED, LOW); else digitalWrite(GREEN_LED, HIGH); } if(PPM_State == READY || PPM_State == FAILSAFE) { if(ChannelNum == 0 || ChannelNum == ChannelCnt) { // during sync pulse or in failsafe if(DSM2_Sent == 0) { // if DSM2 frame is not sent yet for (byte i = 0; i < DSM2_CHANNELS; i++) { // get receiver data pulse = Receiver.getChannelData(ChanIndex[i]) - 978; //original: -1000, angepasst für graupner mx.. pulse = constrain(pulse, 0, 0x3FF); DSM2_Channel[i*2] = (byte)(i<<2) | highByte(pulse); DSM2_Channel[i*2+1] = lowByte(pulse); } sendDSM2(); // send frame DSM2_Sent = 1; // frame sent flag } else { if(PPM_State == FAILSAFE) { delay(20); // in case of failsafe DSM2_Sent = 0; // reset flag after delay } } } else { if(ChannelNum == 1) // after first channel is received DSM2_Sent = 0; // reset flag for the next frame } } } #ifndef DEBUG void sendDSM2() { Serial.write(DSM2_Header, 2); Serial.write(DSM2_Channel, DSM2_CHANNELS*2); } #else void sendDSM2() { Serial.print(" "); // print DSM2 header serialPrintHex(DSM2_Header[0]); Serial.print(" "); serialPrintHex(DSM2_Header[1]); Serial.print(" | "); for(byte i=0; i < DSM2_CHANNELS; i++) { // print channels Hex and Dec serialPrintHex(DSM2_Channel[i*2]); serialPrintHex(DSM2_Channel[i*2+1]); Serial.print(" ("); Serial.print("K"); Serial.print((DSM2_Channel[i*2]>>2)+1, DEC); Serial.print(": "); Serial.print((((DSM2_Channel[i*2]&0x03)<<8 | DSM2_Channel[i*2+1])-512)/5, DEC); Serial.print(") "); } Serial.print(Receiver.getChannelData(0), DEC); // sync pulse length Serial.println(" "); delay(200); } void serialPrintHex(byte b) { byte b1 = (b >> 4) & 0x0F; byte b2 = (b & 0x0F); char c1 = (b1 < 10) ? ('0' + b1) : 'A' + b1 - 10; char c2 = (b2 < 10) ? ('0' + b2) : 'A' + b2 - 10; Serial.print(c1); Serial.print(c2); } #endif |
man verpacke
das ganze in ein hübsches Kistchen
Man fliege
Enjoy! :)
Pingback: Spektrum-Sender im JR Modul |