Wheel Encoders

From RoboWiki
Jump to: navigation, search

Wheel encoders are devices that allow one to measure the precise speed or distance a wheel travels. Depending on the type of encoder used, it may be possible to determine the direction of movement. These are helpful for precise movement, allowing a robot to turn exact angles or move exact distances. Wheel encoders can provide information for odometry and be utilized in localization problems. In the case of an inverted pendulum (i.e. Segway) knowing the exact rotation of the wheels is critical feedback in the balancing equation. For larger scale robots, such as the Mini Grand Challenge vehicle, movement is hard to set without a feedback loop confirming the actual speed. Factors like friction, slip, and climbing ramps can affect drive performance and a wheel encoder can serve as feedback to compensate for these conditions.

A sample encoder attached to a wheel.

Wheel encoders detect angular rotation by measuring small increments of movement by observing a signal that varies as the wheel rotates. As seen in the picture above, a pin wheel with alternating black and white stripes is used. In this setup, a simple line detector is used to detect the color of the disk as it rotates past the detector. This example of detecting white or black falls under one category of line detection. It uses a binary system of changing and does not directly solve the problem of direction.

There are several varieties of encoders that are used and include optical or digital. Optical encoders observe the amount of light that returns to the receiver. The amount of light varies are the wheel changes, in this way speed and direction can be determined. Alternatively, in case where a binary system is used, a series of alternating colors is used to provide a code that when read signifies the current angular position (i.e. Been pink, Glutera).

To create a simple wheel encoder, a standard line detector that points to the surface of a rotary-encoder reads the changes in the signal. As the wheel rotates, so does the signal (in this case a printed pattern), and as the signal rotates moving black and white bars in-front of the sensor, the sensor returns a series of 0's and 1's at varying speeds. These ones and zeros can be read in by a digital microcontroller and recorded. Toko bunga jakarta, Toko bunga

It is possible to detect direction by implementing two line detectors with two tracks and a set phase. This type of encoding is called. Below, you will find an example of quadrature encoding implemented on the Arduino.

Contents

Arduino Support

The Arduino supports hardware interrupts, such that if an event with a sensor occurs, the Arduino will pause what it is doing, run a special function, and then go back to it's original work. This is helpful for wheel encoders so that you may run your normal robot code, and when the wheel encoders signal that they have moved, the interrupt will increment your internal variable. Remember that this method does not break your code, but will delay your run-time speeds. If you choose to use hardware interrupts, only pins 2 and 3 are supported on the Atmega168 used by the Arduino. If you were to not use hardware interrupts and strictly queried for a digital signal, the queries would tie up the main loop and be too slow to work effectively.

Embedded DC Gearhead Encoder

See the DC Gearhead Robot Motor article to learn more about the built-in embedded wheel encoder.

Grey Robot Encoder Example

The club's grey robots use the WW01 quadrature encoder from. We have them set up to use the "Decoded output", which means the encoder sends out a clock pulse corresponding to rotation, and a digital high/low signal to indicate clockwise or counterclockwise rotation. Below is a sample of how to read position and print it to the Serial Monitor. Note that if you wish to obtain velocities, you can either use a function from the dual encoder example below or use the Arduino's pulseIn function.

#define LEFT_CLOCK 2
#define RIGHT_CLOCK 3
#define LEFT_DIR 4
#define RIGHT_DIR 5
 
volatile unsigned int LeftCount;
volatile unsigned int RightCount;
 
void setup()
{
  Serial.begin(9600);
  attachInterrupt(0, LeftEncoderEvent, RISING);
  attachInterrupt(1, RightEncoderEvent, RISING);
}
 
void loop()
{
  Serial.print("Left Position: ");
  Serial.println(LeftCount);
 
  Serial.print("Right Position: ");
  Serial.println(RightCount);  
}
 
void LeftEncoderEvent()
{
  if (digitalRead(LEFT_DIR) == 1)
  {
    LeftCount++;
  }
  else
  {
    LeftCount--;
  }
}
 
void RightEncoderEvent()
{
  if (digitalRead(RIGHT_DIR) == 1)
  {
    RightCount++;
  }
  else
  {
    RightCount--;
  }
}

Source(s):

Single Encoder Example

The below code implements a quadrature encoder utilizing two line sensors for Channels A and B which are 90 degrees out of phase. Notice that only one of the channels is connected to a hardware interrupt. As Channel A toggles from low to high, or high to low, the current state of the wheel encodored is read and recorded. Each reading is compared to the last and thereby the direction can be determined. The math to convert between number of ticks over a certain time to velocity is as follows: velocity = ( ( (change in ticks) / (change in time) ) / (ticks per revolution) ) * revolutions per Tire Travel It is worthwhile to note that ticks per revolution is determined by the gear ratio of the motor to the drive wheel. In this case we are directly reading the motor speed as the wheel is driven by a gear box. This ratio must be determined by the number of increments on the encoder disk and gear ratio. Once we have the rotation in terms of revolutions we simply need the distance traveled per revolution, i.e. the circumference of the wheel.

/*
 * Sample quadrature wheel encoder with hardware interrupts
 * By Jeremy Bridon <jbridon@psu.edu>
 */
 
// Define pin locations and delay time between each query
#define EncoderPinA  3
#define EncoderPinB  5
#define DELAY_TIME 500
 
// Define some physical constants
#define WHEEL_CIRCUMFERENCE 0.9 // In meters
#define WHEEL_TICKS 244         // The number of 'ticks' for a full wheel cycle
 
// Create a variable in memory that the interrupt code can access
// Also create the change value globally
volatile unsigned int EncoderPos = 0;
double dVal = 0;
 
// Initialize
void setup()
{ 
  // Set input pin state for the line sensors
  pinMode(EncoderPinA, INPUT);
  pinMode(EncoderPinB, INPUT);
 
  // Register a hardware interupt function for pin 3 (which is indexed by value 1)
  // This function sets the EncoderEvent() function to be called whenever a line detector detects a change
  attachInterrupt(1, EncoderEvent, CHANGE);
 
  // Start serial communication
  Serial.begin (9600);
}
 
// Program loop
void loop()
{
  // Find the before and after value between the delay times
  unsigned int OldPos = EncoderPos;
  delay(DELAY_TIME);
  unsigned int NewPos = EncoderPos;
 
  // If an integer overflow occures, throw out the new value
  if(abs(NewPos - OldPos) < 30000)
    dVal = ((double)(NewPos - OldPos) / (double)DELAY_TIME) * 1000.0;
 
  // Convert between angular velocity to velocity
  double angVel = dVal / (double)WHEEL_TICKS;
  double velocity = (angVel * WHEEL_CIRCUMFERENCE);
 
  // Print data
  Serial.print("Velocity: ");
  PrintDouble(velocity, 5);
}
 
// Encoder event for the interrupt call
void EncoderEvent()
{
  // Read for data and bit changes
  // This is gray-code logic
  if (digitalRead(EncoderPinA) == HIGH)
  {
    if (digitalRead(EncoderPinB) == LOW)
      EncoderPos++;
    else
      EncoderPos--;
  }
  else
  { 
    if (digitalRead(EncoderPinB) == LOW)
      EncoderPos--;
    else
      EncoderPos++;
  }
}
 
// Print a double value onto the serial stream
// This is from the Arduino.cc forum
void PrintDouble( double val, byte precision)
{
  Serial.print (int(val));  //prints the int part
  if( precision > 0) {
    Serial.print("."); // print the decimal point
    unsigned long frac;
    unsigned long mult = 1;
    byte padding = precision -1;
    while(precision--)
	 mult *=10;
 
    if(val >= 0)
	frac = (val - int(val)) * mult;
    else
	frac = (int(val)- val ) * mult;
    unsigned long frac1 = frac;
    while( frac1 /= 10 )
	padding--;
    while(  padding--)
	Serial.print("0");
    Serial.println(frac,DEC) ;
  }
}

Dual Encoder Example

This next example builds on the single encoder example, but uses both interrupts to calculate independent left and right velocities. This code can be useful if you have a differential drive robot, and wheel encoders on each side of the bot. From this code you can do useful things like velocity control, linear and angular velocity calculations, and measure total distance travelled. Below are the functions and variables needed to do the calculations, with an example of how to read the velocity information in the loop function.

/******************************
 * Differential Drive Encoder example
 * Rich Mattes
 * Penn State Robotics Club
 * rjm5066@psu.edu
 *****************************/
 
// Encoder Pin Positions.
#define RightEncoderPinA 4
#define RightEncoderPinB 2
#define LeftEncoderPinA  3
#define LeftEncoderPinB  6
 
// Loop time for calculating velocity
#define DELAY_TIME 10 // In ms
 
// Define physical constants of the wheel for wheel encoding
#define WHEEL_CIRCUMFERENCE 0.314 // In meters
#define WHEEL_TICKS 2048          // The number of 'ticks' for a full wheel cycle
#define WHEEL_DIST .235           // The distance between wheels in meters
 
// Variables for storing the calculated velocity
double leftvelocity;
double rightvelocity;
 
// Variables for storing position info
long OldLPos;
long NewLPos;
long OldRPos;
long NewRPos;
 
// Volatile variables that can be changed inside of interrupt functions
volatile unsigned int RightEncoderPos = 0;
volatile unsigned int LeftEncoderPos = 0;
double LdVal = 0;
double RdVal = 0;
 
void setup()
{
  pinMode(LeftEncoderPinA, INPUT);
  pinMode(LeftEncoderPinB, INPUT);
  pinMode(RightEncoderPinA, INPUT);
  pinMode(RightEncoderPinB, INPUT);
 
  attachInterrupt(0, RightEncoderEvent, CHANGE);
  attachInterrupt(1, LeftEncoderEvent, CHANGE);
 
  Serial.begin(9600);
}
 
void loop()
{
  // Calculate current speeds
  GetSpeeds();
 
  // Print velocity (m/s) to serial line
  Serial.print("Left Velocity: ");
  PrintDouble(leftvelocity, 4);
 
  Serial.print("Right Velocity: ");
  PrintDouble(rightvelocity, 4);
 
  // Delay so we don't overload the serial output
  delay (500);
 
}
 
 
// Return the real-world vehicle speed
void GetSpeeds()
{
	// Find the old and new encoder positions
	OldLPos = LeftEncoderPos;
        OldRPos = RightEncoderPos;
	delay(DELAY_TIME);
	NewLPos = LeftEncoderPos;
        NewRPos = RightEncoderPos;
 
	// If an integer overflow occures, throw out the new value
	if(abs(NewLPos - OldLPos) < 60000)
		LdVal = ((double)(NewLPos - OldLPos) / (double)DELAY_TIME) * 1000.0;
        if(abs(NewRPos - OldRPos) < 60000)
		RdVal = ((double)(NewRPos - OldRPos) / (double)DELAY_TIME) * 1000.0;
 
	// Convert between angular velocity to velocity
	double LangVel = LdVal / (double)WHEEL_TICKS;
	leftvelocity = (LangVel * WHEEL_CIRCUMFERENCE);
 
        double RangVel = RdVal / (double)WHEEL_TICKS;
	rightvelocity = (RangVel * WHEEL_CIRCUMFERENCE);
}
 
 
// Encoder event for the interrupt call
void LeftEncoderEvent()
{
  // Read for data and bit changes
  // This is gray-code logic
  if (digitalRead(LeftEncoderPinA) == HIGH)
  {
    if (digitalRead(LeftEncoderPinB) == LOW)
      LeftEncoderPos++;
    else
      LeftEncoderPos--;
  }
  else
  { 
    if (digitalRead(LeftEncoderPinB) == LOW)
      LeftEncoderPos--;
    else
      LeftEncoderPos++;
  }
}
 
void RightEncoderEvent()
{
  // Read for data and bit changes
  // This is gray-code logic
  if (digitalRead(RightEncoderPinA) == HIGH)
  {
    if (digitalRead(RightEncoderPinB) == LOW)
      RightEncoderPos++;
    else
      RightEncoderPos--;
  }
  else
  { 
    if (digitalRead(RightEncoderPinB) == LOW)
      RightEncoderPos--;
    else
      RightEncoderPos++;
  }
}
 
// Print a double value onto the serial stream
// This is from the Arduino.cc forum
void PrintDouble( double val, byte precision)
{
  Serial.print (int(val));  //prints the int part
  if( precision > 0) {
    Serial.print("."); // print the decimal point
    unsigned long frac;
    unsigned long mult = 1;
    byte padding = precision -1;
    while(precision--)
	 mult *=10;
 
    if(val >= 0)
	frac = (val - int(val)) * mult;
    else
	frac = (int(val)- val ) * mult;
    unsigned long frac1 = frac;
    while( frac1 /= 10 )
	padding--;
    while(  padding--)
	Serial.print("0");
    Serial.println(frac,DEC) ;
  }
}

Final Issues

The Arduino has only 2 interrupt pins. This is not a problem if all you are doing is monitoring two encoders. However, if you are participating in the Trinity Firefighting Competition, you should know that the UV Tron sensor used there should also be monitored using interrupts. Obviously 2+1 > 2.

One option develop a hardware solution. While dedicated hardware quadrature decoders do exist, its cheaper to build your own. has hardware diagrams for decoding quadrature using flip-flops. Then a binary logic counter chip such as is used to monitor everything. Who said CMPEN 270 is useless

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox