Saturday 9 November 2013

Receiving Manchester encoded data over a single wire using PIC 16F1825 - VirtualWire for PIC

Following an earlier post, describing how to send Manchester Encoded data over a single wire, we just realised that we've been using, but not posted an entry on the blog, for receiving the encoded data! So first up, get familiar with how we encode and send the data (the timing graph from the logic probe is especially useful)


To decode the data, we're going to use a state machine which goes through the following states:

  1. Wait for a falling edge on the data input pin. Remember that this could be happening anywhere within the incoming data stream - but we've got to start somewhere!
  2. Monitor the incoming data, and look for the preamble 10101010 followed by 101011 (the two successive bit values of one tell us that it's the end of the preamble)
  3. Read the rest of the incoming data stream until four bytes have been received (this could easily be amended to look for either an end of message marker, or perhaps the data line being held low for more than two "cycle-lengths"
  4. Display the received data (in our case, we'll spit it out over serial)
  5. (not included in this example, but should be added in any production environment) Recreate the XOR sum and compare to the last byte sent (the XOR checksum). If they match, the data is valid - if there is a mismatch, discard the data and treat as a lost data packet.
We set up a breadboard with two PICs, connecting the dataOut pin of the transmitting mcu to the dataIn on the receiving mcu with a simple jumper wire.

the transmitting PIC is on the left of the breadboard and the large red button starts the data transfer routine; data captured by the PIC on the right is sent via UART to a host (we're using the UART tool in the PICKit2 programmer to display the results onscreen)

Because we know the "baud rate" of the incoming data (since we wrote the code that sends it, we can just reuse the same Timer1 interrupt setup code) we can simple set the Timer1 interrupt to grab the state of the data input pin at a very precise, regular interval.

Since Manchester data is encoded with a transition per bit, we can automatically syncronise with the incoming data stream. Every "cycle" should consist of a high or low data line, followed by the opposite state. So every half cycle should consist of a high followed by a low, or a low followed by a high.

If we ever receive two high or two low data states, we know that we're not reading the correct part of the data signal, and can simply skip by half a cycle step to get back into sync. But skipping a cycle step would mean that instead of 7 lots of 0b10 followed by one 0b11 we'd only get 6 0b10 (since we've effectively discarded one set of binary 0b10 getting back in sync). And because we may lose the very first part of the data signal waiting for our very first high-to-low transition on the data pin, we're going to wait for at least five lots of binary 0b10 on the data line followed by one single 0b11 value. If these two conditions are met, we can say we've reached the end of our preamble, and are ready to start receiving data.

Here's the code

#include <system.h>
// ##################################
// using a 32 Mhz internal oscillator
// on a PIC16F1825
#pragma DATA _CONFIG1, 0x0804
#pragma DATA _CONFIG2, 0x1DFF
#pragma CLOCK_FREQ 32000000
// ##################################

#define dataPin portc.2
#define ledRed portc.5
#define ledGreen portc.4
#define ledBlue portc.3

char state;
char k;
char preAmbleCount;
bit pinState;
bit lastPinState;
int pulseCount;
bit bitValue;
bit bitReceived;
char bitsReceived;
bit lastBitValue;
char byteValue;
char byteCount;
char byteValues[5];

// program initialise
void initialise(){
   
     osccon=0b11110000;            // oscillator=32mhz
delay_ms(50);                  // wait for internal voltages to settle

intcon=0b11000000;            // global interrupts enabled and peripheral interrupts
apfcon0=0b01000000;      // alternative tx/rx so pickit can use programming pins for uart
wpua=0xFF;                        // inputs on portA have weak pull-ups except the data pin (RA.5)
wpuc=0xFF;                        // inputs on portC have weak pull-ups except the data pin (RC.2)
trisa=0b00000010;            // RA.1 is an input (uart rx)
trisc=0b00000100;            // RC.2 is an input (data)
ansela=0x00;                  // turn off analogue pins on portA
anselc=0x00;                  // turn off analogue pins on portC
porta=0x00;                        // set all output pins on portA off
portc=0x00;                        // turn off all output pins on portC

option_reg=0x00;
option_reg.7=0;                  // weak pull-ups enabled on any input pins

     // set up timer1
     t1con=0b00001000;            // set bit zero of t1con to 1 to actually start the timer
pie1.TMR1IE=1;                  // enable the timer1 interrupt

     // enable the global and peripheral interrupts
     intcon.GIE=1;
     intcon.PEIE=1;
   
}

void initUART(){
     //
// UART
//
baudcon.4 = 0; // SCKP synchronous bit polarity
baudcon.3 = 0; // BRG16 enable 16 bit brg
baudcon.1 = 0; // WUE wake up enable off
baudcon.0 = 0; // ABDEN auto baud detect

txsta.6 = 0; // TX9 8 bit transmission
txsta.5 = 1; // TXEN transmit enable
txsta.4 = 0; // SYNC async mode
txsta.3 = 0; // SEDNB break character
txsta.2 = 0; // BRGH high baudrate
txsta.0 = 0; // TX9D bit 9

rcsta.7 = 1; // SPEN serial port enable
rcsta.6 = 0; // RX9 8 bit operation
rcsta.5 = 1; // SREN enable receiver
rcsta.4 = 1; // CREN continuous receive enable

spbrgh = 0; // brg high byte
spbrg = 51; // brg low byte (see datasheet for 9600@32Mhz)

apfcon0.2=1; // tx onto RA.0 (for PicKit2 sharing)
}

void UARTSend(unsigned char c){
txreg = c;
while(!txsta.1);
}

void uart_print(char t){UARTSend(t);}

void uart_print(char *s){
     while(*s) {
           UARTSend(*s);
           s++;
     }
}

void uart_println(char *s){
     uart_print(s);
     uart_print("\r\n");
}

void uart_print_dec(char number){
     if(number > 99) {uart_print(((number / 100) % 10) + '0');}
if(number > 9) {uart_print(((number / 10) % 10) + '0');}
uart_print((number % 10) + '0');
}

// set timer1 value so it fires every X-uS
void preloadTimer1(){
     // at 32Mhz, fosc/4=8million
     // our radio modules are rated *up to* 4khz
     // so let's stick to about half that for now
     // 8m/2000 = 4000
     // so we want our timer1 to only count up to 4000
// timer1 is a 16-bit timer (0-65535) so we need to
// preload it with 65535-4000 = 61535 (0xF05F)
tmr1h=0xF0;
tmr1l=0x5F;
}

void startTimer(){
     // clear any left over interrupt flag
     pir1.TMR1IF=0;
     // set bit zero to start the timer
     t1con.0=1;
}

void stopTimer(){
     // clear bit zero to stop the timer
     t1con.0=0;
}

void endTransmission(){
     // first, kill the timer
     stopTimer();
   
     // turn off any transmission leds
     ledBlue=0;          
         
}

void getPinState(){
     pulseCount++;
     pinState=dataPin;
     k=pulseCount & 1;
     if(k==1){
           // this is the first of a two-part bit
     }else{
         
           // decide which value bit we've just received
           bitReceived=0;
           if(lastPinState==0 && pinState==1){
                 // a rising edge is a bit value 0
                 bitValue=0;
                 bitReceived=1;
           }else if(lastPinState==1 && pinState==0){
                 // a falling edge is a bit value 1
                 bitValue=1;
                 bitReceived=1;
           }else{
                 // no change detected- need to re-sync?
                 bitReceived=0;
                 // move the pulse count on by one, so this
                 // will run again on the next half-cycle
                 pulseCount++;
           }
         
           if(bitReceived==1){
         
           }
     }
     lastPinState=pinState;
}

// main program executes from here
void main(){

     initialise();
     initUART();
     delay_ms(250);
     state=4;
     uart_println("Waiting for data...");
   
   
     // this is the main code loop
     while(1){
           switch(state){
         
                 case 0:
                 // wait for a falling edge on the dataPin
                 if(lastPinState==1 && pinState==0){
                       state=1;
                 }
                 break;
               
                 case 1:
                 // we're monitoring the preamble
                 if(bitReceived==1){
                       // count the number of ones and zeros received
                       if(bitValue==1){
                             if(lastBitValue==0){
                                   preAmbleCount++;
                             }else{
                                   if(preAmbleCount>=5){
                                         // cool, this is the end of the preamble.
                                         // get ready for some data!
                                         ledBlue=1;
                                         byteValue=0;
                                         byteCount=0;
                                         bitsReceived=0;
                                         lastBitValue=0;
                                         state=2;
                                   }else{
                                         // we have two ones, but not following the
                                         // 101010 preamble, so we might be in the middle
                                         // of a datastream. Reset the preamble count
                                         preAmbleCount=0;
                                   }
                             }
                       }else{
                             if(lastBitValue==0){
                                   // we've had two zeros in succession. Ditch this and
                                   // wait for the start of the next preamble
                                   preAmbleCount=0;
                             }
                       }
                     
                       lastBitValue=bitValue;
                       bitReceived=0;
                 }
                 break;
               
                 case 2:
                 if(bitReceived==1){
                       // prepare the byte value for the incoming bit
                       bitsReceived++;
                       byteValue=byteValue<<1;
                       if(bitValue==1){byteValue|=1;}
                     
                       if(bitsReceived>=8){
                             // we've had a full byte of data,
                             // store it in a buffer
                             byteValues[byteCount]=byteValue;
                             byteCount++;
                           
                             byteValue=0;
                             bitsReceived=0;
                           
                             if(byteCount>3){
                                   stopTimer();
                                   state=3;
                             }
                       }
                       bitReceived=0;
                 }
                 break;
               
               
                 case 3:
                 // send what you've received over serial
                 uart_println("received data");
                 for(k=0; k<4; k++){
                       uart_print_dec(byteValues[k]);
                       uart_print(" ");
                 }
                 uart_println(" ");
                 state=4;
                 break;
               
               
                 case 4:
                 ledBlue=0;
                 pinState=0;
                 lastPinState=0;
                 preAmbleCount=0;
                 startTimer();
                 state=0;
                 break;
               
               
           }    
     }
}

void interrupt(){
     // every time timer1 fires, we send another part of a bit
     // (in Manchester encoding). Bit zero of pir1 is tmr1 overflow interrupt
if(pir1.TMR1IF==1){
           // clear the timer1 interrupt flag
           pir1.TMR1IF=0;
           // preload timer1 to get the next interrupt to occur in a timely manner
preloadTimer1();
// send the appropriate bit-part
getPinState();
     }
}


It still needs a little bit of amending for use in a production environment, but demonstrates the principle of reading Manchester Encoded data from a single wire interface.


This matches exactly with the data being sent by our little transmitter mcu:

if(button==0){

     // light the led to show we've acknowledged user action
     ledBlue=1;
   
     // wait for release
     while(button==0){}
   
     // send some data
     // (values chosen to be a mix of 1 and 0 in binary)
     sendData(179,227,24);
     ledBlue=0;
   
}

(The final value 72 is the checksum that the transmitter generates. If we start with a value zero, XOR the value 179, then XOR 227 to the result, and finally XOR this with 24, the result is, indeed, 72 - demonstrating that the data has been transmitted and received correctly).

As we've had this working for a little while, and we've had our toy guns wired up on a breadboard to demonstrate those working, the last little bit of our quickdraw shootout game is to replace the wire between the gun and the host with a 433Mhz RF transmitter/receiver pair.


These are really simple to understand (though to use them you need to implement your own "VirtualWire" single wire interface). When a paired transmitter and receiver are active, the state of the dataOut pin on the transmitter is reflected instantaneously on the dataIn pin on the receiver.

So in theory, we should be able to replace our piece of wire between the data pins with an RF transmitter module at one end, and a receiver module at the other. Schematics, photos and a write up explaining how to achieve this will be in a following post....



No comments:

Post a Comment