Cheap RF

An online electronics store I frequent sells these cheap 400MHz RF links for $14 for a TX-RX pair. These modules are small, work at 2400bps or 4800bps, and work at a range of up to 500 feet according to the specs. Similar RF links can be had for cheaper on ebay, for about $2 for a receive module and $2.50 for a transmitter.

Regardless of where the module comes from, most of these use some form of auto gain control in the receiver. Due to the design of the receiver, if a DC signal is transmitted, it will not be received. That is, if you connect the data line of your transmitter to ground (or +V), the receiver’s data line may not correlate with the input.

This is because the transmitted signal must be oscillating for the receiver to recognize it. In order to get around this, an encoding scheme is needed.

 

A brief explanation of manchester encoding

A simple way of getting around this DC issue is to use encoding. Also with these RF links, sometimes DC balance is needed for proper operation, meaning the data line should be “high” about the same amount that it is “low”. Manchester encoding solves this problem by encoding each bit to be transmitted.

The way it works could be thought of as this: For serial data, expanding each data bit to occupy two bits, where the first is the data bit and the second is its inverse. So, a 0 becomes 01 and a 1 becomes 10. Thus a byte with the value 0 would have the bits 00000000 and encoded in this style, the encoded data would look like 0101010101010101.

Unfortunately the modules I bought (made by laipac) from that first mentioned online electronics hobby shop, do not output the same data as being input into the receiver. That is because the transmitter uses an encoding where, for a “0″, the output is “001″, and for a “1″ it sends “011″. I’ve found that sending a square wave with equal on/off times, results in receiving something different. What is received is still oscillating, but the time “high” is roughly twice as long as it is “low” (see below picture). Some sort of encoding will be needed where, the data line is clocked at around the proper baud rate, and the bits are represented in the time the signal is “high”. (for more information about manchester code, please see wikipedia)

O-scope

 

Hardware setup

Both the receiver and transmitter have four essential pins: ground, power, data, and antenna. The receiver should be run at 5v, but the transmitter can be run at up to 12v. According to the data sheet, the higher the transmitter voltage, the greater the range. The antenna pin can be left as-is but the range won’t be too good. Alternatively the data sheet specifies how long the antenna wire should be if you decide to add one. The length is determined by the RF link’s operating frequency.

Both the receiver and transmitter have data lines which should be attached to an available pin on your microcontroller.

 

On to the code

I compiled a library to make integrating this easier. I’ll go over how this works here. Essentially my code is designed to allow you to transmit a 16-bit integer and receive it on the other end. Transmitting is as simple as creating a reference to the library and specifying which pin is data, and what speed should be used. To transmit, a function is called, passing it an unsigned integer of your choosing.

The receive code works in a similar way, where it is initialized and given a pin number to use. I didn’t feel the need to get into interrupts, timers, or other complexities so the receive code works synchronously. To receive a number, a function is called to listen for intelligible data. The function will listen for up to five seconds. If it receives a number during that time, it will exit and return the number. If nothing is received, it exits returning 0.

This means that if no data is on the air, the function will hang your sketch for five seconds waiting for data. This timeout can be changed, of course. I designed this with certain applications in mind and, for the most part the transmitter is always transmitting, so calling this function is quick and exits as soon as meaningful data has been acquired.

These libraries use a kind of CRC check to verify the integrity of the data, so the receive function will only return the actual number transmitted, or no data at all. It will only receive data sent by the complimentary transmit library.

 

All of the following code can be downloaded in a zip file here.

/*
  RFXmit.cpp - Library for transmitting data
    over cheap RF links with encoding
  Created March 27, 2008
  Released into the public domain
*/#include "WProgram.h"
#include "RFXmit.h"

RFXmit::RFXmit(byte vPin, int vSpeed)
{
  pinMode(vPin, OUTPUT);
  _pin = vPin;
  _xmitdelay = vSpeed;
}

void RFXmit::xmitByte(byte vData) {
  byte bitv = 128;
  for (int bitc = 0; bitc < 8; bitc++) {
    if (vData & bitv) {
      digitalWrite(_pin, HIGH);
      delayMicroseconds(_xmitdelay*2);
      digitalWrite(_pin, LOW);
      delayMicroseconds(_xmitdelay);
    }
    else {
      digitalWrite(_pin, HIGH);
      delayMicroseconds(_xmitdelay);
      digitalWrite(_pin, LOW);
      delayMicroseconds(_xmitdelay);
    }
    bitv = bitv >> 1;
  }
}

void RFXmit::xmitInt(unsigned int vData) {
  byte cks1 = vData;
  byte cks2 = vData >> 8;
  byte cks3 = cks1 + cks2;
  for (int xbr = 0; xbr < 3; xbr++) xmitByte(0);
  xmitByte(150);
  xmitByte(vData >> 8);
  xmitByte(vData & 0xFF);
  xmitByte(cks3);
  xmitByte(~cks3);
  xmitByte(0);
}

A quick look over the code and you’ll see that the data is broken down into bits, and the delay when the transmit line is “high” is either the delay time (set by the sketch) or two times that delay, depending on the bit.

#include <rfxmit.h>
RFXmit rfxmit(7, 208); // Initialize the routine
                       // use pin 7 to transmit
                       // delay 208us between level change
unsigned int curInt;   // an integer to count with

void setup()
{
}

void loop()
{
  for (int rpt = 0; rpt < 5; rpt++) rfxmit.xmitInt(curInt);
  curInt++;
  delay(1000);
}

In the above example sketch, the transmit library is initialized with variables (7, 208). This corresponds to the arduino pin# which the transmitter’s data pin is connected. 208 microseconds of a delay is about right for 2400bps modules. Double this number if you’re using a 4800bps one.

In the loop() function above, the sketch simply sends an integer count,increases that count, and waits a second. The for loop is used to send the number 5 times, since the receiver’s gain will get out of sync if no data is transmitted for a second. As seen above, transmitting is a matter of calling a function with the integer to transmit.

/*
  RFRecv.cpp - Library for receiving data
    over cheap RF links with encoding
  Created March 27, 2008
  Released into the public domain
*/#include "WProgram.h"
#include "RFRecv.h"

RFRecv::RFRecv(byte vPin)
{
  pinMode(vPin, INPUT);
  _pin = vPin;
}

int RFRecv::waitOnlvl(byte vLvl) {
  byte wlvl = digitalRead(_pin);
  byte wlvl2 = wlvl;
  int lvlcnt;
  while (wlvl2 != vLvl) {
    lvlcnt = 0;
    while (wlvl2 == wlvl) {
      wlvl2 = digitalRead(_pin);
      lvlcnt++;
    }
    wlvl = wlvl2;
    delayMicroseconds(40);
  }
  return(lvlcnt);
}

unsigned int RFRecv::recvInt(void) {
  // check for level change
  unsigned long starttime = millis();
  int tmocyc = 0;
  byte reftm = 55;
  byte reftm2 = reftm + (reftm / 2);
  //read potential bits
  unsigned long curdata = 0;
  unsigned int preamble = 0;
  byte cbit = 0;
  waitOnlvl(0);
  waitOnlvl(1);
  while ((preamble != 150) && (tmocyc < 5000)) {
    preamble = (preamble << 1) + (curdata >> 31);
    curdata = curdata << 1;
    cbit = waitOnlvl(0);
    if ((cbit < reftm) && (cbit > 15)) {
      reftm = cbit;
      reftm2 = reftm + (reftm / 2);
    }
    if (cbit > reftm2) curdata++;
    waitOnlvl(1);
    tmocyc++;
    if ((tmocyc % 128) == 127) {
      if (millis() - starttime > 1000) tmocyc = 5000;
    }
  }
  if (tmocyc == 5000) {
    return(0);
  }
  else {
    unsigned int chkdata = curdata >> 16;
    byte chkbyte1 = chkdata;
    byte chkbyte2 = chkdata >> 8;
    byte chkbyte3 = chkbyte1 + chkbyte2;
    chkdata = (chkbyte3 * 0x100) + (255 - chkbyte3);
    if ((preamble == 150) && ((curdata & 0xFFFF) == chkdata)) {
      return(curdata >> 16);
    } else {
      return(0);
    }
  }
}
#include "RFRecv.h";

RFRecv rfrecv(12);  // use pin 12
unsigned int lastint; // last number receivedvoid setup()
{
  Serial.begin(9600);        // open serial
}

void loop()
{
  unsigned int newint; // for storing the last received int
  newint = rfrecv.recvInt();
  if ((newint != lastint) && (newint != 0)) {
    Serial.println(newint, DEC);
    lastint = newint;
  }
}

The last bit of code above is an example sketch using the receive library. The code is initialized in the same manner as the transmit. All that is needed is a pin# where the receiver’s data pin is attached. In the example transmit sketch on this page, the same number is transmitted five times o ensure it goes through. The receive example above compensates for this by ignoring any duplicate numbers. When a valid number comes in and is different than the last, it is printed to the serial port.

Now with this library written, it is possible to send an integer from one arduino to another. The simplicity of the code and limiting data to integers makes this code a very small addition to any sketch. The transmit and receive libraries are provided separately n the event you are only using one in a given sketch and want to conserve program space.

I’ll end this with an example sketch for transmitting the status of buttons to a receiver. The following sketch was copied from the switchboard page and modified to use RF:

/* RF KEYS
 * ---------------
 *
 */
#include "rfxmit.h"

RFXmit rfxmit(7, 208); // Initialize the routine
                       // use pin 7 to transmit
                       // delay 208us between level change
// variable declaration
int sw[7];  // switches / their values
unsigned int curInt;

void setup(void) {
  Serial.begin(9600);
  int cnt = 2;                  // counter
  while (cnt <= 6) {
    sw[cnt] = 1;
    pinMode(cnt, INPUT);        // set to input
    digitalWrite(cnt, HIGH);    // enable pull-up
    cnt = cnt + 1;
  }
}

void loop(void) {
  int cnt = 2;
  int vin = 0;
  cnt = 2;
  while (cnt <= 6) {
    vin = digitalRead(cnt);     // poll the pin.
    if (vin != sw[cnt]) {       // has its state changed?
      sw[cnt] = vin;            // set the new state
      curInt = vin * 0x8 + cnt;
      for (int rpt = 0; rpt < 5; rpt++) rfxmit.xmitInt(curInt);
    }
    cnt = cnt + 1;
  }
}

The MSB (bit at position 0×8) is used to indicate whether the switch is on or off. As the internal pull-up resistors of the MCU are used, this sketch will not do anything until one of pins 2 through 6 are shorted to ground. Since the pin number will be between 2-7, and the pin state is at bit 0×8, that leaves four unused bits which could be used for other purposes.

In that event, the line curInt = vin * 0×8 + cnt; could be changed to:
curInt = curint && 0xFFF0 + vin * 0×8 + cnt;
which would keep any data in the higher bits there, only modifying the lower bits that are used to indicate switch positions.

Leave a Comment