Reading 4-Wire BISS-C Encoder

Hello everyone. I have been working sourcing the clock signal for and read a Posital BISS-C encoder. Just in case the link becomes deprecated, here is the part number: KCD-BC33B-1617-E75W-JAQ. The encoder sends 41 bits in each frame. 16 bit multi-turn, 17 bit single-turn, 1 bit error, 1 bit warning, 6 bit crc. I struggled with this encoder for a very long time and wanted to post my solution for anyone else in this situation.

The encoder takes a CLK + and CLK - at frequencies ranging from 80 kHz to 10 MHz. The encoder sends the position data back on DATA + and DATA - based on the clock signal provided. To produce the necessary clock frequency consistently, I utilized a RS422/ RS 485 to TTL converter and manipulated the ports directly. This project would not have been possible without an oscilloscope. Lucky for me, my employer has a DSOX1204G. This was critical to get accurate timing.

As a warning, the code I posted below is not guaranteed to work for your project. The microseconds delays had to be adjusted while looking at my signal on the Oscope. The delays in code were not the same as the delays shown on the Oscope. Furthermore, I choose to use for loops to parse out the data just to keep the timing consistent as possible. A unsigned long long could store the entire frame in one variable but presents other issues when trying to manipulate the value. This is still a work in progress and further posts will come as the project develops.

/*
Use & operator to place 0's setting things LOW
Use | operator to place 1's setting things HIGH
270⁰	 98304	11000000000000000 // Angle Decimal Binary 
360⁰	131071 	11111111111111111 
*/

#define d5h B00001000 // *** D5 is PORT E3 for the Arduino Mega ***
#define d5l B11110111 // Set D5 low
#define d5o B00001000 // Set D5 high
#define writeDelay 6
#define readDelay 3
#define comh B00001000 // Value use when reading pin register
#define a_iter 4 // # of bits for acknowledge
#define m_iter 16 // # of bits for multi-turn data 
#define s_iter 17 // # of bits for single-turn data
#define e_iter 1 // # of bits for error data
#define w_iter 1 // # of bits for warning data
#define c_iter 6 // # of bits for crc data
#define iter 45 // # of bits for BISS-S frame
#define shift 3  // # of bits to shift in bit registers

unsigned long ac; // acknowledge data
unsigned long mt; // multi-turn data
unsigned long st; // single-turn data
unsigned long er; // error data
unsigned long wn; // warning data
unsigned long crc; // crc data
float raw = 0.0; // dummy variable for conversion
float ratio; // dummy variable for conversion
float ang; // calculated angle
float full = 131071.0; // max single turn value
float circ = 360.0;
int i = 0;
int m = 0;
int s = 0;
int e = 0;
int w = 0;
int c = 0;

void setup(){
  Serial.begin(115200);
  delay(1500);
  DDRE |= d5o; // Set E3 (Digital Pin 5) to an Output
}

void loop(){
for(i; i < a_iter; i++){
    PORTE &= d5l; // TX+ lo   TX- high 
    delayMicroseconds(writeDelay); // use a delay to make sure the frequency is consistent
    PORTE |= d5h; // TX+ high TX- low 
    ac |= (PINH >> shift & comh >> shift); // read the pin register. Only update if the pin is high
    ac <<= 1; // shift the value left one bit for next reading
    delayMicroseconds(readDelay);
  }  
  for(m; m < m_iter; m++){
    PORTE &= d5l; // TX+ lo   TX- high 
    delayMicroseconds(writeDelay);
    PORTE |= d5h; // TX+ high TX- low
    mt |= (PINH >> shift & comh >> shift);
    mt <<= 1;
    delayMicroseconds(readDelay);
  }
  for(s; s < s_iter; s++){
    PORTE &= d5l; // TX+ lo   TX- high 
    delayMicroseconds(writeDelay);
    PORTE |= d5h; // TX+ high TX- low
    st |= (PINH >> shift & comh >> shift);
    st <<= 1;
    delayMicroseconds(readDelay);
  }
  for(e; e < e_iter; e++){
    PORTE &= d5l; // TX+ lo   TX- high 
    delayMicroseconds(writeDelay);
    PORTE |= d5h; // TX+ high TX- low
    er |= (PINH >> shift & comh >> shift);
    er <<= 1;
    delayMicroseconds(readDelay);
  }
  for(w; w < w_iter; w++){
    PORTE &= d5l; // TX+ lo   TX- high 
    delayMicroseconds(writeDelay);
    PORTE |= d5h; // TX+ high TX- low
    wn |= (PINH >> shift & comh >> shift);
    wn <<= 1;
    delayMicroseconds(readDelay);
  }
  for(c; c < c_iter; c++){
    PORTE &= d5l; // TX+ lo   TX- high 
    delayMicroseconds(writeDelay);
    PORTE |= d5h; // TX+ high TX- low
    crc |= (PINH >> shift & comh >> shift);
    crc <<= 1;
    delayMicroseconds(readDelay);
  }
  delay(8000); // Delay so that values can be read and adjustments can be made before next iteration
  i = 0; // reset values for next iteration
  m = 0;
  s = 0;
  e = 0;
  w = 0;
  c = 0;
  ac >>= 1; // shift the value right to get rid of extra bit
  mt >>= 1;
  st >>= 1;
  er >>= 1;
  wn >>= 1;
  crc >>= 1;
  raw = st; // store the single turn value as a float for floating point math
  ratio = raw / full; // ratio of single turn angle
  ang = (circ * ratio); // angle
  Serial.print("ac: ");
  Serial.println(ac, BIN);
  Serial.print("mt: ");
  Serial.println(mt, BIN);
  Serial.print("st: ");
  Serial.println(st, BIN);
  Serial.print("er: ");
  Serial.println(er, BIN);
  Serial.print("wn: ");
  Serial.println(wn, BIN);
  Serial.print("crc: ");
  Serial.println(crc, BIN);
  Serial.print("ratio: "); // print out conversions for validation
  Serial.println(ratio);
  Serial.print("ang: ");
  Serial.println(ang);
  ac = 0; // reset values for next iteration
  mt = 0;
  st = 0;
  er = 0;
  wn = 0;
  crc = 0;
}

There is something wrong with a solution that requires special components and finely tuned code to achieve "critical timing" for an otherwise run-of-the-mill encoder.

In the unlikely event that such attention to detail is indeed a requirement for successful use of the BISS-C protocol, then BISS-C deserves to be ignored and the devices that use it will eventually disappear.

Good riddance!

I totally agree that this was more work than it was worth. I am sure more experienced programmers could have solved this problem easier. I tried multiple tutorials but they could not achieve the necessary speeds. This is just for the person who made a decision and now does not have enough time to search for new products and then get them shipped.

Good ( apreciated ), what is the speed achievable with this solution?

We are sending differential clock at 100 kHz. It can be faster but due to the looping and reading I had to slow it down. This can also be done without the TTL converter but you either have to use a "PORTX =" instead of "|=" and "&=". I want to avoid changing the entire port because I have other peripherals on the Arduino for this project.

I tried the PMOD RS422, RS422/RS485 MOD, and the MAX485 RS485. I could not get it to work with any of the other options. I did not seek out the Oscope until the last converter. I will go back to verify if they work later but I just don't have the time at the moment.

Ok, what is the price of this encoder, roughly ( I looked on aliexpress and found also 20,22,24 bit absolute encoders biss-c more than 300$, know nothing about quality/reliability, didn't even know that such object could exist )

Around $200 for the encoder. There are easier options out there, though.