Encoder Implementation


Introduction

This page outlines how to use multiple simple single channel encoder with an Arduino.

Basic principles

Although the kit itself does not come with any speed sensors, optical encoders can be added to the chassis. The optical encoders can measure rotation, and by scaling the rotation by the wheel radius, a change in position can also be measured.

In this project I used a simple single channel optical encoder placed on two of the wheels. A rather significant limitation is that the encoders cannot differentiate from two different directions of rotation, however for this project we will just assume that the motion is in the same direction as the applied torque. This is true if the robot comes to rest before applying torque to change directions.

Encoder operation

Optical encoders work by emitting a light that is to be sensed across an encoder wheel. The wheel has slots in it to let the light pass through in regular angular intervals as shown in the image below.

The output is either 0 or 1 depending on whether the light is received by the sensor or not. To get the maximum resolution possible, we detect the rising edge and falling edge of the encoder signal, each corresponding to an angular increment.

Encoder resolution calculation

In my case, the wheel only has 20 slots. Since they are evenly spaced we can write the equation below:

$$ \Delta \theta = \frac{2\pi}{2\cdot20} $$

The extra factor of 2 in the denominator comes from the fact that we detect the rising and falling edges of the encoder signal.

Example code

The first problem that we must overcome is how to actually count the state transitions. The first way I will introduced has significant limitations since the Arduino is a single threaded system. The second way is a remedy to this limitation.

For the first method we can wait in a while loop until the state has changed by repeatedly reading the digital input to the Arduino.

int state = 0;
int prev_state = 0;
int encoder_Pin = A0;
unsigned long encoder_Count = 0;

void setup()
{
    pinMode(encoder_Pin,INPUT);
    state = digitalRead(encoder_Pin);
    prev_state = state;
}

void loop()
{
    while( prev_state == state )
    {
        state = digitalRead(encoder_Pin);
    }
    // Since the while loop is exited, the state has changed
    encoder_Count++;
    prev_state = state;
    
}

Although this code will work, the while loop is a "busy wait" on the changing state. That is, it will keep reading and do nothing else which inefficiently uses the processor. Furthermore, if we want to count two encoder state changes we cannot add another while loop. If this was done, a predefined order in which the encoders must change state would have to be followed for counts to be detected. If one encoder never changes state, then the Arduino would not be able to count the other encoder signal, since it is locked in a while loop.

The solution to this is to use if statements where each of the two encoders has their own state variables to detect if any changes occur. One implementation of this is shown below.

unsigned long e1_count = 0;
int e1_pin = A7;
int e1_state = 0;

unsigned long e2_count = 0;
int e2_pin = A6;
int e2_state = 0;

int e1_prev_state = 0;
int e2_prev_state = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  
  pinMode(e1_pin,INPUT); // encoder attached to this pin
  pinMode(e2_pin,INPUT);
  
  // put your main code here, to run repeatedly:
  e1_state = digitalRead(e1_pin);
  e2_state = digitalRead(e2_pin);
  
  e1_prev_state = e1_state;
  e2_prev_state = e2_state;
}

void loop() {
  // use ifs to count multiple encoder states
  e1_state = digitalRead(e1_pin);
  e2_state = digitalRead(e2_pin);

  // this implicitly assumes that the operations inside the if statements don't take that long
  // since all that is happening is some addition and copying, it should be okay
  
  if( e1_state != e1_prev_state )
  {
    e1_count = e1_count+1;    // in this case the e1 state has changed increment the counter
    e1_prev_state = e1_state; // set the previous state equal to the current one
    Serial.print(e1_count);
    Serial.print(',');
    Serial.println(e2_count);
  }
  if( e2_state != e2_prev_state )
  {
    e2_count = e2_count+1;
    e2_prev_state = e2_state;
    Serial.print(e1_count);
    Serial.print(',');
    Serial.println(e2_count);
  }
}