# PIC18 Robot - Travelling exact distance straight using two wheel encoders problems

(EDIT: Added TMR0 & TMR1 set-up code)

I have a small robot vehicle controlled by a PIC18f4585. It has two wheels (ball bearing on the front) as well as two rotary encoders. The following function is supposed to travel a specified distance in inches perfectly straight. It travels straight enough, the problem is the distance. the travelled distance has an error of up to 2cm. I was just wondering whether anyone else could see an error I have missed.

Additional info: The PIC is clocked at 40MHz. I am counting the encoder ticks using TMR0 and TMR1 in 8-bit mode.

Any help/suggestions would be greatly appreciated, thanks.

TMR CONFIG:

TRISAbits.TRISA4 = IO_INPUT;
OpenTimer0( TIMER_INT_OFF &
T0_8BIT &
T0_SOURCE_EXT &
T0_EDGE_RISE &
T0_PS_1_1   );

TRISCbits.TRISC0 = IO_INPUT;
OpenTimer1( TIMER_INT_OFF &
T1_8BIT_RW &
T1_SOURCE_EXT &
T1_PS_1_1 &
T1_OSC1EN_OFF &
T1_SYNC_EXT_ON  );


MOVE STRAIGHT FUNCTION:

void moveForwardDistance(double distance, int power)
{
int tickGoal = distance * 5.13348;      //This seems to get ~the correct distance

int masterPower = power;
int slavePower = power;
int error = 0;
int kp = 50;            //error factor
int totalTicks = 0;     //used to keep track of total ticks

resetEncoder();

while(totalTicks < tickGoal)
{
SetRightMotor(masterPower);
SetLeftMotor(slavePower);

error = TMR1L - TMR0L;
slavePower = power + (error / kp);

resetEncoder();
DelayMs(100);
totalTicks += TMR1L;
}
SetRightMotor(0);
SetLeftMotor(0);

DelayS(1);
}

• What's the problem? Clearly, one "tick" represents 1/5.13348 of whatever unit you're using to measure "distance". Mar 17, 2013 at 3:35
• How far are you trying to move? What percent of the total distance is the 2cm? When driving straight how much does it 'wiggle' back and forth? Veering left and right would cause it to read the correct number of ticks but not all that movement would be forward. Mar 17, 2013 at 3:44
• The distance is measured in inches. I played with the value (5.13348) until I got it travelling the ~correct distance. The confusing part is that the actual circumference of the wheel is 2.6" which at 64 ticks rotation should work out to be a different value. The distances I tested were 5 and 10 inches (10 times each to get the avg). When it travels straight there is no visible "wiggle".
– chib
Mar 17, 2013 at 4:23
• Take data over multiple trials of distances, both short, and long. Is the error random? Constant? Proportional to distance? The answer to that will point towards the type of cause. Also, can your system miss a tick if it comes in as your are resetting? How many ticks on average per cycle of the loop? Are you properly decoding a quadrature encoder, or just a single channel which cannot determine direction of rotation? Mar 17, 2013 at 4:30
• Seriously?!? You reset the timers and then read TMR1L (100 ms later) in order to accumulate the distance? How's that supposed to work? That'll give you a speed estimate, but will totally ruin any sort of odometry. Mar 17, 2013 at 15:38

Sounds like you are missing encoder ticks when your CPU is busy with other tasks.

Your Robot is executing code in a loop and the loop times are approximately constant so you are missing the same number of ticks (roughly) in each loop. You've calibrated this error out of your system.

However, occasionally, you get unlucky and the loop runs longer and you miss more ticks than average.

It is impossible to decode/debug/double-check your firmware with the snippet provided since the register-level configuration of the hardware input capture unit is omitted. I'd look there.

• I added the config code for the timers to the post. What would be causing the loop to run longer, the calculation for slavePower? Thanks for all the help guys, I'm determined to at least reduce the error a bit.
– chib
Mar 17, 2013 at 14:42
• What encoders are you using? Where is the rest of the timer management code? Where is the resetEncoder code? You totalTicks accumulator seems to have a logic flaw in that the resetEncoder should reset TMR1L no? Mar 17, 2013 at 19:20
• I just have that TMR config code. I am using Nubotics WW-11 encoders (goo.gl/2kUWJ). I'm using the them in decoded mode, and have the clock line on one going into TMR0 and the other to TMR1. The resetEncoder() function literally just sets each timers registers (high and low) to 0. Would it be better / more accurate to use external interrupts and just have the encoders wired to PORTB? It's important that they be pretty accurate.
– chib
Mar 17, 2013 at 20:38
• Couple of possible problems: (1) encoder is actually missing ticks, (2) your floating point math is causing variation in your distance target, (3) you need a critical region (disable interrupts) around your timer reads to guarantee atomicity Mar 17, 2013 at 23:58

The example you cited in your comments is just wrong. Sorry, but that happens sometimes.

Conceptually, what you are trying to do is transfer the value from the 8-bit hardware counter to a 16-bit (or more) software counter. The problem with this is that the hardware counter can advance by one tick at any time, including between when you read it and when you reset it.

The example code has many problems with implementing this transfer, and clearing the hardware counter before adding it to the software counter is just the most egregious of them. Reading the hardware counter multiple times is another problem, and allowing an arbitrary amount of time to elapse between reading and clearing is another.

Ideally, you'd like to have zero time between the reading and the clearing, but this is not going to be possible. The next best thing would be to write a function (possibly in assembly language) that reads both counters, resets them in the fewest instructions absolutely possible, and then saves the values read in the first step into variables that the rest of the program can use.

In C, this function would look something like this:

unsigned char tmr0_value;
unsigned char tmr1_value;

{
register unsigned char tmr0_temp;
register unsigned char tmr1_temp;

/* Read the two timers into CPU registers and reset them
* as quickly as absolutely possible.
*/
tmr0_temp = TMR0L;
TMR0L = 0;
tmr1_temp = TMR1L;
TMR1L = 0;

/* Now, write the values to global variables.
* It is less critical how long this takes.
*/
tmr0_value = tmr0_temp;
tmr1_value = tmr1_temp;
}


You would then use this in your main loop like this:

void moveForwardDistance (double distance, int power)
{
int tickGoal = distance * 5.13348;      //This seems to get ~the correct distance

int masterPower = power;
int slavePower = power;
int error = 0;
int kp = 50;            //error factor
int totalTicks = 0;     //used to keep track of total ticks

while (totalTicks < tickGoal) {
SetRightMotor (masterPower);
SetLeftMotor (slavePower);

error = tmr1_value - tmr0_value;
totalTicks += tmr1_value;
slavePower = power + (error / kp);

DelayMs(100);
}

SetRightMotor(0);
SetLeftMotor(0);

DelayS(1);
}

• That makes a lot more sense, cheers. I made the changes, but there still seems to be a large degree of unpredictability. At first I thought the problem was completely solved as the robot travelled the exact same distance 10 times in a row, but then it started to either fall short or overshoot again, sometimes as much as an inch. Do you think writing the same read/reset function in assembler would fix it, or do you think there could be another factor at work too?
– chib
Mar 17, 2013 at 22:34
• How far is the robot moving in 0.1 second? It could be that you simply need better time resolution than that; in other words, reduce the loop delay to something less than 100 ms. If that isn't it, then I would suspect that there's some other factor involved here. Mar 18, 2013 at 0:01