Arduino RF Communications: Data Encoder for VirtualWire

Now that I am a little more comfortable with Arduino programming, I thought it was appropriate to change gears and move from communications over a serial line to RF communications. The mess of wires from my earlier haptic control project also needs cleaning up.

Mess of wiring in my haptic control project

Mess of wiring in my haptic control project

The solution – cheap and effective RF communications. These little units are unbelievably cheap (under US$1.50 for the pair). At first I was understandably sceptical whether they would even work at all, but after having played around with them for a few days now, I’m convinced that they work fine, as long as I don’t expect them to function as effectively as a Xbee.

433 Mhz ASK Tx-Rx Units. Transmitter on the left, receiver on the right

433 Mhz ASK Tx-Rx Units. Transmitter on the left, receiver on the right

Specifications:-

1. These are not transceivers!!! You need to buy two sets if you want 2-way RF communications

2. Frequency 433Mhz. ASK (Amplitude Modulation).

3. Range – about 30 – 40 ft is what I could achieve

4. Max Baud Rate – I achieved about 2000 bps with almost no drops in messaging

Arduino Library:-

Mike McCauley has developed the VirtualWire library for use with these little TX-RX units. This is a great library, very easy to use (I had it up and running in just minutes), and very effective too. It even incorporates an CRC to ensure the data arriving is of good quality.

The only drawback with the VirtualWire library is that it can only send character arrays, not numeric data. This is fine if you want to send ‘h’,’e’,’l’,’l’,’o’ over the RF link but if you want to send a floating point number say 432.67, you would have to encode it as a sequence of characters and send it as ‘4’,’3′,’2′,’.’,6′,’7′

Obviously this is not very effective. Firstly because 432.67 can actually be transmitted using only two bytes (if it is encoded properly) whereas encoding it into a character array would need at least 6 bytes to be transmitted (count the characters 4 3 2 decimal 6 7). Second, converting from character arrays to numeric values and vice-versa is usually a pain to implement.

Most of the time when I send data to/ from an Arduino, it is either sensor data or control signal data. This means numbers, more specifically floating point numbers. Here are some examples of data that I would normally transmit/recieve :

Angle Data – 0 to 360 degrees or 0 – 2*pi radians

Speed/ Distance Data – 0 to 100 – 200 units at the most

Time – 0 to 200 – 300 seconds at the most

Analog Input/Output from/to Arduino – 0 to 1023

Temperature/ Barometric Pressure etc: -50 to + 1100 at the most

Two bytes of memory can hold 65535 different values and if I decide that I would like to work with at most one decimal place of precision, I can encode positive values between 0 to 6553.5 in two bytes of memory. If I needed to work with negative numbers, I could at best encode -3276.7 to + 3276.7. As you can see, most of the data I would need to tx/rx lies well within the limits of 2 bytes of encoding space.I could then transmit these two bytes over the RF line and decode my floating point number at the other end.

Implementation :-

c++ provides us the union declaration which allows the same portion of memory to be accessed as different data types. What this means is that I can allocate the same 2 bytes of memory to my encoded floating point number (which I will ensure to be between 0 and 6553.5 only occupying two bytes) and also have the SAME two bytes of memory accessed as char[2] which could then be transmitted through the VirtualWire library over the RF link. Easy!!!

My union declaration looks like this:-


#define RF_DATA_ARRAY_SIZE 4 //The number of elements to be stored.
//This array will hold 4 elements.

#define RF_DATA_DECIMAL_PLACES 1//The number of decimal places
//that will be encoded

#define UNION_EXPANSION_FACTOR 2//The number of bytes needed
//by each element of the RFData packet

union RFData
 {
 int dataBox[RF_DATA_ARRAY_SIZE];//2 bytes each. In
//Arduino environment, int is 2 bytes.

char s[RF_DATA_ARRAY_SIZE * UNION_EXPANSION_FACTOR];//each char is
//1 byte so we need 2 chars for each dataBox[] element
 };

So what this means is that I can encode 4 floating point numbers into the structure ‘RFData’. These values could come from sensors or be control commands that I am sending out. These values would be available as two bytes each inside RFData and I can access these using s[]. So once the data is encoded, I simply send the character array s[] to VirtualWire for transmission.

At the receiver end, I decode the character array received by VirtualWire into an array of floating point numbers that I can then do things with.

Testing:

I used a transmitter on a Arduino Mega and receiver on an Arduino Uno for trials. To keep things simple, I used fixed values for the data that would be transmitted.

Test Setup - Tx on Mega and Rx on Uno

Test Setup – Tx on Mega and Rx on Uno

Initial testing showed the encoding/ decoding to work perfectly with the VirtualWire library. I have uploaded a zip file with the source code in case anyone would like to try it out. Just download the zip file (you need to click on the red Slow Download button and wait for 30 seconds before the download begins)and extract it into your /Arduino/libraries directory. Or you could also download the source code and examples at my github repo. Then make sure to #include in your sketch. There are a couple of example sketches in the zip file as well.

I’m thinking this technique could be applied not just to the VirtualWire library but to any communication protocol. All that we need to ensure is that the values being encoded are within the limits of the dataBox[] memory size. For more complex data, greater precision etc, dataBox[] could be declared as something bigger than an int. If we do this however, we need to ensure that the UNION_EXPANSION_FACTOR macro is changed accordingly.

I also think this needs to be brought to fruition in the form of an actual class with it’s own methods etc. This would allow much better control of the data transfer, allow multiple RF Objects to be created, each object could have it’s own speed and size of data etc etc.

Advertisements

56 thoughts on “Arduino RF Communications: Data Encoder for VirtualWire

  1. Hi!!Your library and your work are great!!!
    Anyway I experienced an error when I use your library and the Servo.h library:
    “VirtualWire/VirtualWire.cpp.o: In function `__vector_11′:
    /usr/share/arduino/libraries/VirtualWire/VirtualWire.cpp:640: multiple definition of `__vector_11′
    Servo/Servo.cpp.o:/usr/share/arduino/libraries/Servo/Servo.cpp:336: first defined here
    collect2: ld returned 1 exit status”

    Do you have any idea how to fix it?

    Thank You
    Stonage

    • Scroll to the bottom of the FileFactory page and click on “Slow Download”. After that you will have to wait for the obligatory 30 second period. Next click the download button, making sure that “Download with FileFactory’s download manager” is UNCHECKED.

      You will get the .zip file this way.

  2. Pingback: Come realizzare un sistema di allarme con Arduino - iSmanettone

  3. Pingback: Come realizzare un sistema di allarme con Arduino | Campus La Camilla

  4. Pingback: Come realizzare un server web per visualizzare i dati di una stazione meteo - iSmanettone

  5. Thanks for the video and blog. Your encoding method doesn’t exactly help me out on my project, but it does help me understand VW much better. Thanks! To increase range, I used magnet wire cut to 1/4 wave length with no overlapping coils (like you have), and used the battery lead off of a Trinket to push 9.6V directly to the tx’s vcc in (You can get the same by just running a lead directly off your 9V battery into the TX’s vcc, note may need a “normally off” relay switch for best results, but for my project I didn’t need one). I got over 170 ft before I gave up and walked home (baud was set to 1000). Pushing even more volts to the TX (I think 16V max), and using a directional antenna will get you even farther ranges. Hope it helps!

    • Thats a very interesting method to increase range. Thanks! I should try that out sometime! By the way, where did you get the figure of a max Voltage = 16V?

      • I was mistaken. For the unit I’m using, the max vcc is 12V (didn’t have the specs handy in the OP). It’s pretty much the exact same thing as your module, except with a different crystal. Here is the link to the module’s specs on Amazon.
        http://www.amazon.com/YESGOSHOP-433Mhz-Transmitter-Receiver-Arduino/dp/B00D40BM28

        On the back of the TX there is a tiny voltage regulator. It was suggested to me to upgrade it to something that can handle a half Watt and that will let you push even more through it, though may potentially cause issues elsewhere, and will definitely void its FCC rating, so this is not recommended (and I’m not convinced it’ll work). I read somewhere that a user just glued a heat sink to the factory VR and was able to input 14V, but I don’t remember off the top of my head where I saw that, if it lasted very long, or if it even really works (I think it may have been in the comments section on amazon somewhere).

        For fastest results, just feed the TX’s VCC-in, from the VCC-in pin on your 9V battery powered Mega, to bypass the 5v regulator. You should instantly see better results, and still be well within the specs. The RX is fine as is on 5V.

        Thanks again for the blog. Most helpful.

      • Wait.. does the MEGA have a VCC-out pin? I’m wrong in my reply, possibly. I cant remember if the VCC-in can be used as an OUT. I think its possible though.

  6. Found this today while scanning the arduino files and straight away it got my full attention, well done and it worked first try on my pro mini’s, this opens up a whole new world of possible uses for those of you who can write a sketch or two, thanks for the info, it’s proving my belief about these amazing little RF devices, Cheers, Terry.

    • I don’t think the code needs to be changed at all. There are no board specific parts to the code. You will probably only need to make sure you select the correct board before uploading the code.

      • Ok, I got this working. What’s the best way to transmit data from a sensor?
        For example, a temperature sensor.

  7. Hi, I too have keenly followed your excellent blog because I want to transmit real numbers generated on a solar monitor project using 433MHz. I have replicated your specimen array transmission, as you described, with no problem. But I am not sure how to modify your code (as suggested by Pawel Wojcik on YouTube- did it work for him?) to introduce variables (floating point) into the array. Could you please give guidance? I’m really not experienced in C++ or coding in general.
    By the way I had to follow your web blog using Mozilla FireFox -it looks fine. My normal Chrome browser on a PC running Win XP produces an awful display.

    • Yes, you could do exactly as Pawel did…. for example if your solar array is outputting voltages on analog pin 4, you could use this code to encode it into the array and transmit it….
      int solarArrayPin = 4;
      outArray[0] = analogRead(solarArrayPin);

      It would work just fine. Thanks for reading and good luck with your project!

  8. had been trying to send data with the manchester protocol, but never managed to get any code compiled without errors. Sure going to try this

  9. heloo.
    any chance to get the zip not from filefactory?
    they got some maintenance, the zip file doesn’t accessible for now.
    you made exactly what I looked for, nice job.

  10. Im using your code for transfer but Im such a beginner, that I dont know how do add a DHT11 temp/humidy sensor readings to code and send it over.
    Could you help me with example code?

      • Yes, I have DHT test code that prints temperature and humidity to serial and I can read it from terminal.
        Im trying to send this info from arduino uno to mega(what is connected to PC via usb) so I could read from terminal what info mega gets.

        #include “DHT.h”

        DHT dht;

        void setup()
        {
        Serial.begin(9600);
        Serial.println();
        Serial.println(“Status\tHumidity (%)\tTemperature (C)\t(F)”);

        dht.setup(2); // data pin 2
        }

        void loop()
        {
        delay(dht.getMinimumSamplingPeriod());

        float humidity = dht.getHumidity();
        float temperature = dht.getTemperature();

        Serial.print(dht.getStatusString());
        Serial.print(“\t”);
        Serial.print(humidity, 1);
        Serial.print(“\t\t”);
        Serial.print(temperature, 1);
        Serial.print(“\t\t”);
        Serial.println(dht.toFahrenheit(temperature), 1);
        }

      • you can probably just do something like this…

        float hum = dht.getHumidity();
        float temp = dht.getTemperature();
        outArray[0] = hum;
        outArray[1] = temp

        and then just use the rest of my code verbatim to transmit the values.

  11. ok thanks but stupid question, where in code I put that?
    Could you make me example code with my DHT code and your TX/RX code and that what you posted about?

    I know im asking lot, Im trying to learn but this programming just doesnt open to me yet :/
    I would just use your code to see where to put stuff in future and learn more that way.

    • Try this code for your transmitter…

      #include “DHT.h”
      #include "DataCoder.h"
      #include <VirtualWire.h>

      const int transmit_pin = 12;

      const int baudRate = 1000;

      DHT dht;

      void setup()
      {
      Serial.begin(9600);
      Serial.println();
      Serial.println(“Status\tHumidity (%)\tTemperature (C)\t(F)”);
      dht.setup(2); // data pin 2

      SetupRFDataLink(transmit_pin, baudRate);
      }

      void loop()
      {
      delay(dht.getMinimumSamplingPeriod());
      float hum = dht.getHumidity();
      float temp = dht.getTemperature();
      outArray[0] = hum;
      outArray[1] = temp;
      outArray[2] = 0
      outArray[3] = 0

      union RFData outDataSeq;
      EncodeRFData(outArray, outDataSeq);
      TransmitRFData(outDataSeq);
      delay(500);
      }

      And I think the Receiver code should work verbatim

      • Thank you so much 🙂
        I only got one problem,
        on part:
        Serial.println(“Status\tHumidity (%)\tTemperature (C)\t(F)”);

        it gives error:
        stray ‘\’ in program

  12. Correction.
    it gives all following errors:

    Build options changed, rebuilding all

    sketch_sep20c:15: error: stray ‘\’ in program
    sketch_sep20c:15: error: stray ‘\’ in program
    sketch_sep20c:15: error: stray ‘\’ in program
    sketch_sep20c:15: error: stray ‘\’ in program
    sketch_sep20c:15: error: stray ‘\’ in program
    sketch_sep20c.ino: In function ‘void setup()’:
    sketch_sep20c:15: error: ‘u201cStatus’ was not declared in this scope
    sketch_sep20c:18: error: ‘SetupRFDataLink’ was not declared in this scope
    sketch_sep20c.ino: In function ‘void loop()’:
    sketch_sep20c:26: error: ‘outArray’ was not declared in this scope
    sketch_sep20c:29: error: expected ‘;’ before ‘outArray’
    sketch_sep20c:32: error: ‘outDataSeq’ was not declared in this scope
    stray ‘\’ in program

    This report would have more information with
    “Show verbose output during compilation”
    enabled in File > Preferences.

    • 1. You are placing strings inside the wrong type of quotes in your program. use ” once, not ‘ twice for each quote symbol. What sort of keyboard layout are you using?

      2. This program needs both the Datacoder.h and DataCoder.cpp files to compile properly. Do you have both of these inside the correct path on your hard drive?

      • 1. Finnish keyboard layout. I copied your code from here. I tried to type it too, didnt change.

        2. I have both fileds and in correct path also.

        sketch_sep20d.ino: In function ‘void setup()’:
        sketch_sep20d:18: error: ‘baudrate’ was not declared in this scope
        sketch_sep20d:18: error: ‘SetupRFDataLink’ was not declared in this scope
        sketch_sep20d.ino: In function ‘void loop()’:
        sketch_sep20d:26: error: ‘outArray’ was not declared in this scope
        sketch_sep20d:29: error: expected ‘;’ before ‘outArray’
        sketch_sep20d:32: error: ‘outDataSeq’ was not declared in this scope
        ‘baudrate’ was not declared in this scope

      • 1. In setup(), the correct variable name is baudRate, not baudrate

        2. put semi-colons after these two lines…

        outArray[2] = 0; //add the semi-colon
        outArray[3] = 0; //add the semi-colon

        Try and compile now….if it doesn’t work, please post your entire code here, and I’ll try and walk you through it.

      • Still few problems..
        In function ‘void setup()’:
        18: error: ‘SetupRFDataLink’ was not declared in this scope
        In function ‘void loop()’:
        26: error: ‘outArray’ was not declared in this scope
        ‘SetupRFDataLink’ was not declared in this scope

        #include “DHT.h”
        #include “DataCoder.h”
        #include “VirtualWire.h”

        const int transmit_pin =12;

        const int bauDRate=1000;

        DHT dht;

        void setup()
        {
        Serial.begin(9600);
        Serial.println();
        Serial.println(“Status\tHumidity (%)\tTemperature(C)\t(F)”);
        dht.setup(2); //data pin 2

        SetupRFDataLink(transmit_pin, bauDRate);
        }

        void loop()
        {
        delay(dht.getMinimumSamplingPeriod());
        float hum=dht.getHumidity();
        float temp=dht.getTemperature();
        outArray[0]=hum;
        outArray[1]=temp;
        outArray[2]=0;
        outArray[3]=0;

        union RFData outDataSeq;
        EncodeRFData(outArray, outDataSeq);
        TransmitRFData(outDataSeq);
        delay(500);

        }

      • try this code, if it compiles without error, then uncomment the lines that use the DHT class. Make sure you get your UPPER/lower case characters in your code correct.

        //#include “DHT.h”
        #include “DataCoder.h”
        #include “VirtualWire.h”
        const int transmit_pin =12;
        const int baudRate=1000;
        float outArray[RF_DATA_ARRAY_SIZE];

        //DHT dht;
        void setup()
        {
        Serial.begin(9600);
        Serial.println();
        Serial.println(“Status\tHumidity (%)\tTemperature(C)\t(F)”);
        //dht.setup(2); //data pin 2
        SetupRFDataTxnLink(transmit_pin, baudRate);
        }
        void loop()
        {
        //delay(dht.getMinimumSamplingPeriod());
        float hum=1.0;//dht.getHumidity();
        float temp=1.0;//dht.getTemperature();
        outArray[0]=hum;
        outArray[1]=temp;
        outArray[2]=0;
        outArray[3]=0;
        union RFData outDataSeq;
        EncodeRFData(outArray, outDataSeq);
        TransmitRFData(outDataSeq);
        delay(500);
        }

      • Oh man.. I just can’t believe how much better this makes my weather station project. The RF transmission was working fine, but as you noted, the float values needed to be converted to a char array at the tx, and then converted from ascii on the rx. Not ideal. Your solution is SO much better. Thanks again! Really appreciate you taking the time to post this!

  13. Hi I was trying to send data from ADXL335 to my Arduino Uno through 433MHz modules and using HT12E and HT12D for encoding and decoding
    But i am not able to get the data right on Arduino.
    It would be really great if you could help.

    Thanking You
    Yash Agarwal

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s