[Back to Home Page]


Cheap RF modules made easy!!
How to do serial comms using the cheap ebay RF 433/315 MHz modules - 22nd April 2013.

These cheap RF modules usually come in a pair, with one transmitter and one receiver. A pair can be bought on ebay for as cheap as $4, and even as cheap as $2 a pair if you buy 10 pairs.

Much of the information on the internet from people's projects is sketchy and not very comprehensive. I test these modules out, and show how to get good reliable serial comms direct from USART -> USART, and I also show how to greatly speed up the data rate and reliability by using an alternative bit encoding system.


My cheap Ebay modules had this pinout;

Another popular brand (slightly more sophisticated) has this pinout;

But all of these cheap type modules fit these 3 basic pin functions (plus antenna);

Vcc (power+) 3v to 12v (works fine on 5v)
Gnd (ground-)
Data In (accepts logic level digital data, HI = transmitting carrier)

Vcc (power+) 5v (some may work on 3.3v)
Gnd (ground-)
Data Out (logic level digital data, HI = carrier present)

Data and transmission

This is very simple. When the transmitter data pin is LO, the transmitter is off, and draws less than 1uA. (Mine drew 200 nA from the 5v supply when off). When the transmitter data pin is HI, it is transmitting solid 433 or 315 MHz carrier wave, and with a 5v supply my transmitter drew about 12mA when transmitting continuous carrier.

The transmitter can be run from a higher voltage (like 12v) which increases the transmitting power and range. My tests showed a 5v supply was plenty, even for 20 metres range through multiple house walls. The transmitter is very crude, and all it does is make an RF carrier wave whenever its data input is HI.

The receiver (when powered up) will crank up its gain untl it starts to receive something. If no transmitter is working, the receiver will receive some static. If a modulated carrier (a carrier wave which is turning on and off) is received, the receiver will reduce its gain to remove lesser signals, and ideally will then output the same modulated digital data as that which is controlling the transmitter.

It's important to know that the receiver takes a bit of time to adjust its gain, so any "packet" of data transmitted should start with a "preamble" before the main data and the receiver will then have time to self-adjust its gain before the important data starts.

Testing the RF modules!

I ran both modules from separate regulated +5v DC supplies, and also attached 173mm vertical "whip" style antennas. (They were 433.92 MHz modules, so these were "1/4 wave" length antennas). Range was tested from a few metres open air, to about 20 metres through walls, and the range did not seem to influence these tests much. So I assume these test results are fairly typical of normal use.

Frequency and duty cycle testing

I used a digital frequency source with precise frequencies and exactly 50:50 duty cycle, this was used to modulate the transmitter Data IN pin.

I found that as the modulating frequency got higher the received duty cycle was corrumpted, with the HI portion of the duty getting smaller;

Freq      Received HI Duty
50 Hz     49%
200 Hz    49%
500 Hz    48%
1 kHz     47%
2 kHz     43%
5 kHz     37% 
10 kHz    26% 
12 kHz    20-25% 
16 kHz    bad! 

This is very important for modulating the data, because if trying to use a higher datarate the HI periods will become shorter and most serial protocols like USART serial or manchester encoded serial will fail. This is why these modules are normally only good for 1200 baud serial, or maybe handle 2400 baud serial if everything is perfect (like high signal strength).

Above you can see scope results of the receiver output from 3 different frequency tests, pasted into one picture. All three of these signals was transmitted with exactly 50:50 duty going into the transmitter Data IN pin.

At 1kHz the 50:50 duty of the original waveform into the transmitter is ALMOST preserved, and comes out reliable at about 47% duty.

But the 5kHz and 10kHz are much worse! At 10kHz you can see the pulse widths are very narrow and also noise/gain issues etc make each pulse a different width. The receiver is struggling to capture these short (50uS) pulses.

Trying to fix the duty cycle at the transmitter

Next I adjusted the duty cycle at the transmitter, injecting a digital signal still at 10 kHz, but now with 85% HI and 15% LO duty.

This had only a small effect! Below you can see the signal out of the receiver, the received duty was improved slightly to about 36%, but remember this was transmitted at 85% Hi duty!

Obviously it is a receiver issue, that is reducing pulse width at higher frequencies.

One thing that that WAS improved by transmitting with 10 kHz 85% duty (85uS HI pulses), was that the received pulses were more uniform in width and looked more reliable. That is good to know. :)

Simple comms, USART to USART

This was the next thing I tested. Knowing the modulated data was reasonably preserved at 1 kHz, I tried a baudrate of 1200 baud direct from the PIC USART TX pin to the Transmitter Data IN pin.

Plenty of people have done this on the internet with BASIC Stamps and Arduinos etc, so this is the easiest way to get RF comms from a microcontroller to another microcontroller, or from a microcontroller to a PC.


Above shows my simple setup for serial transmission from PIC, to be recieved with my PC.

The only change to the modules was adding a 10uF 25v tantalum cap across the power pins (VCC to GND) on both modules. The transmitter was connected direct to my EasyPIC6 development PCB, and was driven from the PIC USART TX output pin.

The receiver was plugged direct into a cheap USB-TTL Serial adapter (also bought from ebay for a couple of bucks). Three wires were connected; VCC, GND and RX. This also powers the receiver module at 5v from the USB port, handy! This setup is all that is required to send serial data from the PIC to a PC (and be received in the PC using any serial program like Hyperterminal).

Testing serial data; PIC to PC

Set up for 1200 baud, the PIC USART takes about 8.3mS to send a byte. I programmed the PIC to repeat a byte every 12mS, giving a small pause (of approx 4 bits) between bytes sent. Because the PIC USART data out is "typical TTL" type, the default state is HI, so the transmitter is running between bytes (during the extra 4 HI bits between each serial byte). This extra transmit ON time helps to set the receiver gain.

Above (yellow trace) you can see the result at the receiver Data OUT pin. The data is very nice and was decoded perfectly by the PC. I have manually drawn some data on the scope wave so you can see the start and stop bits and the 8 data bits (LSB first).

If you have worked with PIC USART serial data before this should look very familiar, and what is coming out of the RF receiver here looks identical to what came out of the PIC USART TX pin. Yay!

This byte was transmitted over and over, so the RF receiver had already adjusted its gain to the optimal setting and reception was excellent. Becaue of the 4 high (default) bits between each byte, total HI/LO duty of the data was about 8:6 bits, or 57:43 duty cycle.

I moved the PIC transmitter, and tested different ranges, with the max range about 20 metres and going through a few house walls. The result was very good and showed that continuous transmission from a PIC to a PC (or to another PIC) was effortless just directly using their USARTs and the two RF modules.

The "myth" of manchester encoding?

Some people say that these cheap RF modules should be used with manchester encoding, an old style of data encoding that makes an equal amount of 1 and 0 bits (meant to balance the voltages used for the RF receiver gain averaging circuit).

All my testing showed this was not necessary! Each byte sent every 12mS via USART took approx 14 bits total repeat time, and the worst case examples were these two bytes (shown below as 14 bit strings);
0x00 = 00000000011111
0xFF = 01111111111111

I really expected to have trouble with 0xFF as it is transmitting 13 bits HI and only 1 bit LO, which is a duty of 13:1.

But it seems the RF receiver is adjusting the gain mainly on the strength of the carrier (during the HI bits) and seems very tolerant of duty cycle! As long as there is SOME content that is LO the receiver seems to handle the data fine (at 1200 baud). Note! You cannot leave the data HI all the time, because after about 50-100mS the gain will be reduced.

Your RF modules might be slightly different from mine, and if you are concerned about keeping (roughly) the same number of HI and LO bits then one popular method using the USART is to send each byte twice (but the second byte is inverted). Obviously this will have half the data rate, but will keep a good average of HI and LO bits, and it also allows some error checking as each byte is repeated.

If doing that, I would still advise keeping those extra few stop bits (transmitter ON bits) between each byte.

Limits of simple USART serial data

As I mentioned before, as frequency (baudrate) increases the receiver really starts to have trouble maintaining the width of the HI bits, so the data starts to corrupt.

I tested at 2400 baud, and found it worked OK but only if signal strength was high and everything was perfect. Messing with shielding the antenna etc, or anything that made the receiver adjust the gain up or down would start to give data errors. Below shows the problem.

Compare the receiver output above, to the SAME BYTE shown in the 1200 baud picture earlier. Both these scope shots at 4800 and 9600 baud were taken with the signal strength perfect, and with any signal or gain issues they were much worse. You can see from the last two bits (1,0) how the 1 (HI) bit has been greatly reduced in width (and has become dependent on signal strength).

The maximum baudrate for good reliability is about 1200 baud, but 2400 will probably work ok if your signal is excellent.

The minimum baudrate I did not test fully, but frequency tests showed the receiver made excellent looking data and duty even at 50Hz, so I would not be surprised if a baudrate of 100 baud or even less would still work well.

Testing burst times and gain recovery

The receiver's gain auto-adjust works fine if the transmitting is constant. But in real life the transmitter will probably transmit a small packet of data and then turn off for a while. (Note! The legal band requirements for your area might also say that the transmitter is not allowed to transmit continuously.)

I did some simple tests of transmitting a "burst" of carrier, modulated at 1mS HI and 1mS LO (equivalent to 1000 baud data).

Above you can see the receiver data output, with a 100 mS OFF period, followed by a 100 mS "burst" of modulated carrier 1mS Hi 1mS LO (ie 50 pulses). Although the scope doesn't show the 50 pulses perfectly due to pixel aliasing, the result was good when I later zoomed in.

The OFF period of 100mS was short enough for the gain to remain at a reasonable level, and operation seems ok.

Above you can see the next test, again with the same 100mS of burst data, but now with a longer OFF period of 150mS. You can see the last 50mS of the OFF period was full of noise, because the gain had started to crank up. Obviously that would cause a data problem.

I checked by zooming in, and it seemed the gain had recovered within about 2mS from the transmission start, so only the first pulse of the 50 transmitted pulses was corrupted. Apparently the gain stabilises pretty quickly once a real transmission starts.

Below is another test, this time with very short bursts and long pauses;

Above the scope shot looks messy, but what it shows is just 20mS transmitted bursts (10 pulses each) with a 150mS OFF period between each burst.

The problem is that shortly after each burst (about 20mS after) the gain cranks up and then the receiver is picking up noise. (The noise signal is close to 50Hz so is likely coming from my big fluoro lights in my workshop.)

In all three tests above it looks like the receiver is fine for a short while after a data burst, for a time period about as long as the data burst was.

Time before data comes good?

All tests were similar in that once transmitting starts, the data was good within a couple of mS. In my case that was by about the 2nd data pulse.

Even after a long period of no transmission where the receiver gain has reached max (which can occur as quickly as 100mS if the last burst was small) the receiver stabilises within a couple of mS once transmission starts again.

So it looks as though even a single setup byte ("preamble" byte) will be enough to let the receiver lock in to the signal and good data bytes can follow.

You may want to use more preamble bytes, for safety. I did check under different signal conditions and my receiver always locked in very quickly. You may have a different type of receiver. Mine is a very cheap simple analogue type, which only has 2 transistors and a LM358 opamp. (See the photo at the very top of this page). Some receivers look a bit more complex, so whether that makes them lock in quicker or slower will need to be tested. :)

"Preamble" tips for simple USART apps

If you are going to use the direct USART -> USART system then it is important to use a "preamble" system to get the receiver gain sorted before the actual data bytes start.

It is good to send 2 or 3 preamble bytes first, then the data packet.

Synchronising bytes

As the first byte or two might be corrupted, the receiving USART might synchronise badly, and then all following bytes would not be correctly decoded.

A common way to get around this is to keep the first few bytes very short (with minimal 0 bits), and combined with the few extra spare bits between each byte this allows the maximum time to re-synchronise on each following byte;

Above shows a good way to do a 3-byte USART preamble.
To send a packet of data, the USART is turned on (output goes HI) and starts the transmitter, then there is a 12mS pause. That solid 12mS of carrier transmission should be enough for the receiver to set its rough gain so the following preamble bytes can sync.

After that, each byte is transmitted every 12mS. The 3 preamble bytes are 0xF8, 0xF8, 0xFA, all of which have very short LO times (see the scope picture above). This should allow plenty of time for the receiver to fine tune its gain and the long HI periods between bytes ensure that the USART gets a good byte sync.

After the 3 preamble bytes, there can be any number of data bytes, each byte is sent every 12mS. In this case I just sent 3 ascii data bytes; 'A', 'B', 'C'. After the last data byte is sent the USART is turned off (which turns the transmitter off).

Decoding the USART data

The main problem with the USART receiver will be the static the RF receiver picks up when the transmitter is not transmitting. The static will cause the receiving USART to record lots of garbage bytes.

The easy way around this is to keep clearing the USART of all garbage bytes until the byte pair; 0xF8, 0xFA are received. Those two bytes are the last two bytes of the preamble, and should be reliable as the receiver has had time by then to fix its gain. After those two bytes, a packet of data bytes will come out of the USART automatically.

If you want more safety, just send a longer preamble;
0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xFA

And look for a 4-byte set;
0xF8, 0xF8, 0xF8, 0xFA
to signal that valid data will come next.

Getting much higher datarates by NOT using the USART!

Using a simple USART solution is very easy, but limits baudrate to about 1200 baud. Some receivers also might need you to send two of each byte, the second byte inverted to keep 1 and 0 bits roughly even.

That limits the number of bytes sent to about 43 bytes per second, in continuous transmission. It will be slower still in burst transmissions as some time is needed for preambles etc.

A solution?

The problem at higher baudrates than 1200 is the receiver makes the HI bits smaller in width, and their width is further affected by signal strength and noise etc.

Similar problems exist in InfraRed remotes, and they get around this by using a pulse period encoded system, where it does not matter how wide any pulse is.

I threw together a very similar system to the one used in InfraRed remotes, and tried it with the RF modules. :)

Above shows how I transmitted a byte. (Data at the transmitter Data IN pin)
Based on my earlier testing at 10kHz transmission and pulse widths, I made the HI pulses of a fixed width of 80uS. This 80uS width was enough for the receiver to lock on to, even with low signal strengths.

The protocol is very simple;
pause between bytes (Start pulse) = 250 uS
0 bit = 100 uS
1 bit = 150 uS
(HI pulse width always 80uS. Data is sent MSB first)

As all bits contain some HI and some LO component, there is no need to send data bytes twice with the second byte inverted. With this system there is plenty of HI and LO content in a continuous transmission to keep the receiver happy.

Most importantly, it does not matter how wide the received HI pulses are! These pulses are wide enough for the receiver to capture, and even if signal conditions change and the pulses change width a bit, that will not matter. The width of HI pulses is not measured, only the time from pulse to pulse.

Above shows the system works! This is data coming out of the receiver. You can see the HI pulse widths vary a bit, but all pulses are represented reliably (even when I changed signal strength).

Most importantly, the critical decoding is measured from the END of each HI pulse (the \ edge). This is the most reliable edge of the HI pulse. The reason it is the most reliable edge is because the receiver adjusts gain during the HI pulse, so at the time the HI pulse ends the receiver gain is good, and it reproduces the end of the transmitted pulse quite accurately..

You can see how well this works by the timings shown above, measured from each pulse \ edge. All the 0 bits are the same size (100uS) and the 1 bits are all the same size (150uS).

Software decoding can detect byte sync very easily if any pulse is over 200uS (detects the "pause between bytes" or "start pulse");
>200uS = start pulse

Then 0 and 1 bits are 100uS and 150uS, so decoding the 8 bits is as simple as comparing to the mid value (125uS);
<125uS = 0 bit
>125uS = 1 bit

Benefits of this system

* Much higher datarate than USART examples
* Much higher data reliability than USART examples (even at higher baudrates)
* No need to repeat inverted bytes (already has good enough HI/LO balance)
* Seems quite immune to signal strength issues
* Extremely reliable byte-sync detection
* Transmission is normally OFF (energy efficiency)
* Fixed width pulses of transmitter ON (suits higher transmitting power)
* Transmitting and receiving devices do not require a USART (cheap small PICs?)

So it works great! How fast is it?

With the simple USART system aiming for excellent reliability the highest baudrate I could get was 1200, with 1 byte transmitted every 12mS. That is a speed of 83 bytes per second. If you decided to transmit two of each byte (second byte inverted) that would come down to 42 bytes per second.

With the new pulse period protocol it had excellent reliability with the timings shown in the scope images above. The speed depends on whether the data contains more 0's or 1's, but worst case (0xFF) would give 689 bytes per second, and an average speed would be around 800 bytes per second! And there is no longer any need to transmit two of each byte to balance the HI/LO bias.

Source code for pulse period protocol

Here is my Mikro C source code function to transmit a byte;

void send_rf_byte(unsigned char txdat)
  // This is a pulse period encoded system to send a byte to RF module.
  // Bits are sent MSB first. Each byte sends 9 pulses (makes 8 periods).
  // Timing;
  //   HI pulse width; always 80uS
  //   0 bit, LO width; 20uS (total 0 bit pulse period 100uS)
  //   1 bit, LO width; 70uS (total 1 bit pulse period 150uS)
  //   space between bytes, LO width; 170uS (total space period 250uS)
  // (timings tested with 20MHz xtal PIC 18F, no pll)
  unsigned char tbit;

  // make 250uS start bit first
  Delay_uS(170);      // 170uS LO
  LATC.F6 = 1;
  Delay_uS(80-1);     // 80uS HI
  LATC.F6 = 0;

  // now loop and send 8 bits
  for(tbit=0; tbit<8; tbit++)
    Delay_uS(20-1);             // default 0 bit LO period is 20uS
    if(txdat.F7) Delay_uS(50);  // increase the LO period if is a 1 bit!
    LATC.F6 = 1;
    Delay_uS(80-1);             // 80uS HI pulse
    LATC.F6 = 0;
    txdat <<= 1;                // roll data byte left to get next bit

System to receive a byte;
This is a little bit more complex and depends how you want to handle detecting a preamble and packet size. As a general system to receive a 10 byte packet, it could be done like this;

1. must first detect a valid start pulse >200uS, then can try to record 8 bits
2. record 8 bits. If any is >200uS, is some error (static) so start again
3. if we had a good start pulse, and 8 good bits, record it as a received byte!
4. after a full packet of 10 contiguous bytes is received, all done!

Basically this C function below will wait until 10 contiguous (sequential) "good" bytes are received. A "good" byte must have a good start bit followed by 8 good data bits. If static or noise is received the function will just keep resetting itself until 10 good contiguous bytes occur.

unsigned char rxdat[10];  // (global var) holds received RF bytes

void receive_rf_packet(void)
  // This function receives an RF packet of bytes in my pulse period
  // encoded format. The packet must have 10 valid contiguous bytes
  // or the function will not exit. There is no timeout feature, but could be added.
  // global variable; unsigned char rxdat[10] holds the 10 byte result.
  // Note! TMR0 is running at 500kHz, so 200uS = 100 TMR0 ticks
  unsigned char rrp_data;
  unsigned char rrp_period;
  unsigned char rrp_bits;
  unsigned char rrp_bytes;

  rrp_bytes = 0;
  while(rrp_bytes < 10)   // loop until it has received 10 contiguous RF bytes
    // wait for a start pulse >200uS
      while(!PORTC.F7) continue;    // wait for input / edge
      while(PORTC.F7) continue;     // wait for input \ edge
      rrp_period = TMR0L;           // grab the pulse period!
      TMR0L = 0;                    // and ready to record next period
      if(rrp_period < 100) rrp_bytes = 0;   // clear bytecount if still receiving noise
      else break;                   // exit if pulse was >200uS

    // now we had a start pulse, record 8 bits
    rrp_bits = 8;
      while(!PORTC.F7) continue;    // wait for input / edge
      while(PORTC.F7) continue;     // wait for input \ edge
      rrp_period = TMR0L;           // grab the pulse period!
      TMR0L = 0;                    // and ready to record next period

      if(rrp_period >= 100) break;  // if >=200uS, is unexpected start pulse!

      if(rrp_period < 61) rrp_data.F0 = 0;    // 61 = 122uS
      else                rrp_data.F0 = 1;
      rrp_data = (rrp_data << 1);   // save the good bit into rrp_data
      rrp_bits--;                   // and record 1 more good bit done

    // gets to here after 8 good bits OR after an error (unexpected start pulse)
    if(rrp_bits)      // if error
      rrp_bytes = 0;  // reset bytes, must run from start of a new packet again!
    else              // else 8 good bits were received
      rxdat[rrp_bytes] = rrp_data;  // so save the received byte into array
      rrp_bytes++;                  // record another good byte was saved

Note! Each received byte must consist of;
1 valid start pulse (period between 200-500 uS)
8 valid data bits (each bit period is <200 uS)

Preamble for pulse period protocol?

After writing and testing the transmit and receive packet functions, it became obvious the preamble could be very simple.

All that was needed for a "preamble" was to send a handful of start pulses (each has a period of 250uS), these pulses keep resetting (and re-synchronising!) the packet receive function until the first byte starts. (The "first byte" wll be a start pulse followed by 8 good data bit pulses).

There you go! That's all you need to get good reliable one way data comms at 600-800+ bytes per second from very cheap RF modules. Have fun.

- end -

[Back to Home Page]