/*wozItDo: A wozItDo is a puzzle based on the attiny85, 3 LEDs, a
push button switch and two button cells. Tony McGregor (c) 31 Mar 2014.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
tony@mcgregor.org.nz
*/
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <setjmp.h> /* jmp_buf, setjmp, longjmp */
#include "wozItDo.h"
//---------------------------------------------------------------------------
// Forward Declarations
void startWatchdog(const uint8 prescalar) ;
void goToSleep(wdTime time, const byte flags);
void halt(void);
void sendMorse(const char *mes, const uint8 len);
uint8 setCount(void);
short getTemp(void);
void setLeds(const byte leds2set, const bool val);
void flash(const byte leds, const wdTime wdTime);
void ledCountdown(const wdTime delay);
void buttonWait(const bool b, const bool leds, wdTime t);
void flashDigit(const byte led, int count);
void pauseWithType(const wdTime t, const byte flags);
void pauseMultiple(wdTime t, const uint8 m);
void deepPause(const wdTime t);
void pause(const wdTime t);
void ledTrain(const bool direction, const uint8 count);
void error(const uint8 code, const uint p, const fatal_t fatal);
void outputPins(const bool on);
void stopWatchdog(void);
void deathMode(void);
void storyMode(void);
void setSpeed(void);
void tempMode(void);
void countMode(void);
void diceMode(void);
//#define DBG
#ifdef DBG
# define DBG_LED_PIN 2
#else
# define DBG_LED_PIN
#endif
const uint8 SW_PIN = 0; //NOTE: Interrupt set for pin0, only INT0 can wake CPU
const uint8 LED_PIN[] = { 1, 4, 3, DBG_LED_PIN };
const byte MAX_LED_ALL = sizeof(LED_PIN) / sizeof(uint8) - 1;// 0 .. MAX_LED_ALL
#ifdef DBG
const byte MAX_LED = MAX_LED_ALL - 1 ;
# define DBG(a) a
# define DBG_LED(state) {digitalWrite(DBG_LED_PIN, state);}
#else
const byte MAX_LED = MAX_LED_ALL;
# define DBG_LED(state)
# define DBG(a)
#endif
#define LED(n) (0b0001 <<(n))
const byte LED0and1 = LED(0) | LED(1);
const byte LED1and2 = LED(1) | LED(2);
const byte LED0and2 = LED(0) | LED(2);
const byte LED0and1and2 = LED(0) | LED(1) | LED(2);
const byte NO_LEDS = 0;
const byte ALL_LEDS = (LED(MAX_LED) <<1) - 1;//all but the debug led
const uint TEMP_OFFSET = 271; //The temperature needs calibrating. The data
// sheet suggests an offset of 275 at 25oC
const wdTime SUPER_FAST_FLASH_TIME = T32ms;
const wdTime EXTRA_FAST_FLASH_TIME = T96ms;
const wdTime FAST_FLASH_TIME = T128ms;
const wdTime MEDIUM_FLASH_TIME = T250ms;
const wdTime DIGIT_GAP_TIME = T500ms; //between flashes
const wdTime NUMBER_GAP_TIME = T2s; //after showing a num in binary
const wdTime RESULT_TIME = T6s; //after an answer (e.g. dice tho)
const ushort MIN_MODE_CHANGE_TIME = 4000; //ms button down to change mode
const ushort MIN_HIDDEN_MODE_CHANGE_TIME = 10000;
const ulong DEBOUNCE_TIME = 32; //ms since last toggle
const uint8 DEFAULT_MODE = 0;
volatile uint8 mode = DEFAULT_MODE;
uint8 lastMode = DEFAULT_MODE+1;
typedef void (*modeFunc)();
const modeFunc modeFuncs[] = { //normal modes
diceMode, countMode, tempMode, setSpeed,
//hidden modes
storyMode, deathMode};
const uint8 MAX_NORMAL_MODE = 3;
const uint8 MAX_MODE = MAX_NORMAL_MODE + 2;
ulong lastToggle = 0; //last time the button changed state
bool returnOnButton; //return to mainline on button down
const ushort IDLE2DEFAULT_TIME = 300; //seconds (300=5min)
const ushort IDLE2STALL_TIME = 8 * 3600; //seconds (3600 = 1 hour)
bool userNotIdle = TRUE; //Inhibit slowdown
volatile bool button = 0; //TRUE=presed, FALSE=up
const wdTime MAIN_LOOP_FAST = T1s; //setSpeed mode cycles through these
const wdTime MAIN_LOOP_MEDIUM = T4s; // idle timeout sets mainloopSleep
const wdTime MAIN_LOOP_SLOW = T8s; // to MAIN_LOOP_BORED
const wdTime MAIN_LOOP_CRAWL = T1m;
const wdTime MAIN_LOOP_BORED = T15m;
const wdTime MAIN_LOOP_DEFAULT = MAIN_LOOP_MEDIUM;
wdTime mainLoopSleep = MAIN_LOOP_DEFAULT;
bool mainLoopButtonWait = FALSE;
volatile ulong lostMillis = 0; //Clock stops while sleeping
#define MILLIS() (millis() + lostMillis)
const uint8 ERROR_REPETITIONS = 3;
volatile bool watchdogInterrupt = FALSE; //a WDog intr woke us up
volatile bool buttonInterrupt = FALSE; //a button inter woke us up
//---------------------------------------------------------------------------
void setup(){
stopWatchdog(); //A non-power-cycle reset doesn't
// stop the watchdog
pinMode(SW_PIN, INPUT);
digitalWrite(SW_PIN, HIGH); //Turn on pull up resistor for the button pin
sbi(GIMSK,PCIE); //Turn on Pin Change interrupt
PCMSK = 1<<PCINT0; //For just PIN 0
outputPins(TRUE);
# if DBG
pinMode(DBG_LED_PIN, OUTPUT);
digitalWrite(DBG_LED_PIN, OFF);
# endif
lastToggle = millis(); //pretend a button was pressed at boot so we
} //don't immediately change mode
jmp_buf env;
//---------------------------------------------------------------------------
//-----------------MAIN LOOP-------------------------------------------------
//---------------------------------------------------------------------------
void loop(){
/*This use of longjmp() is a dreadful wonderful hack. We longjmp()
back here when the mode changes or when a button is pressed if the
mode specific code requests that. We come from inside the button
toggle interrupt service routine and some unknown sequence of code
before that. Quite often that will be goToSleep() from within a
POWER DOWN. We need to reset things to a clean state. I.E. Turn on
interrupts, turn off the watchdog timer, enable the LED outputs.
The alternative it to pepper the code with mode and button check if
statements that gracefully return back through the layers of
function calls. Clean but ugly. Instead we use longjmp() a.k.a. goto
on steroids. */
static bool envSet = FALSE;
if ( ! envSet ){
/*First iteration of loop so we need to setup env to jump back
to. BTW We could probably have avoided envSet by putting this
code in setup() but that would be adding ugly to hideous. */
envSet = TRUE;
if ( uint8 from = setjmp(env) ){
/*setjmp() returns 0 if it was explicitly called and something else
if we got here from a later from a longjmp(). I.E. this code
runs after the longjmp().*/
stopWatchdog();
sei();
outputPins(TRUE);
if ( from == 2 ){ //a button return, not a mode change
goto MODE_RETURN;
}
}
}
//Display new mode
while ( mode != lastMode ){
if ( mode > MAX_MODE ) { error(3,mode,NON_FATAL); mode = 0; }
lastMode = mode;
ledTrain(0, 2);
pause(DIGIT_GAP_TIME);
flashDigit(LED(2), mode+1);
pause(DIGIT_GAP_TIME);
}
//execute main function for the mode
returnOnButton = FALSE;
mainLoopButtonWait = FALSE;
buttonWait(UP, OFF, Tindefinite);
modeFuncs[mode]();
MODE_RETURN:
returnOnButton = FALSE;
buttonWait(UP, OFF, Tindefinite);
if ( mode == lastMode ) {
//If we haven't changed mode and the button hasn't been pressed for
//a long time we either slow down or stop altogether depending on
//how long it is since the button was pressed.
if ( userNotIdle){
userNotIdle = FALSE;
} else {
uint idle = (MILLIS() - lastToggle)/1000;
if ( idle >= IDLE2DEFAULT_TIME ){
mode = DEFAULT_MODE;
if ( idle >= IDLE2STALL_TIME ){
//no user activity for ages, go totally to sleep until a button press
ledCountdown(NUMBER_GAP_TIME);
mainLoopButtonWait = TRUE;
} else {
mainLoopSleep = MAIN_LOOP_BORED;
}
} //little activity from the user
} //idle slowdown not inhibited
/*loop again at the appropriate time. The mode function can
request we wait for a button press before recalling it. If
that's not the case, and we didn't just change mode, we sleep
for the current main loop delay time. This is set in speedMode
and also when we haven't had a button press for a long time.*/
if ( mainLoopButtonWait ){ //set above or in mode code
buttonWait(DOWN, OFF, T15m);
mainLoopButtonWait = FALSE;
} else {
deepPause(mainLoopSleep);
}
}//mode hasn't changed
}
//---------------------------------------------------------------------------
/* We turn all the output pins off when in sleep mode. It may save a
little power (or a lot if one of the LEDs is on). We have to leave
the button pull up resistor on because we need the button to be able
to wake us up from a sleep.*/
inline void outputPins(const bool on){
for (uint8 led = 0; led <= MAX_LED; ++led){
if ( on ){
pinMode(LED_PIN[led], OUTPUT);
} else {
pinMode(LED_PIN[led], INPUT);
digitalWrite(LED_PIN[led], LOW); // ensure pull up resistor is off
}
}//for each led pin, NOT including the debug led (if there is one)
}
//---------------------------------------------------------------------------
wdTime halveWdTime(wdTime t){
if ( t.prescalar > 0 ){
t.prescalar = t.prescalar >>1;
} else {
t.cycles = t.cycles >>1;
}
return t;
}
//---------------------------------------------------------------------------
void flashDigit(const byte led, int count){
if ( count >= 10 ){
for (int z = 0; z < count; ++z){
flash(led, FAST_FLASH_TIME); if ( button ){ return; }
}
} else if ( count == 0 ){
flash(led, SUPER_FAST_FLASH_TIME);
} else {
for (; count > 0; --count){
flash(led, MEDIUM_FLASH_TIME); if ( button ){ return; }
}
}
pause(DIGIT_GAP_TIME);
}
//---------------------------------------------------------------------------
void flash(const byte leds, const wdTime wdTime){
setLeds(leds, HIGH);
pause(halveWdTime(wdTime));
setLeds(leds, LOW);
pause(wdTime);
pause(halveWdTime(wdTime));
}
//---------------------------------------------------------------------------
// Set one or more LEDs on or off.
void setLeds(const byte leds2set, const bool val){
for (uint8 led = 0; led <= MAX_LED_ALL; ++led){
if ( leds2set & LED(led) ){ digitalWrite(LED_PIN[led], val); }
} //for each led including the debug led if there is one
}
//---------------------------------------------------------------------------
void showBinary(const byte num){
for (uint8 led = 0; led <= MAX_LED; ++led){
digitalWrite(LED_PIN[led], (num & LED(led))==0?LOW:HIGH);
} //for each led NOT including the debug led if there is one
}
//---------------------------------------------------------------------------
void ledCountdown(const wdTime delay){
byte bits = ALL_LEDS;
do {
showBinary(bits);
pause(delay);
} while( bits>>=1 );
}
//---------------------------------------------------------------------------
void ledTrain(const bool direction, const uint8 count){
byte leds = 0;
for(uint8 z = 0; z <= count; ++z){
leds = direction ? LED(0) : LED(MAX_LED);
while ( leds <= LED(MAX_LED) && leds != 0 ){
flash(leds, EXTRA_FAST_FLASH_TIME);
leds = direction ? leds<<1 : leds>>1;
}
}
setLeds(ALL_LEDS, OFF);
}
//---------------------------------------------------------------------------
void fadeLeds(const byte leds){
setLeds(leds, ON);
for (int8 led = MAX_LED; led >= 0; --led){
if ( ! leds & LED(led) ){ continue; } //this led isn't included
for (uint8 percent = 100; percent > 0; percent -= percent/20+1){
uint8 frac = 0;
for (uint cycle = 1; cycle <= 500; ++cycle){
if ( (frac += percent) >= 100 ){
frac -= 100;
setLeds(LED(led), ON);
} else {
setLeds(LED(led), OFF);
}
}//light led at a given percentage
if( button ){ return; }
}//each percentage
setLeds(LED(led), OFF);
}//for each led
}
//--------------------------------------------------------------------------
void nextMode(const modeType_t modeType){
++mode;
if ( modeType == HIDDEN && mode <= MAX_NORMAL_MODE ){
mode = MAX_NORMAL_MODE + 1;
}
if ((modeType == NORMAL && mode > MAX_NORMAL_MODE) ||
(modeType == HIDDEN && mode > MAX_MODE) ){
mode = 0;
}
setLeds(ALL_LEDS, OFF);
}
//--------------------------------------------------------------------------
/* Display an error message. There's no exit from this state (until a
reset) Note, we use delay() here so we're not dependent on the
sleep/watchdog interrupt mechanism.*/
void error(const uint8 code, const uint p, const fatal_t fatal){
int shown = 0;
stopWatchdog();
while ( TRUE ){
//something distinctive
byte bits = LED0and2;
for (int z = 0; z < 10; ++z){
setLeds(bits, ON);
delay(300);
setLeds(bits, OFF);
bits = (~bits) & ALL_LEDS;
}
setLeds(ALL_LEDS, OFF);
delay(500);
//display code
for (uint8 z = 0; z < code; ++z){
setLeds(LED(0), ON);
delay(800);
setLeds(LED(0), OFF);
delay(800);
}
delay(1500);
//display parameter
uint8 count = 1;
bits = p>>15;
uint p2 = p << 1;
while ( count <= 16 && bits == 0){ bits = p2>>13; p2 = p2 <<3; count+=3; }
while ( count <= 16 ){
setLeds(ALL_LEDS, ON);
delay(32);
setLeds(ALL_LEDS, OFF);
delay(64);
setLeds(bits, ON);
delay(4000);
setLeds(ALL_LEDS, OFF);
bits = p2>>13;
p2 = p2 <<3;
count += 3;
}
setLeds(ALL_LEDS, OFF);
//loop again, return or wait for a button press depending on how
//many times we've displayed the message and whether the error is
//fatal
if ( shown++ >= ERROR_REPETITIONS ){
if ( NON_FATAL ){ return; }
buttonWait(DOWN, OFF, Tindefinite);
buttonWait(UP, OFF, Tindefinite);
} //waited for a button press
} //loop forever
}
//---------------------------------------------------------------------------
//----------------TEMP MODE--------------------------------------------------
//---------------------------------------------------------------------------
void tempMode(void){
// Flash the temperature in degrees Celsius.
short temp = getTemp();
returnOnButton = TRUE;
if ( temp >= 100 ){
error(1, 1, NON_FATAL);
return;
} else if ( temp <= 0 ){
error(1, 2, NON_FATAL);
return;
} else {
flashDigit(LED(0), temp / 10);
flashDigit(LED(1), temp % 10);
}
}
//---------------------------------------------------------------------------
short getTemp(void){
short wADC;
//The internal temperature has to be used with the internal
// reference of 1.1V. Set the internal reference and mux. NOTE:
// These are different for the ATtiny85 to the ATmega328
ADMUX = ( _BV(REFS1) |
_BV(MUX0) |_BV(MUX1) | _BV(MUX2) | _BV(MUX3) );
sbi(ADCSRA,ADEN); //Switch A2D ON
delay(3); //Wait for 1.1v reference to become stable.
sbi(ADCSRA, ADSC); //Start the ADC conversion
while (bit_is_set(ADCSRA,ADSC)); //wait until ADC is done
//ADCW is ADCH <<8 | ADCL.
wADC = ADCW - TEMP_OFFSET;
cbi(ADCSRA,ADEN); //Switch A2D OFF
return(wADC);
}
//---------------------------------------------------------------------------
//-----------------COUNT MODE------------------------------------------------
//---------------------------------------------------------------------------
const wdTime SHOW_VALUE_TIME = T1s; //How long to show the current count for
const wdTime BLINK_DELAY = T256ms;
const uint8 FINISHED_FLASHES = 50;
const ushort ABANDON_SET_TIME = 20000; //Give up set mode after this many ms
const ushort COUNT_SET_WAIT = 2000; //ms with no press before moving
// to next digit in set mode
void countMode(void){
static uint8 count = 0;
if ( count == 0 ){
count = setCount();
++count; //we pre-decrement count before we display it
} else {
returnOnButton = TRUE;
if ( --count > 0 ){
//normal case
showBinary(count);
pause(SHOW_VALUE_TIME);
setLeds(ALL_LEDS, OFF);
} else {
//time is up, show our "visual alarm"
mainLoopButtonWait = TRUE;//wait for a button press before going again
for (uint8 f = 0; f < FINISHED_FLASHES; ++f){
flash(ALL_LEDS, EXTRA_FAST_FLASH_TIME);
} //each flash
} //time up
}
userNotIdle = 1;
/*Normally, we require a button press to indicate our user is still
around but for this mode a lot of time might go by while we're
still active so we don't start counting idle time until we're done.
Note that, if we've counted down and are waiting for a button to
start again, we wait in the main loop so we never get here and idle
time does count toward the inactivity time.*/
}
//---------------------------------------------------------------------------
/* Flash each bit in turn until the button is pressed. When it's
pressed, toggle the bit on and off until it's not pressed for a short
while at which point move on to the next bit. Once they are all set,
return with the result.*/
uint8 setCount(void){
byte value = ALL_LEDS;
for (byte bit = LED(0); bit <= LED(MAX_LED); bit = bit <<1 ){
//for each bit
ushort count = 0;
while ( !button ){
if ( (count+= WDTIME2MS(BLINK_DELAY)) > ABANDON_SET_TIME ){
//Nothings been pressed, give up on set
goto done;
}
showBinary(value);
pause(BLINK_DELAY);
value ^= bit;
}
buttonWait(UP, ON, Tindefinite);
value |= bit; //default is bit set
showBinary(value);
ushort waited = 0;
while ( waited < COUNT_SET_WAIT ){
while ( !button && (waited < COUNT_SET_WAIT) ){
pause(T16ms); //shortest pause delay
waited+=16;
}
if ( waited < COUNT_SET_WAIT ){
//toggle digit
value ^= bit;
showBinary(value);
waited = 0;
buttonWait(UP, ON, Tindefinite);
}
} //keep looping until delay between button presses is long enough
//to move on to next bit (or finish)
} //for each bit in the number
done:
setLeds(ALL_LEDS, OFF);
return value;
}
//---------------------------------------------------------------------------
//----------------DICE MODE---------------------------------------------------
//---------------------------------------------------------------------------
void diceMode(void){
uint8 result;
uint8 giveUp = 128;
mainLoopButtonWait = TRUE;
while ( !button && giveUp-- > 0 ){
showBinary(random(NO_LEDS,ALL_LEDS));
pause(SUPER_FAST_FLASH_TIME);
showBinary(0);
pause(SUPER_FAST_FLASH_TIME);
pause(SUPER_FAST_FLASH_TIME);
}
buttonWait(UP, OFF, Tindefinite);
returnOnButton = TRUE;
result = random(1,7); //from 1 to 6
if ( result <= 3 ){
//Turn the number into a sequence of bits that long. E.G. For 2, 1<<2 = 4
//4 - 1 = 3 which in binary is 0b011, I.E. two bits
showBinary((1<<result)-1);
pause(RESULT_TIME);
} else {
for(ushort z = 0;
z <= WDTIME2MS(RESULT_TIME);
z += WDTIME2MS(DIGIT_GAP_TIME)){
if ( z != 0 ) { pause(DIGIT_GAP_TIME); } //avoid extra pause at end
showBinary(7);
pause(DIGIT_GAP_TIME);
showBinary(0);
pause(FAST_FLASH_TIME);
showBinary((1<<(result-3)) -1);
pause(DIGIT_GAP_TIME);
}
}
}
//---------------------------------------------------------------------------
//----------Set Speed Mode---------------------------------------------------
//---------------------------------------------------------------------------
const wdTime MAX_BUTTON_WAIT = T3s;
void setSpeed(void){
byte bits = LED(0); //Start at MAIN_LOOP_MEDIUM (1 bit on)
bool increase = TRUE;
static const wdTime speed[] = {
MAIN_LOOP_FAST, MAIN_LOOP_MEDIUM, MAIN_LOOP_SLOW, MAIN_LOOP_CRAWL
};
buttonWait(UP, OFF, Tindefinite);
showBinary(bits);
while ( TRUE ){
buttonWait(DOWN, ON, MAX_BUTTON_WAIT);
if ( ! button ){
//We're done, return delay time
uint8 n;
for (n = 0; bits > 0; bits = bits>>1){ ++n; } //count bits set
mainLoopSleep = speed[n];
nextMode(NORMAL); //Only need to do set speed once
return;
}
//set one more, or one less led depending on which way we're going
bits = increase ? (bits<<1) | 0b001 : (bits>>1);
if ( bits > ALL_LEDS ) { bits >>= 2; increase = FALSE; }
if ( bits == NO_LEDS ) { increase = TRUE; }
showBinary(bits);
//get ready to loop again
buttonWait(UP, ON, Tindefinite);
} //while ( TRUE )
}
//---------------------------------------------------------------------------
//----------------STORY MODE-------------------------------------------------
//---------------------------------------------------------------------------
#define WORD_GAP_TIME T2s
void storyMode(void){
static const char story[] = "I am A McWozItDo? "
"But you know wot cause you're reading this!\n"
"I was born 29Mar2014 and I'm version 0.9\n"
"My daddy is tony@mcgregor.org.nz";
userNotIdle = 1; //See count mode for explanation
returnOnButton = TRUE;
for (char *c = (char *)story; *c != '\0'; ++c){
ushort bits = *c; //MUST be ushort not byte (needs >=9 bits)
for (short i = 0; i < 3; ++i){
flash(ALL_LEDS, SUPER_FAST_FLASH_TIME);
showBinary((bits & 0b111000000) >>6);
pause(NUMBER_GAP_TIME);
bits = bits <<3;
}
setLeds(ALL_LEDS, OFF);
pause(WORD_GAP_TIME);
} //for each byte in message
mainLoopButtonWait = TRUE; //don't restart until a button is pressed
}
//---------------------------------------------------------------------------
//----------------DEATH MODE-------------------------------------------------
//---------------------------------------------------------------------------
const uint8 MORSE_LED = LED(0);
const uint8 LETTER_LED = LED(2); //if != 0, flash between letters
const wdTime MORSE_RATE = T1s;
/*These are standard ratios for international Morse (ITU M.1677-1
10/09) and are defined in multiples of MORSE_RATE. Because we always
include the intra-character and inter-word gaps, MORSE_CHAR_GAP is 2,
not 3 (the "intra-"character gap plus two more) and MORSE_WORD_GAP is
4 not 7.*/
const uint8 MORSE_SHORT_TIME = 1;
const uint8 MORSE_LONG_TIME = 3;
const uint8 MORSE_SIGNAL_GAP = 1;
const uint8 MORSE_CHAR_GAP = 2;
const uint8 MORSE_WORD_GAP = 4;
#define MORSE_LONG_CODE 0b11
#define MORSE_SHORT_CODE 0b10
void deathMode(void){
static const char *message = " Press me or I will die ";
returnOnButton = TRUE;
//Strictly speaking, SOS is one "letter" (prosign)
sendMorse("SOS", 3);
//now send the rest of the message
for (char *c = (char *)message; *c != '\0'; ++c){
if ( *c == ' ' ){
pauseMultiple(MORSE_RATE, MORSE_WORD_GAP);
} else {
if ( *c > 'Z' ){ *c -= 'a' - 'A'; } //Upper case it
sendMorse(c, 1);
}
} //for each character in the message
sendMorse((char *)"SK", 2);
pause(T2s);
//slowing heartbeat
for ( ushort cycles = 1; cycles < 32; cycles += (cycles / 4) + 1){
wdTime pulse;
pulse.prescalar = WD_64ms;
pulse.cycles = cycles;
flash(ALL_LEDS, pulse);
}
//Last chance; fade out the leds one by one
fadeLeds(ALL_LEDS);
//all done, shutdown forever
halt();
}
//---------------------------------------------------------------------------
/* Send a Morse character or prosign. We include a length so that we
can pass sendMorse a single character or a string with a prosign.
Prosigns have no intra letter gaps (they are, effectively, one longer
character).*/
#define mL )<<2 | MORSE_LONG_CODE
#define mS )<<2 | MORSE_SHORT_CODE
#define mN )<<2 | 0
#define mI ((((0
static const byte morse[26] = {
/*A*/ mI mS mL mN mN,
/*B*/ mI mL mS mS mS,
/*C*/ mI mL mS mL mS,
/*D*/ mI mL mS mS mN,
/*E*/ mI mS mN mN mN,
/*F*/ mI mS mS mL mS,
/*G*/ mI mL mL mS mN,
/*H*/ mI mS mS mS mS,
/*I*/ mI mS mS mN mN,
/*J*/ mI mS mL mL mL,
/*K*/ mI mL mS mL mN,
/*M*/ mI mS mL mS mS,
/*M*/ mI mL mL mN mN,
/*N*/ mI mL mS mN mN,
/*O*/ mI mL mL mL mN,
/*P*/ mI mS mL mL mS,
/*Q*/ mI mL mL mS mL,
/*R*/ mI mS mL mS mN,
/*S*/ mI mS mS mS mN,
/*T*/ mI mL mN mN mN,
/*U*/ mI mS mS mL mN,
/*V*/ mI mS mS mS mL,
/*W*/ mI mS mL mL mN,
/*X*/ mI mL mS mS mL,
/*Y*/ mI mL mS mL mL,
/*Z*/ mI mL mL mS mS,
};
void sendMorse(const char *mes, const uint8 len){
for (uint8 i = 0; i < len; ++i){
uint8 index = mes[i] - 'A';
if ( index > 25 ){ error(6, mes[i], FATAL); }
for (byte code = morse[index]; code > 0; code = code <<2 ){
byte signal = (code >>6); //a 2 bit Morse code (short or long)
if ( signal == 0 ){ continue; } //a NULL, do nothing
//send a short or a long
setLeds(MORSE_LED, ON);
if ( signal == MORSE_LONG_CODE ){
pauseMultiple(MORSE_RATE, MORSE_LONG_TIME);
} else if ( signal == MORSE_SHORT_CODE ){
pauseMultiple(MORSE_RATE, MORSE_SHORT_TIME);
} //a long or short
setLeds(MORSE_LED, OFF);
pauseMultiple(MORSE_RATE, MORSE_SIGNAL_GAP);
} //for each 2 bit code (a Morse long or short)
} //for each letter in the character (normally 1)
if ( LETTER_LED ) { setLeds(LETTER_LED, ON); }
pauseMultiple(MORSE_RATE, MORSE_CHAR_GAP);
if ( LETTER_LED ) { setLeds(LETTER_LED, OFF); }
}
//---------------------------------------------------------------------------
//---------------Pause, Sleep and Interrupt Code-----------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
/* Wait for the button to be in the right state. Debounce is handled
in the button state change interrupt routine which also sets the value
of the button variable used here.*/
void buttonWait(const bool b, const bool leds, wdTime t){
if ( b == DOWN && button ){ return; }
if ( b == UP && !button ){ return; }
byte flags = 0;
if ( ! leds ) { flags |= IO_OFF; }
if ( b == DOWN ){ flags |= BUTTON_DOWN; }
if ( b == UP ) { flags |= BUTTON_UP | CLOCK_RUNNING; }
pauseWithType(t, flags);
}
//---------------------------------------------------------------------------
/* pause(), pauseMultiple and deepPause() are just shims for pauseWithType();
pause et al. work like delay. I.E. the main line stops for that
time. But, instead of busy/idling, we put the CPU to sleep and
wake it up with the watchdog timer. That saves a lot of power. The
time is specified using our type wdTime which contains a watchdog
timer prescalar and a number of iterations. There are predefined
values for this structure of the form T10s (10 seconds), T1m (1
minute etc) */
void pause(const wdTime t){
if ( isT0(t) ){ return; }
pauseWithType(t, BUTTON_DOWN);
}
void deepPause(const wdTime t){
pauseWithType(t, BUTTON_DOWN | IO_OFF);
}
void pauseMultiple(wdTime t, const uint8 m){
t.cycles *= m;
pauseWithType(t, BUTTON_DOWN);
}
//---------------------------------------------------------------------------
void pauseWithType(const wdTime t, const byte flags){
if ( BUTTON_OK(flags) ) { return; }
if ( isTindefinite(t) ){
//indefinite: pause (until a button press)
# if BUSY_WAIT
while ( ! BUTTON_OK(flags) ){ }
# else
goToSleep(t, flags | INDEFINITE);
# endif
} else { //not an indefinite wait
# if BUSY_WAIT
ulong stopTime = MILLIS() + WDTIME2MS(t);
while ( MILLIS() < stopTime && ! BUTTON_OK(flags) ){ }
# else
goToSleep(t, flags);
# endif
}
}
//---------------------------------------------------------------------------
/* Put us into a low power sleep mode. A CPU busy/idle loop uses
about 1.5mA. IDLE mode (with timer1 running for millis()) uses
about 800uA. POWER_DOWN with the watchdog timer uses about 7uA and
without the watchdog about 0.5uA. The A2D and voltage reference use
about 190uA. Each led uses about 10mA so they are the main power
guzzlers. Shutting down the output ports doesn't save anything
much unless they were running a LED. Switching them off is more of
a fail safe. The flags control how what we can shutdown while
we're sleeping. We also check if we're in a button debounce period
as this has no explicit end event other than the passing of time.
We mustn't stop the millis() clock during a debounce as it's needed
to know the debounce time is done. The flags also tell us what
events should end a sleep (e.g. a button up event) The time to
sleep is measured in watchdog timer cycles, i.e. a prescalar
interval as defined above (WD_16ms, WD_8ms etc) and a number of
iterations. Waking up and returning to sleep doesn't seem to use
any appreciable power (although the watchdog itself does). If
we're never going to wake, we turn everything off including the
watchdog and the button input.
goToSleep is never called directly, instead we use pause() (or
deepPause()) which works like delay().
The flags are:
CLOCK_RUNNING: use IDLE mode not SHUT_DOWN so millis() clock keep ticking
IO_OFF: Shutdown all IO except button input if needed
BUTTON_DOWN: Exit on a button down event
BUTTON_UP: Exit on a button up event*/
void goToSleep(wdTime time, const byte flags){
cbi(ADCSRA,ADEN); //Switch A2D OFF (shouldn't be on)
if ( flags & IO_OFF ){ outputPins(FALSE); } //Stop driving the output pins
/*This is a wee bit tricky. If we don't need the millis() clock, we
just use the watchdog timer and sleep in POWER DOWN mode, which is
the cheapest. A watchdog or button change interrupt will wake us
up. If we need the millis() clock we don't start the watchdog
timer and we use sleep mode IDLE. This leaves the clock running
but uses a lot more power. We'll wake on a clock interrupt or a
button change interrupt.
When we wake, if it's a button interrupt and the button is now in
the state we want, we're done. If it's a watchdog interrupt we
check if we've had enough watchdog interrupt for the amount of
time we want to sleep and, if we have, we're done. If it's
neither of those it must be a clock interrupt. We check if the
clock shows we're done. If it's none of those we need to wait
longer.If we end sleeping because of the watchdog timer or a
button press, we adjust the MILLIS() clock as best we can.
When we wait again, we may change from using timer interrupt to
the watchdog timer if the reason we used the timer is no longer
valid. In particular, the button debounce time has passed. This
is important because we may well start a long wait just after a
button press and we don't want to use sleep mode IDLE for all
that time. When we do this, we adjust the number of watchdog
interrupt cycles needed.*/
ulong startTime = MILLIS();//Time when we started
ulong endTime = startTime + WDTIME2MS(time);
byte mode; //What sleep mode is in use (IDLE or POWER DOWN)
bool watchdogRunning = FALSE;//Have we started the watchdog timer
bool millisWait = FALSE; //Are we already waiting with the clock running
ulong now; //current time
while ( TRUE ){
now = MILLIS();
if ( !isINDEFINITE(flags) && time.cycles == 0 ){ break; }
if ( !isINDEFINITE(flags) && now >= endTime ) { break; }
if ( BUTTON_OK(flags) ){ break; }
//for one reason or another, we're not done waiting
if ( flags & CLOCK_RUNNING || (now - lastToggle < DEBOUNCE_TIME) ){
/*We need to keep millis() running during the key debounce time
or for some other reason so we can't POWER DOWN. Instead we
idle which leaves the millis() clock running*/
mode = SLEEP_MODE_IDLE;
millisWait = TRUE;
} else {
if ( millisWait ){
/*we were previously waiting on timer interrupts but can now
change to using the watchdog. First we need to adjust the
watchdog cycles for the time we waited (if it was long enough
to matter, mostly is isn't).*/
if ( !(flags&INDEFINITE) &&
(now - startTime) >= WDPS2MS(time.prescalar)>>1){
ushort cyclesDone = ((now - startTime) / WDPS2MS(time.prescalar)) + 1;
if ( cyclesDone >= time.cycles ) { break; } //waited long enough
time.cycles = time.cycles - cyclesDone;
}
millisWait = FALSE;
}
mode = SLEEP_MODE_PWR_DOWN;
if ( !isINDEFINITE(flags) && ! watchdogRunning ){
startWatchdog(time.prescalar);
watchdogRunning = TRUE;
}
}
watchdogInterrupt = FALSE; //set in watchdog interrupt
buttonInterrupt = FALSE; //set in button state change interrupt
set_sleep_mode(mode);
//-------------------------ZZZZZZZZ-----------------------------
sleep_mode(); //Sleep until an interrupt.
//-------------------------zzzzzzzz------------------------------
//woke up because of watchdog interrupt, a counter interrupt or a
//button toggle
if ( watchdogInterrupt ){
lostMillis += WDPS2MS(time.prescalar);
if ( !(flags&INDEFINITE) ){ time.cycles--; }
} else if ( buttonInterrupt && mode == SLEEP_MODE_PWR_DOWN ) {
lostMillis += 1; //assume some time has passed
}
};
//done sleeping
if ( watchdogRunning ){ stopWatchdog(); }
if ( flags & IO_OFF ) { outputPins(TRUE); } //Reenable the output pins
}
//---------------------------------------------------------------------------
/* Go to sleep forever using as little power as possible (0.5uA).
This is really a special case of goToSleep but that's complicated
enough as it is so it's cleaner to reproduce a bit of code here*/
void halt(void){
stopWatchdog(); //Shouldn't be on
cbi(ADCSRA,ADEN); //Switch A2D OFF (shouldn't be on)
cbi(GIMSK,PCIE); //Turn off Pin Change interrupt
PCMSK = 0; // and clear pin change mask just to be sure
digitalWrite(SW_PIN, LOW); //Turn off button pull up resistor
outputPins(FALSE); //Shutdown all outputs except the DBG_LED
# if DBG
pinMode(DBG_LED_PIN, INPUT); //Shutdown debug LED
digitalWrite(DBG_LED_PIN, OFF);
# endif
while ( TRUE ){
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_mode(); //shouldn't ever wake up
}
}
//---------------------------------------------------------------------------
/* The Watch Dog Timer Control Register (WDTCR) has:
7: WDIF Interrupt flag (generated when an interrupt has occurred)
6: WDIE Interrupt Enabled (generate an interrupt not restart). If WDIE is 1
...
This file has been truncated, please download it to see its full contents.
Comments