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
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"
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
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
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.
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:
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.
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:
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.