Increase voltage resolution with oversampling & decimation

Development & General Chat for the superb openxvario project.

Moderator: rainer

Post Reply
universam
Posts: 13
Joined: Tue May 05, 2015 2:50 pm
Country: Germany
Location: Germany

Increase voltage resolution with oversampling & decimation

Post by universam »

Hi,

I would like to suggest a technique to drastically improve the voltage reading accuracy beyond the hardware limit of 10bit. Especially this would be helpfull for higher volatages for batteries >3S where the high voltage divider needs better resolution to archieve usefull readings.

Atmel describes this really smart idea at http://www.atmel.com/Images/doc8003.pdf
Basically noise (which we have enough) is used to oversample and decimate to higher resolution. From my experience you can easily archieve 12-14bits without any hardware change.
This is NOT to be mixed with smoothing or averaging like it is used currently in the code!

Other projects where Lipo battery voltage is read uses this technique too, like charger: https://github.com/stawel/cheali-charge ... c_noise.md

There is a nice tested implementation available as a library here: https://github.com/ElectricRCAircraftGu ... /Version_2

From my tests I would suggest to use 12bits readings at least and can confirm its usefullness! The good point is that this replaces smoothing per se and would require not too much adjustment. The tricky part is here not to loose the gained resolution by simple downscaling later. Therefore the calculation of the divider must be done with the new higher resolution! Of course the oversampling takes time but we are averaging anyway.
Actually, when it is not desirable to use the above library it could be possible to implement that with only small adjustments to the code.

What do you think?

Thx Sam

kabturek
Posts: 4
Joined: Sun Jun 28, 2015 7:40 am
Country: Poland
Contact:

Re: Increase voltage resolution with oversampling & decimati

Post by kabturek »

I'm using only 3s but this looks great! Are you planning to work on it ?

Cheers!
universam
Posts: 13
Joined: Tue May 05, 2015 2:50 pm
Country: Germany
Location: Germany

Re: Increase voltage resolution with oversampling & decimati

Post by universam »

Yes, I hacked a version with 13bit already with only few changes needed to change in sources.
I found that the analog reading is done in various plugins as well, like current sensor, airspeed and so on.

Now I have to make good setup to test how good / better the reading is in reality...
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Increase voltage resolution with oversampling & decimati

Post by mstrens »

Thanks for your post.
I had a look at the Atmel note.
Basically it performs several ADC readings, summarises them and then divides by some factor. E.g if number of readings is 16 and dividor factor is 4, you can expect 2 extra bits resolution.

I think you can get the same result if you perform the calculation with float instead of integer.
The main advantage is that the number of reading does not need to be 4, 16, 64 , ...

This advantage is to take into account for oXs because, currently, oXs does not perform a fix number of readings but performs as many readings as possible within some delay (e.g. 500ms)
The number of readings performed within the delay depends a lot of the options being activated in oXS (e.g. the number of voltages to be measured).
I expect that the number of readings varies from about 200 to about 30 per 500 ms.
A delay of 500 ms seems good in order to get some averaging because there are anyway quite many (and big) voltage variations due to variations in current comsumption (servo, motors, ...)


I noticed that the current code of oXs is not optimal because the division is not performed with float and there is no rounding when converted back to int.
Current oXs formula is:
voltageData.mVolt[cntVolt] = (voltageData.sumVoltage[cntVolt] / cnt * voltageData.mVoltPerStep[cntVolt] ) + voltageData.offset[cntVolt];

I think it would be better do have:
voltageData.mVolt[cntVolt] = round( ( (voltageData.sumVoltage[cntVolt] * voltageData.mVoltPerStep[cntVolt] ) / cnt ) + voltageData.offset[cntVolt];
which is equivalent to
voltageData.mVolt[cntVolt] = round( ( ((float) voltageData.sumVoltage[cntVolt] * voltageData.mVoltPerStep[cntVolt] ) / (float) cnt ) + voltageData.offset[cntVolt];
This should provide the optimal solution I gess.
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Increase voltage resolution with oversampling & decimati

Post by mstrens »

universam wrote:Yes, I hacked a version with 13bit already with only few changes needed to change in sources.
I found that the analog reading is done in various plugins as well, like current sensor, airspeed and so on.

Now I have to make good setup to test how good / better the reading is in reality...
Sorry, I had not read you post when I sent my previous reply.

Here some additional comments:

I think that in most cases, 10 bits resolution is more than enough. It means that 1 bit error gives only 5 mvolt error when measuring a 5 volt voltage.
If you put an oscillosope on your battery and you activate some servos, I expect you will see much bigger variations.
If you activate a motor, it will be even much more making the measurement meaningless to detect low battery.

When voltage measurement is used to detect low battery, I expect that an accuracy of 100mv/cell is good enough.

I think that the 10 digits resolution can becomes an issue when measuring the voltage of the last (6th) cell of a 6S lipo.
imagine that:
- you use Vcc = 5 volt,
- a divider factor of 5 for the 5th cell (so 1 step ADC = 25 mv)
- a divider factor of 6 for the 6th cell (so 1 step ADC = 30 mv)
- you got an 1/2 ADC step error on each voltage measurement and in the worst case they are in opposite sign for the 2 cells.
Then the result could be an error of 55mvolt what reported to a nominal 3.6 volt/cell.
When you set up an alarm based on the lowest voltage per cell (cell min), you will probably expect precision of about 100 mvolt. So in this case, the error of 55mvolt becomes significant.

For sure you can reduce the resolution error (error due to the number of ADC bits) by oversampling but in practice, I am not sure that it make really sense because there are so many other sources or errors:
- ADC offset
- ADC gain
- ADC non linearity
- external noise
- time to get stable voltage on ADC (when multiplexing)
- stability of VREF (or Vcc)
- impact of temperature on resistors used in voltage divider,
...

In my own tests, I noticed that I got quite big errors if
- I did not calibrate OXS
- the voltage on ADC was not reset to ground between measurement of 2 different Arduino pins (oXs code has been modified in order to perform this, and even more the first reading is discarded)

Note: as far I remember analog read is only used for voltage and current measurements (not for airspeed, ...)

universam
Posts: 13
Joined: Tue May 05, 2015 2:50 pm
Country: Germany
Location: Germany

Re: Increase voltage resolution with oversampling & decimati

Post by universam »

Thank you very much mstrens for taking you time and considerations you made!
Good points that you mentioned - I mainly fully agree with your arguments you wrote, most conclusion I've also made.

Some points I want to comment though before I want to explain a win-win solution idea that covers your concernces but give us an improvement with only minimal change in code IMHO. :)
I think you can get the same result if you perform the calculation with float instead of integer.
The main advantage is that the number of reading does not need to be 4, 16, 64 , ...
Good point - I think you are absolutelly right! Though there are some things to consider: AVR float implementation is not precise ( ex. 6.0 / 3.0 = 1.99 !), it is expensive in time and has only 7 digits precision. Probably therefore it is not recommended.
But still, this is not necessarily needed at all...
mstrens wrote: I think that the 10 digits resolution can becomes an issue when measuring the voltage of the last (6th) cell of a 6S lipo.
Thats the point!
mstrens wrote: When you set up an alarm based on the lowest voltage per cell (cell min), you will probably expect precision of about 100 mvolt. So in this case, the error of 55mvolt becomes significant.
Picture that your 6S will step between 4.19V and 4.25V. A situation I care about. Imagine we could raise the resolution to 12bit, the precision would be 14mV so steping between 4,19V and 4.20V. Significant difference!
This advantage is to take into account for oXs because, currently, oXs does not perform a fix number of readings but performs as many readings as possible within some delay (e.g. 500ms)
Smart implementation, we dont want to replace that dynamic averaging by O&D although it seams to be a replace. Actually, we dont need to and we dont want because averaging and O&D have different benefits! Thats also mentioned in the ATMEL doc.

Averaging covers the fluctuation over time, the x-axis so to say. But oversampling represents the bandwith of the imprecise measurement, the noise. And noise should be cought in the least time slice possible, the Y-axes so to say.
Therefore we should combine averaging and oversampling.

This is a win-win situation because it is so easy to implement while we keep the current logic. We only need to change the function OXS_VOLTAGE::readVoltage to return 12bit values now:

Code: Select all

int OXS_VOLTAGE::readVoltage( int value ) { // value is the index in an aray giving the pin to read
.........
 delayMicroseconds(100); // Wait for ADMux to settle 
  //return analogRead(voltageData.mVoltPin[value]); // use the second measurement
    uint16_t sum = 0;
    for (byte i=0; i<16; i++) sum += analogRead( voltageData.mVoltPin[value]); 
	return (sum + 2) >> 2; // correct rounding
}
Only the last 3 lines are changed.
Of course the mVoltPerStep must be multiplied by 4 too:

Code: Select all

voltageData.mVoltPerStep[cntInit] = tempRef / (1023.0 * 4) * ( tempResistorToGround[cntInit] + tempResistorToVoltage[cntInit] ) / tempResistorToGround[cntInit]  * tempScaleVoltage[cntInit];
} else {
voltageData.mVoltPerStep[cntInit] = tempRef / (1023.0 * 4) ; 
Thats it! :P

Does it make sense to read the 16 samples at once? Indeed, because as we said we dont want to cover the fluctuation over time but the noise which has to be captured as fast as possible. Most probably the noise derives mostly from Motor ESC at 8/16kHz which would fit the speed of reading pretty good.
The fun part is here, that the noise which we try to avoid as much as possible gives us the bandwith for oversampling and the addidtional resolution. On the other hand it means that in lab you wont see the improvement that you will gain in the plane with a running noisy motor :geek:
The number of readings performed within the delay depends a lot of the options being activated in oXS (e.g. the number of voltages to be measured).
I expect that the number of readings varies from about 200 to about 30 per 500 ms.
Lets measure: in my setup the stock OXS performs in an intervall of 500ms the count is ~90, so there are 90 readings averaged.
With the above changes the count is ~27, so 27x 12bit samples are averaged. That means there are 27x16 = 432 readings performed!
Without a question, the result is way better!

Your comments please! :-)
Thanks
universam
Posts: 13
Joined: Tue May 05, 2015 2:50 pm
Country: Germany
Location: Germany

Re: Increase voltage resolution with oversampling & decimati

Post by universam »

Some real life measurements:

5S Battery results:

Code: Select all

At 287789 Cnt = 27 mVolt 0 = 3890
At 287790 Cnt = 27 mVolt 1 = 7774
At 287791 Cnt = 27 mVolt 2 = 11701
At 287792 Cnt = 27 mVolt 3 = 15541
At 287795 Cnt = 27 mVolt 4 = 18332 <-- steping of only 5mV(!!!)
Almost no fluctuation in results, super steady. For cell #5 the steping is 5mV only which results to an actual resolution of 3666 digits which is pretty close to the theoretical 12bit 4096 digits.

Oversampling works great with OXS! :D
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Increase voltage resolution with oversampling & decimati

Post by mstrens »

@universam,
Thanks for comments and for the tests.

Here some reactions:
Reading 16 voltages in the function readVoltage is not optimum for the vario functionality because it could delay reading the baro sensor. With 16 consecutive conversions, OXS_VOLTAGE::readSensor() would probably take about 1.9 - 2.1 msec.
Initially oXs was made mainly as vario in order to get vertical speed. Tests showed that baro sensor is very noisy. This requires some good filtering algorithms. The results from the used algorithm are better if the baro sensor can be at high frequency and if the delay between each reading is fixed.
The frequency of reading the baro sensor (MS5611) depends on the delay for a conversion (delay beween sending a conversion request and the availibility of the measurement).
The delay is about 9 msec.
oXs reads all sensors and performs all calculations in the main loop. In order to give priority to the baro sensor, code has been build in such a way that:
- the loop is run as fast as possible (no blocking functions, tasks spreaded over several loops,...)
- some main functions in the loop are skipped if a baro conversion would become available within the next 2 msec.
This delay of 2 msec has been set expecting that the function would not require more than about 1.5 sec.
So, with this logic, in order to keep the objective of gettin as many baro sensor as possible, it would be necessary to increase the value of 2 msec for the check on readVoltage().

In fact, the best solution would be to use a timer interrupt in order to read the baro. It would free up some CPU time for other tasks. Still this would require quite many changes in the code.

In fact, I really think that:
- my proprosal using this code (see previous post)
voltageData.mVolt[cntVolt] = round( ( ((float) voltageData.sumVoltage[cntVolt] * voltageData.mVoltPerStep[cntVolt] ) / (float) cnt ) + voltageData.offset[cntVolt];
would provide a very similar result. If the number of ADC conversion is high enough, the resolution would even be better.
- there is no issue using float if you take care to round the result when it is converted to integer. So ex. 6.0 / 3.0 will give 2. If float has a precison of about 9 digits, it is more than enough for our purpose because 10 bits = 3 digits and 12 bits is less than 5 digits.

You are right that making several consecutive conversions allows finally to get more conversions in a defined enlapsed time (e.g. 500 msec) and so you get a better averaging.
Probably that the optimum solution would be to perform about 4 consecutive conversions and to calculate in float with round().
So the readvoltage() would be replace
return analogRead(voltageData.mVoltPin[value]); // use the second measurement
by
return analogRead(voltageData.mVoltPin[value]) + analogRead(voltageData.mVoltPin[value]) + analogRead(voltageData.mVoltPin[value]) + analogRead(voltageData.mVoltPin[value]); 4 measurements

And then the code I proposed would become
voltageData.mVolt[cntVolt] = round( ( ((float) voltageData.sumVoltage[cntVolt] * voltageData.mVoltPerStep[cntVolt] ) / (float) (cnt * 4) ) + voltageData.offset[cntVolt];

If you have time, you could test those changes.

Implementing a timer interrupt logic would even increase the performance because there would be more conversions within the 500 msec.

Still one important remark:
This discussion has only an impact on the resolution (resulting from ADC steping). It concerns the minimum number of millivolt that can separate 2 values returned by the algorithm (in fact, with the float calculation, it should be 1 mvolt if the number of conversion is enough).
This does not mean at all that the accuracy of the measurement is the same. In practice I expect that accuracy is much lower.
Best would be to compare the oXs measurements with those given by a good digital voltmeter and this for different voltages, temperatures, ...
I expect that you will find differences of about 0.5%.
This is probably the limiting factor when you want to measure the minimum cell voltage.
universam
Posts: 13
Joined: Tue May 05, 2015 2:50 pm
Country: Germany
Location: Germany

Re: Increase voltage resolution with oversampling & decimati

Post by universam »

mstrens wrote:And then the code I proposed would become
voltageData.mVolt[cntVolt] = round( ( ((float) voltageData.sumVoltage[cntVolt] * voltageData.mVoltPerStep[cntVolt] ) / (float) (cnt * 4) ) + voltageData.offset[cntVolt];

If you have time, you could test those changes.
Okay I've tested your implementation.
The float calculation is a bit slower now for some us which is not a real impact. The readings go up from about 600us to 1010us.
My implemenatation took over 2200us to complete. Though the fluctuation is higher with your code +/- 3 digits due to the fact that now it runs 63 rounds/500ms which = 63x4= 252 converstions compared to my 432. Still the resolution is higher with float calculation which is nice.

I think mostly regarding the time slice issue I think your solution is preferable.

mstrens wrote:This does not mean at all that the accuracy of the measurement is the same. In practice I expect that accuracy is much lower.
Of course, but that's a different topic, not really related to resolution.


BTW, I think there is some logic error in the code:
When I define a voltage reference this is not set at ADMUX. In the OXS_VOLTAGE::readVoltage every time it is set to either internal or default but not to external:

Code: Select all

 //******** First discharge the capacitor of ADCMux to ground in order to avoid that measurement from another pin has an impact on this measurement  
#ifdef USE_INTERNAL_REFERENCE
  ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // binary = 11 00 1111 (11 = use VRef as max, 1111 = measure ground level)
#else
  ADMUX =  _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // binary = 01 00 1111 (01 = use Vcc as max, 1111 = measure ground level)
#endif
There should be at least this line in between:

Code: Select all

#elif defined(REFERENCE_VOLTAGE) && REFERENCE_VOLTAGE < 5000
analogReference(EXTERNAL);
While mentioning this, I wonder why this is done for every conversion? This was already set in OXS_VOLTAGE::setupVoltage so why waste time here?
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Increase voltage resolution with oversampling & decimati

Post by mstrens »

universam wrote: Okay I've tested your implementation.
The float calculation is a bit slower now for some us which is not a real impact. The readings go up from about 600us to 1010us.
My implemenatation took over 2200us to complete. Though the fluctuation is higher with your code +/- 3 digits due to the fact that now it runs 63 rounds/500ms which = 63x4= 252 converstions compared to my 432. Still the resolution is higher with float calculation which is nice.
It is strange that the readings go up from 600 to 1010. In Arduino doc, it is said that analogread() take 100 us. So I had expected an increase of 300 us but not 400.
Anyway, this is not critical.
It would also be easy to save some time implementing my own ADC reading instead of using the standard Arduino Analogread() .

You say that fluctuation is higher +/- 3 digits. I presume that you mean 3 "units" . E.g.: if voltage to measure is stable and around 10000 mv, you get values that varies now by e.g. 8 mvolt (so from 9992 to 10008) instead of 5mvolt with your code.
If so, it seems me ok.

universam wrote: BTW, I think there is some logic error in the code:
When I define a voltage reference this is not set at ADMUX. In the OXS_VOLTAGE::readVoltage every time it is set to either internal or default but not to external:

Code: Select all

 //******** First discharge the capacitor of ADCMux to ground in order to avoid that measurement from another pin has an impact on this measurement  
#ifdef USE_INTERNAL_REFERENCE
  ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // binary = 11 00 1111 (11 = use VRef as max, 1111 = measure ground level)
#else
  ADMUX =  _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // binary = 01 00 1111 (01 = use Vcc as max, 1111 = measure ground level)
#endif
There should be at least this line in between:

Code: Select all

#elif defined(REFERENCE_VOLTAGE) && REFERENCE_VOLTAGE < 5000
analogReference(EXTERNAL);
While mentioning this, I wonder why this is done for every conversion? This was already set in OXS_VOLTAGE::setupVoltage so why waste time here?
I do not think there is an error.
The code I put (about ADMUX) is to force a discharge of the internal ADC capacitor prior a conversion.
If I don't do so, I noticed that the value reported by ADC reading analog pin X varies based on the volotage applied on the pin that was read previously.
To avoid this, I had to wait quite long that voltage was stabilised. This is the result of an internal ADC capacitor and form the resistor dividers used on each analog pins.
Based on my tests, I found that if was better to force a discharge of the capacitor asking ADC to read the ground voltage (connecting internally the ADX to ground).
That is the reason why I set the last 3 bits of ADMUX to "111" and I wait 200 usec and I ask a conversion of the voltage on ground level (the result is not used but I am sure that capacitor is discharged).
Then I read the voltage on a pin but I do not use the result.
I wait 100 usec to get a more stable voltage and I ask a second read on the same pin. This result was the only one that I used in the original code.

Please not that each time I call the arduino function analogread(), this function configures ADMUX based on an arduino variable "analog_reference". This variable is set up ONLY by the function analogReference() which is call once in my function setupVoltage and setupCurrent so prior any conversion; default this variable is on EXTERNAL.
universam
Posts: 13
Joined: Tue May 05, 2015 2:50 pm
Country: Germany
Location: Germany

Re: Increase voltage resolution with oversampling & decimati

Post by universam »

mstrens wrote:You say that fluctuation is higher +/- 3 digits. I presume that you mean 3 "units" .
No I mean digits, f.e. 17997mV -> 18003mV
mstrens wrote:This variable is set up ONLY by the function analogReference() which is call once in my function setupVoltage and setupCurrent so prior any conversion; default this variable is on EXTERNAL.
Thats not the case! There is only a definition of analogReference if internal but not if external. And default by Arduino the AREF is set to DEFAULT and not to EXTERNAL. See the note in the doc:
https://www.arduino.cc/en/Reference/AnalogReference
Warning
If you're using an external reference on the AREF pin, you must set the analog reference to EXTERNAL before calling analogRead(). Otherwise, you will short together the active reference voltage (internally generated) and the AREF pin, possibly damaging the microcontroller on your Arduino board.
wiring_analog.c

Code: Select all

uint8_t analog_reference = DEFAULT;
Well I came across this issue not by chance but because my Nano pulled up the AREF to 5V, and as soon as I define analogReference(EXTERNAL) in setupVoltage it works! So I am pretty sure this is a bug here.

mstrens wrote:That is the reason why I set the last 3 bits of ADMUX to "111" and I wait 200 usec and I ask a conversion of the voltage on ground level (the result is not used but I am sure that capacitor is discharged).
Again I made tests and must conclude that this has no benefit. IMHO the jump switching of AREF brings more noise than stability. Removing these lines give no real measareble difference in my current setup.
BUT the averaging goes up from 63 to 89 per interval which is way better.

In contrast the
mstrens wrote:Then I read the voltage on a pin but I do not use the result.
dummy reading (now from real input ) has a good impact on crosstalk as I could test now. Atmel also suggest to take the second conversion, but from same voltage of course.

Maybe a smarter way to settle the capacitor would be to set ADMUX to the next channel before leaving the reading, so to preload the ADC channel while you perform the other loop stuff and you will get a nicely settled ADC channel.

Something like this:

Code: Select all

//          analogRead( voltageData.mVoltPin[value]); // read the value from the sensor 
  // discard the first measurement
//         delayMicroseconds(100); // Wait for ADMux to settle 
// return analogRead(voltageData.mVoltPin[value]) + analogRead(voltageData.mVoltPin[value]) + analogRead(voltageData.mVoltPin[value]) + analogRead(voltageData.mVoltPin[value]); 4 measurements

uint16_t sum = analogRead(voltageData.mVoltPin[value]) + analogRead(voltageData.mVoltPin[value]) + analogRead(voltageData.mVoltPin[value]) + analogRead(voltageData.mVoltPin[value]); // save the readings

analogRead(value <= 5 ? voltageData.mVoltPin[value + 1] : voltageData.mVoltPin[0] ); // pre-charge the ADC capacitor until next increment

return sum;
}
Now your cap is preloaded, you can save the delays, and the readings go up from 63 to 98! Thats 55% more!
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Increase voltage resolution with oversampling & decimati

Post by mstrens »

No I mean digits, f.e. 17997mV -> 18003mV
So, it very stable (fluctuation is probably much less than accuracy error).
Well I came across this issue not by chance but because my Nano pulled up the AREF to 5V, and as soon as I define analogReference(EXTERNAL) in setupVoltage it works! So I am pretty sure this is a bug here.
I made a mistake in my previous post when I refered to EXTERNAL. In fact I meaned DEFAULT. The code has been written with DEFAULT in mind.
It means that when "USE_INTERNAL_REFERENCE" is not activated, oXs will use DEFAULT and so ADC will use Vcc as voltage reference.
For a AVR 328p (used in Arduino pro mini), DEFAUT is defined as "1" and "INTERNAL as "3".
After setupVoltage() and setupCurrent(), the arduino variable "analog_reference" stay on 1 (if "USE_INTERNAL_REFERENCE" is not activated) or is set up to 3 (if "USE_INTERNAL_REFERENCE" is activated).
Each time function analogRead() is called, AMUX is filled by this code (extracted from file wiring_analog.c :
// set the analog reference (high two bits of ADMUX) and select the
// channel (low 4 bits). this also sets ADLAR (left-adjust result)
// to 0 (the default).
#if defined(ADMUX)
ADMUX = (analog_reference << 6) | (pin & 0x07);
#endif

You can see that the value set in the main 2 bits of ADMUX are exactly the same as the value (1 or 3) that I put in oXs in code :
#ifdef USE_INTERNAL_REFERENCE
ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // binary = 11 00 1111 (11 = use VRef as max, 1111 = measure ground level)
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // binary = 01 00 1111 (01 = use Vcc as max, 1111 = measure ground level)
#endif

So, AVR 328 always keeps using the same Vref (Vcc or internal Vref) for ADC conversion.
I still do not see an error in the code.
I did not check the AVR datasheet in order to see if voltage on VREF pin must be equal to Vcc when DEFAULT is used and to 1.1 Volt when USE_INTERNAL_REFERENCE is used. So I do not know if it makes sense to measure this voltage.
Maybe a smarter way to settle the capacitor would be to set ADMUX to the next channel before leaving the reading, so to preload the ADC channel while you perform the other loop stuff and you will get a nicely settled ADC channel.
This is a good proposal because it would be possible to achieve more conversion in the same enlapsed time (500ms).
Still, it is not so easy to code because:
- at the end of reading pin X, finding the next pin to read has to be calculated taking into account that the next entry in the voltage table has sometime to be skipped
- if current measurement is required, at the end of the last voltage, oXs would have to switch to the pin for current conversion (only if a current sensor is defined) and after this to switch back to the first voltage pin. It is even more complex because current and voltage measurements are performed in another class.
- we should add a delay before reading the current (othewise the voltage on ADC could not be "stabilised").

I am afraid that this change is to big compared to the expected benefit.
Again, a fluctuation of +/-3 mv on 18000 mv seems very good (much less than other errors).


I build the current solution that forces a reading of ground level between each change of arduino analog pin after a test where:
I had some voltage dividers (with quite high resistors : e.g. 10k + 60 K).
Imagine that I read pin 1 and then pin 2 with Vcc (5volt) as voltage reference.
I founded that if apply e.g. 3 volt on pin 2 , I did not get the same measurement when I apply 0 volt or 5 volt on pin 1 (or I had to apply quite long delay when switching)
The measurement on pin 2 becomed much better when I added a "dummy" measurement of ground level.
This explain the current logic.
universam
Posts: 13
Joined: Tue May 05, 2015 2:50 pm
Country: Germany
Location: Germany

Re: Increase voltage resolution with oversampling & decimati

Post by universam »

mstrens wrote:The code has been written with DEFAULT in mind.
It means that when "USE_INTERNAL_REFERENCE" is not activated, oXs will use DEFAULT and so ADC will use Vcc as voltage reference.
Okay I try to explain it better. There are 3 possibilities to measure voltage,
- with AREF at 5V (VCC),
- with an independent refernce Voltage source (EXTERNAL) = something between 0 - 5V
- with the internal 1.1V built in V reference.

Nice, one would assume, and connect an external VREF to the AREF pin which I did, because in OXS you can nicely set the voltage of reference. But instead of OXS switching now to EXTERNAL it is still connected to VCC and as you read the warning i posted before you barbeque your chip. Not nice...
Why on earth would one define the voltage of reference in the config file but not use an externel reference? Makes no sense to assume he has just powered the atmel with less voltage, which is impossible under 3V. So there is a logic error here...
mstrens wrote:So, AVR 328 always keeps using the same Vref (Vcc or internal Vref) for ADC conversion.
I still do not see an error in the code.
Yes, but not EXTERNAL!
mstrens wrote:I did not check the AVR datasheet in order to see if voltage on VREF pin must be equal to Vcc when DEFAULT is used and to 1.1 Volt when USE_INTERNAL_REFERENCE is used. So I do not know if it makes sense to measure this voltage.
If DEFAULT is selected the pins are internaly _shortcuted_, so it is vital not to mess here with an external Ref for not to overload the chip!
See my last posts comment.
The measurement on pin 2 becomed much better when I added a "dummy" measurement of ground level.
Did you really test to do a 'normal' dummy reading ( not ADMUX to ground ) and take the second conversion, like proposed by Atmel? Because when I perform this I dont see the problem you described. You give the cap 2x time to load, so I am pretty sure that the grounding and so on is not needed or I just cant measure a benefit. Still it is so much faster without...
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Increase voltage resolution with oversampling & decimati

Post by mstrens »

Ok, I did not understood that you made test using EXTERNAL and connecting Arduino pin named AREF to some voltage. This pin is available (on the pin header) on the Arduino nano but not the Arduino pro mini.

oXs was mainly made for Arduino pro mini and so it was not expected to use the EXTERNAL option (it is even not documented in the config).
If this option should be added to oXs (which could make sense to get more accurate voltage measurement but that requires adding some component in order to get a good voltage reference), the easier would be to change the code in readVoltage() in order to read AMUX and change only the 4 last bits (using an AND and an OR). So the first 2 bits would always remain unchanged.
Did you really test to do a 'normal' dummy reading ( not ADMUX to ground ) and take the second conversion, like proposed by Atmel? Because when I perform this I dont see the problem you described. You give the cap 2x time to load, so I am pretty sure that the grounding and so on is not needed or I just cant measure a benefit. Still it is so much faster without...
Yes I did : I tested using a dummy reading when I switch from pin 1 to pin 2 and the results where not good. I had to put a quite long delay between the 2 readings to avoid that voltage on pin 1 has an impact on pin 2 reading.
Reading ground level, adding some delay, making a dummy reading, adding a delay and finally making the reading was the fastest way I found to get good results on this test.
Still I am not sure that the measurement is accurate (in absolute value) because it could be that the internal capacitor is still not fully charged when final conversion occurs but at least voltage on pin X does not have an impact on pin Y. Even if internal capacitor, I think you can get quite accurate measurements if you take the time to calibrate the ADC for each pin.

A solution coud be to reduce the values of the resistors in voltage dividers (but it increase current consumption).
Another solution that would certainly help would be to add external capacitors between ground and each analog pin. This would reduce the impedance viewed by the internal capacitor.
Still, this requires additional components and is not very user friendly.
universam
Posts: 13
Joined: Tue May 05, 2015 2:50 pm
Country: Germany
Location: Germany

Re: Increase voltage resolution with oversampling & decimati

Post by universam »

mstrens wrote:If this option should be added to oXs (which could make sense to get more accurate voltage measurement but that requires adding some component in order to get a good voltage reference), the easier would be to change the code in readVoltage() in order to read AMUX and change only the 4 last bits (using an AND and an OR). So the first 2 bits would always remain unchanged.
Yes, bitmasking would be easiest.

What do you think about replacing the define USE_INTERNAL_REFERENCE with ANALOG_REFERENCE in config file.
So User would pick one of DEFAULT, INTERNAL and EXTERNAL which is enough for determination in code what to do. I think that the Arduino implementation is already the best way to go.
mstrens wrote:A solution coud be to reduce the values of the resistors in voltage dividers (but it increase current consumption).
I wonder why I cant reproduce this behaviour in my setup, but okay there may be situations where this has an impact. Well because my cell balancing voltage levels are quite close so not much capacity loading. And my voltage divider impedance is lower too.
mstrens wrote:A solution coud be to reduce the values of the resistors in voltage dividers (but it increase current consumption).
Another solution that would certainly help would be to add external capacitors between ground and each analog pin. This would reduce the impedance viewed by the internal capacitor.
These are good suggestions, I would suggest to put them to the wiki for the ones that like to archieve a more accurate readings.
universam
Posts: 13
Joined: Tue May 05, 2015 2:50 pm
Country: Germany
Location: Germany

Re: Increase voltage resolution with oversampling & decimati

Post by universam »

Any news about implementation?

BTW, I found one issue. If you comment RESISTOR_TO_x but specify SCALE_VOLTAGE which is kind of old style, then SCALE_VOLTAGE is ignored in mVoltPerStep calculation.

So IMHO the line should be corrected to

Code: Select all

    } else {
      voltageData.mVoltPerStep[cntInit] = tempRef  / 1023.0 * tempScaleVoltage[cntInit];  
    }
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Increase voltage resolution with oversampling & decimati

Post by mstrens »

universam wrote:Any news about implementation?

BTW, I found one issue. If you comment RESISTOR_TO_x but specify SCALE_VOLTAGE which is kind of old style, then SCALE_VOLTAGE is ignored in mVoltPerStep calculation.

So IMHO the line should be corrected to

Code: Select all

    } else {
      voltageData.mVoltPerStep[cntInit] = tempRef  / 1023.0 * tempScaleVoltage[cntInit];  
    }
I am currently working on another branch (openXsensor_GPS) in order to support a UBLOX GPS.
So it is easiet to make all the changes in this branch.
I will merge it into master when code is fully tested.

I had already made the change to increase the resolution.
I just added a change to support External reference.
I fixed the bug that you just said.
The version is on github (in openXsensor_GPS).
I did not really tested those last changes.

If you find some bug, just let me now.
User avatar
Tempo
Posts: 83
Joined: Tue Feb 04, 2014 4:04 pm
Country: -

Re: Increase voltage resolution with oversampling & decimati

Post by Tempo »

mstrens wrote: ...
I will merge it into master when code is fully tested.

I had already made the change to increase the resolution.
I just added a change to support External reference.
I fixed the bug that you just said.
The version is on github (in openXsensor_GPS).
...
Thank you. This improvement is useful. :)

(For measurements I use sensors combined with 16-Bit-ADS1115 and log data on board (AT45DB) without sending on SmartPort.
However sending some measurments with SmartPort is very helpful to monitor experiments in flight while logging.)

Post Reply

Return to “OpenXVario - an open source vario supported by the open source firmwares!!”