To decode the data, we're going to use a state machine which goes through the following states:
- 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!
- 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)
- 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"
- Display the received data (in our case, we'll spit it out over serial)
- (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.
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)
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();
}
}
// ##################################
// 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;
}
// 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