How to use an external analog reference in a library?

I have an Arduino Mega 2560 with an analog 6DOF IMU connected. I have used analogReference(EXTERNAL) to use a 3.3V voltage connected to the Aref pin as my analog reference. That works fine when I perform analogRead's in my main program. However, while trying to get fancier I have modified a sensor fusion library and have it performing analogRead's in the library. I have found that if I specify an external analog reference in my main program, the readings coming back from the library are screwed up (changing randomly). Meanwhile the values from analogRead's in the program are fine. I've found some information that libraries set the analog reference back to default (?), so I tried setting the the analog reference to external in the library before I do an analogRead, but this did not work. Does anyone know how to do this correctly?

I suppose I can remove the analogRead's from the library and pass values in to it, but it would be nicer if it worked the way I have it now.

I've found some information that libraries set the analog reference back to default (?), so I tried setting the the analog reference to external in the library before I do an analogRead, but this did not work.

Where did you find this misinformation? The analogRead() function has no idea whether it is called from the sketch or a class. In fact, if you look at the resulting hex file, you will find no clues that any classes were ever involved in the generation of the hex file.

So, something must be wrong with your class. I'd hazard a guess that you are calling analogReference() in the constructor, which is not the correct place to call it.

You need to post your code.

I found that information on someone's blog, but I can't find it now. Anyway, my code is below. I'm calling analogReference() in the setup() function of my program. As I said, if I remove it everything works fine (although I imagine my sensor calibrations are off). I tried placing analogReference() in the functions in the library right before I call analogRead(), but that didn't change the behavior at all.

The basic flow of the program is:
call init() which reads the sensor to get the starting angle
call getEuler() which calls getQ() which calls getValues(). getValues() uses analogRead().

My main program:

#include <FreeSixIMUanalog.h>

float angles[3];

int azpin=4;  // imu analog input pins
int aypin=3;
int axpin=5;
int gxpin=0;
int gypin=1;
int gzpin=2;

FreeSixIMUanalog sixDOF = FreeSixIMUanalog(axpin,aypin,azpin,gxpin,gypin,gzpin);

void setup() {
  
  // debug setup
  Serial.begin(9600);
  
  // set analog reference voltage to external voltage, which is hooked to 3.3V
  analogReference(EXTERNAL);
  
  delay(50);
  sixDOF.init(100); //begin the IMU, read 100 samples
  delay(5);
}

void loop() { 

  sixDOF.getEuler(angles);
  
  Serial.print(angles[0]);
  Serial.print(" | ");  
  Serial.print(angles[1]);
  Serial.print(" | ");
  Serial.print(angles[2]);
  Serial.println(" | ");
  delay(70); 
}

The library header:

#include "Arduino.h"

#ifndef FreeSixIMUanalog_h
#define FreeSixIMUanalog_h

#define twoKpDef  (2.0f * 0.5f) // 2 * proportional gain
#define twoKiDef  (2.0f * 0.1f) // 2 * integral gain

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif

class FreeSixIMUanalog
{
  public:
    FreeSixIMUanalog(int axpin, int aypin, int azpin, int gxpin, int gypin, int gzpin);
    void init();
    void init(int init_samples);
    void getRawValues(int * raw_values);
    void getValues(float * values);
    void getQ(float * q);
    void getEuler(float * angles);
    void getYawPitchRoll(float * ypr);
    void getAngles(float * angles);
       
    int* raw_acc, raw_gyro, raw_magn;
    
  private:
    int _axpin, _aypin, _azpin, _gxpin, _gypin, _gzpin;
    int _gx_zero, _gy_zero, _gz_zero;
    void AHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz);
    float iq0, iq1, iq2, iq3;
    float exInt, eyInt, ezInt;  // scaled integral error
    volatile float twoKp;      // 2 * proportional gain (Kp)
    volatile float twoKi;      // 2 * integral gain (Ki)
    volatile float q0, q1, q2, q3; // quaternion of sensor frame relative to auxiliary frame
    volatile float integralFBx,  integralFBy, integralFBz;
    unsigned long lastUpdate, now; // sample period expressed in milliseconds
    float sampleFreq; // half the sample period expressed in seconds
    int startLoopTime;
};

float invSqrt(float number);

#endif // FreeSixIMUanalog_h

The library source:

#include <inttypes.h>
#include "FreeSixIMUanalog.h"

FreeSixIMUanalog::FreeSixIMUanalog(int axpin, int aypin, int azpin, int gxpin, int gypin, int gzpin) {
	
  // store pin numbers
  _axpin=axpin;
  _aypin=aypin;
  _azpin=azpin;
  _gxpin=gxpin;
  _gypin=gypin;
  _gzpin=gzpin;

  // initialize quaternion
  q0 = 1.0f;
  q1 = 0.0f;
  q2 = 0.0f;
  q3 = 0.0f;
  exInt = 0.0;
  eyInt = 0.0;
  ezInt = 0.0;
  twoKp = twoKpDef;
  twoKi = twoKiDef;
  integralFBx = 0.0f,  integralFBy = 0.0f, integralFBz = 0.0f;
  lastUpdate = 0;
  now = 0;
}

void FreeSixIMUanalog::init() {
  init(100);
}

void FreeSixIMUanalog::init(int init_samples) {
  // delay 10s to allow gyros to stabilize, then take 20 averages for zero of gyros and accel's to get initial angle
  delay(10000);
  for(int x=init_samples; x>0; x--){
    _gx_zero=_gx_zero+analogRead(_gxpin);
    _gy_zero=_gy_zero+analogRead(_gypin);
    _gz_zero=_gz_zero+analogRead(_gzpin);
    delay(10);
  }
  
  // average gyro and accel readings taken
  _gx_zero=_gx_zero/init_samples;
  _gy_zero=_gy_zero/init_samples;
  _gz_zero=_gz_zero/init_samples;
}

void FreeSixIMUanalog::getValues(float * values) {  
  float ax_sens=(3.3/1024)/(.3*3.3/3);                     // accelerometer sensitivies in g's/count
  float ay_sens=(3.3/1024)/(.3*3.3/3);
  float az_sens=(3.3/1024)/(.3*3.3/3);
  int ax_zero=(1024/3.3)*(1.5*3.3/3);                      // accelerometer zero points in counts
  int ay_zero=(1024/3.3)*(1.48*3.3/3);
  int az_zero=(1024/3.3)*(1.56*3.3/3);
  float gx_sens=3.3/1024/.00333;                           // gyro sensitivies in deg/s/count
  float gy_sens=3.3/1024/.00333;
  float gz_sens=3.3/1024/.00333;

  int val[3];
  // read accelerometer values
  val[0]=analogRead(_axpin);
  val[1]=analogRead(_aypin);
  val[2]=analogRead(_azpin);
  values[0] = ((float) val[0]);
  values[1] = ((float) val[1]);
  values[2] = ((float) val[2]);
  // convert accel readings to acceleration in g's
  values[0] = (values[0]-ax_zero)*ax_sens;
  values[1] = (values[1]-ay_zero)*ay_sens;
  values[2] = (values[2]-az_zero)*az_sens;

  
  // read gyro values
  // for gyro y rotating around accel x in negative direction
  // gyro x rotating around accel y in negative direction
  // gyro z rotating around accel z in positive direction
  val[0]=analogRead(_gxpin);
  val[1]=analogRead(_gypin);
  val[2]=analogRead(_gzpin);
  values[4] = (((float) val[0])-_gx_zero)*gx_sens;
  values[3] = (((float) val[1])-_gy_zero)*-gy_sens;
  values[5] = (((float) val[2])-_gz_zero)*gz_sens;
}


// Quaternion implementation of the 'DCM filter' [Mayhony et al].  Incorporates the magnetic distortion
// compensation algorithms from Sebastian Madgwick filter which eliminates the need for a reference
// direction of flux (bx bz) to be predefined and limits the effect of magnetic distortions to yaw
// axis only.
//
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
//=====================================================================================================
void FreeSixIMUanalog::AHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz) {
  float recipNorm;
  float q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3;
  float halfex = 0.0f, halfey = 0.0f, halfez = 0.0f;
  float qa, qb, qc;

  // Auxiliary variables to avoid repeated arithmetic
  q0q0 = q0 * q0;
  q0q1 = q0 * q1;
  q0q2 = q0 * q2;
  q0q3 = q0 * q3;
  q1q1 = q1 * q1;
  q1q2 = q1 * q2;
  q1q3 = q1 * q3;
  q2q2 = q2 * q2;
  q2q3 = q2 * q3;
  q3q3 = q3 * q3;
      
  // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
  if((ax != 0.0f) && (ay != 0.0f) && (az != 0.0f)) {
    float halfvx, halfvy, halfvz;
    
    // Normalise accelerometer measurement
    recipNorm = invSqrt(ax * ax + ay * ay + az * az);
    ax *= recipNorm;
    ay *= recipNorm;
    az *= recipNorm;
    
    // Estimated direction of gravity
    halfvx = q1q3 - q0q2;
    halfvy = q0q1 + q2q3;
    halfvz = q0q0 - 0.5f + q3q3;
  
    // Error is sum of cross product between estimated direction and measured direction of field vectors
    halfex += (ay * halfvz - az * halfvy);
    halfey += (az * halfvx - ax * halfvz);
    halfez += (ax * halfvy - ay * halfvx);
  }

  // Apply feedback only when valid data has been gathered from the accelerometer or magnetometer
  if(halfex != 0.0f && halfey != 0.0f && halfez != 0.0f) {
    // Compute and apply integral feedback if enabled
    if(twoKi > 0.0f) {
      integralFBx += twoKi * halfex * (1.0f / sampleFreq);  // integral error scaled by Ki
      integralFBy += twoKi * halfey * (1.0f / sampleFreq);
      integralFBz += twoKi * halfez * (1.0f / sampleFreq);
      gx += integralFBx;  // apply integral feedback
      gy += integralFBy;
      gz += integralFBz;
    }
    else {
      integralFBx = 0.0f; // prevent integral windup
      integralFBy = 0.0f;
      integralFBz = 0.0f;
    }

    // Apply proportional feedback
    gx += twoKp * halfex;
    gy += twoKp * halfey;
    gz += twoKp * halfez;
  }
  
  // Integrate rate of change of quaternion
  gx *= (0.5f * (1.0f / sampleFreq));   // pre-multiply common factors
  gy *= (0.5f * (1.0f / sampleFreq));
  gz *= (0.5f * (1.0f / sampleFreq));
  qa = q0;
  qb = q1;
  qc = q2;
  q0 += (-qb * gx - qc * gy - q3 * gz);
  q1 += (qa * gx + qc * gz - q3 * gy);
  q2 += (qa * gy - qb * gz + q3 * gx);
  q3 += (qa * gz + qb * gy - qc * gx);
  
  // Normalise quaternion
  recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
  q0 *= recipNorm;
  q1 *= recipNorm;
  q2 *= recipNorm;
  q3 *= recipNorm;
}


void FreeSixIMUanalog::getQ(float * q) {
  float val[9];
  getValues(val);
    
  now = micros();
  sampleFreq = 1.0 / ((now - lastUpdate) / 1000000.0);
  lastUpdate = now;
  // gyro values are expressed in deg/sec, the * M_PI/180 will convert it to radians/sec
  //AHRSupdate(val[3] * M_PI/180, val[4] * M_PI/180, val[5] * M_PI/180, val[0], val[1], val[2], val[6], val[7], val[8]);
  // use the call below when using a 6DOF IMU
  AHRSupdate(val[3] * M_PI/180, val[4] * M_PI/180, val[5] * M_PI/180, val[0], val[1], val[2], 0, 0, 0);
  q[0] = q0;
  q[1] = q1;
  q[2] = q2;
  q[3] = q3;
}

// Returns the Euler angles in degrees defined with the Aerospace sequence.
// See Sebastian O.H. Madwick report 
// "An efficient orientation filter for inertial and intertial/magnetic sensor arrays" Chapter 2 Quaternion representation
void FreeSixIMUanalog::getEuler(float * angles) {
float q[4]; // quaternion
  getQ(q);
  angles[0] = atan2(2 * q[1] * q[2] - 2 * q[0] * q[3], 2 * q[0]*q[0] + 2 * q[1] * q[1] - 1) * 180/M_PI; // psi
  angles[1] = -asin(2 * q[1] * q[3] + 2 * q[0] * q[2]) * 180/M_PI; // theta
  angles[2] = atan2(2 * q[2] * q[3] - 2 * q[0] * q[1], 2 * q[0] * q[0] + 2 * q[3] * q[3] - 1) * 180/M_PI; // phi
}

float invSqrt(float number) {
  volatile long i;
  volatile float x, y;
  volatile const float f = 1.5F;

  x = number * 0.5F;
  y = number;
  i = * ( long * ) &y;
  i = 0x5f375a86 - ( i >> 1 );
  y = * ( float * ) &i;
  y = y * ( f - ( x * y * y ) );
  return y;
}

Nevermind, I found my problem. I was overflowing variables when I was zeroing the gyros. The reason analogReference(EXTERNAL) made a difference is that using the lower reference voltage caused the gyro readings to be a higher value. When I took an average, I added up 100 of these larger values which overflowed the variable which led to screwy results from the rest of the algorithm.