Skip to content

Commit

Permalink
Refactor so that the WDT can be optimised out if it's not required.
Browse files Browse the repository at this point in the history
Thanks to @matthijskooijman who pointed out the possibility in
arduino/arduino-builder#15 (comment)

With LTO turned on the ATTiny13 will now do a simple forever sleep in about 30 bytes.
  • Loading branch information
sleemanj committed Aug 28, 2018
1 parent 640eb14 commit d33bb51
Show file tree
Hide file tree
Showing 10 changed files with 385 additions and 348 deletions.
11 changes: 8 additions & 3 deletions src/SimpleSleep.h
Expand Up @@ -42,7 +42,7 @@
* For AVR, typically implemented as Power Down
*/

inline void deeply() { sleepDeeply(0); }
inline void deeply() { sleepDeeply(); }

/** Sleep deeply for a given time, allow external interrupts where possible (LEVEL only usually), bod off, adc off, timers generally off
*
Expand All @@ -59,7 +59,7 @@
* For AVR, typically either implemented as Extended Standby or ADC Noise Reduction with the ADC **OFF**.
*/

inline void lightly() { sleepLightly(0); }
inline void lightly() { sleepLightly(); }

/** Sleep lightly for a given time, allow many interrupts, adc off, timers generally off
*
Expand All @@ -77,7 +77,7 @@
* For AVR, typically implemented as Idle
*/

inline void idle() { sleepIdle(0); }
inline void idle() { sleepIdle(); }

/** Wait patiently for a given time.
*
Expand Down Expand Up @@ -127,6 +127,11 @@
protected:

void sleepForever();

void sleepDeeply();
void sleepLightly();
void sleepIdle();

void sleepDeeply(uint32_t sleepMs);
void sleepLightly(uint32_t sleepMs);
void sleepIdle(uint32_t sleepMs);
Expand Down
2 changes: 2 additions & 0 deletions src/avr/ATMegaX8.cpp
@@ -1,3 +1,5 @@
#include "../SimpleSleep.h"

#if defined(SS_ATMegax8)

/* There is nothing to be done here, the standard methods defined in avr.cpp will work for us. */
Expand Down
6 changes: 4 additions & 2 deletions src/avr/ATTiny13.cpp
@@ -1,5 +1,7 @@
#include "../SimpleSleep.h"

#if defined(SS_ATTiny13)

/* There is nothing to be done here, the standard methods defined in avr.cpp will work for us. */

/* There is nothing to be done here, the standard methods defined in avr.cpp will work for us. */

#endif
2 changes: 2 additions & 0 deletions src/avr/ATTinyX4.cpp
@@ -1,3 +1,5 @@
#include "../SimpleSleep.h"

#if defined(SS_ATTinyX4)

/* There is nothing to be done here, the standard methods defined in avr.cpp will work for us. */
Expand Down
2 changes: 2 additions & 0 deletions src/avr/ATTinyX5.cpp
@@ -1,3 +1,5 @@
#include "../SimpleSleep.h"

#if defined(SS_ATTinyX5)

/* There is nothing to be done here, the standard methods defined in avr.cpp will work for us. */
Expand Down
101 changes: 101 additions & 0 deletions src/avr/avr-calibrated-sleep.cpp
@@ -0,0 +1,101 @@
/** This file contains implementation of calibration which are common amongst AVR chips.
*
* Keep ifdef to a minimum, use variant implementation files if there is any substantial difference.
*/

#if defined (__AVR__)

#include "../SimpleSleep.h"

#ifdef NO_MILLIS
// Some of the Arduino cores, particularly @sleemanj's ATTinyCore fork,
// allow disabling millis, in which case we can not do any
//
// @TODO Maybe there is some way to do it using a delay() (which will
// still work without millis()), like, start the WDT then start a delay()
// if the WDT triggers before the delay() time then the WDT needed to be longer
// if it hasn't finished when the delay has it needed to be shorter...
//
SimpleSleep_Cal SimpleSleep::getCalibration()
{
return 1;
}

void SimpleSleep::deeplyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
{
(void)(calData); // Silence unused warning
deeplyFor(sleepMs);
}

void SimpleSleep::lightlyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
{
(void)(calData); // Silence unused warning
lightlyFor(sleepMs);
}

void SimpleSleep::idleFor(uint32_t sleepMs, SimpleSleep_Cal calData)
{
(void)(calData); // Silence unused warning
idleFor(sleepMs);
}

#elif SS_USE_INT_CAL == 1

__attribute__((weak)) SimpleSleep_Cal SimpleSleep::getCalibration()
{
SimpleSleep_Cal calData;

uint32_t m = millis();
idleFor(15);
m = millis() - m;
calData.adjust15MS = 15 - m;

m = millis();
idleFor(250);
m=millis() - m;
calData.adjust250MS = 250 - m;

return calData;
}

__attribute__((weak)) void SimpleSleep::deeplyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
{
sleepDeeply(sleepMs + ((sleepMs/250)*calData.adjust250MS) + (((sleepMs - ((sleepMs/250)*250))/15)*calData.adjust15MS));
}

__attribute__((weak)) void SimpleSleep::lightlyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
{
lightlyFor(sleepMs + ((sleepMs/250)*calData.adjust250MS) + ((sleepMs - ((sleepMs/250)*250))/15)*calData.adjust15MS);
}

__attribute__((weak)) void SimpleSleep::idleFor(uint32_t sleepMs, SimpleSleep_Cal calData)
{
idleFor(sleepMs + ((sleepMs/250)*calData.adjust250MS) + ((sleepMs - ((sleepMs/250)*250))/15)*calData.adjust15MS);
}

#else

__attribute__((weak)) SimpleSleep_Cal SimpleSleep::getCalibration()
{
uint32_t m = millis();
idleFor(15);
m = millis() - m;
return (float)15 / (float)m;
}

__attribute__((weak)) void SimpleSleep::deeplyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
{
deeplyFor(sleepMs * calData);
}

__attribute__((weak)) void SimpleSleep::lightlyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
{
lightlyFor(sleepMs * calData);
}

__attribute__((weak)) void SimpleSleep::idleFor(uint32_t sleepMs, SimpleSleep_Cal calData)
{
idleFor(sleepMs * calData);
}
#endif
#endif
159 changes: 159 additions & 0 deletions src/avr/avr-timed-sleep.cpp
@@ -0,0 +1,159 @@
/** This file contains implementation of timed sleeping which are common amongst AVR chips.
*
* SimpleSleep methods in this file are typically declared as weak so that variants may
* define their own.
*
* Keep ifdef to a minimum, use variant implementation files if there is any substantial difference.
*/

#if defined (__AVR__)

#include "../SimpleSleep.h"

static void timed_sleep(uint32_t sleepMs, uint8_t mode, uint8_t bod, uint8_t interrupts);

__attribute__((weak)) void SimpleSleep::sleepDeeply(uint32_t sleepMs)
{
// ADC OFF
uint8_t oldADCSRA = ADCSRA;
ADCSRA &= ~(1 << ADEN);

power_declare_all();
power_save_all();
power_all_disable();


// For a timed sleep we may need millis() in order
// to make up sleep to a multiple of 15mS (min sleep period)
// so we will need to leave timer0 powered up (obviously
// during the actual power-down it won't be counting
// we only need it to count between the power-down periods
// of which a timed_sleep might be made up of more than one).
#if power_has_power()
power_timer0_enable();
#endif

// sleep with bod off, interrupts on
timed_sleep(sleepMs, SLEEP_MODE_PWR_DOWN, false, true);


power_restore_all();
ADCSRA = oldADCSRA;
}

__attribute__((weak)) void SimpleSleep::sleepLightly(uint32_t sleepMs)
{
// ADC OFF
uint8_t oldADCSRA = ADCSRA;
ADCSRA &= ~(1 << ADEN);

// sleep with bod off, interrupts on
#ifdef SLEEP_MODE_EXT_STANDBY
timed_sleep(sleepMs, SLEEP_MODE_EXT_STANDBY, false, true);
#else
timed_sleep(sleepMs, SLEEP_MODE_ADC, false, true);
#endif

ADCSRA = oldADCSRA;
}

__attribute__((weak)) void SimpleSleep::sleepIdle(uint32_t sleepMs)
{
timed_sleep(sleepMs, SLEEP_MODE_IDLE, true, true);
}

#if WDT_HAS_INTERRUPT == 1
volatile uint8_t wdt_triggered = 1;

ISR (WDT_vect)
{
wdt_disable();
wdt_triggered = 1;
}

static void timed_sleep(uint32_t sleepMs, uint8_t mode, uint8_t bod, uint8_t interrupts)
{
do
{
// If we are not waiting on the WDT, and there is time still to sleep, setup the WDT (again)
if (wdt_triggered && sleepMs)
{
wdt_triggered = 0;
wdt_enable(wdt_period_for(&sleepMs));
WDTCSR |= (1 << WDIE);
}

set_sleep_mode(mode);
cli();
sleep_enable();
#ifdef sleep_bod_disable
if(!bod)
{
sleep_bod_disable();
}
#else
(void)(bod); // Silence warning
#endif

// Caution, with interrupts disabled the only way you are likely
// to wake up is with a reset
if(interrupts)
{
sei();
}

sleep_cpu();
sleep_disable();
sei();
} while(!wdt_triggered || sleepMs > 0);
}

#else

/** This implements timed sleep without WDT, instead we force into IDLE mode and just
* spin-wait until the time is up. millis() must be available.
*
*/

void timed_sleep(uint32_t sleepMs, uint8_t mode, uint8_t bod, uint8_t interrupts)
{
mode = SLEEP_MODE_IDLE;
uint32_t startSleep = millis();

do
{
// If we are not waiting on the WDT, and there is time still to sleep, setup the WDT (again)
if((millis() - startSleep)>=sleepMs)
{
return;
}


set_sleep_mode(mode);
cli();
sleep_enable();
#ifdef sleep_bod_disable
if(!bod)
{
sleep_bod_disable();
}
#else
(void)(bod); // Silence warning
#endif

// Caution, with interrupts disabled the only way you are likely
// to wake up is with a reset
if(interrupts)
{
sei();
}

sleep_cpu();
sleep_disable();
sei();
} while(sleepMs > 0);

}
#endif

#endif

0 comments on commit d33bb51

Please sign in to comment.