Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion for improving "sleep" time accuracy. #112

Open
MojaveTom opened this issue Feb 16, 2021 · 1 comment
Open

Suggestion for improving "sleep" time accuracy. #112

MojaveTom opened this issue Feb 16, 2021 · 1 comment

Comments

@MojaveTom
Copy link

This suggestion is similar to issue #111 in that a flag is to be set when the watch dog timer interrupt service routine is executed, but it this case, it is used to re-sleep the CPU if it is awakened by some other interrupt to ensure that the power saving function does not exit before the WDT interrupt happens. As I see it, the problem is that when the CPU wakes up in powerDown it doesn't know which interrupt awakened it, so it just assumes that it was the WDT. Since the interrupt service routine for the WDT is in that same module, it is easy to add a flag that is set when the WDT interrupt is entered, and cleared when the powerDown function first sleeps the CPU. The CPU is just put back to sleep if it is awakened and the wdtFinished flag is still false. When the wdtFinished flag is true, the powerDown function exits as usual.

With this modification, the time in the powerDown function is not short circuited by an unexpected interrupt.

// add new wdtFinished flag class variable
volatile bool LowPowerClass::wdtFinished;

...

void	LowPowerClass::powerDown(period_t period, adc_t adc, bod_t bod)
{
	if (adc == ADC_OFF)	ADCSRA &= ~(1 << ADEN);

	if (period != SLEEP_FOREVER)
	{
		wdt_enable(period);
		WDTCSR |= (1 << WDIE);
	}
	if (bod == BOD_OFF)
	{
		wdtFinished = false;        //  added code
		while (!wdtFinished)        //  added code
		#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__)
			lowPowerBodOff(SLEEP_MODE_PWR_DOWN);
		#else
			lowPowerBodOn(SLEEP_MODE_PWR_DOWN);
		#endif
		wdtFinished = false;        //  added code
	}
	else
	{
		wdtFinished = false;        //  added code
		while (!wdtFinished)        //  added code
			lowPowerBodOn(SLEEP_MODE_PWR_DOWN);
		wdtFinished = false;        //  added code
	}

	if (adc == ADC_OFF) ADCSRA |= (1 << ADEN);
}

...

 // set wdt finished flag
ISR (WDT_vect)
{
	// WDIE & WDIF is cleared in hardware upon entering this ISR
	wdt_disable();
	LowPowerClass::wdtFinished = true;        //  added code
}

The powerSave, powerStandby, and powerExtStandby functions could be modified in a similar manner.

@MojaveTom
Copy link
Author

When I implemented these suggestions, I realized two things:

  1. The wdtFinished flag cannot be defined in the header file as a class variable; the interrupt service routine is not a class function (and cannot be one) and so does not have access to private or protected attributes of the class. I think it would be very dangerous to make this attribute public. The solution is to make the variable local to the .cpp file.
  2. If the user specifies to SLEEP_FOREVER, the routines would never return since the WDT interrupt in not enabled. The solution is to not loop if SLEEP_FOREVER is specified.

LowPower.cpp

// variable global to this file.
volatile static bool wdtFinished;

...

void	LowPowerClass::powerDown(period_t period, adc_t adc, bod_t bod)
{
	if (adc == ADC_OFF)	ADCSRA &= ~(1 << ADEN);

	// set wdtFinished true if SLEEP_FOREVER so single test will terminate sleep
	wdtFinished = period == SLEEP_FOREVER;	// assigned before IE to prevent race
	if (period != SLEEP_FOREVER)
	{
		wdt_enable(period);
		WDTCSR |= (1 << WDIE);
	}

	do {	// Do this at least once even if SLEEP_FOREVER
		if (bod == BOD_OFF)
		{
				#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__)
					lowPowerBodOff(SLEEP_MODE_PWR_DOWN);
				#else
					lowPowerBodOn(SLEEP_MODE_PWR_DOWN);
				#endif
		}
		else
		{
			lowPowerBodOn(SLEEP_MODE_PWR_DOWN);
		}
	} while (!wdtFinished);

	if (adc == ADC_OFF) ADCSRA |= (1 << ADEN);
}

...

ISR (WDT_vect)
{
	// WDIE & WDIF is cleared in hardware upon entering this ISR
	wdt_disable();
	wdtFinished = true;
}

The other functions would have similar modifications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant