Compass Calibration

I’ve made quite a bit of progress on the robomagellan robot recently. Over the past week I’ve installed the Realsense D435 camera, setup a URDF, and made some serious progress towards global localization.

Absolute Heading Reference

I’m using an EKF from the robot_localization package for outdoor localization. This package can merge the wheel odometry, IMU and GPS data together into a cohesive global pose and odometry. I’ll post more details about how I’m using robot_localization in a future post once I finish tuning things.

Since GPS data doesn’t include heading, robot_localization requires an IMU or odometry source with absolute heading information. While some higher end IMUs may put this information out directly, none of the IMUs I have offer a reliable absolute heading.

Today’s IMUs typically include a three-axis accelerometer, gyro and magnetometer. Most robotics people are pretty familiar with gyros and accelerometers, but less so with magnetometers, especially if they have primarily worked on indoor mobile robots. Magnetometers measure the geomagnetic field. If there were no disturbances and our magnetometer was accurately calibrated, then plotting the magnitude vector while rotating the sensor around would form a sphere with a radius equal to the local field strength. A projection of the magnitude vector into the horizontal plane gives us the equivelent of a compass bearing.

The imu_filter_madgwick ROS package is used to merge the accelerometer, gyro, and magnetometer readings into an orientation estimate which we can feed into the EKF. Unfortunately, the geomagnetic field is not very strong so calibration is quite important. This filter is able to take in a magnetometer bias, which corresponds to what is often called the hard iron offset 1. To get this bias vector I wrote a magnetometer_calibration node in the robot_calibration package.

This calibration node rotates the robot around slowly while recording the magnetometer values. It then tries to fit them to a sphere. It outputs the center of that sphere (which corresponds to our bias or hard iron offset). It doesn’t currently do the more complex soft iron calibration since there isn’t a good way to merge that into the pipeline anyways. I’m planning to implement both the soft iron calibration, and the extensions to the filter node so it can use the soft iron calibration soon.

The output of the node looks something like this:

$ rosrun robot_calibration magnetometer_calibration
[ INFO]: Subscribing to /imu/mag
[ INFO]: Publishing to /cmd_vel
[ INFO]: Rotating robot for 60 seconds.
[ INFO]: Done rotating robot.
[ INFO]: Saving bagfile with 1337 poses.
[ INFO]: Ceres Solver Report: <truncated>
[ INFO]: mag_bias_x: 0.042075341
[ INFO]: mag_bias_y: -0.175981837
[ INFO]: mag_bias_z: -1.057499311

The mag_bias parameters can then be passed to the imu_filter_madgwick as parameters (along with use_mag parameter being set to true). I still need to add documentation on the new magnetometer calibration node, but the basics are:

  • Topics (remap these to your actual topics):
    • /imu/mag - the magnetometer data. Should be a sensor_msgs/MagneticField message.
    • /cmd_vel - the commands to the mobile base. geometry_msgs/Twist.
  • Parameters
    • rotation_velocity - this is the angular velocity to command. Should be relatively low, but has to be high enough that your robot actually rotates. Defaults to 0.2 rad/s.
    • rotation_duration - the robot has to make at least one full revolution in place. If your robot is really slow, this might need to be longer. Defaults to 60 seconds.

The node also has some other modes, such as manual rotation (in case your IMU isn’t actually mounted in a robot yet). Right now the best source of documentation is the code itself. I plan to add documentation to the robot_calibration README later this week and then get a new release pushed to debians. UPDATE: documentation is now in the README.

Next Steps

I’m still working on tuning the EKF parameters. I’ve also started to write some GPS-waypoint navigation code. Both of these topics will be the subject of upcoming posts.

A New Website

Earlier this month I started blogging again. My blog was hosted on blogger.com, which was a pretty advanced platform in 2008 when I started blogging. Now it is pretty dated. I had a hell of a time trying to put any code blocks in the blog - which for a software engineer is a real problem.

So this is the new blog. I’ve ported most of the old content over, except for a few posts which were completely irrelevant (for example, a post which consists of nothing but a link to a Kickstarter project for Mech Warfare, both of which are since over).

Porting over all of these posts was actually a somewhat fun exercise, I had forgotten just how much had been posted to this blog over the years. Here’s a few highlights:

In setting up this site, I decided not to bother with a comments section, as the comments on the old site have generally been a wasteland for some time. If you come across an issue or are looking for more information, reach out to me by either filing a ticket against this site on GitHub or by messaging me on Twitter.

I’ve also updated the domain name to reflect that I might actually post some non-robotics things here in the future. In that same spirit, most of the existing posts are now tagged with the new robots tag. I’ve had this domain name for a while now, happy to finally put it to use.

Welcome to Robot & Chisel.

Robomagellan Update

This is day 6 of my National Robotics Week blog marathon. See the full set of posts here.

It is really starting to feel like spring here in New Hampshire, so I've been reviving my Robomagellan robot:

The current robot (not shown: Intel D435 sensor)

If you're not familiar with Robomagellan, here is the wikipedia description:
Robomagellan was created by the Seattle Robotics Society and is a small scale autonomous vehicle race in which robots navigate between predefined start and finish points. The start and finish points are usually represented as GPS coordinates and marked by orange traffic cones. In most versions of the competition there are also optional waypoints that the robot can navigate to in order to earn bonus points. The race is usually conducted on mixed pedestrian terrain which can include obstacles such as park benches, curbs, trees, bushes, hills, people, etc..
Unfortunately, there are not many Robomagellan contests happening anymore - but this platform is still good for me to work on some outdoor navigation. I actually started building this robot in 2012 when Robomagellan was quite popular. The robot was briefly worked on in 2014 and 2018. The GitHub contributions view seems to tell this story quite well:

Contribution timeline for Robomagellan

As with any robot that has been developed sporadically over close to a decade, it has gone through quite a bit of evolution. You can find some of that evolution in the posts tagged robomagellan, but here is a summary:

  • The computer was originally a Turtlebot laptop. It has since been swapped out for an Intel NUC. I've previously posted about how I power the NUC off a 12->19V step up.
  • The original version of the Etherbotix was designed for this robot. It now uses the later Etherbotix design with a plug-in motor driver.
  • The robot now has an Adafruit Ultimate GPS v3. That may change in the near future, as I've been looking at setting up an RTK solution here on the farm.
  • The robot originally used a small chip-level IMU on the Etherbotix, but now uses a UM-7 for better results. That said, I never had any luck with the internal UM-7 EKF (even when trying to calibrate it), so there are probably plenty of cheaper options out there.
  • Originally, the main sensor was going to be a UTM-30 on a tilting servo. I've now simplified that for an Intel D435 depth sensor.
  • The robot is still using the original wheels, however I switched from 100:1 gearboxes to 50:1 to get more speed (the 100:1 were really too much torque, the robot literally could climb a wall).
The robot, as you probably guessed, runs ROS. Specifically I'm using the following packages:
  • etherbotix_python - these are my drivers for the Etherbotix board. In addition to controlling the motors and providing odometry, this board also acts as a serial->ethernet adapter for the GPS module. The drivers publish the raw NMEA sentences that are sent by the GPS into ROS.
  • um7 - this is the driver for the UM7 IMU.
  • nmea_navsat_driver - this is used to convert NMEA sentences into a sensor_msgs/NavSatFix message.
  • imu_transformer - is used to translate the IMU position into the base_link frame. My IMU is actually mounted "upside down" so this is super important.
  • imu_filter_madgwick - this is used to track the pose of the IMU. Importantly it fuses the magnetometer information, allowing the IMU to act like a compass for the global EKF.
  • robot_localization - I use two instances of the EKF filter. The first fuses the IMU with the wheel odometry in order to get a good local odometry frame. The second fuses the IMU, wheel odometry and GPS (processed by the navsat_transform_node) into a global odometry.
  • rviz_satellite - not used on the robot, but is an awesome plugin for RVIZ that can download
Global Localization
Setting up the global localization took me a little while to get working. In order to make this process easier, I setup my main launch file so that I have an "offline_mode" argument which doesn't launch the drivers. Then I have a launch file for recording bagfiles running only the drivers. I can then change everything in my various pipelines when re-running the bagfiles locally. This has been quite useful as I've been tweaking the IMU processing pipeline in parallel with adding the global EKF.

Satellite Imagery in RVIZ
Visualization is always a powerful tool. While RVIZ doesn't have much going for outdoor robots out of the box, the rviz_satellite plugin makes it awesome.

rviz_satellite overlay with some odometry tracks

The one challenging part of rviz_satellite is setting the "Object URI". For an off-road robot, the default OpenStreetMaps don't do much. I ended up using MapBox satellite imagery - but getting the Object URI right took a bit of digging around. It turns out the correct URI is:
https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/256/{z}/{x}/{y}?access_token=XYZ
Also, free accounts with MapBox are limited to 200k tile requests per month. To avoid using these up, you might want to think about running a separate roscore so you can keep RVIZ running even when you restart the robot launch file. That said, I've only used 148 tile requests this month and have been restarting RVIZ quite a bit.

Next Steps
I just recently got the global localization working - I'm probably going to continue to tweak things. The D435 drivers are working pretty reliably now, so the next step is mount the D435 on the robot and start integrating the data and move onto some basic navigation. I also plan to clean up the IMU calibration code I created and get it merged into robot_calibration.