Arduino-IRremote/examples/AllProtocolsOnLCD/ADCUtils.hpp

800 lines
30 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/gpl.html>.
*/
#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