Tricky question about multiple sleep modes in same sketch

Hi all,

I'm just trying to get a good base programming structure for my 328p so that I can pair it with an 18650 and charge from a solar panel. The idea is that not only does it deep sleep till woken on pin2/int0 but while its awake it sleeps for 7 seconds then works for 1 to use an eighth of the power. So far I've successfully got my code to work with the 328p at 8Mhz 3.3v to deep sleep reaching 0.1uA. After interrupt is triggered I use around 5.5mA in active mode. After integrating the final code for the 7 second sleep I find that while in deep sleep the WDT keeps waking the device. I feel like I'm so close but I've tried linking the f_wdt=1; to val so that unless pin2 is grounded/LOW it should not renew the sleep ticket but if I place in the main loop if or the ISR function it still does not work and even acts erratically. If anyone has any ideas please as I'm scratching my head over this one.

//AceTK - Test Program - Deepsleep - Powersaver


#include <avr/sleep.h> // includes library for sleep
#include <avr/power.h> // includes library for sleep
#include <avr/wdt.h>

unsigned short int SolVlts = 2;
volatile unsigned long AuxVolts = 0;
byte intCounter, adcsra, mcucr1, mcucr2;
volatile int f_wdt=1;
short unsigned int auxcharging = 0;
short unsigned int Relay1on = 7;
short unsigned int Relay1off = 8;
float AuxBatt = 0;
volatile short unsigned int val = 0; 


ISR(WDT_vect)
{
  if(f_wdt == 0)  // or if(f_wdt == 0 && val == 0) wont work either
  {
    f_wdt=1;
  }
  else
  {
    Serial.println("WDT Overrun Error!!!");
  }
}

void enterSleep(void)
{
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);   
  sleep_enable();
  
  /* Now enter sleep mode. */
  sleep_mode();
  
  /* The program will continue from here after the WDT timeout*/
  sleep_disable(); /* First thing to do is disable sleep. */
  
  /* Re-enable the peripherals. */
  power_all_enable();
}

void SleepCheck()
{
int val;  
val = digitalRead(SolVlts);   // read the 1.092v/5v charge ready input pin
if (val > 0)                  // if 1.092v / 5v present boolean equals 1 / true
{
  sleep_enable();
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    EIMSK |= _BV(INT0);            //enable INT0
    adcsra = ADCSRA;               //save the ADC Control and Status Register A
    ADCSRA = 0;                    //disable ADC
    cli();
    mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE);  //turn off the brown-out detector
    mcucr2 = mcucr1 & ~_BV(BODSE);
    MCUCR = mcucr1;
    MCUCR = mcucr2;
    sei();                         //ensure interrupts enabled so we can wake up again
    sleep_cpu();                   //go to sleep
    sleep_disable();               //wake up here
    ADCSRA = adcsra;               //restore ADCSRA
} 
}

ISR(INT0_vect)
{
    intCounter++;
    EIMSK &= ~_BV(INT0);           //one interrupt to wake up only
}

void AuxBattery(){
 AuxVolts = analogRead(A4);  
 AuxBatt = AuxVolts / 240.70; // Aux cell resistive divide ratio converter 1023 / 240.70 = 4.25
 if (AuxBatt < 4.1 && auxcharging == 0)
{
 digitalWrite(Relay1on, HIGH);
  delay(4);
  digitalWrite(Relay1on, LOW);
  auxcharging=1;
}

if (AuxBatt > 4.1 && auxcharging == 1)
{
  digitalWrite(Relay1off, HIGH);
  delay(4);
  digitalWrite(Relay1off, LOW);
  auxcharging=0;
 }
}
 
  

void setup() {
 analogReference(INTERNAL); // sets reference to internal 1.092v
 pinMode(13, OUTPUT); // active light test
 pinMode(SolVlts, INPUT);  // specifies pin 2 as available solar volts suitable for charge
 EICRA = 0x00;                 //configure INT0 to trigger on low level
 Serial.begin(9600);
 /*** Setup the WDT ***/
 /* Clear the reset flag. */
 MCUSR &= ~(1<<WDRF);
 /* In order to change WDE or the prescaler, we need to
 * set WDCE (This will allow updates for 4 clock cycles).
 */
 WDTCSR |= (1<<WDCE) | (1<<WDE);
 /* set new watchdog timeout prescaler value */
 WDTCSR = 1<<WDP0 | 1<<WDP3; /* 8.0 seconds */
 /* Enable the WD interrupt (note no reset). */
 WDTCSR |= _BV(WDIE);
}




void loop()
{
 //ReadAuxVolts();  //will this work? + need to write aux battery charging function
 SleepCheck();  //test rising+low uA
 //MiniSleep(); replace code with WDT timed 750ms version
 //SerialDiagnostics(); 
 Serial.println(AuxVolts);
 val = digitalRead(SolVlts);   // read the 1.092v/5v charge ready input pin
 digitalWrite(13, HIGH);
 delay(1000);
 digitalWrite(13, LOW);
 if(f_wdt == 1)
  {
    f_wdt = 0;
    /* Re-enter sleep mode. */
    enterSleep();
  }
  else
  {
    /* Do nothing. */
  }
}

Cheers

I suppose that in your 'deep sleep' mode you need to disable the watchdog interrupt; you'd need to enable it again when you return to 'light sleep' mode.

You have to forgive me I got this far with the sleep code by careful trial and error and copying bits of guides and tutorials whist trying to understand it. Which part of my existing code do I add the disable WDT code to and what is the code please? Is it close to WDTCSR != _BV(WDIE); at end of ? Thanks.

Or if indeed anyone else knows please?
I just need a code example with the commands quoted in my code in the right places so I can learn from them.

Thanks

I use this code to disable the watchdog on an atmega328p:

  // Turn off watchdog timer
  cli();
  wdt_reset();
  MCUSR = 0;    // clear watchdog reset flag
  WDTCSR |= (1 << WDCE) | (1 << WDE);
  WDTCSR = 0x00;
  sei();

You need to call it just before you enter deep sleep mode (by which I presume you mean power-down mode).

Ah that works great thanks. I think I'm starting to make sense of it now. It stuck in WDT sleep mode and did not wake up. Then I added

wdt_enable(WDTO_8S); // enable WDT 8 seconds
  wdt_reset();

Its recovers and does everything I hoped it would. What code do you use to re-enable it please? I mean is there a difference between ways I'm guessing the way you enable uses more than 2 lines?
Thanks again

I find that enabling the watchdog is easy, however when trying to disable it I was unable to get wdt_disable() to work for me. That is why I use the code I posted instead. To enable it, I use this:

   cli();
  wdt_reset();
  WDTCSR |= (1 << WDCE) | (1 << WDE);
  WDTCSR = (1 << WDE) | (1 << WDP2);    // reset after 0.25 sec
  sti();

However, I am using watchdog reset (not interrupt) and after only 0.25sec, so you would need to adapt it. If the code you already have works for you, then I suggest you stick with it.

[EDIT: except that you should reset the watchdog before you enable it, not after.]

Ah thanks il make the amendment. What is the function of wdt_reset(); ? Or more specifically why does the WDT need resetting before activating? What happens if you don't do it say lol? One site just says

Finally, you should put the following line that tells watchdog that everything's ok.

wdt_reset();

lol

AceTK:
Ah thanks il make the amendment. What is the function of wdt_reset(); ?

To reset the timer back to zero so that you don't get woken up until the full 8 seconds (or whatever) has elapsed.

Thank you again. If I may just ask another quick final question lol. What command do you use to get the internal divider so that I can run at 1Mhz with an 8Mhz crystal? Atm I still get 5.5mA during active mode and the spec sheet claims 0.3mA at 1.8v 1Mhz so id be happy with <2mA at 3v. Also I'm intending to use serial still will this be possible? :slight_smile:

From the datasheet:

The ATmega48A/PA/88A/PA/168A/PA/328/P has a system clock prescaler, and the system
clock can be divided by setting the ”CLKPR – Clock Prescale Register” on page 387. This feature
can be used to decrease the system clock frequency and the power consumption when the
requirement for processing power is low. This can be used with all clock source options, and it
will affect the clock frequency of the CPU and all synchronous peripherals. clkI/O, clkADC, clkCPU,
and clkFLASH are divided by a factor as shown in Table 29-12 on page 324.
...
To avoid unintentional changes of clock frequency, a special write procedure must be followed
to change the CLKPS bits:

  1. Write the Clock Prescaler Change Enable (CLKPCE) bit to one and all other bits in
    CLKPR to zero.
  2. Within four cycles, write the desired value to CLKPS while writing a zero to CLKPCE.
    Interrupts must be disabled when changing prescaler setting to make sure the write procedure is
    not interrupted.
    <<

Whether you can still use serial depends on the baud rate you want. There needs to be an integer N such that 16 * N * baud_rate is within 4% or preferably 2% of the clock speed. You can run the UART in double speed mode, then the formula becomes 8 * N * baud rate.

Thanks I've got the hang of the prescaler now. I don't have my bluetooth module or max232+cable to test the serial reliability yet but so far I'm enjoying more success with power reduction. I get 2.9mA @ 3.8v in active mode at 1Mhz. I tested all the way down to divide by 256 with CLKPR = B00001000 but it seemed get stuck and freeze after a few loops and it still used 2.1mA. Anything less than divide by 128 works fine though but 128 still draws 2.4mA. I'm curious though as to if there is anything else that might be holding the power up. I know I could turn off the ADC if the sketches I have planned didn't require it but they do. Perhaps adding a 12K resistor to vcc to restrict volts down so instead of going 4.2v-3v it goes 3v-1.8v to bring it hopefully closer to the 0.3mA of the spec sheet quoted at 1.8v. Only problem with that is I need the digitalWrite volts to be high enough to drive the mosfets and they like about 3.4v+ to fire properly lol. I guess I could try getting it working with the internal resonator but then that is unreliable with serial and for some reason I never was able to get that working anyway either as the programs didn't run unless the crystal was inserted. :stuck_out_tongue:

Apart from turning off the ADC if you don't need it, to reduce power consumption you can:

  • Use the Power Reduction register to turn off other subsystems you don't need
  • Disable the input buffers on pins you are not using as digital inputs (this is especially important on pins that you are using for analog inputs)
  • Disable brownout detection, if you don't need it

You are indeed correct again. Looking at PRR I see nothing else to turn off. The ADC is already activated at right times, and now that I've copied the brownout deactivation code to the void_setup I've saved 0.2mA giving me 2.7mA total. I was kinda hoping for <1mA but I guess I could be splitting hairs over nothing. I mean 2.7mA is still very good. I just like to feel its best to only create devices that use as little power as possible. Also running from solar power it really helps to get as much juice as possible. I've tested leakage and draw on everything and I have a little chart of all the wasted power and what ranks highest. Little things like its better to switch a 135mW relay for 4ms twice once at the beginning and end of charge cycle than to use a mosfet that is connected and requiring gate power and leaking the whole time. Also if I use 40Mohms or more on my ADC resistive divide it draws under 0.68uA. Oh and capacitors waste so much juice. I have some low ESR ones in the post but I'm actually considering not using any except perhaps a few small ceramics ones for decoupling here and there. Diodes too are bad. I have a 1N5820 diode in place but even this leaks backwards. I wondered if perhaps a mosfet with the gate tied would work better perhaps. It needs to take up to 3A charge current tho.

AceTK:
I was kinda hoping for <1mA but I guess I could be splitting hairs over nothing. I mean 2.7mA is still very good. I just like to feel its best to only create devices that use as little power as possible.

Have you considered whether a smaller processor such as an ATtiny might be adequate for this project?

Don't forget to disable the digital input buffers for the pins you are using as analog inputs, and set any unused inputs to a known state (e.g. by turning on the pullup resistors).

AceTK:
Little things like its better to switch a 135mW relay for 4ms twice once at the beginning and end of charge cycle than to use a mosfet that is connected and requiring gate power and leaking the whole time.

Mosfets don't require gate power, and drain/source leakage is also small. I've just looked up one of the standard power mosfets I use (STP40NF12) and it is specified as 100nA max gate leakage, 1uA max drain current @ max drain voltage and zero gate voltage. I don't know what voltage you are switching, but if I assume your relay is 5V then it takes 27mA... and 27mA for 2 x 4ms is equivalent to 1uA for 2.5 days. I'm sure you can get mosfets with lower leakage than 1uA if need be, unless you are switching very high currents.

AceTK:
Oh and capacitors waste so much juice. I have some low ESR ones in the post but I'm actually considering not using any except perhaps a few small ceramics ones for decoupling here and there.

Aluminium electrolytics typically leak several uA (more for higher values). Tantalum electrolytics have much lower leakage.

AceTK:
Diodes too are bad. I have a 1N5820 diode in place but even this leaks backwards. I wondered if perhaps a mosfet with the gate tied would work better perhaps. It needs to take up to 3A charge current tho.

Schottky diodes such as 1N5820 are very leaky compared to regular silicon diodes. A mosfet can work better, but it needs to be connected the "wrong way round" otherwise the body diode will conduct when you don't want it to. Pololu uses this arrangement to protect their motor drivers from reverse polarity.

Yes I had wondered about the tiny85 which I actually have here but my main project uses every single pin. Infact my main project uses so many pins I'm tempted to have a 2nd 328p just so a can get the extra analog pins for a temperature sensor and two hall effect sensors for current in and current out. Also more digital pins would mean I could add an lcd and/or some state / diagnostic lights. I could go for the mega but its only in surface mount and I've not progressed to that spec yet. Although I have to admit through hole although easy to manipulate in the fingers due to its size taks up so much board space. At some point I want to try getting a laser printer and making my own etched pcbs. The smd components are cheaper too so really its the way to go. What would I do about the 10Mohm pots I use? Can you even get 10M smd pots, the biggest I can find is 1M?

I have a design using a LCD display in which every single pin used to control it is also used for something else (the enable pin is driven from an output on a 4->16 line decoder chip, and the other 5 pins are used for other output devices and for input multiplexing).

What are you using the 10M pots for? If they are for setting values, then once you have an LCD display to show the values on, you can use rotary encoders instead, perhaps just a single rotary encoder with a built-in pushbutton to move between values.

That sounds interesting. I already reuse pin2 so why not others. I googled line decoder and I found one that only uses 8uA which seems very good. Turning 4 or 5 pins into 16 or more pins is very handy thanks. As for the 10M pots I use them in the resistive divides that are spanning the 4 18650 cells in series in my main project. To avoid balance issues I increased the resistance per stacked cell so that the static drain of the divides would exactly match providing all the cells were balanced at the same voltage. The 10M pots go at the bottom of each divide and then pipe out up to 1.092v to the analog pins which then is converted back to the actual real voltage. Then I just subtractively back track the cell stack point voltages to find to true values. Its a shame the 328p does not have an ADC which can handle more than 5v then I could remove all the divides and put the cells direct to the analog pins. I had a look at those rotary encoders too and they also seem interesting but not sure if Ill need them as part of my current project.

Off topic but random I just had my HAKKO 888 solder station arrive today from china and when I plugged it in it the main transformer unit started arcing inside and smoke came out top vent. Its a real shame really as it looked and felt top notch quality. Back in the box it goes, ebay dispute opened, sigh. Lol.

AceTK:
As for the 10M pots I use them in the resistive divides that are spanning the 4 18650 cells in series in my main project. To avoid balance issues I increased the resistance per stacked cell so that the static drain of the divides would exactly match providing all the cells were balanced at the same voltage. The 10M pots go at the bottom of each divide and then pipe out up to 1.092v to the analog pins which then is converted back to the actual real voltage. Then I just subtractively back track the cell stack point voltages to find to true values.

  1. Is there any particular reason you are using pots instead of fixed resistors?

  2. Bear in mind that when you feed the adc from a high source resistance, you get two sources of error, which are the finite input resistance of the ADC (100M according to the datasheet) and the fact that the sampling time is very short and the Arduino ADC code doesn't have any delay between setting the analog mux to the correct channel and starting the conversion. To reduce these errors, connect a capacitor of about 0.1uF between each analog input and ground (also useful for noise suppression), and switch the mux to an unused channel when you are not taking a reading.

AceTK:
Off topic but random I just had my HAKKO 888 solder station arrive today from china and when I plugged it in it the main transformer unit started arcing inside and smoke came out top vent. Its a real shame really as it looked and felt top notch quality. Back in the box it goes, ebay dispute opened, sigh. Lol.

It wasn't set for 110V and used on 220V, was it?

  1. Is there any particular reason you are using pots instead of fixed resistors?

I tried that before and the tolerance even on the 1% ones was so bad it was hard to get it precise. Much simpler to just trim the bottom pot to output the proportional ADC voltage.

  1. Bear in mind that when you feed the adc from a high source resistance, you get two sources of error, which are the finite input resistance of the ADC (100M according to the datasheet) and the fact that the sampling time is very short and the Arduino ADC code doesn't have any delay between setting the analog mux to the correct channel and starting the conversion. To reduce these errors, connect a capacitor of about 0.1uF between each analog input and ground (also useful for noise suppression), and switch the mux to an unused channel when you are not taking a reading.

Ah thanks yes I already had a 0.1uF on each middle pot pin before the wire going to the analog pin. Should I move this to exactly on the analog pin? Also to reduce noise I only know to either do an average of the collected figures, do 3 analog reads preceding the one I use and/or run from batteries so there are only stable gently declining volts on vcc. In my current project all 6 analog pins are used. Could I set the mux to something internal like A06 which is AREF I think? Would this remove the need to do the averaging and pre-reads though?

It wasn't set for 110V and used on 220V, was it?

The unit was sold as 220v, sticker on bottom said 220v, no switch. Wrong voltage was the first thing I thought too. If they end up refunding me and not requesting item back I'm going to open the sucker up and see what the real issue was.