/* Teensy 4.x, 3.x, LC ADC library * https://github.com/pedvide/ADC * Copyright (c) 2020 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_Module.h: Declarations of the fuctions of a Teensy 3.x, LC ADC module * */ /*! \page adc_module ADC Module Control each ADC_Module independently. See the ADC_Module class for all methods. */ #ifndef ADC_MODULE_H #define ADC_MODULE_H #include #include #include using ADC_Error::ADC_ERROR; using namespace ADC_settings; // debug mode: blink the led light #define ADC_debug 0 /** Class ADC_Module: Implements all functions of the Teensy 3.x, LC analog to digital converter * */ class ADC_Module { public: #if ADC_DIFF_PAIRS > 0 //! \cond internal //! Dictionary with the differential pins as keys and the SC1A number as values /** Internal, do not use. */ struct ADC_NLIST { //! Pin and corresponding SC1A value. uint8_t pin, sc1a; }; #endif //! \endcond #if ADC_DIFF_PAIRS > 0 //! Constructor /** Pass the ADC number and the Channel number to SC1A number arrays. * \param ADC_number Number of the ADC module, from 0. * \param a_channel2sc1a contains an index that pairs each pin to its SC1A number (used to start a conversion on that pin) * \param a_diff_table is similar to a_channel2sc1a, but for differential pins. * \param a_adc_regs pointer to start of the ADC registers */ ADC_Module(uint8_t ADC_number, const uint8_t *const a_channel2sc1a, const ADC_NLIST *const a_diff_table, ADC_REGS_t &a_adc_regs); #else //! Constructor /** Pass the ADC number and the Channel number to SC1A number arrays. * \param ADC_number Number of the ADC module, from 0. * \param a_channel2sc1a contains an index that pairs each pin to its SC1A number (used to start a conversion on that pin) * \param a_adc_regs pointer to start of the ADC registers */ ADC_Module(uint8_t ADC_number, const uint8_t *const a_channel2sc1a, ADC_REGS_t &a_adc_regs); #endif //! Starts the calibration sequence, waits until it's done and writes the results /** Usually it's not necessary to call this function directly, but do it if the "environment" changed * significantly since the program was started. */ void recalibrate(); //! Starts the calibration sequence void calibrate(); //! Waits until calibration is finished and writes the corresponding registers void wait_for_cal(); /////////////// METHODS TO SET/GET SETTINGS OF THE ADC //////////////////// //! Set the voltage reference you prefer, default is vcc /*! * \param ref_type can be ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_1V2 (not for Teensy LC) or ADC_REFERENCE::REF_EXT * * It recalibrates at the end. */ void setReference(ADC_REFERENCE ref_type); //! 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 immediate 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); //! Returns the resolution of the ADC_Module. /** * \return the resolution of the ADC_Module. */ uint8_t getResolution(); //! Returns the maximum value for a measurement: 2^res-1. /** * \return the maximum value for a measurement: 2^res-1. */ uint32_t getMaxValue(); //! Sets the conversion speed (changes the ADC clock, ADCK) /** * \param speed can be any from the ADC_CONVERSION_SPEED enum: VERY_LOW_SPEED, LOW_SPEED, MED_SPEED, HIGH_SPEED_16BITS, HIGH_SPEED, VERY_HIGH_SPEED, * ADACK_2_4, ADACK_4_0, ADACK_5_2 or ADACK_6_2. * * 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 LOW_SPEED only for 24, 4 or 2 MHz bus frequency. * LOW_SPEED is guaranteed to be the lowest possible speed within specs for all resolutions (higher than 2 MHz). * MED_SPEED is always >= LOW_SPEED and <= HIGH_SPEED. * HIGH_SPEED_16BITS is guaranteed to be the highest possible speed within specs for all resolutions (lower or eq than 12 MHz). * HIGH_SPEED is guaranteed to be the highest possible speed within specs for resolutions less than 16 bits (lower or eq than 18 MHz). * VERY_HIGH_SPEED may be out of specs, it's different from HIGH_SPEED only for 48, 40 or 24 MHz bus frequency. * * Additionally the conversion speed can also be ADACK_2_4, ADACK_4_0, ADACK_5_2 and ADACK_6_2, * where the numbers are the frequency of the ADC clock (ADCK) in MHz and are independent on the bus speed. * This is useful if you are using the Teensy at a very low clock frequency but want faster conversions, * but if F_BUS= compValue (greaterThan=1) or < compValue (greaterThan=0) * Call it after changing the resolution * Use with interrupts or poll conversion completion with isComplete() * \param compValue value to compare * \param greaterThan true or false */ void enableCompare(int16_t compValue, bool greaterThan); //! 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() * \param lowerLimit lower value to compare * \param upperLimit upper value to compare * \param insideRange true or false * \param inclusive true or false */ void enableCompareRange(int16_t lowerLimit, int16_t upperLimit, bool insideRange, bool inclusive); //! Disable the compare function void disableCompare(); #ifdef ADC_USE_PGA //! Enable and set PGA /** Enables the PGA and sets the gain * Use only for signals lower than 1.2 V and only in differential mode * \param gain can be 1, 2, 4, 8, 16, 32 or 64 */ void enablePGA(uint8_t gain); //! Returns the PGA level /** * \return PGA level from 1 to 64 */ uint8_t getPGA(); //! Disable PGA void disablePGA(); #endif //! Set continuous conversion mode void continuousMode() __attribute__((always_inline)) { #ifdef ADC_TEENSY_4 atomic::setBitFlag(adc_regs.GC, ADC_GC_ADCO); #else atomic::setBitFlag(adc_regs.SC3, ADC_SC3_ADCO); #endif } //! Set single-shot conversion mode void singleMode() __attribute__((always_inline)) { #ifdef ADC_TEENSY_4 atomic::clearBitFlag(adc_regs.GC, ADC_GC_ADCO); #else atomic::clearBitFlag(adc_regs.SC3, ADC_SC3_ADCO); #endif } //! Set single-ended conversion mode void singleEndedMode() __attribute__((always_inline)) { #ifdef ADC_TEENSY_4 // Teensy 4 is always single-ended #else atomic::clearBitFlag(adc_regs.SC1A, ADC_SC1_DIFF); #endif } #if ADC_DIFF_PAIRS > 0 //! Set differential conversion mode void differentialMode() __attribute__((always_inline)) { atomic::setBitFlag(adc_regs.SC1A, ADC_SC1_DIFF); } #endif //! Use software to trigger the ADC, this is the most common setting void setSoftwareTrigger() __attribute__((always_inline)) { #ifdef ADC_TEENSY_4 atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADTRG); #else atomic::clearBitFlag(adc_regs.SC2, ADC_SC2_ADTRG); #endif } //! Use hardware to trigger the ADC void setHardwareTrigger() __attribute__((always_inline)) { #ifdef ADC_TEENSY_4 atomic::setBitFlag(adc_regs.CFG, ADC_CFG_ADTRG); #else atomic::setBitFlag(adc_regs.SC2, ADC_SC2_ADTRG); #endif } ////////////// INFORMATION ABOUT THE STATE OF THE ADC ///////////////// //! Is the ADC converting at the moment? /** * \return true or false */ volatile bool isConverting() __attribute__((always_inline)) { #ifdef ADC_TEENSY_4 return atomic::getBitFlag(adc_regs.GS, ADC_GS_ADACT); #else //return (ADC_SC2_adact); return atomic::getBitFlag(adc_regs.SC2, ADC_SC2_ADACT); //return ((adc_regs.SC2) & ADC_SC2_ADACT) >> 7; #endif } //! Is an ADC conversion ready? /** * \return true if yes, false if not. * When a value is read this function returns false until a new value exists, * so it only makes sense to call it before analogReadContinuous() or readSingle() */ volatile bool isComplete() __attribute__((always_inline)) { #ifdef ADC_TEENSY_4 return atomic::getBitFlag(adc_regs.HS, ADC_HS_COCO0); #else //return (ADC_SC1A_coco); return atomic::getBitFlag(adc_regs.SC1A, ADC_SC1_COCO); //return ((adc_regs.SC1A) & ADC_SC1_COCO) >> 7; #endif } #if ADC_DIFF_PAIRS > 0 //! Is the ADC in differential mode? /** * \return true or false */ volatile bool isDifferential() __attribute__((always_inline)) { //return ((adc_regs.SC1A) & ADC_SC1_DIFF) >> 5; return atomic::getBitFlag(adc_regs.SC1A, ADC_SC1_DIFF); } #endif //! Is the ADC in continuous mode? /** * \return true or false */ volatile bool isContinuous() __attribute__((always_inline)) { #ifdef ADC_TEENSY_4 return atomic::getBitFlag(adc_regs.GC, ADC_GC_ADCO); #else //return (ADC_SC3_adco); return atomic::getBitFlag(adc_regs.SC3, ADC_SC3_ADCO); //return ((adc_regs.SC3) & ADC_SC3_ADCO) >> 3; #endif } #ifdef ADC_USE_PGA //! Is the PGA function enabled? /** * \return true or false */ volatile bool isPGAEnabled() __attribute__((always_inline)) { return atomic::getBitFlag(adc_regs.PGA, ADC_PGA_PGAEN); } #endif //////////////// INFORMATION ABOUT VALID PINS ////////////////// //! Check whether the pin is a valid analog pin /** * \param pin to check. * \return true if the pin is valid, false otherwise. */ bool checkPin(uint8_t pin); //! Check whether the pins are a valid analog differential pair of pins /** If PGA is enabled it also checks that this ADCx can use PGA on this pins * \param pinP positive pin to check. * \param pinN negative pin to check. * \return true if the pin is valid, false otherwise. */ bool checkDifferentialPins(uint8_t pinP, uint8_t pinN); //////////////// HELPER METHODS FOR CONVERSION ///////////////// //! Starts a single-ended conversion on the pin /** It sets the mux correctly, doesn't do any of the checks on the pin and * doesn't change the continuous conversion bit. * \param pin to read. */ void startReadFast(uint8_t pin); // helper method #if ADC_DIFF_PAIRS > 0 //! Starts a differential conversion on the pair of pins /** It sets the mux correctly, doesn't do any of the checks on the pin and * doesn't change the continuous conversion bit. * \param pinP positive pin to read. * \param pinN negative pin to read. */ void startDifferentialFast(uint8_t pinP, uint8_t pinN); #endif //////////////// 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 * \param pin pin to read. * \return the value of the pin. */ int analogRead(uint8_t pin); //! Returns the analog value of the special internal source, such as the temperature sensor. /** It calls analogRead(uint8_t pin) internally, with the correct value for the pin for all boards. * Possible values: * TEMP_SENSOR, Temperature sensor. * VREF_OUT, 1.2 V reference (switch on first using VREF.h). * BANDGAP, BANDGAP (switch on first using VREF.h). * VREFH, High VREF. * VREFL, Low VREF. * \param pin ADC_INTERNAL_SOURCE to read. * \return the value of the pin. */ int analogRead(ADC_INTERNAL_SOURCE pin) __attribute__((always_inline)) { return analogRead(static_cast(pin)); } #if ADC_DIFF_PAIRS > 0 //! 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_DIFF_VALUE. * \param pinP must be A10 or A12. * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). * \return the difference between the pins if they are valid, othewise returns ADC_ERROR_DIFF_VALUE. * This function is interrupt safe, so it will restore the adc to the state it was before being called */ int analogReadDifferential(uint8_t pinP, uint8_t pinN); #endif /////////////// NON-BLOCKING CONVERSION METHODS ////////////// //! Starts an analog measurement on the pin and enables interrupts. /** It returns immediately, get value with readSingle(). * If this function interrupts a measurement, it stores the settings in adc_config * \param pin pin to read. * \return true if the pin is valid, false otherwise. */ bool startSingleRead(uint8_t pin); #if ADC_DIFF_PAIRS > 0 //! Start a differential conversion between two pins (pinP - pinN) and enables interrupts. /** It returns immediately, get value with readSingle(). * If this function interrupts a measurement, it stores the settings in adc_config * \param pinP must be A10 or A12. * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). * \return true if the pins are valid, false otherwise. */ bool startSingleDifferential(uint8_t pinP, uint8_t pinN); #endif //! 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() __attribute__((always_inline)) { return analogReadContinuous(); } ///////////// CONTINUOUS CONVERSION METHODS //////////// //! Starts continuous conversion on the pin. /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value. * \param pin can be any of the analog pins * \return true if the pin is valid, false otherwise. */ bool startContinuous(uint8_t pin); #if ADC_DIFF_PAIRS > 0 //! 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). * \return true if the pins are valid, false otherwise. */ bool startContinuousDifferential(uint8_t pinP, uint8_t pinN); #endif //! 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! */ int analogReadContinuous() __attribute__((always_inline)) { #ifdef ADC_TEENSY_4 return (int16_t)(int32_t)adc_regs.R0; #else return (int16_t)(int32_t)adc_regs.RA; #endif } //! Stops continuous conversion void stopContinuous(); //////////// FREQUENCY METHODS //////// // The general API is: // void startTimer(uint32_t freq) // void stopTimer() // uint32_t getTimerFrequency() // For each board the best timer method will be selected //////////// PDB //////////////// //// Only works for Teensy 3.x not LC nor Tensy 4.0 (they don't have PDB) #if defined(ADC_USE_PDB) //! Start the default timer (PDB) triggering the ADC at the frequency /** The default timer in this board is the PDB, you can also call it directly with startPDB(). * Call startSingleRead or startSingleDifferential on the pin that you want to measure before calling this function. * See the example adc_pdb.ino. * \param freq is the frequency of the ADC conversion, it can't be lower that 1 Hz */ void startTimer(uint32_t freq) __attribute__((always_inline)) { startPDB(freq); } //! Start PDB triggering the ADC at the frequency /** Call startSingleRead or startSingleDifferential on the pin that you want to measure before calling this function. * See the example adc_pdb.ino. * \param freq is the frequency of the ADC conversion, it can't be lower that 1 Hz */ void startPDB(uint32_t freq); //! Stop the default timer (PDB) void stopTimer() __attribute__((always_inline)) { stopPDB(); } //! Stop the PDB void stopPDB(); //! Return the default timer's (PDB) frequency /** The default timer in this board is the PDB, you can also call it directly with getPDBFrequency(). * \return the timer's frequency in Hz. */ uint32_t getTimerFrequency() __attribute__((always_inline)) { return getPDBFrequency(); } //! Return the PDB's frequency /** Return the PDB's frequency * \return the timer's frequency in Hz. */ uint32_t getPDBFrequency(); //////////// TIMER //////////////// //// Only works for Teensy 3.x and 4 (not LC) #elif defined(ADC_USE_QUAD_TIMER) //! Start the default timer (QuadTimer) triggering the ADC at the frequency /** The default timer in this board is the QuadTimer, you can also call it directly with startQuadTimer(). * Call startSingleRead or startSingleDifferential on the pin that you want to measure before calling this function. * See the example adc_timer.ino. * \param freq is the frequency of the ADC conversion, it can't be lower that 1 Hz */ void startTimer(uint32_t freq) __attribute__((always_inline)) { startQuadTimer(freq); } //! Start a Quad timer to trigger the ADC at the frequency /** Call startSingleRead or startSingleDifferential on the pin that you want to measure before calling this function. * See the example adc_timer.ino. * \param freq is the frequency of the ADC conversion, it can't be lower that 1 Hz */ void startQuadTimer(uint32_t freq); //! Stop the default timer (QuadTimer) void stopTimer() __attribute__((always_inline)) { stopQuadTimer(); } //! Stop the Quad timer void stopQuadTimer(); //! Return the default timer's (QuadTimer) frequency /** The default timer in this board is the QuadTimer, you can also call it directly with getQuadTimerFrequency(). * \return the timer's frequency in Hz. */ uint32_t getTimerFrequency() __attribute__((always_inline)) { return getQuadTimerFrequency(); } //! Return the Quad timer's frequency /** Return the Quad timer's frequency * \return the timer's frequency in Hz. */ uint32_t getQuadTimerFrequency(); #endif //////// OTHER STUFF /////////// //! Store the config of the adc struct ADC_Config { //! ADC registers #ifdef ADC_TEENSY_4 uint32_t savedHC0, savedCFG, savedGC, savedGS; #else uint32_t savedSC1A, savedSC2, savedSC3, savedCFG1, savedCFG2; #endif } adc_config; //! Was the adc in use before a call? uint8_t adcWasInUse; /** Save config of the ADC to the ADC_Config struct * \param config ADC_Config where the config will be stored */ void saveConfig(ADC_Config *config) { #ifdef ADC_TEENSY_4 config->savedHC0 = adc_regs.HC0; config->savedCFG = adc_regs.CFG; config->savedGC = adc_regs.GC; config->savedGS = adc_regs.GS; #else config->savedSC1A = adc_regs.SC1A; config->savedCFG1 = adc_regs.CFG1; config->savedCFG2 = adc_regs.CFG2; config->savedSC2 = adc_regs.SC2; config->savedSC3 = adc_regs.SC3; #endif } /** Load config to the ADC * \param config ADC_Config from where the config will be loaded */ void loadConfig(const ADC_Config *config) { #ifdef ADC_TEENSY_4 adc_regs.HC0 = config->savedHC0; adc_regs.CFG = config->savedCFG; adc_regs.GC = config->savedGC; adc_regs.GS = config->savedGS; #else adc_regs.CFG1 = config->savedCFG1; adc_regs.CFG2 = config->savedCFG2; adc_regs.SC2 = config->savedSC2; adc_regs.SC3 = config->savedSC3; adc_regs.SC1A = config->savedSC1A; // restore last #endif } //! Number of measurements that the ADC is performing uint8_t num_measurements; //! This flag indicates that some kind of error took place /** Use the defines at the beginning of this file to find out what caused the fail. */ volatile ADC_ERROR fail_flag; //! Resets all errors from the ADC, if any. void resetError() { ADC_Error::resetError(fail_flag); } //! Which adc is this? const uint8_t ADC_num; private: // is set to 1 when the calibration procedure is taking place uint8_t calibrating; // the first calibration will use 32 averages and lowest speed, // when this calibration is over the averages and speed will be set to default. uint8_t init_calib; // resolution uint8_t analog_res_bits; // maximum value possible 2^res-1 uint32_t analog_max_val; // num of averages uint8_t analog_num_average; // reference can be internal or external ADC_REF_SOURCE analog_reference_internal; #ifdef ADC_USE_PGA // value of the pga uint8_t pga_value; #endif // conversion speed ADC_CONVERSION_SPEED conversion_speed; // sampling speed ADC_SAMPLING_SPEED sampling_speed; // translate pin number to SC1A nomenclature const uint8_t *const channel2sc1a; // are interrupts on? bool interrupts_enabled; // same for differential pins #if ADC_DIFF_PAIRS > 0 const ADC_NLIST *const diff_table; //! Get the SC1A value of the differential pair for this pin uint8_t getDifferentialPair(uint8_t pin) { for (uint8_t i = 0; i < ADC_DIFF_PAIRS; i++) { if (diff_table[i].pin == pin) { return diff_table[i].sc1a; } } return ADC_SC1A_PIN_INVALID; } #endif //! Initialize ADC void analog_init(); //! Switch on clock to ADC void startClock() { #if defined(ADC_TEENSY_4) if (ADC_num == 0) { CCM_CCGR1 |= CCM_CCGR1_ADC1(CCM_CCGR_ON); } else { CCM_CCGR1 |= CCM_CCGR1_ADC2(CCM_CCGR_ON); } #else if (ADC_num == 0) { SIM_SCGC6 |= SIM_SCGC6_ADC0; } else { SIM_SCGC3 |= SIM_SCGC3_ADC1; } #endif } // registers point to the correct ADC module typedef volatile uint32_t ® // registers that control the adc module ADC_REGS_t &adc_regs; #ifdef ADC_USE_PDB reg PDB0_CHnC1; // PDB channel 0 or 1 #endif #ifdef ADC_TEENSY_4 uint8_t XBAR_IN; uint8_t XBAR_OUT; uint8_t QTIMER4_INDEX; uint8_t ADC_ETC_TRIGGER_INDEX; #endif const IRQ_NUMBER_t IRQ_ADC; // IRQ number protected: }; #endif // ADC_MODULE_H