Attiny 44 - watchdog as "delay"

Greetings. Looking for a bit of help on some code that kinda works, but is flaky and I can't figure out why. Ultimately what I am trying to achieve is a lower power variant of "delay" that can readily be interrupted by key presses. Or, another way of putting it, I want a timed sleep function. My first thought was to use the watchdog timer - which I know is not precise, but these delays do not need to be precise (flashing LEDs, etc).

In my code below, I put the micro to sleep until a pin interrupt is triggered. If the pin interrupt is from the pin sensing the presence of USB voltage (USB_Sense), the micro enters a flashing cycle which enables the watchdog timer. You can see that basically what I am doing right now is turning on an LED, turning the watchdog timer on for a cycle, turning off the LED, then turning the watchdog timer off for a cycle. I understand that it is silly that I am turning the watchdog timer on and off considering the interval is the same, but ultimately I want to end up with code where I can pass in to my delay_WD function both the watchdog timer cycle time and the number of cycles I want it to run, so I have more flexibility over my pseudo-delay.

While USB_Sense is held high, and delay_WD is doing its thing, I want the code to be interruptable by an external button press. If that interrupt is sensed, the code will enter the "button" function, and will turn on another LED while the button is held down. If USB_Sense is still high after the press, the flashing cycle resumes.

The big issue I am having is when I bring USB_Sense high, the proper LED lights, but it stays lit for 20s or so before the flashing begins. If I press the external button, the other LED will light appropriately, turn off when the button is released, and then the flashing cycle begins (almost like the button press "kick-starts" the cycle). I must not be enabling something or handling initial conditions correctly - or doing something horribly wrong and I've just gotten lucky. Any help would be greatly appreciated.

-Chris

#include <avr/sleep.h>
#include <avr/wdt.h>

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif



int BUTTON = 2; //user feedback button
int PCC_GLED = 5; //PCC Green LED
int PCC_RLED = 6; //PCC Red LED
int USB_SENSE = 9; //USB presence sensor

volatile int inter_check = 0;

volatile boolean f_wdt = 0;

void setup(){
    
  sbi(GIMSK,PCIE0); // Turn on Pin Change interrupt 0
  sbi(GIMSK,PCIE1); // Turn on Pin Change interrupt 1
  
  sbi(PCMSK0,PCINT2); // Feedback switch
  sbi(PCMSK0,PCINT3); // ECig sense
  sbi(PCMSK1,PCINT9); // USB sense
  
  pinMode(PCC_GLED, OUTPUT);
  pinMode(PCC_RLED, OUTPUT);
  pinMode(BUTTON, INPUT);
  pinMode(USB_SENSE, INPUT);

  digitalWrite(PCC_GLED, HIGH);
  digitalWrite(PCC_RLED, HIGH);
  digitalWrite(BUTTON, HIGH);
  digitalWrite(USB_SENSE, LOW);
  wdt_reset(); 
  MCUSR=0; 
  wdt_disable(); 
}

void loop(){
  
  //USB_STATE = digitalRead(USB_SENSE);
  //BUTTON_STATE = digitalRead(BUTTON);
  //ECIG_PRES_STATE = digitalRead(ECIG_PRES);


  if (digitalRead(USB_SENSE)==HIGH){ //send to USB charging if bounced out of ECig charge
    USB_Charging();
  }
  inter_check = 0;
  system_sleep();
} 
  

void USB_Charging() {
  while(digitalRead(USB_SENSE)==HIGH) {
    digitalWrite(PCC_GLED, LOW);
    delay_WD(6,1); //wdtimer setting 6, 1 cycle, 1s
    digitalWrite(PCC_GLED, HIGH);
    delay_WD(6,1); //wdtimer setting 8, 1 cycle, 2s
  }
  
   
}

void button() {
  digitalWrite(PCC_GLED, HIGH);
  while(digitalRead(BUTTON)==LOW) {
    digitalWrite(PCC_RLED, LOW);
  } 
  digitalWrite(PCC_RLED, HIGH);
}


void delay_WD (int WD_set, int WD_cycle) {
  //setup
  byte bb;
  int ww;
  if (WD_set > 9 ) WD_set=9;
  bb=WD_set & 7;
  if (WD_set > 7) bb|= (1<<5);
  bb|= (1<<WDCE);
  ww=bb;

  MCUSR &= ~(1<<WDRF);
  // start timed sequence
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  // set new watchdog timeout value
  WDTCSR = bb;
  WDTCSR |= _BV(WDIE);
  //end setup
  
  system_sleep();
  wdt_reset(); 
  MCUSR=0; 
  wdt_disable(); 
}

void system_sleep() {
  cbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter OFF

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
  sleep_enable();

  sleep_mode();                        // System sleeps here

  sleep_disable();                     // System continues execution here when watchdog timed out 
  sbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter ON
  if (inter_check == 1){
    if (digitalRead(USB_SENSE)==HIGH){
      USB_Charging();
    }
  }
  else if (inter_check == 2){
    if (digitalRead(BUTTON)==LOW){
      button();
    }
  }
}

ISR(WDT_vect) {
  f_wdt=1;  // set global flag
}
ISR(PCINT0_vect) {
  
    inter_check=2; //button
}
  
ISR(PCINT1_vect){
  
    inter_check=1;  
}

Instead of describing your solution to the application try describing the actual application.

Sure, right now I am just trying to set up a attiny44a to be asleep unless two things happen:

  1. One pin is brought high (USB_Sense - senses the presence of USB voltage)
  2. One pin is brought low (Button - sense the depression of an external button)

When #1 happens, I want the 44a to flash an LED (PCC_GLED) on for one second, then off for one second, and continue to do so while the pin is held high.

When #2 happens, I want the 44a to turn on another LED (PCC_RLED) as long as the pin is held low.

When #2 happens while #1 is happening, I want the 44a to stop flashing ~immediately and again turn on PCC_RLED as long as the button is depressed. Once the button is released, if USB_Sense is still high, flashing should start up again.

I don't want to use the delay() function because, in the case of the 1 second blink half-step, the code can't respond to the button press during that one second. Additionally, I see little reason to leave the 44a fully powered up during that time.

I was looking in to using one of the internal timers in the 44a, but the longer intervals of the watchdog timer will be useful when I expand on the code, and the watchdog timer seemed like it might be easier to use.

Am I on the right track? Or should I be looking elsewhere?

-Chris

A good description. However, I don't see any mention of "low power". What does that mean to you?

Honestly right now I am looking for a solution that offers roughly the same functionality as delay(), but can be readily interrupted and offers some level of power savings over delay() - in other words I'm not willing to run some loop where I check for interrupt flags and delay for some short amount of time until the total delay desired is reached. It seems like using the watchdog timer might give me the highest level of power savings considering the watchdog timer can bring the 44 out of its lowest power sleep state, so if possible, I'd like to stick with it unless there is a lower power alternate. Ultimately this code will be implemented on a battery powered device, so power consumption is important.

Any ideas how I might correct the issue I am seeing?

It looks to me like your current code can go "recursive."
loop() calls system_sleep()
system_sleep() calls USB_charging()
USB_charging calls delay_wd()
delay_wd() calls system_sleep()

If you hold the button down too long, this will fill up your stack and cause unpredictable behavior.

You need to reorganized your code so that system_sleep() always just returns, with some sort of decision process based on the wakeup reason occurring at a higher level.

loop() {
  system_sleep();
  switch (wakeup_reason) {
  case USB_PWR_DETECT:
    // blah
    break;
  case BUTTON:
    // stuff
    break;
  case TIMEOUT:
    // etc
    break;
   }
}

Yup, this is exactly the issue, thanks!