/* * ADCUtils.hpp * * ADC utility functions. Conversion time is defined as 0.104 milliseconds for 16 MHz Arduinos in ADCUtils.h. * * Copyright (C) 2016-2023 Armin Joachimsmeyer * Email: armin.joachimsmeyer@gmail.com * * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. * * ArduinoUtils is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef _ADC_UTILS_HPP #define _ADC_UTILS_HPP #include "ADCUtils.h" #if defined(ADC_UTILS_ARE_AVAILABLE) // set in ADCUtils.h, if supported architecture was detected #if !defined(STR_HELPER) #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) #endif /* * By replacing this value with the voltage you measured a the AREF pin after a conversion * with INTERNAL you can calibrate your ADC readout. For my Nanos I measured e.g. 1060 mV and 1093 mV. */ #if !defined(ADC_INTERNAL_REFERENCE_MILLIVOLT) #define ADC_INTERNAL_REFERENCE_MILLIVOLT 1100L // Change to value measured at the AREF pin. If value > real AREF voltage, measured values are > real values #endif // Union to speed up the combination of low and high bytes to a word // it is not optimal since the compiler still generates 2 unnecessary moves // but using -- value = (high << 8) | low -- gives 5 unnecessary instructions union WordUnionForADCUtils { struct { uint8_t LowByte; uint8_t HighByte; } UByte; uint16_t UWord; int16_t Word; uint8_t *BytePointer; }; /* * Enable this to see information on each call. * Since there should be no library which uses Serial, it should only be enabled for development purposes. */ #if defined(DEBUG) && !defined(LOCAL_DEBUG) #define LOCAL_DEBUG #else //#define LOCAL_DEBUG // This enables debug output only for this file #endif /* * Persistent storage for VCC value */ float sVCCVoltage; uint16_t sVCCVoltageMillivolt; // for isVCCTooLowMultipleTimes() long sLastVCCCheckMillis; uint8_t sVCCTooLowCounter = 0; /* * Conversion time is defined as 0.104 milliseconds by ADC_PRESCALE in ADCUtils.h. */ uint16_t readADCChannel(uint8_t aADCChannelNumber) { WordUnionForADCUtils tUValue; ADMUX = aADCChannelNumber | (DEFAULT << SHIFT_VALUE_FOR_REFERENCE); // ADCSRB = 0; // Only active if ADATE is set to 1. // ADSC-StartConversion ADIF-Reset Interrupt Flag - NOT free running mode ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADIF) | ADC_PRESCALE); // wait for single conversion to finish loop_until_bit_is_clear(ADCSRA, ADSC); // Get value tUValue.UByte.LowByte = ADCL; tUValue.UByte.HighByte = ADCH; return tUValue.UWord; // return ADCL | (ADCH <<8); // needs 4 bytes more } /* * Conversion time is defined as 0.104 milliseconds by ADC_PRESCALE in ADCUtils.h. */ uint16_t readADCChannelWithReference(uint8_t aADCChannelNumber, uint8_t aReference) { WordUnionForADCUtils tUValue; ADMUX = aADCChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE); // ADCSRB = 0; // Only active if ADATE is set to 1. // ADSC-StartConversion ADIF-Reset Interrupt Flag - NOT free running mode ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADIF) | ADC_PRESCALE); // wait for single conversion to finish loop_until_bit_is_clear(ADCSRA, ADSC); // Get value tUValue.UByte.LowByte = ADCL; tUValue.UByte.HighByte = ADCH; return tUValue.UWord; } /* * Conversion time is defined as 0.104 milliseconds by ADC_PRESCALE in ADCUtils.h. * Does NOT restore ADMUX after reading */ uint16_t waitAndReadADCChannelWithReference(uint8_t aADCChannelNumber, uint8_t aReference) { checkAndWaitForReferenceAndChannelToSwitch(aADCChannelNumber, aReference); return readADCChannelWithReference(aADCChannelNumber, aReference); } /* * Conversion time is defined as 0.104 milliseconds by ADC_PRESCALE in ADCUtils.h. * Restores ADMUX after reading */ uint16_t waitAndReadADCChannelWithReferenceAndRestoreADMUXAndReference(uint8_t aADCChannelNumber, uint8_t aReference) { uint8_t tOldADMUX = checkAndWaitForReferenceAndChannelToSwitch(aADCChannelNumber, aReference); uint16_t tResult = readADCChannelWithReference(aADCChannelNumber, aReference); checkAndWaitForReferenceAndChannelToSwitch(tOldADMUX & MASK_FOR_ADC_CHANNELS, tOldADMUX >> SHIFT_VALUE_FOR_REFERENCE); return tResult; } /* * To prepare reference and ADMUX for next measurement */ void setADCChannelAndReferenceForNextConversion(uint8_t aADCChannelNumber, uint8_t aReference) { ADMUX = aADCChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE); } /* * @return original ADMUX register content for optional later restoring values * All experimental values are acquired by using the ADCSwitchingTest example from this library */ uint8_t checkAndWaitForReferenceAndChannelToSwitch(uint8_t aADCChannelNumber, uint8_t aReference) { uint8_t tOldADMUX = ADMUX; /* * Must wait >= 7 us if reference has to be switched from 1.1 volt/INTERNAL to VCC/DEFAULT (seen on oscilloscope) * This is done after the 2 ADC clock cycles required for Sample & Hold :-) * * Must wait >= 7600 us for Nano board >= 6200 for Uno board if reference has to be switched from VCC/DEFAULT to 1.1 volt/INTERNAL * Must wait >= 200 us if channel has to be switched to 1.1 volt internal channel if S&H was at 5 Volt */ uint8_t tNewReference = (aReference << SHIFT_VALUE_FOR_REFERENCE); ADMUX = aADCChannelNumber | tNewReference; #if defined(INTERNAL2V56) if ((tOldADMUX & MASK_FOR_ADC_REFERENCE) != tNewReference && (aReference == INTERNAL || aReference == INTERNAL2V56)) { #else if ((tOldADMUX & MASK_FOR_ADC_REFERENCE) != tNewReference && aReference == INTERNAL) { #endif #if defined(LOCAL_DEBUG) Serial.println(F("Switch from DEFAULT to INTERNAL")); #endif /* * Switch reference from DEFAULT to INTERNAL */ delayMicroseconds(8000); // experimental value is >= 7600 us for Nano board and 6200 for Uno board } else if ((tOldADMUX & ADC_CHANNEL_MUX_MASK) != aADCChannelNumber) { if (aADCChannelNumber == ADC_1_1_VOLT_CHANNEL_MUX) { /* * Internal 1.1 Volt channel requires <= 200 us for Nano board */ delayMicroseconds(350); // 350 was ok and 300 was too less for UltimateBatteryTester - result was 226 instead of 225 } else { /* * 100 kOhm requires < 100 us, 1 MOhm requires 120 us S&H switching time */ delayMicroseconds(120); // experimental value is <= 1100 us for Nano board } } return tOldADMUX; } /* * Oversample and multiple samples only makes sense if you expect a noisy input signal * It does NOT increase the precision of the measurement, since the ADC has insignificant noise */ uint16_t readADCChannelWithOversample(uint8_t aADCChannelNumber, uint8_t aOversampleExponent) { return readADCChannelWithReferenceOversample(aADCChannelNumber, DEFAULT, aOversampleExponent); } /* * Conversion time is defined as 0.104 milliseconds by ADC_PRESCALE in ADCUtils.h. */ uint16_t readADCChannelWithReferenceOversample(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aOversampleExponent) { uint16_t tSumValue = 0; ADMUX = aADCChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE); ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1. // ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE); uint8_t tCount = _BV(aOversampleExponent); for (uint8_t i = 0; i < tCount; i++) { /* * wait for free running conversion to finish. * Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion. */ loop_until_bit_is_set(ADCSRA, ADIF); ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished // Add value tSumValue += ADCL | (ADCH << 8); // using WordUnionForADCUtils does not save space here // tSumValue += (ADCH << 8) | ADCL; // this does NOT work! } ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode) // return rounded value return ((tSumValue + (tCount >> 1)) >> aOversampleExponent); } /* * Use ADC_PRESCALE32 which gives 26 us conversion time and good linearity for 16 MHz Arduino */ uint16_t readADCChannelWithReferenceOversampleFast(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aOversampleExponent) { uint16_t tSumValue = 0; ADMUX = aADCChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE); ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1. // ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE32); uint8_t tCount = _BV(aOversampleExponent); for (uint8_t i = 0; i < tCount; i++) { /* * wait for free running conversion to finish. * Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion. */ loop_until_bit_is_set(ADCSRA, ADIF); ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished // Add value tSumValue += ADCL | (ADCH << 8); // using WordUnionForADCUtils does not save space here // tSumValue += (ADCH << 8) | ADCL; // this does NOT work! } ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode) return ((tSumValue + (tCount >> 1)) >> aOversampleExponent); } /* * Returns sum of all sample values * Conversion time is defined as 0.104 milliseconds for 16 MHz Arduino by ADC_PRESCALE (=ADC_PRESCALE128) in ADCUtils.h. * @ param aNumberOfSamples If > 64 an overflow may occur. */ uint16_t readADCChannelMultiSamplesWithReference(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aNumberOfSamples) { uint16_t tSumValue = 0; ADMUX = aADCChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE); ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1. // ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE); for (uint8_t i = 0; i < aNumberOfSamples; i++) { /* * wait for free running conversion to finish. * Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion. */ loop_until_bit_is_set(ADCSRA, ADIF); ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished // Add value tSumValue += ADCL | (ADCH << 8); // using WordUnionForADCUtils does not save space here // tSumValue += (ADCH << 8) | ADCL; // this does NOT work! } ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode) return tSumValue; } /* * Returns sum of all sample values * Conversion time is defined as 0.104 milliseconds for 16 MHz Arduino for ADC_PRESCALE128 in ADCUtils.h. * @ param aPrescale can be one of ADC_PRESCALE2, ADC_PRESCALE4, 8, 16, 32, 64, 128. * ADC_PRESCALE32 is recommended for excellent linearity and fast readout of 26 microseconds * @ param aNumberOfSamples If > 16k an overflow may occur. */ uint32_t readADCChannelMultiSamplesWithReferenceAndPrescaler(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aPrescale, uint16_t aNumberOfSamples) { uint32_t tSumValue = 0; ADMUX = aADCChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE); ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1. // ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | aPrescale); for (uint16_t i = 0; i < aNumberOfSamples; i++) { /* * wait for free running conversion to finish. * Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion. */ loop_until_bit_is_set(ADCSRA, ADIF); ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished // Add value tSumValue += ADCL | (ADCH << 8); // using WordUnionForADCUtils does not save space here // tSumValue += (ADCH << 8) | ADCL; // this does NOT work! } ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode) return tSumValue; } /* * Returns sum of all sample values * Assumes, that channel and reference are still set to the right values * @ param aNumberOfSamples If > 16k an overflow may occur. */ uint32_t readADCChannelMultiSamples(uint8_t aPrescale, uint16_t aNumberOfSamples) { uint32_t tSumValue = 0; ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1. // ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | aPrescale); for (uint16_t i = 0; i < aNumberOfSamples; i++) { /* * wait for free running conversion to finish. * Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion. */ loop_until_bit_is_set(ADCSRA, ADIF); ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished // Add value tSumValue += ADCL | (ADCH << 8); // using WordUnionForADCUtils does not save space here // tSumValue += (ADCH << 8) | ADCL; // this does NOT work! } ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode) return tSumValue; } /* * use ADC_PRESCALE32 which gives 26 us conversion time and good linearity * @return the maximum value of aNumberOfSamples samples. */ uint16_t readADCChannelWithReferenceMax(uint8_t aADCChannelNumber, uint8_t aReference, uint16_t aNumberOfSamples) { uint16_t tADCValue = 0; uint16_t tMaximum = 0; ADMUX = aADCChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE); ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1. // ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE32); for (uint16_t i = 0; i < aNumberOfSamples; i++) { /* * wait for free running conversion to finish. * Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion. */ loop_until_bit_is_set(ADCSRA, ADIF); ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished // check value tADCValue = ADCL | (ADCH << 8); if (tADCValue > tMaximum) { tMaximum = tADCValue; } } ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode) return tMaximum; } /* * use ADC_PRESCALE32 which gives 26 us conversion time and good linearity * @return the maximum value during aMicrosecondsToAquire measurement. */ uint16_t readADCChannelWithReferenceMaxMicros(uint8_t aADCChannelNumber, uint8_t aReference, uint16_t aMicrosecondsToAquire) { uint16_t tNumberOfSamples = aMicrosecondsToAquire / 26; return readADCChannelWithReferenceMax(aADCChannelNumber, aReference, tNumberOfSamples); } /* * aMaxRetries = 255 -> try forever * @return (tMax + tMin) / 2 */ uint16_t readUntil4ConsecutiveValuesAreEqual(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aDelay, uint8_t aAllowedDifference, uint8_t aMaxRetries) { int tValues[4]; // last value is in tValues[3] int tMin; int tMax; /* * Initialize first 4 values before checking */ tValues[0] = readADCChannelWithReference(aADCChannelNumber, aReference); for (int i = 1; i < 4; ++i) { if (aDelay != 0) { delay(aDelay); // Minimum is only 3 delays! } tValues[i] = readADCChannelWithReference(aADCChannelNumber, aReference); } do { /* * Get min and max of the last 4 values */ tMin = 1024; tMax = 0; for (uint_fast8_t i = 0; i < 4; ++i) { if (tValues[i] < tMin) { tMin = tValues[i]; } if (tValues[i] > tMax) { tMax = tValues[i]; } } /* * check for terminating condition */ if ((tMax - tMin) <= aAllowedDifference) { break; } else { /* * Get next value */ // Serial.print("Difference="); // Serial.println(tMax - tMin); // Move values to front for (int i = 0; i < 3; ++i) { tValues[i] = tValues[i + 1]; } // and wait before getting next value if (aDelay != 0) { delay(aDelay); } tValues[3] = readADCChannelWithReference(aADCChannelNumber, aReference); } if (aMaxRetries != 255) { aMaxRetries--; } } while (aMaxRetries > 0); #if defined(LOCAL_DEBUG) if(aMaxRetries == 0) { Serial.print(F("No 4 equal values for difference ")); Serial.print(aAllowedDifference); Serial.print(F(" found ")); Serial.print(tValues[0]); Serial.print(' '); Serial.print(tValues[1]); Serial.print(' '); Serial.print(tValues[2]); Serial.print(' '); Serial.println(tValues[3]); } else { Serial.print(aMaxRetries); Serial.println(F(" retries left")); } #endif return (tMax + tMin) / 2; } /* * !!! Function without handling of switched reference and channel.!!! * Use it ONLY if you only call getVCCVoltageSimple() or getVCCVoltageMillivoltSimple() in your program. * !!! Resolution is only 20 millivolt !!! * Raw reading of 1.1 V is 225 at 5 V. * Raw reading of 1.1 V is 221 at 5.1 V. * Raw reading of 1.1 V is 214 at 5.25 V (+5 %). * Raw reading of 1.1 V is 204 at 5.5 V (+10 %). */ float getVCCVoltageSimple(void) { // use AVCC with (optional) external capacitor at AREF pin as reference float tVCC = readADCChannelMultiSamplesWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 4); return ((1023 * 1.1 * 4) / tVCC); } /* * !!! Function without handling of switched reference and channel.!!! * Use it ONLY if you only call getVCCVoltageSimple() or getVCCVoltageMillivoltSimple() in your program. * !!! Resolution is only 20 millivolt !!! */ uint16_t getVCCVoltageMillivoltSimple(void) { // use AVCC with external capacitor at AREF pin as reference uint16_t tVCC = readADCChannelMultiSamplesWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 4); return ((1023L * ADC_INTERNAL_REFERENCE_MILLIVOLT * 4) / tVCC); } /* * Gets the hypothetical 14 bit reading of VCC using 1.1 volt reference * Similar to getVCCVoltageMillivolt() * 1023 / 1100 */ uint16_t getVCCVoltageReadingFor1_1VoltReference(void) { uint16_t tVCC = waitAndReadADCChannelWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT); /* * Do not switch back ADMUX to enable checkAndWaitForReferenceAndChannelToSwitch() to work correctly for the next measurement */ return ((1023L * 1023L) / tVCC); } /* * !!! Resolution is only 20 millivolt !!! */ float getVCCVoltage(void) { return (getVCCVoltageMillivolt() / 1000.0); } /* * Read value of 1.1 volt internal channel using VCC (DEFAULT) as reference. * Handles reference and channel switching by introducing the appropriate delays. * !!! Resolution is only 20 millivolt !!! * Raw reading of 1.1 V is 225 at 5 V. * Raw reading of 1.1 V is 221 at 5.1 V. * Raw reading of 1.1 V is 214 at 5.25 V (+5 %). * Raw reading of 1.1 V is 204 at 5.5 V (+10 %). */ uint16_t getVCCVoltageMillivolt(void) { uint16_t tVCC = waitAndReadADCChannelWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT); /* * Do not switch back ADMUX to enable checkAndWaitForReferenceAndChannelToSwitch() to work correctly for the next measurement */ return ((1023L * ADC_INTERNAL_REFERENCE_MILLIVOLT) / tVCC); } /* * Does not set sVCCVoltageMillivolt */ uint16_t printVCCVoltageMillivolt(Print *aSerial) { aSerial->print(F("VCC=")); uint16_t tVCCVoltageMillivolt = getVCCVoltageMillivolt(); aSerial->print(tVCCVoltageMillivolt); aSerial->println(" mV"); return tVCCVoltageMillivolt; } void readAndPrintVCCVoltageMillivolt(Print *aSerial) { aSerial->print(F("VCC=")); sVCCVoltageMillivolt = getVCCVoltageMillivolt(); aSerial->print(sVCCVoltageMillivolt); aSerial->println(" mV"); } /* * !!! Function without handling of switched reference and channel.!!! * Use it ONLY if you only call getVCCVoltageSimple() or getVCCVoltageMillivoltSimple() in your program. * !!! Resolution is only 20 millivolt !!! */ void readVCCVoltageSimple(void) { // use AVCC with (optional) external capacitor at AREF pin as reference float tVCC = readADCChannelMultiSamplesWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 4); sVCCVoltage = (1023 * (((float) ADC_INTERNAL_REFERENCE_MILLIVOLT) / 1000) * 4) / tVCC; } /* * !!! Function without handling of switched reference and channel.!!! * Use it ONLY if you only call getVCCVoltageSimple() or getVCCVoltageMillivoltSimple() in your program. * !!! Resolution is only 20 millivolt !!! */ void readVCCVoltageMillivoltSimple(void) { // use AVCC with external capacitor at AREF pin as reference uint16_t tVCCVoltageMillivoltRaw = readADCChannelMultiSamplesWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 4); sVCCVoltageMillivolt = (1023L * ADC_INTERNAL_REFERENCE_MILLIVOLT * 4) / tVCCVoltageMillivoltRaw; } /* * !!! Resolution is only 20 millivolt !!! */ void readVCCVoltage(void) { sVCCVoltage = getVCCVoltageMillivolt() / 1000.0; } /* * Read value of 1.1 volt internal channel using VCC (DEFAULT) as reference. * Handles reference and channel switching by introducing the appropriate delays. * !!! Resolution is only 20 millivolt !!! * Sets also the sVCCVoltageMillivolt variable. */ void readVCCVoltageMillivolt(void) { uint16_t tVCCVoltageMillivoltRaw = waitAndReadADCChannelWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT); /* * Do not switch back ADMUX to enable checkAndWaitForReferenceAndChannelToSwitch() to work correctly for the next measurement */ sVCCVoltageMillivolt = (1023L * ADC_INTERNAL_REFERENCE_MILLIVOLT) / tVCCVoltageMillivoltRaw; } /* * Get voltage at ADC channel aADCChannelForVoltageMeasurement * aVCCVoltageMillivolt is assumed as reference voltage */ uint16_t getVoltageMillivolt(uint16_t aVCCVoltageMillivolt, uint8_t aADCChannelForVoltageMeasurement) { uint16_t tInputVoltageRaw = waitAndReadADCChannelWithReference(aADCChannelForVoltageMeasurement, DEFAULT); return (aVCCVoltageMillivolt * (uint32_t) tInputVoltageRaw) / 1023; } /* * Get voltage at ADC channel aADCChannelForVoltageMeasurement * Reference voltage VCC is determined just before */ uint16_t getVoltageMillivolt(uint8_t aADCChannelForVoltageMeasurement) { uint16_t tInputVoltageRaw = waitAndReadADCChannelWithReference(aADCChannelForVoltageMeasurement, DEFAULT); return (getVCCVoltageMillivolt() * (uint32_t) tInputVoltageRaw) / 1023; } uint16_t getVoltageMillivoltWith_1_1VoltReference(uint8_t aADCChannelForVoltageMeasurement) { uint16_t tInputVoltageRaw = waitAndReadADCChannelWithReference(aADCChannelForVoltageMeasurement, INTERNAL); return (ADC_INTERNAL_REFERENCE_MILLIVOLT * (uint32_t) tInputVoltageRaw) / 1023; } /* * Return true if sVCCVoltageMillivolt is > 4.3 V and < 4.95 V */ bool isVCCUSBPowered() { readVCCVoltageMillivolt(); return (VOLTAGE_USB_POWERED_LOWER_THRESHOLD_MILLIVOLT < sVCCVoltageMillivolt && sVCCVoltageMillivolt < VOLTAGE_USB_POWERED_UPPER_THRESHOLD_MILLIVOLT); } /* * Return true if sVCCVoltageMillivolt is > 4.3 V and < 4.95 V */ bool isVCCUSBPowered(Print *aSerial) { readVCCVoltageMillivolt(); aSerial->print(F("USB powered is ")); bool tReturnValue; if (VOLTAGE_USB_POWERED_LOWER_THRESHOLD_MILLIVOLT < sVCCVoltageMillivolt&& sVCCVoltageMillivolt < VOLTAGE_USB_POWERED_UPPER_THRESHOLD_MILLIVOLT) { tReturnValue = true; aSerial->print(F("true ")); } else { tReturnValue = false; aSerial->print(F("false ")); } printVCCVoltageMillivolt(aSerial); return tReturnValue; } /* * @ return true only once, when VCC_UNDERVOLTAGE_CHECKS_BEFORE_STOP (6) times voltage too low -> shutdown */ bool isVCCUndervoltageMultipleTimes() { /* * Check VCC every VCC_CHECK_PERIOD_MILLIS (10) seconds */ if (millis() - sLastVCCCheckMillis >= VCC_CHECK_PERIOD_MILLIS) { sLastVCCCheckMillis = millis(); # if defined(INFO) readAndPrintVCCVoltageMillivolt(&Serial); # else readVCCVoltageMillivolt(); # endif if (sVCCTooLowCounter < VCC_UNDERVOLTAGE_CHECKS_BEFORE_STOP) { /* * Do not check again if shutdown has happened */ if (sVCCVoltageMillivolt > VCC_UNDERVOLTAGE_THRESHOLD_MILLIVOLT) { sVCCTooLowCounter = 0; // reset counter } else { /* * Voltage too low, wait VCC_UNDERVOLTAGE_CHECKS_BEFORE_STOP (6) times and then shut down. */ if (sVCCVoltageMillivolt < VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT) { // emergency shutdown sVCCTooLowCounter = VCC_UNDERVOLTAGE_CHECKS_BEFORE_STOP; # if defined(INFO) Serial.println( F( "Voltage < " STR(VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT) " mV detected -> emergency shutdown")); # endif } else { sVCCTooLowCounter++; # if defined(INFO) Serial.print(F("Voltage < " STR(VCC_UNDERVOLTAGE_THRESHOLD_MILLIVOLT) " mV detected: ")); Serial.print(VCC_UNDERVOLTAGE_CHECKS_BEFORE_STOP - sVCCTooLowCounter); Serial.println(F(" tries left")); # endif } if (sVCCTooLowCounter == VCC_UNDERVOLTAGE_CHECKS_BEFORE_STOP) { /* * 6 times voltage too low -> return signal for shutdown etc. */ return true; } } } } return false; } /* * Return true if VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT (3 V) reached */ bool isVCCUndervoltage() { readVCCVoltageMillivolt(); return (sVCCVoltageMillivolt < VCC_UNDERVOLTAGE_THRESHOLD_MILLIVOLT); } /* * Return true if VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT (3 V) reached */ bool isVCCEmergencyUndervoltage() { readVCCVoltageMillivolt(); return (sVCCVoltageMillivolt < VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT); } void resetCounterForVCCUndervoltageMultipleTimes() { sVCCTooLowCounter = 0; } /* * Recommended VCC is 1.8 V to 5.5 V, absolute maximum VCC is 6.0 V. * Check for 5.25 V, because such overvoltage is quite unlikely to happen during regular operation. * Raw reading of 1.1 V is 225 at 5 V. * Raw reading of 1.1 V is 221 at 5.1 V. * Raw reading of 1.1 V is 214 at 5.25 V (+5 %). * Raw reading of 1.1 V is 204 at 5.5 V (+10 %). * @return true if 5 % overvoltage reached */ bool isVCCOvervoltage() { readVCCVoltageMillivolt(); return (sVCCVoltageMillivolt > VCC_OVERVOLTAGE_THRESHOLD_MILLIVOLT); } bool isVCCOvervoltageSimple() { readVCCVoltageMillivoltSimple(); return (sVCCVoltageMillivolt > VCC_OVERVOLTAGE_THRESHOLD_MILLIVOLT); } /* * Temperature sensor is enabled by selecting the appropriate channel. * Different formula for 328P and 328PB! * !!! Function without handling of switched reference and channel.!!! * Use it ONLY if you only use INTERNAL reference (e.g. only call getTemperatureSimple()) in your program. */ float getCPUTemperatureSimple(void) { #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) return 0.0; #else // use internal 1.1 volt as reference. 4 times oversample. Assume the signal has noise, but never verified :-( uint16_t tTempRaw = readADCChannelWithReferenceOversample(ADC_TEMPERATURE_CHANNEL_MUX, INTERNAL, 2); #if defined(LOCAL_DEBUG) Serial.print(F("TempRaw=")); Serial.println(tTempRaw); #endif #if defined(__AVR_ATmega328PB__) tTempRaw -= 245; return (float)tTempRaw; #else tTempRaw -= 317; return (float) tTempRaw / 1.22; #endif #endif } /* * Handles usage of 1.1 V reference and channel switching by introducing the appropriate delays. */ float getTemperature(void) { return getCPUTemperature(); } float getCPUTemperature(void) { #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) return 0.0; #else // use internal 1.1 volt as reference checkAndWaitForReferenceAndChannelToSwitch(ADC_TEMPERATURE_CHANNEL_MUX, INTERNAL); return getCPUTemperatureSimple(); #endif } #else // defined(ADC_UTILS_ARE_AVAILABLE) // Dummy definition of functions defined in ADCUtils to compile examples for non AVR platforms without errors /* * Persistent storage for VCC value */ float sVCCVoltage; uint16_t sVCCVoltageMillivolt; uint16_t getVCCVoltageMillivoltSimple(void){ return 3300; } uint16_t readADCChannelWithReferenceOversample(uint8_t aChannelNumber __attribute__((unused)), uint8_t aReference __attribute__((unused)), uint8_t aOversampleExponent __attribute__((unused))) { return 0; } float getCPUTemperature() { return 20.0; } float getVCCVoltage() { return 3.3; } #endif // defined(ADC_UTILS_ARE_AVAILABLE) #if defined(LOCAL_DEBUG) #undef LOCAL_DEBUG #endif #endif // _ADC_UTILS_HPP