Im 2. Teil der Reihe zur Entschlüsselung des RF-603 Funkprotokoll geht es um den A7105 Transceiver. Nachdem die XL7105-SY Module von DX eingetroffen sind. Damit sollte das Senden/Empfangen der Yongnuo RF-603 Blitzauslöser Nachrichten endlich gelingen.
Aber auch mit einem identischen Funk Transceiver ist der Weg recht steinig und mühsam. Leider wird der A7105 Chip nicht so gut unterstützt, wie der NRF24L01. Der Funk Chip wird in einigen Quadrocopter eingesetzt, wie z.B. dem Hubsan X4. Dazu findet man einige Threads im RC-Forum und auf Instrctables:
Dort geht es aber nur um das Senden von Nachrichten, da die Quadrocopter ja keinen Rückkanal haben.
Hardware
Nebem dem XL7105-SY Modul wird ein Arduino mit 3,3V Betriebspannung benötigt.
Achtung: Das verwendete Modul hat keine 5V toleranten Eingänge!
XL7105 Modul
Das DX Modul kommt im 2mm Rastermass daher. Deshalb gilt es zunächst, daraus ein zum NRF24L01 pin-kompatibles Modul zu basteln. Damit kann einfach zwischen den verschiedenen Modulen hin-und hergetauscht werden.

Eine Besonderheit beim A7105 ist die 3-Pin SPI Schnittstelle, mit nur einer Datenleitung für Senden/Empfangen. Es gibt aber die Möglichkeit eine normale 4-Pin SPI Schnittstelle zu konfigurieren. Folgende Beschaltung hat sich für mich als sinnvoll werwiesen:
NRF24L01 Modul | XL7105 Pin |
---|---|
1 GND | 7 GND, 10 GND |
2 VCC | 9 VDD |
3 CE | 12 SCS |
4 CSN | --- |
5 SCK | 13 SCK |
6 MOSI | 14 SDIO |
7 MISO | 2 GIO1 |
8 IRQ | 4 GIO2 |

Reverse Engineering
Um nun das RF-603 Übertragungsformat zu knacken, gibt es 2 Ansatzpunkte.
- Funk Sniffer, mit dem XL7105 Modul alle Pakete mitlauschen
- SPI Sniffer, die SPI Signale am RF-603 anzapfen
Bei Methode 1 hakt es wie schon beim NRF24L01 am fehlenden Promiscous Mode. Außerdem gibt es zahlreiche Einstellmöglichkeiten, die man erst mal alle finden und durchtesten muss. Hier schien mir die 2. Methode des SPI Sniffing die erfolgversprechende. Zum einen sind auf der RF-603 Leiterplatte die SPI Pins auf Testpins gelegt und markiert, zudem zudem verfüge ich mit dem LogicPort über einen Logik Analyzer mit SPI Protokoll Interpreter.
RF603 Initialisierung
Schauen wir uns zunächst die Initialisierung des A7105 Chips an. Daraus lassen sich vielleicht schon einige wertvolle Rückschlüsse schließen. Leider reicht der Speicher des Logic Analyzers nicht aus, alle Initialisierungs-Befehle zu erfassen, aber die wichtigsten sollten dabei sein.
Register | Wert RF603 | Anmerkungen | Wert XL7105-SY | Anmerkungen | Beschreibung |
---|---|---|---|---|---|
0x00 | 0x00 | reset command | Mode register | ||
0x01 | 0x42 | Data filter by CD, FIFO Mode | Mode control register | ||
0x02 | -- | Calibration control register | |||
0x03 | 0x01 | FIFO end pointer 1 | FIFO register 1 | ||
0x04 | 0x00 | FIFO register 2 | |||
0x05 | 0x00 | FIFO data register | |||
0x06 | 0x00 | ID 0000 | ID Data register | ||
0x07 | 0x00 | RC OSC register 1 | |||
0x08 | 0x00 | RC OSC register 2 | |||
0x09 | 0x00 | RC OSC register 3 | |||
0x0a | 0x00 | CKO pin control register | |||
0x0b | 0x01 | WaitTillRx or TX finished GIO1 enable | 0x19 | SDO 4 wire SPI | GIO1 pin control register |
0x0c | 0x21 | RxD direct mode | 0x05 | Frame sync | GIO2 pin control register |
0x0d | 0x05 | Clock register | |||
0x0e | 0x00 | Data rate register | |||
0x0f | 0x9E | Channel number | 0x70 | default chan | PLL register 1 |
0x10 | 0x9E | PLL register 2 | |||
0x11 | 0x48 | PLL register 3 | |||
0x12 | 0x00 | PLL register 4 | |||
0x13 | 0x02 | PLL register 5 | |||
0x14 | 0x16 | TX register 1 | |||
0x15 | 0x2B | TX register 2 | |||
0x16 | 0x12 | Delay register 1 | |||
0x17 | 0x00 | Delay register 2 | |||
0x18 | 0x62 | Bandwith 500kHz | RX register | ||
0x19 | 0x80 | RX gain register 1 | |||
0x1A | 0x80 | RX gain register 2 | |||
0x1B | 0x00 | RX gain register 3 | |||
0x1C | 0x0A | RX gain register 4 | |||
0x1D | 0x32 | RSSI threshold register | |||
0x1E | 0xc3 | ADC control register | |||
0x1F | 0x07 | 4Byte Preambel, 2Byte ID, no CRC | Code register 1 | ||
0x20 | 0x16 | Code register 2 | |||
0x21 | 0x00 | Code register 3 | |||
0x22 | 0x00 | IF calibration register 1 | |||
0x23 | -- | IF calibration register 1 | |||
0x24 | -- | VCO current calibration register | |||
0x25 | -- | VCO single band calibration register 1 | |||
0x26 | -- | VCO single band calibration register 2 | |||
0x27 | -- | Battery detect register |
RF603 Funkprotokoll
Das Protokoll auf der Funkstrecke ergibt sich aus der Initialisierung der A7105 Register und dem gesendeten/empfangenen Daten über die SPI-Schnittstelle.

<4 Byte Präambel> <4 Byte ID> <2 Byte Daten>
Die Daten Bytes bestehen aus 2 Byte Folgen, die aus Doppelzahlen wie 0x44 oder 0xBB bestehen. Die Summe der Byte Folgen ergibt FF. Die Kommandos werden alle 20ms (16ms beim RF-603-II) wiederholt, solange die Taste gedrückt wurde. Bei Taste losgelassen gibt es kein eigenes Kommando, es wird einfach nichts mehr gesendet.
Kommando | Daten RF-603 |
---|---|
WakeUp/Fokus | 44, BB |
Start | 88, 77 |
Die ID mit der gesendet/empfangen wird, bleibt noch offen. Diesen Teil konnte der Logic Analyzer leider nicht aufzeichnen.
RF-603 Funkkanäle
Die Funkkanäle des RF-603 lassen sich über 4 DIP Schalter im Batteriefach einstellen. Die Werte für die Kanalnummern wurden durch SPI Sniffing an einem RF-603 ermittelt. Die Sende- und Empfangsfrequenz liegen um 1 Kanal auseinander.
DIP Schalter 1234 (0=off, 1=on) | Sendekanal (in Hex) | Empfangskanal (in Hex) |
---|---|---|
0000 | 71 | 70 |
1000 | 6B | 6A |
0100 | 65 | 64 |
1100 | 59 | 58 |
0010 | 53 | 52 |
1010 | 4D | 4C |
0110 | 41 | 40 |
1110 | 3B | 3A |
0001 | 35 | 34 |
1001 | 29 | 28 |
0101 | 23 | 22 |
1101 | 1D | 1C |
0011 | 17 | 16 |
1011 | 11 | 10 |
0111 | 0B | 0A |
1111 | 05 | 04 |
A7105 Sniffer
Um die bisherigen Erkenntnisse zu prüfen, steht als nächster Schritt ein Arduino Sketch zum Sniffen der A7105. Ähnlich wie schon der NRF24L01 unterstützt auch der A7105 keinen ‚promiscous Mode‘ zum Empfang aller Daten. Aber auch hier hilft der Trick weiter, bei der Initialisierung des Chips die Präambel auf einen kleineren Wert zu stellen und die Geräte ID, die ja nach wie vor unbekannt ist, auf den Wert der Präambel (0x55) zu setzen.
Nach einigem Rumprobieren ergab sich eine für diesen Zweck recht günstige Konfiguration:
- 2 Byte Präambel
- 4 Byte ID (0x55555555)
- 6 Byte Payload
Das Ergebnis sieht schon ganz gut aus, es gibt nur wenige falsche Pakete:
55 4D 66 66 96 91
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
35 99 9A 5A 44 BB
Man erkennt die Folge 0x44 0xBB, das entspricht dem bereits bekanntem Befehl zum WakeUp/Fokus. Und jetzt haben wir auch die Geräte ID (0x35, 0x99, 0x9A, 0x5A). Damit sind eigentlich alle Geheimnisse gelüftet und das Senden/Empfangen von Paketen sollte kein Problem mehr darstellen.
Doch weit gefehlt. Stellt man die ID entsprechend ein, Präambel auf 4 Byte und die Payload auf 2 Byte, passiert nichts, weder Senden noch Empfangen funktioniert damit. Aber sei es drum, als ‚Proof of concept‘ geht es auch mit den jetzigen Einstellungen. Auch das Senden funktioniert, wenn man die ID als Payload mitsendet. Mission halbwegs erfüllt.
Das verwendete Sketch basiert auf dem Flysky Tx Code von midelic aus dem RCgroups.com Forum. Über das Terminal werden die empfangenen Befehle ausgegeben. Befehle senden geht ebenfalls, mit ‚f‘ für Focus und ’s‘ für Start lauten die Befehle, die über das Terminal gesendet werden müssen.
Der komplette Quellcode befindet sich auf GitHub.
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 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
#include "a7105.h" // Yongnuo RF-603 sniffer // using n A7105 chip and a Arduino /pro/micro with 3.3V // based on Flysky Tx Code by midelic on RCgroups.com // // created by RobotFreak // www.photofreak.de #define SNIFF_MODE #if defined (SNIFF_MODE) #define PAYLOAD_SIZ 6 uint8_t id[4] = { 0x55, 0x55, 0x55, 0x55 }; #else #define PAYLOAD_SIZ 2 uint8_t id[4] = { 0x35, 0x99, 0x9A, 0x5A }; // uint8_t id[4] = { 0x5A, 0x9A, 0x99, 0x35 }; #endif static const uint8_t A7105_regs[] = { 0xFF, // MODE_REG 0x00 0x42, // MODECTRL_REG 0x01 0xFF, // CALIBRATION_REG 0x02 PAYLOAD_SIZ - 1, // FIFO1_REG 0x03 0x01 0x00, // FIFO2_REG 0x04 0x00, // FIFO_REG 0x05 0x00, // IDCODE_REG 0x06 0x00, // RCOSC1_REG 0x07 0x00, // RCOSC2_REG 0x08 0x00, // RCOSC3_REG 0x09 0x00, // CKO_REG 0x0A 0x01, // GPIO1_REG 0x0B 0x21, // GPIO2_REG 0x0C 0x05, // CLOCK_REG 0x0D 0x00, // DATARATE_REG 0x0E 0x50, // PLL1_REG 0x0F 0x9E, // PLL2_REG 0x10 0x4B, // PLL3_REG 0x11 0x00, // PLL4_REG 0x12 0x02, // PLL5_REG 0x13 0x16, // TX1_REG 0x14 0x2B, // TX2_REG 0x15 0x12, // DELAY1_REG 0x16 0x00, // DELAY2_REG 0x17 0x62, // RX_REG 0x18 0x80, // RXGAIN1_REG 0x19 0x80, // RXGAIN2_REG 0x1A 0x00, // RXGAIN3_REG 0x1B 0x0A, // RXGAIN4_REG 0x1C 0x32, // RSSI_REG 0x1D 0xC3, // ADC_REG 0x1E #if defined (SNIFF_MODE) 0x05, // CODE1_REG 0x1F #else 0x07, // CODE1_REG 0x1F #endif 0x16, // CODE2_REG 0x20 0x00, // CODE3_REG 0x21 0x00, // IFCAL1_REG 0x22 0x00, // IFCAL2_REG 0x23 0x00, // VCOCCAL_REG 0x24 0x00, // VCOCAL1_REG 0x25 0x3B, // VCOCAL2_REG 0x26 0x00, // BATTERY_REG 0x27 0x17, // TXTEST_REG 0x28 0x47, // RXDEM1_REG 0x29 0x80, // RXDEM2_REG 0x2A 0x03, // CPC_REG 0x2B 0x01, // CRYSTALTEST_REG 0x2C 0x45, // PLLTEST_REG 0x2D 0x18, // VCOTEST1_REG 0x2E 0x00, // VCOTEST2_REG 0x2F 0x01, // IFAT_REG 0x30 0x0F, // RSCALE_REG 0x31 0x00, // FILTERTEST_REG 0x32 }; //Spi Comm.pins with A7105/PPM #define SDI_pin 11 //SDIO-D11 #define SCLK_pin 13 //SCK-D13 #define CS_pin 10//CS-D10 #define GIO_pin 2 //--------------------------------- #define CS_on PORTB |= 0x04 //D10 #define CS_off PORTB &= 0xFB //D10 // #define SCK_on PORTB |= 0x20//D13 #define SCK_off PORTB &= 0xDF//D13 #define SDI_on PORTB |= 0x08 //D11 #define SDI_off PORTB &= 0xF7 //D11 // #define SDI_1 (PINB & 0x08) == 0x08 //D11 #define SDI_0 (PINB & 0x08) == 0x00 //D11 // #define RED_LED_pin A3 #define Red_LED_ON PORTC |= _BV(3); #define Red_LED_OFF PORTC &= ~_BV(3); #define NOP() __asm__ __volatile__("nop") //########## Variables ################# static uint8_t channel; static byte counter = 255; static uint8_t aid[4];//for debug only static uint8_t packet[21];//inside code there are 16....so don't bother void setup() { Serial.begin(57600); pinMode(RED_LED_pin, OUTPUT); //RF module pins pinMode(SDI_pin, OUTPUT);//SDI SDIO pinMode(SCLK_pin, OUTPUT);//SCLK SCL pinMode(CS_pin, OUTPUT);//CS output CS_on;//start CS high SDI_on;//start SDIO high SCK_off;//start sck low // //for debug delay(10);//wait 10ms for A7105 wakeup A7105_reset();//reset A7105 delay(100); Serial.print("ID: "); A7105_WriteID(&id[0]); A7105_ReadID();//for debug only A7105_DumpRegs(); // Dump registers A7105_Calibrate(); // calibrate A7105 // set read mode _spi_write_adress(0x0F, 0x70); _spi_strobe(A7105_STANDBY); _spi_strobe(A7105_RST_RDPTR); _spi_strobe(A7105_RX); } uint8_t in[PAYLOAD_SIZ]; uint8_t out[PAYLOAD_SIZ]; //############ MAIN LOOP ############## void loop() { int i, c; int len; char cmd; if (digitalRead(GIO_pin) == LOW) { len = PAYLOAD_SIZ; A7105_ReadData(len, &in[0]); // set read mode _spi_write_adress(0x0F, 0x70); _spi_strobe(A7105_STANDBY); _spi_strobe(A7105_RST_RDPTR); _spi_strobe(A7105_RX); // delay(10); } else { if (Serial.available()) { out[0] = 0x35; out[1] = 0x99; out[2] = 0x9a; out[3] = 0x5a; cmd = Serial.read(); switch (cmd) { case 'f': out[4] = 0x44; out[5] = 0xBB; break; case 's': out[4] = 0x88; out[5] = 0x77; break; default: break; } if (cmd == 'f' or cmd == 's') { channel = 0x71; len = PAYLOAD_SIZ; Serial.print("chan: "); Serial.println(channel, HEX); for (c = 0; c < 16; c++) { _spi_strobe(A7105_STANDBY); _spi_strobe(A7105_RST_WRPTR); _spi_write_adress(0x0F, 0x71); A7105_WriteData(len, &out[0]); _spi_strobe(A7105_TX); delay(2); // set read mode _spi_write_adress(0x0F, 0x70); _spi_strobe(A7105_STANDBY); _spi_strobe(A7105_RST_RDPTR); _spi_strobe(A7105_RX); delay(18); } } } } } //------------------------------- //------------------------------- //A7105 SPI routines //------------------------------- //------------------------------- //-------------------------------------- void A7105_reset(void) { _spi_write_adress(0x00, 0x00); } //-------------------------------------- void A7105_WriteID(uint8_t *ida) { int i; CS_off; _spi_write(0x06); for (i = 0; i < 4; i++) { _spi_write(*ida); Serial.print(*ida, HEX); ida++; } Serial.println(""); CS_on; } //-------------------------------------- void A7105_ReadID() { int i; CS_off; _spi_write(0x46); for (i = 0; i < 4; i++) { aid[i] = _spi_read(); } CS_on; } //-------------------------------------- void A7105_DumpRegs() { int i; uint8_t r; for (i = 0; i < 0x33; i++) { if (A7105_regs[i] != 0xff) _spi_write_adress(i, A7105_regs[i]); r = _spi_read_adress(i); Serial.print(r, HEX); Serial.print(" "); } Serial.println(""); } //-------------------------------------- void A7105_Calibrate() { uint8_t if_calibration1; uint8_t vco_calibration0; uint8_t vco_calibration1; _spi_strobe(0xA0);//stand-by _spi_write_adress(0x02, 0x01); while (_spi_read_adress(0x02)) { if_calibration1 = _spi_read_adress(0x22); if (if_calibration1 & 0x10) { //do nothing } } _spi_write_adress(0x24, 0x13); _spi_write_adress(0x26, 0x3b); _spi_write_adress(0x0F, 0x00); //channel 0 _spi_write_adress(0x02, 0x02); while (_spi_read_adress(0x02)) { vco_calibration0 = _spi_read_adress(0x25); if (vco_calibration0 & 0x08) { //do nothing } } _spi_write_adress(0x0F, 0xA0); _spi_write_adress(0x02, 0x02); while (_spi_read_adress(0x02)) { vco_calibration1 = _spi_read_adress(0x25); if (vco_calibration1 & 0x08) { //do nothing } } _spi_write_adress(0x25, 0x08); _spi_write_adress(0x28, 0x1F); //set power to 1db maximum _spi_strobe(0xA0);//stand-by strobe command delay(100); _spi_strobe(A7105_RST_WRPTR); _spi_write_adress(0x0F, 0x70); _spi_strobe(A7105_STANDBY); _spi_strobe(A7105_RST_RDPTR); _spi_strobe(A7105_RX); } //-------------------------------------- void A7105_ReadData(int len, uint8_t * data) { uint8_t i; CS_off; _spi_write(0x45); Serial.print("<"); for (i = 0; i < len; i++) { *data = _spi_read(); Serial.print(*data, HEX); Serial.print(" "); data++; } Serial.println(""); CS_on; } //-------------------------------------- void A7105_WriteData(int len, uint8_t * data) { int i; CS_off; _spi_write(0x05); Serial.print(">"); for (i = 0; i < len; i++) { _spi_write(*data); Serial.print(*data, HEX); Serial.print(" "); data++; } Serial.println(""); CS_on; } //-------------------------------------- void _spi_write(uint8_t command) { uint8_t n = 8; SCK_off; SDI_off; while (n--) { if (command & 0x80) SDI_on; else SDI_off; SCK_on; NOP(); SCK_off; command = command << 1; } SDI_on; } //-------------------------------------- void _spi_write_adress(uint8_t address, uint8_t data) { CS_off; _spi_write(address); NOP(); _spi_write(data); CS_on; } //----------------------------------------- uint8_t _spi_read(void) { uint8_t result; uint8_t i; result = 0; pinMode(SDI_pin, INPUT); //make SDIO pin input //SDI_on; for (i = 0; i < 8; i++) { if (SDI_1) ///if SDIO =1 result = (result << 1) | 0x01; else result = result << 1; SCK_on; NOP(); SCK_off; NOP(); } pinMode(SDI_pin, OUTPUT); //make SDIO pin output again return result; } //-------------------------------------------- uint8_t _spi_read_adress(uint8_t address) { uint8_t result; CS_off; address |= 0x40; _spi_write(address); result = _spi_read(); CS_on; return (result); } //------------------------ void _spi_strobe(uint8_t address) { CS_off; _spi_write(address); CS_on; } |
Fazit
Mit dem entschlüsselten Protokoll und dem A7105_Sniffer Sketch ist der Grundstein gelegt, einen RF-603 Transceiver zu simulieren, bzw. auf die Kommandos des Funk Transceivers zu reagieren. Damit ergeben sich interessante Anwendungsmöglichkeiten:
In einem weiteren Beitrag ist geplant, das A7105-Modul mit Hardware SPI im 4-Bit Mode zu betreiben. Vielleicht lassen sich auch noch die Probleme mit der Geräte ID klären.
Hi,
super Artikel! Ich habe ebenfalls den Logikport Analyer. Bezüglich:
>Leider reicht der Speicher des Logic Analyzers nicht aus, alle Initialisierungs-Befehle zu erfassen
Du kannst entweder die Samplerate des LA herabsetzen, oder auch in den Triggereinstellungen z.B. die ersten z.B. 80 Flanken überspringen, so dass Du sehen kannst was später kommt. Vielleicht hast Du ja das Gerät mit den angezapften Leitungen noch da liegen und hast Lust nochmal reinzumessen?
Stefan