Arduino Secret Voltmeter Explanation?

The math for this is trivially easy if you can do algebra.

First, the datasheet formula:
screenshot.136.jpg

ADC is the independent variable since that's the result generated by the hardware (between 0 and 1023 inclusive). So now there's two unknowns left, VIN and VREF.

The normal way is to take a fixed VREF with VIN as the dependent variable that is solved for. This arrangement creates a direct linear relationship between the dependent and independent variables, so has less error when converted to a real unit.

But there is no reason that you can't use the formula the other way. A fixed VIN (the 1.1V internal bandgap) can be used to calculate a variable VREF (AVCC). The internal reference is convenient to use since it requires no extra hardware, but has poor tolerance (only about 10%). If greater accuracy is needed, a separate voltage reference chip can be used on one of the analog inputs to do the same job. The only thing you would need to do is adjust the formula.

A second option for measuring the power voltage is to use a fixed reference for VREF, and use a voltage divider to scale down the voltage to within the ADC's input range. This is necessary if you are measuring a different power rail than what is supplying AVCC, but is inconvenient for measuring the chip's own power supply since it needs extra hardware.

screenshot.136.jpg

[quote author=Coding Badly date=1490642428 link=msg=3195802] Half correct. The declaration is irrelevant.

Exactly. "Just an assignment." More specifically "two independent assignments" which means the order is irrelevant. Which means the optimizer is free to perform them in any order or even move them to different points in the function. The only requirement is that the two values have to be available when the values are combined by the bitwise-or. [/quote]

I have NEVER seen a compiler that is free to swap to assignment statements at its own choosing.

If I type low = ADCL; high = ADCH;

Then I expect the low byte assignment to be executed first followed by the high byte. If it compiles high = ADCH; low = ADCL;

Then there is an issue with the compiler.

The issue with the 8051 program assigned the bytes in the correct order, but from the "wrong" memory address.

The code uint8_t low = ADCL; uint8_t high = ADCH;

is written differently and may compile differently based on how the compiler optimizes the memory space and fetches the values to be loaded.

So what do you think would happen if the code was written: low = ADCL; x=x+1; high = ADCH;

Will the compiler still rearrange all three lines as it sees fit?

We still haven't addresses rounded vs truncated.

adwsystems: I have NEVER seen a compiler that is free to swap to assignment statements at its own choosing.

Welcome to 1970. Compilers have included that sort of optimization at least since then.

Microsoft C included that sort of optimization 27 years ago.

That you "have never seen a compiler that is free to swap assignment statements" is hardly proof that such things do not exist. I have never seen a koala bear yet I have no doubt of their existence.

Then I expect the low byte assignment to be executed first followed by the high byte.

Your expectations are irrelevant.

Then there is an issue with the compiler.

Take it up with the C++ standards committee.

Will the compiler still rearrange all three lines as it sees fit?

Of course.

We still haven't addresses rounded vs truncated.

Addressed what? Round. Truncate. It's your application. Do whichever is appropriate. You have working code for each.

ADCL and ADCH are declared volatile. The compiler is not allowed to change the order of access to volatile variables (since that's the definition of a side effect), but it is free to move other, non-volatile statements around as it sees fit, even putting them between the two volatile accesses.

Jiggy-Ninja: The compiler is not allowed to change the order of access to volatile variables...

This is, quite simply, BULLSHIT. That exact problem has been discussed ad nauseum on AVR Freaks and other places. That is precisely the reason the avr-gcc compiler has a very specific read/write byte order for pointers to volatile 16 bit data.

I suggest you spend a bit more time reading and a bit less time posting. Something like these are a good place to start... http://blog.regehr.org/archives/28 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=20288

[quote author=Coding Badly link=msg=3197232 date=1490724467] This is, quite simply, BULLSHIT. That exact problem has been discussed ad nauseum on AVR Freaks and other places. That is precisely the reason the avr-gcc compiler has a very specific read/write byte order for pointers to volatile 16 bit data.

I suggest you spend a bit more time reading and a bit less time posting. Something like these are a good place to start... http://blog.regehr.org/archives/28 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=20288 [/quote] I skimmed through both of those pages, and I don't see how they are relevant to what I posted (which was a response to adwsystems's post). Those pages deal with how the compiler accesses data through a 16-bit-referencing pointer. They don't seem to have anything to do with the situation of manually accessing the H and L bytes like this:

 low = ADCL;
 high = ADCH;

Because ADCL and ADCH are accessed as volatile references_*_, the compiler is not permitted to optimize away access to them or reorder them relative to each other. Since L is written before H, the compiler must order than that way. Accessing the ADC register as an entire word is a completely separate issue.

* using these macros:

#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))

#define _SFR_MEM8(mem_addr) _MMIO_BYTE(mem_addr)

#define ADCL _SFR_MEM8(0x78)

The compiler is not supposed to re-order accesses to volatiles. CB’s first reference says specifically:

For every read from a volatile variable by the abstract machine, the actual machine must load from the memory address corresponding to that variable. Also, each read may return a different value. For every write to a volatile variable by the abstract machine, the actual machine must store to the corresponding address. Otherwise, the address should not be accessed (with some exceptions) and also accesses to volatiles should not be reordered (with some exceptions).

  • and -

The C standard is unambiguous that volatile side effects must not move past sequence points

There are some issues that DO cause problems:

  • Expecting an expression like “Val = (ADCH<<8) + ADCL;” to imply a “read” order of the registers.
  • Expecting a 16bit read to read 8bit address space in a particular order (the subject CB’s second link.)
  • volatile access CAN be re-ordered WRT no-volatile accesses (explained in first link.)

Volatile wouldn’t be USEFUL if volatile accesses could be re-ordered, and we wouldn’t be able to access IO registers with C at all (we could with functions written in assembler: external, inline, or intrinsic. That was the solution prior to volatile.)

As for re-ordering code in general, that happens all the time, and we expect it. The easiest example is probably

     for (i=0; i < 10; i++) {
        int x = constant;
        foo[i] = x;
     }

We hope and expect that the “x=constant” part will be moved outside of the loop, since its results don’t depend on i and it doesn’t need to be recalculated all i times. In the old days we might have had to move it ourselves, but since the compiler does it for us, we can write less cryptic code…

I was going to bud out and wonder off, but I forgot to turn off the notifications.

So out of curiosity, since you have all just ruled out:

value = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L;

and

low = ADCL;
high = ADCH;

and

uint8_t high = ADCH; // unlocks both
uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH

and

Val = (ADCH << 8) + ADCL;

as ways to read the ADC.

What is the compiler-fool-proof way to read the ADC bytes?

westfw: The compiler is not supposed to re-order accesses to volatiles.

...relative to each other.

I apologize for the mistake and specifically apologize to @Jiggy-Ninja for being an ass.

...since you have all just ruled out...

The first two are guaranteed to work based on the latest C++ standard / a specific modification to the arv-gcc compiler.

The third is simply wrong.

The last is flawed (it lacks a sequence point necessary to force the order).

Edit: Updated based on @Jiggy-Ninja's latest post. I did not notice @adwsystems had include a clearly incorrect snippet.

I don't think I would say

All but the last are guaranteed to work..

but rather the last is all but guaranteed not to work.

All the comments made so far show there is no [u]guarantee[/u] the first four methods will work either.

Most of all

westfw: Expecting a 16bit read to read 8bit address space in a particular order (the subject CB's second link.)

pretty much makes this option

value = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L;

very suspect. But it should be noted that this line is most suspect because of the ADC, where the bytes [u]must[/u] be read in a specific order.

adwsystems: But it should be noted that this line is most suspect because of the ADC, where the bytes [u]must[/u] be read in a specific order.

The avr-gcc folks have made that guarantee. The compiler always produces the correct code for pointers to 16 bit volatile data. Follow the second link for details.

value = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L;

Yes.

low = ADCL;
high = ADCH;

Yes.

uint8_t high = ADCH; // unlocks both
uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH

No.

Val = (ADCH << 8) + ADCL;

No. The + operator does not create a sequence point, so the result is undefined.