SSI grey code reader

Hi again,
First of all I spotted a silly mistake in the original program - corrected now.

This bit of code was part of a much larger program I wrote, that needed all the I/O that the mega provides. The result is that pin numbers and port names are quite different to the ones on the Uno. To make things easy for anyone (and me too in the future!) I've re-written it for the Uno and the code is below. I've tested it as far as possible without an actual encoder to put on it!

/*

gray code reader for Hengsler SSI 24bit multi-turn absolute encoder

I/O should be via RS224 chip

Pins chosen for Arduino Uno

Target reading bit speed 120kHz

Min reading speed for Henglser 90kHz

*/

#define SSI_CLOCK 8
#define SSI_DATA 14
#define ENCODER_LENGTH 24
#define ENCODER_SPEED 4

byte encoder[ENCODER_LENGTH + 1];
unsigned long value;


void setup(){
  pinMode(SSI_CLOCK, OUTPUT);       //  port B - 0 digital pin 8
  pinMode(SSI_DATA, INPUT);         //  port C - 0 analog pin 0
  PORTB = 1;                        //  set SSI clock idle
}


byte transfer(unsigned long &number){
  int i;
  int flag;

  noInterrupts();  
  delayMicroseconds(50);                               //  ensure everything settled
  flag = !((PINC << 1) & 2);
  if (flag){                                           //  test for start condition
    interrupts();
  }
  else{
    for(i = 0; i <= ENCODER_LENGTH; i++){              //  overcount to read status end bit
      PORTB = 0;                                       //  set the whole port for speed
      delayMicroseconds(ENCODER_SPEED);

      PORTB = 1;                                       //  output from pin 8
      delayMicroseconds(ENCODER_SPEED);
      encoder[i] = PINC & 1;                           //  input to analog pin 0
    }
    interrupts();
    delayMicroseconds(15);                             // delay to ensure no resend
    flag = flag | (encoder[ENCODER_LENGTH] & 1);       // test for status end bit (zero)

    for(i = 1; i < ENCODER_LENGTH; i++){
      encoder[i] = encoder[i] ^ encoder[i - 1];        // convert to binary (remove these 3 lines if already binary)
    }

    number = 0;
    for (i = 0; i <ENCODER_LENGTH; i ++){
      number = (number<<1) | (encoder[i]);             // convert to unsigned long int
    }
  }
  return flag;
}


void loop(){
  bool error;

  // {code}
  error = transfer(value);
  if (error){
    if (error & 2){
      // {start fault}
    }
    else{
      // {stop fault}
    }
  }
  else{
    // {code}
  }
  // {code}
}

There are a few simplifications as there is no need to avoid certain I/O that I was using in the original project. This also makes the flag operation slightly different. but the principle remains the same.

The line:

flag = !((PINC << 1) & 2);

reads PINC (which is a very fast way to grab a block of inputs)
shifts the result one bit to the left so that bit zero (analog pin 0, now acting as digital pin 14) represents the value 2
this is then masked with 2 (the& sign) so all other pins will be ignored.
The '!' then inverts this, so that you get a '2' if the input line is not high.

The line:

flag = flag | (encoder[ENCODER_LENGTH] & 1);

looks at the last value that was put into the encoder buffer
the '& 1' is actually not necessary here as the value should only be 0 or 1, but I kept it just in case some stray values had got into the buffer. In this case we want to see if the value is 1 (input was high, which it shouldn't be)

The '|' symbol puts the two values together so that if everything is OK you end up with a value of 0. This has proved that the encoder has gone from high to low at least once so is presumably working correctly.