Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80e42f1952 |
@@ -1,31 +1,39 @@
|
|||||||
/*
|
/*
|
||||||
=============================================================================
|
=============================================================================
|
||||||
FireFly Morse Throwie
|
FireFly Solar / 日輪蛍 – Nichirin Hotaru / a Solar Morse Throwie
|
||||||
- a light controlled (LED as Sensor) morse blinker throwie with ATTiny85
|
|
||||||
|
太陽を宿し、夜にモールスを灯す蛍。
|
||||||
|
Taiyō o yadoshi, yoru ni Mōrusu o tomosu hotaru.
|
||||||
|
A firefly carrying the sun, lighting Morse in the night.
|
||||||
=============================================================================
|
=============================================================================
|
||||||
|
|
||||||
Project definitions, sources
|
Project definitions, sources
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
Version: 0.4 - ATTiny85, 1 MHz, BOD fuse disabled
|
Version : 0.5 - ATTiny85, 1 MHz, BOD fuse disabled
|
||||||
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
|
used Flash: 1284 bytes
|
||||||
Date : 2026-11-08
|
|
||||||
|
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
|
||||||
Key changes Version 0.4 (goal: lowest possible average current):
|
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
|
- No delay(), no millis(): Timer0 and Timer1 are not needed any more and
|
||||||
are switched off in PRR.
|
are switched off in PRR.
|
||||||
- ALL timing (Morse units, sensing window, 8 s sleep) is done by the watchdog
|
- 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
|
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.
|
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
|
~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.
|
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).
|
- ADC is disabled BEFORE it is gated off in PRR (order matters).
|
||||||
- After a power-on / external reset the sketch waits RECOVER_CYCLES cycles
|
- After a power-on, external, brown-out or watchdog reset the sketch waits
|
||||||
before it blinks again, so a nearly empty supercap can recharge first.
|
RECOVER_CYCLES cycles before blinking, allowing the supercap to recharge.
|
||||||
Default ~16 minutes, therefore added a debug option.
|
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).
|
- 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
|
- Field Test: After a sunny day in Winter, outdoor -1 ... 8°C, with a payload
|
||||||
of 10 signs) it blinks for >> 12h.
|
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
|
// 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.
|
// 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
|
#define DEBUG_TEST 0
|
||||||
|
|
||||||
#ifndef cbi
|
#ifndef cbi
|
||||||
@@ -104,7 +114,7 @@ Inspired by Karl Lunt's FireFly project:
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Sleep interval between two sensing/blink attempts: sleepCycles x 8 s.
|
// 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
|
#if DEBUG_TEST
|
||||||
const uint8_t sleepCycles = 1;
|
const uint8_t sleepCycles = 1;
|
||||||
#else
|
#else
|
||||||
@@ -116,16 +126,16 @@ const uint8_t sleepCycles = 5;
|
|||||||
// After charging the LED junction the CPU sleeps step by step. If the pin
|
// 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.
|
// reads LOW within the window the LED discharged quickly = bright = no blink.
|
||||||
// Still HIGH after the full window = dark = 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.
|
// 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
|
// 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.
|
// sensDarkness() time with micros() at your dusk level and divide it by 16 ms.
|
||||||
const uint8_t darkSteps = 40; // ~640 ms
|
const uint8_t darkSteps = 40; // ~640 ms
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// After a power-on / external reset (e.g. supercap was empty, DC/DC restart)
|
// After a power-on / brown-out / external / watchdog reset, do not blink for
|
||||||
// do not blink for this many cycles (x ~48 s) so the supercap can recharge.
|
// this many cycles (each has 40 s of sleep) so the supercap can recharge.
|
||||||
// 20 -> ~16 min. Set to 0 while testing/uploading (upload = external reset).
|
// 20 -> ~13 min 20 s nominal. Set to 0 for bench testing/uploading.
|
||||||
#if DEBUG_TEST
|
#if DEBUG_TEST
|
||||||
#define RECOVER_CYCLES 0
|
#define RECOVER_CYCLES 0
|
||||||
#else
|
#else
|
||||||
@@ -166,6 +176,19 @@ volatile bool f_wdt = false;
|
|||||||
// Remaining cycles without blinking after a reset.
|
// Remaining cycles without blinking after a reset.
|
||||||
static uint8_t recoverCycles = 0;
|
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.
|
// Forward declarations.
|
||||||
static void sleepWdt(uint8_t ii);
|
static void sleepWdt(uint8_t ii);
|
||||||
@@ -182,10 +205,10 @@ void dah(int LED_P);
|
|||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
// Remember why we restarted, then clear the flags.
|
// Reset cause was captured before C/Arduino initialization in .init3.
|
||||||
uint8_t resetCause = MCUSR;
|
// Include WDRF: a watchdog reset also needs supercap recovery time.
|
||||||
MCUSR = 0;
|
if (resetCauseEarly & ((1 << PORF) | (1 << BORF) |
|
||||||
if (resetCause & ((1 << PORF) | (1 << BORF) | (1 << EXTRF))) {
|
(1 << EXTRF) | (1 << WDRF))) {
|
||||||
recoverCycles = RECOVER_CYCLES;
|
recoverCycles = RECOVER_CYCLES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,11 +303,19 @@ static void sleepWdt(uint8_t ii)
|
|||||||
f_wdt = false;
|
f_wdt = false;
|
||||||
cli();
|
cli();
|
||||||
wdt_reset();
|
wdt_reset();
|
||||||
MCUSR &= ~(1 << WDRF);
|
|
||||||
|
|
||||||
// Timed sequence: the two writes must follow each other within 4 cycles.
|
// WDCE/WDE unlock and configuration: two consecutive OUT instructions.
|
||||||
WDTCR = (1 << WDCE) | (1 << WDE);
|
// The second write is guaranteed to occur within the required four cycles.
|
||||||
WDTCR = cfg;
|
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);
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||||
|
|
||||||
@@ -438,18 +469,24 @@ 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 '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; // -----
|
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 '.': 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 ',': 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; // ..--..
|
case '?': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ..--..
|
||||||
case '!': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // -.-.--
|
case '!': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // -.-.--
|
||||||
case ':': dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ---...
|
case ':': dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ---...
|
||||||
case ';': dah(LED_P); 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); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // -.-.-.
|
||||||
case '(': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // -.--.
|
case '(': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // -.--.
|
||||||
case ')': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -.--.-
|
case ')': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -.--.-
|
||||||
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); 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); 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 '&': 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:
|
// Space handling:
|
||||||
// Previous symbol already ended with 1 unit OFF and then received the
|
// Previous symbol already ended with 1 unit OFF and then received the
|
||||||
@@ -472,4 +509,4 @@ void morse(int LED_N, int LED_P)
|
|||||||
|
|
||||||
digitalWrite(LED_P, LOW);
|
digitalWrite(LED_P, LOW);
|
||||||
digitalWrite(LED_N, LOW);
|
digitalWrite(LED_N, LOW);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user