Sunday, 27 October 2013

Single wire communication between two PIC micros, using 433mhz radio and Manchester encoding - sending

There are plenty of cheap RF transmitter/receiver pairs on sites like eBay and they're a great alternative to implementing full wi-fi for our electronic board game. Instead of wi-fi, we're considering using an ethernet to spi converter to physically plug something into a home router, and using cheap RF comms to get this module to talk to our board game controller.

So our set up will be:
  • All board sections connected via UART/serial
  • Each board section is connected in parallel to the board game controller (connected to one edge of the board)
  • The board controller transmits data via cheap RF to a base unit
  • The base unit is connected to the home router via ethernet cable

This removes the need for setting up the controller module by flipping between AP mode and infrastructure mode and simplifies things at the user end. Admittedly, this makes things a little more complex to implement, but the cost savings and ease-of-use for the end user should make this extra effort worthwhile. If we can get reliable data flowing between the RF transmitter and receiver, we can re-use this technology to make our quick-draw toy guns communicate wireless-ly too.

So the first thing we need to do is look at implementing single wire communications. Simply put, the state of the data line on the transmitter is reflected instantaneously on the data line of the RF receiver. We're going to re-visit Manchester encoding to transmit data over this single wire interface.

Using Manchester encoding, data is transmitted as a series of rising and falling edges on the data line.
The simplest form of single-wire encoding is to hold the line high to represent a one, and pull it low to represent a zero. This is ok, but can lead to long periods of the data line being held high/low, which can be difficult to decipher as valid data. Manchester encoding uses a low-to-high transition on the line to represent a value one, and a high-to-low transition to represent a zero.

The images on the left show the simplest way of encoding data - high is a one, low is a zero. Images on the right show the same data encoded as a transition in the data line.

Because we're going to write the firmware for both sender and receiver, we're just going to stick to a known timing rate. Our cheap RF modules are good (according to the datasheet) for up to 4khz - so to send one byte of data by flickering the line at up to 4,000 times per second, we can send 4000/8 = 500 bytes per second. This is more than fast enough for the tiny packets of data we're going to want to send.

To allow plenty of wiggle-room, let's work at 2khz. This means each "frame" in our data is going to be 1/2000 = 0.0005 seconds (0.5ms) "wide". So to transmit the value one, we would hold the data line high for 0.25ms, then pull is low for 0.25ms. To transmit the value zero, we hold the data line low for 0.25ms, then pull high for 0.25ms.

On the receiving end, we can parse the incoming data, store it in a buffer, and assume an end in data transmission when the data line does not change for more than 1ms (this represents 4 "frames" of the data line not changing) For every packet of data we can keep an XOR sum of all the bytes and send this checksum value along as the last byte in the message. As we decode the data on the receiver, we can recreate this XOR sum for each byte is received (obviously excluding the very last byte in the stream) and compare to the calculated value from the transmitter (the last byte in the stream). If the two match, we can say we've successfully decoded the data.

From this rather simplified description, we can already see that transmitting the data is a relatively simple task - we need to split each byte of data into it's constituent bits and flicker the data line, using a simple delay between changing the state of the data line.
Decoding the data on the receiving end is going to take a bit more effort (and probably a bit more debugging) so to begin with, let's create our transmitter, and make sure that this is working correctly.

Here's some Sourceboost C for a Manchester encoding transmitter for a PIC 16F882 (running off it's own internal oscillator) which can be modified by changing the microcontroller set-up bits in the first few lines, for other devices.

#include <system.h="">

// this sets up the device to run of it's own internal oscillator, at 8Mhz
#pragma DATA _CONFIG1, _DEBUG_OFF & _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_OFF & _WDT_OFF & _INTOSCIO
#pragma DATA _CONFIG2, _WRT_OFF
#pragma CLOCK_FREQ 8000000

#define dataPin portc.0

// program initialise
void initialise(){
   
     // set up the internal oscillator
     osccon.6=1;          
     osccon.5=1;            // 111=8Mhz
     osccon.4=1;
   
     // set up the i/o ports
     trisa=0x00;            // portA are output pins
     trisb=0xFF;            // all of portB is an input
     trisc=0x00;            // portC are output pins
   
     ansel = 0x00;      // only digital inputs (no analogue)
     anselh = 0x00;    
   
     // turn on portb pull up resistors
     option_reg.7=0;      // _RBPU=0 means enable pull-ups
     wpub=0xFF;            // this is the individual pull-ups on portb (all enabled)
         
     // initialise the output pins
     porta=0x00;
     portc=0x00;
   
}

void ManchesterSend(char iValue){
     if(iValue==1){
           // zero to rising edge = value 1
           dataPin=0;
           delay_10us(25);
           dataPin=1;
           delay_10us(25);
     }else{
           // one to falling edge zero = value zero
           dataPin=1;
           delay_10us(25);
           dataPin=0;
           delay_10us(25);
     }
}

void sendByte(char i){
     char k=0;
     char j=i;
     char h=0;
     for(k=0; k<8; k++){
           h=j & 1;
           if(h==0){
                 ManchesterSend(0);
           }else{
                 ManchesterSend(1);
           }
           h=h>>1;
     }
}

void sendData(char i1, char i2, char i3, char i4){
     // this sends some data over a single wire, using Manchester encoding
     // first thing send a preamble of 11111101b
     sendByte(11111101b);
   
     // create an ongoing XOR sum of all bytes sent
     char iXOR=0;
   
     // send each byte and update the XOR sum
     sendByte(i1);
     iXOR^=i1;
     sendByte(i2);
     iXOR^=i2;
     sendByte(i3);
     iXOR^=i3;
     sendByte(i4);
     iXOR^=i4;
   
     // send the XOR sum as a checksum at the end
     sendByte(iXOR);
   
     // pull the data pin low to indicate the end of a transmission
     dataPin=0;
}

// main program executes from here
void main(){
     initialise();
   
     // this is the main code loop
     while(1){
           if(portb.0==0){
                 // debounce
                 delay_ms(10);
                 if(portb.0==0){
                       while(portb.0==0){
                             // wait for release
                       }
                     
                       // send some data
                       // (values chosen to be a mix of 1 and 0 in binary)
                       sendData(179,227,24,81);
                 }
           }
     }
}

void interrupt(){
     // no interrupts used in this transmitter
}

The idea is to wait for a button press. When the user presses the button, we transmit some data (the values chosen were just values which had a "nice mix" of ones and zeros in their binary representation). These values will never change, so when we come to write and debug our receiver code, we at least know what values we should be looking for!

Next up, how to receive and decode data on a single wire data interface....

No comments:

Post a Comment