imu_filter.cpp
Go to the documentation of this file.
00001 /*
00002  *  Copyright (C) 2010, CCNY Robotics Lab
00003  *  Ivan Dryanovski <ivan.dryanovski@gmail.com>
00004  *
00005  *  http://robotics.ccny.cuny.edu
00006  *
00007  *  Based on implementation of Madgwick's IMU and AHRS algorithms.
00008  *  http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
00009  *
00010  *
00011  *  This program is free software: you can redistribute it and/or modify
00012  *  it under the terms of the GNU General Public License as published by
00013  *  the Free Software Foundation, either version 3 of the License, or
00014  *  (at your option) any later version.
00015  *
00016  *  This program is distributed in the hope that it will be useful,
00017  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  *  GNU General Public License for more details.
00020  *
00021  *  You should have received a copy of the GNU General Public License
00022  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00023  */
00024 
00025 #include "imu_filter_madgwick/imu_filter.h"
00026 
00027 ImuFilter::ImuFilter(ros::NodeHandle nh, ros::NodeHandle nh_private):
00028   nh_(nh), 
00029   nh_private_(nh_private),
00030   initialized_(false),
00031   q0(1.0), q1(0.0), q2(0.0), q3(0.0)
00032 {
00033   ROS_INFO ("Starting ImuFilter");
00034 
00035   // **** get paramters
00036 
00037   if (!nh_private_.getParam ("gain", gain_))
00038    gain_ = 0.1;
00039   if (!nh_private_.getParam ("zeta", zeta_))
00040    zeta_ = 0;
00041   if (!nh_private_.getParam ("use_mag", use_mag_))
00042    use_mag_ = true;
00043   if (!nh_private_.getParam ("publish_tf", publish_tf_))
00044    publish_tf_ = true;
00045   if (!nh_private_.getParam ("fixed_frame", fixed_frame_))
00046    fixed_frame_ = "odom";
00047   if (!nh_private_.getParam ("constant_dt", constant_dt_))
00048     constant_dt_ = 0.0;
00049 
00050   // check for illegal constant_dt values
00051   if (constant_dt_ < 0.0)
00052   {
00053     ROS_FATAL("constant_dt parameter is %f, must be >= 0.0. Setting to 0.0", constant_dt_);
00054     constant_dt_ = 0.0;
00055   }
00056   
00057   // if constant_dt_ is 0.0 (default), use IMU timestamp to determine dt
00058   // otherwise, it will be constant
00059   if (constant_dt_ == 0.0)
00060     ROS_INFO("Using dt computed from message headers");
00061   else
00062     ROS_INFO("Using constant dt of %f sec", constant_dt_);
00063 
00064   // **** register dynamic reconfigure
00065   
00066   FilterConfigServer::CallbackType f = boost::bind(&ImuFilter::reconfigCallback, this, _1, _2);
00067   config_server_.setCallback(f);
00068   
00069   // **** register publishers
00070 
00071   imu_publisher_ = nh_.advertise<sensor_msgs::Imu>(
00072     "imu/data", 5);
00073 
00074   // **** register subscribers
00075 
00076   // Synchronize inputs. Topic subscriptions happen on demand in the connection callback.
00077   int queue_size = 5;
00078 
00079   imu_subscriber_.reset(new ImuSubscriber(
00080     nh_, "imu/data_raw", queue_size));
00081 
00082   if (use_mag_)
00083   {
00084     mag_subscriber_.reset(new MagSubscriber(
00085       nh_, "imu/mag", queue_size));
00086 
00087     sync_.reset(new Synchronizer(
00088       SyncPolicy(queue_size), *imu_subscriber_, *mag_subscriber_));
00089     sync_->registerCallback(boost::bind(&ImuFilter::imuMagCallback, this, _1, _2));
00090   }
00091   else
00092   {
00093     imu_subscriber_->registerCallback(&ImuFilter::imuCallback, this);
00094   }
00095 }
00096 
00097 ImuFilter::~ImuFilter()
00098 {
00099   ROS_INFO ("Destroying ImuFilter");
00100 }
00101 
00102 void ImuFilter::imuCallback(const ImuMsg::ConstPtr& imu_msg_raw)
00103 {
00104   boost::mutex::scoped_lock(mutex_);
00105 
00106   const geometry_msgs::Vector3& ang_vel = imu_msg_raw->angular_velocity;
00107   const geometry_msgs::Vector3& lin_acc = imu_msg_raw->linear_acceleration; 
00108   
00109   ros::Time time = imu_msg_raw->header.stamp;
00110   imu_frame_ = imu_msg_raw->header.frame_id;
00111 
00112   if (!initialized_)
00113   {
00114     // initialize roll/pitch orientation from acc. vector    
00115     double sign = copysignf(1.0, lin_acc.z);
00116     double roll = atan2(lin_acc.y, sign * sqrt(lin_acc.x*lin_acc.x + lin_acc.z*lin_acc.z));
00117     double pitch = -atan2(lin_acc.x, sqrt(lin_acc.y*lin_acc.y + lin_acc.z*lin_acc.z));
00118     double yaw = 0.0;
00119                         
00120     tf::Quaternion init_q = tf::createQuaternionFromRPY(roll, pitch, yaw);
00121     
00122     q1 = init_q.getX();
00123     q2 = init_q.getY();
00124     q3 = init_q.getZ();
00125     q0 = init_q.getW();
00126     
00127     // initialize time
00128     last_time_ = time;
00129     initialized_ = true;
00130   }
00131 
00132   // determine dt: either constant, or from IMU timestamp
00133   float dt;
00134   if (constant_dt_ > 0.0)
00135     dt = constant_dt_;
00136   else
00137     dt = (time - last_time_).toSec();
00138 
00139   last_time_ = time;
00140   
00141   madgwickAHRSupdateIMU(
00142     ang_vel.x, ang_vel.y, ang_vel.z,
00143     lin_acc.x, lin_acc.y, lin_acc.z,
00144     dt);
00145 
00146   publishFilteredMsg(imu_msg_raw);
00147   if (publish_tf_)
00148     publishTransform(imu_msg_raw);
00149 }
00150 
00151 void ImuFilter::imuMagCallback(
00152   const ImuMsg::ConstPtr& imu_msg_raw,
00153   const MagMsg::ConstPtr& mag_msg)
00154 {
00155   boost::mutex::scoped_lock(mutex_);
00156   
00157   const geometry_msgs::Vector3& ang_vel = imu_msg_raw->angular_velocity;
00158   const geometry_msgs::Vector3& lin_acc = imu_msg_raw->linear_acceleration; 
00159   const geometry_msgs::Vector3& mag_fld = mag_msg->vector;
00160   
00161   ros::Time time = imu_msg_raw->header.stamp;
00162   imu_frame_ = imu_msg_raw->header.frame_id;
00163 
00164   if (!initialized_)
00165   {
00166     // initialize roll/pitch orientation from acc. vector    
00167     double sign = copysignf(1.0, lin_acc.z);
00168     double roll = atan2(lin_acc.y, sign * sqrt(lin_acc.x*lin_acc.x + lin_acc.z*lin_acc.z));
00169     double pitch = -atan2(lin_acc.x, sqrt(lin_acc.y*lin_acc.y + lin_acc.z*lin_acc.z));
00170     double yaw = 0.0; // TODO: initialize from magnetic raeding?
00171                         
00172     tf::Quaternion init_q = tf::createQuaternionFromRPY(roll, pitch, yaw);
00173     
00174     q1 = init_q.getX();
00175     q2 = init_q.getY();
00176     q3 = init_q.getZ();
00177     q0 = init_q.getW();
00178     
00179     w_bx_ = 0;
00180     w_by_ = 0;
00181     w_bz_ = 0;
00182     
00183     last_time_ = time;
00184     initialized_ = true;
00185   }
00186 
00187   // determine dt: either constant, or from IMU timestamp
00188   float dt;
00189   if (constant_dt_ > 0.0)
00190     dt = constant_dt_;
00191   else
00192     dt = (time - last_time_).toSec();
00193   
00194   last_time_ = time;
00195 
00196   madgwickAHRSupdate(
00197     ang_vel.x, ang_vel.y, ang_vel.z,
00198     lin_acc.x, lin_acc.y, lin_acc.z,
00199     mag_fld.x, mag_fld.y, mag_fld.z,
00200     dt);
00201 
00202   publishFilteredMsg(imu_msg_raw);
00203   if (publish_tf_)
00204     publishTransform(imu_msg_raw);
00205 }
00206 
00207 void ImuFilter::publishTransform(const ImuMsg::ConstPtr& imu_msg_raw)
00208 {
00209   tf::Quaternion q(q1, q2, q3, q0);
00210   tf::Transform transform;
00211   transform.setOrigin( tf::Vector3( 0.0, 0.0, 0.0 ) );
00212   transform.setRotation( q );
00213   tf_broadcaster_.sendTransform( tf::StampedTransform( transform,
00214                    imu_msg_raw->header.stamp,
00215                    fixed_frame_,
00216                    imu_frame_ ) );
00217 
00218 }
00219 
00220 void ImuFilter::publishFilteredMsg(const ImuMsg::ConstPtr& imu_msg_raw)
00221 {
00222   // create orientation quaternion
00223   // q0 is the angle, q1, q2, q3 are the axes  
00224   tf::Quaternion q(q1, q2, q3, q0);   
00225 
00226   // create and publish fitlered IMU message
00227   boost::shared_ptr<ImuMsg> imu_msg = 
00228     boost::make_shared<ImuMsg>(*imu_msg_raw);
00229 
00230   tf::quaternionTFToMsg(q, imu_msg->orientation);  
00231   imu_publisher_.publish(imu_msg);
00232 }
00233 
00234 void ImuFilter::madgwickAHRSupdate(
00235   float gx, float gy, float gz, 
00236   float ax, float ay, float az, 
00237   float mx, float my, float mz, 
00238   float dt)
00239 {
00240         float recipNorm;
00241         float s0, s1, s2, s3;
00242         float qDot1, qDot2, qDot3, qDot4;
00243         float hx, hy;
00244         float _2q0mx, _2q0my, _2q0mz, _2q1mx, _2bx, _2bz, _4bx, _4bz, _2q0, _2q1, _2q2, _2q3, _2q0q2, _2q2q3, q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3;
00245         float _w_err_x, _w_err_y, _w_err_z;
00246 
00247         // Use IMU algorithm if magnetometer measurement invalid (avoids NaN in magnetometer normalisation)
00248   if(std::isnan(mx) || std::isnan(my) || std::isnan(mz))
00249   {
00250                 madgwickAHRSupdateIMU(gx, gy, gz, ax, ay, az, dt);
00251                 return;
00252         }
00253 
00254         
00255         // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
00256         if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) 
00257   {
00258                 // Normalise accelerometer measurement
00259                 recipNorm = invSqrt(ax * ax + ay * ay + az * az);
00260                 ax *= recipNorm;
00261                 ay *= recipNorm;
00262                 az *= recipNorm;   
00263 
00264                 // Normalise magnetometer measurement
00265                 recipNorm = invSqrt(mx * mx + my * my + mz * mz);
00266                 mx *= recipNorm;
00267                 my *= recipNorm;
00268                 mz *= recipNorm;
00269 
00270                 // Auxiliary variables to avoid repeated arithmetic
00271                 _2q0mx = 2.0f * q0 * mx;
00272                 _2q0my = 2.0f * q0 * my;
00273                 _2q0mz = 2.0f * q0 * mz;
00274                 _2q1mx = 2.0f * q1 * mx;
00275                 _2q0 = 2.0f * q0;
00276                 _2q1 = 2.0f * q1;
00277                 _2q2 = 2.0f * q2;
00278                 _2q3 = 2.0f * q3;
00279                 _2q0q2 = 2.0f * q0 * q2;
00280                 _2q2q3 = 2.0f * q2 * q3;
00281                 q0q0 = q0 * q0;
00282                 q0q1 = q0 * q1;
00283                 q0q2 = q0 * q2;
00284                 q0q3 = q0 * q3;
00285                 q1q1 = q1 * q1;
00286                 q1q2 = q1 * q2;
00287                 q1q3 = q1 * q3;
00288                 q2q2 = q2 * q2;
00289                 q2q3 = q2 * q3;
00290                 q3q3 = q3 * q3;
00291 
00292                 // Reference direction of Earth's magnetic field
00293                 hx = mx * q0q0 - _2q0my * q3 + _2q0mz * q2 + mx * q1q1 + _2q1 * my * q2 + _2q1 * mz * q3 - mx * q2q2 - mx * q3q3;
00294                 hy = _2q0mx * q3 + my * q0q0 - _2q0mz * q1 + _2q1mx * q2 - my * q1q1 + my * q2q2 + _2q2 * mz * q3 - my * q3q3;
00295                 _2bx = sqrt(hx * hx + hy * hy);
00296                 _2bz = -_2q0mx * q2 + _2q0my * q1 + mz * q0q0 + _2q1mx * q3 - mz * q1q1 + _2q2 * my * q3 - mz * q2q2 + mz * q3q3;
00297                 _4bx = 2.0f * _2bx;
00298                 _4bz = 2.0f * _2bz;
00299 
00300                 // Gradient decent algorithm corrective step
00301                 s0 = -_2q2 * (2.0f * q1q3 - _2q0q2 - ax) + _2q1 * (2.0f * q0q1 + _2q2q3 - ay) - _2bz * q2 * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (-_2bx * q3 + _2bz * q1) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + _2bx * q2 * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
00302                 s1 = _2q3 * (2.0f * q1q3 - _2q0q2 - ax) + _2q0 * (2.0f * q0q1 + _2q2q3 - ay) - 4.0f * q1 * (1 - 2.0f * q1q1 - 2.0f * q2q2 - az) + _2bz * q3 * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (_2bx * q2 + _2bz * q0) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + (_2bx * q3 - _4bz * q1) * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
00303                 s2 = -_2q0 * (2.0f * q1q3 - _2q0q2 - ax) + _2q3 * (2.0f * q0q1 + _2q2q3 - ay) - 4.0f * q2 * (1 - 2.0f * q1q1 - 2.0f * q2q2 - az) + (-_4bx * q2 - _2bz * q0) * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (_2bx * q1 + _2bz * q3) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + (_2bx * q0 - _4bz * q2) * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
00304                 s3 = _2q1 * (2.0f * q1q3 - _2q0q2 - ax) + _2q2 * (2.0f * q0q1 + _2q2q3 - ay) + (-_4bx * q3 + _2bz * q1) * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (-_2bx * q0 + _2bz * q2) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + _2bx * q1 * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
00305                 recipNorm = invSqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude
00306                 s0 *= recipNorm;
00307                 s1 *= recipNorm;
00308                 s2 *= recipNorm;
00309                 s3 *= recipNorm;
00310 
00311                 // compute gyro drift bias
00312                 _w_err_x = _2q0 * s1 - _2q1 * s0 - _2q2 * s3 + _2q3 * s2;
00313                 _w_err_y = _2q0 * s2 + _2q1 * s3 - _2q2 * s0 - _2q3 * s1;
00314                 _w_err_z = _2q0 * s3 - _2q1 * s2 + _2q2 * s1 - _2q3 * s0;
00315                 
00316                 w_bx_ += _w_err_x * dt * zeta_;
00317                 w_by_ += _w_err_y * dt * zeta_;
00318                 w_bz_ += _w_err_z * dt * zeta_;
00319                 
00320                 gx -= w_bx_;
00321                 gy -= w_by_;
00322                 gz -= w_bz_;
00323                 
00324                 // Rate of change of quaternion from gyroscope
00325                 qDot1 = 0.5f * (-q1 * gx - q2 * gy - q3 * gz);
00326                 qDot2 = 0.5f * ( q0 * gx + q2 * gz - q3 * gy);
00327                 qDot3 = 0.5f * ( q0 * gy - q1 * gz + q3 * gx);
00328                 qDot4 = 0.5f * ( q0 * gz + q1 * gy - q2 * gx);
00329 
00330                 // Apply feedback step
00331                 qDot1 -= gain_ * s0;
00332                 qDot2 -= gain_ * s1;
00333                 qDot3 -= gain_ * s2;
00334                 qDot4 -= gain_ * s3;
00335         } else{
00336                 // Rate of change of quaternion from gyroscope
00337                 qDot1 = 0.5f * (-q1 * gx - q2 * gy - q3 * gz);
00338                 qDot2 = 0.5f * ( q0 * gx + q2 * gz - q3 * gy);
00339                 qDot3 = 0.5f * ( q0 * gy - q1 * gz + q3 * gx);
00340                 qDot4 = 0.5f * ( q0 * gz + q1 * gy - q2 * gx);
00341         }
00342 
00343         // Integrate rate of change of quaternion to yield quaternion
00344         q0 += qDot1 * dt;
00345         q1 += qDot2 * dt;
00346         q2 += qDot3 * dt;
00347         q3 += qDot4 * dt;
00348 
00349         // Normalise quaternion
00350         recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
00351         q0 *= recipNorm;
00352         q1 *= recipNorm;
00353         q2 *= recipNorm;
00354         q3 *= recipNorm;
00355 }
00356 
00357 
00358 void ImuFilter::madgwickAHRSupdateIMU(
00359   float gx, float gy, float gz, 
00360   float ax, float ay, float az,
00361   float dt) 
00362 {
00363         float recipNorm;
00364         float s0, s1, s2, s3;
00365         float qDot1, qDot2, qDot3, qDot4;
00366         float _2q0, _2q1, _2q2, _2q3, _4q0, _4q1, _4q2 ,_8q1, _8q2, q0q0, q1q1, q2q2, q3q3;
00367 
00368         // Rate of change of quaternion from gyroscope
00369         qDot1 = 0.5f * (-q1 * gx - q2 * gy - q3 * gz);
00370         qDot2 = 0.5f * ( q0 * gx + q2 * gz - q3 * gy);
00371         qDot3 = 0.5f * ( q0 * gy - q1 * gz + q3 * gx);
00372         qDot4 = 0.5f * ( q0 * gz + q1 * gy - q2 * gx);
00373 
00374         // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
00375         if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) 
00376   {
00377                 // Normalise accelerometer measurement
00378                 recipNorm = invSqrt(ax * ax + ay * ay + az * az);
00379                 ax *= recipNorm;
00380                 ay *= recipNorm;
00381                 az *= recipNorm;   
00382 
00383                 // Auxiliary variables to avoid repeated arithmetic
00384                 _2q0 = 2.0f * q0;
00385                 _2q1 = 2.0f * q1;
00386                 _2q2 = 2.0f * q2;
00387                 _2q3 = 2.0f * q3;
00388                 _4q0 = 4.0f * q0;
00389                 _4q1 = 4.0f * q1;
00390                 _4q2 = 4.0f * q2;
00391                 _8q1 = 8.0f * q1;
00392                 _8q2 = 8.0f * q2;
00393                 q0q0 = q0 * q0;
00394                 q1q1 = q1 * q1;
00395                 q2q2 = q2 * q2;
00396                 q3q3 = q3 * q3;
00397 
00398                 // Gradient decent algorithm corrective step
00399                 s0 = _4q0 * q2q2 + _2q2 * ax + _4q0 * q1q1 - _2q1 * ay;
00400                 s1 = _4q1 * q3q3 - _2q3 * ax + 4.0f * q0q0 * q1 - _2q0 * ay - _4q1 + _8q1 * q1q1 + _8q1 * q2q2 + _4q1 * az;
00401                 s2 = 4.0f * q0q0 * q2 + _2q0 * ax + _4q2 * q3q3 - _2q3 * ay - _4q2 + _8q2 * q1q1 + _8q2 * q2q2 + _4q2 * az;
00402                 s3 = 4.0f * q1q1 * q3 - _2q1 * ax + 4.0f * q2q2 * q3 - _2q2 * ay;
00403                 recipNorm = invSqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude
00404                 s0 *= recipNorm;
00405                 s1 *= recipNorm;
00406                 s2 *= recipNorm;
00407                 s3 *= recipNorm;
00408 
00409                 // Apply feedback step
00410                 qDot1 -= gain_ * s0;
00411                 qDot2 -= gain_ * s1;
00412                 qDot3 -= gain_ * s2;
00413                 qDot4 -= gain_ * s3;
00414         }
00415 
00416         // Integrate rate of change of quaternion to yield quaternion
00417         q0 += qDot1 * dt;
00418         q1 += qDot2 * dt;
00419         q2 += qDot3 * dt;
00420         q3 += qDot4 * dt;
00421 
00422         // Normalise quaternion
00423         recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
00424         q0 *= recipNorm;
00425         q1 *= recipNorm;
00426         q2 *= recipNorm;
00427         q3 *= recipNorm;
00428 }
00429 
00430 void ImuFilter::reconfigCallback(FilterConfig& config, uint32_t level)
00431 {
00432   boost::mutex::scoped_lock(mutex_);
00433   gain_ = config.gain;
00434   zeta_ = config.zeta;
00435   ROS_INFO("Imu filter gain set to %f", gain_);
00436   ROS_INFO("Gyro drift bias set to %f", zeta_);
00437 }
00438 
00439 


imu_filter_madgwick
Author(s): Ivan Dryanovski
autogenerated on Mon Oct 6 2014 00:49:04