Skip to content

Commit d33bb51

Browse files
committed
Refactor so that the WDT can be optimised out if it's not required.
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.
1 parent 640eb14 commit d33bb51

10 files changed

+385
-348
lines changed

src/SimpleSleep.h

+8-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
* For AVR, typically implemented as Power Down
4343
*/
4444

45-
inline void deeply() { sleepDeeply(0); }
45+
inline void deeply() { sleepDeeply(); }
4646

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

62-
inline void lightly() { sleepLightly(0); }
62+
inline void lightly() { sleepLightly(); }
6363

6464
/** Sleep lightly for a given time, allow many interrupts, adc off, timers generally off
6565
*
@@ -77,7 +77,7 @@
7777
* For AVR, typically implemented as Idle
7878
*/
7979

80-
inline void idle() { sleepIdle(0); }
80+
inline void idle() { sleepIdle(); }
8181

8282
/** Wait patiently for a given time.
8383
*
@@ -127,6 +127,11 @@
127127
protected:
128128

129129
void sleepForever();
130+
131+
void sleepDeeply();
132+
void sleepLightly();
133+
void sleepIdle();
134+
130135
void sleepDeeply(uint32_t sleepMs);
131136
void sleepLightly(uint32_t sleepMs);
132137
void sleepIdle(uint32_t sleepMs);

src/avr/ATMegaX8.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include "../SimpleSleep.h"
2+
13
#if defined(SS_ATMegax8)
24

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

src/avr/ATTiny13.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
#include "../SimpleSleep.h"
2+
13
#if defined(SS_ATTiny13)
2-
3-
/* There is nothing to be done here, the standard methods defined in avr.cpp will work for us. */
44

5+
/* There is nothing to be done here, the standard methods defined in avr.cpp will work for us. */
6+
57
#endif

src/avr/ATTinyX4.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include "../SimpleSleep.h"
2+
13
#if defined(SS_ATTinyX4)
24

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

src/avr/ATTinyX5.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include "../SimpleSleep.h"
2+
13
#if defined(SS_ATTinyX5)
24

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

src/avr/avr-calibrated-sleep.cpp

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/** This file contains implementation of calibration which are common amongst AVR chips.
2+
*
3+
* Keep ifdef to a minimum, use variant implementation files if there is any substantial difference.
4+
*/
5+
6+
#if defined (__AVR__)
7+
8+
#include "../SimpleSleep.h"
9+
10+
#ifdef NO_MILLIS
11+
// Some of the Arduino cores, particularly @sleemanj's ATTinyCore fork,
12+
// allow disabling millis, in which case we can not do any
13+
//
14+
// @TODO Maybe there is some way to do it using a delay() (which will
15+
// still work without millis()), like, start the WDT then start a delay()
16+
// if the WDT triggers before the delay() time then the WDT needed to be longer
17+
// if it hasn't finished when the delay has it needed to be shorter...
18+
//
19+
SimpleSleep_Cal SimpleSleep::getCalibration()
20+
{
21+
return 1;
22+
}
23+
24+
void SimpleSleep::deeplyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
25+
{
26+
(void)(calData); // Silence unused warning
27+
deeplyFor(sleepMs);
28+
}
29+
30+
void SimpleSleep::lightlyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
31+
{
32+
(void)(calData); // Silence unused warning
33+
lightlyFor(sleepMs);
34+
}
35+
36+
void SimpleSleep::idleFor(uint32_t sleepMs, SimpleSleep_Cal calData)
37+
{
38+
(void)(calData); // Silence unused warning
39+
idleFor(sleepMs);
40+
}
41+
42+
#elif SS_USE_INT_CAL == 1
43+
44+
__attribute__((weak)) SimpleSleep_Cal SimpleSleep::getCalibration()
45+
{
46+
SimpleSleep_Cal calData;
47+
48+
uint32_t m = millis();
49+
idleFor(15);
50+
m = millis() - m;
51+
calData.adjust15MS = 15 - m;
52+
53+
m = millis();
54+
idleFor(250);
55+
m=millis() - m;
56+
calData.adjust250MS = 250 - m;
57+
58+
return calData;
59+
}
60+
61+
__attribute__((weak)) void SimpleSleep::deeplyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
62+
{
63+
sleepDeeply(sleepMs + ((sleepMs/250)*calData.adjust250MS) + (((sleepMs - ((sleepMs/250)*250))/15)*calData.adjust15MS));
64+
}
65+
66+
__attribute__((weak)) void SimpleSleep::lightlyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
67+
{
68+
lightlyFor(sleepMs + ((sleepMs/250)*calData.adjust250MS) + ((sleepMs - ((sleepMs/250)*250))/15)*calData.adjust15MS);
69+
}
70+
71+
__attribute__((weak)) void SimpleSleep::idleFor(uint32_t sleepMs, SimpleSleep_Cal calData)
72+
{
73+
idleFor(sleepMs + ((sleepMs/250)*calData.adjust250MS) + ((sleepMs - ((sleepMs/250)*250))/15)*calData.adjust15MS);
74+
}
75+
76+
#else
77+
78+
__attribute__((weak)) SimpleSleep_Cal SimpleSleep::getCalibration()
79+
{
80+
uint32_t m = millis();
81+
idleFor(15);
82+
m = millis() - m;
83+
return (float)15 / (float)m;
84+
}
85+
86+
__attribute__((weak)) void SimpleSleep::deeplyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
87+
{
88+
deeplyFor(sleepMs * calData);
89+
}
90+
91+
__attribute__((weak)) void SimpleSleep::lightlyFor(uint32_t sleepMs, SimpleSleep_Cal calData)
92+
{
93+
lightlyFor(sleepMs * calData);
94+
}
95+
96+
__attribute__((weak)) void SimpleSleep::idleFor(uint32_t sleepMs, SimpleSleep_Cal calData)
97+
{
98+
idleFor(sleepMs * calData);
99+
}
100+
#endif
101+
#endif

src/avr/avr-timed-sleep.cpp

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/** This file contains implementation of timed sleeping which are common amongst AVR chips.
2+
*
3+
* SimpleSleep methods in this file are typically declared as weak so that variants may
4+
* define their own.
5+
*
6+
* Keep ifdef to a minimum, use variant implementation files if there is any substantial difference.
7+
*/
8+
9+
#if defined (__AVR__)
10+
11+
#include "../SimpleSleep.h"
12+
13+
static void timed_sleep(uint32_t sleepMs, uint8_t mode, uint8_t bod, uint8_t interrupts);
14+
15+
__attribute__((weak)) void SimpleSleep::sleepDeeply(uint32_t sleepMs)
16+
{
17+
// ADC OFF
18+
uint8_t oldADCSRA = ADCSRA;
19+
ADCSRA &= ~(1 << ADEN);
20+
21+
power_declare_all();
22+
power_save_all();
23+
power_all_disable();
24+
25+
26+
// For a timed sleep we may need millis() in order
27+
// to make up sleep to a multiple of 15mS (min sleep period)
28+
// so we will need to leave timer0 powered up (obviously
29+
// during the actual power-down it won't be counting
30+
// we only need it to count between the power-down periods
31+
// of which a timed_sleep might be made up of more than one).
32+
#if power_has_power()
33+
power_timer0_enable();
34+
#endif
35+
36+
// sleep with bod off, interrupts on
37+
timed_sleep(sleepMs, SLEEP_MODE_PWR_DOWN, false, true);
38+
39+
40+
power_restore_all();
41+
ADCSRA = oldADCSRA;
42+
}
43+
44+
__attribute__((weak)) void SimpleSleep::sleepLightly(uint32_t sleepMs)
45+
{
46+
// ADC OFF
47+
uint8_t oldADCSRA = ADCSRA;
48+
ADCSRA &= ~(1 << ADEN);
49+
50+
// sleep with bod off, interrupts on
51+
#ifdef SLEEP_MODE_EXT_STANDBY
52+
timed_sleep(sleepMs, SLEEP_MODE_EXT_STANDBY, false, true);
53+
#else
54+
timed_sleep(sleepMs, SLEEP_MODE_ADC, false, true);
55+
#endif
56+
57+
ADCSRA = oldADCSRA;
58+
}
59+
60+
__attribute__((weak)) void SimpleSleep::sleepIdle(uint32_t sleepMs)
61+
{
62+
timed_sleep(sleepMs, SLEEP_MODE_IDLE, true, true);
63+
}
64+
65+
#if WDT_HAS_INTERRUPT == 1
66+
volatile uint8_t wdt_triggered = 1;
67+
68+
ISR (WDT_vect)
69+
{
70+
wdt_disable();
71+
wdt_triggered = 1;
72+
}
73+
74+
static void timed_sleep(uint32_t sleepMs, uint8_t mode, uint8_t bod, uint8_t interrupts)
75+
{
76+
do
77+
{
78+
// If we are not waiting on the WDT, and there is time still to sleep, setup the WDT (again)
79+
if (wdt_triggered && sleepMs)
80+
{
81+
wdt_triggered = 0;
82+
wdt_enable(wdt_period_for(&sleepMs));
83+
WDTCSR |= (1 << WDIE);
84+
}
85+
86+
set_sleep_mode(mode);
87+
cli();
88+
sleep_enable();
89+
#ifdef sleep_bod_disable
90+
if(!bod)
91+
{
92+
sleep_bod_disable();
93+
}
94+
#else
95+
(void)(bod); // Silence warning
96+
#endif
97+
98+
// Caution, with interrupts disabled the only way you are likely
99+
// to wake up is with a reset
100+
if(interrupts)
101+
{
102+
sei();
103+
}
104+
105+
sleep_cpu();
106+
sleep_disable();
107+
sei();
108+
} while(!wdt_triggered || sleepMs > 0);
109+
}
110+
111+
#else
112+
113+
/** This implements timed sleep without WDT, instead we force into IDLE mode and just
114+
* spin-wait until the time is up. millis() must be available.
115+
*
116+
*/
117+
118+
void timed_sleep(uint32_t sleepMs, uint8_t mode, uint8_t bod, uint8_t interrupts)
119+
{
120+
mode = SLEEP_MODE_IDLE;
121+
uint32_t startSleep = millis();
122+
123+
do
124+
{
125+
// If we are not waiting on the WDT, and there is time still to sleep, setup the WDT (again)
126+
if((millis() - startSleep)>=sleepMs)
127+
{
128+
return;
129+
}
130+
131+
132+
set_sleep_mode(mode);
133+
cli();
134+
sleep_enable();
135+
#ifdef sleep_bod_disable
136+
if(!bod)
137+
{
138+
sleep_bod_disable();
139+
}
140+
#else
141+
(void)(bod); // Silence warning
142+
#endif
143+
144+
// Caution, with interrupts disabled the only way you are likely
145+
// to wake up is with a reset
146+
if(interrupts)
147+
{
148+
sei();
149+
}
150+
151+
sleep_cpu();
152+
sleep_disable();
153+
sei();
154+
} while(sleepMs > 0);
155+
156+
}
157+
#endif
158+
159+
#endif

0 commit comments

Comments
 (0)