Wheel Encoders For WallBot

640_1401_IMG_3030

WallBot Version 3

For my WallBot I installed quadrature wheel encoders from Pololu. I have already written about the installation in the WallBot – Version 2.5 post. All the code for WallBot is on GitHub in the WallBot_V3 folder.

If you’ve never heard of a rotary encoder before, it’s simply a device that allows you to measure how much something has turned.  The ones I installed are specifically designed for this chassis/motor/wheel setup. They each have two digital output signals, A and B, that you connect directly to the microcontroller.

To read the encoders I thought I could simply install a library. While Pololu does provide a library for these encoders, it clashed with the SoftwareSerial library I was using to communicate with the bot’s motor controller. Both libraries make use of pin interrupts.

In the end I decided to implement my own reading code from scratch and, in the process, learnt a lot about encoders and using timer interrupts on the Arduino. I thought I might as well document what I learnt here for future reference.

The Analog Side

First it’s important to understand how these encoders work. We use one for each of the wheels.

Wheel Encoder

Wheel Encoder

Each encoder positions two infrared reflectance sensors inside its wheel hub. The wheel hub is specially designed to work with the sensors. It has 12 “teeth” along it’s inner edge that pass close over the sensors as the wheel turns. [You can see the sensors and teeth in the image above. The sensors are the black components on the PCB. The PCB is attached to the chassis along with the motor.]

The IR sensors vary their voltage/resistance depending on how close or far objects are from them. As the wheel turns, and the teeth  pass over the sensors, the gaps in the teeth cause the sensors’ voltage to oscillate from high to low.

Essentially each sensor generates a sine wave like signal:

Screen Shot 2014-01-23 at 4.20.33 PM

Each valley or peak in the wave represents either a tooth or a gap in the wheel hub, depending on how the sensors are configured.

You only need one sensor to measure how much a wheel has turned by simply counting the number of peaks or valleys. [You’d measure one or the other, not both]

But most encoders will use two sensors and position them so their signals are close to 90° out of phase from each other as possible. If you look at the example signals above you will notice that the peaks and valleys of A and B are out of sync.

Doing this allows you to detect the direction the teeth are moving. If the teeth are moving across from A to B then A will get its signal peak just before B does. The converse is true if they are moving in the opposite direction; B will peak before A. By detecting which signal changes first you can deduce the direction.

The Digital Side

But how do we actually read the signals? How do we detect peaks and valleys?

Conveniently these encoders convert the analog sine waves into digital outputs we can read with the microcontroller.

So instead of a sine wave we get a nice square wave:

Screen Shot 2014-01-23 at 4.26.11 PM

We can connect each output to a pin on the Arduino and use digitalRead() to read their values:


  char outputApin = 3;
  char outputBpin = 4;

  pinMode( outputAPin, INPUT);
  pinMode( outputBPin, INPUT);

  char A = digitalRead( outputApin) == HIGH ? 1 : 0;
  char B = digitalRead( outputBpin) == HIGH ? 1 : 0;

So if A equals 1, we have a peak. If A equals 0, we have a valley. Similarly for B.

The tricky part is that we need to continually read A and B to track their changes. It’s the changes in their states that is important too us.

There are two ways to do this.

One way is to use “pin interrupts”. This is a feature of the microcontroller that allows you to have a routine called whenever the state of a pin changes. This is the method that the Pololu library uses.

As I already had a library that was using pin interrupts, for its own purposes, I decided to go with option 2.

Option 2 is to use one of the microcontroller’s timers to generate a regular and frequent interrupt to read the encoder signals. The frequency of the interrupt is important. We don’t want to miss any changes in the signals so we must sample them at a faster rate than the signals themselves can change.

So how fast can the signals change? What’s a reasonable sampling frequency?

The following diagram shows when the state transitions happen:

Screen Shot 2014-01-23 at 4.19.58 PM

There are four transitions per tooth. There are 12 teeth on a hub. This means we have 48 transitions, lets call them “ticks”, every revolution of a wheel.

So if our wheel turns once a second then a “tick” would happen every 1/48th of a second, or about every 20 milliseconds.  My motors can do 320 rpm at max power so probably never going to turn more than 5 revolutions per second, therefore the max rate of ticks will be 240 per second or, more simply, a tick every 4 milliseconds.

So if we were to generate a timer interrupt to sample A and B every millisecond we can easily detect and process any changes that happen and not worry abut missing any ticks.

For my setup, timer 2 was free for use. Setting up timer 2 to generate millisecond interrupts is very simple, though it was hard to find documentation that explained it well. Nevertheless the following code does the trick:


//----------------------------------------
//
//----------------------------------------

void setup_encoder( ... )
{
  :
  cli();
  // set timer2 interrupt at 1000 Hz
  // we are assuming a clk speed of 16MHz
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2  = 0;
  OCR2A = 249;
  TCCR2A |= (1 << WGM21);
  TCCR2B |= (1 << CS22);
  TIMSK2 |= (1 << OCIE2A);
  sei();
  :
}

//----------------------------------------
//
//----------------------------------------

ISR(TIMER2_COMPA_vect)
{
  // this routine gets called once every 1 millisecond
  _counter++;
  :
  :
}

The Code

So what do we have to do to read the encoders?

Put simply, we need to detect each change in state for outputs A and B, and update a running tick count by either incrementing or decrementing it depending on which output changed first.

A positive count means the wheel is moving one direction and a negative count means the wheel is moving in the opposite direction.

Here is the actual code I used to read the encoders:


//----------------------------------------
//
//----------------------------------------

volatile int16_t en_lft_ticks = 0;
volatile int16_t en_rht_ticks = 0;
volatile uint16_t en_counter = 0;
volatile bool en_error = false;

//----------------------------------------
//
//----------------------------------------

ISR(TIMER2_COMPA_vect)
{
  // this routine gets called once every 1 millisecond
  en_counter++;

  static char lastLA = 0;
  static char lastLB = 0;
  static char lastRA = 0;
  static char lastRB = 0;

  _process(en_lApin, en_lBpin, &lastLA, &lastLB, &en_lft_ticks);
  _process(en_rApin, en_rBpin, &lastRA, &lastRB, &en_rht_ticks);
}

//----------------------------------------
//
//----------------------------------------

void _process( char Apin,
               char Bpin,
               char *lastA,
               char *lastB,
      volatile int16_t *ticks )
{
  char A = (digitalRead( Apin) == HIGH) ? 1 : 0;
  char B = (digitalRead( Bpin) == HIGH) ? 1 : 0;
  char lA = *lastA;
  char lB = *lastB;
  char dA = A!=lA;
  char dB = B!=lB;

  if( dA && dB )
  {
    // both should not change at the same time
    en_error = true;
  }
  else if ( dA || dB )
  {
    if (A^lB)
    {
      *ticks += 1;
    }
    else if(B^lA)
    {
      *ticks -= 1;
    }
  }
  *lastA = A;
  *lastB = B;
}

The interrupt routine is calling _process() for each encoder (we have two; one for the left wheel and one for the right). For each call we pass in the pin numbers for outputs A and B, a pointer to a variable holding the last value read for A and another for B, and a pointer to a variable holding the current tick count for that encoder.

_process() reads the current state of the encoder output in to variables A and B. It also loads the last values read for A and B into lA and lB and creates the flags dA and dB.

It first checks that A and B have not both changed.  If they both change at the same time then either there is a problem with the hardware or, more likely, we are not sampling the output quickly enough and are missing transitions.

If there is no error, it tests to see if any of the outputs has changed and increments or decrements the tick counter accordingly.

Lastly it stores the current values for A and B into the ‘last value’ variables.

The test for whether to increment or decrement the ticks is very cool. It perform an exclusive-or between each signal’s current value and the previous  value of the other signal.

Basically A^lB is always true if A changes before B, and always false if it trails B. You can see better what is happening in this diagram where B is changing before A:

Screen Shot 2014-01-23 at 8.56.12 PM

Very elegant.

Finally we provide a routine that the rest of the bot code can use to get the tick counts for left and right wheels:

//----------------------------------------
//
//----------------------------------------

bool get_ticks_since_last( int16_t *lft,
                           int16_t *rht,
                           uint16_t *ms )
{
  cli();
  *lft = en_lft_ticks;
  *rht = en_rht_ticks;
  *ms = en_counter;
  en_lft_ticks = en_rht_ticks = en_counter = 0;
  bool error = en_error;
  en_error = false;
  sei();

  return !error;
}

Note that we clear the tick counts and error flag each call. The call also returns the milliseconds since the last time it was called. This is just a convenience.

An example usage:

 int16_t left;
 int16_t right;
 uint16_t ms;

 if (get_ticks_since_last( &left, &right, &ms) && ms > 0)
 {
   if ( left == 0 && right == 0 )
   {
     // we are stopped
   }
   else if ( left == right )
   {
     // we are traveling straight
   }
   else
   {
     // we are turning
   }
 }
}

I’ll be posting soon about using the encoders to implement a PID controller to help the bot travel in straight lines. That was a lot of fun to play with.

Advertisements

Comments welcome

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s