How not to read internal 1.1v that often

Hi,

I have a few things that I need help with. One of them is to make my code not read the internal 1.1v ref too often.
I have a series of five LEDs that show the battery level (My standalone Atmega328 is powered by a lipo battery).

The problem is that when the battery voltage hits the level between two of the LEDs, the two LEDs flicker on and off. Example: If the voltage is 3,4v (3400), the 20% (3000 to 3400) and 40% (3400 to 3600) LEDs flicker on and off one at the time. The voltage is not stable enough for this not to happen.

I would like to solve this by having my code not read the voltage level "all the time" but with a 1 minute interval.

Could someone please guide me on how to modify my code for this? Or if you have any other good solution?
(Note that I cant use long delays since I have a pushbutton that I use to jump from case to case)

Here is my code. I have shortend the code for this topic, so yes, I know parts of the code is missing:

//  1.1v Battery monitor START

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result; // Back-calculate AVcc in mV
  return result;
}


void setup() {

Serial.begin(9600);

  pinMode(B20, OUTPUT);
  pinMode(B40, OUTPUT);
  pinMode(B60, OUTPUT);
  pinMode(B80, OUTPUT);
  pinMode(B99, OUTPUT);
  
}

void loop() {


 buttonPoll = analogRead(button) > 500 ? 1 : 0;     //For using A7 as button input
  if(buttonPoll == 1){
    delay(30);                                      //For debounce 30ms should be enough
    unsigned long chrono = millis();
    while (buttonPoll == 1) 
 buttonPoll = analogRead(button) > 500 ? 1 : 0;
  if (millis()-chrono < 500) state = old + 1;       //Short press under 0.5 second
    else state = 0;                                 //Long press is over 0.5 second
  } 
    else {
    delay(50);
  }


long currentVoltage;
currentVoltage = readVcc();


//  ----- SWITCH CASE ------   

  if (currentVoltage >= 3000) {
  
  switch (state) {


    case 0:    

//  Batterydisplay

  if ((currentVoltage > 3200) && (currentVoltage < 3400)) {
  digitalWrite(B20, HIGH);}
  else {
  digitalWrite(B20, LOW);}
  
  if ((currentVoltage > 3400) && (currentVoltage < 3600)) {
  digitalWrite(B40, HIGH);}
  else {
  digitalWrite(B40, LOW);}
  
  if ((currentVoltage > 3600) && (currentVoltage < 3800)) {
  digitalWrite(B60, HIGH);}
  else {
  digitalWrite(B60, LOW);}
  
  if ((currentVoltage > 3800) && (currentVoltage < 4000)) {
  digitalWrite(B80, HIGH);}
  else {
  digitalWrite(B80, LOW);}
  
  if (currentVoltage > 4000) {
  digitalWrite(B99, HIGH);}
  else {
  digitalWrite(B99, LOW);}


  old = state;
      break;
    
    case 1: 
   
//my code here

  old = state;
      break;

    
    case 2: 
   
//my code here

  old = state;
      break;
      
    case 3: 
   
//my code here

  old = state;
      break;
      
  old = 1;
      break;

  }   //Switch case
  
  }   //If voltage is over...

  
  else {        //For low battery, only use NL
  digitalWrite(NL, HIGH);
  
  digitalWrite(UV,LOW);
  digitalWrite(LED13,LOW);
  digitalWrite(X1A,LOW);
  digitalWrite(X1B,LOW);
  digitalWrite(X1C,LOW);
  digitalWrite(B20,LOW);
  digitalWrite(B40,LOW);
  digitalWrite(B60,LOW);
  digitalWrite(B80,LOW);
  digitalWrite(B99,LOW);
  digitalWrite(RED,LOW);
  digitalWrite(GREEN,LOW);
  digitalWrite(BLUE,LOW);

  //Add code that makes LED B20 to blink slowly for 10 seconds
  
  //old = 1;
  }
 
  delay(1);        // delay in between reads for stability

}

Could someone please guide me on how to modify my code for this? Or if you have any other good solution?
(Note that I cant use long delays since I have a pushbutton that I use to jump from case to case)

Have look at Using millis() for timing. A beginners guide, Several things at the same time and look at the BlinkWithoutDelay example in the IDE.

use millis() instead of delay

Using millis() for timing. A beginner's guide.

long readVcc()
{
  static unsigned long timer = 0;  // static to keep its value from call to call
  unsigned long interval = 60000;  // one minute interval
  if(millis() - timer >= interval)
  {
     timer = millis();  
     long result;
     // Read 1.1V reference against AVcc
     ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
     delay(2); // Wait for Vref to settle
     ADCSRA |= _BV(ADSC); // Convert
     while (bit_is_set(ADCSRA,ADSC));
     result = ADCL;
     result |= ADCH<<8;
     result = 1126400L / result; // Back-calculate AVcc in mV
     return result;
   }
}

This illustrates the use of the blink without delay method. Call readVcc() every time through loop() but it will only execute every one minute.

Your ranges so that they overlap.
IE 3200-3400-3400-3600-3600 could be 3200-3399-3400-3599-3600 so you don’t have 2 LEDs lighting up sometimes.

You could also skip using millis and instead make a variable that you increment each loop and only execute battery display code say once every 20000 loops. Reset that variable back to 1.

Thanks for the replys. I need to look more into the millis code. Still need that for a few other parts of my code aswell. Thanks for the link to the guide!

groundFungus:

long readVcc()

{
  static unsigned long timer = 0;  // static to keep its value from call to call
  unsigned long interval = 60000;  // one minute interval
  if(millis() - timer >= interval)
  {
    timer = millis(); 
    long result;
    // Read 1.1V reference against AVcc
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    delay(2); // Wait for Vref to settle
    ADCSRA |= _BV(ADSC); // Convert
    while (bit_is_set(ADCSRA,ADSC));
    result = ADCL;
    result |= ADCH<<8;
    result = 1126400L / result; // Back-calculate AVcc in mV
    return result;
  }
}




This illustrates the use of the blink without delay method. Call readVcc() every time through loop() but it will only execute every one minute.

Thanks a lot for the code. I have testet it now and it kind of works. Now the battery level LED only blink one time every 60 seconds. How could I change the code so that the battery level LED stays on "all the time", but only updates the battery voltage reading every 60 seconds?

groundFungus:
This illustrates the use of the blink without delay method. Call readVcc() every time through loop() but it will only execute every one minute.

Me thinking out loud: Now the LEDs are all turned off all the time, but at the 60 seconds mark, the one led indicating the battery voltage percentage blink one time fast. Im trying to get this to happen at 0 seconds after I turn on the circuit and for the LED to stay on, but the voltaga value to be updated only every 60 seconds.
If Im correct, I need to read VCC (readVCC in my code) at 0 seconds, 60 seconds, 120 and so on, and for that value to be stored in a variable. Is that correct? Then I need the series of five LEDs (indicating 20-40-60-80-100 % battery level) to show the readVCC value.
Any guidance on how to achieve this? Been reading a lot about millis, but I still have a way to go to understand this part of the programming.

I added this to my code now, and it all works fine, but I get the first reading first after 60 seconds. Could someone please give me an input on how to make a reading at time=0 (when I power on the Arduino), then after every 60 seconds (like below)?

Thanks!

void loop()
{
 const unsigned long OneMin = 60 * 1000UL;
 static unsigned long lastSampleTime = 0 - OneMin; 

 unsigned long now = millis();

switch (case)

case 1;

 if (now - lastSampleTime >= OneMin)
 {
    lastSampleTime += OneMin;
    
   //My case code for battery LEDs here


 }

  //Code for the rest of the cases here
}

(Thanks to dc42 for the code above)

You can either do it in setup() or use a flag in loop()
Set the flag to true as a global variable and when your program enters loop() have some code like this

if (firstReading == true) {
firstReading = false;
;do your first read
}

so this code will execute only once at entry to loop()

Thanks for the tips.

Unfortunately for me, I still struggle with the understanding of the coding. Been trying to get the code to work, but without any luck.

I have one idea that I think might work.
Now I have the battery monitoring code inside the first case. If I just move this part of the code outside the case function, and makes it so that everytime I holde the push button, the battery monitor LEDs light up for x seconds, and at the same time the case jumps from case x to case x+1? This will then make the push button switch the case, and also show the battery level everytime I jump back to the first case (long press), in theory just what I have been trying to doo.

What do you guys think? I will give it a try

//  1.1v Battery monitor START

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result; // Back-calculate AVcc in mV
  return result;
}


void setup() {

Serial.begin(9600);

  pinMode(B20, OUTPUT);
  pinMode(B40, OUTPUT);
  pinMode(B60, OUTPUT);
  pinMode(B80, OUTPUT);
  pinMode(B99, OUTPUT);
  
}

void loop() {


 buttonPoll = analogRead(button) > 500 ? 1 : 0;     //For using A7 as button input
  if(buttonPoll == 1){
    delay(30);                                      //For debounce 30ms should be enough
    unsigned long chrono = millis();
    while (buttonPoll == 1) 
 buttonPoll = analogRead(button) > 500 ? 1 : 0;
  if (millis()-chrono < 500) state = old + 1;       //Short press under 0.5 second
    else state = 0;                                 //Long press is over 0.5 second
  } 
    else {
    delay(50);
  }


long currentVoltage;
currentVoltage = readVcc();


//  ----- SWITCH CASE ------   

  if (currentVoltage >= 3000) {
  
  switch (state) {


    case 0:    

//  Batterydisplay

  if ((currentVoltage > 3200) && (currentVoltage < 3400)) {
  digitalWrite(B20, HIGH);}
  else {
  digitalWrite(B20, LOW);}
  
  if ((currentVoltage > 3400) && (currentVoltage < 3600)) {
  digitalWrite(B40, HIGH);}
  else {
  digitalWrite(B40, LOW);}
  
  if ((currentVoltage > 3600) && (currentVoltage < 3800)) {
  digitalWrite(B60, HIGH);}
  else {
  digitalWrite(B60, LOW);}
  
  if ((currentVoltage > 3800) && (currentVoltage < 4000)) {
  digitalWrite(B80, HIGH);}
  else {
  digitalWrite(B80, LOW);}
  
  if (currentVoltage > 4000) {
  digitalWrite(B99, HIGH);}
  else {
  digitalWrite(B99, LOW);}


  old = state;
      break;
    
    case 1: 
   
//my code here

  old = state;
      break;

    
    case 2: 
   
//my code here

  old = state;
      break;