/// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
# include <AP_Progmem.h>
# include "AP_InertialSensor.h"
# include <AP_Common.h>
# include <AP_HAL.h>
# include <AP_Notify.h>
# include <AP_Vehicle.h>
/*
enable TIMING_DEBUG to track down scheduling issues with the main
loop . Output is on the debug console
*/
# define TIMING_DEBUG 0
# if TIMING_DEBUG
# include <stdio.h>
# define timing_printf(fmt, args...) do { printf("[timing] " fmt, ##args); } while(0)
# else
# define timing_printf(fmt, args...)
# endif
extern const AP_HAL : : HAL & hal ;
# if APM_BUILD_TYPE(APM_BUILD_ArduCopter)
# define DEFAULT_GYRO_FILTER 20
# define DEFAULT_ACCEL_FILTER 20
# elif APM_BUILD_TYPE(APM_BUILD_APMrover2)
# define DEFAULT_GYRO_FILTER 10
# define DEFAULT_ACCEL_FILTER 10
# else
# define DEFAULT_GYRO_FILTER 20
# define DEFAULT_ACCEL_FILTER 20
# endif
# define SAMPLE_UNIT 1
// Class level parameters
const AP_Param : : GroupInfo AP_InertialSensor : : var_info [ ] PROGMEM = {
// @Param: PRODUCT_ID
// @DisplayName: IMU Product ID
// @Description: Which type of IMU is installed (read-only).
// @User: Advanced
// @Values: 0:Unknown,1:APM1-1280,2:APM1-2560,88:APM2,3:SITL,4:PX4v1,5:PX4v2,256:Flymaple,257:Linux
AP_GROUPINFO ( " PRODUCT_ID " , 0 , AP_InertialSensor , _product_id , 0 ) ,
/*
The following parameter indexes and reserved from previous use
as accel offsets and scaling from before the 16 g change in the
PX4 backend :
ACCSCAL : 1
ACCOFFS : 2
MPU6K_FILTER : 4
ACC2SCAL : 5
ACC2OFFS : 6
ACC3SCAL : 8
ACC3OFFS : 9
CALSENSFRAME : 11
*/
// @Param: GYROFFS_X
// @DisplayName: Gyro offsets of X axis
// @Description: Gyro sensor offsets of X axis. This is setup on each boot during gyro calibrations
// @Units: rad/s
// @User: Advanced
// @Param: GYROFFS_Y
// @DisplayName: Gyro offsets of Y axis
// @Description: Gyro sensor offsets of Y axis. This is setup on each boot during gyro calibrations
// @Units: rad/s
// @User: Advanced
// @Param: GYROFFS_Z
// @DisplayName: Gyro offsets of Z axis
// @Description: Gyro sensor offsets of Z axis. This is setup on each boot during gyro calibrations
// @Units: rad/s
// @User: Advanced
AP_GROUPINFO ( " GYROFFS " , 3 , AP_InertialSensor , _gyro_offset [ 0 ] , 0 ) ,
# if INS_MAX_INSTANCES > 1
// @Param: GYR2OFFS_X
// @DisplayName: Gyro2 offsets of X axis
// @Description: Gyro2 sensor offsets of X axis. This is setup on each boot during gyro calibrations
// @Units: rad/s
// @User: Advanced
// @Param: GYR2OFFS_Y
// @DisplayName: Gyro2 offsets of Y axis
// @Description: Gyro2 sensor offsets of Y axis. This is setup on each boot during gyro calibrations
// @Units: rad/s
// @User: Advanced
// @Param: GYR2OFFS_Z
// @DisplayName: Gyro2 offsets of Z axis
// @Description: Gyro2 sensor offsets of Z axis. This is setup on each boot during gyro calibrations
// @Units: rad/s
// @User: Advanced
AP_GROUPINFO ( " GYR2OFFS " , 7 , AP_InertialSensor , _gyro_offset [ 1 ] , 0 ) ,
# endif
# if INS_MAX_INSTANCES > 2
// @Param: GYR3OFFS_X
// @DisplayName: Gyro3 offsets of X axis
// @Description: Gyro3 sensor offsets of X axis. This is setup on each boot during gyro calibrations
// @Units: rad/s
// @User: Advanced
// @Param: GYR3OFFS_Y
// @DisplayName: Gyro3 offsets of Y axis
// @Description: Gyro3 sensor offsets of Y axis. This is setup on each boot during gyro calibrations
// @Units: rad/s
// @User: Advanced
// @Param: GYR3OFFS_Z
// @DisplayName: Gyro3 offsets of Z axis
// @Description: Gyro3 sensor offsets of Z axis. This is setup on each boot during gyro calibrations
// @Units: rad/s
// @User: Advanced
AP_GROUPINFO ( " GYR3OFFS " , 10 , AP_InertialSensor , _gyro_offset [ 2 ] , 0 ) ,
# endif
// @Param: ACCSCAL_X
// @DisplayName: Accelerometer scaling of X axis
// @Description: Accelerometer scaling of X axis. Calculated during acceleration calibration routine
// @Range: 0.8 1.2
// @User: Advanced
// @Param: ACCSCAL_Y
// @DisplayName: Accelerometer scaling of Y axis
// @Description: Accelerometer scaling of Y axis Calculated during acceleration calibration routine
// @Range: 0.8 1.2
// @User: Advanced
// @Param: ACCSCAL_Z
// @DisplayName: Accelerometer scaling of Z axis
// @Description: Accelerometer scaling of Z axis Calculated during acceleration calibration routine
// @Range: 0.8 1.2
// @User: Advanced
AP_GROUPINFO ( " ACCSCAL " , 12 , AP_InertialSensor , _accel_scale [ 0 ] , 0 ) ,
// @Param: ACCOFFS_X
// @DisplayName: Accelerometer offsets of X axis
// @Description: Accelerometer offsets of X axis. This is setup using the acceleration calibration or level operations
// @Units: m/s/s
// @Range: -300 300
// @User: Advanced
// @Param: ACCOFFS_Y
// @DisplayName: Accelerometer offsets of Y axis
// @Description: Accelerometer offsets of Y axis. This is setup using the acceleration calibration or level operations
// @Units: m/s/s
// @Range: -300 300
// @User: Advanced
// @Param: ACCOFFS_Z
// @DisplayName: Accelerometer offsets of Z axis
// @Description: Accelerometer offsets of Z axis. This is setup using the acceleration calibration or level operations
// @Units: m/s/s
// @Range: -300 300
// @User: Advanced
AP_GROUPINFO ( " ACCOFFS " , 13 , AP_InertialSensor , _accel_offset [ 0 ] , 0 ) ,
# if INS_MAX_INSTANCES > 1
// @Param: ACC2SCAL_X
// @DisplayName: Accelerometer2 scaling of X axis
// @Description: Accelerometer2 scaling of X axis. Calculated during acceleration calibration routine
// @Range: 0.8 1.2
// @User: Advanced
// @Param: ACC2SCAL_Y
// @DisplayName: Accelerometer2 scaling of Y axis
// @Description: Accelerometer2 scaling of Y axis Calculated during acceleration calibration routine
// @Range: 0.8 1.2
// @User: Advanced
// @Param: ACC2SCAL_Z
// @DisplayName: Accelerometer2 scaling of Z axis
// @Description: Accelerometer2 scaling of Z axis Calculated during acceleration calibration routine
// @Range: 0.8 1.2
// @User: Advanced
AP_GROUPINFO ( " ACC2SCAL " , 14 , AP_InertialSensor , _accel_scale [ 1 ] , 0 ) ,
// @Param: ACC2OFFS_X
// @DisplayName: Accelerometer2 offsets of X axis
// @Description: Accelerometer2 offsets of X axis. This is setup using the acceleration calibration or level operations
// @Units: m/s/s
// @Range: -300 300
// @User: Advanced
// @Param: ACC2OFFS_Y
// @DisplayName: Accelerometer2 offsets of Y axis
// @Description: Accelerometer2 offsets of Y axis. This is setup using the acceleration calibration or level operations
// @Units: m/s/s
// @Range: -300 300
// @User: Advanced
// @Param: ACC2OFFS_Z
// @DisplayName: Accelerometer2 offsets of Z axis
// @Description: Accelerometer2 offsets of Z axis. This is setup using the acceleration calibration or level operations
// @Units: m/s/s
// @Range: -300 300
// @User: Advanced
AP_GROUPINFO ( " ACC2OFFS " , 15 , AP_InertialSensor , _accel_offset [ 1 ] , 0 ) ,
# endif
# if INS_MAX_INSTANCES > 2
// @Param: ACC3SCAL_X
// @DisplayName: Accelerometer3 scaling of X axis
// @Description: Accelerometer3 scaling of X axis. Calculated during acceleration calibration routine
// @Range: 0.8 1.2
// @User: Advanced
// @Param: ACC3SCAL_Y
// @DisplayName: Accelerometer3 scaling of Y axis
// @Description: Accelerometer3 scaling of Y axis Calculated during acceleration calibration routine
// @Range: 0.8 1.2
// @User: Advanced
// @Param: ACC3SCAL_Z
// @DisplayName: Accelerometer3 scaling of Z axis
// @Description: Accelerometer3 scaling of Z axis Calculated during acceleration calibration routine
// @Range: 0.8 1.2
// @User: Advanced
AP_GROUPINFO ( " ACC3SCAL " , 16 , AP_InertialSensor , _accel_scale [ 2 ] , 0 ) ,
// @Param: ACC3OFFS_X
// @DisplayName: Accelerometer3 offsets of X axis
// @Description: Accelerometer3 offsets of X axis. This is setup using the acceleration calibration or level operations
// @Units: m/s/s
// @Range: -300 300
// @User: Advanced
// @Param: ACC3OFFS_Y
// @DisplayName: Accelerometer3 offsets of Y axis
// @Description: Accelerometer3 offsets of Y axis. This is setup using the acceleration calibration or level operations
// @Units: m/s/s
// @Range: -300 300
// @User: Advanced
// @Param: ACC3OFFS_Z
// @DisplayName: Accelerometer3 offsets of Z axis
// @Description: Accelerometer3 offsets of Z axis. This is setup using the acceleration calibration or level operations
// @Units: m/s/s
// @Range: -300 300
// @User: Advanced
AP_GROUPINFO ( " ACC3OFFS " , 17 , AP_InertialSensor , _accel_offset [ 2 ] , 0 ) ,
# endif
// @Param: GYRO_FILTER
// @DisplayName: Gyro filter cutoff frequency
// @Description: Filter cutoff frequency for gyroscopes. This can be set to a lower value to try to cope with very high vibration levels in aircraft. This option takes effect on the next reboot. A value of zero means no filtering (not recommended!)
// @Units: Hz
// @Range: 0 127
// @User: Advanced
AP_GROUPINFO ( " GYRO_FILTER " , 18 , AP_InertialSensor , _gyro_filter_cutoff , DEFAULT_GYRO_FILTER ) ,
// @Param: ACCEL_FILTER
// @DisplayName: Accel filter cutoff frequency
// @Description: Filter cutoff frequency for accelerometers. This can be set to a lower value to try to cope with very high vibration levels in aircraft. This option takes effect on the next reboot. A value of zero means no filtering (not recommended!)
// @Units: Hz
// @Range: 0 127
// @User: Advanced
AP_GROUPINFO ( " ACCEL_FILTER " , 19 , AP_InertialSensor , _accel_filter_cutoff , DEFAULT_ACCEL_FILTER ) ,
/*
NOTE : parameter indexes have gaps above . When adding new
parameters check for conflicts carefully
*/
AP_GROUPEND
} ;
AP_InertialSensor : : AP_InertialSensor ( ) :
_gyro_count ( 0 ) ,
_accel_count ( 0 ) ,
_backend_count ( 0 ) ,
_accel ( ) ,
_gyro ( ) ,
_board_orientation ( ROTATION_NONE ) ,
_primary_gyro ( 0 ) ,
_primary_accel ( 0 ) ,
_hil_mode ( false ) ,
_have_3D_calibration ( false ) ,
_calibrating ( false )
{
AP_Param : : setup_object_defaults ( this , var_info ) ;
for ( uint8_t i = 0 ; i < INS_MAX_BACKENDS ; i + + ) {
_backends [ i ] = NULL ;
}
for ( uint8_t i = 0 ; i < INS_MAX_INSTANCES ; i + + ) {
_accel_error_count [ i ] = 0 ;
_gyro_error_count [ i ] = 0 ;
}
memset ( _delta_velocity_valid , 0 , sizeof ( _delta_velocity_valid ) ) ;
memset ( _delta_angle_valid , 0 , sizeof ( _delta_angle_valid ) ) ;
}
/*
register a new gyro instance
*/
uint8_t AP_InertialSensor : : register_gyro ( void )
{
if ( _gyro_count = = INS_MAX_INSTANCES ) {
hal . scheduler - > panic ( PSTR ( " Too many gyros " ) ) ;
}
return _gyro_count + + ;
}
/*
register a new accel instance
*/
uint8_t AP_InertialSensor : : register_accel ( void )
{
if ( _accel_count = = INS_MAX_INSTANCES ) {
hal . scheduler - > panic ( PSTR ( " Too many accels " ) ) ;
}
return _accel_count + + ;
}
void
AP_InertialSensor : : init ( Start_style style ,
Sample_rate sample_rate )
{
// remember the sample rate
_sample_rate = sample_rate ;
if ( _gyro_count = = 0 & & _accel_count = = 0 ) {
// detect available backends. Only called once
_detect_backends ( ) ;
}
// initialise accel scale if need be. This is needed as we can't
// give non-zero default values for vectors in AP_Param
for ( uint8_t i = 0 ; i < get_accel_count ( ) ; i + + ) {
if ( _accel_scale [ i ] . get ( ) . is_zero ( ) ) {
_accel_scale [ i ] . set ( Vector3f ( 1 , 1 , 1 ) ) ;
}
}
// remember whether we have 3D calibration so this can be used for
// AHRS health
check_3D_calibration ( ) ;
if ( WARM_START ! = style ) {
// do cold-start calibration for gyro only
_init_gyro ( ) ;
}
switch ( sample_rate ) {
case RATE_50HZ :
_sample_period_usec = 20000 ;
break ;
case RATE_100HZ :
_sample_period_usec = 10000 ;
break ;
case RATE_200HZ :
_sample_period_usec = 5000 ;
break ;
case RATE_400HZ :
default :
_sample_period_usec = 2500 ;
break ;
}
// establish the baseline time between samples
_delta_time = 0 ;
_next_sample_usec = 0 ;
_last_sample_usec = 0 ;
_have_sample = false ;
}
/*
try to load a backend
*/
void AP_InertialSensor : : _add_backend ( AP_InertialSensor_Backend * ( detect ) ( AP_InertialSensor & ) )
{
if ( _backend_count = = INS_MAX_BACKENDS ) {
hal . scheduler - > panic ( PSTR ( " Too many INS backends " ) ) ;
}
_backends [ _backend_count ] = detect ( * this ) ;
if ( _backends [ _backend_count ] ! = NULL ) {
_backend_count + + ;
}
}
/*
detect available backends for this board
*/
void
AP_InertialSensor : : _detect_backends ( void )
{
if ( _hil_mode ) {
_add_backend ( AP_InertialSensor_HIL : : detect ) ;
return ;
}
# if HAL_INS_DEFAULT == HAL_INS_HIL
_add_backend ( AP_InertialSensor_HIL : : detect ) ;
# elif HAL_INS_DEFAULT == HAL_INS_MPU6000
_add_backend ( AP_InertialSensor_MPU6000 : : detect ) ;
# elif HAL_INS_DEFAULT == HAL_INS_PX4 || HAL_INS_DEFAULT == HAL_INS_VRBRAIN
_add_backend ( AP_InertialSensor_PX4 : : detect ) ;
# elif HAL_INS_DEFAULT == HAL_INS_OILPAN
_add_backend ( AP_InertialSensor_Oilpan : : detect ) ;
# elif HAL_INS_DEFAULT == HAL_INS_MPU9250
_add_backend ( AP_InertialSensor_MPU9250 : : detect ) ;
# elif HAL_INS_DEFAULT == HAL_INS_FLYMAPLE
_add_backend ( AP_InertialSensor_Flymaple : : detect ) ;
# else
# error Unrecognised HAL_INS_TYPE setting
# endif
#if 0 // disabled due to broken hardware on some PXF capes
# if CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_LINUX_PXF
// the PXF also has a MPU6000
_add_backend ( AP_InertialSensor_MPU6000 : : detect ) ;
# endif
# endif
if ( _backend_count = = 0 | |
_gyro_count = = 0 | |
_accel_count = = 0 ) {
hal . scheduler - > panic ( PSTR ( " No INS backends available " ) ) ;
}
// set the product ID to the ID of the first backend
_product_id . set ( _backends [ 0 ] - > product_id ( ) ) ;
}
# if !defined( __AVR_ATmega1280__ )
// calibrate_accel - perform accelerometer calibration including providing user
// instructions and feedback Gauss-Newton accel calibration routines borrowed
// from Rolfe Schmidt blog post describing the method:
// http://chionophilous.wordpress.com/2011/10/24/accelerometer-calibration-iv-1-implementing-gauss-newton-on-an-atmega/
// original sketch available at
// http://rolfeschmidt.com/mathtools/skimetrics/adxl_gn_calibration.pde
bool AP_InertialSensor : : calibrate_accel ( AP_InertialSensor_UserInteract * interact ,
float & trim_roll ,
float & trim_pitch )
{
uint8_t num_accels = min ( get_accel_count ( ) , INS_MAX_INSTANCES ) ;
Vector3f samples [ INS_MAX_INSTANCES ] [ 6 ] ;
Vector3f new_offsets [ INS_MAX_INSTANCES ] ;
Vector3f new_scaling [ INS_MAX_INSTANCES ] ;
Vector3f orig_offset [ INS_MAX_INSTANCES ] ;
Vector3f orig_scale [ INS_MAX_INSTANCES ] ;
uint8_t num_ok = 0 ;
// exit immediately if calibration is already in progress
if ( _calibrating ) {
return false ;
}
_calibrating = true ;
/*
we do the accel calibration with no board rotation . This avoids
having to rotate readings during the calibration
*/
enum Rotation saved_orientation = _board_orientation ;
_board_orientation = ROTATION_NONE ;
for ( uint8_t k = 0 ; k < num_accels ; k + + ) {
// backup original offsets and scaling
orig_offset [ k ] = _accel_offset [ k ] . get ( ) ;
orig_scale [ k ] = _accel_scale [ k ] . get ( ) ;
// clear accelerometer offsets and scaling
_accel_offset [ k ] = Vector3f ( 0 , 0 , 0 ) ;
_accel_scale [ k ] = Vector3f ( 1 , 1 , 1 ) ;
}
memset ( samples , 0 , sizeof ( samples ) ) ;
// capture data from 6 positions
for ( uint8_t i = 0 ; i < 6 ; i + + ) {
const prog_char_t * msg ;
// display message to user
switch ( i ) {
case 0 :
msg = PSTR ( " level " ) ;
break ;
case 1 :
msg = PSTR ( " on its LEFT side " ) ;
break ;
case 2 :
msg = PSTR ( " on its RIGHT side " ) ;
break ;
case 3 :
msg = PSTR ( " nose DOWN " ) ;
break ;
case 4 :
msg = PSTR ( " nose UP " ) ;
break ;
default : // default added to avoid compiler warning
case 5 :
msg = PSTR ( " on its BACK " ) ;
break ;
}
interact - > printf_P (
PSTR ( " Place vehicle %S and press any key. \n " ) , msg ) ;
// wait for user input
if ( ! interact - > blocking_read ( ) ) {
//No need to use interact->printf_P for an error, blocking_read does this when it fails
goto failed ;
}
const uint8_t update_dt_milliseconds = ( uint8_t ) ( 1000.0f / get_sample_rate ( ) + 0.5f ) ;
// wait 100ms for ins filter to rise
for ( uint8_t k = 0 ; k < 100 / update_dt_milliseconds ; k + + ) {
wait_for_sample ( ) ;
update ( ) ;
hal . scheduler - > delay ( update_dt_milliseconds ) ;
}
uint32_t num_samples = 0 ;
while ( num_samples < 400 / update_dt_milliseconds ) {
wait_for_sample ( ) ;
// read samples from ins
update ( ) ;
// capture sample
for ( uint8_t k = 0 ; k < num_accels ; k + + ) {
Vector3f samp ;
if ( get_delta_velocity ( k , samp ) & & _delta_velocity_dt [ k ] > 0 ) {
samp / = _delta_velocity_dt [ k ] ;
} else {
samp = get_accel ( k ) ;
}
samples [ k ] [ i ] + = samp ;
if ( ! get_accel_health ( k ) ) {
interact - > printf_P ( PSTR ( " accel[%u] not healthy " ) , ( unsigned ) k ) ;
goto failed ;
}
}
hal . scheduler - > delay ( update_dt_milliseconds ) ;
num_samples + + ;
}
for ( uint8_t k = 0 ; k < num_accels ; k + + ) {
samples [ k ] [ i ] / = num_samples ;
}
}
// run the calibration routine
for ( uint8_t k = 0 ; k < num_accels ; k + + ) {
if ( ! _check_sample_range ( samples [ k ] , saved_orientation , interact ) ) {
interact - > printf_P ( PSTR ( " Insufficient accel range " ) ) ;
continue ;
}
bool success = _calibrate_accel ( samples [ k ] , new_offsets [ k ] , new_scaling [ k ] , saved_orientation ) ;
interact - > printf_P ( PSTR ( " Offsets[%u]: %.2f %.2f %.2f \n " ) ,
( unsigned ) k ,
new_offsets [ k ] . x , new_offsets [ k ] . y , new_offsets [ k ] . z ) ;
interact - > printf_P ( PSTR ( " Scaling[%u]: %.2f %.2f %.2f \n " ) ,
( unsigned ) k ,
new_scaling [ k ] . x , new_scaling [ k ] . y , new_scaling [ k ] . z ) ;
if ( success ) num_ok + + ;
}
if ( num_ok = = num_accels ) {
interact - > printf_P ( PSTR ( " Calibration successful \n " ) ) ;
for ( uint8_t k = 0 ; k < num_accels ; k + + ) {
// set and save calibration
_accel_offset [ k ] . set ( new_offsets [ k ] ) ;
_accel_scale [ k ] . set ( new_scaling [ k ] ) ;
}
_save_parameters ( ) ;
check_3D_calibration ( ) ;
/*
calculate the trims as well from primary accels
We use the original board rotation for this sample
*/
Vector3f level_sample = samples [ 0 ] [ 0 ] ;
level_sample . rotate ( saved_orientation ) ;
_calculate_trim ( level_sample , trim_roll , trim_pitch ) ;
_board_orientation = saved_orientation ;
_calibrating = false ;
return true ;
}
failed :
interact - > printf_P ( PSTR ( " Calibration FAILED \n " ) ) ;
// restore original scaling and offsets
for ( uint8_t k = 0 ; k < num_accels ; k + + ) {
_accel_offset [ k ] . set ( orig_offset [ k ] ) ;
_accel_scale [ k ] . set ( orig_scale [ k ] ) ;
}
_board_orientation = saved_orientation ;
_calibrating = false ;
return false ;
}
# endif
/*
check if the accelerometers are calibrated in 3 D . Called on startup
and any accel cal
*/
void AP_InertialSensor : : check_3D_calibration ( )
{
_have_3D_calibration = false ;
// check each accelerometer has offsets saved
for ( uint8_t i = 0 ; i < get_accel_count ( ) ; i + + ) {
// exactly 0.0 offset is extremely unlikely
if ( _accel_offset [ i ] . get ( ) . is_zero ( ) ) {
return ;
}
// exactly 1.0 scaling is extremely unlikely
const Vector3f & scaling = _accel_scale [ i ] . get ( ) ;
if ( fabsf ( scaling . x - 1.0f ) < 0.00001f & &
fabsf ( scaling . y - 1.0f ) < 0.00001f & &
fabsf ( scaling . z - 1.0f ) < 0.00001f ) {
return ;
}
}
// if we got this far the accelerometers must have been calibrated
_have_3D_calibration = true ;
}
/*
return true if we have 3 D calibration values
*/
bool AP_InertialSensor : : calibrated ( ) const
{
return _have_3D_calibration ;
}
void
AP_InertialSensor : : init_gyro ( )
{
_init_gyro ( ) ;
// save calibration
_save_parameters ( ) ;
}
// get_gyro_health_all - return true if all gyros are healthy
bool AP_InertialSensor : : get_gyro_health_all ( void ) const
{
for ( uint8_t i = 0 ; i < get_gyro_count ( ) ; i + + ) {
if ( ! get_gyro_health ( i ) ) {
return false ;
}
}
// return true if we have at least one gyro
return ( get_gyro_count ( ) > 0 ) ;
}
// gyro_calibration_ok_all - returns true if all gyros were calibrated successfully
bool AP_InertialSensor : : gyro_calibrated_ok_all ( ) const
{
for ( uint8_t i = 0 ; i < get_gyro_count ( ) ; i + + ) {
if ( ! gyro_calibrated_ok ( i ) ) {
return false ;
}
}
return ( get_gyro_count ( ) > 0 ) ;
}
// get_accel_health_all - return true if all accels are healthy
bool AP_InertialSensor : : get_accel_health_all ( void ) const
{
for ( uint8_t i = 0 ; i < get_accel_count ( ) ; i + + ) {
if ( ! get_accel_health ( i ) ) {
return false ;
}
}
// return true if we have at least one accel
return ( get_accel_count ( ) > 0 ) ;
}
void
AP_InertialSensor : : _init_gyro ( )
{
uint8_t num_gyros = min ( get_gyro_count ( ) , INS_MAX_INSTANCES ) ;
Vector3f last_average [ INS_MAX_INSTANCES ] , best_avg [ INS_MAX_INSTANCES ] ;
Vector3f new_gyro_offset [ INS_MAX_INSTANCES ] ;
float best_diff [ INS_MAX_INSTANCES ] ;
bool converged [ INS_MAX_INSTANCES ] ;
// exit immediately if calibration is already in progress
if ( _calibrating ) {
return ;
}
// record we are calibrating
_calibrating = true ;
// flash leds to tell user to keep the IMU still
AP_Notify : : flags . initialising = true ;
// cold start
hal . console - > print_P ( PSTR ( " Init Gyro " ) ) ;
/*
we do the gyro calibration with no board rotation . This avoids
having to rotate readings during the calibration
*/
enum Rotation saved_orientation = _board_orientation ;
_board_orientation = ROTATION_NONE ;
// remove existing gyro offsets
for ( uint8_t k = 0 ; k < num_gyros ; k + + ) {
_gyro_offset [ k ] . set ( Vector3f ( ) ) ;
new_gyro_offset [ k ] . zero ( ) ;
best_diff [ k ] = 0 ;
last_average [ k ] . zero ( ) ;
converged [ k ] = false ;
}
for ( int8_t c = 0 ; c < 5 ; c + + ) {
hal . scheduler - > delay ( 5 ) ;
update ( ) ;
}
// the strategy is to average 50 points over 0.5 seconds, then do it
// again and see if the 2nd average is within a small margin of
// the first
uint8_t num_converged = 0 ;
// we try to get a good calibration estimate for up to 30 seconds
// if the gyros are stable, we should get it in 1 second
for ( int16_t j = 0 ; j < = 30 * 4 & & num_converged < num_gyros ; j + + ) {
Vector3f gyro_sum [ INS_MAX_INSTANCES ] , gyro_avg [ INS_MAX_INSTANCES ] , gyro_diff [ INS_MAX_INSTANCES ] ;
Vector3f accel_start ;
float diff_norm [ INS_MAX_INSTANCES ] ;
uint8_t i ;
memset ( diff_norm , 0 , sizeof ( diff_norm ) ) ;
hal . console - > print_P ( PSTR ( " * " ) ) ;
for ( uint8_t k = 0 ; k < num_gyros ; k + + ) {
gyro_sum [ k ] . zero ( ) ;
}
accel_start = get_accel ( 0 ) ;
for ( i = 0 ; i < 50 ; i + + ) {
update ( ) ;
for ( uint8_t k = 0 ; k < num_gyros ; k + + ) {
gyro_sum [ k ] + = get_gyro ( k ) ;
}
hal . scheduler - > delay ( 5 ) ;
}
Vector3f accel_diff = get_accel ( 0 ) - accel_start ;
if ( accel_diff . length ( ) > 0.2f ) {
// the accelerometers changed during the gyro sum. Skip
// this sample. This copes with doing gyro cal on a
// steadily moving platform. The value 0.2 corresponds
// with around 5 degrees/second of rotation.
continue ;
}
for ( uint8_t k = 0 ; k < num_gyros ; k + + ) {
gyro_avg [ k ] = gyro_sum [ k ] / i ;
gyro_diff [ k ] = last_average [ k ] - gyro_avg [ k ] ;
diff_norm [ k ] = gyro_diff [ k ] . length ( ) ;
}
for ( uint8_t k = 0 ; k < num_gyros ; k + + ) {
if ( j = = 0 ) {
best_diff [ k ] = diff_norm [ k ] ;
best_avg [ k ] = gyro_avg [ k ] ;
} else if ( gyro_diff [ k ] . length ( ) < ToRad ( 0.1f ) ) {
// we want the average to be within 0.1 bit, which is 0.04 degrees/s
last_average [ k ] = ( gyro_avg [ k ] * 0.5f ) + ( last_average [ k ] * 0.5f ) ;
if ( ! converged [ k ] | | last_average [ k ] . length ( ) < new_gyro_offset [ k ] . length ( ) ) {
new_gyro_offset [ k ] = last_average [ k ] ;
}
if ( ! converged [ k ] ) {
converged [ k ] = true ;
num_converged + + ;
}
} else if ( diff_norm [ k ] < best_diff [ k ] ) {
best_diff [ k ] = diff_norm [ k ] ;
best_avg [ k ] = ( gyro_avg [ k ] * 0.5f ) + ( last_average [ k ] * 0.5f ) ;
}
last_average [ k ] = gyro_avg [ k ] ;
}
}
// we've kept the user waiting long enough - use the best pair we
// found so far
hal . console - > println ( ) ;
for ( uint8_t k = 0 ; k < num_gyros ; k + + ) {
if ( ! converged [ k ] ) {
hal . console - > printf_P ( PSTR ( " gyro[%u] did not converge: diff=%f dps \n " ) ,
( unsigned ) k , ToDeg ( best_diff [ k ] ) ) ;
_gyro_offset [ k ] = best_avg [ k ] ;
// flag calibration as failed for this gyro
_gyro_cal_ok [ k ] = false ;
} else {
_gyro_cal_ok [ k ] = true ;
_gyro_offset [ k ] = new_gyro_offset [ k ] ;
}
}
// restore orientation
_board_orientation = saved_orientation ;
// record calibration complete
_calibrating = false ;
// stop flashing leds
AP_Notify : : flags . initialising = false ;
}
# if !defined( __AVR_ATmega1280__ )
/*
check that the samples used for accel calibration have a sufficient
range on each axis . The sphere fit in _calibrate_accel ( ) can produce
bad offsets and scaling factors if the range of input data is
insufficient .
We rotate each sample in the check to body frame to cope with 45
board orientations which could result in smaller ranges . The sample
inputs are in sensor frame
*/
bool AP_InertialSensor : : _check_sample_range ( const Vector3f accel_sample [ 6 ] , enum Rotation rotation ,
AP_InertialSensor_UserInteract * interact )
{
// we want at least 12 m/s/s range on all axes. This should be
// very easy to achieve, and guarantees the accels have been
// exposed to a good range of data
const float min_range = 12.0f ;
Vector3f min_sample , max_sample ;
// start with first sample
min_sample = accel_sample [ 0 ] ;
min_sample . rotate ( rotation ) ;
max_sample = min_sample ;
for ( uint8_t s = 1 ; s < 6 ; s + + ) {
Vector3f sample = accel_sample [ s ] ;
sample . rotate ( rotation ) ;
for ( uint8_t i = 0 ; i < 3 ; i + + ) {
if ( sample [ i ] < min_sample [ i ] ) {
min_sample [ i ] = sample [ i ] ;
}
if ( sample [ i ] > max_sample [ i ] ) {
max_sample [ i ] = sample [ i ] ;
}
}
}
Vector3f range = max_sample - min_sample ;
interact - > printf_P ( PSTR ( " AccelRange: %.1f %.1f %.1f " ) ,
range . x , range . y , range . z ) ;
bool ok = ( range . x > = min_range & &
range . y > = min_range & &
range . z > = min_range ) ;
return ok ;
}
// _calibrate_model - perform low level accel calibration
// accel_sample are accelerometer samples collected in 6 different positions
// accel_offsets are output from the calibration routine
// accel_scale are output from the calibration routine
// returns true if successful
bool AP_InertialSensor : : _calibrate_accel ( const Vector3f accel_sample [ 6 ] ,
Vector3f & accel_offsets , Vector3f & accel_scale ,
enum Rotation rotation )
{
int16_t i ;
int16_t num_iterations = 0 ;
float eps = 0.000000001f ;
float change = 100.0f ;
float data [ 3 ] ;
float beta [ 6 ] ;
float delta [ 6 ] ;
float ds [ 6 ] ;
float JS [ 6 ] [ 6 ] ;
bool success = true ;
// reset
beta [ 0 ] = beta [ 1 ] = beta [ 2 ] = 0 ;
beta [ 3 ] = beta [ 4 ] = beta [ 5 ] = 1.0f / GRAVITY_MSS ;
while ( num_iterations < 20 & & change > eps ) {
num_iterations + + ;
_calibrate_reset_matrices ( ds , JS ) ;
for ( i = 0 ; i < 6 ; i + + ) {
data [ 0 ] = accel_sample [ i ] . x ;
data [ 1 ] = accel_sample [ i ] . y ;
data [ 2 ] = accel_sample [ i ] . z ;
_calibrate_update_matrices ( ds , JS , beta , data ) ;
}
_calibrate_find_delta ( ds , JS , delta ) ;
change = delta [ 0 ] * delta [ 0 ] +
delta [ 0 ] * delta [ 0 ] +
delta [ 1 ] * delta [ 1 ] +
delta [ 2 ] * delta [ 2 ] +
delta [ 3 ] * delta [ 3 ] / ( beta [ 3 ] * beta [ 3 ] ) +
delta [ 4 ] * delta [ 4 ] / ( beta [ 4 ] * beta [ 4 ] ) +
delta [ 5 ] * delta [ 5 ] / ( beta [ 5 ] * beta [ 5 ] ) ;
for ( i = 0 ; i < 6 ; i + + ) {
beta [ i ] - = delta [ i ] ;
}
}
// copy results out
accel_scale . x = beta [ 3 ] * GRAVITY_MSS ;
accel_scale . y = beta [ 4 ] * GRAVITY_MSS ;
accel_scale . z = beta [ 5 ] * GRAVITY_MSS ;
accel_offsets . x = beta [ 0 ] * accel_scale . x ;
accel_offsets . y = beta [ 1 ] * accel_scale . y ;
accel_offsets . z = beta [ 2 ] * accel_scale . z ;
// sanity check scale
if ( accel_scale . is_nan ( ) | | fabsf ( accel_scale . x - 1.0f ) > 0.1f | | fabsf ( accel_scale . y - 1.0f ) > 0.1f | | fabsf ( accel_scale . z - 1.0f ) > 0.1f ) {
success = false ;
}
// sanity check offsets (3.5 is roughly 3/10th of a G, 5.0 is roughly half a G)
if ( accel_offsets . is_nan ( ) | | fabsf ( accel_offsets . x ) > 3.5f | | fabsf ( accel_offsets . y ) > 3.5f | | fabsf ( accel_offsets . z ) > 3.5f ) {
success = false ;
}
// return success or failure
return success ;
}
void AP_InertialSensor : : _calibrate_update_matrices ( float dS [ 6 ] , float JS [ 6 ] [ 6 ] ,
float beta [ 6 ] , float data [ 3 ] )
{
int16_t j , k ;
float dx , b ;
float residual = 1.0f ;
float jacobian [ 6 ] ;
for ( j = 0 ; j < 3 ; j + + ) {
b = beta [ 3 + j ] ;
dx = ( float ) data [ j ] - beta [ j ] ;
residual - = b * b * dx * dx ;
jacobian [ j ] = 2.0f * b * b * dx ;
jacobian [ 3 + j ] = - 2.0f * b * dx * dx ;
}
for ( j = 0 ; j < 6 ; j + + ) {
dS [ j ] + = jacobian [ j ] * residual ;
for ( k = 0 ; k < 6 ; k + + ) {
JS [ j ] [ k ] + = jacobian [ j ] * jacobian [ k ] ;
}
}
}
// _calibrate_reset_matrices - clears matrices
void AP_InertialSensor : : _calibrate_reset_matrices ( float dS [ 6 ] , float JS [ 6 ] [ 6 ] )
{
int16_t j , k ;
for ( j = 0 ; j < 6 ; j + + ) {
dS [ j ] = 0.0f ;
for ( k = 0 ; k < 6 ; k + + ) {
JS [ j ] [ k ] = 0.0f ;
}
}
}
void AP_InertialSensor : : _calibrate_find_delta ( float dS [ 6 ] , float JS [ 6 ] [ 6 ] , float delta [ 6 ] )
{
//Solve 6-d matrix equation JS*x = dS
//first put in upper triangular form
int16_t i , j , k ;
float mu ;
//make upper triangular
for ( i = 0 ; i < 6 ; i + + ) {
//eliminate all nonzero entries below JS[i][i]
for ( j = i + 1 ; j < 6 ; j + + ) {
mu = JS [ i ] [ j ] / JS [ i ] [ i ] ;
if ( mu ! = 0.0f ) {
dS [ j ] - = mu * dS [ i ] ;
for ( k = j ; k < 6 ; k + + ) {
JS [ k ] [ j ] - = mu * JS [ k ] [ i ] ;
}
}
}
}
//back-substitute
for ( i = 5 ; i > = 0 ; i - - ) {
dS [ i ] / = JS [ i ] [ i ] ;
JS [ i ] [ i ] = 1.0f ;
for ( j = 0 ; j < i ; j + + ) {
mu = JS [ i ] [ j ] ;
dS [ j ] - = mu * dS [ i ] ;
JS [ i ] [ j ] = 0.0f ;
}
}
for ( i = 0 ; i < 6 ; i + + ) {
delta [ i ] = dS [ i ] ;
}
}
// _calculate_trim - calculates the x and y trim angles (in radians) given a raw accel sample (i.e. no scaling or offsets applied) taken when the vehicle was level
void AP_InertialSensor : : _calculate_trim ( const Vector3f & accel_sample , float & trim_roll , float & trim_pitch )
{
// scale sample and apply offsets
const Vector3f & accel_scale = _accel_scale [ 0 ] . get ( ) ;
const Vector3f & accel_offsets = _accel_offset [ 0 ] . get ( ) ;
Vector3f scaled_accels_x ( accel_sample . x * accel_scale . x - accel_offsets . x ,
0 ,
accel_sample . z * accel_scale . z - accel_offsets . z ) ;
Vector3f scaled_accels_y ( 0 ,
accel_sample . y * accel_scale . y - accel_offsets . y ,
accel_sample . z * accel_scale . z - accel_offsets . z ) ;
// calculate x and y axis angle (i.e. roll and pitch angles)
Vector3f vertical = Vector3f ( 0 , 0 , - 1 ) ;
trim_roll = scaled_accels_y . angle ( vertical ) ;
trim_pitch = scaled_accels_x . angle ( vertical ) ;
// angle call doesn't return the sign so take care of it here
if ( scaled_accels_y . y > 0 ) {
trim_roll = - trim_roll ;
}
if ( scaled_accels_x . x < 0 ) {
trim_pitch = - trim_pitch ;
}
}
# endif // __AVR_ATmega1280__
// save parameters to eeprom
void AP_InertialSensor : : _save_parameters ( )
{
_product_id . save ( ) ;
for ( uint8_t i = 0 ; i < INS_MAX_INSTANCES ; i + + ) {
_accel_scale [ i ] . save ( ) ;
_accel_offset [ i ] . save ( ) ;
_gyro_offset [ i ] . save ( ) ;
}
}
/*
update gyro and accel values from backends
*/
void AP_InertialSensor : : update ( void )
{
// during initialisation update() may be called without
// wait_for_sample(), and a wait is implied
wait_for_sample ( ) ;
if ( ! _hil_mode ) {
for ( uint8_t i = 0 ; i < INS_MAX_INSTANCES ; i + + ) {
// mark sensors unhealthy and let update() in each backend
// mark them healthy via _publish_gyro() and
// _publish_accel()
_gyro_healthy [ i ] = false ;
_accel_healthy [ i ] = false ;
_delta_velocity_valid [ i ] = false ;
_delta_angle_valid [ i ] = false ;
}
for ( uint8_t i = 0 ; i < _backend_count ; i + + ) {
_backends [ i ] - > update ( ) ;
}
// adjust health status if a sensor has a non-zero error count
// but another sensor doesn't.
bool have_zero_accel_error_count = false ;
bool have_zero_gyro_error_count = false ;
for ( uint8_t i = 0 ; i < INS_MAX_INSTANCES ; i + + ) {
if ( _accel_healthy [ i ] & & _accel_error_count [ i ] = = 0 ) {
have_zero_accel_error_count = true ;
}
if ( _gyro_healthy [ i ] & & _gyro_error_count [ i ] = = 0 ) {
have_zero_gyro_error_count = true ;
}
}
for ( uint8_t i = 0 ; i < INS_MAX_INSTANCES ; i + + ) {
if ( _gyro_healthy [ i ] & & _gyro_error_count [ i ] ! = 0 & & have_zero_gyro_error_count ) {
// we prefer not to use a gyro that has had errors
_gyro_healthy [ i ] = false ;
}
if ( _accel_healthy [ i ] & & _accel_error_count [ i ] ! = 0 & & have_zero_accel_error_count ) {
// we prefer not to use a accel that has had errors
_accel_healthy [ i ] = false ;
}
}
// set primary to first healthy accel and gyro
for ( uint8_t i = 0 ; i < INS_MAX_INSTANCES ; i + + ) {
if ( _gyro_healthy [ i ] ) {
_primary_gyro = i ;
break ;
}
}
for ( uint8_t i = 0 ; i < INS_MAX_INSTANCES ; i + + ) {
if ( _accel_healthy [ i ] ) {
_primary_accel = i ;
break ;
}
}
}
_have_sample = false ;
}
/*
wait for a sample to be available . This is the function that
determines the timing of the main loop in ardupilot .
Ideally this function would return at exactly the rate given by the
sample_rate argument given to AP_InertialSensor : : init ( ) .
The key output of this function is _delta_time , which is the time
over which the gyro and accel integration will happen for this
sample . We want that to be a constant time if possible , but if
delays occur we need to cope with them . The long term sum of
_delta_time should be exactly equal to the wall clock elapsed time
*/
void AP_InertialSensor : : wait_for_sample ( void )
{
if ( _have_sample ) {
// the user has called wait_for_sample() again without
// consuming the sample with update()
return ;
}
uint32_t now = hal . scheduler - > micros ( ) ;
if ( _next_sample_usec = = 0 & & _delta_time < = 0 ) {
// this is the first call to wait_for_sample()
_last_sample_usec = now - _sample_period_usec ;
_next_sample_usec = now + _sample_period_usec ;
goto check_sample ;
}
// see how long it is till the next sample is due
if ( _next_sample_usec - now < = _sample_period_usec ) {
// we're ahead on time, schedule next sample at expected period
uint32_t wait_usec = _next_sample_usec - now ;
hal . scheduler - > delay_microseconds_boost ( wait_usec ) ;
uint32_t now2 = hal . scheduler - > micros ( ) ;
if ( now2 + 100 < _next_sample_usec ) {
timing_printf ( " shortsleep %u \n " , ( unsigned ) ( _next_sample_usec - now2 ) ) ;
}
if ( now2 > _next_sample_usec + 400 ) {
timing_printf ( " longsleep %u wait_usec=%u \n " ,
( unsigned ) ( now2 - _next_sample_usec ) ,
( unsigned ) wait_usec ) ;
}
_next_sample_usec + = _sample_period_usec ;
} else if ( now - _next_sample_usec < _sample_period_usec / 8 ) {
// we've overshot, but only by a small amount, keep on
// schedule with no delay
timing_printf ( " overshoot1 %u \n " , ( unsigned ) ( now - _next_sample_usec ) ) ;
_next_sample_usec + = _sample_period_usec ;
} else {
// we've overshot by a larger amount, re-zero scheduling with
// no delay
timing_printf ( " overshoot2 %u \n " , ( unsigned ) ( now - _next_sample_usec ) ) ;
_next_sample_usec = now + _sample_period_usec ;
}
check_sample :
if ( ! _hil_mode ) {
// we also wait for at least one backend to have a sample of both
// accel and gyro. This normally completes immediately.
bool gyro_available = false ;
bool accel_available = false ;
while ( ! gyro_available | | ! accel_available ) {
for ( uint8_t i = 0 ; i < _backend_count ; i + + ) {
gyro_available | = _backends [ i ] - > gyro_sample_available ( ) ;
accel_available | = _backends [ i ] - > accel_sample_available ( ) ;
}
if ( ! gyro_available | | ! accel_available ) {
hal . scheduler - > delay_microseconds ( 100 ) ;
}
}
}
now = hal . scheduler - > micros ( ) ;
_delta_time = ( now - _last_sample_usec ) * 1.0e-6 f ;
_last_sample_usec = now ;
#if 0
{
static uint64_t delta_time_sum ;
static uint16_t counter ;
if ( delta_time_sum = = 0 ) {
delta_time_sum = _sample_period_usec ;
}
delta_time_sum + = _delta_time * 1.0e6 f ;
if ( counter + + = = 400 ) {
counter = 0 ;
hal . console - > printf ( " now=%lu _delta_time_sum=%lu diff=%ld \n " ,
( unsigned long ) now ,
( unsigned long ) delta_time_sum ,
( long ) ( now - delta_time_sum ) ) ;
}
}
# endif
_have_sample = true ;
}
/*
support for setting accel and gyro vectors , for use by HIL
*/
void AP_InertialSensor : : set_accel ( uint8_t instance , const Vector3f & accel )
{
if ( _accel_count = = 0 ) {
// we haven't initialised yet
return ;
}
if ( instance < INS_MAX_INSTANCES ) {
_accel [ instance ] = accel ;
_accel_healthy [ instance ] = true ;
if ( _accel_count < = instance ) {
_accel_count = instance + 1 ;
}
}
}
void AP_InertialSensor : : set_gyro ( uint8_t instance , const Vector3f & gyro )
{
if ( _gyro_count = = 0 ) {
// we haven't initialised yet
return ;
}
if ( instance < INS_MAX_INSTANCES ) {
_gyro [ instance ] = gyro ;
_gyro_healthy [ instance ] = true ;
if ( _gyro_count < = instance ) {
_gyro_count = instance + 1 ;
_gyro_cal_ok [ instance ] = true ;
}
}
}