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
}
// 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