8

TL;DR - I can't figure out why I'm failing to replicate the Quadcopter's remote control with, say, Arduino and NRF24L01 after eavesdropping the SPI commands on the remote control.

So I got this cheap quadcopter, actually 3 different models (this, this and this, and the principle is the same with all three) All models use the BK2423 radio, which should be air-compatible with the NRF24l01 - which is what I have.

So since I have found (surprisingly) very little information on this matter online, I put some probes between the remote control's MPU and it's BK2423 and used Saleae Logic Analyzer to read the communication running on the SPI bus between the two. What I have find out is it has 3 main stages: Config, Search/Bind and Command, where in between there are some tweaks that the MCU performs, as I described below.

On the Config phase, some registers are being set, like the pipe number for Rx and the address, RF channel and more. Here is part of the config capture: config phase

So the initial Tx (and Rx) address is 5 bytes: 0xCCCCCCCCCC. And the initial RF Channel is 0x2D. The default address length for the two chips is 5 bytes and since the remote control does not change it, the length should stay 5 bytes.

Then comes the search phase. In that phase, the remote sends basically the same data over and over, and only changes the RF channel. Here is the last search packet:

Search stage

So after many transmissions with different RF channels, something weird happens. The remote hits the correct RF channel for the quadcopter, but I can't figure out how he understands that, since the Status register (on the MISO channel) stays the same on the last search packets. Here is where the remote control MCU tells the BK2423 to change the RF Channel, with no evidence to anything else around:

After search phase

Then the MCU tells the BK2423 to change the address and RF channel to the right ones. It must be the right ones since the quadcopter reacts properly:

Changing address and RF channel again

From this point and on, the remote control sends command packets and the quadcopter happily fly around. I couldn't manage to properly pass the search phase.

I used some code from this project (taken from this page) and changed HCD.cpp to match what I sniffed. Here are only the two methods I changed (rest of the code within that file stays the same):

void HCD::bind(unsigned char *ID)
{
  for(unsigned char i=0;i<4;i++)
    noteID[i]=ID[i];

  state=HS_CONNECT;
  radio.offline();
    radio.txbuf[0] = 0x3F;
    radio.txbuf[1] = 0x4C;
    radio.txbuf[2] = 0x84;
    radio.txbuf[3] = 0x6F;
    radio.txbuf[4] = 0x9C;
    radio.txbuf[5] = 0x20;
    radio.sendBuffer(6); 
    radio.txbuf[0] = 0x3E;
    radio.txbuf[1] = 0xC9;
    radio.txbuf[2] = 0x9A;
    radio.txbuf[3] = 0xB0;
    radio.txbuf[4] = 0x61;
    radio.txbuf[5] = 0xBB;
    radio.txbuf[6] = 0xAB;
    radio.txbuf[7] = 0x9C;
    radio.sendBuffer(8); 
    radio.txbuf[0] = 0x39;
    radio.txbuf[1] = 0x0B;
    radio.txbuf[2] = 0xDF;
    radio.txbuf[3] = 0xC4;
    radio.txbuf[4] = 0xA7;
    radio.txbuf[5] = 0x03;
    radio.sendBuffer(6); 

    radio.txbuf[0] = 0x30;
    radio.txbuf[1] = 0xCC;
    radio.txbuf[2] = 0xCC;
    radio.txbuf[3] = 0xCC;
    radio.txbuf[4] = 0xCC;
    radio.txbuf[5] = 0xCC;
    radio.sendBuffer(6);

    radio.txbuf[0] = 0x2A;
    radio.txbuf[1] = 0xCC;
    radio.txbuf[2] = 0xCC;
    radio.txbuf[3] = 0xCC;
    radio.txbuf[4] = 0xCC;
    radio.txbuf[5] = 0xCC;
    radio.sendBuffer(6);

    radio.txbuf[0] = 0xE1;
    radio.txbuf[1] = 0x00;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0xE2;
    radio.txbuf[1] = 0x00;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x27;
    radio.txbuf[1] = 0x70;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x21;
    radio.txbuf[1] = 0x00;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x22;
    radio.txbuf[1] = 0x01;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x23;
    radio.txbuf[1] = 0x03;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x25;
    radio.txbuf[1] = 0x2D;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x24;
    radio.txbuf[1] = 0x00;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x31;
    radio.txbuf[1] = 0x09;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x26;
    radio.txbuf[1] = 0x07;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x50;
    radio.txbuf[1] = 0x73;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x3C;
    radio.txbuf[1] = 0x00;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x3D;
    radio.txbuf[1] = 0x00;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x31;
    radio.txbuf[1] = 0x09;
    radio.sendBuffer(2);

    radio.txbuf[0] = 0x1D;
    radio.txbuf[1] = 0x00;
    radio.sendBuffer(2);


}


void HCD::update(unsigned char *payload)
{
Serial.print("Sending, state: ");
Serial.print(state);
Serial.print("  Channel: ");
Serial.println(channel);
  unsigned char c=0;

  switch(state)
  {
  case HS_CONNECT:

    radio.offline();
    radio.txbuf[0] = 0x27;
    radio.txbuf[1] = 0x70;
    radio.sendBuffer(2); 
    radio.txbuf[0] = 0xE1;
    radio.txbuf[1] = 0x00;
    radio.sendBuffer(2); 
    radio.txbuf[0]=0x25;
    radio.txbuf[1]= channel; //0x2A;
    radio.sendBuffer(2);      //-Chan

    //radio.block(baseAddress);
    //radio.block(flushTX);

    radio.txbuf[0]=0xA0;
    radio.txbuf[1]=0x20;
    radio.txbuf[2]=0x14;
    radio.txbuf[3]=0x03;
    radio.txbuf[4]=0x25;
    radio.txbuf[5]=0x1F; //noteID[0];
    radio.txbuf[6]=0xC3; //noteID[1];
    radio.txbuf[7]=0x00; //noteID[2];
    radio.txbuf[8]=0xE2; //noteID[3];
    radio.txbuf[8]=0xAA; //noteID[3];
    radio.sendBuffer(9);      //-tx data
    //radio.block(PTX);
    radio.online();

    while(!c)
      c=radio.getStatus();
    radio.clrStatus();
     Serial.print("Status: " );
     Serial.println(c);
    if(c&0x10)  //MRT
    {
      state=HS_UNINITIALIZED;
      Serial.println("Uninitializing :( Connecting");
    }

    if(c&0x20)  //ACK
    {
      Serial.println("ACK Connecting");
      state=HS_CONNECTED;
    }
    break;

  case HS_CONNECTED:

    radio.offline();

    radio.txbuf[0]=0x25;
    radio.txbuf[1]=channel;
    radio.sendBuffer(2);      //-Chan


    radio.txbuf[0]=0x2a;
    radio.txbuf[1]=noteID[0];
    radio.txbuf[2]=noteID[1];
    radio.txbuf[3]=noteID[2];
    radio.txbuf[4]=noteID[3];
    radio.txbuf[5]=0xCC;
    radio.sendBuffer(6);      //-tx data

    radio.txbuf[0]=0x30;
    radio.txbuf[1]=noteID[0];
    radio.txbuf[2]=noteID[1];
    radio.txbuf[3]=noteID[2];
    radio.txbuf[4]=noteID[3];
    radio.txbuf[5]=0xAA;
    radio.sendBuffer(6);      //-tx data

    radio.online();

    radio.block(flushTX);

    radio.txbuf[0]=0xA0;
    radio.txbuf[1]=payload[0];
    radio.txbuf[2]=payload[1];
    radio.txbuf[3]=payload[2];
    radio.txbuf[4]=payload[3];
    radio.txbuf[5]=payload[4];
    radio.txbuf[6]=payload[5];
    radio.txbuf[7]=payload[6];
    radio.txbuf[8]=payload[7];
    radio.sendBuffer(9);      //-tx data

    radio.txbuf[0]=0x20;
    radio.txbuf[1]=0x7A;  //-TX
    radio.sendBuffer(2);

    while(!c)
      c=radio.getStatus();
    radio.clrStatus();


    if(c&0x10)  //MRT
    {
      Serial.println("MRT - connected");
    }

    if(c&0x20)  //ACK
    {
      Serial.println("ACK - connected");
    }

    break;

  }
}

And I'm using a fairly simple Arduino sketch to invoke these methods (which is a modified version of the INO file form the project mentioned above):

#include <HCD.h>
HCD drone0;

unsigned char ID0[]={
  0x1F, 0xC3, 0x00, 0xCC};
unsigned char payload0[]={  0x0, 0x20, 0x14, 0x03, 0x25, 0x1F, 0xC3, 0x00 };
unsigned char payload1[]={
  0x00,0x70,0x70,0x1E,0x1E,0x00,0x10,0x7E};

void setup()
{
  Serial.begin(19200);
  Serial.println("ON");
  Serial.println("Connecting a");
  drone0.bind(ID0);
  drone0.setChannel(0x25);
}

unsigned long timer=0;

unsigned char chs[] = { 0x2A, 0x20, 0x34, 0x16, 0x3E, 0x0C, 0x48};
void loop()
{
  for (int i=0; i<128; i++)
  {
     drone0.setChannel(i);
     drone0.update(payload0);
     delay(10);
  }
  delay(2000);  
  drone0.setChannel(0x2); 
  drone0.update(payload1);
  delay(2000);
}

Yet the Quadcopter seems to gently ignore this code and many variations of it.

If anyone got to this line in my question, I would appreciate any help he/she can provide.

  • 3
    I'm afraid i don't have the knowledge to help you, but i wish more questions on RE.SE were as well researched and written. Upvoted. – Guntram Blohm Jul 12 '15 at 6:30
1

There are some items I do not understand in your code:

  1. In the initial "bind" routine, there are commands 0x3F, 0x3E, 0x39, corresponding to registers 1F, 1E, 19, if I understood this right. These are not documented in the NRF24l01 data sheet (btw your link to it does not work).

  2. You send the payload with the correct command 0xA0. However, you send a total of 9 bytes, including the command. Does this not mean that the payload you are sending contains only eight bytes? And in one of your payload send commands you put the txbuf[8] twice. maybe you meant as second time txbuf[9] instead?

  3. As the last command you send 0x20. If I got it right, this is the "Write configuration". Should this not come at an earlier stage?

Otherwise, I do not know anything about quadcopters (only that Amazon wants to use them...)

  • Thanks for the remarks! 1. True - I just replicated what the logic analyzer caught. 2. True again. And the second [8] is a bug. 3. This is the same as the original code, basically it can come at this stage as well, there are no restriction on where in the flow this register can be configured so I guess it should be OK. – Omer Jul 12 '15 at 12:58
  • But shouldn't you send a total of 10 bytes, as the payload size has been setup to be 9? You also catch 10 bytes in your logic analyzer, when including the command. – josh Jul 12 '15 at 13:29
  • Right. default payload is 8 bytes and they didn't change it. But after everything else failed, I decided to replicate exactly what was captured. I know it doesn't make sense, the same as the non-existing registers 1F and 1E, but that's what I sniffed, several times. Any experience with such discrepancies? – Omer Jul 12 '15 at 13:45
  • 1
    Sorry, but I do not understand. In your "bind" routine, you set the payload length to 9 bytes (command 0x31), but then you send only 8 bytes payload. Why? But maybe I got you completely wrong, and we are talking about different items. I have only limited experience with microcontrollers. – josh Jul 12 '15 at 14:11
  • I think you got it right again. I missed that. At some stage I just copied the bytes, and indeed 0x31 followed by 0x9 means the payload is set to 9 bytes. Fixing the bug of the double "txbuf[8]" should reflect that as well (meaning 10 bytes total including the command). I should perform this fix and update the question (who knows, perhaps this was the issue?!...) – Omer Jul 12 '15 at 14:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.