Interfacing an Inductive Encoder (SPI)

Hello guys, I am hoping you guys could help me out.

I am trying to interface a 17-bit resolution inductive encoder but have difficulty getting good results.
I have looked at previous examples and have managed to get a very rough reading using this thread as a guideline.

Looking at the datasheet i know that the setup is correct.

I believe that my issue resides in not reading the correct bits or data.
According to the data sheet the binary position resides on D29-D8.

Encoder Model: INC-11-58.120-171001-SPI1-RC2-5-S

Datasheet (4.6 Serial Peripheral Interface Protocol Definition):

Arduino Code:

#include <SPI.h>

#define CS 10 //Chip or Slave select 

uint16_t ABSposition = 0;
uint16_t ABSposition_last = 0;
uint8_t temp[2];

float deg = 0.00;

void setup()
{
  pinMode(CS,OUTPUT);//Slave Select
  digitalWrite(CS,HIGH);
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE1);
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  Serial.begin(115200);
  Serial.println("starting");
  Serial.flush();
  delay(2000);
  SPI.end();

}
uint8_t SPI_T (uint8_t msg)    //Repetive SPI transmit sequence
{
   uint8_t msg_temp = 0;  //vairable to hold recieved data
   digitalWrite(CS,LOW);     //select spi device
   msg_temp = SPI.transfer(msg);    //send and recieve
   digitalWrite(CS,HIGH);    //deselect spi device
   return(msg_temp);      //return recieved byte
}

void loop()
{ 
   uint8_t recieved = 0x00;    //just a temp vairable
   ABSposition = 0;    //reset position vairable
   SPI.begin();    //start transmition
   digitalWrite(CS,LOW);
   SPI_T(0x00);   
   recieved = SPI_T(0x00);    //issue NOP to check if encoder is ready to send

   temp[0] = SPI_T(0);    //Recieve MSB
   temp[1] = SPI_T(0);    // recieve LSB
   digitalWrite(CS,HIGH); 
   SPI.end();    //end transmition
   ABSposition = temp[0] << 9;    //shift MSB to correct ABSposition in ABSposition message
   ABSposition += temp[1] >> 0;    // add LSB to ABSposition message to complete message
    
   if (ABSposition != ABSposition_last)    //if nothing has changed dont wast time sending position
   {
     ABSposition_last = ABSposition;    //set last position to current position
     deg = ABSposition;
     deg = deg * 360/65536; 
     Serial.print(ABSposition); //raw value
     Serial.print("\t");
     Serial.println(deg,4);     //send position in degrees
   }   

   delay(10); 

}

Serial Monitor Output:

15980	87.7808
16000	87.8906
15980	87.7808
16000	87.8906
15980	87.7808
16000	87.8906
15980	87.7808
16000	87.8906
24	0.1318
15980	87.7808
16000	87.8906
15980	87.7808
16000	87.8906
12319	67.6703
15980	87.7808
16000	87.8906

1.) Each message comes in a series of 6 bytes. This means you will need to call SPI_T(0); six times per sample

2.) You should be using all 6 of these bytes - not just the positional data bytes. The extra bytes give you status updates on the health of the encoder AND whether or not your received message is corrupt

3.) The positional data is in bits D29-D8 - the code you posted assumes the positional data is in bits D15:D0

4.) There is no difference between SPI_T(0); and SPI_T(0x00);

5.) Storing the pieces of data in an array as opposed to individual variables only makes sense if you are going to iterate through the array later in code

Hi, thanks for the feedback!

I have updated the code to account for the 6 bytes, the problem I'm now facing is outputting the data.
I've looked at various examples using smaller bit counts but I am not sure how to handle 6 bytes.
I used uint64_t but I ran into issues.

I am getting mostly stable data with some random readings.

I was also expecting to see higher resolution.

#include <SPI.h>
#define CS 10

uint8_t readbuf[6];
  
void setup() 
{ 
  Serial.begin(115200);
  //initialise spi communication
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  SPI.setDataMode(SPI_MODE1);
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
}


void loop(){
  digitalWrite(CS, LOW);  
  uint8_t val = 0xFF;
  for (int k=0; k <= 5; k++){
  SPI.transfer(val);
  } 
  digitalWrite(CS, HIGH);

  digitalWrite(CS,LOW);
  
  val = 0x00;
  for (int i=0; i <= 5; i++){
  readbuf[i]=SPI.transfer(val);
  }
  
  digitalWrite(CS, HIGH);
  
  //Break down readbuf into its integer representation:
  uint32_t encoderPosition = readbuf[0] << 40;
     encoderPosition += readbuf[1] << 32;
     encoderPosition += readbuf[2] << 24;
     encoderPosition += readbuf[1] << 16;
     encoderPosition += readbuf[2] << 8;
     encoderPosition += readbuf[3];
  
  Serial.println(encoderPosition);
  delay(50);        // delay in between reads for stability
}

Serial Monitor:

8191
8191
8191
0
0
0
0
0
8191
8191
8191
8191
4294935039
8191

As per the datasheet.

byte frame0[5]={};
byte frame1[5]={};
byte frame2[5]={};
byte frame3[5]={};

frame0[0] = SPI.Byte0;
frame0[1] = SPI.Byte1;
frame0[2] = SPI.Byte2;
……...
frame1[0] = SPI.Byte0;
……...
frame2[0] = SPI.Byte0;
……...

and so forth and so on.

I'm a little confused. Do I need to sample all 4 frames?

From the datasheet the above seems to be applicable for Case 1, what about Case 2 or 3?

I was under the impression that only one frame needs to be processed since it repeats itself.

sephex:
I'm a little confused. Do I need to sample all 4 frames?

Forget what Idahowalker posted - don't even worry about it.

sephex:
//Break down readbuf into its integer representation:
uint32_t encoderPosition = readbuf[0] << 40;
encoderPosition += readbuf[1] << 32;
encoderPosition += readbuf[2] << 24;
encoderPosition += readbuf[1] << 16;
encoderPosition += readbuf[2] << 8;
encoderPosition += readbuf[3];

No, no, no. You are still treating the positional data as being in the first bytes in the message when this is NOT the case. You need to become VERY familiar with the section labeled: Data Definition for IncOder SPI Protocol. Once you understand that section, you will know how to program the logic into your code.

Let me break it down for you a little more clearly:

  • First byte read --> 47 46 45 44 43 42 41 40 --- Bits 47 through 40 are all 0, always.
  • Second byte read --> 39 38 37 36 35 34 33 32 --- Bits 39 through 33 are all 0, always. Bit 32 is the Zero Point Default flag.
  • Third byte read --> 31 30 29 28 27 26 25 24 --- Bit 31 is the Position Valid flag. Bit 30 is the Position Synchronised flag. Bits 29 through 24 are the 6 most significant bits in the positional data.
  • Fourth byte read --> 23 22 21 20 19 18 17 16 --- Bits 23 through 16 are the next 8 most significant bits in the positional data.
  • Fifth byte read --> 15 14 13 12 11 10 9 8 --- Bits 15 through 8 are the 8 least significant bytes in the positional data
  • Sixth byte read --> 7 6 5 4 3 2 1 0 --- Bit 7 is the Stale Data flag. Bits 6 through 0 make up the Cyclic Redundancy Checksum (CRC)

Does this make sense?

Hi thank you for the clarification, it really helped!

I managed to get some pretty stable readings after analazying your post.

I am not sure if my code could be improved but please let me know.

Thanks again!

#include <SPI.h>
#define CS 10
uint8_t readbuf[6];

void setup() 
{
  Serial.begin(115200);
  //initialise spi communication
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV32);
  SPI.setDataMode(SPI_MODE1);
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
}

void loop(){
  
  digitalWrite(CS,LOW); 
  uint8_t val = 0x00;
  for (int j=0; j<=5; j++){
  readbuf[j]=SPI.transfer(val);
  }
  digitalWrite(CS, HIGH);
 
  //Break down readbuf into its integer representation:
    uint64_t encoderPosition = 0;
    for (int k=0;k<5;k++){
      encoderPosition |=(uint64_t)readbuf[k]<< 8*(5-k);
    }
    
    float angle = ((long)encoderPosition/134217728.0);
    Serial.print((long)encoderPosition);
    Serial.print("\t");
    Serial.println(360*angle,3);
 
  delay(15);      // delay in between reads for stability
}

Serial Monitor:

36096	0.097
34816	0.093
34816	0.093
34048	0.091
34048	0.091
34048	0.091
32768	0.088
29952	0.080
21760	0.058
10240	0.027
2048	0.005
134214912	359.992
134213632	359.989
134212864	359.987
134212864	359.987
134212864	359.987
134212864	359.987
134212864	359.987
134211584	359.984
134211584	359.984
134210816	359.981
134210816	359.981
134210816	359.981
134210816	359.981
134210816	359.981
134210816	359.981
134210816	359.981
134210816	359.981
134210816	359.981

sephex:

  //Break down readbuf into its integer representation:

uint64_t encoderPosition = 0;
   for (int k=0;k<5;k++){
     encoderPosition |=(uint64_t)readbuf[k]<< 8*(5-k);
   }
   
   float angle = ((long)encoderPosition/134217728.0);
   Serial.print((long)encoderPosition);
   Serial.print("\t");
   Serial.println(360*angle,3);

I'm having a really hard time making sense of the logic here and highly doubt this solution is correct. I still thing you are misunderstanding what bits are and are not part of the positional data.

In order to more clearly explain the packet protocol, here is a more visual friendly breakdown of how the packet looks bit by bit:

encoder_protocol.PNG

encoder_protocol.PNG