Looking for help implementing SSI protocol with absolute encoder

This is my first foray into using Arduino beyond the simple LED example, so I'm very inexperienced with all of this. I am using an Arduino UNO.

Sorry for the novel, just want to provide relevant information and explain what I've tried already.

Encoder data sheet (MB029 readhead, 17 bit resolution w/ multiturn counter).

Also using this RS422/TTL conversion module.

With everything connected, I'm just trying to get reasonable data back from the encoder. I am reading the data bits while the clock is LOW (code pasted at end of post), and I'm just getting "10101010..." for the total 43 bits I read. My code reads the different bits into different variables based on what number they are (eg. first bit comes in as the first bit for the multiturn counter, 17th bit comes in as first bit for encoder position, etc.). The encoder has an LED onboard to help debug problems but it stays solid green, so it thinks everything is just dandy.

My guess is the problem is either with the code, the overall configuration, or the Arduino boards I've used (swapped out another board, still didn't work). I'll start with explaining my configuration.

My setup is wired as follows:

Clock signal:
Arduino pin 4 -> conversion module (CM) Rxd pin
CM Y pin (or T+) -> encoder clock +
CM Z pin (or T-) -> encoder clock -

Data signal:
Arduino pin 2 -> CM Txd pin
CM A pin (or R+) -> encoder data +
CM B pin (or R-) -> encoder data -

I have checked with a multimeter that both the encoder and the CM are getting 5V. All grounds are on the same ground bus connected to one of Arduino's ground pins. I doubt my problem is here, but if there's something wrong I'd appreciate a heads up.

For swapping out boards, I have the UNO I'm using as well as an Arduino 101 on hand that I could try out. Neither produced sane data, but I have oscilloscope probes attached to CM Rxd and Txd pins to monitor the signals going in and out. When I was doing a simple clock frequency check with the following code

const int c = 4;
const int del = 4;

void setup() {

  pinMode(c, OUTPUT);
  pinMode(2, INPUT);

  Serial.begin(250000);
  digitalWrite(c, HIGH);

}

void loop() {

  digitalWrite(c, LOW);
  delayMicroseconds(del);
  digitalWrite(c, HIGH);
  delayMicroseconds(del);
  
}

I found that the generated signal from my bit-banging never agreed with what I coded in. For example, the above code has a clock period of 8 microseconds (4 high, 4 low), but the oscilloscope shows a period of 12.4 microseconds (6.2 high, 6.2 low). It's also showing faint flickering reciprocal signals on the same line. Inexperience makes me unsure of how (ab)normal that is.

Can the timing discrepancy cause issues? I didn't think it would as long as it's consistent.

And lastly, my code.

First, is there some way to store all 43 bits in one variable? All I could find is using unsigned long, but that has a max of 32. I'm worried that my current method of parsing out the bits to multiturn, encoder, general status and detailed status is messing with the overall performance. I don't know how else to get and store all the bits though.

Second, when I open serial monitor I see that sometimes the encoder variable has one more bit than it should and both the general and detailed status variables have one less bit than they should. Does anyone know/see why this is happening?

Third, when I decrease the clock period/increase clock frequency, all the bits read as 1. I have no idea what that could indicate, but it seems significant.

To finally end, is there anything wrong with my code in general? Is there a simpler or better way of doing this?

Thank you very much for any help you can offer.

const int CLOCK = 4;
const int DATA = 2;
const int BIT_COUNT = 42 + 1; //CAREFUL CHANGING THIS, MAY CAUSE PROBLEMS WITH BIT ORGANIZATION

void setup() {
  pinMode(CLOCK, OUTPUT);
  pinMode(DATA, INPUT);

  digitalWrite(CLOCK, HIGH); //Initialize clock signal as HIGH

  delay(200); //Data sheet instructs to leave time for the power supply to apply before sending clock sequence

  Serial.begin(250000);
}

// Clock cycles to retrieve encoder data
void loop() {
  
  digitalWrite(CLOCK, LOW); //Start with "Delay First Clock" cycle
  delayMicroseconds(5);
  
  unsigned long multi = 0;
  unsigned long enc = 0;
  unsigned long G_status = 0;
  unsigned long D_status = 0;

  //Create clock pulse to start data transmission
  for (int i=0; i<BIT_COUNT; i++) {
    
    digitalWrite(CLOCK, HIGH);
    delayMicroseconds(4);
    digitalWrite(CLOCK, LOW);

    //Write data into seperate variables for BIT ORGANIZATION
    if (i < 16) {
      multi <<= 1;
      multi |= digitalRead(DATA);
      delayMicroseconds(4);
    }

    else if(i > 15 && i < 33) {
      enc <<= 1;
      enc |= digitalRead(DATA);
      delayMicroseconds(4);
    }

    else if(i > 32 && i < 35) {
      G_status <<= 1;
      G_status |= digitalRead(DATA);
      delayMicroseconds(4);
    }

    else {
      D_status <<= 1;
      D_status |= digitalRead(DATA);
      delayMicroseconds(4);
    }

  }

  //Begin printing status and encoder position
  Serial.println();
  Serial.println("Multiturn bits: ");
  Serial.println(multi, BIN);
  Serial.println("Encoder bits: ");
  Serial.println(enc, BIN);
  Serial.println("General status bits: ");
  Serial.println(G_status, BIN);
  Serial.println("Detailed status bits: ");
  Serial.println(D_status, BIN);
  Serial.println();
  

  //Start monoflop time to reset transmission cycle
  digitalWrite(4, HIGH);
  delayMicroseconds(20);

  //Optional delay before next reading
  delay(5);
  //delayMicroseconds(1000);

}

Okay I got it figured out. I'll post what worked for me here for posterity.

TL;DR - I hooked a scope probe onto my clock line and adjusted my code until the clock was doing it's job well.

The key was the oscilloscope. For other electronics noobs that have access to expensive tools like me, the "faint flickering" lines are part of the whole signal the scope is picking up. If you can pause the scope and look at a section of a signal, you can see more clearly what is going on.

So digitalWrite() and digitalRead() obviously take time to execute. In a lot of cases that execution time is negligible. With very time sensitive SSI communication, however, it is just too slow. After hooking a probe onto my clock signal and pausing, it was all over the place and very uneven.

The solution is to use direct port manipulation (see this article to learn more about it/how to implement it). This let me ditch the RS422/TTL converter entirely and generate a differential clock signal that the encoder liked. The key is to oscillate your clock+ and clock- lines at the same time, which port manipulation allows and digitalWrite() does not.

My updated and working code will be pasted at the bottom again. Do not copy/paste it and expect it to work with your encoder. It almost definitely won't (unless you have the same encoder, even then it probably won't work). The code is specifically timed with my encoder/Arduino board to generate a good clock signal. Even with port manipulation, the delayMicroseconds(n) were not entirely accurate even if they were consistent (produced approx. n-1 microsecond delays, weird). I also got away with using digitalRead() to read in the bits. Their inherent delays worked well with what I had coded in already. Again, don't try to use this code, just reference it.

const int del = 5;

const byte CLOCK_PLUS  = B00000100; //Clock+ -> pin 2
const byte CLOCK_MINUS = B00010000; //Clock- -> pin 4
const byte DATA_PLUS   = B00010000; //Data+  -> pin 12
const byte DATA_MINUS  = B00100000; //Data-  -> pin 13

void setup() {

  DDRD = DDRD | B11111100;
  DDRB = DDRB | B11001111;

  Serial.begin(250000);
  delay(200); //Minimum delay after powering encoder

  //Initialize clock+ high
  PORTD = CLOCK_PLUS;

}

void loop() {
  
  unsigned long enc_data = takeReading();
  double result = enc_data;
  double angle_result = result*360/65536; //360/(2^(bit_resolution-1))
  if(angle_result>360){
    angle_result = angle_result - 360;
  }
  Serial.println(angle_result);

  delayMicroseconds(20); //Required monoflop time
  delay(500); //Adjust as desired
  
  PORTD = CLOCK_PLUS;
}



unsigned long takeReading() {

  unsigned long multi = 0;
  unsigned long data = 0;
  unsigned long gs = 0;
  unsigned long ds = 0;

  PORTD = CLOCK_MINUS; //Set CLOCK_PLUS LOW, CLOCK_MINUS HIGH
  delayMicroseconds(del);

  //Multiturn bits, NOT STORING/TRANSMITTING YET
  for(int i=1; i<16; i++){
    multi <<= 0;
    PORTD = CLOCK_PLUS; //Set CLOCK_PLUS HIGH, CLOCK_MINUS LOW
    delayMicroseconds(del);
    PORTD = CLOCK_MINUS; //CLOCK_PLUS LOW, CLOCK_MINUS HIGH

    //Store multiturn bits
    multi |= digitalRead(12);
    
    //delayMicroseconds(del);
  }

  //Encoder position bits
  for(int i=0; i<17; i++){
    data <<= 1;
    PORTD = CLOCK_PLUS; //Set CLOCK_PLUS HIGH, CLOCK_MINUS LOW
    delayMicroseconds(del);
    PORTD = CLOCK_MINUS; //CLOCK_PLUS LOW, CLOCK_MINUS HIGH
    
    //Store encoder position bits
    data |= digitalRead(12);
    
    //delayMicroseconds(del);
  }

  //General status bits
  for(int i=0; i<2; i++){
    gs <<= 1;
    PORTD = CLOCK_PLUS; //Set CLOCK_PLUS HIGH, CLOCK_MINUS LOW
    delayMicroseconds(del);
    PORTD = CLOCK_MINUS; //CLOCK_PLUS LOW, CLOCK_MINUS HIGH
    
    //Store general status bits
    gs |= digitalRead(12);
    
    //delayMicroseconds(del);
  }

  //Detailed status bits
  for(int i=0; i<8; i++){
    ds <<= 1;
    PORTD = CLOCK_PLUS; //Set CLOCK_PLUS HIGH, CLOCK_MINUS LOW
    delayMicroseconds(del);
    PORTD = CLOCK_MINUS; //CLOCK_PLUS LOW, CLOCK_MINUS HIGH
    
    //Store detailed status bits
    ds |= digitalRead(12);
    
    //delayMicroseconds(del);
  }

  PORTD = CLOCK_PLUS; //Set CLOCK_PLUS HIGH, CLOCK_MINUS LOW

  //Use this line if you want to ignore status bits
  return data;

  //Alert for when a status bit is set
  if(gs>0 || ds>0){
    Serial.println("Status error, see error bits.");
    Serial.print("General: ");
    Serial.println(gs, BIN);
    Serial.print("Detailed: ");
    Serial.println(ds, BIN);

    return data;
  }

  else {
    return data;
  }
}
1 Like