Secret attiny 84 voltmeter

I like the idea of this feature, but cant employ it properly. I have a 5v voltage source that i need to read. Th goal is to have a green led illuminate when the voltage is above 4.8 v and a red led illuminate if below 4.4.

uint16_t readVcc() {                  // set reference to internal 1.1V reference
  ADMUX = _BV(MUX5) | _BV(MUX0);  // MUX3 & MUX2 select the 1.1V reference for measurement
  delay(2);                       // wait for Vref to settle
  ADCSRA |= _BV(ADSC);            // Start the conversion
  while (bit_is_set(ADCSRA, ADSC));  // WAIT FOR CONVERSION TO COMPLETE
  uint16_t low = ADCL;         
  uint8_t high = ADCH;
  long vcc = (high<<8) | low;
   vcc = (1125300L / vcc) /1000;  //Calculate Vcc in volts
  return vcc;  // Vcc in volts}

void loop() {
  // battery reading
   readVcc();

  if (vcc <= 4.40) { digitalWrite(rd_led, HIGH), digitalWrite(grn_led, LOW); }  // Low Battery
  if (vcc >= 4.8) { digitalWrite(rd_led, LOW), digitalWrite(grn_led, HIGH); }  // Good battery

}

The error i got was vcc wasnt defined.

try

void loop() 
{
  // battery reading
  float vcc = readVcc();

  if (vcc <= 4.40)  //  Low Battery
  { 
    digitalWrite(rd_led, HIGH);
    digitalWrite(grn_led, LOW); 
  }  
  if (vcc >= 4.8)  //  Good battery
  { 
    digitalWrite(rd_led, LOW);
    digitalWrite(grn_led, HIGH);    
  }
}
2 Likes

thing is originally i tried it with int instead of float and it would flicker in an unstable manner between the red and green light

Please post your complete sketch, not just a fragment.

Having said that, @robtillaart 's solution should work in principle.

Not sure where you used an int, but obviously for a value that is a floating point value, integer is not an appropriate type.

Try printing what is returned by readVcc, might be useful
Please post its output.

1 Like

Note that returning value of readVcc() is uint16_t

1 Like
uint16_t readVcc() {                  // set reference to internal 1.1V reference
  ADMUX = _BV(MUX5) | _BV(MUX0);  // MUX3 & MUX2 select the 1.1V reference for measurement
  delay(2);                       // wait for Vref to settle
  ADCSRA |= _BV(ADSC);            // Start the conversion
  while (bit_is_set(ADCSRA, ADSC));  // WAIT FOR CONVERSION TO COMPLETE
  uint16_t low = ADCL;         
  uint8_t high = ADCH;
  low = (high<<8) | low;
  return low;  }

void loop() {
  // battery reading
  uint16_t val = readVcc();
  float  vcc = (1125300L / val) /1000.0;  //Calculate Vcc in volts

  if (vcc <= 4.40) { digitalWrite(rd_led, HIGH), digitalWrite(grn_led, LOW); }  // Low Battery
  if (vcc >= 4.8) { digitalWrite(rd_led, LOW), digitalWrite(grn_led, HIGH); }  // Good battery

}
1 Like

The function should calculate and return a float:

 return 1125.3 / vcc;  //Calculate Vcc in volts
1 Like

Your function returns a 16 bit unsigned integer, so it can never give you the voltage as decimal values.

That said there's no point converting the A2D value to volts, just find out what value from the A2D represents low and normal supply voltage and compare with those values directly.

However, I suspect you might have another problem when the voltage is very close to the threshold, the LED might flicker on and off as the measured value hovers around the threshold. To fix this you need 2 threshold values close together, one to turn the LED on, the other to turn it off.

2 Likes

Yes I noticed, float can handle the full range of uint16_t

It can even handle the returntype after readVcc() is fixed :wink:

Because the optimizer is free to reorder those instructions, that is a bug. Use ADC directly.

I think you be better off sticking with integers too.
Manipulating stuff by converting to float then dividing by big numbers can introduce errors or mislead you into thinking you have more resolution .
If you can , just use the raw integer values or use map to translate into say 1/10 volts and use that .
Some form of calibration routine will be needed anyway .

  • add a print statement to print the raw values and just use the numbers at 4.8 volts or whatever .
2 Likes

Hmmm … not sure about that !
Questions and answers seem in an odd style :
“ Operator function means operation defined for that operator so if user defines a function for an operator then that is called operator overloading i.e. overloading already present operator function.”

Or stick to integers:

uint16_t readVcc() {                  
  ...
  return 1125300 / vcc;  //Calculate Vcc in millivolts
}

Nick Gammon's code from https://www.gammon.com.au/adc

const float InternalReferenceVoltage = 1.096; // as measured

void setup ()
{
  Serial.begin (115200);
  ADCSRA =  bit (ADEN);   // turn ADC on
  ADCSRA |= bit (ADPS0) |  bit (ADPS1) | bit (ADPS2);  // Prescaler of 128
  ADMUX = bit (REFS0) | bit (MUX3) | bit (MUX2) | bit (MUX1);
  
  delay (10);  // let it stabilize
  
  bitSet (ADCSRA, ADSC);  // start a conversion  
  while (bit_is_set(ADCSRA, ADSC))
    { }
  
  float results = InternalReferenceVoltage / float (ADC + 0.5) * 1024.0; 
  Serial.print ("Voltage = ");
  Serial.println (results);  
}  // end of setup

void loop () { }

...gives a hint at where your 1125.3 magic number comes from. It is 1.1*1023=1125.3 from the bandgap voltage and the TOP value of the ADC. Nick would use a different magic number: 1024 *1.1=1126.4 or measure your bandgap voltage and do the math.

You could use a DMM to measure your chip's actual 1.1V reference by exposing it on the AREF pin per:

...with:

void setup() {
  ADMUX = _BV(REFS1) | _BV(REFS0) ; // expose internal bandgap 1.1V on ATTINY84 AREF pin PA0
}
void loop() {}
1 Like

Im fairly new. What does that mean and how do i do that? does that involve directly manipulating a register?

idk why but i wasnt able to pick the proper topic, forgive me.

In post #15, you will notice that @DaveX references neither ADCL nor ADCH but instead uses ADC. That is the correct choice.

Forget the uc....https://forum.allaboutcircuits.com/threads/tri-colour-voltage-indicator-circuit-wanted.27020/

This should work:

//
// https://github.com/SpenceKonde/ATTinyCore
//

// Must be adapted to the pin assignment of the ATTiny84.
constexpr byte LEDPIN_GREEN {PB0};
constexpr byte LEDPIN_RED {PB1};

constexpr uint16_t INTERNAL_REF {1085};   // Must be determined before use
constexpr uint32_t INERNAL_REFxRESOLUTION {INTERNAL_REF * 1024UL};

//
// Initializes the ADC depending on the microcontroller used
//
void initADC0() {
  // Read 1.1V reference as input with reference operating voltage vcc
  // Reference Vcc and analog input = internal reference 1.1V
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  // Initialise ADC with REFS[2:0] is 0 = VCC as Ref,  MUX[3:0] 1100 = Vbg as Input,
  ADCSRA = _BV(ADPS2) | _BV(ADPS1);   // Samplingrate = 125kHz with 8Mhz Corecycles
  ADMUX = _BV(MUX3) | _BV(MUX2);
#else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
  ADCSRA |= _BV(ADEN);   // Enable
  // After activating the ADC, a "dummy readout" is recommended.
  // In other words, a value is read and discarded to allow the ADC to "warm up"
  while ((ADCSRA & _BV(ADSC))) { ; }
  (void)ADCW;   // Discard dummy readout..
}

//
// Returns the operating voltage in millivolts
//
uint16_t measurementVCC() {
  ADCSRA |= _BV(ADSC);                 // Start conversion
  while ((ADCSRA & _BV(ADSC))) { ; }   // measure
  return static_cast<uint16_t>(INERNAL_REFxRESOLUTION / ADCW);
}

void setup() {
  pinMode(LEDPIN_GREEN, OUTPUT);
  pinMode(LEDPIN_RED, OUTPUT);
  initADC0();   // Set ADC to interal reference voltage as input (Vbg)
}

void loop() {
  uint16_t vcc = measurementVCC();
  if (vcc <= 4400) { digitalWrite(LEDPIN_RED, HIGH), digitalWrite(LEDPIN_GREEN, LOW); }  // Low Battery
  if (vcc >= 4800) { digitalWrite(LEDPIN_RED, LOW), digitalWrite(LEDPIN_GREEN, HIGH); }  // Good battery
  delay(2000);
}

Correct! I have verified and have learnt a new thing. The disadvanatge is that the float declaration consumes four memory locations.
Output:

Printing y1: 659
Printing y2: 659.0000000000
//===========================
Printing y1: 657           //unsigned int y1;
Printing y2: 657.0000000000  //float y2;
//===========================