Fix ATtiny85 watchdog register timing with atomic ASM writes; handle WDRF resets with early watchdog disable and recovery delay.
This commit is contained in:
1 parent
28f49e8463
commit
80e42f1952
1 file changed
+65
-28
@@ -1,31 +1,39 @@
|
||||
/*
|
||||
=============================================================================
|
||||
FireFly Morse Throwie
|
||||
- a light controlled (LED as Sensor) morse blinker throwie with ATTiny85
|
||||
=============================================================================
|
||||
FireFly Solar / 日輪蛍 – Nichirin Hotaru / a Solar Morse Throwie
|
||||
|
||||
太陽を宿し、夜にモールスを灯す蛍。
|
||||
Taiyō o yadoshi, yoru ni Mōrusu o tomosu hotaru.
|
||||
A firefly carrying the sun, lighting Morse in the night.
|
||||
=============================================================================
|
||||
Project definitions, sources
|
||||
-----------------------------------------------------------------------------
|
||||
Version: 0.4 - ATTiny85, 1 MHz, BOD fuse disabled
|
||||
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
|
||||
Date : 2026-11-08
|
||||
Version : 0.5 - ATTiny85, 1 MHz, BOD fuse disabled
|
||||
used Flash: 1284 bytes
|
||||
|
||||
Key changes Version 0.4 (goal: lowest possible average current):
|
||||
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
|
||||
Date : 2026-11-10
|
||||
|
||||
|
||||
Key changes Version 0.5 (goal: lowest possible average current):
|
||||
|
||||
- No delay(), no millis(): Timer0 and Timer1 are not needed any more and
|
||||
are switched off in PRR.
|
||||
- ALL timing (Morse units, sensing window, 8 s sleep) is done by the watchdog
|
||||
in interrupt mode while the CPU is in POWER-DOWN. The CPU runs only for a
|
||||
few microseconds per wake-up. LED pins keep their state in power-down.
|
||||
- Darkness sensing no longer polls digitalRead() in a busy loop (that was
|
||||
- Darkness sensing by LED no longer polls digitalRead() in a busy loop (that was
|
||||
~0.3-1 s awake per cycle). The LED junction is charged, then the CPU sleeps
|
||||
in 16 ms steps and checks the pin: bright light exits after the first step.
|
||||
- ADC is disabled BEFORE it is gated off in PRR (order matters).
|
||||
- After a power-on / external reset the sketch waits RECOVER_CYCLES cycles
|
||||
before it blinks again, so a nearly empty supercap can recharge first.
|
||||
Default ~16 minutes, therefore added a debug option.
|
||||
- After a power-on, external, brown-out or watchdog reset the sketch waits
|
||||
RECOVER_CYCLES cycles before blinking, allowing the supercap to recharge.
|
||||
Default ~13 min 20 s (nominal); the debug option bypasses recovery.
|
||||
- Startup code saves MCUSR and disables the watchdog early (.init3), avoiding
|
||||
a watchdog-reset loop before setup() can execute.
|
||||
- Optional: BOD disable during sleep (only useful if BOD fuse is enabled).
|
||||
Not tested yet.
|
||||
But found out that also BOD Fuse for 1.8V shorten unnecessary blinking time,
|
||||
will still work ~1V, meaning ~20 min longer blinking time.
|
||||
- Field Test: After a sunny day in Winter, outdoor -1 ... 8°C, with a payload
|
||||
of 10 signs) it blinks for >> 12h.
|
||||
|
||||
@@ -58,7 +66,9 @@ Inspired by Karl Lunt's FireFly project:
|
||||
'.', ".-.-.-" ',', "--..--" '?', "..--.."
|
||||
'!', "-.-.--" ':', "---..." ';', "-.-.-."
|
||||
'(', "-.--." ')', "-.--.-" '"', ".-..-."
|
||||
'@', ".--.-." '&', ".-..."
|
||||
'@', ".--.-." '&', ".-..." '-', "-....-"
|
||||
'/', "-..-." ''', ".----." '+', ".-.-."
|
||||
'=', "-...-" '_', "..--.-"
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
@@ -77,7 +87,7 @@ Inspired by Karl Lunt's FireFly project:
|
||||
// ---------------------------------------------------------------------------
|
||||
// DEBUG_TEST 1 = bench testing: no reset-recovery wait, a blink attempt every
|
||||
// 8 s, and a short flash when the sketch thinks it is BRIGHT.
|
||||
// SET TO 0 FOR THE FIELD (48 s cycle, 16 min recovery wait after a reset).
|
||||
// SET TO 0 FOR THE FIELD (40 s idle sleep, ~13m20s reset recovery).
|
||||
#define DEBUG_TEST 0
|
||||
|
||||
#ifndef cbi
|
||||
@@ -104,7 +114,7 @@ Inspired by Karl Lunt's FireFly project:
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sleep interval between two sensing/blink attempts: sleepCycles x 8 s.
|
||||
// 6 x 8 s = 48 s. Goal ~ 1 blink/min.
|
||||
// 5 x 8 s = 40 s. Goal ~ 1 blink/min.
|
||||
#if DEBUG_TEST
|
||||
const uint8_t sleepCycles = 1;
|
||||
#else
|
||||
@@ -116,16 +126,16 @@ const uint8_t sleepCycles = 5;
|
||||
// After charging the LED junction the CPU sleeps step by step. If the pin
|
||||
// reads LOW within the window the LED discharged quickly = bright = no blink.
|
||||
// Still HIGH after the full window = dark = blink.
|
||||
// Longer window = triggers in brighter conditions (like the old threshold).
|
||||
// Longer window = triggers in darker conditions
|
||||
// The window costs almost no energy (CPU sleeps), so it is only a threshold.
|
||||
// Calibrate at real dusk. Old threshold 17000 loops: measure the old
|
||||
// sensDarkness() time with micros() at your dusk level and divide it by 16 ms.
|
||||
const uint8_t darkSteps = 40; // ~640 ms
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// After a power-on / external reset (e.g. supercap was empty, DC/DC restart)
|
||||
// do not blink for this many cycles (x ~48 s) so the supercap can recharge.
|
||||
// 20 -> ~16 min. Set to 0 while testing/uploading (upload = external reset).
|
||||
// After a power-on / brown-out / external / watchdog reset, do not blink for
|
||||
// this many cycles (each has 40 s of sleep) so the supercap can recharge.
|
||||
// 20 -> ~13 min 20 s nominal. Set to 0 for bench testing/uploading.
|
||||
#if DEBUG_TEST
|
||||
#define RECOVER_CYCLES 0
|
||||
#else
|
||||
@@ -166,6 +176,19 @@ volatile bool f_wdt = false;
|
||||
// Remaining cycles without blinking after a reset.
|
||||
static uint8_t recoverCycles = 0;
|
||||
|
||||
// Preserve the reset flags before Arduino startup and stop a watchdog left
|
||||
// running by a previous reset. AVR-libc's recommended .init3 pattern.
|
||||
// .noinit is not cleared by C runtime initialization.
|
||||
static uint8_t resetCauseEarly __attribute__((section(".noinit")));
|
||||
|
||||
void earlyWatchdogInit(void) __attribute__((used, naked, section(".init3")));
|
||||
void earlyWatchdogInit(void)
|
||||
{
|
||||
resetCauseEarly = MCUSR;
|
||||
MCUSR = 0; // Clear WDRF before disabling WDT.
|
||||
wdt_disable(); // Prevent repeated ~15 ms watchdog resets.
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Forward declarations.
|
||||
static void sleepWdt(uint8_t ii);
|
||||
@@ -182,10 +205,10 @@ void dah(int LED_P);
|
||||
// ===========================================================================
|
||||
void setup()
|
||||
{
|
||||
// Remember why we restarted, then clear the flags.
|
||||
uint8_t resetCause = MCUSR;
|
||||
MCUSR = 0;
|
||||
if (resetCause & ((1 << PORF) | (1 << BORF) | (1 << EXTRF))) {
|
||||
// Reset cause was captured before C/Arduino initialization in .init3.
|
||||
// Include WDRF: a watchdog reset also needs supercap recovery time.
|
||||
if (resetCauseEarly & ((1 << PORF) | (1 << BORF) |
|
||||
(1 << EXTRF) | (1 << WDRF))) {
|
||||
recoverCycles = RECOVER_CYCLES;
|
||||
}
|
||||
|
||||
@@ -280,11 +303,19 @@ static void sleepWdt(uint8_t ii)
|
||||
f_wdt = false;
|
||||
cli();
|
||||
wdt_reset();
|
||||
MCUSR &= ~(1 << WDRF);
|
||||
|
||||
// Timed sequence: the two writes must follow each other within 4 cycles.
|
||||
WDTCR = (1 << WDCE) | (1 << WDE);
|
||||
WDTCR = cfg;
|
||||
// WDCE/WDE unlock and configuration: two consecutive OUT instructions.
|
||||
// The second write is guaranteed to occur within the required four cycles.
|
||||
const uint8_t unlock = _BV(WDCE) | _BV(WDE);
|
||||
__asm__ __volatile__(
|
||||
"out %[wdt], %[unlock] \n\t"
|
||||
"out %[wdt], %[config] \n\t"
|
||||
:
|
||||
: [wdt] "I" (_SFR_IO_ADDR(WDTCR)),
|
||||
[unlock] "r" (unlock),
|
||||
[config] "r" (cfg)
|
||||
: "memory"
|
||||
);
|
||||
|
||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||
|
||||
@@ -438,7 +469,7 @@ void morse(int LED_N, int LED_P)
|
||||
case '9': dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // ----.
|
||||
case '0': dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // -----
|
||||
|
||||
// ----- Punctuation -----
|
||||
// ----- Punctuation & Specials -----
|
||||
case '.': dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // .-.-.-
|
||||
case ',': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // --..--
|
||||
case '?': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ..--..
|
||||
@@ -450,6 +481,12 @@ void morse(int LED_N, int LED_P)
|
||||
case '"': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .-..-.
|
||||
case '@': dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .--.-.
|
||||
case '&': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // .-...
|
||||
case '-': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -....-
|
||||
case '/': dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // -..-.
|
||||
case '\'': dit(LED_P); dah(LED_P); dah(LED_P);dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .----.
|
||||
case '+': dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .-.-.
|
||||
case '=': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -...-
|
||||
case '_': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // ..--.-
|
||||
|
||||
// Space handling:
|
||||
// Previous symbol already ended with 1 unit OFF and then received the
|
||||
|
||||
Reference in new issue
Block a user