From 46a2bec5513d44e8003e2dec9d2e0636cccf7653 Mon Sep 17 00:00:00 2001 From: Thomas Gohle Date: Sat, 13 Jun 2026 19:16:56 +0200 Subject: [PATCH] bug (like morse for K) and power improvements --- .../ATTINY85_2026_MorseThrowie.ino | 675 ++++++------------ 1 file changed, 221 insertions(+), 454 deletions(-) diff --git a/firmware/ATTINY85_2026_MorseThrowie/ATTINY85_2026_MorseThrowie.ino b/firmware/ATTINY85_2026_MorseThrowie/ATTINY85_2026_MorseThrowie.ino index 6aba0ad..6114029 100644 --- a/firmware/ATTINY85_2026_MorseThrowie/ATTINY85_2026_MorseThrowie.ino +++ b/firmware/ATTINY85_2026_MorseThrowie/ATTINY85_2026_MorseThrowie.ino @@ -1,138 +1,51 @@ /* ============================================================================= 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 ----------------------------------------------------------------------------- - Version: 0.1 - Attiny85 Version - Date : 25.05.2026 - + 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 I wrote some code to make this - Throwie lasting longer, blinking only at low light levels and morse also - some text. + Inspired by Karl Lunt's FireFly project: http://www.seanet.com/~karllunt/fireflyLED.html - How it works (only the Morsethrowy part): - 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: - + Morse code reference: 'A', ".-" 'B', "-..." 'C', "-.-." 'D', "-.." 'E', "." 'F', "..-." 'G', "--." 'H', "...." 'I', ".." - 'J', ".---" 'K', ".-.-" 'L', ".-.." + '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: - Creative Commons Attribution ShareAlike 3.0.: - http://creativecommons.org/licenses/by-sa/3.0/legalcode + '.', ".-.-.-" ',', "--..--" '?', "..--.." + '!', "-.-.--" ':', "---..." ';', "-.-.-." + '(', "-.--." ')', "-.--.-" '"', ".-..-." + '@', ".--.-." '&', ".-..." + + Legal stuff / Copyright: + License_-_CC_BY-NC_4.0 + 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 #include +#include // FIX 3: needed for PROGMEM / pgm_read_byte -// =========================================================================== -// Some definitions for powersave depending of ATTiny type. #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif @@ -140,405 +53,259 @@ #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif -// =========================================================================== -// for ATTiny85 -// Unit length < 150 will hard to be read for optical morse code -// > 300 are too slow in my feeling for longer text -// -// 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 +// --------------------------------------------------------------------------- +// 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, -// theoretical you can use up to 3 LED. For example you can use it for a -// landmark beacon. So if you point this LEDs to different directions you can -// detect the position in dependence of your location to the bacon. -#define LED1_N_SIDE 3 +// LED pin definitions (N-side = cathode, P-side = anode for sensing/driving) +#define LED1_N_SIDE 3 #define LED1_P_SIDE 4 -// =========================================================================== -// Variables -// =========================================================================== -// ########################################################################### -// # and now the text, but be aware, only 256Byte RAM for Attiny45 # -// # so there are only 22 Chars left for your message. # -// # BTW with an Attiny85 you will have additional 256byte # -// # => more than for twitter :-) # -// # => for FireFly with SuperCap: less ist better, charge last longer # +// --------------------------------------------------------------------------- +// 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! -String morseText = - "TOGO LAB"; -// # "....5....1....5....2.."; just a ruler, remember 22 Chars for ATTiny # -// IT IS USELESS -// HAIL GLOW CLOUD -// ########################################################################### -// Define light trigger threshold. best way to set it on dusk / dawn level -// diffuse red ones are less sensitive than clear green ones... -int darknessThreshold = 17000; +// --------------------------------------------------------------------------- +// Darkness threshold. +// Higher value = triggers in brighter conditions. +// Best calibrated at actual dusk/dawn with the chosen LED type. +unsigned int darknessThreshold = 17000; -// Interrupt Flag, should be volatile, means: read from RAM, not register -// because registers are used for interrupt handling, it have to be volatile. +// --------------------------------------------------------------------------- +// 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 watchdog -// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms -// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec +// Setup // =========================================================================== void setup() { - setup_watchdog(9); + // 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 +// Main loop // =========================================================================== +void loop() +{ + if (f_wdt == 1) { + f_wdt = 0; -void loop(){ + // 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 (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); + 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); } - // set all used port to intput to save power - pinMode(LED1_N_SIDE,INPUT); - pinMode(LED1_P_SIDE,INPUT); - system_sleep(); - f_wdt=0; // 2nd time sleep - system_sleep(); + + // 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. } // =========================================================================== -// Subroutines for Sleeping +// Sleep helpers // =========================================================================== -// set system into the sleep state -// system wakes up when wtchdog is timed out -void system_sleep() { - cbi(ADCSRA,ADEN); // switch Analog to - // Digitalconverter OFF - set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here +void system_sleep() +{ + set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); - - sleep_mode(); // System sleeps here - - sleep_disable(); // System continues execution here when - // watchdog timed out - sbi(ADCSRA,ADEN); // switch Analog to Digitalconverter ON + sleep_mode(); // CPU halts here until WDT fires + sleep_disable(); + // ADC stays off - re-enable it in loop() only when sensing } -// ---------------------------------------------------------------------------- -// Setup for watchdog. Parameter ii for sleeping time: -// 0 = 16ms, 1 = 32ms, 2 = 64ms, -// 3 = 128ms, 4 = 250ms, 5 = 500ms -// 6 = 1 sec, 7 = 2 sec, 8 = 4 sec, -// 9 = 8 sec -void setup_watchdog(int ii) { - +// 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; - int ww; - if (ii > 9 ) ii=9; - bb=ii & 7; - if (ii > 7) bb|= (1<<5); - bb|= (1< 9) ii = 9; + bb = ii & 7; + if (ii > 7) bb |= (1 << 5); + bb |= (1 << WDCE); - MCUSR &= ~(1<