Arduino VirtualWire for PIC microcontrollers

Share:

Overview

This is a port of the Arduino VirtualWire 1.20 [1] for PIC microcontrollers with some modifications to compile it with the Hitech's PICC. It features a reduced memory footprint and has been extensively tested on a PIC 16F628, because it is the smallest PIC 16 I have available, but it's possible to compile it for more powerful PIC microcontrollers like a 16F88, 16F1825, etc.

It's fully interrupt driven and compatible with the original Arduino VirtualWire.

Schematic and radios

I'm using a pair of receiver and transmitter equal to the ones below. They both work on 433.920Mhz which is included in the ISM ranges [2] for region 1, freely available for use in Europe, Africa and Asia without a license [3].

By default the receiver output pin connects to PIC pin 9 while the transmitter input pin connects to PIC pin 10. The two pins colored blue and purple are the serial port, used in the example programs for debug.

 

Software

The original VirtualWire for the Arduino is written as a set of functions compiled in C++. This port compiles in C, with the Hitech PICC compiler either in Lite, Standard or Pro modes. In Pro mode the code takes around 33% of the total flash memory of a PIC 16F628.

You may adapt it to other compilers.

Improvements

Keeping in mind that most low-end PIC 16 microcontrollers have a lot less memory than an Arduino Uno (ATMEGA328P) I made changes the original code to make it better suitable for these PICs. Here are the changes:

Virtualwire transmission rates vs. Crystal frequency

PIC microcontroller's oscillator frequency is internally divided by 4, so if we are using a 4MHz crystal the instruction frequency (Program Counter increment) will be 1MHz [4]. This has a great impact in the bitrates supported by Virtualwire.

After testing, the maximum frequencies I was able to achieve are the ones presented in the table below. The RX value was tested up until the PLL clock started to drift and missing bits and the TX value was tested until the PIC was left running almost exclusively inside the ISR, without any free time.

Bitrate vs XTAL
Frequency Max RX bitrate Max TX bitrate Recommended bitrate Oscillator Info
 4 MHz  800  1000  600 4MHz internal
 8 MHz  1600  2000  1200 8MHz crystal
 10 MHz  2000  2500  1200 / 1500 10MHz crystal

 

Pin selection and configuration

On the original VirtualWire library all pins may be changed with dedicated functions prior to the call to vw_setup(). Those functions are not available in the PIC version to reduce the code size but it's possible to change the TX and RX pins at compile time by changing the macros TxData, TxTris, RxData and RxTris in the virtualwire.cpp file. The default pins are RB3 (pin 9) and RB4 (pin 10) for RX and TX respectively.

The PTT pin is not used and not implemented.

Bitrate oversampling

Bitrate is set in vw_setup(uint16_t brate) at startup. VirtualWire uses timer 0 to run the ISR containing the transmit section and the receiver PLL. This timer will run at 8 times the desired bitrate by default but you may modify the oversampling value by changing the macro OVERSAMPLING in virtualwire.c.

Message size

The maximum message size is set on the macro VW_MAX_MESSAGE_LEN and defaults to 24 bytes. On the original VirtualWire the default is 80 bytes.

As with the other macros it's possible to modify the maximum message size but keep in mind that the VirtualWire internal buffer resides on memory bank 1 and its space is limited. The size required by the internal buffer is always two times the size of VW_MAX_MESSAGE_LEN. A maximum message size of 24 bytes will require 48 bytes in bank 1.

 

PIC example code and Arduino VW sketches

PIC Receiver Demo


#include <htc.h>
#include "..\stdint.h"
#include "..\virtualwire.h"
#include "rs232.h"


__CONFIG(MCLREN & PWRTEN & BOREN & LVPDIS & WDTDIS & INTIO);

static bank1 uint8_t text[VW_MAX_MESSAGE_LEN];

void interrupt global_isr(void)
{
    if(T0IF)
        vw_isr_tmr0();
}

void main(void)
{
    uint16_t i;
    
    serialInit(9600);
    vw_setup(600);

    puts("PIC Receiver Demo\n");

    vw_rx_start();

    while(1)
    {
        if (vw_have_message())
        {
            uint8_t len = VW_MAX_MESSAGE_LEN;

            if (vw_recv(text, &len))
            {
                for (i = 0; i < len; i++)
                    putchar(text[i]);
                putchar('\n');
            }
        }
    }
}

Arduino VW Transmitter Sketch


// based on transmitter.pde

#include <VirtualWire.h>

void setup()
{
    Serial.begin(9600); // Debugging only
    Serial.println("setup");

    pinMode(13, OUTPUT);

    // Initialise the IO and ISR
    vw_set_rx_pin(6);
    vw_set_tx_pin(7);
    vw_set_ptt_inverted(true); // Required for DR3100
    vw_setup(600); // Bits per sec
}

void loop()
{
    const char *msg = "Hello from Arduino";

    digitalWrite(13, true); // Flash when transmitting

    vw_send((uint8_t *)msg, strlen(msg));
    vw_wait_tx(); // Wait until the whole message is gone

    digitalWrite(13, false);
    delay(2000);
}

 

PIC Transmitter Demo


#include <htc.h>
#include "..\virtualwire.h"

__CONFIG(MCLREN & PWRTEN & BOREN & LVPDIS & WDTDIS & INTIO);



void interrupt global_isr(void)
{
  if(T0IF)
  vw_isr_tmr0();
}

void delay(unsigned int delay)
{
  while(delay--);
}

void main(void)
{
  const char text[] = "Hello from PIC";

  CMCON = 0x07; // analog comparator disabled
  VRCON = 0x00; // voltage reference module disabled

  vw_setup(600);

  while(1)
  {
    vw_send(text, sizeof(text)-1);
    delay(20000);
  }
}

Arduino VW Receiver Sketch


// based on receiver.pde

#include <VirtualWire.h>

void setup()
{
  Serial.begin(9600);// Debugging only
  Serial.println("setup");

  pinMode(13, OUTPUT);

  // Initialise the IO and ISR
  vw_set_rx_pin(6);
  vw_set_tx_pin(7);
  vw_set_ptt_inverted(true); // Required for DR3100
  vw_setup(600); // Bits per sec

  vw_rx_start(); // Start the receiver PLL running
}

void loop()
{
  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) // Non-blocking
  {
    int i;

    digitalWrite(13, true); // Flash on received

    // Message with a good checksum, dump it.
    Serial.print("Got: ");
    for (i = 0; i < buflen; i++)
    {
      Serial.print((char) buf[i]);
    }
    Serial.println("");
    digitalWrite(13, false);
  }
}

 

 

Pictures and Videos

Video showing a console application running on an Arduino and sending commands to a PIC via VirtualWire.

Downloads

The complete source code of VirtualWire with two example programs is available in the following zip file:

References

  1. VirtualWire for Arduino and other boards homepage
  2. ISM bands
  3. ISM Regions
  4. PICmicro MID-RANGE MCU Family Architecture

 

Published on Tuesday 2014/01/14, last modified on Sunday 2015/08/09