ADC library, with support for Teensy 4, 3.x, and LC

I just checked the Git repository where this library is hosted and I see that the ADC.h file is different than the version I have. The copyright at the top of my version of the file is 2014. The version I have was installed through the Teensyduino installer two weeks ago, so it didn't occur to me that it would be outdated and/or incompatible with Teensy 3.1.
Which version of Teensyduino did you use?

Pretty sure that is relevant question; reasonably sure you didn't use the latest...
 
Okay cool. You are awesome by the way. Thanks for clearing that up, it makes too much sense now. So each tine the PDB triggers the ADC conversion will the pdb_isr() function be called then? I'm trying not to read the last reported conversion twice, so if this is the case that would make things easy.

The pdb_isr() is fired when the PDB triggers the ADC, but then it takes some time for the ADC to finish the measurement. To make sure you only read "fresh" values you can use the adc0_isr() (or adc1_isr), which only gets called when a measurement has finished.
 
Which version of Teensyduino did you use?

Pretty sure that is relevant question; reasonably sure you didn't use the latest...

The version I installed was 1.23. I downloaded it on 06/07/15, it was digitally signed on 05/13/15.

Teensyduino_installer1.png

I see that 1.24 is out now, and although there is no date associated with it, the revision history on https://www.pjrc.com/teensy/td_download.html shows:

Update Snooze, ADC and i2c_t3 libraries

Here's ADC.h
Code:
/* Teensy 3.x ADC library
 * https://github.com/pedvide/ADC
 * Copyright (c) 2014 Pedro Villanueva
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/* ADC.h: Control for one (Teensy 3.0) or two ADC modules (Teensy 3.1).
 *
 */

/* TODO
* - Function to measure more that 1 pin consecutively (stream?)
*
* bugs:
* - comparison values in 16 bit differential mode are twice what they should be
*/

#ifndef ADC_H
#define ADC_H

#define ADC_0 0
#define ADC_1 1


// include ADC module class
#include "ADC_Module.h"

// include the circular buffer
#include "RingBuffer.h"
#include "RingBufferDMA.h"

// dma assigment
//#include "DMAChannel.h"



#ifdef __cplusplus
extern "C" {
#endif

/** Class ADC: Controls the Teensy 3.x ADC
*
*/
class ADC
{
    public:

        /** Default constructor */
        ADC();


        // create both adc objects

        //! Object to control the ADC0
        //static ADC_Module *adc0; // adc object
        ADC_Module *adc0; // adc object
        #if defined(__MK20DX256__)
        //! Object to control the ADC1
        //static ADC_Module *adc1; // adc object
        ADC_Module *adc1; // adc object
        #endif


        /////////////// METHODS TO SET/GET SETTINGS OF THE ADC ////////////////////

        //! Set the voltage reference you prefer, default is vcc
        /*!
        * \param type can be DEFAULT, EXTERNAL or INTERNAL.
        *
        *  It recalibrates at the end.
        */
        void setReference(uint8_t type, int8_t adc_num = -1);


        //! Change the resolution of the measurement.
        /*!
        *  \param bits is the number of bits of resolution.
        *  For single-ended measurements: 8, 10, 12 or 16 bits.
        *  For differential measurements: 9, 11, 13 or 16 bits.
        *  If you want something in between (11 bits single-ended for example) select the inmediate higher
        *  and shift the result one to the right.
        *
        *  Whenever you change the resolution, change also the comparison values (if you use them).
        */
        void setResolution(uint8_t bits, int8_t adc_num = -1);

        //! Returns the resolution of the ADC_Module.
        uint8_t getResolution(int8_t adc_num = -1);

        //! Returns the maximum value for a measurement.
        uint32_t getMaxValue(int8_t adc_num = -1);


        //! Sets the conversion speed
        /**
        * \param speed can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED.
        *
        * ADC_VERY_LOW_SPEED is guaranteed to be the lowest possible speed within specs for resolutions less than 16 bits (higher than 1 MHz),
        * it's different from ADC_LOW_SPEED only for 24, 4 or 2 MHz.
        * ADC_LOW_SPEED is guaranteed to be the lowest possible speed within specs for all resolutions (higher than 2 MHz).
        * ADC_MED_SPEED is always >= ADC_LOW_SPEED and <= ADC_HIGH_SPEED.
        * ADC_HIGH_SPEED_16BITS is guaranteed to be the highest possible speed within specs for all resolutions (lower or eq than 12 MHz).
        * ADC_HIGH_SPEED is guaranteed to be the highest possible speed within specs for resolutions less than 16 bits (lower or eq than 18 MHz).
        * ADC_VERY_HIGH_SPEED may be out of specs, it's different from ADC_HIGH_SPEED only for 48, 40 or 24 MHz.
        */
        void setConversionSpeed(uint8_t speed, int8_t adc_num = -1);


        //! Sets the sampling speed
        /** Increase the sampling speed for low impedance sources, decrease it for higher impedance ones.
        * \param speed can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED.
        *
        * ADC_VERY_LOW_SPEED is the lowest possible sampling speed (+24 ADCK).
        * ADC_LOW_SPEED adds +16 ADCK.
        * ADC_MED_SPEED adds +10 ADCK.
        * ADC_HIGH_SPEED (or ADC_HIGH_SPEED_16BITS) adds +6 ADCK.
        * ADC_VERY_HIGH_SPEED is the highest possible sampling speed (0 ADCK added).
        */
        void setSamplingSpeed(uint8_t speed, int8_t adc_num = -1);


        //! Set the number of averages
        /*!
        * \param num can be 0, 4, 8, 16 or 32..
        */
        void setAveraging(uint8_t num, int8_t adc_num = -1);


        //! Enable interrupts
        /** An IRQ_ADC0 Interrupt will be raised when the conversion is completed
        *  (including hardware averages and if the comparison (if any) is true).
        */
        void enableInterrupts(int8_t adc_num = -1);

        //! Disable interrupts
        void disableInterrupts(int8_t adc_num = -1);


        //! Enable DMA request
        /** An ADC DMA request will be raised when the conversion is completed
        *  (including hardware averages and if the comparison (if any) is true).
        */
        void enableDMA(int8_t adc_num = -1);

        //! Disable ADC DMA request
        void disableDMA(int8_t adc_num = -1);


        //! Enable the compare function to a single value
        /** A conversion will be completed only when the ADC value
        *  is >= compValue (greaterThan=1) or < compValue (greaterThan=0)
        *  Call it after changing the resolution
        *  Use with interrupts or poll conversion completion with isComplete()
        */
        void enableCompare(int16_t compValue, bool greaterThan, int8_t adc_num = -1);

        //! Enable the compare function to a range
        /** A conversion will be completed only when the ADC value is inside (insideRange=1) or outside (=0)
        *  the range given by (lowerLimit, upperLimit),including (inclusive=1) the limits or not (inclusive=0).
        *  See Table 31-78, p. 617 of the freescale manual.
        *  Call it after changing the resolution
        *  Use with interrupts or poll conversion completion with isComplete()
        */
        void enableCompareRange(int16_t lowerLimit, int16_t upperLimit, bool insideRange, bool inclusive, int8_t adc_num = -1);

        //! Disable the compare function
        void disableCompare(int8_t adc_num = -1);


        //! Enable and set PGA
        /** Enables the PGA and sets the gain
        *   Use only for signals lower than 1.2 V
        *   \param gain can be 1, 2, 4, 8, 16, 32 or 64
        *
        */
        void enablePGA(uint8_t gain, int8_t adc_num = -1);

        //! Returns the PGA level
        /** PGA level = from 1 to 64
        */
        uint8_t getPGA(int8_t adc_num = -1);

        //! Disable PGA
        void disablePGA(int8_t adc_num = -1);



        ////////////// INFORMATION ABOUT THE STATE OF THE ADC /////////////////

        //! Is the ADC converting at the moment?
        bool isConverting(int8_t adc_num = -1);

        //! Is an ADC conversion ready?
        /**
        *  \return 1 if yes, 0 if not.
        *  When a value is read this function returns 0 until a new value exists
        *  So it only makes sense to call it with continuous or non-blocking methods
        */
        bool isComplete(int8_t adc_num = -1);

        //! Is the ADC in differential mode?
        bool isDifferential(int8_t adc_num = -1);

        //! Is the ADC in continuous mode?
        bool isContinuous(int8_t adc_num = -1);



        //////////////// BLOCKING CONVERSION METHODS //////////////////

        //! Returns the analog value of the pin.
        /** It waits until the value is read and then returns the result.
        * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE.
        * This function is interrupt safe, so it will restore the adc to the state it was before being called
        * If more than one ADC exists, it will select the module with less workload, you can force a selection using
        * adc_num. If you select ADC1 in Teensy 3.0 it will return ADC_ERROR_VALUE.
        */
        int analogRead(uint8_t pin, int8_t adc_num = -1);

        //! Reads the differential analog value of two pins (pinP - pinN).
        /** It waits until the value is read and then returns the result.
        * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE.
        * \param pinP must be A10 or A12.
        * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12).
        * Other pins will return ADC_ERROR_VALUE.
        *
        * This function is interrupt safe, so it will restore the adc to the state it was before being called
        * If more than one ADC exists, it will select the module with less workload, you can force a selection using
        * adc_num
        */
        int analogReadDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num = -1);


        /////////////// NON-BLOCKING CONVERSION METHODS //////////////

        //! Starts an analog measurement on the pin and enables interrupts.
        /** It returns inmediately, get value with readSingle().
        *   If the pin is incorrect it returns ADC_ERROR_VALUE
        *   If this function interrupts a measurement, it stores the settings in adc_config
        */
        int startSingleRead(uint8_t pin, int8_t adc_num = -1);

        //! Start a differential conversion between two pins (pinP - pinN) and enables interrupts.
        /** It returns inmediately, get value with readSingle().
        *   \param pinP must be A10 or A12.
        *   \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12).
        *
        *   Other pins will return ADC_ERROR_DIFF_VALUE.
        *   If this function interrupts a measurement, it stores the settings in adc_config
        */
        int startSingleDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num = -1);

        //! Reads the analog value of a single conversion.
        /** Set the conversion with with startSingleRead(pin) or startSingleDifferential(pinP, pinN).
        *   \return the converted value.
        */
        int readSingle(int8_t adc_num = -1);



        ///////////// CONTINUOUS CONVERSION METHODS ////////////

        //! Starts continuous conversion on the pin.
        /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value.
        */
        void startContinuous(uint8_t pin, int8_t adc_num = -1);

        //! Starts continuous conversion between the pins (pinP-pinN).
        /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value.
        * \param pinP must be A10 or A12.
        * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12).
        * Other pins will return ADC_ERROR_DIFF_VALUE.
        */
        void startContinuousDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num = -1);

        //! Reads the analog value of a continuous conversion.
        /** Set the continuous conversion with with analogStartContinuous(pin) or startContinuousDifferential(pinP, pinN).
        *   \return the last converted value.
        *   If single-ended and 16 bits it's necessary to typecast it to an unsigned type (like uint16_t),
        *   otherwise values larger than 3.3/2 V are interpreted as negative!
        */
        inline int analogReadContinuous(int8_t adc_num = -1) { // make it inline so it's a bit faster
            if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
                #if defined(__MK20DX256__)
                return adc1->analogReadContinuous();
                #else
                adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
                #endif
                return false;
            }
            return adc0->analogReadContinuous();
        }

        //! Stops continuous conversion
        void stopContinuous(int8_t adc_num = -1);



        /////////// SYNCHRONIZED METHODS ///////////////

        //! Struct for synchronous measurements
        /** result_adc0 has the result from ADC0 and result_adc1 from ADC1.
        */
        typedef struct SYNC_RESULT{
            int32_t result_adc0, result_adc1;
        } Sync_result;

        //////////////// SYNCHRONIZED BLOCKING METHODS //////////////////

        //! Returns the analog values of both pins, measured at the same time by the two ADC modules.
        /** It waits until the values are read and then returns the result as a struct Sync_result,
        *   use Sync_result.result_adc0 and Sync_result.result_adc1.
        * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct.
        * This function is interrupt safe, so it will restore the adc to the state it was before being called
        */
        Sync_result analogSynchronizedRead(uint8_t pin0, uint8_t pin1);
        inline Sync_result analogSyncRead(uint8_t pin0, uint8_t pin1) {return analogSynchronizedRead(pin0, pin1);}

        //! Returns the differential analog values of both sets of pins, measured at the same time by the two ADC modules.
        /** It waits until the values are read and then returns the result as a struct Sync_result,
        *   use Sync_result.result_adc0 and Sync_result.result_adc1.
        * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct.
        * This function is interrupt safe, so it will restore the adc to the state it was before being called
        */
        Sync_result analogSynchronizedReadDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N);
        inline Sync_result analogSyncReadDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) {
            return analogSynchronizedReadDifferential(pin0P, pin0N, pin1P, pin1N);
        }

        /////////////// SYNCHRONIZED NON-BLOCKING METHODS //////////////

        //! Starts an analog measurement at the same time on the two ADC modules
        /** It returns inmediately, get value with readSynchronizedSingle().
        *   If the pin is incorrect it returns ADC_ERROR_VALUE
        *   If this function interrupts a measurement, it stores the settings in adc_config
        */
        int startSynchronizedSingleRead(uint8_t pin0, uint8_t pin1);

        //! Start a differential conversion between two pins (pin0P - pin0N) and (pin1P - pin1N)
        /** It returns inmediately, get value with readSynchronizedSingle().
        *   \param pinP must be A10 or A12.
        *   \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12).
        *   Other pins will return ADC_ERROR_DIFF_VALUE.
        *   If this function interrupts a measurement, it stores the settings in adc_config
        */
        int startSynchronizedSingleDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N);

        //! Reads the analog value of a single conversion.
        /**
        *   \return the converted value.
        */
        Sync_result readSynchronizedSingle();


        ///////////// SYNCHRONIZED CONTINUOUS CONVERSION METHODS ////////////

        //! Starts a continuous conversion in both ADCs simultaneously
        /** Use readSynchronizedContinuous to get the values
        *
        */
        void startSynchronizedContinuous(uint8_t pin0, uint8_t pin1);

        //! Starts a continuous differential conversion in both ADCs simultaneously
        /** Use readSynchronizedContinuous to get the values
        *
        */
        void startSynchronizedContinuousDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N);

        //! Returns the values of both ADCs.
        Sync_result readSynchronizedContinuous();

        //! Stops synchronous continuous conversion
        void stopSynchronizedContinuous();


        /////////// DMA METHODS ///////////////

        // DMA stuff
        static uint8_t dma_Ch0, dma_Ch1;

        static void dma_isr_0(void);
        static void dma_isr_1(void);

        #if defined(__MK20DX128__)
        //! DMA buffer to store all converted values
        RingBufferDMA *buffer0;
        #elif defined(__MK20DX256__)
        //! DMA buffer to store all converted values
        RingBufferDMA *buffer0;
        RingBufferDMA *buffer1;
        #endif

        void useDMA(uint8_t ch0=0, uint8_t ch1=1);


        // PDB stuff
        //void startPDB(double period);
        //double adc_pdb_period;

        //! Translate pin number to SC1A nomenclature and viceversa
        static const uint8_t channel2sc1aADC0[44];
        static const uint8_t sc1a2channelADC0[31];
        static const uint8_t channel2sc1aADC1[44];
        static const uint8_t sc1a2channelADC1[31];


    protected:
    private:


        #if defined(__MK20DX128__)
        const uint8_t num_ADCs = 1;
        #elif defined(__MK20DX256__)
        const uint8_t num_ADCs = 2;
        #endif


};


#ifdef __cplusplus
}
#endif


#endif // ADC_H

And ADC.cpp
Code:
 /* Teensy 3.x ADC library
 * https://github.com/pedvide/ADC
 * Copyright (c) 2014 Pedro Villanueva
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/* ADC.cpp: Implements the control of one or more ADC modules of Teensy 3.x
 *
 */

#include "ADC.h"

/*
// static adc module objects
ADC_Module *ADC::adc0 = new ADC_Module(0);
#if defined(__MK20DX256__)
ADC_Module *ADC::adc1 = new ADC_Module(1);
#endif
*/
uint8_t ADC::dma_Ch0 = -1;
uint8_t ADC::dma_Ch1 = -1;


// translate pin number to SC1A nomenclature and viceversa
// we need to create this static const arrays so that we can assign the "normal arrays" to the correct one
// depending on which ADC module we will be.
/* channel2sc1aADCx converts a pin number to their value for the SC1A register, for the ADC0 and ADC1
*  sc1a2channelADCx does the opposite.
*/
const uint8_t ADC::channel2sc1aADC0[]= { // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC.
    5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 0, 19, 3, 21, // 0-13, we treat them as A0-A13
    5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9)
    31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // 24-33
    0, 19, 3, 21, // 34-37 (A10-A13)
    26, 22, 23, 27, 29, 30 // 38-43: temp. sensor, VREF_OUT, A14, bandgap, VREFH, VREFL. A14 isn't connected to anything in Teensy 3.0.
};
const uint8_t ADC::sc1a2channelADC0[]= { // new version, gives directly the pin number
    34, 0, 0, 36, 23, 14, 20, 21, 16, 17, 0, 0, 19, 18, // 0-13
    15, 22, 0, 0, 0, 35, 0, 37, // 14-21
    39, 40, 0, 0, 38, 41, 42, 43, // VREF_OUT, A14, temp. sensor, bandgap, VREFH, VREFL.
    0 // 31 means disabled, but just in case
};

const uint8_t ADC::channel2sc1aADC1[]= { // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC.
    31, 31, 8, 9, 31, 31, 31, 31, 31, 31, 3, 31, 0, 19, // 0-13, we treat them as A0-A13
    31, 31, 8, 9, 31, 31, 31, 31, 31, 31, // 14-23 (A0-A9)
    31, 31,  // 24,25 are digital only pins
    5, 5, 4, 6, 7, 4, 31, 31, // 26-33 26=5a, 27=5b, 28=4b, 29=6b, 30=7b, 31=4a, 32,33 are digital only
    3, 31, 0, 19, // 34-37 (A10-A13) A11 isn't connected.
    26, 18, 31, 27, 29, 30 // 38-43: temp. sensor, VREF_OUT, A14 (not connected), bandgap, VREFH, VREFL.
};
const uint8_t ADC::sc1a2channelADC1[]= { // new version, gives directly the pin number
    36, 0, 0, 34, 28, 26, 29, 30, 16, 17, 0, 0, 0, 0, // 0-13. 5a=26, 5b=27, 4b=28, 4a=31
    0, 0, 0, 0, 39, 37, 0, 0, // 14-21
    0, 0, 0, 0, 38, 41, 0, 42, // 22-29. VREF_OUT, A14, temp. sensor, bandgap, VREFH, VREFL.
    43
};


ADC::ADC() {
    //ctor

    // make sure the clocks to the ADC are on
    SIM_SCGC6 |= SIM_SCGC6_ADC0;
    #if defined(__MK20DX256__)
    SIM_SCGC3 |= SIM_SCGC3_ADC1;
    #endif

    adc0 = new ADC_Module(0);
    #if defined(__MK20DX256__)
    adc1 = new ADC_Module(1);
    #endif

    //dmaControl = new DMAControl;

}



/* Set the voltage reference you prefer, default is vcc
*
*/
void ADC::setReference(uint8_t type, int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->setReference(type);
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->setReference(type); // adc_num isn't changed or has selected ADC0
    return;
}


// Change the resolution of the measurement.
/*
*  \param bits is the number of bits of resolution.
*  For single-ended measurements: 8, 10, 12 or 16 bits.
*  For differential measurements: 9, 11, 13 or 16 bits.
*  If you want something in between (11 bits single-ended for example) select the inmediate higher
*  and shift the result one to the right.
*  If you select, for example, 9 bits and then do a single-ended reading, the resolution will be adjusted to 8 bits
*  In this case the comparison values will still be correct for analogRead and analogReadDifferential, but not
*  for startSingle* or startContinous*, so whenever you change the resolution, change also the comparison values.
*/
void ADC::setResolution(uint8_t bits, int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->setResolution(bits);
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->setResolution(bits); // adc_num isn't changed or has selected ADC0
    return;
}

//! Returns the resolution of the ADC_Module.
uint8_t ADC::getResolution(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        return adc1->getResolution();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return 0;
    }
    return adc0->getResolution(); // adc_num isn't changed or has selected ADC0

}

//! Returns the maximum value for a measurement.
uint32_t ADC::getMaxValue(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        return adc1->getMaxValue();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return 1;
    }
    return adc0->getMaxValue();
}


// Sets the conversion speed
/*
* \param speed can be ADC_LOW_SPEED, ADC_MED_SPEED or ADC_HIGH_SPEED
*
*  It recalibrates at the end.
*/
void ADC::setConversionSpeed(uint8_t speed, int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->setConversionSpeed(speed);
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->setConversionSpeed(speed); // adc_num isn't changed or has selected ADC0
    return;

}


// Sets the sampling speed
/*
* \param speed can be ADC_LOW_SPEED, ADC_MED_SPEED or ADC_HIGH_SPEED
*
*  It recalibrates at the end.
*/
void ADC::setSamplingSpeed(uint8_t speed, int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->setSamplingSpeed(speed);
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->setSamplingSpeed(speed); // adc_num isn't changed or has selected ADC0
    return;

}


// Set the number of averages
/*
* \param num can be 0, 4, 8, 16 or 32.
*/
void ADC::setAveraging(uint8_t num, int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->setAveraging(num);
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->setAveraging(num); // adc_num isn't changed or has selected ADC0
    return;
}


//! Enable interrupts
/** An IRQ_ADC0 Interrupt will be raised when the conversion is completed
*  (including hardware averages and if the comparison (if any) is true).
*/
void ADC::enableInterrupts(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->enableInterrupts();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->enableInterrupts();
    return;
}

//! Disable interrupts
void ADC::disableInterrupts(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->disableInterrupts();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->disableInterrupts();
    return;
}


//! Enable DMA request
/** An ADC DMA request will be raised when the conversion is completed
*  (including hardware averages and if the comparison (if any) is true).
*/
void ADC::enableDMA(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->enableDMA();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->enableDMA();
    return;
}

//! Disable ADC DMA request
void ADC::disableDMA(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->disableDMA();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->disableDMA();
    return;
}


// Enable the compare function to a single value
/* A conversion will be completed only when the ADC value
*  is >= compValue (greaterThan=true) or < compValue (greaterThan=false)
*  Call it after changing the resolution
*  Use with interrupts or poll conversion completion with isComplete()
*/
void ADC::enableCompare(int16_t compValue, bool greaterThan, int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->enableCompare(compValue, greaterThan);
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->enableCompare(compValue, greaterThan);
    return;
}

// Enable the compare function to a range
/* A conversion will be completed only when the ADC value is inside (insideRange=1) or outside (=0)
*  the range given by (lowerLimit, upperLimit),including (inclusive=1) the limits or not (inclusive=0).
*  See Table 31-78, p. 617 of the freescale manual.
*  Call it after changing the resolution
*  Use with interrupts or poll conversion completion with isComplete()
*/
void ADC::enableCompareRange(int16_t lowerLimit, int16_t upperLimit, bool insideRange, bool inclusive, int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->enableCompareRange(lowerLimit, upperLimit, insideRange, inclusive);
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->enableCompareRange(lowerLimit, upperLimit, insideRange, inclusive);
    return;
}

//! Disable the compare function
void ADC::disableCompare(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->disableCompare();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->disableCompare();
    return;
}


// Enable and set PGA
/* Enables the PGA and sets the gain
*   Use only for signals lower than 1.2 V
*   \param gain can be 1, 2, 4, 8, 16, 32 or 64
*
*/
void ADC::enablePGA(uint8_t gain, int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->enablePGA(gain);
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->enablePGA(gain);
    return;
}

//! Returns the PGA level
/** PGA level = 2^gain, from 0 to 64
*/
uint8_t ADC::getPGA(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        return adc1->getPGA();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        return 1;
        #endif
    }
    return adc0->getPGA();
}

//! Disable PGA
void ADC::disablePGA(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->disablePGA();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->disablePGA();
    return;
}

//! Is the ADC converting at the moment?
bool ADC::isConverting(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        return adc1->isConverting();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return false;
    }
    return adc0->isConverting();
}

// Is an ADC conversion ready?
/*
*  \return 1 if yes, 0 if not.
*  When a value is read this function returns 0 until a new value exists
*  So it only makes sense to call it before analogReadContinuous() or readSingle()
*/
bool ADC::isComplete(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        return adc1->isComplete();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return false;
    }
    return adc0->isComplete();;
}

//! Is the ADC in differential mode?
bool ADC::isDifferential(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        return adc1->isDifferential();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return false;
    }
    return adc0->isDifferential();
}

//! Is the ADC in continuous mode?
bool ADC::isContinuous(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        return adc1->isContinuous();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return false;
    }
    return adc0->isContinuous();
}


/* Returns the analog value of the pin.
* It waits until the value is read and then returns the result.
* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE.
* This function is interrupt safe, so it will restore the adc to the state it was before being called
* If more than one ADC exists, it will select the module with less workload, you can force a selection using
* adc_num. If you select ADC1 in Teensy 3.0 it will return ADC_ERROR_VALUE.
*/
int ADC::analogRead(uint8_t pin, int8_t adc_num) {
    #if defined(__MK20DX128__)
    /* Teensy 3.0
    */
    if( adc_num==1 ) { // If asked to use ADC1, return error
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        return ADC_ERROR_VALUE;
    }
    if ( (pin <= 23) || (pin>=34) ) { // check that the pin is correct (pin<0 or pin>43 have been ruled out already)
        return adc0->analogRead(pin); // use ADC0
    } else {
        adc0->fail_flag |= ADC_ERROR_WRONG_PIN;
    }
    #elif defined(__MK20DX256__)
    /* Teensy 3.1
    */
    // Check to which ADC the pin corresponds
    if( (pin==16) || (pin==17) || (pin>=34 && pin<=37) )  { // Both ADCs: pins 16, 17, 34, 35, 36, and 37.
        if( adc_num==-1 ) { // use no ADC in particular
            if( (adc0->num_measurements) >= (adc1->num_measurements)) { // use the ADC with less workload
                return adc1->analogRead(pin);
            } else {
                return adc0->analogRead(pin);
            }
        }
        else if( adc_num==0 ) { // user wants ADC0
            return adc0->analogRead(pin);
        }
        else if( adc_num==1 ){ // user wants ADC 1
            return adc1->analogRead(pin);
        }
    } else if(pin>=26) { // Those pins correspond to ADC1 only
        return adc1->analogRead(pin);
    } else if(pin<=23){ // Pin corresponds to ADC0
        return adc0->analogRead(pin);
    }
    #endif
    adc0->fail_flag |= ADC_ERROR_OTHER;
    return ADC_ERROR_VALUE;
}

/* Reads the differential analog value of two pins (pinP - pinN).
* It waits until the value is read and then returns the result.
* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE.
* \param pinP must be A10 or A12.
* \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12).
* Other pins will return ADC_ERROR_VALUE.
* This function is interrupt safe, so it will restore the adc to the state it was before being called
* If more than one ADC exists, it will select the module with less workload, you can force a selection using
* adc_num. If you select ADC1 in Teensy 3.0 it will return ADC_ERROR_VALUE.
*/
int ADC::analogReadDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num) {
    if( adc_num==-1 ) { // adc_num isn't changed
        #if defined(__MK20DX128__)
        return adc0->analogReadDifferential(pinP, pinN); // use ADC0
        #elif defined(__MK20DX256__)
        if( (adc0->num_measurements) >= (adc1->num_measurements)) { // use the ADC with less workload
            return adc1->analogReadDifferential(pinP, pinN);
        } else {
            return adc0->analogReadDifferential(pinP, pinN);
        }
        #endif
    }
    else if( adc_num==0 ) { // use ADC0
        return adc0->analogReadDifferential(pinP, pinN);
    }
    else if( adc_num==1 ){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        return adc1->analogReadDifferential(pinP, pinN);
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        return ADC_ERROR_VALUE;
        #endif
    }
    adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
    return ADC_ERROR_VALUE;
}


// Starts an analog measurement on the pin and enables interrupts.
/* It returns inmediately, get value with readSingle().
*   If the pin is incorrect it returns ADC_ERROR_VALUE
*   This function is interrupt safe. The ADC interrupt will restore the adc to its previous settings and
*   restart the adc if it stopped a measurement. If you modify the adc_isr then this won't happen.
*/
int ADC::startSingleRead(uint8_t pin, int8_t adc_num) {
    /* Teensy 3.0
    */
    #if defined(__MK20DX128__)
    if( adc_num==1 ) { // If asked to use ADC1, return error
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        return ADC_ERROR_VALUE;
    }
    if ( (pin <= 23) || (pin>=34) ) { // check that the pin is correct (pin<0 or pin>43 have been ruled out already)
        return adc0->startSingleRead(pin); // use ADC0
    }

    /* Teensy 3.1
    */
    #elif defined(__MK20DX256__)

    // Check to which ADC the pin corresponds
    if( (pin==16) || (pin==17) || (pin>=34 && pin<=37) )  { // Both ADCs: pins 16, 17, 34, 35, 36, and 37.
        if( adc_num==-1 ) { // use no ADC in particular
            if( (adc0->num_measurements) >= (adc1->num_measurements)) { // use the ADC with less workload
                return adc1->startSingleRead(pin);
            } else {
                return adc0->startSingleRead(pin);
            }
        }
        else if( adc_num==0 ) { // use ADC0
            return adc0->startSingleRead(pin);
        }
        else if( adc_num==1 ){ // user wants ADC 1
            return adc1->startSingleRead(pin);
        }
    } else if(pin>=26) { // Those pins correspond to ADC1 only
        return adc1->startSingleRead(pin);
    } else if(pin<=23){ // Pin corresponds to ADC0
        return adc0->startSingleRead(pin);
    }
    #endif
    adc0->fail_flag |= ADC_ERROR_OTHER;
    return ADC_ERROR_VALUE;
}

// Start a differential conversion between two pins (pinP - pinN) and enables interrupts.
/* It returns inmediately, get value with readSingle().
*   \param pinP must be A10 or A12.
*   \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12).
*   Other pins will return ADC_ERROR_DIFF_VALUE.
*   This function is interrupt safe. The ADC interrupt will restore the adc to its previous settings and
*   restart the adc if it stopped a measurement. If you modify the adc_isr then this won't happen.
*/
int ADC::startSingleDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num) {
    if( adc_num==-1 ) { // adc_num isn't changed
        #if defined(__MK20DX128__)
        return adc0->startSingleDifferential(pinP, pinN); // use ADC0
        #elif defined(__MK20DX256__)
        if( (adc0->num_measurements) >= (adc1->num_measurements)) { // use the ADC with less workload
            return adc1->startSingleDifferential(pinP, pinN);
        } else {
            return adc0->startSingleDifferential(pinP, pinN);
        }
        #endif
    }
    else if( adc_num==0 ) { // use ADC0
        return adc0->startSingleDifferential(pinP, pinN);
    }
    else if( adc_num==1 ){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        return adc1->startSingleDifferential(pinP, pinN);
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        return ADC_ERROR_VALUE;
        #endif
    }
    adc0->fail_flag |= ADC_ERROR_OTHER;
    return ADC_ERROR_VALUE;
}

// Reads the analog value of a single conversion.
/* Set the conversion with with startSingleRead(pin) or startSingleDifferential(pinP, pinN).
*   \return the converted value.
*/
int ADC::readSingle(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        return adc1->readSingle();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return false;
    }
    return adc0->readSingle();
}


// Starts continuous conversion on the pin.
/* It returns as soon as the ADC is set, use analogReadContinuous() to read the value.
*/
void ADC::startContinuous(uint8_t pin, int8_t adc_num) {
    /* Teensy 3.0
    */
    #if defined(__MK20DX128__)
    if( adc_num==1 ) { // If asked to use ADC1, return error
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        return;
    }
    if ( (pin <= 23) || (pin>=34) ) { // check that the pin is correct (pin<0 or pin>43 have been ruled out already)
        adc0->startContinuous(pin); // use ADC0
        return;
    }

    /* Teensy 3.1
    */
    #elif defined(__MK20DX256__)

    // Check to which ADC the pin corresponds
    if( (pin==16) || (pin==17) || (pin>=34 && pin<=37) )  { // Both ADCs: pins 16, 17, 34, 35, 36, and 37.
        if( adc_num==-1 ) { // use no ADC in particular
            if( (adc0->num_measurements) >= (adc1->num_measurements)) { // use the ADC with less workload
                adc1->startContinuous(pin);
                return;
            } else {
                adc0->startContinuous(pin);
                return;
            }
        }
        else if( adc_num==0 ) { // use ADC0
            adc0->startContinuous(pin);
            return;
        }
        else if( adc_num==1 ){ // user wants ADC 1
            adc1->startContinuous(pin);
            return;
        }
    } else if(pin>=26) { // Those pins correspond to ADC1 only
        adc1->startContinuous(pin);
        return;
    } else if(pin<=23){ // Pin corresponds to ADC0
        adc0->startContinuous(pin);
        return;
    }
    #endif
    adc0->fail_flag |= ADC_ERROR_OTHER;
    return;
}

// Starts continuous conversion between the pins (pinP-pinN).
/* It returns as soon as the ADC is set, use analogReadContinuous() to read the value.
* \param pinP must be A10 or A12.
* \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12).
* Other pins will return ADC_ERROR_DIFF_VALUE.
*/
void ADC::startContinuousDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num) {
    if( adc_num==-1 ) { // adc_num isn't changed
        #if defined(__MK20DX128__)
        adc0->startContinuousDifferential(pinP, pinN); // use ADC0
        return;
        #elif defined(__MK20DX256__)
        if( (adc0->num_measurements) >= (adc1->num_measurements)) { // use the ADC with less workload
            adc1->startContinuousDifferential(pinP, pinN);
            return;
        } else {
            adc0->startContinuousDifferential(pinP, pinN);
            return;
        }
        #endif
    }
    else if( adc_num==0 ) { // use ADC0
        adc0->startContinuousDifferential(pinP, pinN);
        return;
    }
    else if( adc_num==1 ){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->startContinuousDifferential(pinP, pinN);
        return;
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        return;
        #endif
    }
    adc0->fail_flag |= ADC_ERROR_OTHER;
    return;
}

// Reads the analog value of a continuous conversion.
/* Set the continuous conversion with with analogStartContinuous(pin) or startContinuousDifferential(pinP, pinN).
*   \return the last converted value.
*/
// inlined! see .h

//! Stops continuous conversion
void ADC::stopContinuous(int8_t adc_num) {
    if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0
        #if defined(__MK20DX256__)
        adc1->stopContinuous();
        #else
        adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
        #endif
        return;
    }
    adc0->stopContinuous();
    return;
}



//////////////// SYNCHRONIZED BLOCKING METHODS //////////////////

/*Returns the analog values of both pins, measured at the same time by the two ADC modules.
* It waits until the value is read and then returns the result as a struct Sync_result,
* use Sync_result.result_adc0 and Sync_result.result_adc1.
* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct.
*/
ADC::Sync_result ADC::analogSynchronizedRead(uint8_t pin0, uint8_t pin1) {

    Sync_result res;

    #if defined(__MK20DX128__)
    res.result_adc0 = ADC_ERROR_VALUE;
    res.result_adc1 = ADC_ERROR_VALUE;
    adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
    return res;
    #else

    if ( (pin0 < 0) || (pin0 > 43) || (channel2sc1aADC0[pin0]==31) ) {
        adc0->fail_flag |= ADC_ERROR_WRONG_PIN;
        res.result_adc0 = ADC_ERROR_VALUE;
        return res;
    }
    if ( (pin1 < 0) || (pin1 > 43) || (channel2sc1aADC1[pin1]==31) ) {
        adc1->fail_flag |= ADC_ERROR_WRONG_PIN;
        res.result_adc1 = ADC_ERROR_VALUE;
        return res;
    }


    // check if we are interrupting a measurement, store setting if so.
    // vars to save the current state of the ADC in case it's in use
    ADC_Module::ADC_Config old_adc0_config = {0};
    uint8_t wasADC0InUse = adc0->isConverting(); // is the ADC running now?
    if(wasADC0InUse) { // this means we're interrupting a conversion
        // save the current conversion config, the adc isr will restore the adc
        __disable_irq();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        old_adc0_config.savedSC1A = ADC0_SC1A;
        old_adc0_config.savedCFG1 = ADC0_CFG1;
        old_adc0_config.savedCFG2 = ADC0_CFG2;
        old_adc0_config.savedSC2 = ADC0_SC2;
        old_adc0_config.savedSC3 = ADC0_SC3;
        __enable_irq();
    }
    ADC_Module::ADC_Config old_adc1_config = {0};
    uint8_t wasADC1InUse = adc1->isConverting(); // is the ADC running now?
    if(wasADC1InUse) { // this means we're interrupting a conversion
        // save the current conversion config, the adc isr will restore the adc
        __disable_irq();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        old_adc1_config.savedSC1A = ADC1_SC1A;
        old_adc1_config.savedCFG1 = ADC1_CFG1;
        old_adc1_config.savedCFG2 = ADC1_CFG2;
        old_adc1_config.savedSC2 = ADC1_SC2;
        old_adc1_config.savedSC3 = ADC1_SC3;
        __enable_irq();
    }


    // start both measurements
    adc0->startSingleReadFast(pin0);
    adc1->startSingleReadFast(pin1);

    // wait for both ADCs to finish
    while( (adc0->isConverting()) || (adc1->isConverting()) ) { // wait for both to finish
        yield();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
    }
    __disable_irq(); // make sure nothing interrupts this part
    if ( adc0->isComplete() ) { // conversion succeded
        res.result_adc0 = adc0->readSingle();
    } else { // comparison was false
        adc0->fail_flag |= ADC_ERROR_COMPARISON;
        res.result_adc0 = ADC_ERROR_VALUE;
    }
    if ( adc1->isComplete() ) { // conversion succeded
        res.result_adc1 = adc1->readSingle();
    } else { // comparison was false
        adc1->fail_flag |= ADC_ERROR_COMPARISON;
        res.result_adc1 = ADC_ERROR_VALUE;
    }
    __enable_irq();



    // if we interrupted a conversion, set it again
    if (wasADC0InUse) {
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        ADC0_CFG1 = old_adc0_config.savedCFG1;
        ADC0_CFG2 = old_adc0_config.savedCFG2;
        ADC0_SC2 = old_adc0_config.savedSC2;
        ADC0_SC3 = old_adc0_config.savedSC3;
        ADC0_SC1A = old_adc0_config.savedSC1A;
    }
    if (wasADC1InUse) {
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        ADC1_CFG1 = old_adc1_config.savedCFG1;
        ADC1_CFG2 = old_adc1_config.savedCFG2;
        ADC1_SC2 = old_adc1_config.savedSC2;
        ADC1_SC3 = old_adc1_config.savedSC3;
        ADC1_SC1A = old_adc1_config.savedSC1A;
    }

    //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );

    return res;

    #endif

}

/*Returns the diff analog values of both sets of pins, measured at the same time by the two ADC modules.
* It waits until the value is read and then returns the result as a struct Sync_result,
* use Sync_result.result_adc0 and Sync_result.result_adc1.
* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct.
*/
ADC::Sync_result ADC::analogSynchronizedReadDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) {

    ADC::Sync_result res;

    #if defined(__MK20DX128__)
    res.result_adc0 = ADC_ERROR_VALUE;
    res.result_adc1 = ADC_ERROR_VALUE;
    adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
    return res;
    #else

    // check if we are interrupting a measurement, store setting if so.
    // vars to save the current state of the ADC in case it's in use
    ADC_Module::ADC_Config old_adc0_config = {0};
    uint8_t wasADC0InUse = adc0->isConverting(); // is the ADC running now?
    if(wasADC0InUse) { // this means we're interrupting a conversion
        // save the current conversion config, the adc isr will restore the adc
        __disable_irq();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        old_adc0_config.savedSC1A = ADC0_SC1A;
        old_adc0_config.savedCFG1 = ADC0_CFG1;
        old_adc0_config.savedCFG2 = ADC0_CFG2;
        old_adc0_config.savedSC2 = ADC0_SC2;
        old_adc0_config.savedSC3 = ADC0_SC3;
        __enable_irq();
    }
    ADC_Module::ADC_Config old_adc1_config = {0};
    uint8_t wasADC1InUse = adc1->isConverting(); // is the ADC running now?
    if(wasADC1InUse) { // this means we're interrupting a conversion
        // save the current conversion config, the adc isr will restore the adc
        __disable_irq();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        old_adc1_config.savedSC1A = ADC1_SC1A;
        old_adc1_config.savedCFG1 = ADC1_CFG1;
        old_adc1_config.savedCFG2 = ADC1_CFG2;
        old_adc1_config.savedSC2 = ADC1_SC2;
        old_adc1_config.savedSC3 = ADC1_SC3;
        __enable_irq();
    }

    // start both measurements
    adc0->startSingleDifferentialFast(pin0P, pin0N);
    adc1->startSingleDifferentialFast(pin1P, pin1N);

    // wait for both ADCs to finish
    while( (adc0->isConverting()) || (adc1->isConverting()) ) {
        yield();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
    }
    __disable_irq(); // make sure nothing interrupts this part
    if (adc0->isComplete()) { // conversion succeded
        res.result_adc0 = adc0->readSingle();
    } else { // comparison was false
        adc0->fail_flag |= ADC_ERROR_COMPARISON;
        res.result_adc0 = ADC_ERROR_VALUE;
    }
    if (adc1->isComplete()) { // conversion succeded
        res.result_adc1 = adc1->readSingle();
    } else { // comparison was false
        adc1->fail_flag |= ADC_ERROR_COMPARISON;
        res.result_adc1 = ADC_ERROR_VALUE;
    }
    __enable_irq();


    // if we interrupted a conversion, set it again
    if (wasADC0InUse) {
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        ADC0_CFG1 = old_adc0_config.savedCFG1;
        ADC0_CFG2 = old_adc0_config.savedCFG2;
        ADC0_SC2 = old_adc0_config.savedSC2;
        ADC0_SC3 = old_adc0_config.savedSC3;
        ADC0_SC1A = old_adc0_config.savedSC1A;
    }
    if (wasADC1InUse) {
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        ADC1_CFG1 = old_adc1_config.savedCFG1;
        ADC1_CFG2 = old_adc1_config.savedCFG2;
        ADC1_SC2 = old_adc1_config.savedSC2;
        ADC1_SC3 = old_adc1_config.savedSC3;
        ADC1_SC1A = old_adc1_config.savedSC1A;
    }

    //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );

    return res;

    #endif

}

/////////////// SYNCHRONIZED NON-BLOCKING METHODS //////////////

// Starts an analog measurement at the same time on the two ADC modules
/* It returns inmediately, get value with readSynchronizedSingle().
*   If the pin is incorrect it returns ADC_ERROR_VALUE
*   If this function interrupts a measurement, it stores the settings in adc_config
*/
int ADC::startSynchronizedSingleRead(uint8_t pin0, uint8_t pin1) {

    #if defined(__MK20DX128__)
    adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
    return false;
    #else

    // check if we are interrupting a measurement, store setting if so.
    adc0->adcWasInUse = adc0->isConverting(); // is the ADC running now?
    if(adc0->adcWasInUse) { // this means we're interrupting a conversion
        // save the current conversion config, the adc isr will restore the adc
        __disable_irq();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        adc0->adc_config.savedSC1A = ADC0_SC1A;
        adc0->adc_config.savedCFG1 = ADC0_CFG1;
        adc0->adc_config.savedCFG2 = ADC0_CFG2;
        adc0->adc_config.savedSC2 = ADC0_SC2;
        adc0->adc_config.savedSC3 = ADC0_SC3;
        __enable_irq();
    }
    adc1->adcWasInUse = adc1->isConverting(); // is the ADC running now?
    if(adc1->adcWasInUse) { // this means we're interrupting a conversion
        // save the current conversion config, the adc isr will restore the adc
        __disable_irq();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        adc1->adc_config.savedSC1A = ADC0_SC1A;
        adc1->adc_config.savedCFG1 = ADC0_CFG1;
        adc1->adc_config.savedCFG2 = ADC0_CFG2;
        adc1->adc_config.savedSC2 = ADC0_SC2;
        adc1->adc_config.savedSC3 = ADC0_SC3;
        __enable_irq();
    }


    // start both measurements
    adc0->startSingleReadFast(pin0);
    adc1->startSingleReadFast(pin1);


    //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
    return true;

    #endif
}

// Start a differential conversion between two pins (pin0P - pin0N) and (pin1P - pin1N)
/* It returns inmediately, get value with readSynchronizedSingle().
*   \param pinP must be A10 or A12.
*   \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12).
*   Other pins will return ADC_ERROR_DIFF_VALUE.
*   If this function interrupts a measurement, it stores the settings in adc_config
*/
int ADC::startSynchronizedSingleDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) {

    #if defined(__MK20DX128__)
    adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
    return false;
    #else

    // check if we are interrupting a measurement, store setting if so.
    adc0->adcWasInUse = adc0->isConverting(); // is the ADC running now?
    if(adc0->adcWasInUse) { // this means we're interrupting a conversion
        // save the current conversion config, the adc isr will restore the adc
        __disable_irq();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        adc0->adc_config.savedSC1A = ADC0_SC1A;
        adc0->adc_config.savedCFG1 = ADC0_CFG1;
        adc0->adc_config.savedCFG2 = ADC0_CFG2;
        adc0->adc_config.savedSC2 = ADC0_SC2;
        adc0->adc_config.savedSC3 = ADC0_SC3;
        __enable_irq();
    }
    adc1->adcWasInUse = adc1->isConverting(); // is the ADC running now?
    if(adc1->adcWasInUse) { // this means we're interrupting a conversion
        // save the current conversion config, the adc isr will restore the adc
        __disable_irq();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
        adc1->adc_config.savedSC1A = ADC0_SC1A;
        adc1->adc_config.savedCFG1 = ADC0_CFG1;
        adc1->adc_config.savedCFG2 = ADC0_CFG2;
        adc1->adc_config.savedSC2 = ADC0_SC2;
        adc1->adc_config.savedSC3 = ADC0_SC3;
        __enable_irq();
    }

    // start both measurements
    adc0->startSingleDifferentialFast(pin0P, pin0N);
    adc1->startSingleDifferentialFast(pin1P, pin1N);

    //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );

    return true;

    #endif

}

// Reads the analog value of a single conversion.
/*
*   \return the converted value.
*/
ADC::Sync_result ADC::readSynchronizedSingle() {
    ADC::Sync_result res;

    #if defined(__MK20DX128__)
    res.result_adc0 = ADC_ERROR_VALUE;
    res.result_adc1 = ADC_ERROR_VALUE;
    adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
    #elif defined(__MK20DX256__)
    res.result_adc0 = adc0->readSingle();
    res.result_adc1 = adc1->readSingle();
    #endif // defined

    return res;
}


///////////// SYNCHRONIZED CONTINUOUS CONVERSION METHODS ////////////

//! Starts a continuous conversion in both ADCs simultaneously
/** Use readSynchronizedContinuous to get the values
*
*/
void ADC::startSynchronizedContinuous(uint8_t pin0, uint8_t pin1) {

    #if defined(__MK20DX128__)
    adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
    return;
    #elif defined(__MK20DX256__)
    adc0->startContinuous(pin0);
    adc1->startContinuous(pin1);

    // setup the conversions the usual way, but to make sure that they are
    // as synchronized as possible we stop and restart them one after the other.
    uint32_t temp_ADC0_SC1A = ADC0_SC1A; ADC0_SC1A = 0x1F;
    uint32_t temp_ADC1_SC1A = ADC1_SC1A; ADC1_SC1A = 0x1F;

    __disable_irq(); // both measurements should have a maximum delay of an instruction time
    ADC0_SC1A = temp_ADC0_SC1A;
    ADC1_SC1A = temp_ADC1_SC1A;
    __enable_irq();
    #endif
}

//! Starts a continuous differential conversion in both ADCs simultaneously
/** Use readSynchronizedContinuous to get the values
*
*/
void ADC::startSynchronizedContinuousDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) {

    #if defined(__MK20DX128__)
    adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
    return;
    #elif defined(__MK20DX256__)
    adc0->startContinuousDifferential(pin0P, pin0N);
    adc1->startContinuousDifferential(pin1P, pin1N);

    // setup the conversions the usual way, but to make sure that they are
    // as synchronized as possible we stop and restart them one after the other.
    uint32_t temp_ADC0_SC1A = ADC0_SC1A; ADC0_SC1A = 0x1F;
    uint32_t temp_ADC1_SC1A = ADC1_SC1A; ADC1_SC1A = 0x1F;

    __disable_irq();
    ADC0_SC1A = temp_ADC0_SC1A;
    ADC1_SC1A = temp_ADC1_SC1A;
    __enable_irq();
    #endif
}

//! Returns the values of both ADCs.
ADC::Sync_result ADC::readSynchronizedContinuous() {
    ADC::Sync_result res;

    #if defined(__MK20DX128__)
    res.result_adc0 = ADC_ERROR_VALUE;
    res.result_adc1 = ADC_ERROR_VALUE;
    adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
    #elif defined(__MK20DX256__)
    res.result_adc0 = adc0->analogReadContinuous();
    res.result_adc1 = adc1->analogReadContinuous();
    #endif // defined

    return res;
}

//! Stops synchronous continuous conversion
void ADC::stopSynchronizedContinuous() {

    #if defined(__MK20DX128__)
    adc0->fail_flag |= ADC_ERROR_WRONG_ADC;
    return;
    #elif defined(__MK20DX256__)
    adc0->stopContinuous();
    adc1->stopContinuous();
    #endif // defined

}


//Our dma ISR
void dma_isr_0(void)
{
    DMA_CINT = ADC::dma_Ch0;
	Serial.print("isr, dma_chanel: "); Serial.println(ADC::dma_Ch0);
	digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
}
void dma_isr_1(void)
{
    DMA_CINT = ADC::dma_Ch1;
	Serial.print("isr, dma_chanel: "); Serial.println(ADC::dma_Ch1);
	digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
}

void ADC::useDMA(uint8_t ch0, uint8_t ch1) {
/*
    // request channels
    DMAChannel dma0, dma1;

    // Initialize buffers
    //channel

    #if defined(__MK20DX128__)
    dma_Ch0 = dma0.channel;
    if(dma_Ch0!=-1) {
        buffer0 = new RingBufferDMA(dma_Ch0);
    }
    #elif defined(__MK20DX256__)
    dma_Ch0 = dma0.channel;
    dma_Ch1 = dma1.channel;
    if(dma_Ch0!=-1) {
        buffer0 = new RingBufferDMA(dma_Ch0);
        buffer0->start();
    }
    if(dma_Ch1!=-1) {
        buffer1 = new RingBufferDMA(dma_Ch1);
        buffer1->start();
    }
    #endif

*/
}



/*
// period in seconds
void ADC_Module::startPDB(double period) {
    //                  software trigger    enable PDB     PDB interrupt
    #define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_PDBIE \
        | PDB_SC_CONT |  PDB_SC_LDMOD(0))
    //    continuous mode load inmediately
    // period = (MOD * PRESCALER * MULT)/F_BUS

    #define PDB_CH0C1_TOS 0x0100
    #define PDB_CH0C1_EN 0x01

    if (!(SIM_SCGC6 & SIM_SCGC6_PDB)) { // setup PDB
        SIM_SCGC6 |= SIM_SCGC6_PDB; // enable pdb clock
    }

    uint8_t mult = 0;
    if(period>0.05) {
        period = period/10;
        mult = 1;
    }

    double temp = period*F_BUS/65535;
    uint8_t power_2 = ceil(1.443*log(temp)); // max period 0.17 s
    if(power_2>7) {
        mult=power_2-7+1; // use mult
        power_2=7;
    }
    uint16_t mult_value = 1;
    if(mult==2) {
        mult_value = 10;
    } else if(mult==3) {
        mult_value = 20;
    } else if(mult==4) {
        mult_value = 40;
    }

    //first we set the period to be closest to the desired period,
    // then we use MOD to actually get our period

    PDB0_MOD = (uint16_t)(period/pow(2,power_2)/mult_value*F_BUS); // we adjust the counter to fit the period
    PDB0_IDLY = 0; // the pdb interrupt happens when IDLY is equal to CNT
    PDB0_SC = PDB_CONFIG | PDB_SC_PRESCALER(power_2) | PDB_SC_MULT(mult) | PDB_SC_LDOK; // load all new values

    PDB0_SC |= PDB_SC_SWTRIG; // start the counter!

    NVIC_ENABLE_IRQ(IRQ_PDB);

    // real period
    adc_pdb_period = PDB0_MOD*pow(2,power_2)*mult_value/F_BUS;

}
*/

I haven't had a chance to update to the latest version from Github, but I suspect it will solve this issue.
 
Has anyone tried the ringBufferDMA.ino example lately? I see AD values going only into the first 4 buffer slots - the last 4 never change. Perhaps there could be more discussion of what this example code should do.

More importantly, I need to start a DMA transfer from the ADC to a buffer, free running as fast as possible and stopping when the buffer is full. No interrupts. Can this library do this or should I use something else?
 
Hello,

Is it possible to use two instances of this library in order to sample two analog pins simultaneously (using ADC 0 and 1), and then sample another two analog pins simultaneously (again using ADC 0 and 1, but different pins) in order to obtain 4 individual analog samples pseudo-simultaneously? I realize that there will be a time difference between the first set obtained and the second set.

Thanks for your help.
 
Hello,

Is it possible to use two instances of this library in order to sample two analog pins simultaneously (using ADC 0 and 1), and then sample another two analog pins simultaneously (again using ADC 0 and 1, but different pins) in order to obtain 4 individual analog samples pseudo-simultaneously? I realize that there will be a time difference between the first set obtained and the second set.

Thanks for your help.


If im reading your question right then yes.

This is what I do with my Egocart ADC's, im measuring Individual Battery voltages, Bus Voltage, current, throttle, motor temp and Heatsink temp. In total it takes around 34uS.

Code:
    result = adc->analogSynchronizedRead(aThrottle, aCurrent);
    throttle[averaged] = result.result_adc0;
    current[averaged] = result.result_adc1;
    result = adc->analogSynchronizedRead(aBusV, aBattV);
    busv[averaged] = result.result_adc0;
    battv[averaged] = result.result_adc1;
    result = adc->analogSynchronizedRead(aBattV3, aBattV2);
    battv3[averaged] = result.result_adc0;
    battv2[averaged] = result.result_adc1;    
    result = adc->analogSynchronizedRead(aMotorT, aHST);
    motort[averaged] = result.result_adc0;
    hst[averaged] = result.result_adc1;
 
If im reading your question right then yes.

This is what I do with my Egocart ADC's, im measuring Individual Battery voltages, Bus Voltage, current, throttle, motor temp and Heatsink temp. In total it takes around 34uS.

Code:
    result = adc->analogSynchronizedRead(aThrottle, aCurrent);
    throttle[averaged] = result.result_adc0;
    current[averaged] = result.result_adc1;
    result = adc->analogSynchronizedRead(aBusV, aBattV);
    busv[averaged] = result.result_adc0;
    battv[averaged] = result.result_adc1;
    result = adc->analogSynchronizedRead(aBattV3, aBattV2);
    battv3[averaged] = result.result_adc0;
    battv2[averaged] = result.result_adc1;    
    result = adc->analogSynchronizedRead(aMotorT, aHST);
    motort[averaged] = result.result_adc0;
    hst[averaged] = result.result_adc1;

Thank you, Donziboy. I was trying to create two instances of the ADC class, start both of them with continuos synchronous sampling, and then poll each instance for 2 sets of 2 results. The second call to each ADC looked remarkably similar to the first call (but not quite identical). This works great, your help is appreciated!
 
As a person that has used digital signal analysers for many years, and more recently started analyzing data in matlab, I am now trying my hand at a controller using the Teensy. So you might say I know a lot of the principles from a high level, but implementation details were never required until now. From this perspective, I reviewed the ADC library documentation from the .h files, a bit of the manual, and scanned this thread, to try and obtain implementation information.

The Questions I had in mind when preparing the following summary.

What do different functions do and how does it impact the CPU loading? This document attempts to answer that by reviewing the header files mostly, which are quite well documented, and reviewing the example files.

Outstanding questions?

Presume that the CPU is busy with ADC related stuff while the call is being completed. To relieve the CPU, the non-blocking calls are included. The ADC is started. The CPU can do other work. When the ADC is complete, an ISR is called to retrieve the data. How much CPU time is relieved by this method? The timing accuracay is determined by when the startSynchronizedSingleDifferential is called.

If accurate timing is needed, there are two methods. If nothing else is going on, a delay can be used between calls for the ADC. If there are other things keeping the CPU busy, then an interval timer can be used to call the ADC using ISR's.

If using an interval timer for calling the ADC, what are the relative advantages and disadvantages of using a blocking method vs. non-blocking?

If I use non blocking, with the startSynchronizedSingleDifferential call initiated by the interval timer. Then the ISR upon completion can put the digital value into the ring buffer. Then a program that is executing can retrieve the data from the ring buffer as needed.

Probably a good way to start is use the ring buffer without DMA. On the basis of starting simple. How much performance improvement is possible using DMA?

I assume that averaging will help with significant digits, but at a cost of increased ADC time?

And a copy of the summary that I wrote is attached. If this template is useful to add to the library documentation, once some of the questions within it are answered, I would be honoured to let you use it.
 

Attachments

  • Documentation.doc
    57 KB · Views: 246
I would like to measure both the absolute value of a an incoming signal along with the differential value. Thus I need single ended mode for the absolute value, and differential mode for added precision. I need to do this on two channels and plan on measuring the two signals simultaneously. I am planning to use the IntervalTimer to schedule non-blocking synchronous ADC calls. Using the current library the code will have two ISR's. One is called by the IntervalTimer, and the other is called when the ADC system has finished its work.

There are two different approaches I am considering. Perhaps the simplest approach is to use the current ADC library, and use variables and if statements to select whether to call the differential measurement or the single ended measurement.

However a cleaner approach would be able to tell the ADC library which ISR to call when a non-blocking ADC conversion has completed, much like we tell the IntervalTimer which ISR to call. Then when the ADC is completed a differential measurement, one ISR is called, and when the ADC has completed a single ended measurement another ISR is called. Not being a C++ programmer, I would appreciate knowing how much work would be involved in making this change to the ADC library? And is this a feature that would be useful to others?
 
jonr, The ringbuffer dma has always given that sort of problems, I've tried to solve them but I haven't been very successful.
Also, what you want to do is not implemented in the library, however the DMAChannel library (DMAChannel.h in the teensy3 folder) has many functions that will help you there.

mikestebbins, it makes little sense two have to ADC library objects, the ADC library already controls all underlying hardware. If you create two of them they will "fight" against each other, so please don't do it. If you want access to the ADC modules then create the ADc object and access them directly with adc->adcX.

Jake, I'm reading your documentation, it seems to me that you understand many things but not everything. I'll try to answer you questions soon!
Also, to make the ADC execute a different function at completion you can use
attachInterruptVector(IRQ_ADC0/IRQ_ADC1, void (*function)(void));
 
Last edited:
I have my data collection working with the attachInterruptVector as you suggested, thank you.
 
Thanks. I was able to use the DMAChannel library to get continuous AD with DMA.

Hi jonr,

I am seeing the same problem with the DMA ring buffer. Half of the buffer is not used, independent of the size of the buffer.
Is it possible to share your source or part of it?

My goal is to measure 4 signals continuously for 8 secs, each at a sample rate of at least 10 kHz and send it over USB to my laptop.
I want to achieve this with a minimum amount of jitter.

Thanks
 
Last edited:
Sure, here is what I have.

Code:
// WORKS - reads A/D values into buffer as fast as possible (about 2.7 usec/sample)
// Could probably be modified to use two ADCs for twice the rate
// based on various sources

#include <string.h>

void setup() {

  delay(1000);
  Serial.println("hello");

}
#define BUF_SIZE 128
//DMAMEM static uint16_t adcbuffer[BUF_SIZE];
DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer[BUF_SIZE];

void loop() {

  int i;
  unsigned cycles;

   // set up cycle counter
  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
  
  // clear buffer
  for (i = 0; i < BUF_SIZE; ++i)
      adcbuffer[i] = 0;
      
  Serial.println(adcbuffer[0]);
  Serial.println(adcbuffer[1]);
 
  Serial.println("do dma");
  setup_dma(38);  // 38 is temp sensor

  while (adcbuffer[1] == 0) {};
  cycles = ARM_DWT_CYCCNT;
  while (adcbuffer[2] == 0) {};
  
  //delayMicroseconds(32);    // 2.6 or 2.7 usec/sample

  // Serial.println(adcbuffer[11]);

  Serial.println(ARM_DWT_CYCCNT - cycles);

#if 0
  uint16_t buffer[BUF_SIZE];
  // copy results
  memcpy((void *)buffer,(void *)adcbuffer,sizeof(adcbuffer));

  // display results - see how many samples we got
  for (i = 0; i < 24; ++i)
      Serial.println(buffer[i]);
 #endif
 
  for (;;) {}

}  // loop()




// #include "pdb.h"
#include <DMAChannel.h>


DMAChannel dma(false);

void setup_dma(int pin) {

  // Configure the ADC and run at least one software-triggered
  // conversion.  This completes the self calibration stuff and
  // leaves the ADC in a state that's mostly ready to use
  analogReadRes(16);
  analogReference(INTERNAL); // INTERNAL OR DEFAULT
  analogReadAveraging(0);
  analogRead(pin);
  delay(1);

#if 0
  // set the programmable delay block to trigger DMA requests
  SIM_SCGC6 |= SIM_SCGC6_PDB; // enable PDB clock
  PDB0_IDLY = 0; // interrupt delay register
  PDB0_MOD = PDB_PERIOD; // modulus register, sets period
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // load registers from buffers
  PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG; // reset and restart
  PDB0_CH0C1 = 0x0101; // channel n control register?
#endif

  dma.begin(true);              // allocate the DMA channel first
  dma.TCD->SADDR = &ADC0_RA;    // where to read from
  dma.TCD->SOFF = 0;            // source increment each transfer
  dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1);
  dma.TCD->NBYTES_MLNO = 2;     // bytes per transfer
  dma.TCD->SLAST = 0;

  dma.destinationBuffer(adcbuffer, sizeof(adcbuffer));   // destinaton
  dma.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma.disableOnCompletion();    // require restart in code
  
  dma.enable();

  // start first one

  ADC0_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  ADC0_SC3 |= ADC_SC3_ADCO;
  ADC0_SC1A = get_pin(pin);   // set to hardware input channel
  
}  // setup_dma()



// convert pin name to hardware pin
// teensy 3.1 only

#if defined(__MK20DX256__)

int 
get_pin(int pin)
{
 
static const uint8_t channel2sc1a[] = {
  5, 14, 8, 9, 13, 12, 6, 7, 15, 4,
  0, 19, 3, 19+128, 26, 18+128, 23,
  5+192, 5+128, 4+128, 6+128, 7+128, 4+192
// A15  26   E1   ADC1_SE5a  5+64
// A16  27   C9   ADC1_SE5b  5
// A17  28   C8   ADC1_SE4b  4
// A18  29   C10  ADC1_SE6b  6
// A19  30   C11  ADC1_SE7b  7
// A20  31   E0   ADC1_SE4a  4+64
};

int index;

  if (pin <= 13) {
    index = pin;      // 0-13 refer to A0-A13
  } else if (pin <= 23) {
    index = pin - 14; // 14-23 are A0-A9
  } else if (pin >= 26 && pin <= 31) {
    index = pin - 9;  // 26-31 are A15-A20
  } else if (pin >= 34 && pin <= 40) {
    index = pin - 24; // 34-37 are A10-A13, 38 is temp sensor,
                // 39 is vref, 40 is A14
  } else {
    return 5;
  }

return channel2sc1a[index];

}   // get_pin()

#endif
 
Is there any way to get the ADC to sample at some fixed number of clock ticks offset from a PWM channel output transition? I have an experiment where the T3.1 PWM output is the stimulus and the ADC measures a response, but any variability in the relative timing will affect the precision of the measurement, so I want the PWM edge and the ADC sample timing to be locked together. Thanks for any insights!

(EDIT: This is a summary of the question I asked here: https://forum.pjrc.com/threads/29790-synchronize-ADC-samples-with-PWM-output )
 
Last edited:
getMaxValue in the library says it gets 2^res-1. When I try it with a number of values with differential mode I always get 65535. I also notice that with differential mode, according to the manual on page 657, the value is stored in twos-complement output. Thus the max value should be 2^(res-1)-1 at least for 16 bit mode. It appears that the other modes in differential add a bit to the conversion according to this table.

Poking around in the ADC library the file ADC_Module.cpp on lines 376 to 402 contains the code that sets the maximum value. This sets the maximum value to 65535 for 16 bit mode differential and single ended.

Code:
     // conversion resolution
    // single-ended 8 bits is the same as differential 9 bits, etc.
    if ( (config == 8) || (config == 9) )  {
        // *ADC_CFG1_mode1 = 0;
        // *ADC_CFG1_mode0 = 0;
        clearBit(ADC_CFG1, ADC_CFG1_MODE1_BIT);
        clearBit(ADC_CFG1, ADC_CFG1_MODE0_BIT);
        analog_max_val = 255; // diff mode 9 bits has 1 bit for sign, so max value is the same as single 8 bits
    } else if ( (config == 10 )|| (config == 11) ) {
        // *ADC_CFG1_mode1 = 1;
        // *ADC_CFG1_mode0 = 0;
        setBit(ADC_CFG1, ADC_CFG1_MODE1_BIT);
        clearBit(ADC_CFG1, ADC_CFG1_MODE0_BIT);
        analog_max_val = 1023;
    } else if ( (config == 12 )|| (config == 13) ) {
        // o*ADC_CFG1_mode1 = 0;
        // *ADC_CFG1_mode0 = 1;
        clearBit(ADC_CFG1, ADC_CFG1_MODE1_BIT);
        setBit(ADC_CFG1, ADC_CFG1_MODE0_BIT);
        analog_max_val = 4095;
    } else {
        // *ADC_CFG1_mode1 = 1;
        // *ADC_CFG1_mode0 = 1;
        setBit(ADC_CFG1, ADC_CFG1_MODE1_BIT);
        setBit(ADC_CFG1, ADC_CFG1_MODE0_BIT);
        analog_max_val = 65535;
    }

Then the code in lines 1058 to 1070 multiplies the 16 bit differential output by two to make the analog_max_value correct, after converting the values to 32 bit.

Code:
    startDifferentialFast(pinP, pinN); // start conversion

    // wait for the ADC to finish
    while( isConverting() ) {
        yield();
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
    }

    // it's done, check if the comparison (if any) was true
    int32_t result;
    __disable_irq(); // make sure nothing interrupts this part
    if (isComplete()) { // conversion succeded
        result = (int16_t)(int32_t)(*ADC_RA); // cast to 32 bits
        if(res==16) { // 16 bit differential is actually 15 bit + 1 bit sign
            result *= 2; // multiply by 2 as if it were really 16 bits, so that getMaxValue gives a correct value.
        }
    } else { // comparison was false
        result = ADC_ERROR_VALUE;
        fail_flag |= ADC_ERROR_COMPARISON;
    }
    __enable_irq();

    // if we interrupted a conversion, set it again
    if (wasADCInUse) {
        __disable_irq();
        loadConfig(&old_config);
        __enable_irq();
    }

Additional insight appreciated.
 
Last edited:
I am not familiar with C++, but when I review the RingBuffer code, I believe that to change the default ring buffer size, I need to change the define statement in the RingBuffer.h code

Code:
// THE SIZE MUST BE A POWER OF 2!!
#define RING_BUFFER_DEFAULT_BUFFER_SIZE 8

Is there a way to do this with a function call instead?
 
I want to continuous read a voltage (using ADC library) and I wonder what could be the best settings for setSamplingSpeed, setConversionSpeed and setAveraging parameters to get the best results.

I'm reading this feedback voltage to regulate the output of a sinewave inverter (230V AC). I'm using a low-power 50Hz transformer to convert the 230V AC output voltage to 9V DC voltage then I'm using a voltage divider to further convert it to 3.3V (I'm using a Teensy 3.1).

As I only call analogReadContinuous every 10ms (during AC waveform zero crossing), I want to find a way to extend the average readings for the whole 10ms interval. For now I'm using setAveraging to 32 hence I'm only getting the average of the last 32 voltage readings prior zero crossing (if it takes 1us for one reading then I get the last 32us average voltage).

Is there a way to get an extended average voltage without doing it in the main subroutine? (I want to save the CPU power for other tasks, including serial reading/printing and such)

Does it help if I choose the lowest settings for setSamplingSpeed and setConversionSpeed? What's the maximum value for setAveraging (I've read about 32 but I'm not quite sure)?

Thanks in advance for any clue.
 
Teensy LC: error: 'DMAMUX_SOURCE_ADC1' was not declared in this scope

The RingBufferDMA wouldn't compile for Teensy LC target. I don't use it in my code but I couldn't get past it.

The error was:
arduino-1.6.6/hardware/teensy/avr/libraries/ADC/RingBufferDMA.cpp: In member function 'void RingBufferDMA::start()':
arduino-1.6.6/hardware/teensy/avr/libraries/ADC/RingBufferDMA.cpp:108:29: error: 'DMAMUX_SOURCE_ADC1' was not declared in this scope
DMAMUX_SOURCE_ADC = DMAMUX_SOURCE_ADC1;

Since Teensy LC doesn't have the second ADC, I just did the quick fix below to get it to compile. Not sure if it's the right fix but it works.

Code:
--- arduino-1.6.6/hardware/teensy/avr/libraries/ADC/RingBufferDMA.cpp.orig	2015-12-06 18:24:09.495189809 -0800
+++ arduino-1.6.6/hardware/teensy/avr/libraries/ADC/RingBufferDMA.cpp	2015-12-06 18:24:11.238206692 -0800
@@ -104,9 +104,11 @@
 
 
 	uint8_t DMAMUX_SOURCE_ADC = DMAMUX_SOURCE_ADC0;
+#ifdef DMAMUX_SOURCE_ADC1
     if(ADC_number==1){
         DMAMUX_SOURCE_ADC = DMAMUX_SOURCE_ADC1;
     }
+#endif
 
     // point here so call_dma_isr actually calls this object's isr function
     static_ringbuffer_dma = this;
 
Laur I just had this same problem with my LC even though I know it used to work with LC; made a thread in support, thanks for posting a fix, I'm a little unclear with exactly where it goes or if it replaces anything. I tried replacing what looked like that in RingBufferDMA.cpp but get RingBuffer.cpp:27:4: error: 'uint8_t' does not name a type

https://forum.pjrc.com/threads/31932-Teensy-LC-DMAMUX_SOURCE_ADC1-was-not-declared
 
Last edited:
The change I did is at line 107.

It was like this:
Code:
     if(ADC_number==1){
         DMAMUX_SOURCE_ADC = DMAMUX_SOURCE_ADC1;
     }

now it's like this:
Code:
#ifdef DMAMUX_SOURCE_ADC1
     if(ADC_number==1){
         DMAMUX_SOURCE_ADC = DMAMUX_SOURCE_ADC1;
     }
#endif

Hope this helps!
 
Hi,
Im using teensy 3.0 to read an adxl335 at high frequency through an amp.
Im having a hard time reaching the high sampling rates mentioned here(632K etc).. when i set for no averaging, 8 bit resolution and fastest sampling and conversion, i only get some 8us per sample..
tinkering with the registers myself got me to 5us but i couldn`t get any lower..
I need the fastest sampling i can get, any help will be appreciated!
my code attached:

Code:
#include <ADC.h>
#include <ADC_Module.h>
#include <RingBuffer.h>
#include <RingBufferDMA.h>

ADC *adc = new ADC();

unsigned int rate=0, g=0, i=0, samples=100, microslast=0, val=0;


void setup() {
  Serial.begin(9600);
  pinMode(A3, INPUT);
  adc->setAveraging(0); 
  adc->setResolution(8); 
  adc->setSamplingSpeed(ADC_VERY_HIGH_SPEED);
  adc->setConversionSpeed(ADC_VERY_HIGH_SPEED);
  
     
}

void loop() {

  i=0;
  microslast=micros();
  while(i<samples){
    val = adc->analogRead(A3);
    g=g+val;
    i++;
  }
  
  rate=micros()-microslast;
  g=g/samples;
  Serial.print(rate);
  Serial.print("  ");
  Serial.println(g);
  

}
 
Last edited:
Back
Top