Reading an absolute encoder (4-Bit, Gray code, 16 position) [SOLVED]

Hello! I am brand new to working with Arduino and I have no programming experience, so I am at a loss as to how to read the position of a Grayhill 4-bit, 16 position absolute encoder. Here is the datasheet for that particular encoder (my model is the 25LB22-G). It outputs in Gray code, supposedly. Right now, I just want to be able to serial print its absolute position in a way that’s easy to read (e.g. “position 1” or “position 12”).

I’m running this on an Arduino Uno and have gotten to the point where I can read the output of each pin as a 1 or a 0 and have it print on a new line. I am not using external pull up resistors because I do not understand what that means but I believe pull up resistors are necessary(?) so I am using the internal ones :-[
Here is my code so far:

const int dialPin1 = 6; // Labeled "1" on encoder
const int dialPin2 = 7; // Labeled "2" on encoder
const int dialPin3 = 8; // Labeled "4" on encoder
const int dialPin4 = 9; // Labeled "8" on encoder

int dialVal1 = 0; 
int dialVal2 = 0; 
int dialVal3 = 0; 
int dialVal4 = 0;

int dialAbs = 0; // Absolute position of dial

void setup() {
  delay( 3000 );
  pinMode( dialPin1, INPUT_PULLUP ); 
  pinMode( dialPin2, INPUT_PULLUP );
  pinMode( dialPin3, INPUT_PULLUP );
  pinMode( dialPin4, INPUT_PULLUP );
  Serial.begin( 9600 );
}

void loop()
{
    dialPos();
    Serial.println( dialVal1 );
    Serial.println( dialVal2 );
    Serial.println( dialVal3 );
    Serial.println( dialVal4 );
    Serial.println( dialAbs );
    Serial.println("------"); // For visibility
    delay( 2000 );
}

void dialPos() { // determine position based on output of each pin
  dialVal1 = digitalRead( dialPin1 );
  dialVal2 = digitalRead( dialPin2 );
  dialVal3 = digitalRead( dialPin3 );
  dialVal4 = digitalRead( dialPin4 );

  dialAbs = dialPin1 || ( dialPin2<<1 ) || ( dialPin3<<2 ) || ( dialPin4<<3 );
  
}

Like I said, zero programming experience :slight_smile:
I tried to get the Gray code to print out all on one line from each pin using bit shifting but to no avail; it only will output the number “1”

Any help is appreciated!

Good job with using code tags on your first post.

I tried to get the Gray code to print out all on one line from each pin using bit shifting but to no avail; it only will output the number “1”

dialAbs = dialPin1 || ( dialPin2<<1 ) || ( dialPin3<<2 ) || ( dialPin4<<3 );

You are but shifting the pin numbers, not the digitalRead() values read on the pins.

D’oh, of course! So I changed all dialPin variables to dialVal but it was still displaying just the number “1” on my Serial read. Just messing around, I changed the double pipe (||) to a single pipe (|) so now that last line reads

dialAbs = dialVal1 | (dialVal2 << 1) | (dialVal3 << 2) | (dialVal4 << 3);

Now dialAbs shows an integer that is between 0 and 15 (which is the right number of positions for this particular encoder) but the positions seem wrong. I’ll turn clockwise one detent from what dialAbs reads as position 4 and it will jump to 10 for some reason.

I’ve tried converting my Gray code to an integer using this function

int gray2int (byte gray)
{
  return gray ^ (gray >> 1) ;
}

and feeding dialAbs into the function, but dialAbs still displays the wrong (though a different kind of wrong) position.

That is not the correct way to change Gray code to binary. Here are some better ideas, with code examples.

In particular, try the following, but make sure that the bits you read from the encoder match what you see on the data sheet, as demonstrated by the test program below.

/*
 * This function converts an unsigned binary
 * number to reflected binary Gray code.
 *
 * The operator >> is shift right. The operator ^ is exclusive or.
 */
unsigned int BinaryToGray(unsigned int num)
{
    return num ^ (num >> 1);
}

/*
 * This function converts a reflected binary
 * Gray code number to a binary number.
 * Each Gray code bit is exclusive-ored with all
 * more significant bits.
 */
unsigned int GrayToBinary(unsigned int num)
{
    unsigned int mask = num >> 1;
    while (mask != 0)
    {
        num = num ^ mask;
        mask = mask >> 1;
    }
    return num;
}
void setup()
{
unsigned int i,j;
Serial.begin(9600);
for (i=0; i<16; i++) { 
  Serial.print( (j=BinaryToGray(i)),BIN); //print j, the simulated encoder reading
  Serial.print(" -> ");
  Serial.println(GrayToBinary(j),BIN);  //invert encoder reading, should match binary input.
}
}
void loop() {}

double pipe (||) to a single pipe (|)

Called “logical OR” and “bitwise OR” respectively. You must use the bitwise OR.

This should do it.

  dialVal1 = digitalRead( dialPin1 );  // read encoder pins
  dialVal2 = digitalRead( dialPin2 );
  dialVal3 = digitalRead( dialPin3 );
  dialVal4 = digitalRead( dialPin4 );

  dialVal3 ^= dialVal4 ;  // convert Gray code to binary
  dialVal2 ^= dialVal3 ;
  dialVal1 ^= dialVal2 ;

  dialAbs = dialPin1 || ( dialPin2<<1 ) || ( dialPin3<<2 ) || ( dialPin4<<3 );

jdfranklin2: I've tried converting my Gray code to an integer using this function

int gray2int (byte gray)
{
  return gray ^ (gray >> 1) ;
}

That's converting integer to Gray code... The inverse mapping is a chain of XORs as I posted.

Technically there are many Gray codings, the common one, as used here, is called the reflected Gray code.

With only sixteen possibilities it would be easy to work out each gray-binary translation and stick them in an array. At run time plug the gray value into an array subscript and read out the converted value.

Sorry for the late reply on this, COVID-19 has messed with my work schedule and has pushed this to a back burner.

After looking at the serial feed, I noticed that the bits each pin was outputting was not in line with what the datasheet says they’re supposed to be. Obviously, that has to be right before I try converting the Gray code to binary or an integer. The pins are labelled “C”, “1”, “2”, “4”, and “8” on the encoder. Here is where those pins are on the Arduino Uno:
C - GND
1 - Digital 8
2 - Digital 9
4 - Digital 10
8 - Digital 11

This is what the datasheet says the pins should read at each position:

Position Pin 1 Pin 2 Pin 4 Pin 8
0 0 0 0 0
1 1 0 0 0
2 1 1 0 0
3 0 1 0 0
4 0 1 1 0
5 1 1 1 0
6 1 0 1 0
7 0 0 1 0
8 0 0 1 1
9 1 0 1 1
10 1 1 1 1
11 0 1 1 1
12 0 1 0 1
13 1 1 0 1
14 1 0 0 1
15 0 0 0 1

This is what the encoder reads when I move the encoder clockwise (note that I don’t know the actual position of the encoder, I am just assuming position 0 is where the encoder pins read out 0 0 0 0):

Position Pin 1 Pin 2 Pin 4 Pin 8
0 0 0 0 0
1 1 0 0 0
2 0 1 1 0
3 1 1 1 0
4 0 0 1 0
5 1 0 1 0
6 0 1 0 0
7 1 1 0 1
8 0 0 0 1
9 1 0 0 1
10 0 1 1 1
11 1 1 1 1
12 0 0 1 1
13 1 0 1 1
14 0 1 0 0
15 1 1 0 0

Clearly, what is being printed on the serial monitor does not line up with the datasheet. Is this indicative of a hardware or wiring issue, or am I missing something in the code?

Gray codes are used because only 1 bit changes during each state transition, which reduces the possibility of erroneous readings. Your encoder does not seem to be following that rule, so I wonder if you have the correct data sheet.

I am not using external pull up resistors because I do not understand what that means

It means to connect a resistor (often 10K Ohms) between the input and Vcc (5V on an Arduino Uno).

Keep in mind that with pullup resistors (either internal or external), a logic "1" output from digitalRead() indicates the switch is OPEN, not connected to ground. To invert the signal, use "!" as follows:

 dialVal1 = !digitalRead( dialPin1 );  // read encoder pins and invert

lf you can't figure out an alternative wiring (possibly combined with signal inversion) that does produce a Gray series, simply use a 16 byte lookup table (array) to convert the input values to standard binary.

The table entries could look like this (consult your table)

byte conv[16]={0,1,3,6, ...
...
byte binary_val = conv[dialAbs];  //lookup binary value from encoder value

The encoder is now putting out the correct position on the serial monitor so my problem is solved! Here are the two changes courtesy of jremington and MarkT that fixed my issues, followed by my full code.

MarkT:
This should do it.

  dialVal1 = digitalRead( dialPin1 );  // read encoder pins

dialVal2 = digitalRead( dialPin2 );
  dialVal3 = digitalRead( dialPin3 );
  dialVal4 = digitalRead( dialPin4 );

dialVal3 ^= dialVal4 ;  // convert Gray code to binary
  dialVal2 ^= dialVal3 ;
  dialVal1 ^= dialVal2 ;

dialAbs = dialPin1 || ( dialPin2<<1 ) || ( dialPin3<<2 ) || ( dialPin4<<3 );

jremington:
Keep in mind that with pullup resistors (either internal or external), a logic “1” output from digitalRead() indicates the switch is OPEN, not connected to ground. To invert the signal, use “!” as follows:

 dialVal1 = !digitalRead( dialPin1 );  // read encoder pins and invert

Working Arduino code:

#include <stdio.h> // Not sure if this is necessary

const int dialPin1 = 8; // Labeled "1" on encoder
const int dialPin2 = 9; // Labeled "2" on encoder
const int dialPin3 = 10; // Labeled "4" on encoder
const int dialPin4 = 11; // Labeled "8" on encoder
// Declaring all variables
int dialVal1 = 0; 
int dialVal2 = 0; 
int dialVal3 = 0; 
int dialVal4 = 0;
int dialAbs = 0;

void setup() {
  delay( 3000 ); // Let the Arduino wake up :)
  pinMode( dialPin1, INPUT_PULLUP ); 
  pinMode( dialPin2, INPUT_PULLUP );
  pinMode( dialPin3, INPUT_PULLUP );
  pinMode( dialPin4, INPUT_PULLUP );
  Serial.begin( 9600 );
}

void loop()
{
    dialPos();
    Serial.println( dialVal1 );
    Serial.println( dialVal2 );
    Serial.println( dialVal3 );
    Serial.println( dialVal4 );
    Serial.println( dialAbs );
    Serial.println("------"); // For visibility
    delay( 2000 ); // Keep that Serial monitor slow
}

void dialPos() { // determine position based on output of each pin
  dialVal1 = !digitalRead( dialPin1 );
  dialVal2 = !digitalRead( dialPin2 );
  dialVal3 = !digitalRead( dialPin3 );
  dialVal4 = !digitalRead( dialPin4 );

  dialVal3 ^= dialVal4;
  dialVal2 ^= dialVal3;
  dialVal1 ^= dialVal2;
  
  dialAbs = dialVal1 | (dialVal2 << 1) | (dialVal3 << 2) | (dialVal4 << 3); // Bit shifting
}

For those experienced in the Arduino forum, is there anything I should do to indicate that a solution has been found?

You can edit the title of the opening post. Usually folks add [SOLVED] in the title. Thanks for posting a solution, some future questioner can now benefit.

Thanks dougp! The title has been updated. Thanks to all who helped solve this!

dougp: You can edit the title of the opening post. Usually folks add [SOLVED] in the title. Thanks for posting a solution, some future questioner can now benefit.