Need help reading Encoder values.

Okay.

The long story short is that I am building a hexapod robot. I'm using six pololu gear motors with quadrature hall effect encoders mounted on the back. I am using an Arduino Mega to read all six encoders, and I am having a hell of a lot of trouble getting the data to look the way it should.

I tested the encoder inputs on a oscilloscope. Everything appeared correct. 0-5V signals, 90 degree offset.

The first thing I tried was loading the Teensy Encoder library that the Arduino Encoder page links to. I loaded the library, set up an encoder object, and made a small chunk of test code. This test code told one of my robot legs to spin at 1/5 speed, so approximately 20 RPM. I outputted the position data via serial to see what my microcomputer was seeing. My data was spazzing between the values of -12 to 12, and seemed to get "stuck" on a value sometimes, sitting at 0 to 9 for half a revolution at a time. I couldn't make any sense of it.

I set the teensy code aside and tried to use the nested "For" loop code that the Arduino Encoder page used. When I used that, I got ~256 counts per revolution, which was odd, as even measuring the falling and rising edge of Encoder Pin A and Encoder Pin B, I should only get a maximum of 64 counts per revolution. If I'm reading it correctly, that code is only counting a single edge of Pin A (when it goes from high to low) so I should only be counting sixteen.

Well, at least it was consistent, I thought. But when I tried to extend the nested for loop code to two motors, things got crazy again. It started only counting ~38 counts per revolution of each motor. Then I was royally confused.

So, has anyone used a two-channel quadrature encoder with an Arduino before? And does anyone know where I should start in getting consistent values for each encoder/motor combo? Any and all advice appreciated.

Hello,

in the past I was working on making your own encoder (hardware and software). You can look at my blog:

Simple rotary encoder:

Quadrature encoder:

This is a very basic example for reading an encoder:

Elektrix

projectzero:
The first thing I tried was loading the Teensy Encoder library that the Arduino Encoder page links to. I loaded the library, set up an encoder object, and made a small chunk of test code. This test code told one of my robot legs to spin at 1/5 speed, so approximately 20 RPM. I outputted the position data via serial to see what my microcomputer was seeing. My data was spazzing between the values of -12 to 12, and seemed to get "stuck" on a value sometimes, sitting at 0 to 9 for half a revolution at a time. I couldn't make any sense of it.

You need to post your code if you want help with it, we can't magically guess what
the problem is!

Elektrix:
Hello,

in the past I was working on making your own encoder (hardware and software). You can look at my blog:

Simple rotary encoder:
Make your own rotary encoder - heliosoph

Quadrature encoder:
Make your own quadrature rotary encoder - heliosoph

This is a very basic example for reading an encoder:
rotary encoder software for arduino - heliosoph

Elektrix

Thanks Elektrix. I look into interrupts but because of the volume of encoder data (six encoders, two signal wires apiece) I'd need 12 interrupts which the Arduino doesn't come near to having. Have you written any encoder code that uses digitalRead()? I know it's inferior, but shikata ga nai, eh?

MarkT:

projectzero:
The first thing I tried was loading the Teensy Encoder library that the Arduino Encoder page links to. I loaded the library, set up an encoder object, and made a small chunk of test code. This test code told one of my robot legs to spin at 1/5 speed, so approximately 20 RPM. I outputted the position data via serial to see what my microcomputer was seeing. My data was spazzing between the values of -12 to 12, and seemed to get "stuck" on a value sometimes, sitting at 0 to 9 for half a revolution at a time. I couldn't make any sense of it.

You need to post your code if you want help with it, we can't magically guess what
the problem is!

I'm at work now, but I'll post my code tonight.

You should be able to take some other inputs as interrupt, too:

http://playground.arduino.cc/Code/Interrupts

Never tried this myself...

There are links to libraries, you could also search for "Pin Change Interrupts".

Elektrix

My issue is very similar to the encoder issues I see discussed here. I'm new to Arduino but very experienced with electronics & programming.

I'm trying to use an Adruino Leonardo to replace the cruise control module in my car. I'm not sure the Leonardo will have enough program memory for the task, but it was a good place to start the project. I'll upgrade the hardware if necessary.

The problem I'm having is with interrupts. The Leonardo is mounted directly on the old cruise control module's PCB to take advantage of drivers for two solenoids and various inputs, filtering etc. But I can ground the interrupt pin and I still get interrupts! This is with the Leonardo powered by USB and the board NOT in the car.

I went so far as to remove the Leonardo from the cruise module PCB so it's just the Leonardo alone. Still I get interrupts even when the pin is grounded (the "attach" uses the RISING parameter).

According to the Arduino reference and Leonardo specific hardware pages, 5 external interrupts are possible for the Leonardo (quoted from Arduino website): "3 (interrupt 0), 2 (interrupt 1), 0 (interrupt 2), 1 (interrupt 3) and 7 (interrupt 4)". I presume the numbers outside parenthesis are the actual pin numbers, so interrupt 1 is on pin 3 (4th pin: rx, tx, pin 2, PIN 3). I use this pin for the speedometer pulse input.

With the pin grounded the only conclusion to draw is something is either wrong with the Leonardo or there is something in the "operational / overhead" code provided by the Arduino IDE that is triggering the interrupts.

I have reduced the sketch to bare minimum. The Leonardo is connected to a Sparkfun 16x2 LCD, driven off pin 12. The sketch attaches the function to serve as an interrupt handler, which just saves the delta time from the last interrupt occurrence. The loop() calls one function to display the time interval on the LCD.

The documentation is rather sparse concerning interrupts. But if anyone can provide any info or pointers as to how to get an accurate time between two pulses using an interrupt service routine, or have input about the sketch I'de love to hear from you.

Thanks for taking the time to read this lengthy post. My test sketch will follow in another post.

Here is my test sketch:

#include <SoftwareSerial.h>

//// Pin definitions
#define LED 13
#define DIS 7
#define VENT 6
#define VAC 5
#define BRAKE 4 // Move this to pin 2 to use interrupt 0
#define SPEED 3
#define CNTRL A0
#define S_POS A1

//// Global vars
byte ledState;
volatile unsigned long deltaT, lastTime;

SoftwareSerial LCD(13,12); // RX, TX pins

//-------------------------------------------------------------------------------------------
void setup()
{
LCD.begin(9600); // All SerLCDs are 9600 Baud by default
if (0) toggleSplash(); // This is only necessary once?
// delay(1200); // Required if splash is displayed

pinMode(BRAKE, INPUT); // A "1" when brake is applied
pinMode(LED, OUTPUT); // The on board LED on pin 13

pinMode(SPEED, INPUT); // Speed is relative to space between pulses
attachInterrupt(1, speedInterupt, RISING);

pinMode(VENT, OUTPUT); // Vent
pinMode(VAC, OUTPUT); // Vac
pinMode(DIS, OUTPUT); // Disable = 1
digitalWrite(DIS, HIGH); // Start with cruise disabled
}

//-------------------------------------------------------------------------------------------
void loop()
{
// clearScreen();
// selectLineOne();
// showBrake();
showSpeed();
// delay(100);
// digitalWrite(LED, ledState ^= 1); // Toggle the LED to indicate looping
}

// Prints a double value with number of decimal places determined by precision.
// example: double2ASCII(buf, 3.1415, 2); // prints 3.14 (two decimal places).
// The allowed range of precision is 1 to 6 inclusive. Otherwise the value will
// be displayed as an integer (no decimal point and all to the right of it will
// not be displayed).
char * double2ASCII(char *buffer, double val, char precision)
{
double frac, frac1, mult = 1;
char i, padding[7];

if (precision > 0 && precision < 7) {
frac = frac1 = (abs(val) - (int)abs(val)); // Get the fractional portion (2 copies)
while (precision--) mult *= 10; // Multiplier to move fraction to left
frac *= mult; // of decimal point

// Fill a buffer with leading zeros for the value to the right of decimal point
for (i = 0; frac1 && (frac1 *= 10) < 1; i++) padding = '0';
_ padding = 0;_

* sprintf(buffer, "%d.%s%u", (int)val, padding, (unsigned)frac); //*
* }*
* else sprintf(buffer, "%d", (int)val); // No precision; treat as an integer*

* return buffer;*
}
void showBrake()
{
* LCD.print(digitalRead(BRAKE) ? "Br=1 " : "Br=0 ");*
}
void showSpeed()
{
char buffer[17];
* clearScreen();*
* selectLineOne();*
* LCD.print(double2ASCII(buffer, (double)deltaT, 0));*
}
// Interrupt service routine for speedometer pulses.
// Calculates the time since the last interrupt. The
// result is in global var deltaT.
void speedInterupt()
{
* unsigned long diff, now = micros();*
// noInterrupts(); // Using this causes loop to stop
* if (now < lastTime) now += lastTime; // Accomodate rollover*
* diff = now - lastTime;*
* if (diff > 100) // Can also use to gauge min speed for cruise*
* {*
* deltaT = diff;*
* lastTime = now;*
* }*
// interrupts();
}
////////////////////////////////////// LCD SUPPORT BELOW \\\\\\\\\\\\\\\\\\
//-------------------------------------------------------------------------------------------
void clearScreen()
{
* //clears the screen, you will use this a lot!*
* LCD.write(0xFE);*
* LCD.write(0x01);*
}
//-------------------------------------------------------------------------------------------
void selectLineOne()
{
* //puts the cursor at line 0 char 0.*
* LCD.write(0xFE); //command flag*
* LCD.write(128); //position*
}
//-------------------------------------------------------------------------------------------
void selectLineTwo()
{
* //puts the cursor at line 0 char 0.*
* LCD.write(0xFE); //command flag*
* LCD.write(192); //position*
}
//-------------------------------------------------------------------------------------------
void toggleSplash()
{
* //this toggles the spalsh screenif off send this to turn onif on send this to turn off*
* LCD.write(0x7C); //command flag = 124 dec*
* LCD.write(9); // 0x09*
}
//--------------------------------------------------------------------------------------------
*// Sets the cursor position of the SerLCD. *
void setCursorPosition(int row, int col)
{
int pos;
if (row == 1) pos = col;
else pos = col + 64;

pos = pos + 128; // Cursor move command
LCD.write(0xFE);
LCD.write(pos);
}
[/quote]

MarkT, Electrix - Apologies for the delay. This is the nested "if" loop code I was trying to use for the encoders. The first ~10 lines of code, digitalRead(hefmotor5), is the hall effect sensor. Once the hall effect sensor goes high, y5 is set to high, and the arduino is supposed to start reading the encoder. Hefmotor5 is the hall effect sensor, enc5 and enc52 are the two hall effect sensor and.

    //THIS BLOCK OF CODE HOMES MOTOR 5
       digitalRead(hefmotor5);
      // if (digitalRead(hefmotor5) == HIGH){
        // Serial.println("Motor5 is not at home.");}
         
       if (digitalRead(hefmotor5) == LOW){
         Serial.println("Motor 5 HEF Trigger.");
         init5dir = LOW;
          y5 = HIGH;
       }
       
       if (y5 == HIGH){  
         n5 = digitalRead(enc5);
         if ((enc5last == LOW) && (n5 == HIGH)) {
           if (digitalRead(enc5) == LOW) {enc5pos--;} 
           else {enc5pos++;}
         } 
         enc5last = n5; 
         x5 = abs(enc5pos);
         Serial.println(x5);
       }
       
       if (x5 >= 8){
       init5 = 0;
       Serial.println("5 is Home");
       }

This code worked well enough until I tried to paste an identical block with all the variables changed to the next motor, motor six. When I tried to run the two codes together it started counting a different value for the amount of pulses per revolution. I thought it must be missing pulses for some reason.

I had deleted this code, but I made a rough sketch of what I used for the teensy Arduino library. The problem was it never got near breaking out of the "if" loop because the encoder.read() function was twitching randomly between -10 and 10.

void loop() { 
  
  digitalWrite(motor6dir, init6dir);
  analogWrite(motor6speed, init6);
  Encoder encoder6(enc6, enc62);

  while (true){
    
    digitalRead(hefmotor6);
      // if (digitalRead(hefmotor5) == HIGH){
        // Serial.println("Motor5 is not at home.");}
         
       if (digitalRead(hefmotor6) == LOW){
         Serial.println("Motor 5 HEF Trigger.");
         init5dir = LOW;
          y5 = HIGH;
          
       if (y5 == HIGH){
         encoder6.read();
         if (encoder6.read() > 32){
         analogWrite(motor6speed, 0); 
         }
         
       }
    
    
    
   
  } 
}