574 lines
18 KiB
C++
574 lines
18 KiB
C++
/*
|
|
=============================================================================
|
|
FireFly Morse Throwie
|
|
- a light controled (LED as Sensor) morseblinker throwie with ATTiny45/85
|
|
=============================================================================
|
|
|
|
Project definitions, sources
|
|
-----------------------------------------------------------------------------
|
|
Version: 0.5 - Attiny45 Version
|
|
Date : 23.02.2014
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
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.
|
|
http://www.seanet.com/~karllunt/fireflyLED.html
|
|
|
|
How it works:
|
|
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
|
|
|
|
Programming:
|
|
by Arduino IDE + ATTiny extension:
|
|
http://highlowtech.org/?p=1695
|
|
|
|
an USB Programmer for ATMELs or you can use an Arduino UNO to do this.
|
|
http://highlowtech.org/?p=1706
|
|
|
|
Partslist:
|
|
|
|
1x ATTiny45 (or Attiny85 but I had have a couple of 45er left, not tryed
|
|
out at a *85),
|
|
|
|
1x LED (all typ will work, but you have to adjust the light sensity
|
|
(see --> "darknessThreshold"). Clear plastic LED types are more light
|
|
sensitiv,
|
|
|
|
1x 100 Ohm Resistor
|
|
|
|
1x CR2032 Battery other batterytypes also good. everything was provide a
|
|
Voltage from 3V...6V.
|
|
I have also one version working with a 1F Goldcap and a small 5V solar
|
|
module.
|
|
1x rare earth magnet, because it is a throwie ;-), but not need for working
|
|
|
|
~x some tape or other stuff to protect the throwie
|
|
that's up to you.
|
|
|
|
The wiring, it's very simple:
|
|
|
|
+---+ +------\/------+
|
|
| | ATTINY45 /85 |
|
|
> | -+1=PB5 VCC=8+-> to battery Vcc 3V
|
|
100 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 ohm resistor
|
|
<
|
|
|
|
|
|
|
|
-----
|
|
/ \ 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', "-.-."
|
|
'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:
|
|
Creative Commons Attribution ShareAlike 3.0.:
|
|
http://creativecommons.org/licenses/by-sa/3.0/legalcode
|
|
-----------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
// ===========================================================================
|
|
// 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/wdt.h>
|
|
|
|
// ===========================================================================
|
|
// Some definitions for powersave depending of ATTiny type.
|
|
#ifndef cbi
|
|
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
|
|
#endif
|
|
#ifndef sbi
|
|
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
|
|
#endif
|
|
|
|
// ===========================================================================
|
|
// for ATTiny45
|
|
// Unit length < 150 will hard to be read for optical morse code
|
|
// > 300 are too slow in my feeling for longer text
|
|
//
|
|
// for ATTiny85
|
|
// Unit length < 5 will hard to be read for optical morse code
|
|
// > 10 are too slow in my feeling for longer text
|
|
//
|
|
|
|
#define unitLength 200
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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 4
|
|
#define LED1_P_SIDE 3
|
|
|
|
// ===========================================================================
|
|
// 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 :-) #
|
|
|
|
String morseText =
|
|
"ICL Q1-16";
|
|
// # "....5....1....5....2.."; just a ruler, remember 22 Chars for ATTiny45 #
|
|
// IT IS USELESS
|
|
// ALL HAIL THE GLOW CLOUD
|
|
// ALL MY VICES ARE DEVICES
|
|
// I HATE YOU SO MUCH
|
|
// THE INSIDES OF THINGS ARE BEAUTIFUL
|
|
// LIBERTAS PER SCIENTIAM (Freedom through Knowledge)
|
|
// http://en.wikipedia.org/wiki/List_of_mottos
|
|
// ###########################################################################
|
|
|
|
// 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;
|
|
|
|
// Interrupt Flag, should be volatile, means: read from RAM, not register
|
|
// because registers are used for interrupt handling, it have to be volatile.
|
|
volatile boolean f_wdt = 1;
|
|
|
|
// ===========================================================================
|
|
// 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
|
|
// ===========================================================================
|
|
void setup()
|
|
{
|
|
setup_watchdog(9);
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Main
|
|
// ===========================================================================
|
|
|
|
void loop(){
|
|
|
|
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);
|
|
}
|
|
}
|
|
// 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();
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Subroutines for Sleeping
|
|
// ===========================================================================
|
|
// 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
|
|
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
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 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) {
|
|
|
|
byte bb;
|
|
int ww;
|
|
if (ii > 9 ) ii=9;
|
|
bb=ii & 7;
|
|
if (ii > 7) bb|= (1<<5);
|
|
bb|= (1<<WDCE);
|
|
ww=bb;
|
|
|
|
MCUSR &= ~(1<<WDRF);
|
|
|
|
WDTCR |= (1<<WDCE) | (1<<WDE); // start timed sequence
|
|
|
|
WDTCR = bb; // set new watchdog timeout value
|
|
WDTCR |= _BV(WDIE);
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
// Watchdog Interrupt Service / is executed when watchdog timed out
|
|
ISR(WDT_vect) {
|
|
f_wdt=1; // set global flag
|
|
}
|
|
|
|
// =============================================================================
|
|
// Subroutines for light sensor
|
|
// =============================================================================
|
|
// 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
|
|
|
|
// charge the capacitor of LED
|
|
pinMode(LED_N,OUTPUT);
|
|
pinMode(LED_P,OUTPUT);
|
|
digitalWrite(LED_N,HIGH);
|
|
digitalWrite(LED_P,LOW);
|
|
|
|
// Isolate the N end of the diode and turn off internal pull-up resistor
|
|
pinMode(LED_N,INPUT);
|
|
digitalWrite(LED_N,LOW);
|
|
|
|
// Count how long it takes the diode to bleed back down to a logic zero
|
|
for ( i = 0; i < 30000; i++) {
|
|
if ( digitalRead(LED_N)==0) break;
|
|
}
|
|
// thats it, return result
|
|
return i;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Subroutines for morse text
|
|
// =============================================================================
|
|
// -----------------------------------------------------------------------------
|
|
// Function dit = ".", a dit is as long as a unitLength, used in morse()
|
|
//
|
|
void dit(int LED_P){
|
|
digitalWrite( LED_P, HIGH ); delay( unitLength );
|
|
digitalWrite( LED_P, LOW ); delay( unitLength );
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Function dah = "-", a dah is as long as a 3*unitLength
|
|
//
|
|
void dah(int LED_P){
|
|
digitalWrite( LED_P, HIGH ); delay( unitLength*3 );
|
|
digitalWrite( LED_P, LOW ); delay( unitLength );
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Function morse:
|
|
// 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){
|
|
|
|
// Because I have more Flash than RAM I decided to use a case structure.
|
|
// Its not so pretty, but I can use the RAM to hold my morseText.
|
|
//
|
|
// set N-Side of LED Low, so you can set P-side high to let them flash
|
|
|
|
pinMode( LED_N, OUTPUT);
|
|
pinMode( LED_P, OUTPUT);
|
|
|
|
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 );
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// That's all, folks :-)
|
|
// -----------------------------------------------------------------------------
|