2 Commits
2 changed files with 276 additions and 109 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. During the day, the solar cell charges the energy storage.
At night, the circuit wakes up and sends a simple Morse-style light signal. 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 ## 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 - Test mechanical concepts for outdoor 3D-printed parts
- Learn what is needed to turn a small electronics project into a possible DIY kit - 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 ## Naming
The English project name remains: 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 ## Contributing
PRs and issues welcome. Keep it simple. 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. 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 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.3 - ATTiny85, 1 MHz, BOD fuse disabled Version : 0.5 - ATTiny85, 1 MHz, BOD fuse disabled
used Flash: 1284 bytes
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
Date : 2026-06-14 Date : 2026-11-10
Key changes Version 0.3:
- Removed unnecessary ADC enable/disable from loop() Key changes Version 0.5 (goal: lowest possible average current):
- Disabled ADC and analog comparator in setup()
- Added INPUT_PULLUP for unused Arduino pins 0, 1, 2 will save power - No delay(), no millis(): Timer0 and Timer1 are not needed any more and
- Added safer watchdog setup with interrupt protection to save run near brownout are switched off in PRR.
- Added wdt_reset() before sleep for cleaner 8 s timing - ALL timing (Morse units, sensing window, 8 s sleep) is done by the watchdog
- Added lowercase-to-uppercase handling in interrupt mode while the CPU is in POWER-DOWN. The CPU runs only for a
- Unsupported characters are now ignored without adding fake timing gaps 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 <avr/interrupt.h>
#include <string.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 #ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif #endif
@@ -68,16 +98,55 @@ Inspired by Karl Lunt's FireFly project:
#endif #endif
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Timing: unit length in ms. // Watchdog timeout codes (nominal, at ~5 V; real values vary with Vcc/temp):
// At 1 MHz, delay() is accurate when F_CPU=1000000L is set in boards.txt. // actual HW uses 3.3 (standard) or 5V (for blue LED experimental setup)
// 100 ms gives readable optical Morse; raise to 150 ms if readability is poor. // so don't expect exact timing)
#define unitLength 100 // 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. // Sleep interval between two sensing/blink attempts: sleepCycles x 8 s.
// Watchdog setup uses 8 s. For final use, change sleepCycles from 4 to 6 // 5 x 8 s = 40 s. Goal ~ 1 blink/min.
// for about 32s - 48s between sensing/blinking activations. Goal ~ 1 blink/min #if DEBUG_TEST
const uint8_t sleepCycles = 6; 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). // 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. // ATTiny85 has 512 bytes SRAM; this keeps the Morse text in flash.
// Only uppercase letters, digits, spaces, and the special chars in the switch // Only uppercase letters, digits, spaces, and the special chars in the switch
// below are emitted. Lowercase letters are converted to uppercase. // 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 = less LED time = less energy.
// shorter text will better, more text will cost more power. const char morseText[] PROGMEM = "TOGO-LAB.IO FIREFLY";
const char morseText[] PROGMEM = "TGO 26"; // Ruler: 0....0....1....1....2
// Ruler: 0....0....1...1....2 // Ruler: 01...5....0....5....0
// 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;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Watchdog interrupt flag — volatile because it is written in an ISR. // Watchdog interrupt flag - volatile because it is written in an ISR.
volatile bool f_wdt = true; 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. // Forward declarations.
void setup_watchdog(uint8_t ii); static void sleepWdt(uint8_t ii);
void system_sleep(); static void waitUnits(uint8_t n);
void configureUnusedPins(); void configureUnusedPins();
void releaseLedPins(int LED_N, int LED_P); 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 morse(int LED_N, int LED_P);
void dit(int LED_P); void dit(int LED_P);
void dah(int LED_P); void dah(int LED_P);
@@ -126,22 +205,28 @@ void dah(int LED_P);
// =========================================================================== // ===========================================================================
void setup() void setup()
{ {
// Disable unused peripherals immediately to save power. // Reset cause was captured before C/Arduino initialization in .init3.
// PRTIM1, PRUSI: unused, safe to gate off. // Include WDRF: a watchdog reset also needs supercap recovery time.
// PRADC: ADC is not used; LED sensing is done with digitalRead(). if (resetCauseEarly & ((1 << PORF) | (1 << BORF) |
// PRTIM0: must stay ON while awake because delay() depends on Timer0. (1 << EXTRF) | (1 << WDRF))) {
PRR = (1 << PRTIM1) | (1 << PRUSI) | (1 << PRADC); recoverCycles = RECOVER_CYCLES;
}
// 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);
// Pins first (while the Arduino core can still reach all timer registers
// for its turnOffPWM() handling in digitalWrite()).
configureUnusedPins(); configureUnusedPins();
releaseLedPins(LED1_N_SIDE, LED1_P_SIDE); 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() void loop()
{ {
if (f_wdt) { if (recoverCycles > 0) {
f_wdt = false; recoverCycles--;
} else if (isDark(LED1_N_SIDE, LED1_P_SIDE)) {
if (sensDarkness(LED1_N_SIDE, LED1_P_SIDE) > darknessThreshold) {
morse(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. // Return LED pins to high-Z before sleeping.
// Internal pullups stay disabled for the LED pins. // Internal pullups stay disabled for the LED pins.
releaseLedPins(LED1_N_SIDE, LED1_P_SIDE); 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++) { 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. f_wdt = true;
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();
} }
// Watchdog setup - ii selects timeout: // Sleep in POWER-DOWN until the watchdog interrupt fires after timeout code ii
// 0=16ms 1=32ms 2=64ms 3=128ms 4=250ms 5=500ms // (0=16 ms ... 9=8 s). Reconfigures the watchdog on every call, so the full
// 6=1s 7=2s 8=4s 9=8s // interval always starts fresh.
void setup_watchdog(uint8_t ii) static void sleepWdt(uint8_t ii)
{ {
if (ii > 9) ii = 9; if (ii > 9) ii = 9;
uint8_t bb = ii & 7; uint8_t bb = ii & 7;
if (ii > 7) bb |= (1 << WDP3); 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(); cli();
wdt_reset(); wdt_reset();
MCUSR &= ~(1 << WDRF);
// Timed sequence: enable configuration change, then set interrupt-only WDT. // 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 = bb | (1 << WDIE); 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 // Light sensor
// =========================================================================== // ===========================================================================
// Returns a "darkness level": higher = darker. // Returns true if it is dark.
// Charges the LED junction capacitance, then times how long it takes to bleed // Charges the LED junction capacitance, then lets it bleed through the
// back through the reverse-biased LED until the input reads LOW. // reverse-biased LED while the CPU sleeps. Checks the pin every ~16 ms.
// ~30000 = pitch black, ~0 = bright light // LOW within the window -> bright (discharged fast)
unsigned int sensDarkness(int LED_N, int LED_P) // 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. // Charge the LED junction capacitance.
pinMode(LED_N, OUTPUT); pinMode(LED_N, OUTPUT);
pinMode(LED_P, OUTPUT); pinMode(LED_P, OUTPUT);
digitalWrite(LED_N, HIGH); digitalWrite(LED_N, HIGH);
digitalWrite(LED_P, LOW); 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); pinMode(LED_N, INPUT);
digitalWrite(LED_N, LOW); // disable internal pullup digitalWrite(LED_N, LOW);
for (i = 0; i < 30000; i++) { bool dark = true;
if (digitalRead(LED_N) == LOW) break; 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); pinMode(LED_N, OUTPUT);
digitalWrite(LED_N, LOW); digitalWrite(LED_N, LOW);
pinMode(LED_P, OUTPUT); pinMode(LED_P, OUTPUT);
digitalWrite(LED_P, LOW); 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) void dit(int LED_P)
{ {
digitalWrite(LED_P, HIGH); digitalWrite(LED_P, HIGH);
delay(unitLength); waitUnits(1);
digitalWrite(LED_P, LOW); digitalWrite(LED_P, LOW);
delay(unitLength); waitUnits(1);
} }
void dah(int LED_P) void dah(int LED_P)
{ {
digitalWrite(LED_P, HIGH); digitalWrite(LED_P, HIGH);
delay(unitLength * 3); waitUnits(3);
digitalWrite(LED_P, LOW); digitalWrite(LED_P, LOW);
delay(unitLength); waitUnits(1);
} }
// =========================================================================== // ===========================================================================
@@ -354,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 '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; // ..--..
@@ -366,12 +481,18 @@ 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); 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
// normal 2 unit character gap. Add 4 units here -> total word gap 7 units. // normal 2 unit character gap. Add 4 units here -> total word gap 7 units.
case ' ': case ' ':
delay(unitLength * 4); waitUnits(4);
break; break;
// Unsupported chars are ignored without adding an artificial gap. // 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. // Inter-character gap: 3 units total.
// dit()/dah() already trail 1 unit OFF, so add the remaining 2 units. // dit()/dah() already trail 1 unit OFF, so add the remaining 2 units.
if (symbolSent) { if (symbolSent) {
delay(unitLength * 2); waitUnits(2);
} }
} }