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.
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.
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.
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:
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.
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 |
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 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.
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);
}
}
Video showing a console application running on an Arduino and sending commands to a PIC via VirtualWire.
The complete source code of VirtualWire with two example programs is available in the following zip file:
Published on Tuesday 2014/01/14, last modified on Sunday 2015/08/09