|
|
@@ -6,19 +6,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
Project definitions, sources
|
|
|
|
Project definitions, sources
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
Version: 0.3 - ATTiny85, 1 MHz, BOD fuse disabled
|
|
|
|
Version: 0.4 - ATTiny85, 1 MHz, BOD fuse disabled
|
|
|
|
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-08
|
|
|
|
|
|
|
|
|
|
|
|
Key changes Version 0.3:
|
|
|
|
Key changes Version 0.4 (goal: lowest possible average current):
|
|
|
|
|
|
|
|
|
|
|
|
- Removed unnecessary ADC enable/disable from loop()
|
|
|
|
- No delay(), no millis(): Timer0 and Timer1 are not needed any more and
|
|
|
|
- Disabled ADC and analog comparator in setup()
|
|
|
|
are switched off in PRR.
|
|
|
|
- Added INPUT_PULLUP for unused Arduino pins 0, 1, 2 will save power
|
|
|
|
- ALL timing (Morse units, sensing window, 8 s sleep) is done by the watchdog
|
|
|
|
- Added safer watchdog setup with interrupt protection to save run near brownout
|
|
|
|
in interrupt mode while the CPU is in POWER-DOWN. The CPU runs only for a
|
|
|
|
- Added wdt_reset() before sleep for cleaner 8 s timing
|
|
|
|
few microseconds per wake-up. LED pins keep their state in power-down.
|
|
|
|
- Added lowercase-to-uppercase handling
|
|
|
|
- Darkness sensing no longer polls digitalRead() in a busy loop (that was
|
|
|
|
- Unsupported characters are now ignored without adding fake timing gaps
|
|
|
|
~0.3-1 s awake per cycle). The LED junction is charged, then the CPU sleeps
|
|
|
|
|
|
|
|
in 16 ms steps and checks the pin: bright light exits after the first step.
|
|
|
|
|
|
|
|
- ADC is disabled BEFORE it is gated off in PRR (order matters).
|
|
|
|
|
|
|
|
- After a power-on / external reset the sketch waits RECOVER_CYCLES cycles
|
|
|
|
|
|
|
|
before it blinks again, so a nearly empty supercap can recharge first.
|
|
|
|
|
|
|
|
Default ~16 minutes, therefore added a debug option.
|
|
|
|
|
|
|
|
- Optional: BOD disable during sleep (only useful if BOD fuse is enabled).
|
|
|
|
|
|
|
|
Not tested yet.
|
|
|
|
|
|
|
|
- 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.
|
|
|
|
|
|
|
|
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
@@ -60,6 +74,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 (48 s cycle, 16 min recovery wait after a reset).
|
|
|
|
|
|
|
|
#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 +88,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
|
|
|
|
// 6 x 8 s = 48 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 brighter conditions (like the old 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
|
|
|
|
|
|
|
|
// sensDarkness() time with micros() at your dusk level and divide it by 16 ms.
|
|
|
|
|
|
|
|
const uint8_t darkSteps = 40; // ~640 ms
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// After a power-on / external reset (e.g. supercap was empty, DC/DC restart)
|
|
|
|
|
|
|
|
// do not blink for this many cycles (x ~48 s) so the supercap can recharge.
|
|
|
|
|
|
|
|
// 20 -> ~16 min. Set to 0 while testing/uploading (upload = external reset).
|
|
|
|
|
|
|
|
#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 +154,25 @@ 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;
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// 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 +182,28 @@ void dah(int LED_P);
|
|
|
|
// ===========================================================================
|
|
|
|
// ===========================================================================
|
|
|
|
void setup()
|
|
|
|
void setup()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// Disable unused peripherals immediately to save power.
|
|
|
|
// Remember why we restarted, then clear the flags.
|
|
|
|
// PRTIM1, PRUSI: unused, safe to gate off.
|
|
|
|
uint8_t resetCause = MCUSR;
|
|
|
|
// PRADC: ADC is not used; LED sensing is done with digitalRead().
|
|
|
|
MCUSR = 0;
|
|
|
|
// PRTIM0: must stay ON while awake because delay() depends on Timer0.
|
|
|
|
if (resetCause & ((1 << PORF) | (1 << BORF) | (1 << EXTRF))) {
|
|
|
|
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 +211,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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Return LED pins to high-Z before sleeping.
|
|
|
|
|
|
|
|
// Internal pullups stay disabled for the LED pins.
|
|
|
|
|
|
|
|
releaseLedPins(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++) {
|
|
|
|
for (uint8_t i = 0; i < sleepCycles; i++) {
|
|
|
|
system_sleep();
|
|
|
|
sleepWdt(9); // 8 s each
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -188,87 +257,102 @@ 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.
|
|
|
|
cli();
|
|
|
|
const uint8_t cfg = bb | (1 << WDIE) | (1 << WDIF);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
f_wdt = false;
|
|
|
|
|
|
|
|
cli();
|
|
|
|
wdt_reset();
|
|
|
|
wdt_reset();
|
|
|
|
MCUSR &= ~(1 << WDRF);
|
|
|
|
MCUSR &= ~(1 << WDRF);
|
|
|
|
|
|
|
|
|
|
|
|
// Timed sequence: enable configuration change, then set interrupt-only WDT.
|
|
|
|
// Timed sequence: the two writes must follow each other within 4 cycles.
|
|
|
|
WDTCR |= (1 << WDCE) | (1 << WDE);
|
|
|
|
WDTCR = (1 << WDCE) | (1 << WDE);
|
|
|
|
WDTCR = bb | (1 << WDIE);
|
|
|
|
WDTCR = cfg;
|
|
|
|
|
|
|
|
|
|
|
|
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 +361,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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
// ===========================================================================
|
|
|
@@ -371,7 +455,7 @@ void morse(int LED_N, int LED_P)
|
|
|
|
// 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,10 +466,10 @@ 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
digitalWrite(LED_P, LOW);
|
|
|
|
digitalWrite(LED_P, LOW);
|
|
|
|
digitalWrite(LED_N, LOW);
|
|
|
|
digitalWrite(LED_N, LOW);
|
|
|
|
}
|
|
|
|
}
|