Let's Make Robots!

Is an Arduino Capable of Reading All Four Rover 5 Quadrature Encoders?

Note: Sorry this post is so long. I used bold text to indicate the main point of the post.

I didn't want to hijack Oddbot's Rover 5 threadso I'm starting this one here in the forum.

In replying to odong's question about the quadrature encoders I stated (among other things):

"From what I've read, I think most Arduinos aren't fast enough to capture all the quadrature encoder interrupts and still have time to perform other tasks. I believe reading all four quadrature encoders requires a fast microcontroller. I used a Propeller in both of my Rover 5 projects."

I'm glad I used "From what I've read" and "I believe" to indicate my uncertainty about the topic because bdk6 soon corrected me with:

"The arduino running at 16 MHz (the normal speed) processes close to 16 million instructions per second.  At 180 RPM on each wheel, using all four encoders, and the motors turning 180 RPM, there would be a total of right at 1000 interrupts per second.  With each interrupt taking about 20 instruction cycles, that would be 20000 out of 16000000 each second, or 00.125% of the total processing cycles available.  That amount is negligible.  Even horrible code that took 100 times longer would still leave over 87% of the processing cycles for other things."

This is very encouraging for anyone wanting to control the individual motors of a 4WD Rover 5 with an Arduino. What I don't understand is where are the four quadrature reading Arduino controlled Rover 5's?

I know of two examples of people stating all the interrupts were causing problems for their Arduinos.

CliffO improved my technique of mounting Vex Mecanum wheels to his Rover 5 and mentions his frustrations of having the interrupts overwhelming the Arduino.

amano001 mentioned in my thread over at the Parallax forums:

"we were looking into using one of the propeller proto boards instead of the arduino mega as we have seen the code response get very sluggish with interrupts going off all the time for the encoders."

I also recall Oddbot mentioning the need of a fast microcontroller (I think he was suggesting one of the faster Arduinos) to read four quadrature encoders. Sorry, I couldn't find a link.

There are a lot of Rover 5 chassis on LMR. I'm pretty sure I've looked at all the robot projects on LMR using the Rover 5 and I haven't found any (other than mine) using four quadrature encoders.

Most of the builders/programers of the Rover 5 projects on LMR state they are new to programming and robotics. Is this why there aren't any Arduino controlled Rover 5's using all four quadrature encoders? (I don't recall seeing any using two quadrature encoders.)

amano001 who I previously mentioned ended up using a Propeller QuickStart board as a slave to the Arduino. The Arduino would send direction and speed commands to the Propeller and the Propeller (using my code) would drive the motors while monitoring the encoders to produce the desired movements.

Is a 16MHz Arduino really capable of monitoring four quadrature encoders (with time left over for other tasks)? I'd really like to know.

Just yesterday I started designing a shield with a Propeller. My intention is to make it easier for others to use the Propeller as a slave to an Arduino. The Arduino would send speed and direction commands to the Propeller. The Propeller would then drive the motors appropriately based on a PID algorithm using feedback from the encoders.

If an Arduino can do this on its own, then the Propeller shield I plan to make wouldn't be needed.

Here are some numbers to help any interested parties to aid me in my decision to continue to develop a Propeller shield.

In my tests the max rotation speed of a Rover 5 wheel is 90 rpm. This produces 500 encoder transitions per second per wheel. All four wheels would generate 2000 transitions per second at 90 rpm. (This was a free spinning wheel. The speed will likely be lower when used in contact with the ground.)

Besides simple odometry (how far has the wheel traveled), I think it's very useful to also compute the speed of each wheel. The speed of each wheel should probably be computed at about 50Hz.

Are these interrupt rates and computation requirements within an Arduino's capability?

I have plenty of other projects I'd like to work on if a quadrature encoder reading Propeller shield isn't needed for an Arduino controlled 4WD Rover 5. I presently do not have enough experience with the Arduino to know if the proposed shield is needed. I'd really apperciate information about the Arduino's ability to monitor four quadrature encoders unassisted.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

I'm just getting started working with bots and am using a Rover 5 with 4 motors/encoders running on the standard treads.  I'm using an Arduino ADK, the Adafruit V2 motor shield, an Adafruit GPS logger shield, an Adafruit LCD shield (mounted to the side), 2 sonic sensors and an IR sensor mounted on a servo so it turns L&R.  I had everthing hooked up and working but my robot was drifting to the right when it was supposed to go straight.  After research lead me to decide I should the encoders to keep the bot going straight, I hooked up all 4 of the encoders and discovered (after much teeth grinding...) that the ground and +5 wires on one of the encoders were swapped (the wires already connected to the motors by the manufacturer, so be aware this does happen). Now I'm wondering if there is any reason to use all 4 encoders if my bot is running on treads?  I would think just using the two on the front would be sufficient.  Thoughts?

I need all four encoders with my Mecanum wheeled Rover 5 and just to make things easier on myself, I use almost the same code to control the my treaded Rover 5.

IMO, the treads really equalize the effort the pair of motors driving them use so I'd be inclined to think you'd be fine with just two encoders. I dont' think it matters if you use the front or the back ones.

Having the power lines swapped on your encoders stinks. I'm glad you got it figured out.

I hope you take some time to document your robot and let us know what you're doing with it.

 

Since this post was written I have done a lot of experimentation with the encoders on the Rover 5. Speed is definitely not the issue. An ATmega8 can easily monitor all 4 encoders on a rover 5. The problem lies in how the code is written and what else the processor needs to do.

As most people use delay(), delayMicroseconds() or Serial.print() in their code combined with functions like analogWrite(), Tone() and the servo library using timer interrupts it is no wonder people have problems monitoring all 4 encoders with a single Arduino processor.

I found the best solution was to use 1 Arduino for all your basic robot functions and a second processor (as an I2C slave) dedicated to motor control. This eliminates all the problems with timers and interrupt priorities.

Some examples:
http://letsmakerobots.com/robot/project/scamper-omni-directional-high-speed-robot
http://letsmakerobots.com/node/39492

My tutorials on the subject:
http://letsmakerobots.com/node/39098
http://letsmakerobots.com/content/how-use-quadrature-encoder

I read that Serial.print() uses Interrupts, and interacts unfavorably with ISR()'s.

In my case, the symptom was that my Rover-5 Encoders-monitoring-via-ISRs Sketch would hang after running 1 Wheel for a few min., and after ~1 sec. when running all 4 Wheels simultaneously. 

So I replaced my Serial.print()'s in my ISR()'s with:

    if (isrMsgsCount < MAX_ISR_MSGS)
    strcpy( isrMsgs[isrMsgsCount++], msg);

where the above is defined as follows:

    int isrMsgsCount;

    #define MAX_ISR_MSGS 30

    char isrMsgs[MAX_ISR_MSGS][40];

and in loop(), I added:

  if (isrMsgsCount > 0)
  {
    for (byte i = 0 ; i < isrMsgsCount ; i++)
    {
      Serial.println( isrMsgs[i]);
    }
    isrMsgsCount = 0;
  }

in setup() I added:

    isrMsgsCount = 0;

and now my Sketch doesn't hang, even when running all 4 Wheels for 10+ minutes.

=Cliff

Interrupt Service Routines (ISRs) should be very short and fast.  The absolute minimum of processing power should be used there.  Printing, no matter whether it uses interrupts or not, should NOT be done in an ISR.  The string copying you do in your example is pushing the limit. 

A different approach is to use a single timer interrupt and read all encoders signals at once. I had described this in my timer&interrupt tutorial. The example3 is for 2 quadrature encoders. The code should be easy to modify to work with 4 QEs.

Well, now I'll need to try it both ways to see which a like more.

I think the way bdk6 suggested may offer better speed resolution at slow speeds where I'll want to keep track of the time between transition and not just count transitions.

The Vex Mecanum wheels (which IMO work much better than the Fingertech version) are kind of large for the Rover 5's motors (the Fingertech's smaller size is a plus here (not a big enough plus to choose them over the Vex version)). Moving the a Rover 5 with Vex Mecanum wheels at slow speeds is challenging since the PWM amounts need to continuously changed in order to maintain the slow speed. All the static friction in the gearbox requires a lot of power to overcome but once the wheels are moving the power has to drastically cut back if one wants to move at a slow speed.

At slow speeds, I don't think there are enough encoder transitions to monitor speed without measuring the time between transitions (another one of OddBot's very useful ideas).

I don't know if using timer interrupts would offer enough resolution in speed calculations (though as I think about, they might). I'll likely try it both ways as some point to compare the two methods.

I think it's a shame there are so many 4WD Rover 5 projects that aren't taking advantage of one its coolest features (built-in quadrature encoders). 

Thanks for the link to your tutorial and thank you very much for writing the tutorial.

I have debated whether or not to chime in.  Since this thread was started from a comment I made  I wasn't sure if I should.  Obviously, I decided I would.  Mainly because there is a lot of really bad information floating around about the subject.  This will probably be a long post, so venture forth at your own risk.

Interrupts are very simple on the surface, but using them is very tricky business.  There are lots of gotchas to watch out for, and even experienced professionals often get caught up with the problems.  However, they are very, very powerful.  I am only aware of two processor architectures currently in production that don't have interrupts:  (very) low end PICs and the propellor.  I refuse to use either mainly because they lack interrupts.  I can forgive the PIC since it was designed in about 1975 to be an extremely low cost chip for very specific purposes:  Peripheral Interface Controller (PIC) for larger computers.  The propellor I can't forgive.  But that's another story for another time.  To do any type of real time programming interrupts are very nearly a necessity.  Not having and using them only makes things more difficult and less precise.

So what is an interrupt and how does it work?  What makes it so special and dangerous?  Here is a bit of technical detail to make it clear.  Any processor executes machine language instructions as a seqeunce of cycles.  It fetches the instruction from memory, decodes it, then performs whatever action is required.  Then it repeats the sequence over and over.  That is one "instruction cycle".  An interrupt does just that, it "interrupts" the normal flow.  There is extra hardware inside the processor that at some specific point (usually the end) of each instruction cycle checks the interrupt source(s).  If one of them is activated, it stops the normal cycle and transfers from the normal sequence of instructions to some "special" place, the interrupt routine.  It does this without any interference to or knowlege by the main code.  Unless you do something wrong.  Obviously the main code stops running for the length of time the interrupt code is running.  That is where the performance hit comes in.  Hence the often quoted rule of keeping interrupt routines short.  It is wise to do the absolute minimum of processing in the interrupt routine and pass the information back to the main program for further processing.  There are several ways to do that, global variables being the most common.  When the interrupt routine (ISR) is finished it uses a special instruction (RTI, return from interrupt) to transfer control back to the main program.  Care must be taken to leave the processor and memory in the same state it was or "bad things" happen.  Modern microcontrollers (like the AVR) usually have dozens of possible interrupt sources.  The advantage of interrupts is that you don't have to write code to check for an event.  The processor does it in hardware.  Checking in software is called "polling" and requires carefully placed and timed code to catch events in a timely manner.

I looked briefly at a couple of the links you posted.  I didn't see the code so couldn't look at it, but from the description in the first link it sounds like they were using a timer interrupt to then poll for the encoder input.  That is just asking for trouble.  I didn't look too hard for the code because I suspect I would have gone into convulsions when reading it.  The way to read wheel encoders is to have each wheel produce an interrupt.  Each interrupt gets it's own ISR.  Those ISRs simply read the input and increment or decrement a global variable.  That passes the information back to the main program.  The main program is then free to read them at it's convenience as long as care is taken that it is often enough to do the job.  But even that is full of gotchas. Let's say we are using an AVR (arduino).  It is an 8 bit processor.  Now let's say the global variable we use is an int (16 bits) to give us plently of time to increment and decrement it before the main program has to read it.  An 8 bit processor can only read, modify, and write 8 bits at a time.  The 16 bit variable requires several instructions to read that variable.  An interrupt can happen right in the middle.  So the main program could read half of it, then the interrupt fires and modifies it and returns, then the main program reads the other half, then sets the variable to 0.  The number will be corrupted.  The simple solution in this case is to disable interrupts while reading the variable.  That prevents corruption, but it also prevents the interrupt from firing at precisely the right time.  That usually will be ok.Ther is another issue that needs to be addressed.  The compiler tries to make the code as small and fast as possible.  It rearranges your code and removes unnecessary parts.  If it sees a variable that doesn't appear to have anything assigned to it, it will eleminate that.  The C (and C++) languages give you a way to tell the compiler not to mess with it:  the "volatile" keyword.  

So having said all that, here is a brief outline of a program to read 4 wheel encoders.  This is certainly not a complete program, but it sketches the main points.

// global variables at top of program

volatile int wheel_1;        // global variable for wheel 1

volatile int wheel_2;       

volatile int wheel_3;

volatile int wheel_4;

ISR_whatever( xxx )     // for encoder 1

{

   // read other (quadrature) bit from a port

   BYTE input = PORTB;    // read all the bits of the input port

   input = input & (1<< bit_number);   // get only the bit we are interested in

   if(input ==  0)    // Will be zero if second input bit was zero

   {

     ++wheel_1;  // increment if low

   }

   else

   {

     --wheel_f1;   // decrement if high

   }

 

// three more ISRs for the other encoders

 

void loop()

{

   int encoder_1, encoder_2, encoder_3, encoder_4;

   // whatever other procesing here

   cli();   // turn off interrupts to keep from corrupting read

   encoder_1 = wheel_1;

   wheel_1 = 0;

   encoder_2 = wheel_2;

   wheel_2 = 0;

   ...

 

}

Now with that code, each interrupt should finish in somewhat less than the 20 cycles I mentioned in my original post.  You can read an insanely high number of interrupts per second without hardly affecting the main processing code.  The 2000 interrupts per second you mention would take less than 40,000 of the 16,000,000 cycles available every second, or 0.25%.  You would never notice that.

I hope this is helpful.

This is great. I think I will give reading four quadrature encoders with an Arduino a try.

I haven't seen the Arduino code which caused the problems in the other Mecanum wheel projects either. I don't recall seeing it posted.

I very much appreciate the time you're spent explaining interrupts and giving direction on how to get started writing Arduino code to use four quadrature encoders.

I was just looking at my Arduino Uno board. I'm pretty sure I'll need to upgrade to an Arduino with more pins than the Uno if I want to duplicate my Mecanum wheeled robot code on an Arduino. I don't think the Uno has enough pins to monitor the eight encoder lines and drive four motor controllers.

Thank you for setting me straight in the Rover 5 thread about the Arduino and encoders. I'm always glad when someone stops me from spreading incorrect information.

pin change interrupt lib that was linked to on the page you mentioned earlier, I wonder if you could combine the interrupt lines from pairs of encoders. I am sure it would be more code heavy to do, but, the hardware interrupts are twice as fast as the pin change interrupts based on the speed tests that were run. If it is track driven, you could(?) easily pair both left and both right encoders. I don't at the moment have a clue how one would code for that setup yet.

PinChangeInt Speed Test pdf