Showing posts with label sensors. Show all posts
Showing posts with label sensors. Show all posts

Monday, December 12, 2016

DIY Vehicle Speed Sensor Buffer

Here's the riveting tale of how I created a Vehicle Speed Sensor (VSS) interface circuit for my Jeep Grand Wagoneer (yeah, that Jeep).

Jeep VSS
Speed sensors are used by cruise control systems and by fuel injection computers. Jeep used one for the former.

I wanted the sensor for the GM throttle body injection (TBI) system I retrofitted onto the Jeep's old AMC 360 c.i. V8.

Back when my Jeep was built, vehicle speedometers were driven by a flexible steel cable, the speedometer cable, connected by a tiny gear to the output shaft of the transmission.

Jeep, as with other vehicles of the era, split the speedo cable in half and stuck the VSS in between.

So, with insufficient hobby funds to buy an expensive, off-the-shelf Vehicle Speed Sensor/Buffer, but wanting to reap the benefits of a VSS, I created my own interface. Here's how it went down...

Tuesday, August 6, 2013

Building a Pseudo Theremin


Here's how I built a simple musical instrument with Sharp IR Rangers to demo my A2D boards.

Tuesday, July 30, 2013

Oversampling, Decimation, Filtering

To convert Sharp IR Rangers to I2C, I now use an ATtiny44a for signal processing.

Sharp Range Sensor I2C Adapter prototypes.
The new approach, guided by Atmel application note AVR121 (pdf), works much better than the dedicated ADCs I was using and will form the core of my Sharp I2C adapter, after I make a few usability tweaks and finish the firmware.

Read on to see how I improved performance with oversampling and filtering.

Friday, March 29, 2013

Sharp IR Ranger Noise

Time to look at supply and signal noise on the Sharp IR I2C adapters I'm working on.

You often read how noisy these sensors are. Now you can see it for yourself.

Friday, September 7, 2012

Curve Fitting Sharp IR Rangers

Sharp GP2Y0A710K0F sensor
Here's an example of curve fitting a function to calibrate a Sharp infrared ranger for distance measurement using OpenOffice Calc.

I tried to do this with Gnuplot but found that using Excel or Calc is infinitely easier.

The voltage measurement versus distance for these sensors is non-linear. Here's a plot of the values I experimentally collected from a GP2Y0A710K0F 100-550cm sensor.

curve fitting a sharp IR ranger sensor


This line can be fit using a power function of the form:


Simply edit the plot, right click on one of the data points and select Insert Trend Line...


Then select Power under the Regression Type section then click the Show Equation checkbox.


You now get a trendline with the equation on your plot.

curve fitting a sharp IR ranger sensor


The equation works out to be:

d=3.8631e8 * x^-2.438

In c code you'd do the following (this assumes that your MCU or compiler supports floating point, and that using it won't use up all your memory and processing cycles):


To avoid the cpu- and memory-intensive math on an integer-only MCU, a faster option is to use a lookup table and interpolate between adjacent entries. This could be done with floating point or integer math. Store the range values multiplied by 100 for greater precision in the interpolation.

Also of note, the IR rangers have a usable range. If an object is too far away, the range will be unreliable. Modify the adc function so that it returns a large value like 999 if the adc value is below the usable limit (around 300 in this case).

If the object is too close, the ranger reports distance incorrectly. The best solution is to place the ranger so that objects cannot be closer than the minimum distance.

I also like to set a lower range in software.  In our case, an adc value of 500 or higher should report back the minimum distance of 100cm.

One final note, every sensor is a little different, so it might be a good idea to calibrate each one if precision is an issue.

Sources:
http://www.acroname.com/robotics/info/articles/irlinear/irlinear.html
http://www.basicx.com/Products/robotbook/ir%20curve%20fit.pdf

Friday, June 10, 2011

Fast Robots are Hard to Build

Back when I was delusional enough to think I should try to actually win the 2011 Sparkfun AVC, I realized I needed a fast robot. The 2010 winning time worked out to a speed of something like 5mph while the 2011 winning speeds were a little over 10 mph, about what I was aiming for, myself.

I wonder what the winning speed will be in 2012, assuming no changes to the rules? 20mph? 30? more? Several of the robots entered this year were capable of running at more than 30mph. A Traxxas Rustler can peak out at 35mph with a 7-cell battery and a large pinion gear.  The VXL version with LiPo and brushless motor can run 60mph.

But, speed without proper control simply won't get it done.

One of my big goof-ups in building Pokey, the firefighting robot, was that I didn't carefully think about speed, timing, and sensor range. The faster your robot is, the more carefully you need to deal with timing, speed, physics, and sensor range.  Here's what I learned from that failed experiment.

Sensor Range

My AVC robot was originally designed to be speedy (10-20mph), but the 2011 AVC incorporated giant barrel obstacles to detect and avoid.

Imagine dodging boulders on the highway while driving 100mph--in a fog with 10 foot visibility! No way. Sensor range has to be long enough to allow the robot to react to obstacles.

I considered several long range sensor options.  To illustrate the issues: one Sharp IR ranger has a range extending to 150cm.  The MaxBotics LV-EZ1 sonar extends to 6.45m at best. Let's say the robot runs at 8mph or ~358 cm/s. That means the robot will only have 419ms to react to the IR range but a full 1800ms for sonar.

The processor can react within a few microseconds, but... there's the steering servo to consider. It's comparatively slow, moving through 60 degrees in 220 ms.  That takes up a whopping 52% of the entire reaction time for the IR but only 12% for the sonar. The time required for tires to grip and suspension to load before turning rate increases may also be a factor.

And what if you want to double your speed to 16mph?  Or double again to 32?  And from hands-on experimentation, sonar readings may not be entirely reliable at > 6m.

I considered slowing down when an obstacle is detected. Doing so would buy more time for the robot to react and change course. Someday when the robot can actually detect an obstacle, I'll try this. :)

Bottom line, if you want to go fast, you better look pretty far ahead. In addition to 6m sonar, there are some other long range options. There's a Sharp IR sensor, the R316-GP2Y0A710YK, that can reach out to about 5.5 meters.

I also considered machine vision to extend object detection range out past 6m. As you might imagine, the learning curve in the area of machine vision is pretty steep, so that's why I tried a CMUcam1. It was starting to look promising until I ran into other issues.

If I had won the lotto I would've spent $9095 of my winnings on a MESA 10m SwissRanger. :)  Ah well.

Timing

System timing is pretty important too.  At one point, Pokey's infrared ranger sensors updated 20 times a second for wall-following, while steering correction happened at least an order of magnitude faster. The robot would make a steering correction to avoid a wall, but wouldn't wait for the next sensor update.  It'd turn the wheel but nothing would (seem to) happen!  So it'd turn the wheel harder... bad news.

This time around, I carefully planned out the 'schedule' for sensor update and control output to ensure a sane approach to course correction.  I checked the EZ1 sonar, IR Rangers, and IMU sensors at a 20Hz rate and made steering corrections every 100ms. A vision system could be run at 10-20Hz to match other sensor scheduling. Depending on the module, GPS updates at 1-10Hz. Even at 5Hz, that's a long period of time without data so dead reckoning (heading + odometry) has to fill in.

Also worth considering is the update rate versus vehicle speed and sensor range. Let's say the robot detects something at 6 meters. How many more meters will the robot drive until the next ranger update?  At 20Hz and 8mph, it'll be 1/20th of 358cm = 18cm. How many degrees can the steering servo turn in this time?  Only 0.18 degrees in this case.  The robot better not be impatient waiting for something to change.

To the MCU, it's analogous to a person steering a giant battleship.

Physics

Pokey was heavy and took time to change velocity (direction or speed) but was also fast. By the time it detected the need to stop or turn, it overshot its mark considerably.  One can largely ignore momentum in a slow and/or light robot.

As you know by now, Data Bus is at its core an RC truck. Based on testing (read: playing with it) in front of the house, it has all the expected handling limits of any car.  It can't take a corner at straightaway speed.  It takes time to stop and start.  It takes time to change directions even after the steering is turned.  If you turn the wheel too fast at full speed, the front tires can break free and lose traction.

All these physical limitations and nuances have to be considered in the design if the robot is to run reliably and quickly. I took a simple, two-fold approach. First, handling improvements in the form of suspension tuning and better tires, tricks taken from my SCCA Autocross days.

Second, run the car comfortably below its handling limits. Wider turns and slower speeds, in other words. A slightly more sophisticated approach to reach higher average speeds would involve slowing or braking before turns.

A far more sophisticated approach was taken by Project240, limiting lateral acceleration, tracking to different speeds around the course (e.g., arriving at waypoint turns). Quite impressive.

My simple approach worked in that the robot could take corners pretty sharply, entering them around 10-12mph, despite the sensor mast that degraded handling considerably. But the solution was far from maximizing speed and handling potential.

Results
In the end, despite actual navigation issues, the scheduling of sensor, steering, and gps updates worked well, and the vehicle's handling easily supported the meager demands placed on it. So at least something worked out ok.

Monday, April 18, 2011

Quick and Dirty Compass Calibration in 3d!

No, you don't need funny glasses to read this.

As I continue to wage epic, nightly battles against the army of problems on my robot, Data Bus, I thought I'd share my simplistic approach towards 3d compass calibration that hopefully is "good enough" to get you started.

The problem: incorrect headings from 2- and 3-axis magnetometers not only in absolute terms, but also relative. When the compass is rotated 180 degrees, the resulting heading changes by more or less than 180 degrees.

References:
Using LSM303DLH for a tilt compensated electronic compass (pdf)
Compensating for Tilt, Hard Iron, Soft Iron Effects
http://www.varesano.net/blog/fabio/first-steps-hmc5843-arduino-verify-accuracy-its-results

Types of Correctable Distortion

Two types of correctable distortion affect magnetometers like the ST Microelectronics LSM303DLH and 2-axis Honeywell HMC6532 that I've been experimenting with: hard iron and soft iron distortions.

Hard iron distortions are caused by magnetized objects in a fixed location relative to the mounting point of the compass.  Soft iron distortions are caused by magnetically soft objects or PCB traces that alter the magnetic field around the compass depending on the direction the compass is pointing.

If you plot the x, y and z magnetic readings at various positions in a 3d scatter plot, in a perfect world you'd see a sphere surface mapped out by the points.

Hard iron distortions will offset the sphere along the x, y and z axes. Soft iron distortions turn the sphere into a tilted ellipsoid.

There's also scale distortion. All the axes should have equal sensitivity but they rarely, if ever, do. Correcting this is a question of normalizing the reported results of each axis.

Simpleton Calibration

The ST Datasheet explains the mathematical equations and approaches required to identify hard and soft iron distortions and corrections. I'm still coming up to speed on matrix math, linear algebra, etc., and time is running out very quickly so I decided to try a quick and dirty (read: simpleton) approach.

To wit: simply taking maximum and minimum values for each axis to compute offset and scale. That's assuming no significant soft iron distortion.

To verify that assumption, I needed to visualize the measurements from my LSM303DLH breakout from Pololu. I modifed an example program on mbed.org to print out X, Y and Z readings in a form that could be plotted easily by gnuplot.

I manually spun the compass around for a few minutes to collect a reasonable number of data points then I plotted the results in a 3d scatter plot (in gnuplot: splot "file.csv") I don't have a handy way to create an anaglyph so let me show you the X-Y, X-Z and Y-Z plots.



You can see that there is a pretty big x-axis offset and obvious Y scale distortion. I couldn't tell by eyeballing if the Y or Z axes were offset or mis-scaled.

A pretty simple perl script calculated the answers for me:

Xmax =  98.00  Xmin = -157.00 Xoff = -29.50
Ymax = 124.00  Ymin = -123.00 Yoff =   0.50
Zmax = 101.00  Zmin = -109.00 Zoff =  -4.00
Max  = 127.50  Min  = -127.50
Xsca =   1.00  Ysca =   1.03  Zsca =   1.21

------------8<--- cut here ---8<------------
#!/usr/bin/perl

open(FIN, "<$ARGV[0]") || die "can't open: $ARGV[0]";

$label{0} = "X";
$label{1} = "Y";
$label{2} = "Z";

$count = 0;
while () {

    s/^\s+//;
    @data = split(/\s+/);
    for ($i = 0; $i < 3; $i++) {
        $sum[$i] += $data[$i];
        $max[$i] = $data[$i] if ($data[$i] > $max[$i]);
        $min[$i] = $data[$i] if ($data[$i] < $min[$i]);
    }
    $count++;
}
close FIN;

for ($i = 0; $i < 3; $i++) {
    $med[$i] = ($max[$i]+$min[$i])/2.0;
    printf STDERR "%smax = %6.2f  %smin = %6.2f %soff = %6.2f\n",
      $label{$i}, $max[$i],
      $label{$i}, $min[$i],
      $label{$i}, $med[$i];
    $max[$i] -= $med[$i];
    $min[$i] -= $med[$i];
    $max = $max[$i] if ($max[$i] > $max);

    $min = $min[$i] if ($min[$i] < $min);
}

printf STDERR "Max  = %6.2f  Min  = %6.2f\n", $max, $min;

for ($i = 0; $i < 3; $i++) {
    $scale[$i]=$max/$max[$i];
    printf STDERR "%ssca = %6.2f  ", $label{$i}, $max/$max[$i];
}
printf STDERR "\n";

Results

The C++ library that I'm writing/porting/adapting on mbed.org for the LSM303DLH features two member functions, setOffset() and setScale() to which I can provide the offset and scale numbers from above.

A simple test program exercises the library and tests the scale and offset values.  Rough tests show improved results in heading as well as when swinging the compass 180 degrees.  The true test will happen when I mount the new compass onto my robot and collect data. Hopefully the calibration is close enough to significantly improve dead reckoning position calculations.

Tuesday, April 5, 2011

Interfacing MaxSonar EZ1 with mbed

Libby carefully inspects my wiring...
Introduction

MaxBotics makes the LV-EZ series of sonar rangers. The devices are aptly named and provide very flexible interfaces: TTL serial, analog, and pulse width to represent distance. The rangers can be chained together so that they read and output results sequentially. Pretty neato.

Several weeks ago, while facing problems getting my new GPS to work, I decided to try something easier: working with a recently purchased MaxSonar EZ1 ranger (datasheet, PDF).

MCU of choice for the interfacing project was the ARM Cortex M3-based mbed.  The mbed is the brains on board my AVC competition robot Data Bus.

Wiring

One first has to solder header pins onto the ranger module. Not a big deal. The pins are clearly labeled GND, +5, TX, RX, AN, PW, BW. The module can take any voltage from 2.5-5.5V so standard 3.3V and 5.0V will work. My mbed runs on 3.3V.  The EZ1 datasheet provides scaling information for 3.3V and 5.0V supplies.

For the mbed, connect VOUT (3.3V, pin 40) to +5, GND (pin 1) to GND, p20 (pin 20) to AN. That's all there is to it on the hardware side.

Connecting an mbed to the EZ1 sonar

Software

Initially I tried reading serial data out of the EZ1 but for some reason I was not getting the output indicated in the datasheet. So I thought I'd try reading the analog values.

Use the mbed library's AnalogIn API, read the analog value as a float. Then convert the analog reading to voltage, and finally convert to inches using the scaling factor provided in the datasheet (6.4mV/inch for 3.3V supply)

Source code on mbed.org


AnalogIn ain(p20);
Serial pc(USBTX, USBRX); // tx, rx

int main() {
    float adc, volts, inches;
    int feet, in;
    
    pc.baud(115200);
    
    while (1){
        adc = ain.read();           // read analog as a float
        volts = adc * 3.3;          // convert to volts
        inches = volts / 0.0064;    // 3.3V supply: ~6.4mV per inch
        feet = (int) inches / 12;   // inches to feet (trunc)
        in = (int) inches % 12;     // remainder: in(ches)
        
        pc.printf("%8.2f adc %8.2fV %8.2f in %d'%d\"\n", 
                  adc, volts, inches, feet, in);

        wait(0.05);                 // ~20Hz update rate ; note we aren't
                                    // truly synchronized ...   
    }
}

Moving the sonar around the room, pointing at various objects:

0.11 adc     0.35V    54.77 in 4'6"
    0.11 adc     0.35V    54.77 in 4'6"
    0.03 adc     0.10V    15.36 in 1'3"
    0.05 adc     0.17V    26.32 in 2'2"
    0.02 adc     0.07V    10.58 in 0'10"
    0.07 adc     0.23V    36.39 in 3'0"
    0.02 adc     0.07V    10.58 in 0'10"
    0.03 adc     0.11V    17.63 in 1'5"
    0.02 adc     0.06V     9.19 in 0'9"
    0.02 adc     0.06V     9.07 in 0'9"
    0.01 adc     0.05V     7.43 in 0'7"

Testing and LCD

To test the unit outside, I hooked up a serial-enabled LCD to the mbed, and transmitted the foot and inch data to the LCD. The LCD uses a common HD44780 controller.(For wiring, see the wiring diagram above)

Send 0xFE to indicate a command followed by 0x01 to clear the screen. To position the cursor, send 0xFE followed by 0x80|n where n is the cell number (0x00 is the top left, 0x40 is row 2, left, 0x10 is row 3, left and 0x50 is row 4 left.

I added the following to the code above to print out feet and inches.

        lcd.putc(254);
        lcd.putc(0x80 | 0x00);
        lcd.printf("%2d'%2d\"             ", feet, in);

I powered the mbed by connecting a 9V batter on VIN (pin 2) and GND (pin 1) (see wiring diagram above)

The test session was intended to determine the feasibility of using the sonar on my AVC robot for detecting barrels, people, cars, and other UGVs.

Results

The maximum detection distance appears to be approximately 22 feet (6.7m). The device can detect a variety of objects, narrow to wide: my Jeep Grand Wagoneer is huge and hard to miss, my mailbox is much smaller but the sonar found it too.

The ranger will sit only a few inches off the ground and I found that the beam pattern causes echoes from the ground in front of the ranger. That's because the propagation pattern expands both vertically and horizontally.

A week or two ago, I ordered a parabolic reflector for the Parallax Ping sonar that may help to reduce the vertical dispersion while increasing the range of the EZ1.

Friday, March 25, 2011

GPS, Gyro, and Compass Errors

Continuing to flail, here. I still don't have reliable navigation working and I'm a month away. That sounds like a lot of time but it's a flash in the pan. I'm very nearly screwed.

My recent test runs suggest various possible errors in the navigation sensors. Here's what I've come up with so far based on two test runs last night and some offline analysis of the raw sensor data.

GPS Errors

I equipped Data Bus with the iGPS-500 and collected data for both GPS units simultaneously. I thought because the Locosys was getting a fix that it was to be trusted.

But it's been unreliable. Last night the iGPS-500 was the only one of the two modules getting a fix... inside the house!  A multi-satellite fix, at that.  Long story short the iGPS-500 frequently seems to produce better data.

Below, the iGPS is pictured in blue, the Locosys LS20031 in green. It's all over the map. In my other test run,  the iGPS did ok, but went squirrely in the upper right near the big tree. The Locosys looked better in terms of shape but was offset about 50 feet.


The dark red path above is the dead reckoning plot using compass. The lighter red is the dead reckoning plot produced offline with a perl script, using raw gyro data with a hand-tuned gyro bias. It's easily the closest to reality.

Compass Error

I wanted to see if the compass simply had a bias so I tried correcting and replotting for 20 degrees then 25 degrees.

The pink path below is the original on-board dead reckoning plot. The green is the same plot rotated 20 degrees and the blue, 25 degrees. The red plot is the hand-tuned gyro-based dead reckoning.


Clearly the issue isn't a simple bias.  The compass heading does not seem to change with the correct proportion during turns.

Part of that may be due to the tilt from the convex surface of the road throwing off the heading. Possibly also because the road itself slopes slightly downhill. I don't really know for sure. I need to...

  • recalibrate the compass with the on-board calibration routine
  • gather data a few more times from a site with flat ground
  • experiment plotting my prior 3 test runs
  • if the error is still there, determine the nature of the error

If the error isn't due to the sloping street, and if I can describe the error in state space form along with the heading and heading rate (gyro), then I 'should' be able to build a Kalman Filter to deal with it.

Gyro Error

I had been trusting the compass most. It looked so good on a graph. But I was wrong. Using it to plot dead reckoning shows the real truth. I hadn't really trusted the gyro until now.

Experimenting with different bias values, the gyro provides the best dead reckoning information and in fact the best positioning information. It appears that the biggest contributor of error is the bias (or drift of the bias). Which suggests that if could only find a way to calibrate for the bias, I'd have reliable navigation.

The plot shows dead reckoning plots with different bias values. The purple plot shows a previously measured bias of 2027 (that's the raw, 12-bit ADC conversion value, approximately equal to 2.4496V). The blue corresponds to 2028 (2.4508V), green to 2029 (2.4520V). So I tried 2028.5 (2.4514V), the red, and I think that's probably the closest match.


I can't just take an average at standstill because I tried averaging the first several seconds of the data before the robot started moving. The resulting plot was off quite a bit from reality. So... is there a bias that comes into play when in motion, or during turns? Or was it due to temperature effects?

At worst, maybe I can make test runs and hand calibrate bias until the plots look right, and somehow correlate that to temperature. What super sucks is that we're talking about nailing the bias down to the nearest half a millivolt.

Dead Reckoning Integration Error

All the off-line plots were done by integrating heading and distance measurements every 20ms intervals, an order of magnitude more often than the robot's onboard code. Yet, the difference between the plots is pretty negligible. So integration error doesn't seem to play in.


I wonder if there's some distance error due to using straight line interpolation to represent the curved vehicle path. If that error was large, I'd expect to see a bigger discrepancy in plot length visible at the end of the path in the lower left part of the pic above. Instead, the length of the two plots are within 10cm of each other.

Kalman Filtering, Fusion

Maybe this is inexperience talking but at this point I don't see how Kalman filtering can possibly make use of the massive compass and gps errors to stand any chance of correcting for bias on the gyro and producing reliable heading information. I would expect a Kalman filter would basically tune out the measurements from GPS and compass to the point of ignoring them.


Oh yeah, almost forgot, I still have to dodge barrels.

And I haven't done squat with obstacle detection let alone avoidance.

Yup, I'm Screwed

Now I'm struggling to get a reliable 5V power supply for the robot that can deal with massive voltage sags from the BEC under heavy acceleration.

And as of last night, I'm fighting with that stupid, buggy RC switch board again. It switches off control of the MCU but doesn't switch on transmitter control. Great.

And on top of everything else, my espresso machine has been broken for a week and my efforts to revive it last night were in vain.

I'm just not winning right now.

But hey, I'm not complaining. Ok, maybe I am.

Meanwhile, you may wonder why I'm bothering to write all this when I should be working on the robot. Believe me I have been working very hard on the 'bot. These posts help me to collect my thoughts. Also, I feel more accountable to get busy when the time permits.

Wednesday, March 9, 2011

AVC Bot: Adding a Compass

From previous posts you probably deduced that Data Bus, my 2011 Sparkfun AVC entry, now has a compass. I forgot to write about its selection and installation.

Experience with gyro data collection led me to the conclusion that a compass would be really helpful with vehicle navigation. Why?

A gyro tends to have drift that can be hard to predict. GPS updates come too infrequently and are too unreliable as a reference source. A compass offers frequent updates (20Hz  in this case) and a reliable source of steady state heading information.

Selecting a Compass

Sparkfun HMC6352 breakout
Having spent a fairly big pile of money on this robot so far and with my hobby fund dwindling rapidly, a high end compass was out of the question.

Two cheap options presented themselves, a simple Honeywell HMC6352 two-axis magnetometer/compass and a 3-axis magnetometer from Sparkfun.

I had (and still have) my hands full with other mathematical problems that are smacking me to the curb, so having to figure out an algorithm for a tilt-compensated compass was very unappealing.

Plus, the robot is going to be on flat ground at all times and body roll should be minimal. A tilt-compensating compass would be nice but the robot can live without one.

The Honeywell compass won out. It's ridiculously simple to use.

Interfacing the Compass to mbed

The compass is an I2C based device. At it's simplest, you query and it tells you your magnetic heading. To make matters easier, someone had already written a nice, full-function, mbed driver library. So I used it. Like this:

#include "HMC6352.h"

HMC6352 compass(p28, p27);              // Driver for compass

int main() {
    // Initialize compass; continuous mode, 
    // periodic set/reset, 20Hz measurement rate.
    compass.setOpMode(HMC6352_CONTINUOUS, 1, 20);

    // read compass, convert to float
    compassHdg = compass.sample() / 10.0;       

    // Correct for local declination
    compassHdg -= declination;

    // Clamp to 0-360
    if (compassHdg < 0.0) compassHdg += 360;    

    // ...
}

Mounting

Compasses are sensitive to iron and current. I mounted mine as far away from screws, the main battery cables, and the steering servo as possible. In fact, I rotated the battery 180° so that the main cables exit the back of the robot, far, far away from the compass.

The compass is currently affixed with double-sided tape to a 2" high "bridge" made of brass that's mounted to the main lexan base. It's final location and mounting scheme are still in the works.

Magnetic Declination

If you've played with compasses at all, you probably know that the error between magnetic and true heading varies all over the globe because the Earth's magnetic field isn't perfectly uniform. The error is called magnetic declination.

If only one could figure out how much the error was at any geographic location... but wait: you can!

Not only is there an online calculator available, but also Geomagix, a downloadable trial application for Windows.

There's more. A magnetic survey was conducted just a year ago in 2010, meaning the utmost in accurate information. How cool is that?

Download the Geomagix code and the 2010 cof file and you're good to go.  Declination at my house: +8.91305°

Now I can configure the robot with the correct declination at the Sparkfun Building or wherever else Data Bus runs.

Tuesday, March 8, 2011

Precision Gyro Calibration

Updated version for 2012

Heading accuracy is going to be kind of important and in my data captures thus far, there is a slight discrepancy between the compass and gyro readings.

Notice the slight discrepancy after long heading changes.
It's possible that the gyro is experiencing some drift when held at a relatively steady rotational rate. It does track the compass heading well in the long run. But I wanted to see if the scale factor was correct or not.

So I decided to make use of my high precision rate gyro calibrator.

High Precision Gyro Rate Calibrator
This is a 1970's Realistic Lab-400 direct drive turntable (it's a surprisingly good-sounding deck, I might add). I would've used my Technics SL-QD33 quartz drive table for greater precision but it's not in such an accessible location.

The Theory

It's really pretty simple. We're talking about a straight line function that, given a voltage, returns a heading rate of change.



Remember y-intercept form?





Where y is heading rate of change r, x is voltage v, m is actually the inverse of the scale factor SF in degrees/sec, and b is the y-intercept aka the gyro's null value N.

We find the slope (m aka scale factor) by getting two or more x, y points. The turntable can spin at 45 rpm (270°/sec) and 33-1/3 rpm (200°/sec)

Once we know m, we can solve for b. This is all high school algebra stuff. Which is probably why it took me a bit for the memories to come flooding back (hey, it's probably been 25 years! Yes, I am that old).

So let's get our data points.

Data Collection

To get the null value I collected data with the robot perfectly at rest.  To get the 33-1/3 and 45 rpm data, I placed the entire robot, all 5 lbs of it, on a large, empty, inverted can on top of the platter to raise the robot up above where it might rip off my turntable's arm while spinning.

After removing the headshell with my precious cartridge and stylus, I balanced the robot and turned it on to collect data.

But wait. How do I know if the built-in strobe light is precise enough? I don't. And it matters.

I hooked up a high intensity white LED to a breadboard Arduino, and coded up a 50Hz blinker program (tuned with DMM to 3 significant digits).with a 25% duty cycle. The platter is marked for 50Hz and 60Hz.


/* Blinking LED
 * ------------
 *
 * Blinks an LED at 50Hz e.g., for turntable calibration
 */

int ledPin = 8;

void setup()
{
  pinMode(ledPin, OUTPUT);      // sets the digital pin as output
}

void loop()
{
  digitalWrite(ledPin, HIGH);   // sets the LED on
  delayMicroseconds(4960);      // Tests show ~40usec for code exec
  digitalWrite(ledPin, LOW);    // sets the LED off
  delayMicroseconds(15000);     // total, approx 50Hz
}

Turns out the strobe was off by a percent or two. (Hm, I may have to modify my turntable with a better calibrated strobe...)

I manually spun up the platter and engaged the motor. Since it's a big, powerful motor, it had no problem spinning a 5 lb weight for a few minutes.

Data Analysis

Here are the gyro plots (click to enlarge):


Obviously there's some noise in the signals. I wrote a quick perl script to average the results for the stationary (0°/sec) , 45 rpm (270°/sec) and 33.33 rpm (200°/sec) data captures.

#!/usr/bin/perl

$sum = 0.0;
$count = 0;

$GYRO=2;

while (<>) {

  s/[\r\n]+//g;
  s/^\s+//;

  @data = split(/\s+/);

  if ($data[$GYRO] ne 'NaN') {
    $sum += $data[$GYRO];
    $count++;
  }

}

print "Sum: $sum  Count $count  Avg ";
print $sum/$count if ($count != 0);
print "\n";

The output:
  • 33.3: Sum: 6743185  Count 2247  Avg 3000.97240765465
  • 45: Sum: 10857452  Count 3247  Avg 3343.84108407761
  • 0: Sum: 10281588  Count 5072  Avg 2027.12697160883
The ADC used for the gyro is 12 bit and the scale range is 0-5V. To convert the reading to a voltage, multiply by 5.0 ... actually 4.95V according to the DMM







Ok! We now have our points, (2.445V, 0°/sec), (4.041V, 270°/sec) and (3.627V, 200°/sec) and we can find the slope aka scale factor.





Assuming I did all the measurements right, the actual scale factor of the device varies slightly from the 6 mV/°/sec specified in the datasheet. Best not assume...

Double Checking

What if I double check the slope by solving for it using other points? I get 5.886mV (-0.476%) and 5.893 mV/°/sec (-0.353%) versus the 5.91 mV/°/sec.

Not bad. Some of the error may be due to doing null data collection the next day. Perhaps there was some temperature effect.

If the gyro reading had been 2.444V, the scale factors would've been within 0.015% of each other.

Another double check is to solve for the predicted null value from the first two points (45 and 33.3 rpm)







The predicted null voltage agrees with the measured voltage to 4 significant digits.

Eliminating Voltage Errors!

EDIT: Coming back to this a few weeks later, and re-reading the datasheet, the null and sensitivity are proportional to voltage.

The datasheet recommends using a ratiometric ADC. Simply, use the same supply for the gyro and the ADC's analog voltage reference. That's just what I did.

Therefore, I don't need to include supply voltage in the calculations above!  Instead of calculating scale factor in terms of volts/°/sec, calculate it in terms of LSBs/°/sec.

That is, find SF in terms of the raw ADC conversion value. Calculate slope from raw ADC values (in bold below).



Then, in code when it comes time to convert to °/sec, calculate using the bias (null) and SF in raw ADC value.


float gyroRate(unsigned int adc)
{
  return ((float) adc - 4027) / 4.898;
}


Other Considerations

Temperature will no doubt affect null and sensitivity somewhat. I'd need to calibrate again for at least two different temperatures to calculate the influence of temperature. I need to see if the additional accuracy is needed by the system or not.

To further reduce error, I could improve the numerical integration approach.

Integrating the Gyro signal more frequently than 20Hz should reduce error accumulation over time.

The integration function can be improved. The simple approach that I'm currently taking is to simply add the fixed gyro voltage to a running sum.  That's a 0th order interpolation function also known as the rectangle (or midpoint) rule.

Rectangle rule, from Wikipedia
A 1st order interpolation function, the trapezoidal rule, would reduce error and is not computationally intensive.

Trapezoidal rule, from Wikipedia
There are other interpolation functions. But I can't afford to over-engineer the solution given how much more work I have to do in other areas.

One of my readers, (who has become a trusted adviser!) and blog author of the excellent Guy NXT Door blog, suggests the numerical integration error is negligible compared to other errors. He and I have been working on IMU issues at the same time and comparing notes.

Anyway, no sense in over-engineering with so much else to work on!

Friday, February 18, 2011

AVC Bot: Gyro Noise and Accuracy

Last we left off, the first data captures from the LISY300AL rate gyro on my RC truck robot were really noisy!


See? I can't use that to navigate around the Sparkfun building for the Autonomous Vehicle Competition.

Accuracy
I need accurate heading data.  According to basic trigonometry, if E is the cross-track error in feet and L is the length of one side of the Sparkfun building, then heading precision, theta, is given by:


Each long side of the AVC course is about 73m and the short sides are about 56m, for a total of about 260m. A maximum error of 5m over that distance requires heading accuracy of about 1°. So an accurate, low noise gyro is going to be critical to success.

Finding the Noise Source
Here's a plot (generated by gnuplot this time) showing detail between 10 and 20 seconds (click to see the full sized image)


That's really bad.

So what was the cause? Mechnical vibration or EM noise?

A quick experiment indicated that mechanical vibration definitely played a role. I put the RC truck in the passenger's seat of my Grand Wagoneer, drove around the block, and obtained a much smoother gyro plot, below.


One more experiment was needed to isolate the effects, if any, of EM noise from the motor. I set the truck up on a box and ran the motor and steering servo.

But I wasn't able to isolate the EM effects after all because the truck chassis vibrated all over the place.

I ran the motor twice, then turned the steering back and forth lock to lock. Then slowly turned the servo back and forth.


Vibrating, unbalanced rear wheels and vibrations from the steering wheels whisking back and forth clearly set off the gyro.

A Better Gyro
I could have experimented with the rear wheels off, but I was pretty sure vibration was the major contributor of the noise. I was also pretty sure that the LISY300AL is overly sensitive to vibration.


ADXRS610 Gyro
So I bought an Analog Devices ADXRS610 rate gyro breakout board from Sparkfun (discontinued shortly after my purchase). The datasheet claims high vibration rejection. Just what I needed.

I wired it up, mounted it between two foam pads for further isolation, and performed the servo / motor experiment again. Here's the plot.

Much better! I powered up the RC truck and after about 6 seconds, I moved it by hand to it's test stand (see "Moved the truck" above between the 6-12 second timeframe).

After that I ran the servo and the motor a few times. The truck vibrated around and changed its heading slightly. I pushed it back into place. You can see these subtle events on the plot.

What you can't see are the massive output swings like the LISY300AL exhibited. Cool! I think this sensor is a winner. High vibration rejection indeed!

Real World Driving Test
Next, I tested to see how it performed in a real driving test. The results were promising. Here's a plot of a subset of the data from an extended test run.

It's a little noisy, sure, but you can easily discern heading changes from periods with a static heading.  Were the readings anywhere close to reality?

Verifying Heading With GPS
To find out, I compared gyro heading to GPS heading.  I tweaked some Perl code to convert the raw gyro readings to heading and plotted that against GPS heading data from the test run.

I initialized the gyro heading to align initially to the gps values and then subtracted the fix age time from the gps heading readings to time align both plots.


You'll have to click the plot to see a larger image and make out the relevant detail.

Heading data from the gyro (red) is fairly smooth and it matches the general shape of the gps plot (green) which is great news, even if the scale and absolute heading are off.

This result suggests that the gyro readings may get quite a bit closer to the GPS readings by calibrating the gyro for temperature, null point and scale.

In the end, the noise issue was specific to the LISY300AL gyro and seems to be alleviated.

Friday, January 14, 2011

Interfacing an Arduino with a Rate Gyro

After breadboarding an Arduino for the Sparkfun AVC RC truck robot (somebody help me name this thing) the time was ripe to tackle the LISY300AL rate gyro breakout board I bought from Sparkfun.

My hope is that this device or something like it can be used to help Pokey negotiate 90 degree turns in the firefighting maze and to help the AVC RC Truck bot execute turns and hold a heading.

The breakout board is simple.  There's 3.3V power, GND, analog voltage out, then two logic pins, one for powerdown (PD) and one for self test (ST).  Tie PD and ST to GND, and feed the thing power, then read the output.

I'd previously made a DIY, surface mount (SMT/SMD) 3.3V regulator expressly to power a rate gyro. That gyro is dead now because I accidentally fed it 9V. Oops. I was more careful this time with the new gyro.

I hooked it up to the scope and after realizing my ground line was actually hooked to +5V, I got a proper ~1.65V reading on the scope and on my DMM.

It was time to get an Arduino to read values and spit them out over serial. My breadboard Arduino came in handy, although I wished I'd used a longer breadboard as it's pretty cramped.  I wrote up some simple code to convert analog data and spit it back out over serial.


int gyroPin = 3;
float val;
float sum;

void setup() {
  Serial.begin(38400);
  delay(1000); // allow setup time for gyro
  // try to calibrate to an average
  for (i=0; i < 2048; i++) {
    delay(5);
    sum += (float) analogRead(gyroPin);
  }
  val = sum / 2048;
  sum = 0;
}

void loop() {
  sum += val - (float) analogRead(gyroPin);
  Serial.println(sum, DEC);
  delay(5);
}

The result was ... a mess.  A sine wave output with occasional, negative spikes.  How the heck was I supposed to use this data to steer a car straight?  I played around with capacitors, software averaging, all kinds of stuff, and none of it was a match for this gruesome analog signal.


Then I realized I had left PD and ST floating. I tried grounding them and...


Oh. So that was the problem. *sigh*

Now the graph holds pretty tightly to an average value with little variance.  That's some usable data. The other problem is that Vref for the ADC is 5V and the gyro outputs a max of 3.3V, so I'm losing some resolution.

Calibrating the sensor and dealing with drift are the next problems. I need to find the steady state middle point voltage put out by the sensor.  Then I can read voltage values at a fixed period, convert the voltage value to degrees/sec and integrate (add them up) to find out the current heading. Sounds easy on paper.

After some playing around to get familiar with the device and its output over time, my first experiment with the rate gyro was an attempt to study the noise level from the device while it is sitting still.

I set up simple code to track the change in output between each set of 64 samples (with 2ms delay between each).

I wrote a small perl program to generate a histogram of values and calculate the mean, variance, and standard deviation.


#!/usr/bin/perl

$i = 0;
while (<>) {
  s/[\r\n\s]//g;

  $HIST{$_}++;

  $sum += $_;
  $i++;
}
$count = $i;

$avg = $sum / $i;

$var = 0;
for ($i=-40; $i <= 40; $i++) {
  print $i, ",", $HIST{$i}, "\n";
  $var += $HIST{$i}/$count * ($i - $avg)^2;
}

$stddev = sqrt($var);

print "Average, $avg\nVariance, $var\nStdDev, $stddev\n";

Then I graphed the histogram in the OpenOffice Calc (spreadsheet) and came up with this fairly Gaussian-looking distribution after almost 22,000 samples.


I got a mean of 411.4e-6 (which seems to be drifting a tiny bit, by the way), Var = 162 and StdDev = 12.7280. Aside from being sort of interesting and instructive for simple algorithms, I am hoping that this modeling of the sensor's uncertainty will be useful in developing a Kalman Filter when it comes time to fuse multiple sensors to get more accurate heading estimates.

I'm just starting to dig into the Kalman Filter topic but it seems that some knowledge of the noise statistics of the sensor might be useful, although manufacturers put these in their datasheets. Essentially the Kalman filter is intended to filter out measurement noise to estimate some 'true' value, like heading.

It's a bit involved and I don't pretend to understand it yet, but I gather it's sort of an iterative implementation of least squares curve fitting and is related to Bayesian statistics wherein a new but statistically noisy input is used to refine a prior estimate.

As far as I can tell you take a measurement at each fixed time step, predict where the system will be at the next time step, and use the next measurement to refine the estimate and to make a better prediction for next time step.

All the cool roboticists are doing it.

So, I'm in the middle of reading a number of papers and a book, Fundamentals of Kalman Filtering: A Practical Approach (Zarchan). I hope to know more soon.

Rest assured if I manage to learn anything, I'll share it with you.

Thursday, November 25, 2010

Ideas for cheap Laser Rangfinder

The bounty for hacking the XV-11's $30 LIDAR, teardown talk on Sparkfun and hacking thread on Trossen Robotics prodded me into doing some quick experiments.

I've been pondering, but not actually doing anything with laser-based object detection for a year now, for both the Trinity-style firefighting competition and the Sparkfun AVC.

The XV-11 unit looks to me like a camera and a laser pointer spun by a small CD motor (details of the system in this white paper). Picture from Sparkfun below. How hard would it be to hack something like this together?



Awhile ago I bought a line-generating laser to identify obstacles. Powered with a LM317 generating the required 3.0V, the laser generates a pretty nice line once you focus it.



Based on ideas I found online, one places the laser at some point low on a robot, with the laser's axis and line parallel to the ground, while the camera is placed some distance above and pointed slightly down, so that parallax can be leveraged.

A horizontal line is generated lower in the camera image for nearby objects, and higher in the image for far away objects.

Since I'm using Pokey's grayscale camera, rather than a color camera, I took pictures with and without a photographic red filter to see if that would help distinguish the laser reflection.

Laser line, red filter. Note height of line on cup, laser printer.

Laser line, no red filter

One could probably write code to detect the line even in a grayscale image although this would be substantially easier with an IR laser and IR filter. Or with a color camera.

Here's a color picture of the test area, my desk. Old HP laser printer to the left, McD's cup, front and center, etc.

As for the LIDAR concept, put a camera next to a laser pointer, align them to leverage horizontal parallax so one can measure distance based on the position of the dot.

I was curious to see what a tiny red laser pointer dot would look like on the grayscale camera. The dot is pretty tiny at about 8' distance (see red arrows).

Distant dot (red arrow), no red filter

Distant dot, with red filter

Maybe too tiny to reliably detect, although with the LIDAR one can constrain the search area vertically and possibly horizontally.

And constrain the search algorithm to square and circular shapes.

Come to think of it the code could correlate dot size with position for added robustness.

And actually the red filter seems to make the laser dot the brightest thing in the image, once the exposure and gain is set properly.

Nearby dot, no red filter

Nearby dot with red filter
Once code and camera can find the dot, the system has to do it quickly so it can be mounted on something spinny.

Doing a 360 degree sweep at 10Hz like the XV-11 means 3600fps which suggests it is using a much faster optical device than Pokey's camera or anything like it. I am guessing Neato is using a linescan sensor. You also need a fast processor. Speculation on the Sparkfun teardown blog post points to a Texas Instruments DSP device, TMS320C2801 at the heart of the XV-11 LIDAR's image processing.

I hit a wall at 3fps with an Arduino running Pokey's camera. I'm in the middle of interfacing with a Propeller but at absolute best I can get from that camera is 30fps. Nowhere close to fast enough for LIDAR. But workable with the laser line sensor concept. But all that's best left to another blog post or two.

I plan to just wait for someone to hack the XV-11 LIDAR and wait for spare parts to come out of Neato and hope I can afford the darn thing or build it myself for $30 as promised.