Go Down

Topic: Measuring VCC with an Attiny 85 (Read 20940 times) previous topic - next topic

Tom Carpenter

Its 1024 as there are 1024 levels.

Essentially it comes down to the way these ADCs work, they measure fence posts not fences.
There are 1024 levels, so for a 3V reference, each level DeltaV=3/1024=2.9296875 [mV]
So this means:









LevelVoltage [mV]
00
12.93
25.86
......
10222994.14
10232997.07
(1024)(3000)


Notice how regardless of starting at 0 that it is actually the number '1024' which represents the full reference voltage. Now clearly you can never get that reading, but that doesn't change the fact the calculation has to use 1024.
~Tom~

smithy


...so it should be exactly the same.


Unless the compiler reorders the two lines of code.  Which it is allowed to do. Splitting the read is a bad idea. 


You are right, the compiler is allowed to reorder but as fas as i know not the logical meaning behind it.
If you set one register and then another this should always result into the same order just the structure surrounding theese 2  commands may change. An immediate execution is not guaranteed, but it will never change register 2 first because you manifested to set register 1 first in your code. If no order is guaranteed, coding would be nonsense.

Correct me if i´m wrong but theese are my thoughts about this issue.

Coding Badly

#17
Mar 09, 2014, 10:31 pm Last Edit: Mar 09, 2014, 11:57 pm by Coding Badly Reason: 1
You are right, the compiler is allowed to reorder but as fas as i know not the logical meaning behind it.


Code: [Select]
a = 1 + 2;
Code: [Select]
a = 2 + 1;

The compiler sees those two lines of code as equivalent.  Why?  Because addition is commutative making the semantics identical.  As long as the semantics are not changed, the compiler can do what ever it wants.

Code: [Select]

 uint8_t low  = ADCL;
 uint8_t high = ADCH;
 long result = (high<<8) | low;

Code: [Select]

 uint8_t high = ADCH;
 uint8_t low  = ADCL;
 long result = (high<<8) | low;

Code: [Select]

 long result = (ADCH<<8) | ADCL;

Code: [Select]

 long result = ADCL | (ADCH<<8);


The semantics are all the same.  ADCH is read, as requested.  ADCL is read, as requested.  The value from ADCH is shifted, as requested.  The two values are combined using a logical-or, as requested.  Why are the semantics all the same?  Because logical-or is commutative and the compiler has no sense that ADCH and ADCL have a relationship.

smithy

#18
Mar 10, 2014, 12:05 am Last Edit: Mar 10, 2014, 12:10 am by smithy Reason: 1
Well after some research, i found out arduino uses bitwise & for setting a variable which is commutative.

But then i don´t understand why its explained that way in the atmel datasheets and what is ADC then ?

If it´s a macro what code does it replace ?

For the latter part :
Quote
long result = (ADCH<<8) | ADCL;

the order doesn´t matter since you are using OR and shift one value 8 to the left.
Example :
Quote

               01 1 1 001 1
11111100 00000000
               |
11111100 01 1 1 001 1

There will always be zeros in the first 8 bit so it has no effect with OR.



fungus

#19
Mar 10, 2014, 02:23 am Last Edit: Mar 10, 2014, 02:25 am by fungus Reason: 1

But then i don´t understand why its explained that way in the atmel datasheets and what is ADC then ?


Code: [Select]

#define _MMIO_WORD(mem_addr) (*(volatile uint16_t *)(mem_addr))
#define _SFR_MEM16(mem_addr) _MMIO_WORD(mem_addr)

...

#define ADC     _SFR_MEM16(0x04)

Advanced Arduino

Tom Carpenter

http://www.atmel.com/Images/doc8006.pdf
Read Page 106.

Notice how in the C code examples, they never directly use the ***H and ***L registers.

There is also another problem which the current analogRead() code fails to consider which is interrupts. If an interrupt occurs after you have read ADCL but before you have read ADCH, then you can loose the conversion result. The same is true for using the ADC register name:
Code: [Select]

uint16_t read;
uint8_t oldSREG = SREG;
cli();
read = ADC;
SREG = oldSREG;
~Tom~

smithy

#21
Mar 10, 2014, 10:07 pm Last Edit: Mar 10, 2014, 10:27 pm by smithy Reason: 1
As far as i understand ADC defines a 16bit memory address which is read out directly in the processor in 1 step ?
But then this will only work on chips who have ADCL directly after ADCH registers ?

Quote
uint16_t read;
uint8_t oldSREG = SREG;
cli();
read = ADC;
SREG = oldSREG;

Why are you saving the status register ?
I guess theres an sei(); at the end of this and ADC is setting ADSC to 1.

Thanks for your explanations so far !

LastSamurai

Waho, so much feedback ;)

So this should work?!

Code: [Select]
long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  ADMUX = _BV(MUX3) | _BV(MUX2);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring

So this sets the reference Voltage. I dont really understand this but if it works...

Code: [Select]

  uint8_t result = ADC; //In one read?
  result = 1126400L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1024*1000 // 1024 not 1023?
  return result; // Vcc in millivolts
}

And this reads the Vcc?!

What do I need the SREG for? And do I have to use cli() / sei() here (Thats enables/disables intterupts, right?!)?

I am wondering if noone has done this successfully before?! Seems like a "standard" thing to do with mobile battery powered projects.

smithy

#23
Mar 10, 2014, 11:05 pm Last Edit: Mar 11, 2014, 12:21 am by smithy Reason: 1
Have a look at your Datasheet : http://www.atmel.com/Images/Atmel-2586-AVR-8-bit-Microcontroller-ATtiny25-ATtiny45-ATtiny85_Datasheet.pdf

When i understood this right, it should look like :
Quote
long readVcc() {   
  ADMUX = _BV(MUX3) | _BV(MUX2);  // selecting bandgap reference of 1.1V  (on page 135)
  delay(2); // Wait for bandgap reference to stabilize
  uint8_t oldSREG = SREG;   // saving status register
  cli();   //deactivate Interrupts
  ADCSRA |= _BV(ADSC); // Start  ADC conversion, this is where the 1.1V reference is read against vcc
  while (bit_is_set(ADCSRA,ADSC)); // wait until complete
  uint16_t result = ADC; //Needs to be 16 bit long ! This only reads the two registers in one command
  SREG = oldSREG;  // setting status register to "old" values
  sei(); // Activate Interrupts
  result = 1126400L / result; // Calculate Vcc (in mV); 1126400L = 1.1*1024*1000 // 1024 should be right
  return result; // Vcc in millivolts
}

Tom Carpenter

#24
Mar 11, 2014, 12:23 am Last Edit: Mar 11, 2014, 12:29 am by Tom Carpenter Reason: 1
no, you don't need the sei() instruction, that is the whole purpose of backing up the SREG (the register which the cli() and sei() instructions act upon).

Imagine interrupts were disabled before calling that function, it will come in, disable them again, and at the end if you call sei() it will enable the interrupts - but they shouldn't be because they were disabled before the function.
By restoring the SREG to what it was before the cli() instruction, you ensure that the state of the interrupt bit is the same as when the function was called.

The 'ADC' register is 16bit and is known to the compiler to be such and the way in which it should be accessed is known to the compiler. It will actually execute two consecutive 'lds' or 'in' instructions, placing the result of each into two consecutive working registers (equivalent to performing the bit shift (<<8) ). Essentially it converts it into the same ADCL then ADCH read instructions be ensures that they are never rearranged.
If you read the datasheet you will see that it is actually in certain circumstances legal to read just the ADCH register and not the ADCL one - for example if you want an 8bit conversion result, rather than doing what everyone seems to do which is:
analogRead()/4 ;
or
analogRead()>>2;
What you can instead do is set the ADLAR bit in the ADCSRB register before performing the conversion, and then you can read just the ADCH register and you have direct access to an 8bit conversion result.
~Tom~

Coding Badly

There is also another problem which the current analogRead() code fails to consider which is interrupts. If an interrupt occurs after you have read ADCL but before you have read ADCH, then you can loose the conversion result.


The read is not timed.  Why would it matter if an interrupt occurs between reading L and H?

Tom Carpenter

As far as I recall, all the 16bit registers share the same 8bit temp register, in which case if you access another 16bit register in an interrupt you are screwed. I could be wrong about that, maybe it was just for the timer modules, if so, ignore the interrupt thing.
~Tom~

fungus


As far as I recall, all the 16bit registers share the same 8bit temp register, in which case if you access another 16bit register in an interrupt you are screwed. I could be wrong about that, maybe it was just for the timer modules, if so, ignore the interrupt thing.


Never heard that before...

(and it's the sort of thing the datasheet would mention!)
Advanced Arduino

Tom Carpenter

It was just for timer modules, never mind.

"Note that the 16-bit Timer (Timer1) has only one temporary register that is
shared between all it's 16-bit register pairs" [http://www.atmel.com/Images/doc1493.pdf]

But also, if the interrupt that occurs happens to access the same registers, then you need to ensure atomic operation on the reads.
~Tom~

Coding Badly

So this should work?!


Looks reasonable to me but I have not tried running it.

Quote
What do I need the SREG for?


You don't.

Quote
And do I have to use cli() / sei() here (Thats enables/disables intterupts, right?!)?


Not necessary.

Quote
I am wondering if noone has done this successfully before?!


http://forum.arduino.cc/index.php/topic,38119.0.html
http://forum.arduino.cc/index.php?topic=133907.msg1007492#msg1007492
(Google will get you the rest of the list)

Go Up