Go Down

Topic: Manchester encoding algorithm and library (Read 9879 times) previous topic - next topic

Cheater

Quote
I've decided that manchester encoding was not needed in this case.  The pairs don't specifically need it and I can get double the bitrate without it.

Are you sure?
Try sending 0xFF ten times and then send 0x00 to make sure.

8b/10b encoding is another more efficient encoding that we can use.

I'm sure.  The library that I am writing doesn't use manchester encoding at all.  My experience of how the Rx/Tx pairs work is you just raise the Tx pin and the Rx pin goes high too.  Now, there are some serious issues with timing, bit slicing, and noise but mine work as I described.  I have an ancient oscilloscope that I've been working with and it is clear to see that the Rx/Tx pair just raises the pin when you do, except for some noise.

ZingBerra

Thanks! With some simple tests (no library writing) I found something similar.

However: You need to transmit/recieve in rather tight loops, otherwise the syncronization gets lost.

Ok, this is still very far from finished.  Very far...

The Tx side of things just prints the numbers from 0-254 to the air.

Tx:
Code: [Select]

/*********************************\
*  Author: Dustin Johnson       *
*  E-mail: Dustin@Dustinj.us    *
*  Date: February 1, 2008       *
\*********************************/

#define _delay delayMicroseconds
#define DELAY 1000
#define OUTPUT_PIN 7
byte last = 0;

void print_byte(int pin, byte input_byte) {
 int i;
 
 for (i=0; i<8; i++) {
   if (((input_byte >> i) & 1) != last) {
     last = !last;
     digitalWrite(pin, last);
   }
   _delay(DELAY);
 }
 
 // Parity bits
 input_byte ^= input_byte >> 4;
 input_byte ^= input_byte >> 2;
 input_byte &= 0x3;
 for (i=0; i<2; i++) {
   if (((input_byte >> i) & 1) != last) {
     last = !last;
     digitalWrite(pin, last);
   }
   _delay(DELAY);
 }
}

void print_preamble(int pin) {
 for (int i=0; i<7; i++) {
   if (((0x5B >> i) & 1) != last) {
       last = !last;
       digitalWrite(pin, last);
   }
   _delay(DELAY);
 }
}

void wireless_print(int pin, byte input_byte) {
 print_preamble(pin);
 print_byte(pin, input_byte);
 digitalWrite(OUTPUT_PIN, LOW);
 last = 0;
}

void setup() {
 pinMode(OUTPUT_PIN, OUTPUT);
 digitalWrite(OUTPUT_PIN, LOW);
}

void loop() {

 for (byte i=0; i<254; i++) {
   wireless_print(OUTPUT_PIN, i);
   delay(10);
 }
}


The Rx side of things gets the numbers from the air and prints them to the serial port (that you can see with the built in Serial Monitor or Hyperterminal or anything else...) at 115200 baud.

Rx:
Code: [Select]

/*********************************\
*  Author: Dustin Johnson       *
*  E-mail: Dustin@Dustinj.us    *
*  Date: February 1, 2008       *
\*********************************/

#define DELAY 1000
#define GRACE 150
#define LOWER_BOUND (DELAY-GRACE)
#define UPPER_BOUND (DELAY+GRACE)
#define INPUT_PIN 2

void setup() {
 Serial.begin(115200);
 pinMode(INPUT_PIN, INPUT);
}

void loop() {
//#define _PRINT_DEBUG
#ifndef _PRINT_DEBUG
 unsigned long read_bits;
 boolean timed_out;
 boolean current_bit;
 byte last_state = digitalRead(INPUT_PIN);
 unsigned long start_time = millis() + 500;

 timed_out = true;
 while(millis() < start_time) {
   unsigned int length = pulseIn(INPUT_PIN, HIGH);
   if (length >= LOWER_BOUND*2 && length <=UPPER_BOUND*2) {
     length = pulseIn(INPUT_PIN, LOW);
     if (length >= LOWER_BOUND && length <=UPPER_BOUND) {
       timed_out = false;
       break;
     }
   }
 }

 if (timed_out == true) {
   return;
 }

 delayMicroseconds (DELAY/2);

 timed_out = true;
 while(millis() < start_time) {
   read_bits = (read_bits >> 1) | (digitalRead(INPUT_PIN) << 6);
   delayMicroseconds(DELAY);
   if ((read_bits & 0x78) == (0x5B & 0x78)) {
     timed_out = false;
     break;
   }
 }

 if (timed_out == true) {
   return;
 }

 read_bits = 0;  
 for (int i=0; i<10; i++) {
   current_bit = digitalRead(INPUT_PIN);
   read_bits |= (current_bit << i);
   delayMicroseconds(DELAY);
 }

 // Parity check
 byte check = (read_bits & 0xFF);
 check ^= check >> 4;
 check ^= check >> 2;
 check &= 0x3;
 if (check == ((read_bits >> 8) & 0x3)) {
   Serial.println((read_bits & 0xFF), DEC);
 } else {
   Serial.print('\n');
 }

#else
 unsigned long test[60];
 
 Serial.println("Starting!");
 for (int i=0; i<60; i+=2) {
   test[i] = pulseIn(INPUT_PIN, LOW);
   test[i+1] = pulseIn(INPUT_PIN, HIGH);
 }
 
 for (int i=0; i<60; i++) {
   if (i%2 == 1) {
     Serial.print("1 - ");
     if (test[i] >= LOWER_BOUND && test[i] <=UPPER_BOUND){
       Serial.print("S ");
     }
   }else{
     Serial.print("0 - ");
   }
   Serial.println(test[i], DEC);
 }
#endif
}


Anyhow I'll have more time in a few days to get back to this.... hopefully!   :P

ZingBerra

#34
Feb 02, 2008, 05:42 pm Last Edit: Feb 02, 2008, 05:43 pm by ZingBerra Reason: 1
Awesome Dustin!
As I said, I've made the same observation as you, that these pairs are stable. Creating a fine pattern on the TX-side shows up very nice on the RX-side if connected to a scope.

Your code works fine for me both at 5 inches (no problems) and for at least 300 feet (just a few drops, but no "false positives") with simple wire antennas at 434 MHz.

How do you anticipate that transmitting of the byte "0x5B" would be? Isn't there a risk for it to be misintepreted as a preamble?

I look forward to your code even if this works now!

ZingBerra

I'll answer that myself. I've tested, and a simple sequence of 0x5B,47,11 and it transmits without problem at all ranges 1-300 feet!

ZingBerra,
  I'm happy that it's working for you!  This code really needs quite a lot of help, and isn't nearly robust nor general enough to be refactored into a library, yet.  I'll be working on making this more release quality in the next short while.  This project is moving slower than I anticipated as my free time seems to evaporate from beneath me.  :D

yerg2k

Oops, sorry I kind of left you all hanging after posting partial code. I had both waitOnlvl and waitOnTime in my code and cut out the wrong one, the one which is used. Anyway, waitOnlvl is similar to pulseIn so next time I'm working on this I'll try to switch to that.

As someone noted before it takes some time for the levels to level off, so transmitting one byte then going off and doing something for some time, will not work. My transmitting arduino is only checking a few digital inputs as well as one analog input so I have it looping, doing these things and alternately transmitting the byte. The reading of the pins doesn't take that long so it is as if the data is being sent repeatedly in quick succession. If your code needs to do things which take awhile, then simply call the sending function in a for loop to send it out repeatedly a bunch of times - I don't remember what the datasheet calls for but it may be something like 15ms, which is the length of about 15 byts (?)

Here's the code. The waitOnTime can be omitted from the previous code posted.
Code: [Select]
int waitOnlvl(byte vLvl) {
 byte wlvl = digitalRead(12);
 byte wlvl2 = wlvl;
 int lvlcnt;
 while (wlvl2 != vLvl) {
   lvlcnt = 0;
   while (wlvl2 == wlvl) {
     wlvl2 = digitalRead(12);
     lvlcnt++;
   }
   wlvl = wlvl2;
   delayMicroseconds(40);
 }
 return(lvlcnt);
}

yerg2k

#38
Feb 15, 2008, 12:26 am Last Edit: Feb 15, 2008, 12:27 am by yerg2k Reason: 1
All code in this post copyleft 2008, please reproduce & reuse, and give credit where due.
Example transmit code:
Code: [Select]
int ledPin = 13;                    // LED connected to digital pin 13
int inputpin = 0;
int val = 0;                        // variable to store the read value
int sw[8];  // switches / their values
int xmitpin = 8;
unsigned int curInt;
int dlyms = 208;

void xmitByte(byte vData) {
 byte bitv = 128;
 for (int bitc = 0; bitc < 8; bitc++) {
   if (vData & bitv) {
     digitalWrite(xmitpin, HIGH);
     delayMicroseconds(dlyms*2);
     digitalWrite(xmitpin, LOW);
     delayMicroseconds(dlyms);
   }
   else {
     digitalWrite(xmitpin, HIGH);
     delayMicroseconds(dlyms);
     digitalWrite(xmitpin, LOW);
     delayMicroseconds(dlyms);
   }
   bitv = bitv >> 1;
 }
}

void setup()
{
 pinMode(ledPin, OUTPUT);   // sets the digital pin 13 as output
 digitalWrite(ledPin, HIGH);
 int cnt = 2;                  // counter
 while (cnt <= 7) {
   sw[cnt] = 1;
   pinMode(cnt, INPUT);        // set to input
   digitalWrite(cnt, HIGH);    // enable pull-up
   cnt++;
 }
 pinMode(xmitpin, OUTPUT);
}

void loop()
{
 int cnt = 2;
 int vin = 0;
 cnt = 2;
 while (cnt <= 7) { // poll pins 2 through 7
   vin = digitalRead(cnt);     // poll the pin.
   if (vin != sw[cnt]) {       // has its state changed?
     sw[cnt] = vin;            // store the new state
     curInt = 0x4000 + (8 * vin) + cnt; // set number to transmit to reflect this
   }
   cnt++;
 }
 for (int xbr = 0; xbr < 3; xbr++) xmitByte(0);
 xmitByte(150); // preamble
 xmitByte(curInt >> 8); // data
 xmitByte(curInt & 0xFF);
 xmitByte(~curInt >> 8); // to error check
 xmitByte(~curInt & 0xFF);
 xmitByte(0); // padding
 digitalWrite(xmitpin, HIGH);
 curInt++;
}


This code turns on pins 2 through 7 internal pull-up resistor, and polls them for changes. It also continuously transmits data on pin 8, the data being an unsigned int stored in curInt. Shorting a pin (2 through 7) to ground causes this number to change, and it also changes when the pin is disconnected or its level goes high.

The data sent to indicate a switch event will always have bit 0x4000 set, and the bit with mask 0x8 indicates high or low. Bits with mask 0x7 (the three LSBits in curInt) indicates which pin the event occurred on. The number 2 in the loops above can be changed to 0 to accommodate two more pins, if one is not also using the serial port.

Receiving end:
Code: [Select]
int ledPin = 13;                    // LED connected to digital pin 13
byte amlvl = 15;
unsigned int lastint;

int waitOnlvl(byte vLvl) {
 byte wlvl = digitalRead(12);
 byte wlvl2 = wlvl;
 int lvlcnt;
 while (wlvl2 != vLvl) {
   lvlcnt = 0;
   while (wlvl2 == wlvl) {
     wlvl2 = digitalRead(12);
     lvlcnt++;
   }
   wlvl = wlvl2;
   delayMicroseconds(40);
 }
 return(lvlcnt);
}

unsigned int recvInt(void) {
 // check for level change
 unsigned long starttime = millis();
 int tmocyc = 0;
 byte reftm = amlvl + 40;
 byte reftm2 = reftm + (reftm / 2);
 //read potential bits
 unsigned long curdata = 0;
 unsigned int preamble = 0;
 byte lvlq = 0;
 byte cbit = 0;
 waitOnlvl(0);
 waitOnlvl(1);
 while ((preamble != 150) && (tmocyc < 5000)) {
   preamble = (preamble << 1) + (curdata >> 31);
   curdata = curdata << 1;
   cbit = waitOnlvl(0);
   if ((cbit < reftm) && (cbit > amlvl)) {
     reftm = cbit;
     reftm2 = reftm + (reftm / 2);
   }
   if (cbit > reftm2) curdata++;
   waitOnlvl(1);
   tmocyc++;
   if ((tmocyc % 128) == 127) {
     if (millis() - starttime > 1000) tmocyc = 5000;
   }
 }
 if (tmocyc == 5000) {
   return(0);
 }
 else {
   if ((preamble == 150) && ((curdata >> 16) == (~curdata & 0xFFFF))) {
     return(curdata >> 16);
   }
 }
}

void setup()
{
 pinMode(ledPin, OUTPUT);   // sets the digital pin 13 as output
 pinMode(12, INPUT);
 digitalWrite(12, LOW);
 Serial.begin(115200);        // open serial
}

void loop()
{
 unsigned int newInt;
 newInt = recvInt();
 if ((newInt != lastint) && (newInt != 0) && (newInt != 65535)) {
   Serial.print(newInt >> 8, BYTE);
   Serial.print(newInt & 0xFF, BYTE);
   lastint = newInt;
 }
}


The receive code simply looks for legible data coming in on pin 12, and when it sees good data and the number is different than the last one received, it prints it to the serial port. This is since, the transmitter will be continuously transmitting a number in curInt, which only changes with the line levels of pins 2-7 on the transmitting MCU.

Hopefully that works this time. I had a lot of extra function in my code that I snipped out for the purposes of this example, and I couldn't test it. But it did work before snipping out the unrelated code.

To have the loop print the received number in decimal, change the two Serial.print lines above to, Serial.println(newInt, DEC);

Please post here if you're getting results! I'll check back from time to time, and hopefully soon I'll have motivation enough to rewrite this code to use this 8b/10b encoding, and perhaps turn it all into a library.

Dustin, we must be using different modules. Mine are likely cheaper in price & quality. They certainly do not work as you described - changing the tx on one to high or low does not cause the receivers rx to do the same, unless the line level in changing rapidly. This was my whole reason for trying to implement manchester-ish encoding.

(arrgh 5500 char post limit)

yerg2k

Soon I'm going to clean up the library and change the error checking, since it seems once in a while static is recognized as valid data. I'd like to write 8b/10b encoding but since it has to be done in software, this may take up more program space than I'd like. I may end up trying both just to see the difference in data rates and sketch sizes.

I haven't looked around too much, but are there any tutorials on writing libraries? I'd like to see about making one with these codecs integrated.

Cheater

I've got a bunch of ATtiny2313's running at 20mhz.
My goal now is to get Manchester encoding running on them so they do the hard work and the Arduino is free to do other things.

follower

Quote
I haven't looked around too much, but are there any tutorials on writing libraries?

Arduino Library Tutorial

--Phil.

yerg2k

#42
Mar 27, 2008, 11:37 pm Last Edit: Mar 28, 2008, 12:41 am by yerg2k Reason: 1
All done!  :)

I decided to make two separate libraries as some of my projects only use the transmitter or receiver (not both), and I found combining the code into one library makes the sketch larger.

Here is an archive with the libraries for both transmit and receive, along with example sketces for both: download

I'll put the example code here as well, so you can see how it works.
All of this code is released into the public domain, I hope it helps those of you who have been looking to use these RF links for some time  :)

Code: [Select]
#include <RFXmit.h>
RFXmit rfxmit(7, 208); // Initialize the routine
                      // use pin 7 to transmit
                      // delay 208us between level change

unsigned int curInt;   // an integer to count with

void setup()
{
}

void loop()
{
 for (int rpt = 0; rpt < 5; rpt++) rfxmit.xmitInt(curInt);
 curInt++;
 delay(1000);
}


Please note, 208 is the delay in microseconds between oscillations, suitable for 2400bps RF links. Halve this number if yours is a 4800bps. The receive code auto-detects. The above code just counts up frm 0, transmitting a new number every second.

Code: [Select]
#include <RFRecv.h>
RFRecv rfrecv(12);  // use pin 12
unsigned int lastint; // last number received

void setup()
{
 Serial.begin(9600);        // open serial
}

void loop()
{
 unsigned int newint; // for storing the last received int
 newint = rfrecv.recvInt();
 if ((newint != lastint) && (newint != 0)) {
   Serial.println(newint, DEC);
   lastint = newint;
 }
}


This will print to the serial port, the number received, when a valid number is received which is different than the last valid number received. It will not return any data if the data received is illegible (a simple CRC is used). It also doesn't repeat the same number twice. In the above transmit example, the current number is transmitted multiple times to allow the receiver time enough to adjust its gain.

Please post here with your questions, comments, improvements, etc.

I have tested this code with good results. No erroneous data is returned, and everything continues to work at a good distance (with proper antennas).

yerg2k

I also wanted to note that I scrapped the old error checking code which I spent so much time debugging  :P It seems it didn't catch all errors, and sometimes illegible data would get through.

The new code uses a CRC check like this:
Code: [Select]
   unsigned int chkdata = curdata >> 16;
   byte chkbyte1 = chkdata;
   byte chkbyte2 = chkdata >> 8;
   byte chkbyte3 = chkbyte1 + chkbyte2;
   chkdata = (chkbyte3 * 0x100) + (255 - chkbyte3);
   if ((preamble == 150) && ((curdata & 0xFFFF) == chkdata)) {
     return(curdata >> 16);
   } else {
     return(0);
   }

yerg2k

I crafted a page with all of the source code on it, including examples. There is also a write up about how it works and how it is different from manchester encoding. Still, it is similar to manchester and it's purpose is the same as far as encoder-required RF transceivers. Here is the page: click

Go Up