Robotics StackExchange | Archived questions

Odometry optimisation

Hi All

I have been playing with ROS1 for 6 weeks or so.

I have a robot built - 2 motors and a caster - differential drive, pi 4 (no arduinio etc), motor controllers, 2 x wheel encoders, ld19 lidar, ICM20948 IMU.

I have Ubuntu 20.04 on the Pi and on a virtual machine on the laptop, ROS Noetic installed on both, a driver node and odom node written, ld19 and imu drivers working into ROS.

My (I say my, mostly used from various tutorials) code is all here: https://github.com/KevWal/ROS-Bot-Workspace

More info here: https://answers.ros.org/question/417643/first-robot-rubbish-odom-laser_scan_matcher-architecture/ and here: https://answers.ros.org/question/417695/laser_scan_matcher-with-amcl-amd-move_base/

I want to get it navigating from a Pose estimate to a Nav Goal, based on a pre built map, but am having trouble due it seems to poor odometry.

I have a couple of videos here:

https://www.youtube.com/watch?v=JPdHXrhvbyc https://www.youtube.com/watch?v=fkxcIxV6M-g

and numbers in a spreadhseet here: https://github.com/KevWal/ROS-Bot-Workspace/blob/master/Encoder-tests.xlsx

The raw values in the spreadhseet are:

Raw Encoder Tests

The values normalised to all be for the equivilent to 1m to all be comparable are:

image description

Summary is in forwards and backwards movements my reported distance is out by a maximum of 8cm in a meter over 18 readings across both wheels. The only thing I can think to do from here is have slightly different encoder ticks per m values for the L and R wheels. 11,048 ticks per m for L and 11,083 for the right, but thats only 1/2 a cm difference?

Edit 1:

Doing some more testing, running each wheel for a fixed 10 turns, totally outside of ROS, and counting the encoder ticks. '10 turns' is judged by eye so will have a little variance. Because of slight differences in the motors 10 turns of motor L cant be assumed (isnt) 10 turns of motor R.

Lightly highlighted sections are those that match the 10 turns of that wheel. Bright yellow highlighted sections are areas of concern.

10 turns freee wheeling

Clearly my B encoder of the Right motor has some serious issues. But I also have a whole set of readings on my A encode of the Right motor that are a good 250 ticks short too.

10 turns on the ground

In my 'on the ground' tests I still have the B encoder of the Right motor issue, but I also have 1 line of readinds across all encoders on both motors that is a bit haywire.

Edit 2:

An osciliscope of Motor L (recording at 50Khz) generally looks good, signal voltage for a 3.3v signal is perhaps a bit low at 2.4v for a high? A few of the pulses are a bit different in length - generally 1.3ms but some are 2ms. Some corelation with every third pulse - and given the encoder is 3 magnets maybe one is a bit stronger than the others?

Trace Motor L

And on M R we clearly have periods of missing pulses on one encoder:

Trace Motor R

PWM Frequency 50, 500, 1000 etc) doesnt seem to make any difference to anything.

A slight oddity, with the econders connected to the Pi and my DSO, I see highs of 2.4v on an analog reading, and I need to set the DSO to 1.8v logic in order for it to recognise that as a high. When I disconnect the encoder outputs from the Pi and just connect them to the DSO, I see highs of 3.3v and the DSO recognises that as High with a 3.3v logic setting (or infact any of the 1v, 1.8v, 2.5v, 3.3v, or 5v settings). I have tried playing with the pull up / pull down / float settings of the Pi DigitalInputDevice(encoderal, pull_up=True) etc) to no effect at all.

Edit 3

More testing - I installed and ran pigpio's read_PWM code and used it to test each of the encoder outputs in turn. It is supposed to be able to read frequencies up into the 10's of Khz fine, so my 350hz or so signal should be a problem for it. The readings are taken every second and are average frequency (hz), pulse width (micro seconds) and duty cycle (%) per second

Encoder A Left Wheel:

f=359.1 pw=2140 dc=76.84
f=354.6 pw=2140 dc=75.89
f=351.5 pw=2200 dc=77.33
f=354.0 pw=2165 dc=76.64
f=349.0 pw=2200 dc=76.79
f=351.5 pw=2200 dc=77.33
f=352.7 pw=2150 dc=75.84
f=354.0 pw=2180 dc=77.17
f=350.9 pw=2145 dc=75.26
f=375.2 pw=2195 dc=82.36
f=373.1 pw=2155 dc=80.41
f=375.2 pw=2195 dc=82.36
f=347.2 pw=2210 dc=76.74
f=349.7 pw=2215 dc=77.45

Encoder B Left Wheel:

f=358.4 pw=2005 dc=71.86
f=348.4 pw=2090 dc=72.82
f=372.4 pw=2065 dc=76.91
f=373.8 pw=2055 dc=76.82
f=375.9 pw=1975 dc=74.25
f=354.6 pw=2035 dc=72.16
f=379.5 pw=2050 dc=77.80
f=373.8 pw=1985 dc=74.21
f=351.5 pw=2025 dc=71.18
f=377.4 pw=1970 dc=74.34
f=359.1 pw=2000 dc=71.81
f=373.1 pw=2070 dc=77.24
f=347.8 pw=2090 dc=72.70
f=354.0 pw=1990 dc=70.44
f=353.4 pw=2045 dc=72.26

Encoder A Right Wheel:

f=363.0 pw=2060 dc=74.77
f=359.7 pw=2030 dc=73.02
f=363.6 pw=2060 dc=74.91
f=363.6 pw=2075 dc=75.45
f=359.7 pw=2020 dc=72.66
f=356.5 pw=2036 dc=72.58
f=316.0 pw=2010 dc=63.51
f=318.0 pw=1910 dc=60.73
f=317.5 pw=1910 dc=60.63
f=346.5 pw=1874 dc=64.93
f=346.0 pw=1900 dc=65.74
f=361.0 pw=2075 dc=74.91
f=363.0 pw=2075 dc=75.32
f=363.6 pw=2005 dc=72.91
f=320.0 pw=1891 dc=60.51
f=322.6 pw=1981 dc=63.90
f=363.0 pw=2085 dc=75.68

And my broken Encoder B Right Wheel:

f=313.0 pw=2185 dc=68.39
f=175.7 pw=5285 dc=92.88
f=175.7 pw=5235 dc=92.00
f=175.9 pw=5255 dc=92.44
f=309.6 pw=2200 dc=68.11
f=451.5 pw=5 dc=0.23
f=311.5 pw=5250 dc=163.55
f=310.6 pw=2195 dc=68.17
f=314.9 pw=2171 dc=68.36
f=314.5 pw=2175 dc=68.40
f=310.1 pw=2205 dc=68.37
f=176.7 pw=5216 dc=92.16

Edit 4:

I can now clearly match up my driver node ticks running in ROS to my 'scope output. Going forwards only at this stage as no quadature encoders working.

The debugging output of my driver_node, showing the requested speed through to the duty cycle sent to the motors:

Driver Output

And the matching scope output - for those that don't want to count them I get them the same plus or minus 1 tick!

Scope Output

Thoughts, comments, answers pointing our issues in my code welcomed!

Thanks very much Kev

Asked by KevWal on 2023-07-28 11:18:49 UTC

Comments

How can 1 meter travel be less than both 0.5m and 2m travel as shown in your 1st, 2nd, and 6th forward trials and 1 meter travel be greater than 0.5m and 2m in the 2nd and 3rd backward trials? And what are the numbers in the table? Are these numbers in the registers(encoder counts)? If so what are the values at zero meters? It looks like you significant issues even beyond the significant 8% error noted(and 8% linear error is huge when you convert to rotation).

Asked by billy on 2023-07-28 14:00:12 UTC

Hi Billy, In the table I have pasted above all the values are normalised to a self contained 1m. ie if they are 0,5m they are multipled by 2, if they are a reading subsequent to the first reading then they have the previous readings taken away from them - so every number is a comparable to every other.

The original values are in the xls I link to above.

Yes these are encoder counts counted at the output of the topic - /lwheel_ticks and /rwheel_ticks.

Every test started at an encoder count of zero and there are 12 tests above (3 tests full power ahead, 3 tests full power stop start ahead, 3 tests full power reverse, 3 tests stop start reverse).

When I say "full power " vs "full power stop start", I mean on "full power" I kept the stick pushed forwards and just marked the encoder reading at which the robot crossed the 0.5/1/2m lines.where as on "full power stop start" I stoped asking the robot to move at the 0.5/1/2m points, took the reading, and then started moving again.

Asked by KevWal on 2023-07-28 15:47:25 UTC

Looking at these normlaised encoder ticks per m values, reguardless if they are a true meter run, a 0.5m run scaled up, the start, middle, or end of a continuious 2m run, there are some big ish inconsistencies.

For example, the 0.5m to 1m part of run 2 and run 3 for full power no stopping - the equvilent of 10,666 ticks vs 11,286. Why does the same 0.5m traveled sometimes show 620 less ticks - the equivilent of 5cm difference!

I am pretty sure this is not wheel or tire slipage - there is nothing holding the robot back.

Next test is off the ground I think - how many ticks for ten visual rotations of the tire, totally isolated from ros, just make the motor go full power and count the ticks for 10 rotations. One for the morning.

Asked by KevWal on 2023-07-28 16:05:15 UTC

+1 for planning isolation testing. Do you have an oscilloscope you can use to look at raw electrical signal? I'm suspecting you may find the signal drops out sometimes.

Asked by billy on 2023-07-28 16:25:16 UTC

Some thoughts:

  • Are your encoders measuring rotation of the wheel axel or rotation of motor spindle?
  • According to the spec sheet for the encoder, how many ticks is a single 360° rotation?
  • For these tests, you should not be using a joystick because it's difficult to keep angular velocity at 0. It's better to use code to publish to /cmd_vel at a slow velocity your motors can smoothly handle.
  • The phrase "full power" implies "wheel slip" to me. Have you checked the left/right encoder difference with the drive wheels not touching the ground?

Asked by Mike Scheutzow on 2023-07-29 09:26:43 UTC

Hi Mike thanks for your questions:

  • Encoders are measuring rotation of the motor spindle, not rotation of the wheels..
  • 6 ticks (3 ups, 3 downs) per rotation
  • Pror to the Edit 1 tag this was uing ROS and a Joystick. After Edit 1 tag this is not using ros at all, just pythos code controlling the motors and reading the ticks directly - but in the same was as I do in my driver_node that I use for ROS..
  • 'Full power' was a 'direct to the motor controller' PWM setting of 100. Re "checked the left/right encoder difference with the drive wheels not touching the ground" - Yes, the first screen shot after the 'Edit 1' Tag labeled "Free Wheeling" is wheels off the ground.

Thanks very much Kev

Asked by KevWal on 2023-07-29 09:54:48 UTC

A slight oddity, with the econders connected to the Pi and my DSO, I see highs of 2.4v on an analog reading, and I need to set the DSO to 1.8v logic in order for it to recognise that as a high. When I disconnect the encoder outputs from the Pi and just connect them to the DSO, I see highs of 3.3v and the DSO recognises that as High with a 3.3v logic setting (or infact any of the 1v, 1.8v, 2.5v, 3.3v, or 5v settings). I have tried playing with the pull up / pull down / float settings of the Pi DigitalInputDevice(encoder_a_l, pull_up=True) etc) to no effect at all.

Asked by KevWal on 2023-07-29 10:39:09 UTC

Looks like you found the issue. Your right encoder is failed.

Asked by billy on 2023-07-29 12:54:17 UTC

Unfortunately I don't think so. My driver_node code only uses the A encoder on each motor, and the A encoder on both motors is ok.....

Asked by KevWal on 2023-07-29 13:15:14 UTC

I can see two possible explanations for your data:

  • the hardware is not counting all the encoder output edges (possibly on both L and R, but with different loss rates.)
  • the motors are mismatched, and a particular PWM value does not result in the same spindle velocity.

Perhaps this test would tell you something: run a motor at "constant" speed and see if the oscilloscope reports the same number of ticks/second as your software?

Asked by Mike Scheutzow on 2023-07-29 16:41:46 UTC

How do you know which way the motor is turning if you only use one channel of the encoders? How confident are you that it really only uses one channel? I've never heard of any application using an encoder single channel (it would then just be a tachometer).

Asked by billy on 2023-07-29 16:44:15 UTC

@Billy - My code just says "If the Power is positive then encoder ticks must be going up, if negative, encoder ticks must be going down". See here:

I used to use the yellow TT motors with a single optical encoder on the output shaft - so had no choice but to do it this way.

I changed to the Micro Metal Gear Motors to get a higher gear ratio (to get slower minimum speed) and to get more encoder ticks per ros rate spin. With the TT Yellow motors I was only getting 15 ticks per ros rate spin at a rate of 5hz, so no chance to be acurate, now geting 60 ticks per ros spin at a rate of 10hz as encoders attached to motor shafts rather than wheel shafts.

Asked by KevWal on 2023-07-30 03:00:02 UTC

@Mike-Scheutzow thanks for the comment,

  • Re not counting all the encoder ticks - I agree a posibility, but not sure how to check. I could run the motors for a fixed 1 second, the software would tell me about 600 ticks, but then I have to go an manually count 600 ticks on the 'scope to compare! I could do 1/10th of a second I guess and count 60...

  • Re motors are mismayched - oh they are, one generates slightly more speed for the same pwm thatn the other - thats clear in the data. 10 turns of the L wheel causes 9.7 turns of the R wheel in the same length of time. 10 turns of the right wheel generates 10.36 turns of the left wheel in the same length of time.

Re the test - my current 'scope is not telling me frequency of the signal, i can only get frequency of a single pulse. My multimeter will tell me frequency, but the values in the read_PWM code are up and down like a yoyo (within a range), and the multimeter output is similar - so compar

Asked by KevWal on 2023-07-30 03:13:48 UTC

[encoder output] up and down like a yoyo (within a range), and the multimeter output is similar

Unfortunately this doesn't tell us much - it could be a sensor-reading problem or a motor-control problem (or both.)

I'm not familiar with the PWM control requirements of inexpensive motors - maybe it expects your control loop to keep shifting the PWM width in the desired direction? I assume your raspberry pi generates the PWM width using hardware. Have you tried to create a software control loop to run the motor at a constant velocity (as reported by the encoder)?

Asked by Mike Scheutzow on 2023-07-30 10:15:04 UTC

Regarding:

My code just says "If the Power is positive then encoder ticks must be going up, if negative, encoder ticks must be going down". See here:

OK, so they are simply guessing which way the encoders are counting based on the polarity of the voltage at the motor. It's no wonder then you're having issues. On deceleration you can have negative motor power and positive going encoder counts and this code would generate errors. And if you're rotating robot with one wheel "stopped" on the edge of an encoder line while power is hovering around zero you could get false counts (wrong way) as well. You really want to use a proper encoder counting method. Use this as a learning opportunity and take the time to do it right. There are multiple ways of counting quadrature to assure you count the correct direction.

Asked by billy on 2023-07-30 18:33:33 UTC

@billy thanks, once I get a 2nd motor with both encoders working (2nd replacement sent back to Amazon today due faulty encoder!) I will sort out quadrature encoder reading - can't do much on this area till then. I must admit I can't see this being a big area of error - the motors change direction within 30ms - I can see the change on the 'scope, but can only help and a good learning experiance moving to quadrature encoder reading as you say.

Asked by KevWal on 2023-07-31 06:26:54 UTC

@Mike-Scheutzow (I don't seem to be able to get the @ to work for you btw)

Lots of work updating pigpiod and making sure my code is as logical as possible and then testing ros code output vs what I see on the 'scope and I can now confirm they match up within 1 tick or so - for forward movement only - see images about to post in original question edit 4.

Asked by KevWal on 2023-07-31 09:33:06 UTC

So I fixed my 1 failed encoder, I implimented quadrature encoding (easy with gpiozero's RotaryEncoder code), but I still have crap odom. Given Hector slam does a near perfect job, with both accuracy and speed I'm gona give up on encoder based odometry for a while...

Asked by KevWal on 2023-08-05 05:19:49 UTC

"give up on encoder based odometry for a while..." I think this sounds reasonable if you have a work around for now. Eventually you may want to come back to it since the odom from hector slam can't update any faster than your laser and that will limit performance. Have fun.

Asked by billy on 2023-08-05 14:34:43 UTC

Answers