Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c396dbf3f6 | |||
| ebf338c97f | |||
| 46a2bec551 |
@ -1,103 +1,35 @@
|
|||||||
/*
|
/*
|
||||||
=============================================================================
|
=============================================================================
|
||||||
FireFly Morse Throwie
|
FireFly Morse Throwie
|
||||||
- a light controled (LED as Sensor) morseblinker throwie with ATTiny45/85
|
- a light controlled (LED as Sensor) morse blinker throwie with ATTiny85
|
||||||
=============================================================================
|
=============================================================================
|
||||||
|
|
||||||
Project definitions, sources
|
Project definitions, sources
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
Version: 0.1 - Attiny85 Version
|
Version: 0.3 - ATTiny85, 1 MHz, BOD fuse disabled
|
||||||
Date : 25.05.2026
|
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
|
||||||
|
Date : 2026-06-14
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
Key changes Version 0.3:
|
||||||
|
|
||||||
Inspired by Karl Lunt's FireFly project I wrote some code to make this
|
- Removed unnecessary ADC enable/disable from loop()
|
||||||
Throwie lasting longer, blinking only at low light levels and morse also
|
- Disabled ADC and analog comparator in setup()
|
||||||
some text.
|
- 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
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Inspired by Karl Lunt's FireFly project:
|
||||||
http://www.seanet.com/~karllunt/fireflyLED.html
|
http://www.seanet.com/~karllunt/fireflyLED.html
|
||||||
|
|
||||||
How it works (only the Morsethrowy part):
|
Morse code reference:
|
||||||
LED detects light level, using bleeding out time of LED pn-capacitor.
|
|
||||||
If dark enough blink a text in morsecode (ATTiny45 = 22 Chars, text will
|
|
||||||
be defined in line 191/192 of this file)
|
|
||||||
To save energy go to powersave mode after blinking or light level is
|
|
||||||
over threshold. Tests this every 8s (maximum time for watchdog timer
|
|
||||||
possible for ATTiny). Repeat this endless
|
|
||||||
|
|
||||||
|
|
||||||
The wiring, it's very simple, see also HW part of the Project.
|
|
||||||
|
|
||||||
+---+ +------\/------+
|
|
||||||
| | ATTINY45 /85 |
|
|
||||||
> | -+1=PB5 VCC=8+-> to SuperCap / DC-DC 3V/5V
|
|
||||||
xxx Ohm < | | |
|
|
||||||
> +------+2=PB3 PB2=7+-
|
|
||||||
| | |
|
|
||||||
----- +------+3=PB4 PB1=6+-
|
|
||||||
LED / \ | | |
|
|
||||||
----- | +--+4=GND PB0=5+-
|
|
||||||
| | | | |
|
|
||||||
+---+ | +--------------+
|
|
||||||
|
|
|
||||||
+---------------------> to battery GND
|
|
||||||
|
|
||||||
You can use other pins if necessary, theoretical up to 3 LEDs, as example I
|
|
||||||
tried out to connect another one at PB2&PB1. working fine. Basic wiring:
|
|
||||||
|
|
||||||
+ LED_N_Side), wired to a digital pin, NOT Vcc
|
|
||||||
|
|
|
||||||
<
|
|
||||||
> 100 - 460 ohm resistor depending Voltage
|
|
||||||
<
|
|
||||||
|
|
|
||||||
|
|
|
||||||
-----
|
|
||||||
/ \ LED, maybe a 5mm, clear plastic is good
|
|
||||||
-----
|
|
||||||
|
|
|
||||||
|
|
|
||||||
+ LED_P_Side), wired to a digital pin, NOT Gnd
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
Im just a newbe in ATTiny / Arduino programming, so many thanks to:
|
|
||||||
|
|
||||||
Programming ATTiny with Arduino IDE:
|
|
||||||
http://highlowtech.org/?p=1695
|
|
||||||
|
|
||||||
LED Lightsensor, Arduino Playground:
|
|
||||||
http://playground.arduino.cc/Learning/LEDSensor
|
|
||||||
|
|
||||||
Powersaving Mode for Attiny45/85:
|
|
||||||
A good description by Martin Nawrath nawrath@khm.de and the folks at
|
|
||||||
http://www.insidegadgets.com/2011/02/05/reduce-attiny-power-consumption-by-sleeping-with-the-watchdog-timer/
|
|
||||||
|
|
||||||
With this description I got ~5uA during sleep mode.
|
|
||||||
in Morse-Mode with LED OFF: 1.7mA
|
|
||||||
LED ON: 6.9mA
|
|
||||||
--> I hope to run this throwies ~40days with one CR2032.
|
|
||||||
|
|
||||||
Morsecode
|
|
||||||
To translate a text into morsecode, there are several ways. You can use
|
|
||||||
this fine online translator:
|
|
||||||
http://morsecode.scphillips.com/jtranslator.html
|
|
||||||
|
|
||||||
There is also a great morse coder / encoder from Matthias Esterl aka madc.
|
|
||||||
Works fine for Arduino, but not for ATTiny because of RAM limitations. But
|
|
||||||
maybe this is helpful if your project works with an Arduino.
|
|
||||||
https://gist.github.com/madc/4474559
|
|
||||||
|
|
||||||
For this project I use a simple case structur. Looks arkward, but the
|
|
||||||
ATTiny45 has only 256byte RAM but 4kFlash. A data structure is better programming
|
|
||||||
but needs a lot of RAM we not have. Therefore this ugly case structure.
|
|
||||||
This will waste the flash memory but we have enough and saving RAM for
|
|
||||||
our payload - the ASCII-string with the morsetext.
|
|
||||||
|
|
||||||
BTW - Morsecode itself:
|
|
||||||
|
|
||||||
'A', ".-" 'B', "-..." 'C', "-.-."
|
'A', ".-" 'B', "-..." 'C', "-.-."
|
||||||
'D', "-.." 'E', "." 'F', "..-."
|
'D', "-.." 'E', "." 'F', "..-."
|
||||||
'G', "--." 'H', "...." 'I', ".."
|
'G', "--." 'H', "...." 'I', ".."
|
||||||
'J', ".---" 'K', ".-.-" 'L', ".-.."
|
'J', ".---" 'K', "-.-" 'L', ".-.."
|
||||||
'M', "--" 'N', "-." 'O', "---"
|
'M', "--" 'N', "-." 'O', "---"
|
||||||
'P', ".--." 'Q', "--.-" 'R', ".-."
|
'P', ".--." 'Q', "--.-" 'R', ".-."
|
||||||
'S', "..." 'T', "-" 'U', "..-"
|
'S', "..." 'T', "-" 'U', "..-"
|
||||||
@ -114,25 +46,20 @@
|
|||||||
'(', "-.--." ')', "-.--.-" '"', ".-..-."
|
'(', "-.--." ')', "-.--.-" '"', ".-..-."
|
||||||
'@', ".--.-." '&', ".-..."
|
'@', ".--.-." '&', ".-..."
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
Legal stuff / Copyright:
|
Legal stuff / Copyright:
|
||||||
Creative Commons Attribution ShareAlike 3.0.:
|
License_-_CC_BY-NC_4.0
|
||||||
http://creativecommons.org/licenses/by-sa/3.0/legalcode
|
https://creativecommons.org/licenses/by-nc/4.0/
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
|
||||||
// Ok folks, let's start
|
|
||||||
// ===========================================================================
|
|
||||||
// for sleep Mode / Powersave we need some additional stuff. Its alredy there,
|
|
||||||
// if you installed the Attiny extension for Arduino IDE, no additional
|
|
||||||
// installing needed.
|
|
||||||
#include <avr/sleep.h>
|
#include <avr/sleep.h>
|
||||||
#include <avr/wdt.h>
|
#include <avr/wdt.h>
|
||||||
|
#include <avr/pgmspace.h>
|
||||||
|
#include <avr/interrupt.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
// ===========================================================================
|
|
||||||
// Some definitions for powersave depending of ATTiny type.
|
|
||||||
#ifndef cbi
|
#ifndef cbi
|
||||||
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
|
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
|
||||||
#endif
|
#endif
|
||||||
@ -140,405 +67,325 @@
|
|||||||
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
|
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ===========================================================================
|
// ---------------------------------------------------------------------------
|
||||||
// for ATTiny85
|
// Timing: unit length in ms.
|
||||||
// Unit length < 150 will hard to be read for optical morse code
|
// At 1 MHz, delay() is accurate when F_CPU=1000000L is set in boards.txt.
|
||||||
// > 300 are too slow in my feeling for longer text
|
// 100 ms gives readable optical Morse; raise to 150 ms if readability is poor.
|
||||||
//
|
|
||||||
// for ATTiny85 at 8MHz (for 1MHz see ATTiny45)
|
|
||||||
// Unit length < 5 will hard to be read for optical morse code
|
|
||||||
// > 10 are too slow in my feeling for longer text
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#define unitLength 100
|
#define unitLength 100
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// LED Pin definitions,
|
// Sleep interval.
|
||||||
// theoretical you can use up to 3 LED. For example you can use it for a
|
// Watchdog setup uses 8 s. For final use, change sleepCycles from 4 to 6
|
||||||
// landmark beacon. So if you point this LEDs to different directions you can
|
// for about 32s - 48s between sensing/blinking activations. Goal ~ 1 blink/min
|
||||||
// detect the position in dependence of your location to the bacon.
|
const uint8_t sleepCycles = 6;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// LED pin definitions (N-side = cathode, P-side = anode for sensing/driving).
|
||||||
#define LED1_N_SIDE 3
|
#define LED1_N_SIDE 3
|
||||||
#define LED1_P_SIDE 4
|
#define LED1_P_SIDE 4
|
||||||
|
|
||||||
// ===========================================================================
|
// ---------------------------------------------------------------------------
|
||||||
// Variables
|
// 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
|
||||||
// # and now the text, but be aware, only 256Byte RAM for Attiny45 #
|
#define UNUSED_PIN_1 1
|
||||||
// # so there are only 22 Chars left for your message. #
|
#define UNUSED_PIN_2 2
|
||||||
// # BTW with an Attiny85 you will have additional 256byte #
|
|
||||||
// # => more than for twitter :-) #
|
|
||||||
// # => for FireFly with SuperCap: less ist better, charge last longer #
|
|
||||||
|
|
||||||
String morseText =
|
// ---------------------------------------------------------------------------
|
||||||
"TOGO LAB";
|
// ATTiny85 has 512 bytes SRAM; this keeps the Morse text in flash.
|
||||||
// # "....5....1....5....2.."; just a ruler, remember 22 Chars for ATTiny #
|
// Only uppercase letters, digits, spaces, and the special chars in the switch
|
||||||
// IT IS USELESS
|
// below are emitted. Lowercase letters are converted to uppercase.
|
||||||
// HAIL GLOW CLOUD
|
// 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;
|
||||||
|
|
||||||
// Define light trigger threshold. best way to set it on dusk / dawn level
|
// ---------------------------------------------------------------------------
|
||||||
// diffuse red ones are less sensitive than clear green ones...
|
// Watchdog interrupt flag — volatile because it is written in an ISR.
|
||||||
int darknessThreshold = 17000;
|
volatile bool f_wdt = true;
|
||||||
|
|
||||||
// Interrupt Flag, should be volatile, means: read from RAM, not register
|
// ---------------------------------------------------------------------------
|
||||||
// because registers are used for interrupt handling, it have to be volatile.
|
// Forward declarations.
|
||||||
volatile boolean f_wdt = 1;
|
void setup_watchdog(uint8_t ii);
|
||||||
|
void system_sleep();
|
||||||
|
void configureUnusedPins();
|
||||||
|
void releaseLedPins(int LED_N, int LED_P);
|
||||||
|
unsigned int sensDarkness(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 watchdog
|
// Setup
|
||||||
// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
|
|
||||||
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
setup_watchdog(9);
|
// 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);
|
||||||
|
|
||||||
|
configureUnusedPins();
|
||||||
|
releaseLedPins(LED1_N_SIDE, LED1_P_SIDE);
|
||||||
|
|
||||||
|
setup_watchdog(9); // 8-second watchdog interval.
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Main
|
// Main loop
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (f_wdt) {
|
||||||
|
f_wdt = false;
|
||||||
|
|
||||||
void loop(){
|
if (sensDarkness(LED1_N_SIDE, LED1_P_SIDE) > darknessThreshold) {
|
||||||
|
|
||||||
if (f_wdt==1) { // wait for timed out watchdog
|
|
||||||
// flag is set when a watchdog timeout occurs
|
|
||||||
f_wdt=0; // reset flag
|
|
||||||
|
|
||||||
// if it's dark enough [>darknesThreshold] morse
|
|
||||||
if (sensDarkness(LED1_N_SIDE, LED1_P_SIDE) > darknessThreshold){
|
|
||||||
// looks like it's dark, so do your job
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
// set all used port to intput to save power
|
|
||||||
pinMode(LED1_N_SIDE,INPUT);
|
// Test setup: 1 x 8 s sleep.
|
||||||
pinMode(LED1_P_SIDE,INPUT);
|
// Final setup: set sleepCycles = 4 for about 32 s.
|
||||||
system_sleep();
|
for (uint8_t i = 0; i < sleepCycles; i++) {
|
||||||
f_wdt=0; // 2nd time sleep
|
|
||||||
system_sleep();
|
system_sleep();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Subroutines for Sleeping
|
// Pin helpers
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// set system into the sleep state
|
void configureUnusedPins()
|
||||||
// system wakes up when wtchdog is timed out
|
{
|
||||||
void system_sleep() {
|
pinMode(UNUSED_PIN_0, INPUT_PULLUP);
|
||||||
cbi(ADCSRA,ADEN); // switch Analog to
|
pinMode(UNUSED_PIN_1, INPUT_PULLUP);
|
||||||
// Digitalconverter OFF
|
pinMode(UNUSED_PIN_2, INPUT_PULLUP);
|
||||||
|
}
|
||||||
|
|
||||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
|
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
|
||||||
|
// ===========================================================================
|
||||||
|
void system_sleep()
|
||||||
|
{
|
||||||
|
// 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();
|
sleep_enable();
|
||||||
|
sei();
|
||||||
|
sleep_cpu();
|
||||||
|
|
||||||
sleep_mode(); // System sleeps here
|
// WDT interrupt wakes the CPU here.
|
||||||
|
sleep_disable();
|
||||||
sleep_disable(); // System continues execution here when
|
|
||||||
// watchdog timed out
|
|
||||||
sbi(ADCSRA,ADEN); // switch Analog to Digitalconverter ON
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// Watchdog setup - ii selects timeout:
|
||||||
// Setup for watchdog. Parameter ii for sleeping time:
|
// 0=16ms 1=32ms 2=64ms 3=128ms 4=250ms 5=500ms
|
||||||
// 0 = 16ms, 1 = 32ms, 2 = 64ms,
|
// 6=1s 7=2s 8=4s 9=8s
|
||||||
// 3 = 128ms, 4 = 250ms, 5 = 500ms
|
void setup_watchdog(uint8_t ii)
|
||||||
// 6 = 1 sec, 7 = 2 sec, 8 = 4 sec,
|
{
|
||||||
// 9 = 8 sec
|
if (ii > 9) ii = 9;
|
||||||
void setup_watchdog(int ii) {
|
|
||||||
|
|
||||||
byte bb;
|
uint8_t bb = ii & 7;
|
||||||
int ww;
|
if (ii > 7) bb |= (1 << WDP3);
|
||||||
if (ii > 9 ) ii=9;
|
|
||||||
bb=ii & 7;
|
|
||||||
if (ii > 7) bb|= (1<<5);
|
|
||||||
bb|= (1<<WDCE);
|
|
||||||
ww=bb;
|
|
||||||
|
|
||||||
MCUSR &= ~(1<<WDRF);
|
uint8_t oldSREG = SREG;
|
||||||
|
cli();
|
||||||
|
|
||||||
WDTCR |= (1<<WDCE) | (1<<WDE); // start timed sequence
|
wdt_reset();
|
||||||
|
MCUSR &= ~(1 << WDRF);
|
||||||
|
|
||||||
WDTCR = bb; // set new watchdog timeout value
|
// Timed sequence: enable configuration change, then set interrupt-only WDT.
|
||||||
WDTCR |= _BV(WDIE);
|
WDTCR |= (1 << WDCE) | (1 << WDE);
|
||||||
}
|
WDTCR = bb | (1 << WDIE);
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Watchdog Interrupt Service / is executed when watchdog timed out
|
SREG = oldSREG;
|
||||||
ISR(WDT_vect) {
|
|
||||||
f_wdt=1; // set global flag
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
ISR(WDT_vect)
|
||||||
// Subroutines for light sensor
|
{
|
||||||
// =============================================================================
|
f_wdt = true;
|
||||||
// Function sensDarkness
|
}
|
||||||
// Usage: sensDarkness(Pin-No. N-Side of LED, Pin-No. P-Side of LED):
|
|
||||||
// will result "darkness-level" - the higher the darker:
|
|
||||||
// 30000 = pitch black
|
|
||||||
// 0 = sunshine
|
|
||||||
int sensDarkness(int LED_N, int LED_P){
|
|
||||||
|
|
||||||
unsigned int i; //Parameter bleed-out LED capacitor
|
// ===========================================================================
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
// charge the capacitor of LED
|
// 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);
|
||||||
|
|
||||||
// Isolate the N end of the diode and turn off internal pull-up resistor
|
// Let the N-side float and measure bleed-down time.
|
||||||
pinMode(LED_N,INPUT);
|
pinMode(LED_N, INPUT);
|
||||||
digitalWrite(LED_N,LOW);
|
digitalWrite(LED_N, LOW); // disable internal pullup
|
||||||
|
|
||||||
// Count how long it takes the diode to bleed back down to a logic zero
|
for (i = 0; i < 30000; i++) {
|
||||||
for ( i = 0; i < 30000; i++) {
|
if (digitalRead(LED_N) == LOW) break;
|
||||||
if ( digitalRead(LED_N)==0) break;
|
|
||||||
}
|
}
|
||||||
// thats it, return result
|
|
||||||
|
// Clean up after sensing: both LED pins low, then caller releases them.
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// ===========================================================================
|
||||||
// Subroutines for morse text
|
// Morse helpers
|
||||||
// =============================================================================
|
// ===========================================================================
|
||||||
// -----------------------------------------------------------------------------
|
void dit(int LED_P)
|
||||||
// Function dit = ".", a dit is as long as a unitLength, used in morse()
|
{
|
||||||
//
|
digitalWrite(LED_P, HIGH);
|
||||||
void dit(int LED_P){
|
delay(unitLength);
|
||||||
digitalWrite( LED_P, HIGH ); delay( unitLength );
|
digitalWrite(LED_P, LOW);
|
||||||
digitalWrite( LED_P, LOW ); delay( unitLength );
|
delay(unitLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
void dah(int LED_P)
|
||||||
// Function dah = "-", a dah is as long as a 3*unitLength
|
{
|
||||||
//
|
digitalWrite(LED_P, HIGH);
|
||||||
void dah(int LED_P){
|
delay(unitLength * 3);
|
||||||
digitalWrite( LED_P, HIGH ); delay( unitLength*3 );
|
digitalWrite(LED_P, LOW);
|
||||||
digitalWrite( LED_P, LOW ); delay( unitLength );
|
delay(unitLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// ===========================================================================
|
||||||
// Function morse:
|
// Morse sender
|
||||||
// hand over text and LED Pins
|
// ===========================================================================
|
||||||
// because you want to use more than one LED hand over also LED Pins
|
void morse(int LED_N, int LED_P)
|
||||||
|
{
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
|
||||||
void morse(int LED_N, int LED_P){
|
const size_t len = strlen_P(morseText);
|
||||||
|
|
||||||
// Because I have more Flash than RAM I decided to use a case structure.
|
for (size_t i = 0; i < len; i++) {
|
||||||
// Its not so pretty, but I can use the RAM to hold my morseText.
|
char c = (char)pgm_read_byte(&morseText[i]);
|
||||||
//
|
|
||||||
// set N-Side of LED Low, so you can set P-side high to let them flash
|
|
||||||
|
|
||||||
pinMode( LED_N, OUTPUT);
|
// Allow lowercase source text without silently breaking timing.
|
||||||
pinMode( LED_P, OUTPUT);
|
if (c >= 'a' && c <= 'z') {
|
||||||
|
c = c - 'a' + 'A';
|
||||||
digitalWrite( LED_N, LOW);
|
|
||||||
|
|
||||||
// send it to defined LED
|
|
||||||
for(int i=0; i<=morseText.length(); i++)
|
|
||||||
{
|
|
||||||
switch( morseText[i] )
|
|
||||||
{
|
|
||||||
|
|
||||||
// Chars A-Z
|
|
||||||
case 'A': // .-
|
|
||||||
dit(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'B': // -...
|
|
||||||
dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'C': // -.-.
|
|
||||||
dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'D': // -..
|
|
||||||
dah(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'E': // .
|
|
||||||
dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'F': // ..-.
|
|
||||||
dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'G': // --.
|
|
||||||
dah(LED_P); dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'H': // ....
|
|
||||||
dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'I': // ..
|
|
||||||
dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'J': // .---
|
|
||||||
dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'K': // .-.-
|
|
||||||
dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'L': // .-..
|
|
||||||
dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'M': // --
|
|
||||||
dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'N': // -.
|
|
||||||
dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'O': // ---
|
|
||||||
dah(LED_P); dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'P': // .--.
|
|
||||||
dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Q': // --.-
|
|
||||||
dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'R': // .-.
|
|
||||||
dit(LED_P); dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'S': // ...
|
|
||||||
dit(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'T': // -
|
|
||||||
dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'U': // ..-
|
|
||||||
dit(LED_P); dit(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'V': // ...-
|
|
||||||
dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'W': // .--
|
|
||||||
dit(LED_P); dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'X': // -..-
|
|
||||||
dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Y': // -.--
|
|
||||||
dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Z': // --..
|
|
||||||
dah(LED_P);dah(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Numbers
|
|
||||||
case '1': // .----
|
|
||||||
dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '2': // ..---
|
|
||||||
dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '3': // ...--
|
|
||||||
dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '4': // ....-
|
|
||||||
dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '5': // .....
|
|
||||||
dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '6': // -....
|
|
||||||
dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '7': // --...
|
|
||||||
dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '8': // ---..
|
|
||||||
dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '9': // ----.
|
|
||||||
dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '0': // -----
|
|
||||||
dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Special Signs
|
|
||||||
case ' ': // Gap
|
|
||||||
delay( unitLength*5 );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '.': // .-.-.-
|
|
||||||
dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ',': // --..--
|
|
||||||
dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '?': // ..--..
|
|
||||||
dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '!': // -.-.--
|
|
||||||
dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ':': // ---...
|
|
||||||
dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ';': // -.-.-.
|
|
||||||
dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '(': // -.--.
|
|
||||||
dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ')': // -.--.-
|
|
||||||
dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '"': // .-..-.
|
|
||||||
dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '@': // .--.-.
|
|
||||||
dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '&': // .-...
|
|
||||||
dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
|
|
||||||
}
|
}
|
||||||
// wait at end of sign
|
|
||||||
delay( unitLength*3 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
bool symbolSent = false;
|
||||||
// That's all, folks :-)
|
|
||||||
// -----------------------------------------------------------------------------
|
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 -----
|
||||||
|
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; // .-...
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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) {
|
||||||
|
delay(unitLength * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
}
|
||||||
@ -0,0 +1,311 @@
|
|||||||
|
/*
|
||||||
|
=============================================================================
|
||||||
|
FireFly Morse Throwie
|
||||||
|
- a light controlled (LED as Sensor) morse blinker throwie with ATTiny85
|
||||||
|
=============================================================================
|
||||||
|
|
||||||
|
Project definitions, sources
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
Version: 0.2 - ATTiny85, 1 MHz, BOD fuse disabled
|
||||||
|
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
|
||||||
|
Date : 2026-06-13
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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> // FIX 3: needed for PROGMEM / pgm_read_byte
|
||||||
|
|
||||||
|
#ifndef cbi
|
||||||
|
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
|
||||||
|
#endif
|
||||||
|
#ifndef sbi
|
||||||
|
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
|
||||||
|
#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 if readability is poor.
|
||||||
|
#define unitLength 100
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// LED pin definitions (N-side = cathode, P-side = anode for sensing/driving)
|
||||||
|
#define LED1_N_SIDE 3
|
||||||
|
#define LED1_P_SIDE 4
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ATTiny85 has 512 bytes SRAM; this frees all of it for the stack.
|
||||||
|
// Only uppercase letters, digits, and the special chars in the switch below.
|
||||||
|
// Test only: 20 x "0" due 0 = "-----" most energy draining
|
||||||
|
const char morseText[] PROGMEM = "0000000000000000000";
|
||||||
|
// Ruler: 0...0....1...1....2 Attention: more Text
|
||||||
|
// Ruler: 1...5....0...5....0 will cost more power!
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Darkness threshold.
|
||||||
|
// Higher value = triggers in brighter conditions.
|
||||||
|
// Best calibrated at actual dusk/dawn with the chosen LED type.
|
||||||
|
unsigned int darknessThreshold = 17000;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Watchdog interrupt flag — volatile because it is written in an ISR
|
||||||
|
volatile boolean f_wdt = 1;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Forward declarations
|
||||||
|
void setup_watchdog(int ii);
|
||||||
|
void system_sleep();
|
||||||
|
unsigned int sensDarkness(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()
|
||||||
|
{
|
||||||
|
// Disable unused peripherals immediately to save some µ/mA.
|
||||||
|
// PRTIM1, PRUSI: genuinely unused, safe to gate off
|
||||||
|
// PRADC: controlled manually around sensDarkness()
|
||||||
|
// PRTIM0: must stay ON — delay() and millis() depend on Timer0
|
||||||
|
PRR = (1 << PRTIM1) | (1 << PRUSI) | (1 << PRADC);
|
||||||
|
|
||||||
|
// ADC is gated via PRR above; also clear ADEN just in case
|
||||||
|
cbi(ADCSRA, ADEN);
|
||||||
|
|
||||||
|
setup_watchdog(9); // 8-second watchdog interval
|
||||||
|
// simple repeat if more delay is need, eg 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Main loop
|
||||||
|
// ===========================================================================
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (f_wdt == 1) {
|
||||||
|
f_wdt = 0;
|
||||||
|
|
||||||
|
// Enable ADC only for the sensing window, then shut it off again.
|
||||||
|
cbi(PRR, PRADC); // un-gate ADC clock
|
||||||
|
sbi(ADCSRA, ADEN); // power ADC on
|
||||||
|
|
||||||
|
if (sensDarkness(LED1_N_SIDE, LED1_P_SIDE) > darknessThreshold) {
|
||||||
|
morse(LED1_N_SIDE, LED1_P_SIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
cbi(ADCSRA, ADEN); // ADC off
|
||||||
|
sbi(PRR, PRADC); // re-gate ADC clock
|
||||||
|
|
||||||
|
// Return LED pins to input (high-Z) before sleeping
|
||||||
|
pinMode(LED1_N_SIDE, INPUT);
|
||||||
|
pinMode(LED1_P_SIDE, INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleep 4 x 8 s = ~32 s between activations
|
||||||
|
for (uint8_t i = 0; i < 1; i++) {
|
||||||
|
system_sleep();
|
||||||
|
}
|
||||||
|
// f_wdt is set to 1 by the WDT ISR when the 8 s expire; no manual clear needed here.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Sleep helpers
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
void system_sleep()
|
||||||
|
{
|
||||||
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||||
|
sleep_enable();
|
||||||
|
sleep_mode(); // CPU halts here until WDT fires
|
||||||
|
sleep_disable();
|
||||||
|
// ADC stays off - re-enable it in loop() only when sensing
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(int ii)
|
||||||
|
{
|
||||||
|
byte bb;
|
||||||
|
if (ii > 9) ii = 9;
|
||||||
|
bb = ii & 7;
|
||||||
|
if (ii > 7) bb |= (1 << 5);
|
||||||
|
bb |= (1 << WDCE);
|
||||||
|
|
||||||
|
MCUSR &= ~(1 << WDRF);
|
||||||
|
WDTCR |= (1 << WDCE) | (1 << WDE); // timed sequence - must not be split
|
||||||
|
WDTCR = bb;
|
||||||
|
WDTCR |= _BV(WDIE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ISR(WDT_vect)
|
||||||
|
{
|
||||||
|
f_wdt = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Light sensor
|
||||||
|
// ===========================================================================
|
||||||
|
// Returns a "darkness level": higher = darker.
|
||||||
|
// Charges the LED junction capacitor, then times how long it takes to bleed
|
||||||
|
// back through the reverse-biased diode to a logic LOW.
|
||||||
|
// ~30000 = pitch black, ~0 = bright light
|
||||||
|
|
||||||
|
// return type is unsigned int (counter can reach 30000, fits in 16 bits)
|
||||||
|
unsigned int sensDarkness(int LED_N, int LED_P)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
// Charge the LED (forward-bias momentarily)
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_N, HIGH);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
|
||||||
|
// Let the N-end float and measure bleed-down time
|
||||||
|
pinMode(LED_N, INPUT);
|
||||||
|
digitalWrite(LED_N, LOW); // disable internal pull-up
|
||||||
|
|
||||||
|
for (i = 0; i < 30000; i++) {
|
||||||
|
if (digitalRead(LED_N) == 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up after sensing
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
return i;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Morse helpers
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
void dit(int LED_P)
|
||||||
|
{
|
||||||
|
digitalWrite(LED_P, HIGH); delay(unitLength);
|
||||||
|
digitalWrite(LED_P, LOW); delay(unitLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dah(int LED_P)
|
||||||
|
{
|
||||||
|
digitalWrite(LED_P, HIGH); delay(unitLength * 3);
|
||||||
|
digitalWrite(LED_P, LOW); delay(unitLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Morse sender
|
||||||
|
// ===========================================================================
|
||||||
|
void morse(int LED_N, int LED_P)
|
||||||
|
{
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
|
||||||
|
// read each character from flash with pgm_read_byte()
|
||||||
|
// use < morseText length, not <= (avoids reading past the null terminator)
|
||||||
|
uint8_t len = strlen_P(morseText);
|
||||||
|
for (uint8_t i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
char c = (char)pgm_read_byte(&morseText[i]);
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
// ----- Letters -----
|
||||||
|
case 'A': dit(LED_P); dah(LED_P); break; // .-
|
||||||
|
case 'B': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // -...
|
||||||
|
case 'C': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); break; // -.-.
|
||||||
|
case 'D': dah(LED_P); dit(LED_P); dit(LED_P); break; // -..
|
||||||
|
case 'E': dit(LED_P); break; // .
|
||||||
|
case 'F': dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); break; // ..-.
|
||||||
|
case 'G': dah(LED_P); dah(LED_P); dit(LED_P); break; // --.
|
||||||
|
case 'H': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // ....
|
||||||
|
case 'I': dit(LED_P); dit(LED_P); break; // ..
|
||||||
|
case 'J': dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); break; // .---
|
||||||
|
case 'K': dah(LED_P); dit(LED_P); dah(LED_P); break; // -.-
|
||||||
|
case 'L': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); break; // .-..
|
||||||
|
case 'M': dah(LED_P); dah(LED_P); break; // --
|
||||||
|
case 'N': dah(LED_P); dit(LED_P); break; // -.
|
||||||
|
case 'O': dah(LED_P); dah(LED_P); dah(LED_P); break; // ---
|
||||||
|
case 'P': dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); break; // .--.
|
||||||
|
case 'Q': dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); break; // --.-
|
||||||
|
case 'R': dit(LED_P); dah(LED_P); dit(LED_P); break; // .-.
|
||||||
|
case 'S': dit(LED_P); dit(LED_P); dit(LED_P); break; // ...
|
||||||
|
case 'T': dah(LED_P); break; // -
|
||||||
|
case 'U': dit(LED_P); dit(LED_P); dah(LED_P); break; // ..-
|
||||||
|
case 'V': dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); break; // ...-
|
||||||
|
case 'W': dit(LED_P); dah(LED_P); dah(LED_P); break; // .--
|
||||||
|
case 'X': dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); break; // -..-
|
||||||
|
case 'Y': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); break; // -.--
|
||||||
|
case 'Z': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); break; // --..
|
||||||
|
|
||||||
|
// ----- Digits -----
|
||||||
|
case '1': dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); break; // .----
|
||||||
|
case '2': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); break; // ..---
|
||||||
|
case '3': dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); break; // ...--
|
||||||
|
case '4': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); break; // ....-
|
||||||
|
case '5': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // .....
|
||||||
|
case '6': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // -....
|
||||||
|
case '7': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // --...
|
||||||
|
case '8': dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); break; // ---.
|
||||||
|
case '9': dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); break; // ----.
|
||||||
|
case '0': dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); break; // -----
|
||||||
|
|
||||||
|
// ----- Punctuation -----
|
||||||
|
case ' ': delay(unitLength * 5); break; // word gap (5 + 3 = 8 units total)
|
||||||
|
case '.': dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); break; // .-.-.-
|
||||||
|
case ',': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); break; // --..--
|
||||||
|
case '?': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); break; // ..--..
|
||||||
|
case '!': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); break; // -.-.--
|
||||||
|
case ':': dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // ---...
|
||||||
|
case ';': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); break; // -.-.-.
|
||||||
|
case '(': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); break; // -.--.
|
||||||
|
case ')': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); break; // -.--.-
|
||||||
|
case '"': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); break; // .-..-.
|
||||||
|
case '@': dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); break; // .--.-.
|
||||||
|
case '&': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // .-...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inter-character gap: 3 units (the dit/dah functions already trail 1 unit,
|
||||||
|
// so this adds the remaining 2 to reach the standard 3-unit gap).
|
||||||
|
// Space case already handles word gap - no extra delay needed there.
|
||||||
|
if (c != ' ') {
|
||||||
|
delay(unitLength * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user