Serial Communication using Radio Frequency (RF) Transmission

 

Once you've learned how to send data between two computers (or microcontrollers) via serial communication, a common next step is to want to get rid of the serial cable. We're very used to wireless devices in our lives, from the garage door opener to cell phones to wireless internet connections. All of these applications have a number of unique complications to them, and solutions that they employ, but they all share two things in common: first, that they are transmitting data serially, and second, that they are sending it using a pair of radios, one to transmit, and one to receive.

Transmitter, Receiver, Transceiver?

When you start looking for radios to interface to a microcontroller, there are a few details you'll need to know. First, radios are grouped into three large categories: transmitters, receivers, and transceivers. Transmitters can send, but not receive, data. Receivers can receive, but not transmit. Transmitters and receivers are usually sold in pairs. Finally, transceivers can both send and receive data. Typically, transceivers are more expensive than transmitters or receivers alone.

What's the Frequency (Kenneth)?

The first characteristic that you'll want to know about your radios is their transmission frequency. Every transmitter or receiver operates at a particular frequency. Though there are some tunable radios available for microcontroller interface, the most common ones have fixed transmission frequencies. Your transmitter and receiver must operate on the same frequency, or they won't be able to talk to each other.

Frequencies across the RF spectrum are regulated by various standards organizations around the world, so that radio applications coexist harmoniously. The International Telecommunication Union (ITU), part if the UN, sets world standards, and each country has an equivalent regulatory body to set local standards. In the US, the Federal Communications Commission (FCC) regulates radio spectrum and grants licenses to use it. Within the ITU standards are a few bands of radio spectrum known as the ISM bands (Industrial, Scientific, Medical). Many short-range radio applications fall under this standard, and as a result, they all share the same spectrum ranges. You'll notice when buying radios that there are some common frequency ranges: 433 MHz, 900MHz, and 2.4GHz.. This is because these ranges are all ISM ranges.

When buying your radios, it's helpful to know what other radios in your environment will interfere with your application. For example, 2.4GHZ spectrum has been very popular lately for high-bandwidth applications like wireless ethernet (802.11) and Bluetooth, because the high frequency affords faster transmission rates. It's also the transmission range of microwave ovens, so turning on a microwave will interfere with your wireless ethernet. Similarly, some 900MHz cordless phones will interfere with some of the 900MHz-range serial radios mentioned here.

ASK, OOK, FSK???

You'll see these terms a lot when dealing with RF receivers. Basically, they're all kinds of modulating the signal to produce 0's and 1's. ASK is Amplitude Shift Keying, in which the signal amplitude is varied to produce data; OOK is On-Off Keying, meaning that literally, On = 1 and Off = 0. FSK is Frequency Shift Keying, meaning that the frequency of a transmitter is varied to produce 0's and 1's. Of the three, OOK is easiest for a manufacturer to implement, but the most error-prone. ASK is somewhat more work, but less error-prone. FSK is the least error prone, but takes the most bandwidth. This is relevant, because it means that data sent by a receiver that uses these methods is a digital signal, not an analog signal. If you plan to send a varying voltage via RF, you'll need to encode it as a digital signal first.

For more on OOK, here's a decent engineering white paper.

Buying Radios

The wisest thing you can do when buying your radios is to buy them as a set. Matching a transmitter from one company to a receiver from another is asking for headaches. Likewise, trying to hack an analog radio, such as that from a baby monitor or a walkie-talkie may seem like a cheap and easy solution, but in the end, it'll cost you time and eat your soul. When looking for radios, look for something that can take the serial output of your microcontroller. Most microcontrollers send serial data at TTL levels, with 0V for logic 0 and 5V for logic 1. Converting the output to RS-232 levels is also fairly simple, so radios that can take those signals are good for your purposes.

Glolab makes some lovely radios, that are simple and cheap. Abacom has some nice transmitter-receiver pairs, and some nice transceivers as well. Linx makes some nice reliable radios that are a bit more expensive.
Sending Data

Most of the inexpensive radio transmitters mentioned above send data at relatively low rates (under 9600 bps). Given the noisy nature of RF, it's wise not to attempt to send at the top speed if you don't need to. In the example below, my transceivers can operate at up to 4800 bps, but I am only sending at 2400 bps.

It's also wise to send your data in short packets, so there's less chance of dropping a few bits or bytes in the middle. In the example below, I send four bytes, and only one is actually data. The first three are a unique header. If I see four bytes and the first three are the header, then I know the fourth is good to read.

Sending the message more than once can be helpful, in case the first transmission is missed. For example, on the button press in the example below, I might send several times instead of once. This would give the receiver a much better chance of receiving the message. Don't send continuously, though, unless you know you're the only transmitter in the area, or you'll flood everyone else's transmissions.

Finally, a call-and-response approach can be helpful. Note that this requires transceivers on both ends. If the receiver gets some data, but it's not complete, or appears garbled, a request for re-transmission can be sent. The transmitter, when not transmitting, listens for requests. When it gets one, it sends out its most recent status.

Robert Poor has a brief tutorial on sending little packets that includes links to several RF module manufacturers and some C code for the PIC.

 

This example is the most basic RF example I could come up with. I added the header and the check for the header when I noticed that my receiver was getting a lot of noise from the environment.

The transceivers used are Abacom RTF-DATA-SAW transceivers. They take in TTL serial, which I'm sending and receiving at 2400 bps, 8 bits per byte, no parity, 1 stop bit, inverted logic. The transmitter is a BX-24 microcontroller, and the receiver is a PIC16F818, programmed in PicBasic Pro.

The Code

Transmitter code (BX-Basic). When a switch attached to pin 14 of the BX-24 is pressed, the BX-24 transmits a four-byte string of data. This example only transmits once per button push, on the button-down.

The transceiver is attached to pins 11 and 12. Pin 11 is the transmit pin, and 12 is the receive pin.

dim inputBuffer(1 To 13) As Byte '4-byte output buffer.
dim outputBuffer(1 To 10) As Byte '1-byte output buffer.
dim lastSwitchstate as byte
dim switchstate as byte
dim switchCount as byte
const debounceTime as single = 0.01
const sendLED as byte = 10
dim sendString as string

sub main ()
 ' define which pins COM3 will be:
  call defineCom3(12,11,bx1000_1000)

 ' set aside memory for input and output:
  call openQueue(inputBuffer, 13)
  call openQueue(outputBuffer, 13)

 ' open COM3:
  call openCom(3, 2400, inputBuffer, outputBuffer)
  switchCount  =0
  lastSwitchstate = 0

  do
    sendString = "OBJ"
    switchstate = getPin(14)  
    ' if the switch isn't the same as it was last time through
    ' the main loop, then you want to do something:
    if switchstate  <> lastSwitchState then    
      if switchstate  = 1 then
        ' the switch went from off to on
        switchCount = switchCount + 1
        debug.print "switch turned on."
        debug.print cstr(switchCount); " button pushes."

        ' append switchCount to the send string as a byte:
        sendString = sendString & chr(switchCount)
        call putQueuestr(outputBuffer, sendstring)
        call putPin(sendLED, 1)
      else
        ' the switch went from on to off
        debug.print "switch turned off"
        call putPin(sendLED, 0)
      end if
      ' store the state of the switch for next check:
      lastSwitchState = switchstate  
    end if
  loop
end sub




Receiver code. The PIC listens constantly for incoming data. When it gets incoming data, it sends that data serially out another pin to a PC. The RF transceiver is attached to pins portb.3 (receive) and portb.2 (transmit). The PC serial is transmitted on pin portb.7.
inbyte var byte(4)  ' incoming string
output portb.1    ' status LED
output portb.6    ' serial out to PC
input portb.3    ' serial in from transceiver
n_2400 con 16780  ' baud mode for serin2 (2400-8-N-1 inverted)
n_9600 con 16468  '  baud mode for serin2 (9600-8-N-1 inverted)
i var byte      ' loop counter

RFserialIn var portb.3
PCserialOut var portb.6
StatusLED var portb.1

' flash status LED to start:
high StatusLED
pause 500
low StatusLED
main:
  ' this serial call does nothing until it gets bytes:
   serin2 RFserialIn, n_2400, [STR inByte\4]

   high statusLED
   ' if the first three bytes are "OBJ", then
    ' the fourth byte is data. parse it as a number (DEC inbyte(3))
   if inbyte(0) = "O" and inbyte(1) = "B" and inbyte(2) = "J" then
      serout2 PCserialOut, n_9600, [str inbyte\3, DEC inbyte(3), 10, 13]
    endif
    
  low statusLED
goto main
Hardware notes

The transceivers in this example are connected exactly according the the data sheet supplied by Abacom. It was built on a solderless breadboard. In general, however, if you can avoid solderless breadboards when making RF devices, do so. the impedance of the breadboard and the loose connections makes for a lot of RF noise.

Make sure to decouple the power on your radios. In my example, I am using a 1µF capacitor across power and ground, right by the transmitter and receiver voltage pins. I have also put a 1µF capacitor across the power and ground for the microcontrollers.

When possible, use the antenna recommended by the manufacturer. It may be large and unweildy, but it will be stable. Once you know your application works with it, you can think about fashioning your own. In my case, Abacom included some nice notes on antenna length and coil radius, and I was able to coil my own antennas from 22AWG solid core wire. Not all manufacturers are so nice. If your radio has an antenna on the module itself, so much the better.

Manchester encoding

Another form of error-checking that can be useful when sending RF data is Manchester encoding. In this scheme, each byte is split in two so that a clock pulse can be sent in the middle. Glolab sells a nice manchester encoding chip to do the job for you, or you can do it yourself in software. Here are some notes on how it's done, and here's a quick PicBasic example.