Saturday, June 14, 2014

Controlling a 27mhz remote control car with AD9850 and arduino uno




Controlling an RC car with AD9850 dds module and an arduino

If you have an rc car, an ad9850 board (cheap modules available on ebay) and an arduino uno, it is possible to convert them to a computer controlled or programmable rc car. After seeing a post on converting a cheap rc car by jon, to a programmable one by modifying the remote control with an arduino, i decided to make a universal controller which doesn't need to solder or modify the cars electronics. So this approach can add an independent control channel and some fun with decryping and cloning rc remotes of simple toy grade cars (without any encryption)

RC car control signals

A simple rc car uses an amplitude modulated carrier (rf signal) to send the control signal. So most of simple toys uses a 4 pulses of a specific length followed by a multiple of pulses to send a specific command (more of this in a later post). I used gnuradio and funcube dongle to capture the signals and to analyse the control signals. Brandon has a similar project where he used a raspberry pi's pll to directly generate the rf signals and brute force to identify the correct pulse lengths. You can use audacity to measure the pulse lengths after capturing the audio from a radio receiver or sdr tuned to 27mhz signals from the rc remote [read more on decoding the signals using sdr].

AD9850 control signals and speed issues

One of the challenge with this project was the time delays while updating the ad9850 frequency words via arduino. It was overcomed by using the hardware spi for controlling the frequency and usng the PORT commands instead of the digitalwrites. An other small issue was with analogue read delays, which was used to read the keypad inputs. This was solved with an adc update and later i found that all these issues were addressed in a project by Zisis Chiotis which implements an fm modulation on an arduino! (at around 16khz)

connecting ad9850 to arduino spi pins

Using a single analogue pin to assign multiple key functions to control the rc car


Data connections to AD9850. See here for the corresponding pins


  • MOSI = DATA (pin 11 on uno) 
  • CLK = W_CLK (pin 13 on uno) 
  • FU_UD (PIN 7 on uno)  (see port command here)
  • GND = RESET

Source code

//#AD9850 rc remote control  
//blog.riyas.org  
//courtesy to Zisis Chiotis
  
#define PLEN 550  //optimised pulse length adjust this to match your remote:-audacity and sdr
#define PRELEN PLEN*3
#define FREQUENCY 542879
#define FREQUENCY2 0
#define btnRIGHT 0
#define btnUP   1
#define btnDOWN  2
#define btnLEFT  3
#define btnNONE  4
int adc_key_in = 0;
int key = 0;
#include <SPI.h>
uint32_t frequency = FREQUENCY;    //The desired frequency must be divided by 50 ex. 34,7MHz/50 = 694000  
uint32_t tword = frequency * 3436 / 100;    //tuning word calculation  
byte W[5] = { 0, 0, 0, 0, 0 };
const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);    //these are the prescalers for the ADC sampling rate.  
const unsigned char PS_32 = (1 << ADPS2) | (1 << ADPS0);
const unsigned char PS_16 = (1 << ADPS2);
int mic = 512;
void
setup ()
{
  Serial.begin (9600);
  //DDRB = B01100000;             //portb has outputs, PB5(may be used for reset) PB6(FU_UD pin)  
  DDRD = DDRD | B01100000;    //  
  //PORTB = 0x00;  
  PORTD = 0x00;
  // Serial.begin(115200);          // everything can be controlled by serial comm  
  pinMode (A0, INPUT);        // here comes the audio frequency in  
  SPI.setDataMode (SPI_MODE0);    // mode 0 seems to be the right one  
  SPI.setClockDivider (SPI_CLOCK_DIV2);    // this is pretty fast  
  SPI.setBitOrder (LSBFIRST);    // AD9850 wants LSB first  
  SPI.begin ();
  // set up the ADC  
  ADCSRA &= ~PS_128;        // remove bits set by Arduino library  
  ADCSRA |= PS_32;        // setting the sampling rate at 16MHz/32 this makes the analogRead() complete in around 40μs  
}

void
loop ()
{          
  key = read_key ();
  switch (key)            
  {
  case btnRIGHT:
    {
      Serial.println ("FORWARD");
      sendcommand (10);
      break;
    }
  case btnLEFT:
    {
      Serial.println ("RIGHT");
      sendcommand (34);
      break;
    }
  case btnUP:
    {
      Serial.println ("BACKWARD");
      sendcommand (40);
      break;
    }
  case btnDOWN:
    {
      Serial.println ("LEFT");
      sendcommand (28);
      break;
    }
  case btnNONE:
    {
      //Serial.println("NONE");   
      break;
    }
  }
}

void
preample (int len)
{
  int i;
  for (i = 0; i < len; i++) {
    setfreq (FREQUENCY);
    delayMicroseconds (PRELEN);
    setfreq (FREQUENCY2);
    delayMicroseconds (PLEN);
  }
}

void
command (int len)
{
  int i;
  for (i = 0; i < len; i++) {
    setfreq (FREQUENCY);
    delayMicroseconds (PLEN);
    setfreq (FREQUENCY2);
    delayMicroseconds (PLEN);
  }
}

void
setfreq (uint32_t frequency)
{
  tword = frequency * 1718;    //calculating the tuning word for AD9850  
  W[0] = (byte) tword;
  W[1] = (byte) (tword >> 8);
  W[2] = (byte) (tword >> 16);    //converting it to bytes  
  W[3] = (byte) (tword >> 24);
  W[4] = 0;            //phase zero  
  //start sending with spi interface  
  PORTD = B01000000;
  PORTD = 0x00;            //pulse FU_UD  
  for (int j = 0; j < 5; j++) {
    SPI.transfer (W[j]);    //send the word  
  }
  PORTD = B01000000;
  PORTD = 0x00;            //pulse FU_UD  
}

int
read_key ()
{
  adc_key_in = analogRead (0);
  if (adc_key_in > 600)
    return btnNONE;
  if (adc_key_in > 500)
    return btnRIGHT;
  if (adc_key_in > 450)
    return btnUP;
  if (adc_key_in > 400)
    return btnDOWN;
  if (adc_key_in < 400)
    return btnLEFT;
  return btnNONE;
}

void
sendcommand (int n)
{
  for (int i = 0; i < 5; i++) {
    preample (4);
    command (n);
  }
}
end

1 comment:

  1. What a wonderful project! Well done and thanks for the reference! Keep up with good ideas like this!

    ReplyDelete