3 Commits
10 changed files with 343 additions and 162 deletions

No files matched your search

+48 -2
View File
@@ -14,7 +14,16 @@ FireFly Solar is a small outdoor LED blinker inspired by the idea of an LED thro
During the day, the solar cell charges the energy storage.
At night, the circuit wakes up and sends a simple Morse-style light signal.
The project is partly a small garden object, partly a low-power electronics experiment, and partly a design exercise for future DIY kits.
## About this Project
FireFly Solar is my **technology playground**. It is a small garden object, but its real purpose is to learn by building something complete. With it I learn about:
- **Circuit design at the edge**: in this case, as low power as possible
- **PCB design**
- **Weatherproof casing**
- **ATtiny programming**
Possible next technology step: everything about DIY SMD design and soldering.
## Project Goals
@@ -24,6 +33,40 @@ The project is partly a small garden object, partly a low-power electronics expe
- Test mechanical concepts for outdoor 3D-printed parts
- Learn what is needed to turn a small electronics project into a possible DIY kit
## Software
The firmware runs on an **ATtiny85** at 1 MHz (internal clock, ATTinyCore: `clock=1internal`, `millis=disabled`). The LED itself is used as the light sensor. When it is dark, the firefly sends a short Morse text ("TGO 26") about once a minute.
### Current Firmware version: v0.4 (2026-11-08)
Goal: lowest possible average current.
Changes vs v0.3:
- No `delay()` / `millis()`; Timer0 and Timer1 are gated off in PRR.
- The watchdog (interrupt mode) is the only timebase: Morse units (~96 ms), the sensing window and the 8 s sleeps. The CPU sleeps in power-down in between.
- Darkness sensing: the CPU sleeps in 16 ms steps while the LED junction discharges, instead of a busy `digitalRead()` loop. Bright light exits after the first step. `darknessThreshold` is replaced by `darkSteps`.
- The ADC is disabled before it is gated off in PRR; pins are set up before PRR.
- After a power-on or external reset, no blinking for `RECOVER_CYCLES` (about 16 min), so the supercap can recharge.
- `DEBUG_TEST` switch for bench testing: 8 s cycle, no recovery wait, short flash when the sketch decides "bright". Set it to 0 for the field.
- Optional `BOD_FUSE_ENABLED` (BOD off during sleep); unused with the BOD fuse off.
Tested: compiles with ATTinyCore and runs on hardware. Current draw is not yet measured.
## PCB
- Finished: a batch of 10 is assembled, tested and working.
- Latest adjustment: fine-tuned the resistors for the LED, and the 3.9 V Zener diode is confirmed safe avoid overcharging 4V supercap.
- Next version: the PCB should be a little shorter. The wiring space is a bit too tight now.
## Casing
Works basically as intended. Lessons for the next version:
- Better locking of the solar modules.
- The rope structure works, but building it is too complicated and takes a lot of nerves.
- Using material tension to secure the modules via rope was basically a good idea.
## Naming
The English project name remains:
@@ -39,9 +82,12 @@ The name roughly means **sun-disc firefly**. It keeps the solar theme, adds a sl
---
## Contributing
PRs and issues welcome. Keep it simple.
Photos of your build you send me go in `Documentation/gallery/` with a short caption and license notice.
---
## Default license**: [CC-BY-NC-4.0](https://creativecommons.org/licenses/by-nc/4.0/)
## License
Default license: [CC-BY-NC-4.0](https://creativecommons.org/licenses/by-nc/4.0/)
@@ -1,24 +1,46 @@
/*
=============================================================================
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.3 - ATTiny85, 1 MHz, BOD fuse disabled
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
Date : 2026-06-14
Version : 0.5 - ATTiny85, 1 MHz, BOD fuse disabled
used Flash: 1284 bytes
Key changes Version 0.3:
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
Date : 2026-11-10
- Removed unnecessary ADC enable/disable from loop()
- Disabled ADC and analog comparator in setup()
- Added INPUT_PULLUP for unused Arduino pins 0, 1, 2 will save power
- Added safer watchdog setup with interrupt protection to save run near brownout
- Added wdt_reset() before sleep for cleaner 8 s timing
- Added lowercase-to-uppercase handling
- Unsupported characters are now ignored without adding fake timing gaps
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 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, 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).
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.
Timing note: the watchdog oscillator is not precise (changes with Vcc and
temperature). All Morse timing comes from the same clock, so dit/dah/gap
ratios stay correct; only the absolute speed drifts a little.
The HW actual allowes 5V and 3.3V, standard is 3.3V. So don't expec exact timing.
-----------------------------------------------------------------------------
@@ -44,7 +66,9 @@ Inspired by Karl Lunt's FireFly project:
'.', ".-.-.-" ',', "--..--" '?', "..--.."
'!', "-.-.--" ':', "---..." ';', "-.-.-."
'(', "-.--." ')', "-.--.-" '"', ".-..-."
'@', ".--.-." '&', ".-..."
'@', ".--.-." '&', ".-..." '-', "-....-"
'/', "-..-." ''', ".----." '+', ".-.-."
'=', "-...-" '_', "..--.-"
-----------------------------------------------------------------------------
@@ -60,6 +84,12 @@ Inspired by Karl Lunt's FireFly project:
#include <avr/interrupt.h>
#include <string.h>
// ---------------------------------------------------------------------------
// 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 (40 s idle sleep, ~13m20s reset recovery).
#define DEBUG_TEST 0
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
@@ -68,16 +98,55 @@ Inspired by Karl Lunt's FireFly project:
#endif
// ---------------------------------------------------------------------------
// Timing: unit length in ms.
// At 1 MHz, delay() is accurate when F_CPU=1000000L is set in boards.txt.
// 100 ms gives readable optical Morse; raise to 150 ms if readability is poor.
#define unitLength 100
// Watchdog timeout codes (nominal, at ~5 V; real values vary with Vcc/temp):
// actual HW uses 3.3 (standard) or 5V (for blue LED experimental setup)
// so don't expect exact timing)
// 0=16ms 1=32ms 2=64ms 3=128ms 4=250ms 5=500ms
// 6=1s 7=2s 8=4s 9=8s
//
// One Morse unit = UNIT_WDT_A + UNIT_WDT_B -> 64 ms + 32 ms = ~96 ms
// A=3, B=0 -> ~144 ms (easier to read, more LED time)
// A=3, B=0xFF -> ~128 ms
// A=2, B=0xFF -> ~64 ms (fast, less LED time, harder to read by eye)
// Use 0xFF for B to skip the second sleep.
#define UNIT_WDT_A 2
#define UNIT_WDT_B 1
// ---------------------------------------------------------------------------
// Sleep interval.
// Watchdog setup uses 8 s. For final use, change sleepCycles from 4 to 6
// for about 32s - 48s between sensing/blinking activations. Goal ~ 1 blink/min
const uint8_t sleepCycles = 6;
// Sleep interval between two sensing/blink attempts: sleepCycles x 8 s.
// 5 x 8 s = 40 s. Goal ~ 1 blink/min.
#if DEBUG_TEST
const uint8_t sleepCycles = 1;
#else
const uint8_t sleepCycles = 5;
#endif
// ---------------------------------------------------------------------------
// Darkness window in 16 ms steps (nominal).
// 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 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 / 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
#define RECOVER_CYCLES 20
#endif
// ---------------------------------------------------------------------------
// Set to 1 only if the BOD fuse is enabled (e.g. hfuse 0xDE = 1.8 V).
// Then BOD is switched off during power-down to save its ~20 uA.
// With BOD fuse disabled this has no effect, leave it 0.
#define BOD_FUSE_ENABLED 0
// ---------------------------------------------------------------------------
// LED pin definitions (N-side = cathode, P-side = anode for sensing/driving).
@@ -95,28 +164,38 @@ const uint8_t sleepCycles = 6;
// ATTiny85 has 512 bytes SRAM; this keeps the Morse text in flash.
// Only uppercase letters, digits, spaces, and the special chars in the switch
// below are emitted. Lowercase letters are converted to uppercase.
// Test setup: 20 x "0" because 0 = "-----" gives maximum LED ON time for test.
// shorter text will better, more text will cost more power.
const char morseText[] PROGMEM = "TGO 26";
// Ruler: 0....0....1...1....2
// Ruler: 0....5....0...5....0
// ---------------------------------------------------------------------------
// Darkness threshold.
// Higher value = triggers in brighter conditions.
// Best calibrated at actual dusk/dawn with the chosen LED type.
const unsigned int darknessThreshold = 17000;
// Shorter text = less LED time = less energy.
const char morseText[] PROGMEM = "TOGO-LAB.IO FIREFLY";
// Ruler: 0....0....1....1....2
// Ruler: 01...5....0....5....0
// ---------------------------------------------------------------------------
// Watchdog interrupt flag — volatile because it is written in an ISR.
volatile bool f_wdt = true;
// Watchdog interrupt flag - volatile because it is written in an ISR.
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.
void setup_watchdog(uint8_t ii);
void system_sleep();
static void sleepWdt(uint8_t ii);
static void waitUnits(uint8_t n);
void configureUnusedPins();
void releaseLedPins(int LED_N, int LED_P);
unsigned int sensDarkness(int LED_N, int LED_P);
bool isDark(int LED_N, int LED_P);
void morse(int LED_N, int LED_P);
void dit(int LED_P);
void dah(int LED_P);
@@ -126,22 +205,28 @@ void dah(int LED_P);
// ===========================================================================
void setup()
{
// Disable unused peripherals immediately to save power.
// PRTIM1, PRUSI: unused, safe to gate off.
// PRADC: ADC is not used; LED sensing is done with digitalRead().
// PRTIM0: must stay ON while awake because delay() depends on Timer0.
PRR = (1 << PRTIM1) | (1 << PRUSI) | (1 << PRADC);
// ADC off. It is not needed for digitalRead()-based LED sensing.
cbi(ADCSRA, ADEN);
// Analog comparator off. Saves a little sleep current.
ACSR |= (1 << ACD);
// 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;
}
// Pins first (while the Arduino core can still reach all timer registers
// for its turnOffPWM() handling in digitalWrite()).
configureUnusedPins();
releaseLedPins(LED1_N_SIDE, LED1_P_SIDE);
setup_watchdog(9); // 8-second watchdog interval.
// ADC off FIRST, then gate its clock. With PRADC set, ADC register
// writes are ignored, so the order matters.
cbi(ADCSRA, ADEN);
// Analog comparator off.
ACSR |= (1 << ACD);
// Power reduction: ADC, USI, Timer0, Timer1 all off.
// Nothing in this sketch uses them: no delay(), no millis(), no analogWrite().
PRR = (1 << PRADC) | (1 << PRUSI) | (1 << PRTIM0) | (1 << PRTIM1);
}
// ===========================================================================
@@ -149,22 +234,29 @@ void setup()
// ===========================================================================
void loop()
{
if (f_wdt) {
f_wdt = false;
if (sensDarkness(LED1_N_SIDE, LED1_P_SIDE) > darknessThreshold) {
morse(LED1_N_SIDE, LED1_P_SIDE);
}
// Return LED pins to high-Z before sleeping.
// Internal pullups stay disabled for the LED pins.
releaseLedPins(LED1_N_SIDE, LED1_P_SIDE);
if (recoverCycles > 0) {
recoverCycles--;
} else if (isDark(LED1_N_SIDE, LED1_P_SIDE)) {
morse(LED1_N_SIDE, LED1_P_SIDE);
}
#if DEBUG_TEST
else {
// Sketch decided "bright": one short ~128 ms flash so you can see it.
pinMode(LED1_N_SIDE, OUTPUT);
pinMode(LED1_P_SIDE, OUTPUT);
digitalWrite(LED1_N_SIDE, LOW);
digitalWrite(LED1_P_SIDE, HIGH);
sleepWdt(3);
digitalWrite(LED1_P_SIDE, LOW);
}
#endif
// Return LED pins to high-Z before sleeping.
// Internal pullups stay disabled for the LED pins.
releaseLedPins(LED1_N_SIDE, LED1_P_SIDE);
// Test setup: 1 x 8 s sleep.
// Final setup: set sleepCycles = 4 for about 32 s.
for (uint8_t i = 0; i < sleepCycles; i++) {
system_sleep();
sleepWdt(9); // 8 s each
}
}
@@ -188,87 +280,110 @@ void releaseLedPins(int LED_N, int LED_P)
}
// ===========================================================================
// Sleep helpers
// Sleep helpers (watchdog = only timebase)
// ===========================================================================
void system_sleep()
ISR(WDT_vect)
{
// Reset watchdog counter so each sleep cycle starts with a fresh interval.
wdt_reset();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// Atomic sleep entry pattern.
// After sei(), AVR executes the next instruction before servicing interrupts,
// so sleep_cpu() is not skipped by a just-pending interrupt.
cli();
sleep_enable();
sei();
sleep_cpu();
// WDT interrupt wakes the CPU here.
sleep_disable();
f_wdt = true;
}
// Watchdog setup - ii selects timeout:
// 0=16ms 1=32ms 2=64ms 3=128ms 4=250ms 5=500ms
// 6=1s 7=2s 8=4s 9=8s
void setup_watchdog(uint8_t ii)
// Sleep in POWER-DOWN until the watchdog interrupt fires after timeout code ii
// (0=16 ms ... 9=8 s). Reconfigures the watchdog on every call, so the full
// interval always starts fresh.
static void sleepWdt(uint8_t ii)
{
if (ii > 9) ii = 9;
uint8_t bb = ii & 7;
if (ii > 7) bb |= (1 << WDP3);
uint8_t oldSREG = SREG;
// Interrupt-only mode (WDE = 0). WDIF written as 1 clears a stale flag.
const uint8_t cfg = bb | (1 << WDIE) | (1 << WDIF);
f_wdt = false;
cli();
wdt_reset();
MCUSR &= ~(1 << WDRF);
// Timed sequence: enable configuration change, then set interrupt-only WDT.
WDTCR |= (1 << WDCE) | (1 << WDE);
WDTCR = bb | (1 << WDIE);
// 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"
);
SREG = oldSREG;
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// Atomic sleep entry: after sei() the next instruction (sleep_cpu) still
// executes before any interrupt is serviced, so the wake-up cannot be missed.
// Loop again if something else woke the CPU.
while (!f_wdt) {
sleep_enable();
#if BOD_FUSE_ENABLED
sleep_bod_disable();
#endif
sei();
sleep_cpu();
sleep_disable();
cli();
}
sei();
}
ISR(WDT_vect)
// Wait n Morse units in power-down.
static void waitUnits(uint8_t n)
{
f_wdt = true;
while (n--) {
sleepWdt(UNIT_WDT_A);
#if UNIT_WDT_B != 0xFF
sleepWdt(UNIT_WDT_B);
#endif
}
}
// ===========================================================================
// Light sensor
// ===========================================================================
// Returns a "darkness level": higher = darker.
// Charges the LED junction capacitance, then times how long it takes to bleed
// back through the reverse-biased LED until the input reads LOW.
// ~30000 = pitch black, ~0 = bright light
unsigned int sensDarkness(int LED_N, int LED_P)
// Returns true if it is dark.
// Charges the LED junction capacitance, then lets it bleed through the
// reverse-biased LED while the CPU sleeps. Checks the pin every ~16 ms.
// LOW within the window -> bright (discharged fast)
// still HIGH at the end -> dark
// In power-down the input buffer of the floating pin is disabled by hardware,
// so the slowly falling voltage causes no shoot-through current.
bool isDark(int LED_N, int LED_P)
{
unsigned int i;
// Charge the LED junction capacitance.
pinMode(LED_N, OUTPUT);
pinMode(LED_P, OUTPUT);
digitalWrite(LED_N, HIGH);
digitalWrite(LED_P, LOW);
// Let the N-side float and measure bleed-down time.
// Let the N-side float (no pullup) and measure bleed-down time.
pinMode(LED_N, INPUT);
digitalWrite(LED_N, LOW); // disable internal pullup
digitalWrite(LED_N, LOW);
for (i = 0; i < 30000; i++) {
if (digitalRead(LED_N) == LOW) break;
bool dark = true;
for (uint8_t k = 0; k < darkSteps; k++) {
sleepWdt(0); // ~16 ms
if (digitalRead(LED_N) == LOW) {
dark = false; // bright: stop early
break;
}
}
// Clean up after sensing: both LED pins low, then caller releases them.
// Clean up after sensing: both LED pins low, caller releases them.
pinMode(LED_N, OUTPUT);
digitalWrite(LED_N, LOW);
pinMode(LED_P, OUTPUT);
digitalWrite(LED_P, LOW);
return i;
return dark;
}
// ===========================================================================
@@ -277,17 +392,17 @@ unsigned int sensDarkness(int LED_N, int LED_P)
void dit(int LED_P)
{
digitalWrite(LED_P, HIGH);
delay(unitLength);
waitUnits(1);
digitalWrite(LED_P, LOW);
delay(unitLength);
waitUnits(1);
}
void dah(int LED_P)
{
digitalWrite(LED_P, HIGH);
delay(unitLength * 3);
waitUnits(3);
digitalWrite(LED_P, LOW);
delay(unitLength);
waitUnits(1);
}
// ===========================================================================
@@ -354,24 +469,30 @@ 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 -----
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; // ..--..
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); 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); 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); 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; // .-...
// ----- 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; // ..--..
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); 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); 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); 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
// normal 2 unit character gap. Add 4 units here -> total word gap 7 units.
case ' ':
delay(unitLength * 4);
waitUnits(4);
break;
// Unsupported chars are ignored without adding an artificial gap.
@@ -382,7 +503,7 @@ void morse(int LED_N, int LED_P)
// Inter-character gap: 3 units total.
// dit()/dah() already trail 1 unit OFF, so add the remaining 2 units.
if (symbolSent) {
delay(unitLength * 2);
waitUnits(2);
}
}
@@ -9,7 +9,7 @@
(paper "A4")
(title_block
(title "日輪蛍 / Nichirin Hotaru / FireFly Solar")
(date "2026-07-02")
(date "2026-09-27")
(rev "FireFly Sol 2")
(company "WWW: https://togo-lab.io")
(comment 1 "Email: tgohle@togo-lab.io")
@@ -2094,8 +2094,8 @@
)
)
)
(property "Value" "3V6 1.3W"
(at 5.588 0.508 0)
(property "Value" "3V9 1.3W"
(at 5.5372 0.0508 0)
(layer "F.SilkS")
(uuid "0961063d-f8b2-473d-ac71-4e9497f2e68d")
(effects
@@ -9803,7 +9803,7 @@
(justify left bottom)
)
)
(gr_text "#0001 日輪蛍\nNichirin Hotaru\n\nFireFly Solar\n\ntogo-lab.io\n2026 V 1.0"
(gr_text "#0001 日輪蛍\nNichirin Hotaru\n\nFireFly Solar\n\ntogo-lab.io\n2026 V 3.0"
(at 135.382 84.709 0)
(layer "B.SilkS")
(uuid "88b0a397-e4a2-4145-86f6-0fa60b4a4bd9")
@@ -9816,7 +9816,7 @@
)
(justify bottom mirror)
)
(render_cache "#0001 日輪蛍\nNichirin Hotaru\n\nFireFly Solar\n\ntogo-lab.io\n2026 V 1.0"
(render_cache "#0001 日輪蛍\nNichirin Hotaru\n\nFireFly Solar\n\ntogo-lab.io\n2026 V 3.0"
0
(polygon
(pts
@@ -11359,25 +11359,37 @@
)
(polygon
(pts
(xy 132.305492 83.4953) (xy 132.314285 83.529005) (xy 132.327181 83.570771) (xy 132.3407 83.588268)
(xy 132.364843 83.601472) (xy 132.374882 83.627484) (xy 132.393493 83.659577) (xy 132.414229 83.697752)
(xy 132.420316 83.715223) (xy 132.419871 83.727721) (xy 132.539672 83.915739) (xy 132.597455 84.01795)
(xy 132.648775 84.119511) (xy 132.702436 84.223652) (xy 132.765719 84.333834) (xy 132.835364 84.438492)
(xy 132.905304 84.523757) (xy 132.921099 84.519152) (xy 132.927945 84.505879) (xy 132.930583 84.472759)
(xy 132.930583 84.463234) (xy 132.921649 84.459409) (xy 132.904718 84.434877) (xy 132.86559 84.354351)
(xy 132.819281 84.247006) (xy 132.775464 84.138782) (xy 132.742931 84.054225) (xy 132.736337 84.049975)
(xy 132.722195 84.045286) (xy 132.720713 84.024039) (xy 132.707394 83.987034) (xy 132.669219 83.909438)
(xy 132.642474 83.857707) (xy 132.630824 83.820924) (xy 132.621518 83.816277) (xy 132.610161 83.796524)
(xy 132.591989 83.742083) (xy 132.581804 83.699658) (xy 132.575796 83.695408) (xy 132.556012 83.679801)
(xy 132.539892 83.660163) (xy 132.539879 83.650038) (xy 132.55037 83.637742) (xy 132.542195 83.624458)
(xy 132.531099 83.618251) (xy 132.521226 83.61234) (xy 132.51791 83.601106) (xy 132.536359 83.590858)
(xy 132.567589 83.58352) (xy 132.640203 83.578831) (xy 132.706588 83.584546) (xy 132.730403 83.592341)
(xy 132.737655 83.599787) (xy 132.75728 83.608464) (xy 132.778354 83.608725) (xy 132.802575 83.599787)
(xy 132.860681 83.559926) (xy 132.849836 83.536552) (xy 132.849201 83.52133) (xy 132.857603 83.503286)
(xy 132.815325 83.490317) (xy 132.795016 83.480533) (xy 132.787481 83.462107) (xy 132.703344 83.436625)
(xy 132.621738 83.419389) (xy 132.537841 83.412686) (xy 132.441267 83.419535) (xy 132.431971 83.428417)
(xy 132.421996 83.431113) (xy 132.397523 83.427742) (xy 132.384041 83.42613) (xy 132.370046 83.424445)
(xy 132.34691 83.435561) (xy 132.330771 83.45456)
(xy 132.102893 83.703028) (xy 132.149778 83.746548) (xy 132.206794 83.779305) (xy 132.328427 83.832941)
(xy 132.388647 83.886206) (xy 132.456068 83.932739) (xy 132.59045 84.022937) (xy 132.581218 84.057229)
(xy 132.578067 84.085806) (xy 132.560533 84.091707) (xy 132.535056 84.110498) (xy 132.490653 84.159298)
(xy 132.475747 84.183918) (xy 132.47336 84.196008) (xy 132.449373 84.212983) (xy 132.422069 84.248251)
(xy 132.3773 84.33127) (xy 132.349383 84.393991) (xy 132.351509 84.41993) (xy 132.368946 84.441325)
(xy 132.392561 84.452427) (xy 132.407708 84.4477) (xy 132.563852 84.475324) (xy 132.649371 84.483492)
(xy 132.737216 84.486242) (xy 132.824442 84.483972) (xy 132.905524 84.477376) (xy 132.983273 84.465188)
(xy 133.050457 84.447773) (xy 133.029476 84.436084) (xy 132.990813 84.423227) (xy 132.894752 84.402418)
(xy 132.793782 84.386884) (xy 132.726298 84.379043) (xy 132.70776 84.379043) (xy 132.644965 84.379043)
(xy 132.608563 84.376692) (xy 132.577994 84.370104) (xy 132.554249 84.357412) (xy 132.545387 84.340722)
(xy 132.546275 84.326793) (xy 132.55799 84.300861) (xy 132.596312 84.241803) (xy 132.639836 84.184944)
(xy 132.669145 84.151458) (xy 132.690748 84.131855) (xy 132.71626 84.115554) (xy 132.738221 84.095772)
(xy 132.752383 84.061552) (xy 132.741386 84.049543) (xy 132.736117 84.034221) (xy 132.733625 84.003813)
(xy 132.733625 83.997732) (xy 132.693353 83.964706) (xy 132.644452 83.931493) (xy 132.543556 83.868551)
(xy 132.459145 83.81008) (xy 132.429495 83.781769) (xy 132.417307 83.760327) (xy 132.367921 83.741716)
(xy 132.345324 83.728468) (xy 132.330258 83.714166) (xy 132.322976 83.697839) (xy 132.326815 83.681046)
(xy 132.342529 83.664113) (xy 132.370339 83.656207) (xy 132.402194 83.650219) (xy 132.435112 83.636643)
(xy 132.446933 83.626784) (xy 132.451891 83.617226) (xy 132.457379 83.609352) (xy 132.471748 83.605062)
(xy 132.517925 83.578938) (xy 132.579166 83.558094) (xy 132.645633 83.544235) (xy 132.718092 83.535526)
(xy 132.861487 83.529591) (xy 132.929014 83.531692) (xy 132.983779 83.537431) (xy 133.032407 83.546654)
(xy 133.058444 83.55509) (xy 133.065185 83.567766) (xy 133.077348 83.569818) (xy 133.089658 83.569818)
(xy 133.106584 83.57568) (xy 133.122645 83.541587) (xy 133.120872 83.51926) (xy 133.097572 83.482477)
(xy 133.082184 83.462913) (xy 133.079347 83.453219) (xy 133.081818 83.442836) (xy 133.057696 83.422252)
(xy 133.038514 83.414993) (xy 132.998214 83.41404) (xy 132.968538 83.415725) (xy 132.954304 83.412354)
(xy 132.937031 83.400484) (xy 132.89336 83.402243) (xy 132.840531 83.402243) (xy 132.737143 83.402243)
(xy 132.624009 83.409131) (xy 132.612808 83.425319) (xy 132.608768 83.427376) (xy 132.600342 83.421587)
(xy 132.587006 83.412355) (xy 132.579428 83.414676) (xy 132.566197 83.431113) (xy 132.554405 83.425924)
(xy 132.527362 83.426643) (xy 132.461856 83.441884) (xy 132.394519 83.463719) (xy 132.343594 83.481451)
(xy 132.341689 83.483429) (xy 132.27567 83.52351) (xy 132.205621 83.563956) (xy 132.176091 83.592213)
(xy 132.153671 83.63188) (xy 132.140744 83.632576) (xy 132.132348 83.641186) (xy 132.12253 83.667564)
(xy 132.115203 83.687934)
)
)
(polygon
@@ -379,7 +379,7 @@
"internal_id": "",
"mfg": "",
"mpn": "",
"sch_revision": "2"
"sch_revision": "3"
},
"layer_pairs": [],
"layer_presets": [],
@@ -665,6 +665,7 @@
"netlist": "",
"plot": "",
"specctra_dsn": "",
"step": "",
"vrml": ""
},
"page_layout_descr_file": ""
@@ -6,8 +6,8 @@
(paper "A4")
(title_block
(title "日輪蛍 / Nichirin Hotaru / FireFly Solar")
(date "2026-07-02")
(rev "2")
(date "2026-09-27")
(rev "3")
(company "Tōgō Lab / 塔郷研究所")
(comment 1 "WWW: https://togo-lab.io")
(comment 2 "Email: tgohle@togo-lab.io")
@@ -2123,9 +2123,9 @@
(embedded_fonts no)
)
)
(text "R2 value depends on volage Setting and D6 LED Type:\n\n3.3 V: - RED 470 Ω (tested)\n - GREEN, LOW-VF 470 Ω (not tested)\n - YELLOW, LOW-VF 470 Ω (not tested)\n\n5.0 V: - RED 560 Ω tested, but 5V not recommended"
(text "R2 value depends on volage Setting and D6 LED Type.\nOnly tested this settings:\n\n3.3 V:\n- RED 640 Ω (~ 2,2 mA tested,)\n- GREEN 330 Ω (~ 2,2 mA tested, very bright!)\n\n"
(exclude_from_sim no)
(at 196.342 59.69 0)
(at 219.964 58.42 0)
(effects
(font
(size 1.27 1.27)
@@ -2144,9 +2144,9 @@
)
(uuid "0bf54ea3-f975-4977-80ee-b53880b5df6d")
)
(text "I use 4V SuperCap 50F.\nThis needs protection for overvoltage \nfrom Solar panels therefore Z-Diode\nwith 3.6V.\nDuring a bright Day most energy is\nused by R1, therefore 2W typ.\nC1 = SuperCap should load < 1h."
(text "I use 4V SuperCap 50F.\nThis needs protection for overvoltage \nfrom solar panels therefore Z-Diode\nwith 3.6 - 3.9V. (depending on solar panel)\nDuring a bright Day most energy is\nused by R1, therefore 2W typ.\nC1 = SuperCap should load 2..3 h."
(exclude_from_sim no)
(at 76.2 69.342 0)
(at 76.708 63.246 0)
(effects
(font
(size 1.27 1.27)
@@ -2155,13 +2155,14 @@
)
(uuid "151e3e93-9403-408a-bca5-e165542b5719")
)
(text "C6 RESET, optional"
(text "C6 RESET, optional\n\nin V1 not used, works without"
(exclude_from_sim no)
(at 263.398 105.156 0)
(at 254.762 108.966 0)
(effects
(font
(size 1.27 1.27)
)
(justify left)
)
(uuid "1bf5fdfb-548f-4ff1-9dbf-73a875269c83")
)
@@ -2176,9 +2177,9 @@
)
(uuid "71b72fad-9748-4a7a-92a5-a4bc4876fb33")
)
(text "max 3x Solar Modules,\n5V Type with 60x60mm\n ~ 4.5 V and 10mA under real conditions\n will charge C1 in ~ 1.5h upt to 3.5V\n\nPin 1 = +\nPin 2 = -/GND\n"
(text "max 3x Solar Modules,\n~ 5V Type with 60x60mm\n -> ~ 4.5 V and 10mA under real conditions\n will charge C1 in ~ 3h upt to > 3.5V\n\nPin 1 = +\nPin 2 = -/GND\n"
(exclude_from_sim no)
(at 25.4 69.596 0)
(at 25.908 63.5 0)
(effects
(font
(size 1.27 1.27)
@@ -4930,7 +4931,7 @@
(justify left)
)
)
(property "Value" "BZV85-C3V6 1.3W"
(property "Value" "BZV85-C3V9 1.3W"
(at 80.772 84.582 0)
(show_name no)
(do_not_autoplace no)
Binary file not shown.