Subversion Repositories FlightCtrl

Rev

Rev 2164 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdlib.h>
#include <stdio.h>

#include "analog.h"
#include "sensors.h"
// for Delay functions used in calibration.
#include "timer0.h"
// For reading and writing acc. meter offsets.
#include "eeprom.h"
#include "debug.h"

// set ADC enable & ADC Start Conversion & ADC Interrupt Enable bit
#define startADC() (ADCSRA |= (1<<ADEN)|(1<<ADSC)|(1<<ADIE))

// TODO: Off to PROGMEM .
const char* recal = ", recalibration needed.";

/*
 * Gyro and accelerometer values for attitude computation.
 * Unfiltered (this is unnecessary as noise should get absorbed in DCM).
 * Normalized to rad/s.
 * Data flow: ADCs (1 iteration) --> samplingADCData --offsetting-->  gyro_attitude_tmp
 * --rotation-->
 * [filtering] --> gyro_attitude.
 * Altimeter is also considered part of the "long" attitude loop.
 */

Vector3f gyro_attitude;
Vector3f accel;

/*
 * This stuff is for the aircraft control thread. It runs in unprocessed integers.
 * (well some sort of scaling will be required).
 * Data flow: ADCs (1 iteration) -> samplingADCData -> [offsetting and rotation] ->
 * [filtering] --> gyro_control
 */

int16_t gyro_control[3];
int16_t gyroD[2];
int16_t gyroDWindow[2][GYRO_D_WINDOW_LENGTH];
uint8_t gyroDWindowIdx;

/*
 * Air pressure. TODO: Might as well convert to floats / well known units.
 */

int32_t groundPressure;
int16_t dHeight;

/*
 * Offset values. These are the raw gyro and acc. meter sums when the copter is
 * standing still. They are used for adjusting the gyro and acc. meter values
 * to be centered on zero.
 */

sensorOffset_t gyroOffset;
sensorOffset_t accelOffset;
sensorOffset_t gyroAmplifierOffset;

/*
 * Redo this to that quadrant 0 is normal with an FC2.x.
 */

void rotate(int16_t* result, uint8_t quadrant, uint8_t reverse) {
    static const int8_t rotationTab[] = { 1, 1, 0, -1, -1, -1, 0, 1 };
    // Pitch to Pitch part
    int8_t xx = reverse ? rotationTab[(quadrant + 4) & 7] : rotationTab[quadrant]; // 1
    // Roll to Pitch part
    int8_t xy = rotationTab[(quadrant + 2) & 7]; // -1
    // Pitch to Roll part
    int8_t yx = reverse ? rotationTab[(quadrant + 2) & 7] : rotationTab[(quadrant + 6) & 7]; // -1  
    // Roll to Roll part
    int8_t yy = rotationTab[quadrant]; // -1

    int16_t xIn = result[0];
    int32_t tmp0, tmp1;

    tmp0 = xx * xIn + xy * result[1];
    tmp1 = yx * xIn + yy * result[1];

    if (quadrant & 1) {
        tmp0 = (tmp0 * 181L) >> 8;
        tmp1 = (tmp1 * 181L) >> 8;
    }

    result[0] = (int16_t) tmp0;
    result[1] = (int16_t) tmp1;
}

/*
 * Air pressure
 */

volatile uint8_t rangewidth = 105;

// Direct from sensor, irrespective of range.

// Value of 2 samples, with range.
uint16_t simpleAirPressure;

// Value of AIRPRESSURE_OVERSAMPLING samples, with range, filtered.
int32_t filteredAirPressure;

#define MAX_D_AIRPRESSURE_WINDOW_LENGTH 32
//int32_t lastFilteredAirPressure;
int16_t dAirPressureWindow[MAX_D_AIRPRESSURE_WINDOW_LENGTH];
uint8_t dWindowPtr = 0;

#define MAX_AIRPRESSURE_WINDOW_LENGTH 32
int16_t airPressureWindow[MAX_AIRPRESSURE_WINDOW_LENGTH];
int32_t windowedAirPressure;
uint8_t windowPtr = 0;

// Partial sum of AIRPRESSURE_SUMMATION_FACTOR samples.
int32_t airPressureSum;

// The number of samples summed into airPressureSum so far.
uint8_t pressureSumCount;

/*
 * Battery voltage, in units of: 1k/11k / 3V * 1024 = 31.03 per volt.
 * That is divided by 3 below, for a final 10.34 per volt.
 * So the initial value of 100 is for 9.7 volts.
 */

int16_t UBat = 100;

/*
 * Control and status.
 */

volatile uint16_t samplingADCData[8];
volatile uint16_t attitudeADCData[8];

volatile uint8_t analog_controlDataStatus = CONTROL_SENSOR_DATA_READY;
volatile uint8_t analog_attitudeDataStatus = ATTITUDE_SENSOR_NO_DATA;
// Number of ADC iterations done for current attitude loop.
volatile uint8_t attitudeSumCount;

volatile uint8_t ADCSampleCount;
volatile uint8_t adChannel;


const uint8_t channelsForStates[] PROGMEM = {
    AD_GYRO_X,
    AD_GYRO_Y,
    AD_GYRO_Z,

    AD_ACCEL_X,
    AD_ACCEL_Y,

    AD_GYRO_X,
    AD_GYRO_Y,
    //AD_GYRO_Z,

    AD_ACCEL_Z,
    AD_AIRPRESSURE,

    AD_GYRO_X,
    AD_GYRO_Y,
    AD_GYRO_Z,

    AD_ACCEL_X,
    AD_ACCEL_Y,

    AD_GYRO_X,
    AD_GYRO_Y,
    //AD_GYRO_Z,

    //AD_ACCEL_Z,
    //AD_AIRPRESSURE,

    AD_GYRO_X,
    AD_GYRO_Y,
    AD_GYRO_Z,

    AD_ACCEL_X,
    AD_ACCEL_Y,

    AD_GYRO_X,
    AD_GYRO_Y,
    //AD_GYRO_Z,

    AD_ACCEL_Z,
    AD_AIRPRESSURE,

    AD_GYRO_Y,
    AD_GYRO_X,
    AD_GYRO_Z,

    AD_ACCEL_X,
    AD_ACCEL_Y,

    AD_GYRO_X,
    AD_GYRO_Y,
    // AD_GYRO_Z,

    //AD_ACCEL_Z,
    //AD_AIRPRESSURE,
    AD_UBAT
};

// Feature removed. Could be reintroduced later - but should work for all gyro types then.
// uint8_t GyroDefectPitch = 0, GyroDefectRoll = 0, GyroDefectYaw = 0;

void analog_init(void) {
    uint8_t sreg = SREG;
    // disable all interrupts before reconfiguration
    cli();

    //ADC0 ... ADC7 is connected to PortA pin 0 ... 7
    DDRA = 0x00;
    PORTA = 0x00;
    // Digital Input Disable Register 0
    // Disable digital input buffer for analog adc_channel pins
    DIDR0 = 0xFF;
    // external reference, adjust data to the right
    ADMUX &= ~((1 << REFS1) | (1 << REFS0) | (1 << ADLAR));
    // set muxer to ADC adc_channel 0 (0 to 7 is a valid choice)
    ADMUX = (ADMUX & 0xE0);
    //Set ADC Control and Status Register A
    //Auto Trigger Enable, Prescaler Select Bits to Division Factor 128, i.e. ADC clock = SYSCKL/128 = 156.25 kHz
    ADCSRA = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
    //Set ADC Control and Status Register B
    //Trigger Source to Free Running Mode
    ADCSRB &= ~((1 << ADTS2) | (1 << ADTS1) | (1 << ADTS0));

    for (uint8_t i = 0; i < MAX_AIRPRESSURE_WINDOW_LENGTH; i++) {
        airPressureWindow[i] = 0;
    }

    windowedAirPressure = 0;

    startADCCycle();

    // restore global interrupt flags
    SREG = sreg;
}

// Convert axis number (X, Y, Z to ADC channel mapping (1, 2, 0)
uint16_t gyroValue(uint8_t axis, volatile uint16_t dataArray[]) {
    switch (axis) {
    case X:
        return dataArray[AD_GYRO_X];
    case Y:
        return dataArray[AD_GYRO_Y];
    case Z:
        return dataArray[AD_GYRO_Z];
    default:
        return 0; // should never happen.
    }
}

uint16_t gyroValueForFC13DACCalibration(uint8_t axis) {
    return gyroValue(axis, samplingADCData);
}

// Convert axis number (X, Y, Z to ADC channel mapping (6, 7, 5)
uint16_t accValue(uint8_t axis, volatile uint16_t dataArray[]) {
    switch (axis) {
    case X:
        return dataArray[AD_ACCEL_X];
    case Y:
        return dataArray[AD_ACCEL_Y];
    case Z:
        return dataArray[AD_ACCEL_Z];
    default:
        return 0; // should never happen.
    }
}

/*
 * Min.: 0
 * Max: About 106 * 240 + 2047 = 27487; it is OK with just a 16 bit type.
 */

uint16_t getSimplePressure(int advalue) {
    uint16_t result = (uint16_t) OCR0A * /*(uint16_t)*/ rangewidth + advalue;
    result += (/*accel.z*/0 * (staticParams.airpressureAccZCorrection - 128)) >> 10;
    return result;
}

void startADCCycle(void) {
    for (uint8_t i=0; i<8; i++) {
        samplingADCData[i] = 0;
    }
    ADCSampleCount = 0;
    adChannel = AD_GYRO_X;
    ADMUX = (ADMUX & 0xE0) | adChannel;
    analog_controlDataStatus = CONTROL_SENSOR_SAMPLING_DATA;
    J4HIGH;
    startADC();
}

/*****************************************************
 * Interrupt Service Routine for ADC
 * Runs at 12 kHz. When all states are processed
 * further conversions are stopped.
 *****************************************************/

ISR( ADC_vect) {
    samplingADCData[adChannel] += ADC;
    // set up for next state.
    ADCSampleCount++;
    if (ADCSampleCount < sizeof(channelsForStates)) {
        adChannel = pgm_read_byte(&channelsForStates[ADCSampleCount]);
        // set adc muxer to next adChannel
        ADMUX = (ADMUX & 0xE0) | adChannel;
        // after full cycle stop further interrupts
        startADC();
    } else {
        J4LOW;
        analog_controlDataStatus = CONTROL_SENSOR_DATA_READY;
        // do not restart ADC converter.
    }
}

/*
 * Used in calibration only!
 * Wait the specified number of millis, and then run a full sensor ADC cycle.
 */

void waitADCCycle(uint16_t delay) {
    delay_ms(delay);
    startADCCycle();
    while(analog_controlDataStatus != CONTROL_SENSOR_DATA_READY)
        ;
}

void analog_updateControlData(void) {
    /*
     * 1) Near-saturation boost (dont bother with Z)
     * 2) Offset
     * 3) Rotation
     * 4) Filter
     * 5) Extract gyroD (should this be without near-saturation boost really? Ignore issue)
     */


    int16_t tempOffsetGyro[2];
    int16_t tempGyro;
   
    for (uint8_t axis=X; axis<=Y; axis++) {
        tempGyro = gyroValue(axis, samplingADCData);
        //debugOut.analog[3 + axis] = tempGyro;
        //debugOut.analog[3 + 2] = gyroValue(Z, samplingADCData);

        /*
         * Process the gyro data for the PID controller.
         */

        // 1) Extrapolate: Near the ends of the range, we boost the input significantly. This simulates a
        //    gyro with a wider range, and helps counter saturation at full control.
        //    There is hardly any reason to bother extrapolating yaw.

        if (staticParams.bitConfig & CFG_GYRO_SATURATION_PREVENTION) {
            if (tempGyro < SENSOR_MIN_XY) {
                debugOut.digital[0] |= DEBUG_SENSORLIMIT;
                tempGyro = tempGyro * EXTRAPOLATION_SLOPE - EXTRAPOLATION_LIMIT;
            } else if (tempGyro > SENSOR_MAX_XY) {
                debugOut.digital[0] |= DEBUG_SENSORLIMIT;
                tempGyro = (tempGyro - SENSOR_MAX_XY) * EXTRAPOLATION_SLOPE + SENSOR_MAX_XY;
            }
        }

        // 2) Apply offset (rotation will take care of signs).
        tempOffsetGyro[axis] = tempGyro - gyroOffset.offsets[axis];
    }

    // 2.1: Transform axes.
    rotate(tempOffsetGyro, IMUConfig.gyroQuadrant, IMUConfig.imuReversedFlags & IMU_REVERSE_GYRO_XY);

    for (uint8_t axis=X; axis<=Y; axis++) {
        // Filter. There is no filter for Z and no need for one.
     
      tempGyro = (gyro_control[axis] * (IMUConfig.gyroPIDFilterConstant - 1) + tempOffsetGyro[axis]) / IMUConfig.gyroPIDFilterConstant;
        // 5) Differential measurement.
        int16_t diff = tempGyro - gyro_control[axis];
        gyroD[axis] -= gyroDWindow[axis][gyroDWindowIdx];
        gyroD[axis] += diff;
        gyroDWindow[axis][gyroDWindowIdx] = diff;

        // 6) Done.
        gyro_control[axis] = tempGyro;
    }

    if (++gyroDWindowIdx >= IMUConfig.gyroDWindowLength) {
        gyroDWindowIdx = 0;
    }
   
    if (IMUConfig.imuReversedFlags & IMU_REVERSE_GYRO_Z)
      tempGyro = gyroOffset.offsets[Z] - gyroValue(Z, samplingADCData);
    else
      tempGyro = gyroValue(Z, samplingADCData) - gyroOffset.offsets[Z];

    gyro_control[Z] = tempGyro;
   
    startADCCycle();
}

/*
 * The uint16s can take a max. of 1<<16-10) = 64 samples summed.
 * Assuming a max oversampling count of 8 for the control loop, this is 8 control loop iterations
 * summed. After 8 are reached, we just throw away all further data. This (that the attitude loop
 * is more than 8 times slower than the control loop) should not happen anyway so there is no waste.
 */

#define MAX_OVEROVERSAMPLING_COUNT 8

void analog_sumAttitudeData(void) {
    // From when this procedure completes, there is attitude data available.
    if (analog_attitudeDataStatus == ATTITUDE_SENSOR_NO_DATA)
        analog_attitudeDataStatus = ATTITUDE_SENSOR_DATA_READY;


    if (analog_attitudeDataStatus == ATTITUDE_SENSOR_DATA_READY && attitudeSumCount < MAX_OVEROVERSAMPLING_COUNT) {
        for (uint8_t i = 0; i < 8; i++) {
            attitudeADCData[i] += samplingADCData[i];
        }
        attitudeSumCount++;
        debugOut.analog[24] = attitudeSumCount;
    }
}

void clearAttitudeData(void) {
    for (uint8_t i = 0; i < 8; i++) {
        attitudeADCData[i] = 0;
    }
    attitudeSumCount = 0;
    analog_attitudeDataStatus = ATTITUDE_SENSOR_NO_DATA;
}

void updateAttitudeVectors(void) {
    /*
     int16_t gyro_attitude_tmp[3];
     Vector3f gyro_attitude;
     Vector3f accel;
     */


    int16_t tmpSensor[3];

    // prevent gyro_attitude_tmp and attitudeSumCount from being updated.
    // TODO: This only prevents interrupts from starting. Well its good enough really?
    analog_attitudeDataStatus = ATTITUDE_SENSOR_READING_DATA;

    tmpSensor[X] = gyroValue(X, attitudeADCData) - gyroOffset.offsets[X] * attitudeSumCount;
    tmpSensor[Y] = gyroValue(Y, attitudeADCData) - gyroOffset.offsets[Y] * attitudeSumCount;

    rotate(tmpSensor, IMUConfig.gyroQuadrant, IMUConfig.imuReversedFlags & IMU_REVERSE_GYRO_XY);

    if (IMUConfig.imuReversedFlags & IMU_REVERSE_GYRO_Z)
        tmpSensor[Z] = gyroOffset.offsets[Z] * attitudeSumCount - gyroValue(Z, attitudeADCData);
    else
        tmpSensor[Z] = gyroValue(Z, attitudeADCData) - gyroOffset.offsets[Z] * attitudeSumCount;

    gyro_attitude.x = ((float) tmpSensor[X]) / (GYRO_RATE_FACTOR_XY * attitudeSumCount);
    gyro_attitude.y = ((float) tmpSensor[Y]) / (GYRO_RATE_FACTOR_XY * attitudeSumCount);
    gyro_attitude.z = ((float) tmpSensor[Z]) / (GYRO_RATE_FACTOR_Z  * attitudeSumCount);

    // Done with gyros. Now accelerometer:
    tmpSensor[X] = accValue(X, attitudeADCData) - accelOffset.offsets[X] * attitudeSumCount;
    tmpSensor[Y] = accValue(Y, attitudeADCData) - accelOffset.offsets[Y] * attitudeSumCount;

    rotate(tmpSensor, IMUConfig.accQuadrant, IMUConfig.imuReversedFlags & IMU_REVERSE_ACCEL_XY);

    // Z acc.
    if (IMUConfig.imuReversedFlags & IMU_REVERSE_ACCEL_Z)
        tmpSensor[Z] = accelOffset.offsets[Z] * attitudeSumCount - accValue(Z, attitudeADCData);
    else
        tmpSensor[Z] = accValue(Z, attitudeADCData) - accelOffset.offsets[Z] * attitudeSumCount;

    // Blarh!!! We just skip acc filtering. There are trillions of samples already.
    accel.x = (float)tmpSensor[X] / (ACCEL_FACTOR_XY * attitudeSumCount); // (accel.x + (float)tmpSensor[X] / (ACCEL_FACTOR_XY * attitudeSumCount)) / 2.0;
    accel.y = (float)tmpSensor[Y] / (ACCEL_FACTOR_XY * attitudeSumCount); // (accel.y + (float)tmpSensor[Y] / (ACCEL_FACTOR_XY * attitudeSumCount)) / 2.0;
    accel.z = (float)tmpSensor[Z] / (ACCEL_FACTOR_Z  * attitudeSumCount); // (accel.z + (float)tmpSensor[Z] / (ACCEL_FACTOR_Z  * attitudeSumCount)) / 2.0;

    for (uint8_t i=0; i<3; i++) {
      debugOut.analog[3 + i] = (int16_t)(gyro_attitude[i] * 100);
      debugOut.analog[6 + i] = (int16_t)(accel[i] * 100);
    }
}

void analog_updateAirPressure(void) {
    static uint16_t pressureAutorangingWait = 25;
    uint16_t rawAirPressure;
    int16_t newrange;
    // air pressure
    if (pressureAutorangingWait) {
        //A range switch was done recently. Wait for steadying.
        pressureAutorangingWait--;
    } else {
        rawAirPressure = attitudeADCData[AD_AIRPRESSURE] / attitudeSumCount;
        if (rawAirPressure < MIN_RAWPRESSURE) {
            // value is too low, so decrease voltage on the op amp minus input, making the value higher.
            newrange = OCR0A - (MAX_RAWPRESSURE - MIN_RAWPRESSURE) / (rangewidth * 4);
            if (newrange > MIN_RANGES_EXTRAPOLATION) {
                pressureAutorangingWait = (OCR0A - newrange) * AUTORANGE_WAIT_FACTOR; // = OCRA0 - OCRA0 +
                OCR0A = newrange;
            } else {
                if (OCR0A) {
                    OCR0A--;
                    pressureAutorangingWait = AUTORANGE_WAIT_FACTOR;
                }
            }
        } else if (rawAirPressure > MAX_RAWPRESSURE) {
            // value is too high, so increase voltage on the op amp minus input, making the value lower.
            // If near the end, make a limited increase
            newrange = OCR0A + (MAX_RAWPRESSURE - MIN_RAWPRESSURE) / (rangewidth * 4);
            if (newrange < MAX_RANGES_EXTRAPOLATION) {
                pressureAutorangingWait = (newrange - OCR0A) * AUTORANGE_WAIT_FACTOR;
                OCR0A = newrange;
            } else {
                if (OCR0A < 254) {
                    OCR0A++;
                    pressureAutorangingWait = AUTORANGE_WAIT_FACTOR;
                }
            }
        }

        // Even if the sample is off-range, use it.
        simpleAirPressure = getSimplePressure(rawAirPressure);
       
        if (simpleAirPressure < (uint16_t)(MIN_RANGES_EXTRAPOLATION * rangewidth)) {
            // Danger: pressure near lower end of range. If the measurement saturates, the
            // copter may climb uncontrolledly... Simulate a drastic reduction in pressure.
            debugOut.digital[1] |= DEBUG_SENSORLIMIT;
            airPressureSum += (int16_t) MIN_RANGES_EXTRAPOLATION * rangewidth
                    + (simpleAirPressure - (int16_t) MIN_RANGES_EXTRAPOLATION
                            * rangewidth) * PRESSURE_EXTRAPOLATION_COEFF;
        } else if (simpleAirPressure > (uint16_t)(MAX_RANGES_EXTRAPOLATION * rangewidth)) {
            // Danger: pressure near upper end of range. If the measurement saturates, the
            // copter may descend uncontrolledly... Simulate a drastic increase in pressure.
            debugOut.digital[1] |= DEBUG_SENSORLIMIT;
            airPressureSum += (int16_t) MAX_RANGES_EXTRAPOLATION * rangewidth
                    + (simpleAirPressure - (int16_t) MAX_RANGES_EXTRAPOLATION
                            * rangewidth) * PRESSURE_EXTRAPOLATION_COEFF;
        } else {
            // normal case.
            // If AIRPRESSURE_OVERSAMPLING is an odd number we only want to add half the double sample.
            // The 2 cases above (end of range) are ignored for this.
            debugOut.digital[1] &= ~DEBUG_SENSORLIMIT;
            airPressureSum += simpleAirPressure;
        }

        // 2 samples were added.
        pressureSumCount += 2;
        // Assumption here: AIRPRESSURE_OVERSAMPLING is even (well we all know it's 28...)
        if (pressureSumCount == AIRPRESSURE_OVERSAMPLING) {

            // The best oversampling count is 14.5. We add a quarter of the double ADC value to get the final half.
            airPressureSum += simpleAirPressure >> 2;

            uint32_t lastFilteredAirPressure = filteredAirPressure;

            if (!staticParams.airpressureWindowLength) {
                filteredAirPressure = (filteredAirPressure
                        * (staticParams.airpressureFilterConstant - 1)
                        + airPressureSum
                        + staticParams.airpressureFilterConstant / 2)
                        / staticParams.airpressureFilterConstant;
            } else {
                // use windowed.
                windowedAirPressure += simpleAirPressure;
                windowedAirPressure -= airPressureWindow[windowPtr];
                airPressureWindow[windowPtr++] = simpleAirPressure;
                if (windowPtr >= staticParams.airpressureWindowLength)
                    windowPtr = 0;
                filteredAirPressure = windowedAirPressure / staticParams.airpressureWindowLength;
            }

            // positive diff of pressure
            int16_t diff = filteredAirPressure - lastFilteredAirPressure;
            // is a negative diff of height.
            dHeight -= diff;
            // remove old sample (fifo) from window.
            dHeight += dAirPressureWindow[dWindowPtr];
            dAirPressureWindow[dWindowPtr++] = diff;
            if (dWindowPtr >= staticParams.airpressureDWindowLength)
                dWindowPtr = 0;
            pressureSumCount = airPressureSum = 0;
        }
    }
   
    debugOut.analog[25] = simpleAirPressure;
    debugOut.analog[26] = OCR0A;
    debugOut.analog[27] = filteredAirPressure;
}

void analog_updateBatteryVoltage(void) {
    // Battery. The measured value is: (V * 1k/11k)/3v * 1024 = 31.03 counts per volt (max. measurable is 33v).
    // This is divided by 3 --> 10.34 counts per volt.
    UBat = (3 * UBat + attitudeADCData[AD_UBAT] / 3) / 4;
}

void analog_updateAttitudeData(void) {
    updateAttitudeVectors();

    // TODO: These are waaaay off by now.
    analog_updateAirPressure();
    analog_updateBatteryVoltage();

    clearAttitudeData();
}

void analog_setNeutral() {
    gyro_init();

    if (gyroOffset_readFromEEProm()) {
        printf("gyro offsets invalid%s", recal);
        gyroOffset.offsets[X] = gyroOffset.offsets[Y] = 512 * GYRO_OVERSAMPLING_XY;
        gyroOffset.offsets[Z] = 512 * GYRO_OVERSAMPLING_Z;
        // This will get the DACs for FC1.3 to offset to a reasonable value.
        gyro_calibrate();
    }

    if (accelOffset_readFromEEProm()) {
        printf("acc. meter offsets invalid%s", recal);
        accelOffset.offsets[X] = accelOffset.offsets[Y] = 512 * ACCEL_OVERSAMPLING_XY;
        accelOffset.offsets[Z] = 512 * ACCEL_OVERSAMPLING_Z;
        if (IMUConfig.imuReversedFlags & IMU_REVERSE_ACCEL_Z) {
            accelOffset.offsets[Z] -= ACCEL_G_FACTOR_Z;
        } else {
            accelOffset.offsets[Z] += ACCEL_G_FACTOR_Z;
        }
    }

    // Noise is relative to offset. So, reset noise measurements when changing offsets.
    for (uint8_t i=X; i<=Y; i++) {
        // gyroNoisePeak[i] = 0;
        gyroD[i] = 0;
        for (uint8_t j=0; j<GYRO_D_WINDOW_LENGTH; j++) {
            gyroDWindow[i][j] = 0;
        }
    }
    // Setting offset values has an influence in the analog.c ISR
    // Therefore run measurement for 100ms to achive stable readings
    waitADCCycle(100);
}

void analog_calibrateGyros(void) {
#define GYRO_OFFSET_CYCLES 100
    uint8_t i, axis;
    int32_t offsets[3] = { 0, 0, 0 };

    flightControlStatus = BLOCKED_FOR_CALIBRATION;
    delay_ms(10);

    gyro_calibrate();

    // determine gyro bias by averaging (requires that the copter does not rotate around any axis!)
    for (i = 0; i < GYRO_OFFSET_CYCLES; i++) {
        waitADCCycle(5);
        for (axis=X; axis<=Z; axis++) {
            offsets[axis] += gyroValue(axis, samplingADCData);
        }
    }

    for (axis=X; axis<=Z; axis++) {
        gyroOffset.offsets[axis] = (offsets[axis] + GYRO_OFFSET_CYCLES/2) / GYRO_OFFSET_CYCLES;
        int16_t min = (512 - 200) * (axis==Z) ? GYRO_OVERSAMPLING_Z : GYRO_OVERSAMPLING_XY;
        int16_t max = (512 + 200) * (axis==Z) ? GYRO_OVERSAMPLING_Z : GYRO_OVERSAMPLING_XY;
        if (gyroOffset.offsets[axis] < min || gyroOffset.offsets[axis] > max)
            versionInfo.hardwareErrors[0] |= FC_ERROR0_GYRO_X << axis;
    }

    gyroOffset_writeToEEProm();
    //startADCCycle();
}

/*
 * Find acc. offsets for a neutral reading, and write them to EEPROM.
 * Does not (!} update the local variables. This must be done with a
 * call to analog_calibrate() - this always (?) is done by the caller
 * anyway. There would be nothing wrong with updating the variables
 * directly from here, though.
 */

void analog_calibrateAcc(void) {
#define ACCEL_OFFSET_CYCLES 100
    uint8_t i, axis;
    int32_t offsets[3] = { 0, 0, 0 };

    flightControlStatus = BLOCKED_FOR_CALIBRATION;
    delay_ms(10);

    for (i = 0; i < ACCEL_OFFSET_CYCLES; i++) {
        waitADCCycle(5);
        for (axis=X; axis<=Z; axis++) {
            offsets[axis] += accValue(axis, samplingADCData);
        }
    }

    for (axis=X; axis<=Z; axis++) {
        accelOffset.offsets[axis] = (offsets[axis] + ACCEL_OFFSET_CYCLES / 2) / ACCEL_OFFSET_CYCLES;
        int16_t min, max;
        if (axis == Z) {
            if (IMUConfig.imuReversedFlags & IMU_REVERSE_ACCEL_Z) {
                // TODO: This assumes a sensitivity of +/- 2g.
                min = (256 - 200) * ACCEL_OVERSAMPLING_Z;
                max = (256 + 200) * ACCEL_OVERSAMPLING_Z;
            } else {
                // TODO: This assumes a sensitivity of +/- 2g.
                min = (768 - 200) * ACCEL_OVERSAMPLING_Z;
                max = (768 + 200) * ACCEL_OVERSAMPLING_Z;
            }
        } else {
            min = (512 - 200) * ACCEL_OVERSAMPLING_XY;
            max = (512 + 200) * ACCEL_OVERSAMPLING_XY;
        }
        if (gyroOffset.offsets[axis] < min || gyroOffset.offsets[axis] > max) {
            versionInfo.hardwareErrors[0] |= FC_ERROR0_ACCEL_X << axis;
        }
    }

    if (IMUConfig.imuReversedFlags & IMU_REVERSE_ACCEL_Z) {
        accelOffset.offsets[Z] -= ACCEL_G_FACTOR_Z;
    } else {
        accelOffset.offsets[Z] += ACCEL_G_FACTOR_Z;
    }

    accelOffset_writeToEEProm();
    // startADCCycle();
}

void analog_setGround() {
    groundPressure = filteredAirPressure;
}

int32_t analog_getHeight(void) {
    int32_t height = groundPressure - filteredAirPressure;
    debugOut.analog[28] = height;
    return height;
}

int16_t analog_getDHeight(void) {
    return dHeight;
}