433MHz RF Modules...

I've noticed a lot of discussion on the forums and various websites about the generic 433MHz (and 315MHz) RF Transmitter/Receiver modules that can be purchased for pennies on the Internet. I decided to have a look at these little modules as I wanted to add a remote sensor to my ongoing Home Automation project. My project uses the nRF24L01+ modules but this particular sensor was going to be a little too far away for their range.

Separate Transmitter & Receiver
Combined Transmitter & Receiver

I wasn't expecting much as there is scant information about these devices and some of the things I have seen written and published on YouTube made me think that I would be wasting my time and money but, I like a challenge.

The schematic of the TX module is shown on the left and it shows how simple the Transmitter really is. The heart of the module is the SAW resonator which is tuned for 433.xx MHz operation. There is an RF oscillator/output transistor plus a switching transistor and apart from a few passive components, that's it. When a logic HIGH is applied to the DATA input, the oscillator runs producing a constant RF output carrier wave at 433.xx MHz and when the DATA input is taken to logic LOW, the oscillator stops. 315MHz modules are identical except for the SAW resonator which will be tuned for the lower frequency. The Vcc voltage for the Transmitter can be between 3.5 & 12V on most modules (be sure to check the specification) and the RF output is dependant upon the Vcc voltage. More volts = more RF output.

Some users have reported that they have had problems with the higher Vcc voltages but  have not seen this myself.

Some TX modules don't even have the switching transistor so logic HIGH = carrier OFF, logic LOW = carrier ON. This is important when you start to use a library to handle your data such as VirtualWire, mentioned later.

The TX modules use basic ASK (Amplitude Shift Keying)  modulation i.e. ON/OFF carrier output. I won't say much about the Receiver but it is very simple and consists of a RF tuned circuit and a couple of OP Amps to amplify the received carrier wave from the TX. Some Receivers employ a PLL (Phase Lock Loop) device which enables the decoder to "lock" onto a stream of digital bits which gives better decoded output and noise immunity. In its simplest form, the output of the RX reflects the state of the RF input from the TX. The Receiver is not good at rejecting unwanted noise, although a PLL helps, and has a wide frequency window so it relies on a good signal from the Transmitter.

The illustration above shows, on the upper trace, a burst of data, or packet,  from the Transmitter. The lower trace shows the output of the Receiver. You can clearly see the noise being picked up by the RX prior to the TX signal being received and the clear period following the transmission completion where the RX recovers.

The modules will transmit up to a metre or two without an antenna so to go any further you need to add one. The length of the antenna is calculated as follows:

Speed of light (in metres per second) / Frequency (in Hertz) = Wavelength;

300,000,000 metres per second

 = 0.6928 metres or 69.28 cm
433,000,000 Hz

A full wave antenna would need to be 69.28 cm long which is a little unwieldy so we'll opt for a quarter wave antenna which will be 173 mm in length for best performance. Both the TX and RX modules have a solder pad for attaching an antenna  which should be made of solid core copper wire capable of fitting the hole in the PCB.

Do NOT be tempted to coil the antenna to make it more compact as this will seriously impact range. A straight antenna is always best!

I ordered some modules from China and a couple of weeks later I had 2 pairs of these devices sitting on my bench ready to be tested so I took a long breadboard and parked an Arduino UNO at each end of it and with the help of 6 jumper wires I had a Transmitter (TX) installed at one end and a Receiver (RX) at the other. The distance between the TX and the RX was 120mm but I wasn't using any form of antenna so there wasn't much danger of RF swamping the RX. The TX UNO used the Blink sketch modified to switch the TX on and off in time with the LED and the RX UNO used a simple sketch to turn an LED on and off when the DATA pin of the RX changed. It all worked perfectly!

But, we need to do more than just send ON/OFF signals if we're to send anything meaningful. I'm going to be very critical of some publishers of so-called Video Tutorials on YouTube as some of them clearly haven't a clue what they are talking about. The most common misinformation is that you can only send single bytes at a time or just text characters but, none of this is true. One video presenter admits that he knows nothing about the 433MHz RF modules at the start of his "tutorial" so, why make yourself look an idiot then?

I don't claim to know it all and if I'm wrong I hope someone will tell me, kindly.


I'm going to introduce a library called VirtualWire (other libraries are available) which, if you do your research, will be listed as obsolete but I still think that VirtualWire is the best and simplest to use library for the 433MHz and 315MHz modules. I have included the last version on the downloads page along with the examples I will discuss later.

VirtualWire is a library that allows simple data transfer between devices and you can test it out by just hooking up a jumper wire between two Arduinos using the sketches below. What VirtualWire does is to take our data, encapsulate it into a packet of data which includes a CRC (Cyclic Redundancy Check) and then send it with the necessary preamble and header to another device. If the data is received correctly, the receiving device is informed that there is data available and proceeds to decode and action it.

I am going to use the term "Packet" to refer to the encapsulated data and control information from now on. You may hear the terms "Packet" and "Frame" used in networking for instance and, strictly speaking, they are not the same thing as a "Packet" is really the collection of data, including headers, CRCs etc., that we wish to send and a "Frame" is what is passed to the transmitting medium such as Ethernet or Radio. Hold on a minute I hear you say, isn't that the same thing? Well, no, because the sending medium may require a bit more data just for sending and receiving which is not part of our data Packet. It is quite normal for information to be added to our Packet as it is sent and for that additional information to be removed before we actually receive the Packet. It's transparent to us but it means that the Packet is not necessarily all that gets sent and received. This is the simple difference between a Packet and a Frame. We create a "Payload" of data and VirtualWire creates a "Packet" of our data and other necessary information such as the CRC checksum and then sends it to the Transmitter. The Frame in the VirtualWire environment is the data packet plus the preamble bits.

The bit rate is quite slow with 10000 bits per second for a really good connection, falling to 1000 - 2000 bps for longer RF link so, I'll stick my neck out and say that these modules are good for a bit rate of 4.7K bps or 4700 bps if you prefer.

I can hear the shouts of "Why not use the Serial protocol then?" and my reply would be "If only it were that simple!". Serial communication in Arduino land is a Byte at a time protocol with each Byte being sent individually along with a Start bit, Stop bit and Parity bit. Where the 433MHz RF modules are concerned there is no method of handling Byte based transmission without huge overhead. The RX needs a certain amount time to stabilise when a signal is received so that it can adjust to the strength of the signal or if a PLL is used, train to the incoming bit stream.

There are 433MHz modules available that can interface with Serial ports but they are relatively expensive . Other modules can be purchased with higher RF output for longer range and faster bit rates but, again, they are more costly.

The VirtualWire "Frame" is made up as follows: A 36 bit stream of "1" and "0" bit pairs, called a "Training Preamble", is sent at the start of every transmission followed by a 12 bit "Start Symbol"  and then the data followed by a two Byte checksum. It looks something like this:

<36 bit Preamble><12 bit Start Symbol><8 bit Message Length><2 Byte FCS (Frame Check Sequence)>

A Frame Check Sequence or CRC is added at the end of the packet which is recalculated by VirtualWire at the RX end and if the CRC check is correct, the receiving device is alerted. If the CRC check fails, the packet is discarded.

This application allows us to send up to 27 Bytes of data with some assurance that if it reaches the receiving device, it will be intact. The data can be anything you like from a single Byte to multiple text characters, it's up to you. You can see from the illustration above that sending single Bytes would be very time consuming at low data rates.

The upper trace, shown above, is the actual packet bits being sent by the TX and, going left to right, you should be able to make out the 36 preamble bit pairs. They are sent as "1100" so you see 18 pairs. I'll leave it up to you to decode the rest of the packet but good luck, the data is encoded into 6 bit chunks! The lower trace shows the RX output and, on the left, the noise received before the RF signal is received. As you can see, the Receiver settles very quickly in this example which was with a TX and RX within a few centimetres of each other. At longer ranges, noise, signal strength and other factors will make the RX take longer to settle.

The Sketches:

The sketches above show a simple transmission of the current millis() count and a sequence number to the receiving Arduino. The receiving Arduino checks that no packets have been lost and displays the received millis() and sequence count on the Serial Monitor at 57600 baud. You can adjust the TX_RATE to see what speed you can get but be aware that the delay() setting at the bottom of the TX loop must be long enough to allow the receiver to display the data otherwise you will lose packets.

The output of the Receiver sketch looks like this:

I have used a structure for the data as follows:

struct PacketData{
uint32_t data0_3;    // some data
uint32_t data4_7;    // some more data
uint32_t dummy8_11;    // 4 Byte packet filler
uint32_t dummy12_15;    // 4 Byte packet filler
uint32_t dummy16_19;    // 4 Byte packet filler
uint32_t dummy20_23;    // 4 Byte packet filler
uint16_t dummy24_25;    // 2 Byte packet filler
uint8_t packetCount26;    // packet sequence counter

This is an example of using all 27 available Bytes of the payload and to show that it works I have used the first data field, data0_3 for the millis() count and the last data field, packetCount26 for the sequence count. The data values in between are still transmitted so the payload is 27 Bytes irrespective of what data is in the fields. If you want to send less data, reduce the payload content. You could have a payload as short as this:

struct PacketData{
uint8_t myDate;    // 1Byte of data

The above payload is just 1 Byte long but would be less prone to interference when transmitted. Try it for your self but remember, if you use structures, they must be the same at the transmitting and receiving end.

You can just use a Byte array which you would declare as normal e.g.. "uint8_t myBuffer[<up to 27 Bytes>];" and then fill with your data. You would provide the Transmitter with the data thus:

vw_send(myBuffer, sizeof(myBuffer));

At the Receiver you would declare a similar Byte array and tell the receiver to get the incoming data thus:

vw_get_message(myBuffer, sizeof(myBuffer));****

**** There is a problem with the VirtualWire library as the "vw_get_message(myBuffer, sizeof(myBuffer));" function does not accept "sizeof()" by default so you will have to change the sketch to something like this...

uint8_t bufferSize = sizeof(myBuffer);
vw_get_message(myBuffer, bufferSize);

...for the sketches to work with the standard VirtualWire library. However, I have a corrected version of the library in the downloads section which the above sketches are written for.

And finally:

If you remember, I mentioned TX modules with and without switching transistors earlier? Well, the Transmitter sketch above is for TX modules with switching transistors and uses the following configuration line:

vw_set_ptt_inverted(true); // 433MHz RF with switching transistor

(ptt or PTT (Push To Talk) comes from radios where pushing a button turned the transmitter ON so that the operator could talk e.g. Walkie-Talkies, CB Radios, Ham radios etc.)

This line tells VirtualWire that your TX module needs a logic HIGH to switch ON the carrier. If your TX module does not have a switching transistor, you need to change this line to:

vw_set_ptt_inverted(false); // 433MHz RF no switching transistor

How do you tell whether there's a switching transistor or not? Basically, with the type of module being discussed here just look at both sides of the TX PCB. If there's 1 transistor it's likely to be the second type and if there's 2 transistors it's likely to have a switching transistor.

In a moment of madness, I decided to see just how rapidly I could send packets to VirtualWire and decode them successfully at the receiver. Using the sketches above with the delay() statement removed I got this:

You should just be able to make out the preamble blocks, most evident on the left and centre, in the above traces (top TX, bottom RX) and the gap between each packet is around 1mS. VirtualWire handled the data with very few lost packets but I had to up the Serial Monitor to 115200 baud to get printing the data out over as quickly as possible.