512 lines
21 KiB
Arduino
512 lines
21 KiB
Arduino
/*
|
||
=============================================================================
|
||
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.5 - ATTiny85, 1 MHz, BOD fuse disabled
|
||
used Flash: 1284 bytes
|
||
|
||
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 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.
|
||
|
||
-----------------------------------------------------------------------------
|
||
|
||
Inspired by Karl Lunt's FireFly project:
|
||
http://www.seanet.com/~karllunt/fireflyLED.html
|
||
|
||
Morse code reference:
|
||
'A', ".-" 'B', "-..." 'C', "-.-."
|
||
'D', "-.." 'E', "." 'F', "..-."
|
||
'G', "--." 'H', "...." 'I', ".."
|
||
'J', ".---" 'K', "-.-" 'L', ".-.."
|
||
'M', "--" 'N', "-." 'O', "---"
|
||
'P', ".--." 'Q', "--.-" 'R', ".-."
|
||
'S', "..." 'T', "-" 'U', "..-"
|
||
'V', "...-" 'W', ".--" 'X', "-..-"
|
||
'Y', "-.--" 'Z', "--.."
|
||
|
||
'1', ".----" '2', "..---" '3', "...--"
|
||
'4', "....-" '5', "....." '6', "-...."
|
||
'7', "--..." '8', "---.." '9', "----."
|
||
'0', "-----"
|
||
|
||
'.', ".-.-.-" ',', "--..--" '?', "..--.."
|
||
'!', "-.-.--" ':', "---..." ';', "-.-.-."
|
||
'(', "-.--." ')', "-.--.-" '"', ".-..-."
|
||
'@', ".--.-." '&', ".-..." '-', "-....-"
|
||
'/', "-..-." ''', ".----." '+', ".-.-."
|
||
'=', "-...-" '_', "..--.-"
|
||
|
||
-----------------------------------------------------------------------------
|
||
|
||
Legal stuff / Copyright:
|
||
License_-_CC_BY-NC_4.0
|
||
https://creativecommons.org/licenses/by-nc/4.0/
|
||
-----------------------------------------------------------------------------
|
||
*/
|
||
|
||
#include <avr/sleep.h>
|
||
#include <avr/wdt.h>
|
||
#include <avr/pgmspace.h>
|
||
#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
|
||
#ifndef sbi
|
||
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
|
||
#endif
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 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 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).
|
||
#define LED1_N_SIDE 3
|
||
#define LED1_P_SIDE 4
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// ATTiny85 Arduino pins 0, 1, 2 are unused in actual design and physically open.
|
||
// Keep them in INPUT_PULLUP so they do not float and waste current.
|
||
#define UNUSED_PIN_0 0
|
||
#define UNUSED_PIN_1 1
|
||
#define UNUSED_PIN_2 2
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 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.
|
||
// 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 = 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);
|
||
static void waitUnits(uint8_t n);
|
||
void configureUnusedPins();
|
||
void releaseLedPins(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);
|
||
|
||
// ===========================================================================
|
||
// Setup
|
||
// ===========================================================================
|
||
void setup()
|
||
{
|
||
// 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);
|
||
|
||
// 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);
|
||
}
|
||
|
||
// ===========================================================================
|
||
// Main loop
|
||
// ===========================================================================
|
||
void loop()
|
||
{
|
||
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);
|
||
|
||
for (uint8_t i = 0; i < sleepCycles; i++) {
|
||
sleepWdt(9); // 8 s each
|
||
}
|
||
}
|
||
|
||
// ===========================================================================
|
||
// Pin helpers
|
||
// ===========================================================================
|
||
void configureUnusedPins()
|
||
{
|
||
pinMode(UNUSED_PIN_0, INPUT_PULLUP);
|
||
pinMode(UNUSED_PIN_1, INPUT_PULLUP);
|
||
pinMode(UNUSED_PIN_2, INPUT_PULLUP);
|
||
}
|
||
|
||
void releaseLedPins(int LED_N, int LED_P)
|
||
{
|
||
// digitalWrite LOW before INPUT disables the internal pullup on Arduino cores.
|
||
digitalWrite(LED_N, LOW);
|
||
digitalWrite(LED_P, LOW);
|
||
pinMode(LED_N, INPUT);
|
||
pinMode(LED_P, INPUT);
|
||
}
|
||
|
||
// ===========================================================================
|
||
// Sleep helpers (watchdog = only timebase)
|
||
// ===========================================================================
|
||
ISR(WDT_vect)
|
||
{
|
||
f_wdt = true;
|
||
}
|
||
|
||
// 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);
|
||
|
||
// 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();
|
||
|
||
// 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);
|
||
|
||
// 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();
|
||
}
|
||
|
||
// Wait n Morse units in power-down.
|
||
static void waitUnits(uint8_t n)
|
||
{
|
||
while (n--) {
|
||
sleepWdt(UNIT_WDT_A);
|
||
#if UNIT_WDT_B != 0xFF
|
||
sleepWdt(UNIT_WDT_B);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
// ===========================================================================
|
||
// Light sensor
|
||
// ===========================================================================
|
||
// 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)
|
||
{
|
||
// 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 (no pullup) and measure bleed-down time.
|
||
pinMode(LED_N, INPUT);
|
||
digitalWrite(LED_N, LOW);
|
||
|
||
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, caller releases them.
|
||
pinMode(LED_N, OUTPUT);
|
||
digitalWrite(LED_N, LOW);
|
||
pinMode(LED_P, OUTPUT);
|
||
digitalWrite(LED_P, LOW);
|
||
|
||
return dark;
|
||
}
|
||
|
||
// ===========================================================================
|
||
// Morse helpers
|
||
// ===========================================================================
|
||
void dit(int LED_P)
|
||
{
|
||
digitalWrite(LED_P, HIGH);
|
||
waitUnits(1);
|
||
digitalWrite(LED_P, LOW);
|
||
waitUnits(1);
|
||
}
|
||
|
||
void dah(int LED_P)
|
||
{
|
||
digitalWrite(LED_P, HIGH);
|
||
waitUnits(3);
|
||
digitalWrite(LED_P, LOW);
|
||
waitUnits(1);
|
||
}
|
||
|
||
// ===========================================================================
|
||
// Morse sender
|
||
// ===========================================================================
|
||
void morse(int LED_N, int LED_P)
|
||
{
|
||
pinMode(LED_N, OUTPUT);
|
||
pinMode(LED_P, OUTPUT);
|
||
digitalWrite(LED_N, LOW);
|
||
digitalWrite(LED_P, LOW);
|
||
|
||
const size_t len = strlen_P(morseText);
|
||
|
||
for (size_t i = 0; i < len; i++) {
|
||
char c = (char)pgm_read_byte(&morseText[i]);
|
||
|
||
// Allow lowercase source text without silently breaking timing.
|
||
if (c >= 'a' && c <= 'z') {
|
||
c = c - 'a' + 'A';
|
||
}
|
||
|
||
bool symbolSent = false;
|
||
|
||
switch (c)
|
||
{
|
||
// ----- Letters -----
|
||
case 'A': dit(LED_P); dah(LED_P); symbolSent = true; break; // .-
|
||
case 'B': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // -...
|
||
case 'C': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // -.-.
|
||
case 'D': dah(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // -..
|
||
case 'E': dit(LED_P); symbolSent = true; break; // .
|
||
case 'F': dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // ..-.
|
||
case 'G': dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // --.
|
||
case 'H': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ....
|
||
case 'I': dit(LED_P); dit(LED_P); symbolSent = true; break; // ..
|
||
case 'J': dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // .---
|
||
case 'K': dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -.-
|
||
case 'L': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // .-..
|
||
case 'M': dah(LED_P); dah(LED_P); symbolSent = true; break; // --
|
||
case 'N': dah(LED_P); dit(LED_P); symbolSent = true; break; // -.
|
||
case 'O': dah(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // ---
|
||
case 'P': dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .--.
|
||
case 'Q': dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // --.-
|
||
case 'R': dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .-.
|
||
case 'S': dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ...
|
||
case 'T': dah(LED_P); symbolSent = true; break; // -
|
||
case 'U': dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // ..-
|
||
case 'V': dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // ...-
|
||
case 'W': dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // .--
|
||
case 'X': dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -..-
|
||
case 'Y': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // -.--
|
||
case 'Z': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // --..
|
||
|
||
// ----- Digits -----
|
||
case '1': dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // .----
|
||
case '2': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // ..---
|
||
case '3': dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // ...--
|
||
case '4': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // ....-
|
||
case '5': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // .....
|
||
case '6': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // -....
|
||
case '7': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // --...
|
||
case '8': dah(LED_P); dah(LED_P); dah(LED_P); dit(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; // -----
|
||
|
||
// ----- 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 ' ':
|
||
waitUnits(4);
|
||
break;
|
||
|
||
// Unsupported chars are ignored without adding an artificial gap.
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// Inter-character gap: 3 units total.
|
||
// dit()/dah() already trail 1 unit OFF, so add the remaining 2 units.
|
||
if (symbolSent) {
|
||
waitUnits(2);
|
||
}
|
||
}
|
||
|
||
digitalWrite(LED_P, LOW);
|
||
digitalWrite(LED_N, LOW);
|
||
} |