Pages: 1 2 [3] 4   Go Down
Author Topic: Manchester encoding algorithm and library  (Read 6953 times)
0 Members and 1 Guest are viewing this topic.
Brisbane, Australia
Offline Offline
God Member
*****
Karma: 1
Posts: 593
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Sacramento, CA, USA
Offline Offline
Newbie
*
Karma: 0
Posts: 30
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 14
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Sacramento, CA, USA
Offline Offline
Newbie
*
Karma: 0
Posts: 30
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
/*********************************\
 *  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:
/*********************************\
 *  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!   smiley-razz
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 14
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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!
« Last Edit: February 02, 2008, 11:43:40 am by ZingBerra » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 14
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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!
Logged

Sacramento, CA, USA
Offline Offline
Newbie
*
Karma: 0
Posts: 30
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.  smiley-grin
Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 81
everywhen
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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);
}
Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 81
everywhen
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

All code in this post copyleft 2008, please reproduce & reuse, and give credit where due.
Example transmit code:
Code:
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:
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)
« Last Edit: February 14, 2008, 06:27:19 pm by yerg2k » Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 81
everywhen
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Brisbane, Australia
Offline Offline
God Member
*****
Karma: 1
Posts: 593
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

New Zealand
Offline Offline
God Member
*****
Karma: 0
Posts: 999
Arduino pebbles
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

--Phil.
Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 81
everywhen
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

All done!  smiley

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  smiley

Code:
#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:
#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).
« Last Edit: March 27, 2008, 06:41:27 pm by yerg2k » Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 81
everywhen
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I also wanted to note that I scrapped the old error checking code which I spent so much time debugging  smiley-razz 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:
   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);
    }
Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 81
everywhen
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

Pages: 1 2 [3] 4   Go Up
Jump to: