Skip to content

Commit ae53665

Browse files
committed
Improved output
1 parent 6b96d5d commit ae53665

File tree

4 files changed

+806
-244
lines changed

4 files changed

+806
-244
lines changed

SBMInfo/ADCUtils.cpp

Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
/*
2+
* ADCUtils.cpp
3+
*
4+
* ADC utility functions. Conversion time is defined as 0.104 milliseconds for 16 MHz Arduinos in ADCUtils.h.
5+
*
6+
* Copyright (C) 2016-2020 Armin Joachimsmeyer
7+
8+
*
9+
* This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils.
10+
*
11+
* ArduinoUtils is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* This program is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU General Public License
22+
* along with this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
23+
*/
24+
25+
#include "ADCUtils.h"
26+
#if defined(__AVR__) && defined(ADATE)
27+
28+
// Union to speed up the combination of low and high bytes to a word
29+
// it is not optimal since the compiler still generates 2 unnecessary moves
30+
// but using -- value = (high << 8) | low -- gives 5 unnecessary instructions
31+
union Myword {
32+
struct {
33+
uint8_t LowByte;
34+
uint8_t HighByte;
35+
} byte;
36+
uint16_t UWord;
37+
int16_t Word;
38+
uint8_t *BytePointer;
39+
};
40+
41+
/*
42+
* Conversion time is defined as 0.104 milliseconds for 16 MHz Arduino by ADC_PRESCALE in ADCUtils.h.
43+
*/
44+
uint16_t readADCChannel(uint8_t aChannelNumber) {
45+
Myword tUValue;
46+
ADMUX = aChannelNumber | (DEFAULT << SHIFT_VALUE_FOR_REFERENCE);
47+
48+
// ADCSRB = 0; // Only active if ADATE is set to 1.
49+
// ADSC-StartConversion ADIF-Reset Interrupt Flag - NOT free running mode
50+
ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADIF) | ADC_PRESCALE);
51+
52+
// wait for single conversion to finish
53+
loop_until_bit_is_clear(ADCSRA, ADSC);
54+
55+
// Get value
56+
tUValue.byte.LowByte = ADCL;
57+
tUValue.byte.HighByte = ADCH;
58+
return tUValue.UWord;
59+
// return ADCL | (ADCH <<8); // needs 4 bytes more
60+
}
61+
62+
/*
63+
* Conversion time is defined as 0.104 milliseconds for 16 MHz Arduino by ADC_PRESCALE in ADCUtils.h.
64+
*/
65+
uint16_t readADCChannelWithReference(uint8_t aChannelNumber, uint8_t aReference) {
66+
Myword tUValue;
67+
ADMUX = aChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE);
68+
69+
// ADCSRB = 0; // Only active if ADATE is set to 1.
70+
// ADSC-StartConversion ADIF-Reset Interrupt Flag - NOT free running mode
71+
ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADIF) | ADC_PRESCALE);
72+
73+
// wait for single conversion to finish
74+
loop_until_bit_is_clear(ADCSRA, ADSC);
75+
76+
// Get value
77+
tUValue.byte.LowByte = ADCL;
78+
tUValue.byte.HighByte = ADCH;
79+
return tUValue.UWord;
80+
}
81+
82+
/*
83+
* @return original ADMUX register content for optional later restoring values
84+
* All experimental values are acquired by using the ADCSwitchingTest example from this library
85+
*/
86+
uint8_t checkAndWaitForReferenceAndChannelToSwitch(uint8_t aChannelNumber, uint8_t aReference) {
87+
uint8_t tOldADMUX = ADMUX;
88+
/*
89+
* Must wait >= 7 us if reference has to be switched from 1.1 volt/INTERNAL to VCC/DEFAULT (seen on oscilloscope)
90+
* This is done after the 2 ADC clock cycles required for Sample & Hold :-)
91+
*
92+
* 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
93+
* Must wait >= 200 us if channel has to be switched to 1.1 volt internal channel if S&H was at 5 Volt
94+
*/
95+
uint8_t tNewReference = (aReference << SHIFT_VALUE_FOR_REFERENCE);
96+
ADMUX = aChannelNumber | tNewReference;
97+
if ((tOldADMUX & MASK_FOR_ADC_REFERENCE) != tNewReference && aReference == INTERNAL) {
98+
/*
99+
* Switch reference from DEFAULT to INTERNAL
100+
*/
101+
delayMicroseconds(8000); // experimental value is >= 7600 us for Nano board and 6200 for UNO board
102+
} else if ((tOldADMUX & 0x0F) != aChannelNumber) {
103+
if (aChannelNumber == ADC_1_1_VOLT_CHANNEL_MUX) {
104+
/*
105+
* Internal 1.1 Volt channel requires <= 200 us for Nano board
106+
*/
107+
delayMicroseconds(200);
108+
} else {
109+
/*
110+
* 100 kOhm requires < 100 us, 1 MOhm requires 120 us S&H switching time
111+
*/
112+
delayMicroseconds(120); // experimental value is <= 1100 us for Nano board
113+
}
114+
}
115+
return tOldADMUX;
116+
}
117+
118+
uint16_t readADCChannelWithOversample(uint8_t aChannelNumber, uint8_t aOversampleExponent) {
119+
return readADCChannelWithReferenceOversample(aChannelNumber, DEFAULT, aOversampleExponent);
120+
}
121+
122+
/*
123+
* Conversion time is defined as 0.104 milliseconds for 16 MHz Arduino by ADC_PRESCALE in ADCUtils.h.
124+
*/
125+
uint16_t readADCChannelWithReferenceOversample(uint8_t aChannelNumber, uint8_t aReference, uint8_t aOversampleExponent) {
126+
uint16_t tSumValue = 0;
127+
ADMUX = aChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE);
128+
129+
ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1.
130+
// ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag
131+
ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE);
132+
133+
for (uint8_t i = 0; i < _BV(aOversampleExponent); i++) {
134+
/*
135+
* wait for free running conversion to finish.
136+
* Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion.
137+
*/
138+
loop_until_bit_is_set(ADCSRA, ADIF);
139+
140+
ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished
141+
// Add value
142+
tSumValue += ADCL | (ADCH << 8); // using myWord does not save space here
143+
// tSumValue += (ADCH << 8) | ADCL; // this does NOT work!
144+
}
145+
ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode)
146+
return (tSumValue >> aOversampleExponent);
147+
}
148+
149+
/*
150+
* Use ADC_PRESCALE32 which gives 26 us conversion time and good linearity for 16 MHz Arduino
151+
*/
152+
uint16_t readADCChannelWithReferenceOversampleFast(uint8_t aChannelNumber, uint8_t aReference, uint8_t aOversampleExponent) {
153+
uint16_t tSumValue = 0;
154+
ADMUX = aChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE);
155+
156+
ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1.
157+
// ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag
158+
ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE32);
159+
160+
for (uint8_t i = 0; i < _BV(aOversampleExponent); i++) {
161+
/*
162+
* wait for free running conversion to finish.
163+
* Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion.
164+
*/
165+
loop_until_bit_is_set(ADCSRA, ADIF);
166+
167+
ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished
168+
// Add value
169+
tSumValue += ADCL | (ADCH << 8); // using myWord does not save space here
170+
// tSumValue += (ADCH << 8) | ADCL; // this does NOT work!
171+
}
172+
ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode)
173+
return (tSumValue >> aOversampleExponent);
174+
}
175+
176+
/*
177+
* Returns sum of all sample values
178+
* Conversion time is defined as 0.104 milliseconds for 16 MHz Arduino by ADC_PRESCALE in ADCUtils.h.
179+
*/
180+
uint16_t readADCChannelWithReferenceMultiSamples(uint8_t aChannelNumber, uint8_t aReference, uint8_t aNumberOfSamples) {
181+
uint16_t tSumValue = 0;
182+
ADMUX = aChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE);
183+
184+
ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1.
185+
// ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag
186+
ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE);
187+
188+
for (uint8_t i = 0; i < aNumberOfSamples; i++) {
189+
/*
190+
* wait for free running conversion to finish.
191+
* Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion.
192+
*/
193+
loop_until_bit_is_set(ADCSRA, ADIF);
194+
195+
ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished
196+
// Add value
197+
tSumValue += ADCL | (ADCH << 8); // using myWord does not save space here
198+
// tSumValue += (ADCH << 8) | ADCL; // this does NOT work!
199+
}
200+
ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode)
201+
return tSumValue;
202+
}
203+
204+
/*
205+
* use ADC_PRESCALE32 which gives 26 us conversion time and good linearity
206+
* @return the maximum of aNumberOfSamples measurements.
207+
*/
208+
uint16_t readADCChannelWithReferenceMax(uint8_t aChannelNumber, uint8_t aReference, uint16_t aNumberOfSamples) {
209+
uint16_t tADCValue = 0;
210+
uint16_t tMaximum = 0;
211+
ADMUX = aChannelNumber | (aReference << SHIFT_VALUE_FOR_REFERENCE);
212+
213+
ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1.
214+
// ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag
215+
ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE32);
216+
217+
for (uint16_t i = 0; i < aNumberOfSamples; i++) {
218+
/*
219+
* wait for free running conversion to finish.
220+
* Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion.
221+
*/
222+
loop_until_bit_is_set(ADCSRA, ADIF);
223+
224+
ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished
225+
// check value
226+
tADCValue = ADCL | (ADCH << 8);
227+
if (tADCValue > tMaximum) {
228+
tMaximum = tADCValue;
229+
}
230+
}
231+
ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode)
232+
return tMaximum;
233+
}
234+
235+
/*
236+
* use ADC_PRESCALE32 which gives 26 us conversion time and good linearity
237+
*/
238+
uint16_t readADCChannelWithReferenceMaxMicros(uint8_t aChannelNumber, uint8_t aReference, uint16_t aMicrosecondsToAquire) {
239+
uint16_t tNumberOfSamples = aMicrosecondsToAquire / 26;
240+
return readADCChannelWithReferenceMax(aChannelNumber, aReference, tNumberOfSamples);
241+
}
242+
243+
/*
244+
* aMaxRetries = 255 -> try forever
245+
* @return (tMax + tMin) / 2
246+
*/
247+
uint16_t readUntil4ConsecutiveValuesAreEqual(uint8_t aChannelNumber, uint8_t aDelay, uint8_t aAllowedDifference,
248+
uint8_t aMaxRetries) {
249+
int tValues[4];
250+
int tMin;
251+
int tMax;
252+
253+
tValues[0] = readADCChannel(aChannelNumber);
254+
for (int i = 1; i < 4; ++i) {
255+
delay(aDelay); // Only 3 delays!
256+
tValues[i] = readADCChannel(aChannelNumber);
257+
}
258+
259+
do {
260+
// find min and max
261+
tMin = 1024;
262+
tMax = 0;
263+
for (int i = 0; i < 4; ++i) {
264+
if (tValues[i] < tMin) {
265+
tMin = tValues[i];
266+
}
267+
if (tValues[i] > tMax) {
268+
tMax = tValues[i];
269+
}
270+
}
271+
/*
272+
* check for terminating condition
273+
*/
274+
if ((tMax - tMin) <= aAllowedDifference) {
275+
break;
276+
} else {
277+
// Serial.print("Difference=");
278+
// Serial.println(tMax - tMin);
279+
280+
// move values
281+
for (int i = 0; i < 3; ++i) {
282+
tValues[i] = tValues[i + 1];
283+
}
284+
// and wait
285+
delay(aDelay);
286+
tValues[3] = readADCChannel(aChannelNumber);
287+
}
288+
if (aMaxRetries != 255) {
289+
aMaxRetries--;
290+
}
291+
} while (aMaxRetries > 0);
292+
293+
return (tMax + tMin) / 2;
294+
}
295+
296+
/*
297+
* !!! Function without handling of switched reference and channel.!!!
298+
* Use it ONLY if you only call getVCCVoltageSimple() or getVCCVoltageMillivoltSimple() in your program.
299+
* !!! Resolution is only 20 millivolt !!!
300+
*/
301+
float getVCCVoltageSimple(void) {
302+
// use AVCC with (optional) external capacitor at AREF pin as reference
303+
float tVCC = readADCChannelWithReferenceMultiSamples(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 4);
304+
return ((1023 * 1.1 * 4) / tVCC);
305+
}
306+
307+
/*
308+
* !!! Function without handling of switched reference and channel.!!!
309+
* Use it ONLY if you only call getVCCVoltageSimple() or getVCCVoltageMillivoltSimple() in your program.
310+
* !!! Resolution is only 20 millivolt !!!
311+
*/
312+
uint16_t getVCCVoltageMillivoltSimple(void) {
313+
// use AVCC with external capacitor at AREF pin as reference
314+
uint16_t tVCC = readADCChannelWithReferenceMultiSamples(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 4);
315+
return ((1023L * 1100 * 4) / tVCC);
316+
}
317+
318+
/*
319+
* !!! Function without handling of switched reference and channel.!!!
320+
* Use it ONLY if you only use INTERNAL reference (call getTemperatureSimple()) in your program.
321+
*/
322+
float getTemperatureSimple(void) {
323+
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
324+
return 0.0;
325+
#else
326+
// use internal 1.1 volt as reference
327+
float tTemp = (readADCChannelWithReferenceMultiSamples(ADC_TEMPERATURE_CHANNEL_MUX, INTERNAL, 4) - 317);
328+
return (tTemp * (4 / 1.22));
329+
#endif
330+
}
331+
332+
float getVCCVoltage(void) {
333+
return (getVCCVoltageMillivolt() / 1000.0);
334+
}
335+
336+
/*
337+
* Read value of 1.1 volt internal channel using VCC as reference.
338+
* Handles reference and channel switching by introducing the appropriate delays.
339+
* !!! Resolution is only 20 millivolt !!!
340+
*/
341+
uint16_t getVCCVoltageMillivolt(void) {
342+
uint8_t tOldADMUX = checkAndWaitForReferenceAndChannelToSwitch(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT);
343+
uint16_t tVCC = readADCChannelWithReferenceOversample(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 2);
344+
ADMUX = tOldADMUX;
345+
/*
346+
* Do not wait for reference to settle here, since it may not be necessary
347+
*/
348+
return ((1023L * 1100) / tVCC);
349+
}
350+
351+
void printVCCVoltageMillivolt(Print *aSerial) {
352+
aSerial->print(F("VCC="));
353+
aSerial->print(getVCCVoltageMillivolt());
354+
aSerial->println(" mV");
355+
}
356+
357+
/*
358+
* Handles reference and channel switching by introducing the appropriate delays.
359+
*/
360+
float getTemperature(void) {
361+
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
362+
return 0.0;
363+
#else
364+
// use internal 1.1 volt as reference
365+
uint8_t tOldADMUX = checkAndWaitForReferenceAndChannelToSwitch(ADC_TEMPERATURE_CHANNEL_MUX, INTERNAL);
366+
float tTemp = (readADCChannelWithReferenceOversample(ADC_TEMPERATURE_CHANNEL_MUX, INTERNAL, 2) - 317);
367+
ADMUX = tOldADMUX;
368+
return (tTemp / 1.22);
369+
#endif
370+
}
371+
#elif defined(ARDUINO_ARCH_APOLLO3)
372+
void ADCUtilsDummyToAvoidBFDAssertions(){
373+
;
374+
}
375+
#endif // defined(__AVR__)

0 commit comments

Comments
 (0)