diff --git a/README.md b/README.md index 521b7cd..003608a 100755 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ I have translated most of the original sample programs to the library here: http Be aware that this library is a work in progress. In particular,
* the accelerometer api will change -* power consumption ~~has not been optimized~~ [is about as good as it is going to get](https://github.com/dhiltonp/hexbright/tree/master/experiments/power_draw#optimizing-power-draw). +* power consumption has not been optimized ~~is about as good as it is going to get~~ [(see notes)](https://github.com/dhiltonp/hexbright/tree/master/experiments/power_draw#optimizing-power-draw). Enjoy! diff --git a/experiments/power_draw/README.md b/experiments/power_draw/README.md index 025dd9e..78449a4 100644 --- a/experiments/power_draw/README.md +++ b/experiments/power_draw/README.md @@ -30,6 +30,18 @@ consumes 80-90% of the 5.4 mA in standby. This would vary based on the differenc I believe Christian put the power regulator at its point in order to prevent any power draw when the light is off, but as a consequence the standby time is 5-10x shorter than it would otherwise be. +I need to run some numbers, calculating the difference between expected and observed power loss. + +Formula for power loss in the voltage regulator: [(4.2 - 3.3) * .500 + (4.2*.09)](http://en.wikipedia.org/wiki/Low-dropout_regulator#Efficiency_and_Heat_Dissipation)
+.5 milliamps consumed by other components would cause a power loss of .83 milliamps, with 1.33 milliamps used
+1 milliamp consumed, loss of 1.27 milliamps, 2.27 milliamps used
+2 milliamps consumed, loss of 2.178, 4.178 milliamps used
+3 milliamps consumed, loss of 3.078, 6.078 milliamps used
+ +2.64 milliamps consumed, loss of 2.75 milliamps would be 5.4 milliamps used. +We are probably powering about 2.64 milliamps. Lowering the power used for each device will roughly double the savings listed on the data sheet. + + Raw Data -------- Recorded with a Fluke 287 diff --git a/hardware/hexbright/HexBright-Schematic-MCU.jpg b/hardware/hexbright/HexBright-Schematic-MCU.jpg new file mode 100644 index 0000000..8fd55ee Binary files /dev/null and b/hardware/hexbright/HexBright-Schematic-MCU.jpg differ diff --git a/hardware/hexbright/avr/boards.txt b/hardware/hexbright/avr/boards.txt new file mode 100644 index 0000000..a54024f --- /dev/null +++ b/hardware/hexbright/avr/boards.txt @@ -0,0 +1,48 @@ +############################################################## + +hexbright.name=Hexbright + +hexbright.upload.protocol=arduino +hexbright.upload.tool=arduino:avrdude +hexbright.upload.maximum_size=14336 +hexbright.upload.maximum_data_size=1024 +hexbright.upload.speed=19200 + +hexbright.bootloader.low_fuses=0xe2 +hexbright.bootloader.high_fuses=0xdd +hexbright.bootloader.extended_fuses=0x00 +hexbright.bootloader.path=hexbright:atmega +hexbright.bootloader.file=ATmegaBOOT_168_hexbright.hex +hexbright.bootloader.unlock_bits=0x3F +hexbright.bootloader.lock_bits=0x0F + +hexbright.build.mcu=atmega168 +hexbright.build.f_cpu=8000000L +hexbright.build.core=arduino:arduino +hexbright.build.variant=arduino:standard + + +############################################################## + +hexbright_tiny.name=Hexbright Tinyboot + +hexbright_tiny.upload.protocol=arduino +hexbright_tiny.upload.tool=arduino:avrdude +hexbright_tiny.upload.maximum_size=15360 +hexbright_tiny.upload.maximum_data_size=1024 +hexbright_tiny.upload.speed=19200 + +hexbright_tiny.bootloader.low_fuses=0xe2 +hexbright_tiny.bootloader.high_fuses=0xdd +hexbright_tiny.bootloader.extended_fuses=0x02 +hexbright_tiny.bootloader.path=hexbright:atmega +hexbright_tiny.bootloader.file=ATmegaBOOT_168_hexbright_tiny.hex +hexbright_tiny.bootloader.unlock_bits=0x3F +hexbright_tiny.bootloader.lock_bits=0x0F + +hexbright_tiny.build.mcu=atmega168p +hexbright_tiny.build.f_cpu=8000000L +hexbright_tiny.build.core=arduino:arduino +hexbright_tiny.build.variant=arduino:standard + + diff --git a/hb-examples/alarm_clock/alarm_clock.ino b/hb-examples/alarm_clock/alarm_clock.ino old mode 100644 new mode 100755 index 7a76eb9..a4ba3b3 --- a/hb-examples/alarm_clock/alarm_clock.ino +++ b/hb-examples/alarm_clock/alarm_clock.ino @@ -1,5 +1,10 @@ +#include +#include +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include #include hexbright hb; @@ -35,14 +40,14 @@ void loop() { mode = SET_MODE; hb.set_light(0, 150, 300); } - if(!hb.printing_number()) { - hb.print_charge(GLED); + if(!printing_number()) { + print_charge(GLED); } break; case SET_MODE: - hb.input_digit(duration*10, duration*10+time[place]); + input_digit(duration*10, duration*10+time[place]); if(hb.button_just_released() && hb.button_pressed_time()<300) { - duration = hb.get_input_digit(); + duration = get_input_digit(); place++; } if(place==PLACES) { @@ -60,9 +65,9 @@ void loop() { mode = WAKE_MODE; } else { // display our current wait time... - if(!hb.printing_number()) { + if(!printing_number()) { // print hours, and minutes remaining - hb.print_number(duration_remaining(duration)); + print_number(duration_remaining(duration)); } } break; @@ -80,4 +85,4 @@ int duration_remaining(int duration) { minutes_remaining+=60; } return hours_remaining*100+minutes_remaining; -} +} diff --git a/hb-examples/down_light/down_light.ino b/hb-examples/down_light/down_light.ino old mode 100644 new mode 100755 index 351400d..5e8d6f4 --- a/hb-examples/down_light/down_light.ino +++ b/hb-examples/down_light/down_light.ino @@ -26,11 +26,12 @@ The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include - hexbright hb; void setup() { @@ -73,5 +74,5 @@ void loop() { hb.set_light(CURRENT_LEVEL, 200, 50); } } - hb.print_power(); -} + print_power(); +} diff --git a/hb-examples/numeric_input/numeric_input.ino b/hb-examples/numeric_input/numeric_input.ino index 798c1f4..7a56adb 100755 --- a/hb-examples/numeric_input/numeric_input.ino +++ b/hb-examples/numeric_input/numeric_input.ino @@ -1,5 +1,8 @@ +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include hexbright hb; @@ -16,14 +19,14 @@ unsigned int value = 0; void loop() { hb.update(); if(hb.button_just_released() && hb.button_pressed_time()<300) { // on button press, we could do it based on whatever input signal we want. - value = hb.get_input_digit(); + value = get_input_digit(); } else if (hb.button_just_released()) { - value = hb.get_input_digit(); + value = get_input_digit(); hb.set_light(CURRENT_LEVEL, value<1 ? 1 : value, 300); value = 0; } else if (hb.button_just_pressed() && hb.button_released_time()<500) { hb.set_light(CURRENT_LEVEL, OFF_LEVEL, NOW); value = 0; } - hb.input_digit(value*10, value*10+10); -} + input_digit(value*10, value*10+10); +} diff --git a/hb-examples/stroboscope/stroboscope.ino b/hb-examples/stroboscope/stroboscope.ino index 95d6d15..48fa88e 100755 --- a/hb-examples/stroboscope/stroboscope.ino +++ b/hb-examples/stroboscope/stroboscope.ino @@ -1,6 +1,9 @@ -// uncomment (delete // before) '#define STROBE' in hexbright.h +#include +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include hexbright hb; @@ -17,7 +20,7 @@ int mode = OFF_MODE; void loop() { hb.update(); - + if(mode==OFF_MODE) { if(hb.button_pressed_time()<200 && hb.button_just_released()) { fpm = 2000; @@ -27,7 +30,7 @@ void loop() { if(mode==STROBE_MODE) { // turn off? if (hb.button_pressed_time()>200) { - hb.set_strobe_delay(STROBE_OFF); // if this line doesn't compile, uncomment STROBE in hexbright.h + set_strobe_delay(STROBE_OFF); // if this line doesn't compile, uncomment STROBE in hexbright.h hb.set_light(CURRENT_LEVEL, OFF_LEVEL, NOW); mode = OFF_MODE; return; @@ -44,7 +47,7 @@ void loop() { } while (Serial.available()>0); if(buffer>0) { fpm = buffer; - hb.set_strobe_fpm(buffer); + set_strobe_fpm(buffer); } char spin = hb.get_spin(); @@ -54,13 +57,13 @@ void loop() { else fpm /= 1+spin/200.0; //Serial.println(avg_spin); - hb.set_strobe_fpm(fpm); + set_strobe_fpm(fpm); } - if(!hb.printing_number()) { - Serial.println(hb.get_strobe_fpm()); - Serial.println(hb.get_strobe_error()); - hb.print_number(hb.get_strobe_fpm()); + if(!printing_number()) { + Serial.println(get_strobe_fpm()); + Serial.println(get_strobe_error()); + print_number(get_strobe_fpm()); } } -} +} diff --git a/hb-examples/unplugged_action/unplugged_action.ino b/hb-examples/unplugged_action/unplugged_action.ino index f36b982..c8bbbd3 100755 --- a/hb-examples/unplugged_action/unplugged_action.ino +++ b/hb-examples/unplugged_action/unplugged_action.ino @@ -1,5 +1,8 @@ +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include hexbright hb; @@ -38,7 +41,7 @@ void loop() { mode=AUTO_OFF_MODE; auto_off_start_time=millis(); } else { // flash LED because we're plugged in - hb.print_charge(GLED); + print_charge(GLED); } } else if (mode==AUTO_OFF_MODE) { if(millis()-auto_off_start_time > AUTO_OFF_WAIT) { @@ -46,4 +49,4 @@ void loop() { } } } - + diff --git a/libraries/Readme.md b/libraries/Readme.md new file mode 100644 index 0000000..f66a881 --- /dev/null +++ b/libraries/Readme.md @@ -0,0 +1,14 @@ +Planned structure in moving to a framework: + +3 key directories: + +hb_modes - a collection of light modes (like light_spin, light_down, strobe_serial, selector_tap)
+hb_utilities - things like print_number, click_counter, advanced +accelerometer interfaces. In general, code that expands on the core +library without any dependencies.
+hexbright - core library, plus .h files for overridable +options/behavior (low_battery, overheat, set_light_level)
+ +hexbright functions by itself, but hb_utilities provide convenience functions and in some cases alter the behavior of the core library. + +hb_modes simply provides an easy way to customize and share code. diff --git a/libraries/hb_utilities/click_counter.cpp b/libraries/hb_utilities/click_counter.cpp new file mode 100644 index 0000000..654c16a --- /dev/null +++ b/libraries/hb_utilities/click_counter.cpp @@ -0,0 +1,49 @@ +#include "click_counter.h" + +byte clickState; +byte clickCount; +word max_click_time; + +void config_click_count(word click_time) { + max_click_time = click_time; + clickState=0; +} + +char click_count() { + switch(clickState) { + case CLICK_OFF: + if(hexbright::button_just_pressed()) { + // click just started + clickState = CLICK_ACTIVE; + clickCount=0; + //Serial.println("Clicking just started"); + } + break; + case CLICK_ACTIVE: + if(hexbright::button_just_released()) { + if(hexbright::button_pressed_time() > max_click_time) { + // button held to long + //Serial.println("Click held too long"); + clickState = CLICK_OFF; + } else { + // click is counted + clickState = CLICK_WAIT; + clickCount++; + //Serial.println("Click counted"); + } + } + break; + case CLICK_WAIT: + // button is released for long enough, we're done clicking + if(hexbright::button_released_time() > max_click_time) { + clickState = CLICK_OFF; + //Serial.print("Click finished: "); Serial.println((int)clickCount); + return clickCount; + } else if(hexbright::button_pressed()) { + // move back to active state + clickState = CLICK_ACTIVE; + //Serial.println("Click active"); + } + } + return -127; +} diff --git a/libraries/hb_utilities/click_counter.h b/libraries/hb_utilities/click_counter.h new file mode 100644 index 0000000..41f8201 --- /dev/null +++ b/libraries/hb_utilities/click_counter.h @@ -0,0 +1,20 @@ +#ifndef CLICK_COUNTER_H +#define CLICK_COUNTER_H + + +#include "hexbright.h" + +#define CLICK_OFF 0 +#define CLICK_ACTIVE 1 +#define CLICK_WAIT 2 + +// Call in setup loop if you want to use the click counter +// click_time is the maximum time a click can take before the counter resets +extern void config_click_count(word click_time); + +// The number of clicks <= click_time. Will not return a count until click_time ms after the button release. +// Will return -127 unless returning a valid count. +extern char click_count(); + + +#endif //CLICK_COUNTER_H diff --git a/libraries/hb_utilities/input_digit.cpp b/libraries/hb_utilities/input_digit.cpp new file mode 100644 index 0000000..6bb0fba --- /dev/null +++ b/libraries/hb_utilities/input_digit.cpp @@ -0,0 +1,21 @@ +#include "input_digit.h" + +static unsigned int read_value = 0; + +unsigned int get_input_digit() { + return read_value; +} + +void input_digit(unsigned int min_digit, unsigned int max_digit) { + unsigned int tmp2 = 999 - atan2(hexbright::vector(0)[0], hexbright::vector(0)[2])*159 - 500; // scale from 0-999, counterclockwise = higher + tmp2 = (tmp2*(max_digit-min_digit))/1000+min_digit; + if(tmp2 == read_value) { + if(!printing_number()) { + print_number(tmp2); + } + } else { + reset_print_number(); + hexbright::set_led(GLED,100); + } + read_value = tmp2; +} diff --git a/libraries/hb_utilities/input_digit.h b/libraries/hb_utilities/input_digit.h new file mode 100644 index 0000000..4cdfcc2 --- /dev/null +++ b/libraries/hb_utilities/input_digit.h @@ -0,0 +1,19 @@ +#ifndef INPUT_DIGIT_H +#define INPUT_DIGIT_H + +#include "hexbright.h" +#include "print_number.h" + +// reads a value between min_digit to max_digit-1, see hb-examples/numeric_input +// Twist the light to change the value. When the current value changes, +// the green LED will flash, and the number that is currently being printed +// will be reset. Suppose you are at 2 and you want to go to 5. Rotate +// clockwise 3 green flashes, and you'll be there. +// The current value is printed through the rear leds. +// Get the result with get_input_digit. +extern void input_digit(unsigned int min_digit, unsigned int max_digit); + +// grab the value that is currently selected (based on twist orientation) +extern unsigned int get_input_digit(); + +#endif // INPUT_DIGIT_H diff --git a/libraries/hb_utilities/print_binary.cpp b/libraries/hb_utilities/print_binary.cpp new file mode 100644 index 0000000..27c49eb --- /dev/null +++ b/libraries/hb_utilities/print_binary.cpp @@ -0,0 +1,19 @@ +#include + +void print_binary(int value) { + String s = String(value, BIN); + + while(s.length()<16) { + s = "0"+s; + } + Serial.println(s); +} + +void print_binary(byte value) { + String s = String(value, BIN); + + while(s.length()<8) { + s = "0"+s; + } + Serial.println(s); +} diff --git a/libraries/hb_utilities/print_binary.h b/libraries/hb_utilities/print_binary.h new file mode 100644 index 0000000..55f21fa --- /dev/null +++ b/libraries/hb_utilities/print_binary.h @@ -0,0 +1,20 @@ +#ifndef PRINT_BINARY_H +#define PRINT_BINARY_H + +#include +/* +// these 2 includes are needed for print_binary to actually print. +// Arduino is trying to pull in this file when it's not asked for, causing +// dependency issues/build errors. +// Commenting out these includes will mean that any code that calls print_binary +// without including Serial.h and String.h in the .ino file will just fail to +// print (instead of printing a warning as designed). +#include +#include +*/ + +// convenience functions that will print 1 or 2 byte values through the serial port. +extern void print_binary(byte value); +extern void print_binary(int value); + +#endif // PRINT_BINARY_H diff --git a/libraries/hb_utilities/print_number.cpp b/libraries/hb_utilities/print_number.cpp new file mode 100644 index 0000000..c9d3a3a --- /dev/null +++ b/libraries/hb_utilities/print_number.cpp @@ -0,0 +1,80 @@ +#include "print_number.h" + +long _number = 0; +unsigned char _color = GLED; +int print_wait_time = 0; + + +unsigned char flip_color(unsigned char color) { + return (color+1)%2; +} + +BOOL printing_number() { + return _number || print_wait_time; +} + +void reset_print_number() { + _number = 1; + print_wait_time = 0; +} + +void update_number() { + if(_number>0) { // we have something to do... +#if (DEBUG==DEBUG_NUMBER) + static int last_printed = 0; + if(last_printed != _number) { + last_printed = _number; + Serial.print("number remaining (read from right to left): "); + Serial.println(_number); + } +#endif + if(!print_wait_time) { + if(_number==1) { // minimum delay between printing numbers + print_wait_time = 2500/UPDATE_DELAY; + _number = 0; + return; + } else { + print_wait_time = 300/UPDATE_DELAY; + } + if(_number/10*10==_number) { +#if (DEBUG==DEBUG_NUMBER) + Serial.println("zero"); +#endif + // print_wait_time = 500/UPDATE_DELAY; + hexbright::set_led(_color, 400); + } else { + hexbright::set_led(_color, 120); + _number--; + } + if(_number && !(_number%10)) { // next digit? + print_wait_time = 600/UPDATE_DELAY; + _color = flip_color(_color); + _number = _number/10; + } + } + } + + if(print_wait_time) { + print_wait_time--; + } +} + +void print_number(long number) { + // reverse number (so it prints from left to right) + BOOL negative = false; + if(number<0) { + number = 0-number; + negative = true; + } + _color = GLED; + _number=1; // to guarantee printing when dealing with trailing zeros (100 can't be stored as 001, use 1001) + do { + _number = _number * 10 + (number%10); + number = number/10; + _color = flip_color(_color); + } while(number>0); + if(negative) { + hexbright::set_led(flip_color(_color), 500); + print_wait_time = 600/UPDATE_DELAY; + } +} diff --git a/libraries/hb_utilities/print_number.h b/libraries/hb_utilities/print_number.h new file mode 100644 index 0000000..239f523 --- /dev/null +++ b/libraries/hb_utilities/print_number.h @@ -0,0 +1,25 @@ +#ifndef PRINT_NUMBER_H +#define PRINT_NUMBER_H + +#include "hexbright.h" + +// returns the opposite color from the one passed in +// Takes up 12 bytes. +extern unsigned char flip_color(unsigned char color); + +// prints a number through the rear leds +// 120 = 1 red flashes, 2 green flashes, one long red flash (0), 2 second delay. +// the largest printable value is +/-999,999,999, as the left-most digit is reserved. +// negative numbers begin with a leading long flash. +extern void print_number(long number); + +// currently printing a number +extern BOOL printing_number(); + +// reset printing; this immediately terminates the currently printing number. +extern void reset_print_number(); + +// internal interface, automatically called during update +extern void update_number(); + +#endif // PRINT_NUMBER_H diff --git a/libraries/hb_utilities/print_power.cpp b/libraries/hb_utilities/print_power.cpp new file mode 100644 index 0000000..a42fb98 --- /dev/null +++ b/libraries/hb_utilities/print_power.cpp @@ -0,0 +1,18 @@ +#include "print_power.h" + +void print_power() { + print_charge(GLED); + if (hexbright::low_voltage_state() && hexbright::get_led_state(RLED) == LED_OFF) { + hexbright::set_led(RLED,50,1000); + } +} + +void print_charge(unsigned char led) { + unsigned char charge_state = hexbright::get_charge_state(); + if(charge_state == CHARGING && hexbright::get_led_state(led) == LED_OFF) { + hexbright::set_led(led, 350, 350); + } else if (charge_state == CHARGED) { + hexbright::set_led(led,50); + } +} + diff --git a/libraries/hb_utilities/print_power.h b/libraries/hb_utilities/print_power.h new file mode 100644 index 0000000..1956ea5 --- /dev/null +++ b/libraries/hb_utilities/print_power.h @@ -0,0 +1,31 @@ +#ifndef PRINT_POWER_H +#define PRINT_POWER_H + +#include "hexbright.h" + +// A convenience function that will print the charge state over the led specified +// CHARGING = 350 ms on, 350 ms off. +// CHARGED = solid on +// BATTERY = nothing. +// If you are using print_number, call it before this function if possible. +// I recommend the following (if using print_number()): +// ...code that may call print number... +// if(!printing_number()) +// print_charge(GLED); +// ...end of loop... +// See also: print_power +extern void print_charge(unsigned char led); + +// prints charge state (using print_charge). +// if in a low battery state, flashes red for 50 ms, followed by a 1 second delay +// If you are using print_number, call it before this function if possible. +// I recommend the following (if using print_number()): +// ...code that may call print number... +// if(!printing_number()) +// print_power(); +// ...end of loop... +// see also print_charge for usage +extern void print_power(); + + +#endif // PRINT_POWER_H diff --git a/libraries/hb_utilities/strobe.cpp b/libraries/hb_utilities/strobe.cpp new file mode 100644 index 0000000..ae0534e --- /dev/null +++ b/libraries/hb_utilities/strobe.cpp @@ -0,0 +1,33 @@ +#include "strobe.h" + +unsigned long next_strobe = STROBE_OFF; +unsigned long strobe_delay = 0; +int strobe_duration = 100; + + + +///////////////STROBE CONTROL////////////////// + +void set_strobe_delay(unsigned long delay) { + strobe_delay = delay; + next_strobe = micros()+strobe_delay; +} + +void set_strobe_duration(int duration) { + strobe_duration = duration; +} + +void set_strobe_fpm(unsigned int fpm) { + set_strobe_delay(60000000/fpm); +} + +unsigned int get_strobe_fpm() { + return 60000000 / (strobe_delay/8*8); +} + +unsigned int get_strobe_error() { + // 90000000 because we have an error of 3*8 microseconds; 1.5 above, 1.5 below + return 90000000 / ((strobe_delay/8)*8) - 90000000 / ((strobe_delay/8+1)*8); +} + + diff --git a/libraries/hb_utilities/strobe.h b/libraries/hb_utilities/strobe.h new file mode 100644 index 0000000..d57f20d --- /dev/null +++ b/libraries/hb_utilities/strobe.h @@ -0,0 +1,66 @@ +#ifndef STROBE_H +#define STROBE_H + +#include "hexbright.h" + +// turn off strobe... (aka max unsigned long) +#define STROBE_OFF -1 + + +// Strobing features, limitations, and usage notes: +// - not compatible with set_light +// (turn off light with set_light(0,0,NOW) before using strobes +// turn off strobe with set_strobe_delay(STROBE_OFF) before using set_light) +// - the strobe will not drift over time (hooray!) +// - strobes will occur within a 25 microsecond window of their update time or not at all +// (flicker indicates that we missed our 25 microsecond window) +// - strobes occur during the update function; the longer your code takes to execute, the less strobes will occur. +// - strobes close to a multiple of 8333 will cause update() to occasionally take slightly +// longer to execute, eventually snap back to the start time, something like this: +// 8.33 8.33 8.33 8.7 9.4 9.4 9.4 4.83 8.33 (9.4 = 9400 microsecond delay). +// - strobe code ignores overheating. Under most circumstances this should be ok. + +// delay between strobes in microseconds. +// STROBE_OFF turns off strobing. +extern void set_strobe_delay(unsigned long delay); + +// set duration to a value between 50 and 3000. +// below 50, you will not see the light +// 75-400 work well for strobing; +// above 3000 you will lose some accelerometer samples. +extern void set_strobe_duration(int duration); + + +// convenience functions: +// example usage: +// set_strobe_fpm(59500); +// get_strobe_fpm(); // returns 59523 +// get_strobe_error(); // returns 703 +// you are strobing at 59523 fpm, +- 703 fpm. +// This code requires calibration/tuning + +// fpm accepted is from 0-65k +// the higher the fpm, the higher the error. +extern void set_strobe_fpm(unsigned int fpm); + +// returns the actual fpm you can currently expect +extern unsigned int get_strobe_fpm(); + +// returns the current margin of error in fpm +extern unsigned int get_strobe_error(); + + + + +#define UPDATE_SPIN // use the alternative update spin associated with this .h file (found in +//extern void update_spin(); + +extern unsigned long next_strobe; +extern unsigned long strobe_delay; +extern int strobe_duration; + + + + + +#endif // STROBE_H diff --git a/libraries/twi/Readme.md b/libraries/hexbright/Readme.md similarity index 100% rename from libraries/twi/Readme.md rename to libraries/hexbright/Readme.md diff --git a/libraries/hexbright/hexbright.cpp b/libraries/hexbright/hexbright.cpp deleted file mode 100755 index c58ddd4..0000000 --- a/libraries/hexbright/hexbright.cpp +++ /dev/null @@ -1,1288 +0,0 @@ -/* -Copyright (c) 2012, "David Hilton" -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those -of the authors and should not be interpreted as representing official policies, -either expressed or implied, of the FreeBSD Project. -*/ - - -#include "hexbright.h" -#include - -#ifndef __AVR // we're not compiling for arduino (probably testing), use these stubs -#include "includes/NotArduino.h" -#else -#include "includes/pin_interface.h" -#include "../digitalWriteFast/digitalWriteFast.h" -#endif - -// Pin assignments -#define DPIN_RLED_SW 2 // both red led and switch. pinMode OUTPUT = led, pinMode INPUT = switch -#define DPIN_GLED 5 -#define DPIN_PWR 8 -#define DPIN_DRV_MODE 9 -#define DPIN_DRV_EN 10 -#define APIN_TEMP 0 -#define APIN_CHARGE 3 -#define APIN_BAND_GAP 14 - - -/////////////////////////////////////////////// -/////////////HARDWARE INIT, UPDATE///////////// -/////////////////////////////////////////////// - -const float update_delay = 8.3333333; // in lock-step with the accelerometer -unsigned long continue_time; - -#ifdef STROBE -unsigned long next_strobe = STROBE_OFF; -unsigned long strobe_delay = 0; -int strobe_duration = 100; -#endif - -hexbright::hexbright() { -} - -#ifdef FLASH_CHECKSUM -int hexbright::flash_checksum() { - int checksum = 0; - for(int i=0; i<16384; i++) { - checksum+=pgm_read_byte(i); - } - return checksum; -} -#endif - -void hexbright::init_hardware() { - // These next 8 commands are for reference and cost nothing, - // as we are initializing the values to their default state. - pinModeFast(DPIN_PWR, OUTPUT); - digitalWriteFast(DPIN_PWR, LOW); - pinModeFast(DPIN_RLED_SW, INPUT); - pinModeFast(DPIN_GLED, OUTPUT); - pinModeFast(DPIN_DRV_MODE, OUTPUT); - pinModeFast(DPIN_DRV_EN, OUTPUT); - digitalWriteFast(DPIN_DRV_MODE, LOW); - digitalWriteFast(DPIN_DRV_EN, LOW); - -#if (DEBUG!=DEBUG_OFF) - // Initialize serial busses - Serial.begin(9600); - twi_init(); // for accelerometer - Serial.println("DEBUG MODE ON"); -#endif - -#if (DEBUG!=DEBUG_OFF && DEBUG!=DEBUG_PRINT) -#ifdef FREE_RAM - Serial.print("Ram available: "); - Serial.print(freeRam()); - Serial.println("/1024 bytes"); -#endif -#ifdef FLASH_CHECKSUM - Serial.print("Flash checksum: "); - Serial.println(flash_checksum()); -#endif -#endif //(DEBUG!=DEBUG_OFF && DEBUG!=DEBUG_PRINT) - -#ifdef ACCELEROMETER - enable_accelerometer(); -#endif - - // was this power on from battery? if so, it was a button press, even if it was too fast to register. - read_charge_state(); - if(get_charge_state()==BATTERY) - press_button(); - - continue_time = micros(); -} - -word loopCount; -void hexbright::update() { - unsigned long now; - loopCount++; - -#if (DEBUG==DEBUG_LOOP) - unsigned long start_time=micros(); -#endif - - -#ifdef STROBE - while (true) { - do { - now = micros(); - } while (next_strobe > now && // not ready for strobe - continue_time > now); // not ready for update - - if (next_strobe <= now) { - if (now - next_strobe <26) { - digitalWriteFast(DPIN_DRV_EN, HIGH); - delayMicroseconds(strobe_duration); - digitalWriteFast(DPIN_DRV_EN, LOW); - } - next_strobe += strobe_delay; - } - if(continue_time <= now) { - if(strobe_delay>update_delay && // we strobe less than once every 8333 microseconds - next_strobe-continue_time < 4000) // and the next strobe is within 4000 microseconds (may occur before we return) - continue; - else - break; - } - } // do nothing... (will short circuit once every 70 minutes (micros maxint)) -#else - do { - now = micros(); - } while ((signed long)(continue_time - now) > 0); // not ready for update -#endif - - // if we're in debug mode, let us know if our loops are too large -#if (DEBUG!=DEBUG_OFF && DEBUG!=DEBUG_PRINT) - static int i=0; -#if (DEBUG==DEBUG_LOOP) - static unsigned long last_time = 0; - if(!i) { - Serial.print("Time used: "); - Serial.print(start_time-last_time); - Serial.println("/8333"); - } - last_time = now; -#endif - if(now-continue_time>5000 && !i) { - // This may be caused by too much processing for our update_delay, or by too many print statements) - // If you're triggering this, your button and light will react more slowly, and some accelerometer - // data is being missed. - Serial.println("WARNING: code is too slow"); - } - if (!i) - i=1000/update_delay; // display loop output every second - else - i--; -#endif - - - // power saving modes described here: http://www.atmel.com/Images/2545s.pdf - //run overheat protection, time display, track battery usage - -#ifdef LED - // regardless of desired led state, turn it off so we can read the button - _led_off(RLED); - delayMicroseconds(50); // let the light stabilize... - read_button(); - // turn on (or off) the leds, if appropriate - adjust_leds(); -#ifdef PRINT_NUMBER - update_number(); -#endif -#else - read_button(); -#endif - - read_thermal_sensor(); // takes about .2 ms to execute (fairly long, relative to the other steps) - read_charge_state(); - read_avr_voltage(); - -#ifdef ACCELEROMETER - read_accelerometer(); - find_down(); -#endif - detect_overheating(); - detect_low_battery(); - apply_max_light_level(); - - // change light levels as requested - adjust_light(); - - // advance time at the same rate as values are changed in the accelerometer. - // advance continue_time here, so the first run through short-circuits, - // meaning we will read hardware immediately after power on. - continue_time = continue_time+(int)(1000*update_delay); -} - -#ifdef FREE_RAM -//// freeRam function from: http://playground.arduino.cc/Code/AvailableMemory -int hexbright::freeRam () { - extern int __heap_start, *__brkval; - int v; - return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); -} -#endif - - -/////////////////////////////////////////////// -///////////////////Filters///////////////////// -/////////////////////////////////////////////// - -#ifdef ACCELEROMETER - -inline int hexbright::low_pass_filter(int last_estimate, int current_reading) { - // The sum of these two constant multipliers (which equals the divisor), - // should not exceed 210 (to avoid integer overflow) - // the individual values selected do effect the resulting sketch size :/ - return (2*last_estimate + 3*current_reading)/5; -} - -inline int hexbright::stdev_filter(int last_estimate, int current_reading) { - float stdev = 3.3; // our standard deviation due to noise (pre calculated using accelerometer data at rest) - int diff = -abs(last_estimate-current_reading); - float deviation = diff/stdev; - float probability = exp(deviation)/1.25; // 2.5 ~= M_SQRT2PI, /2 because cdf is only one way - // exp by itself takes 400-500 bytes. This isn't good. - return probability*last_estimate + (1-probability)*current_reading; -} - - -inline int hexbright::stdev_filter2(int last_estimate, int current_reading) { - // uses 1/deviation^2 for our cdf approximation - float stdev = 3.5; // our standard deviation due to noise (pre calculated using accelerometer data at rest) - int diff = abs(last_estimate-current_reading); - float deviation = diff/stdev; - float probability; - if(deviation>1) // estimate where we fall on the cdf - probability = 1/(deviation*deviation); - else - probability = .65; - // exp by itself takes 400-500 bytes. This isn't good. - return probability*last_estimate + (1-probability)*current_reading; -} - -inline int hexbright::stdev_filter3(int last_estimate, int current_reading) { - // uses 1/deviation^2 for our cdf approximation, switched to ints - int stdev = 3.5*10; // our standard deviation due to noise (pre calculated using accelerometer data at rest) - int diff = abs(last_estimate-current_reading)*10; - // differences < 4 have a deviation of 0 - int deviation = diff/stdev; - int probability; - if(deviation>1) // estimate where we fall on the cdf - probability = 100/(deviation*deviation); - else - probability = 70; - return (probability*last_estimate + (100-probability)*current_reading)/100; -} - -#endif // ACCELEROMETER - -/////////////////////////////////////////////// -////////////////LIGHT CONTROL////////////////// -/////////////////////////////////////////////// - -// Light level must be sufficiently precise for quality low-light brightness and accurate power adjustment at high brightness. -// light level should be converted to logarithmic, square root or cube root values (from lumens), so as to be perceptually linear... -// http://www.candlepowerforums.com/vb/newreply.php?p=3889844 -// This is handled inside of set_light_level. - - -int start_light_level = 0; -int end_light_level = OFF_LEVEL; // go to OFF_LEVEL once change_duration expires (unless set_light overrides) -int change_duration = 5000/update_delay; // stay on for 5 seconds -int change_done = 0; - -int max_light_level = MAX_LEVEL; - - -void hexbright::set_light(int start_level, int end_level, long time) { - // duration ranges from 1-MAXINT - // light_level can be from 0-1000 - int current_level = get_light_level(); - start_light_level = start_level == CURRENT_LEVEL ? current_level : start_level; - end_light_level = end_level == CURRENT_LEVEL ? current_level : end_level; - - change_duration = ((float)time)/update_delay; - change_done = 0; -#if (DEBUG==DEBUG_LIGHT) - Serial.print("Light adjust requested, start level: "); - Serial.println(start_light_level); - Serial.print("Over "); - Serial.print(change_duration); - Serial.println(" updates"); -#endif - -} - -int hexbright::get_light_level() { - if(change_done>=change_duration) - return end_light_level; - else - return (end_light_level-start_light_level)*((float)change_done/change_duration) +start_light_level; -} - -int hexbright::get_max_light_level() { - int light_level = get_light_level(); - - if(light_level>max_light_level) - return max_light_level; - return light_level; -} - -int hexbright::light_change_remaining() { - // change_done ends up at -1, add one to counter - // return (change_duration-change_done+1)*update_delay; - int tmp = change_duration-change_done; - if(tmp<=0) - return 0; - return tmp*update_delay; -} - -void hexbright::set_light_level(unsigned long level) { - // LOW 255 approximately equals HIGH 48/49. There is a color change. - // Values < 4 do not provide any light. - // I don't know about relative power draw. - - // look at linearity_test.ino for more detail on these algorithms. - -#if (DEBUG==DEBUG_LIGHT) - Serial.print("light level: "); - Serial.println(level); -#endif - digitalWriteFast(DPIN_PWR, HIGH); - if(level == 0) { - // lowest possible power, but cpu still running (DPIN_PWR still high) - digitalWriteFast(DPIN_DRV_MODE, LOW); - analogWrite(DPIN_DRV_EN, 0); - } else if(level == OFF_LEVEL) { - // power off (DPIN_PWR LOW) - digitalWriteFast(DPIN_PWR, LOW); - digitalWriteFast(DPIN_DRV_MODE, LOW); - analogWrite(DPIN_DRV_EN, 0); - } else { - byte value; - if(level<=500) { - digitalWriteFast(DPIN_DRV_MODE, LOW); - value = (byte)(.000000633*(level*level*level)+.000632*(level*level)+.0285*level+3.98); - } else { - level -= 500; - digitalWriteFast(DPIN_DRV_MODE, HIGH); - value = (byte)(.00000052*(level*level*level)+.000365*(level*level)+.108*level+44.8); - } - analogWrite(DPIN_DRV_EN, value); - } -} - -void hexbright::adjust_light() { - // sets actual light level, altering value to be perceptually linear, based on steven's area brightness (cube root) - if(change_done<=change_duration) { - int light_level = hexbright::get_max_light_level(); - set_light_level(light_level); - - change_done++; - } -} - -void hexbright::apply_max_light_level() { - // if max_light_level has changed, guarantee a light adjustment: - // the second test guarantees that we won't turn on if we are - // overheating and just shut down - if(max_light_level < MAX_LEVEL && get_light_level()>MIN_OVERHEAT_LEVEL) { -#if (DEBUG!=DEBUG_OFF && DEBUG!=DEBUG_PRINT) - Serial.print("Max light level: "); - Serial.println(max_light_level); -#endif - change_done = change_done < change_duration ? change_done : change_duration; - } -} - - -#ifdef STROBE - -///////////////STROBE CONTROL////////////////// - -void hexbright::set_strobe_delay(unsigned long delay) { - strobe_delay = delay; - next_strobe = micros()+strobe_delay; -} - -void hexbright::set_strobe_duration(int duration) { - strobe_duration = duration; -} - -void hexbright::set_strobe_fpm(unsigned int fpm) { - set_strobe_delay(60000000/fpm); -} - -unsigned int hexbright::get_strobe_fpm() { - return 60000000 / (strobe_delay/8*8); -} - -unsigned int hexbright::get_strobe_error() { - // 90000000 because we have an error of 3*8 microseconds; 1.5 above, 1.5 below - return 90000000 / ((strobe_delay/8)*8) - 90000000 / ((strobe_delay/8+1)*8); -} - -#endif - -/////////////////////////////////////////////// -///////////////////LED CONTROL///////////////// -/////////////////////////////////////////////// - -#ifdef LED - -// >0 = countdown, 0 = change state, -1 = state changed -int led_wait_time[2] = {-1, -1}; -int led_on_time[2] = {-1, -1}; -unsigned char led_brightness[2] = {0, 0}; -byte rledMap[4] = {0b0001, 0b0101, 0b0111, 0b1111}; - -void hexbright::set_led(unsigned char led, int on_time, int wait_time, unsigned char brightness) { -#if (DEBUG==DEBUG_LED) - Serial.println("activate led"); -#endif - led_on_time[led] = on_time/update_delay; - led_wait_time[led] = wait_time/update_delay; - led_brightness[led] = brightness; -} - - -unsigned char hexbright::get_led_state(unsigned char led) { - //returns true if the LED is on - if(led_on_time[led]>=0) { - return LED_ON; - } else if(led_wait_time[led]>0) { - return LED_WAIT; - } else { - return LED_OFF; - } -} - -inline void hexbright::_led_on(unsigned char led) { - if(led == RLED) { // DPIN_RLED_SW - pinModeFast(DPIN_RLED_SW, OUTPUT); - - byte l = rledMap[led_brightness[RLED]>>6]; - byte r = 1<<(loopCount & 0b11); - if(l & r) { - digitalWriteFast(DPIN_RLED_SW, HIGH); - } else { - digitalWriteFast(DPIN_RLED_SW, LOW); - } - } else { // DPIN_GLED - analogWrite(DPIN_GLED, led_brightness[GLED]); - } -} - -inline void hexbright::_led_off(unsigned char led) { - if(led == RLED) { // DPIN_RLED_SW - digitalWriteFast(DPIN_RLED_SW, LOW); - pinModeFast(DPIN_RLED_SW, INPUT); - } else { // DPIN_GLED - // digitalWriteFast doesn't work when set to a non-digital value, - // using analogWrite. digitalWrite could be used, but would increase - // complexity in porting to straight AVR. - analogWrite(DPIN_GLED, LOW); - } -} - -inline void hexbright::adjust_leds() { - // turn off led if it's expired -#if (DEBUG==DEBUG_LED) - if(led_on_time[GLED]>=0) { - Serial.print("green on countdown: "); - Serial.println(led_on_time[GLED]*update_delay); - } else if (led_on_time[GLED]<0 && led_wait_time[GLED]>=0) { - Serial.print("green wait countdown: "); - Serial.println((led_wait_time[GLED])*update_delay); - } - if(led_on_time[RLED]>=0) { - Serial.print("red on countdown: "); - Serial.println(led_on_time[RLED]*update_delay); - } else if (led_on_time[RLED]<0 && led_wait_time[RLED]>=0) { - Serial.print("red wait countdown: "); - Serial.println((led_wait_time[RLED])*update_delay); - } -#endif - - int i=0; - for(i=0; i<2; i++) { - if(led_on_time[i]>0) { - _led_on(i); - led_on_time[i]--; - } else if(led_on_time[i]==0) { - _led_off(i); - led_on_time[i]--; - } else if (led_wait_time[i]>=0) { - led_wait_time[i]--; - } - } -} - -#endif - - -/////////////////////////////////////////////// -/////////////////////BUTTON//////////////////// -/////////////////////////////////////////////// - -#define BUTTON_FILTER 7 // 00000111 (this is applied at read time) -#define BUTTON_JUST_OFF(state) (state==4) // 100 = just off -#define BUTTON_JUST_ON(state) (state==1) // 001 = just on -#define BUTTON_STILL_OFF(state) (state==0) // 000 = still off -// 010, 011, 101, 110, 111 = still on -#define BUTTON_STILL_ON(state) !(BUTTON_JUST_OFF(state) | BUTTON_JUST_ON(state) | BUTTON_STILL_OFF(state)) -#define BUTTON_ON(state) (state & 3) // either of right most bits is on: 00000011 (not 100 or 000) -#define BUTTON_OFF(state) !BUTTON_ON(state) - -// button state could hold a history of the last 8 switch values, 1 == on, 0 == off. -// for debouncing, we only need the most recent 3, so BUTTON_FILTER=7, 00000111 -// This is applied during the read process. -unsigned char button_state = 0; - -unsigned long time_last_pressed = 0; // the time that button was last pressed -unsigned long time_last_released = 0; // the time that the button was last released - -byte press_override = false; - -void hexbright::press_button() { - press_override = true; -} - -BOOL hexbright::button_pressed() { - return BUTTON_ON(button_state); -} - -BOOL hexbright::button_just_pressed() { - return BUTTON_JUST_ON(button_state); -} - -BOOL hexbright::button_just_released() { - return BUTTON_JUST_OFF(button_state); -} - -int hexbright::button_pressed_time() { - if(BUTTON_ON(button_state) || BUTTON_JUST_OFF(button_state)) { - return millis()-time_last_pressed; - } else { - return time_last_released - time_last_pressed; - } -} - -int hexbright::button_released_time() { - if(BUTTON_ON(button_state)) { - return time_last_pressed-time_last_released; - } else { - return millis()-time_last_released; - } -} - -void hexbright::read_button() { - if(BUTTON_JUST_OFF(button_state)) { - // we update time_last_released before the read, so that the very first time through after a release, - // button_released_time() returns the /previous/ button_released_time. - time_last_released=millis(); -#if (DEBUG==DEBUG_BUTTON) - Serial.println("Button just released"); - Serial.print("Time spent pressed (ms): "); - Serial.println(time_last_released-time_last_pressed); -#endif - } - - /* READ THE BUTTON!!! - button_state = button_state << 1; // make space for the new value - button_state = button_state | digitalReadFast(DPIN_RLED_SW); // add the new value - button_state = button_state & BUTTON_FILTER; // remove excess values */ - // Doing the three commands above on one line saves 2 bytes. We'll take it! - byte read_value = digitalReadFast(DPIN_RLED_SW); - if(press_override) { - read_value = 1; - press_override = false; - } - button_state = ((button_state<<1) | read_value) & BUTTON_FILTER; - - if(BUTTON_JUST_ON(button_state)) { - time_last_pressed=millis(); -#if (DEBUG==DEBUG_BUTTON) - Serial.println("Button just pressed"); - Serial.print("Time spent released (ms): "); - Serial.println(time_last_pressed-time_last_released); -#endif - } -} - -byte clickState; -#define CLICK_OFF 0 -#define CLICK_ACTIVE 1 -#define CLICK_WAIT 2 - -byte clickCount; -word max_click_time; -void hexbright::config_click_count(word click_time) { - max_click_time = click_time; - clickState=0; -} - -char hexbright::click_count() { - switch(clickState) { - case CLICK_OFF: - if(button_just_pressed()) { - // click just started - clickState = CLICK_ACTIVE; - clickCount=0; - //Serial.println("Clicking just started"); - } - break; - case CLICK_ACTIVE: - if(button_just_released()) { - if(button_pressed_time() > max_click_time) { - // button held to long - //Serial.println("Click held too long"); - clickState = CLICK_OFF; - } else { - // click is counted - clickState = CLICK_WAIT; - clickCount++; - //Serial.println("Click counted"); - } - } - break; - case CLICK_WAIT: - // button is released for long enough, we're done clicking - if(button_released_time() > max_click_time) { - clickState = CLICK_OFF; - //Serial.print("Click finished: "); Serial.println((int)clickCount); - return clickCount; - } else if(button_pressed()) { - // move back to active state - clickState = CLICK_ACTIVE; - //Serial.println("Click active"); - } - } - return -127; -} - -/////////////////////////////////////////////// -////////////////ACCELEROMETER////////////////// -/////////////////////////////////////////////// - -#ifdef ACCELEROMETER - -// return degrees of movement? -// Possible things to work with: -//MOVE_TYPE, value returned from a successful detect_movement -#define ACCEL_NONE 0 // nothing -#define ACCEL_TWIST 1 // return degrees - light axis remains constant -#define ACCEL_TURN 2 // return degrees - light axis changes -#define ACCEL_DROP 3 // return change of velocity - period of no acceleration before impact? -#define ACCEL_TAP 4 // return change of velocity - acceleration before impact - -// I considered operating with bytes to save space, but the savings were -// offset by still needing to do /some/ things as ints. I've not completely -// tested which is more compact, but preliminary work suggests difficulty -// in the implementation with little to no benefit. - - -unsigned char tilt = 0; -int vectors[] = {0,0,0, 0,0,0, 0,0,0, 0,0,0}; -int current_vector = 0; -unsigned char num_vectors = 4; -int down_vector[] = {0,0,0}; - -/// SETUP/MANAGEMENT - -void hexbright::enable_accelerometer() { - // Configure accelerometer - byte config[] = { - ACC_REG_INTS, // First register (see next line) - 0xE4, // Interrupts: shakes, taps - 0x00, // Mode: not enabled yet - 0x00, // Sample rate: 120 Hz (see datasheet page 19) - 0x0F, // Tap threshold - 0x05 // Tap debounce samples - }; - twi_writeTo(ACC_ADDRESS, config, sizeof(config), true /*wait*/, true /*send stop*/); - - // Enable accelerometer - byte enable[] = {ACC_REG_MODE, 0x01}; // Mode: active! - twi_writeTo(ACC_ADDRESS, enable, sizeof(enable), true /*wait*/, true /*send stop*/); - - // pinModeFast(DPIN_ACC_INT, INPUT); - // digitalWriteFast(DPIN_ACC_INT, HIGH); -} -void print_binary(byte value) { - String s = String(value, BIN); - - while(s.length()<8) { - s = "0"+s; - } - Serial.println(s); -} -void hexbright::read_accelerometer() { - /*unsigned long time = 0; - if((millis()-init_time)>*/ - // advance which vector is considered the first - next_vector(); - char read=0; - while(read!=4) { - byte reg = ACC_REG_XOUT; - twi_writeTo(ACC_ADDRESS, ®, sizeof(reg), true /*wait*/, true /*send stop*/); - byte acc_data[4]; - twi_readFrom(ACC_ADDRESS, acc_data, sizeof(acc_data), true /*send stop*/); - read = 0; - int i = 0; - for(i; i<4; i++) { - char tmp = acc_data[i]; - if (tmp & 0x40) { // Bx1xxxxxx, - // invalid data, re-read per data sheet page 14 - continue; // continue, so we finish flushing the read buffer. - } - if(i==3){ //read tilt register - tilt = tmp; - } else { // read vector - if(tmp & 0x20) // Bxx1xxxxx, it's negative - tmp |= 0xC0; // extend to B111xxxxx - vectors[current_vector+i] = stdev_filter3(vector(1)[i], tmp*(100/21.3)); - } - read++; // successfully read. - } - } -} - -unsigned char hexbright::read_accelerometer(unsigned char acc_reg) { - if (!digitalReadFast(DPIN_ACC_INT)) { - byte acc_data; - twi_writeTo(ACC_ADDRESS, &acc_reg, sizeof(acc_reg), true /*wait*/, true /*send stop*/); - twi_readFrom(ACC_ADDRESS, &acc_data, sizeof(acc_data), true /*send stop*/); - return acc_data; - } - return 0; -} - - -inline void hexbright::find_down() { - // currently, we're just averaging the last four data points. - // Down is the strongest constant acceleration we experience - // (assuming we're not dropped). Heuristics to only find down - // under specific circumstances have been tried, but they only - // tell us when down is less certain, not where it is... - copy_vector(down_vector, vector(0)); // copy first vector to down - double magnitudes = magnitude(vector(0)); - for(int i=1; i>2; // shift us all the way to the right - // PoLa: 5, 6 = horizontal, 1 = up, 2 = down, 0 = unknown - if(tmp & 0x04) - return TILT_HORIZONTAL; - return tmp; -} - -char hexbright::get_tilt_rotation() { - static unsigned char last = 0; - unsigned char current = tilt & 0x1F; // filter out tap/shake registers - // 21,22,26,25 (in order, rotating when horizontal) - switch(current ) { - case 21: // 10101 - current = 1; - break; - case 22: // 10110 - current = 2; - break; - case 26: // 11010 - current = 3; - break; - case 25: // 11001 - current = 4; - break; - default: // we can't determine orientation with this reading - last = 0; - return 0; - } - - if(last==0) { // previous reading wasn't usable - last = current; - return 0; - } - - // we have two valid values, calculate! - char retval = last-current; - last = current; - if(retval*retval>1) { // switching from a 4 to a 1 or vice-versa - retval = -(retval%2); - } -#if (DEBUG==DEBUG_ACCEL) - if(retval!=0) { - Serial.print("tilt rotation: "); - Serial.println((int)retval); - } -#endif - return retval; -} - -/// some sample functions using vector operations - -double hexbright::angle_change() { - int* vec1 = vector(0); - int* vec2 = vector(1); - return angle_difference(dot_product(vec1, vec2), - magnitude(vec1), - magnitude(vec2)); -} - -void hexbright::absolute_vector(int* out_vector, int* in_vector) { - sub_vectors(out_vector, in_vector, down_vector); -} - -double hexbright::difference_from_down() { - int light_axis[3] = {0, -100, 0}; - return (angle_difference(dot_product(light_axis, down_vector), 100, 100)); -} - -BOOL hexbright::stationary(int tolerance) { - // low acceleration vectors - return abs(magnitude(vector(0))-100)tolerance; -} - -char hexbright::get_spin() { - // quick formula: - //(atan2(vector(1)[0], vector(1)[2]) - atan2(vector(0)[0], vector(0)[2]))*32; - // we cache the last position, because it takes up less space. - static char last_spin = 0; - // 2*PI*32 = 200 - return atan2(down()[0], down()[2])*32 - atan2(vector(0)[0], vector(0)[2])*32; - char spin = atan2(vector(0)[0], vector(0)[2])*32; - if(abs(last_spin)-10<0) { - - } - - return spin; -} - -/// VECTOR TOOLS -int* hexbright::vector(unsigned char back) { - return vectors+((current_vector/3+back)%num_vectors)*3; -} - -int* hexbright::down() { - return down_vector; -} - -void hexbright::next_vector() { - current_vector=((current_vector/3+(num_vectors-1))%num_vectors)*3; -} - -double hexbright::angle_difference(int dot_product, double magnitude1, double magnitude2) { - // multiply to account for 100 being equal to one for our vectors - double tmp = dot_product*100/(magnitude1*magnitude2); - return acos(tmp)/3.14159; -} - -int hexbright::dot_product(int* vector1, int* vector2) { - // the max value sum could hit is technically > 2^16. - // However, the only instance where this is possible is if both vectors have - // experienced maximum acceleration on all axes - with 4 of the 6 being the max in the negative - // direction (-32 min, 31 max), (31*100/21.3) = 145, (-32*100/21.3) = -150 - // 3*(150^2) = 67500 (not safe) - // (150*150)+(150*150)+(145*145) = 66025 (not safe) - // (150*150)+(150*145)+(145*145) = 65275 (safe) - // 3*(145^2) = 63075 (safe) - // Given the difficulty of triggering this condition, I'm using an int. - // This could also be avoided by /100 inside the loop, costing 12 bytes. - // We should revisit this decision when drop detect code has been written. - // An alternate solution would be to convert using READING*100/21.65, giving - // a max of 147. 3*(147^2) = 64827 (safe). A max of 148 would be safe so - // long as no more than 5 values are -32. - unsigned int sum = 0; // max value is about 3*(150^2), a tad over the maximum unsigned int - for(int i=0;i<3;i++) { - //sum+=vector1[i]*vector2[i]/100; // avoids overflow, but costs space - sum+=vector1[i]*vector2[i]; - } - return sum/100; // small danger of overflow -} - -void hexbright::cross_product(int * axes_rotation, - int* in_vector1, - int* in_vector2, - double angle_difference) { - for(int i=0; i<3; i++) { - axes_rotation[i] = (in_vector1[(i+1)%3]*in_vector2[(i+2)%3] \ - - in_vector1[(i+2)%3]*in_vector2[(i+1)%3]); - //axes_rotation[i] /= magnitude(in_vector1)*magnitude(in_vector2); - //axes_rotation[i] /= asin(angle_difference*3.14159); - } -} - -double hexbright::magnitude(int* vector) { - // This has a small possibility of failure, see the comments at the start - // of dot_product. A long would cost another 28 bytes, a double 42. - // A cheaper solution (costing 20 bytes) is to divide the squaring by 100, - // then multiply the sqrt by 10 (commented out). - unsigned int result = 0; - for(int i=0; i<3;i++) { - result += vector[i]*vector[i]; // vector[i]*vector[i]/100; - } - return sqrt(result); // sqrt(result)*10; -} - -void hexbright::normalize(int* out_vector, int* in_vector, double magnitude) { - for(int i=0; i<3; i++) { - // normalize to 100, not 1 - out_vector[i] = in_vector[i]/magnitude*100; - } -} - -void hexbright::sum_vectors(int* out_vector, int* in_vector1, int* in_vector2) { - for(int i=0; i<3;i++) { - out_vector[i] = in_vector1[i]+in_vector2[i]; - } -} - -void hexbright::sub_vectors(int* out_vector, int* in_vector1, int* in_vector2) { - for(int i=0; i<3;i++) { - out_vector[i] = in_vector1[i]-in_vector2[i]; - } -} - -void hexbright::copy_vector(int* out_vector, int* in_vector) { - for(int i=0; i<3;i++) { - out_vector[i] = in_vector[i]; - } -} - -void hexbright::print_vector(int* vector, const char* label) { -#if (DEBUG!=DEBUG_OFF) - for(int i=0; i<3; i++) { - Serial.print(vector[i]); - Serial.print("/"); - } - Serial.println(label); -#endif -} - -#endif - - -/////////////////////////////////////////////// -//////////////////UTILITIES//////////////////// -/////////////////////////////////////////////// - -long _number = 0; -unsigned char _color = GLED; -int print_wait_time = 0; - -#if (defined(LED) && defined(PRINT_NUMBER)) -BOOL hexbright::printing_number() { - return _number || print_wait_time; -} - -void hexbright::reset_print_number() { - _number = 1; - print_wait_time = 0; -} - -void hexbright::update_number() { - if(_number>0) { // we have something to do... -#if (DEBUG==DEBUG_NUMBER) - static int last_printed = 0; - if(last_printed != _number) { - last_printed = _number; - Serial.print("number remaining (read from right to left): "); - Serial.println(_number); - } -#endif - if(!print_wait_time) { - if(_number==1) { // minimum delay between printing numbers - print_wait_time = 2500/update_delay; - _number = 0; - return; - } else { - print_wait_time = 300/update_delay; - } - if(_number/10*10==_number) { -#if (DEBUG==DEBUG_NUMBER) - Serial.println("zero"); -#endif - // print_wait_time = 500/update_delay; - set_led(_color, 400); - } else { - set_led(_color, 120); - _number--; - } - if(_number && !(_number%10)) { // next digit? - print_wait_time = 600/update_delay; - _color = flip_color(_color); - _number = _number/10; - } - } - } - - if(print_wait_time) { - print_wait_time--; - } -} - -unsigned char hexbright::flip_color(unsigned char color) { - return (color+1)%2; -} - - -void hexbright::print_number(long number) { - // reverse number (so it prints from left to right) - BOOL negative = false; - if(number<0) { - number = 0-number; - negative = true; - } - _color = GLED; - _number=1; // to guarantee printing when dealing with trailing zeros (100 can't be stored as 001, use 1001) - do { - _number = _number * 10 + (number%10); - number = number/10; - _color = flip_color(_color); - } while(number>0); - if(negative) { - set_led(flip_color(_color), 500); - print_wait_time = 600/update_delay; - } -} - - -#ifdef ACCELEROMETER -//// Numeric entry -static unsigned int read_value = 0; - -unsigned int hexbright::get_input_digit() { - return read_value; -} - -void hexbright::input_digit(unsigned int min_digit, unsigned int max_digit) { - unsigned int tmp2 = 999 - atan2(vector(0)[0], vector(0)[2])*159 - 500; // scale from 0-999, counterclockwise = higher - tmp2 = (tmp2*(max_digit-min_digit))/1000+min_digit; - if(tmp2 == read_value) { - if(!printing_number()) { - print_number(tmp2); - } - } else { - reset_print_number(); - set_led(GLED,100); - } - read_value = tmp2; -} -#endif // ACCELEROMETER -#endif // (defined(LED) && defined(PRINT_NUMBER)) - -void hexbright::print_power() { - print_charge(GLED); - if (low_voltage_state() && get_led_state(RLED) == LED_OFF) { - set_led(RLED,50,1000); - } -} - - -/////////////////////////////////////////////// -////////////////TEMPERATURE//////////////////// -/////////////////////////////////////////////// - -int thermal_sensor_value = 0; -void hexbright::read_thermal_sensor() { - // do not call this directly. Call get_temperature() - // read temperature setting - // device data sheet: http://ww1.microchip.com/downloads/en/devicedoc/21942a.pdf - - thermal_sensor_value = read_adc(APIN_TEMP); -} - -int hexbright::get_thermal_sensor() { - return thermal_sensor_value; -} - -int hexbright::get_celsius() { - // 0C ice water bath for 20 minutes: 153. - // 40C water bath for 20 minutes (measured by medical thermometer): 275 - // intersection with 0: 50 = (40C-0C)/(275-153)*153 - - // 40.05 is to force the division to floating point. The extra parenthesis are to - // tell the compiler to pre-evaluate the expression. - return thermal_sensor_value * ((40.05-0)/(275-153)) - 50; -} - - -int hexbright::get_fahrenheit() { - //return get_celsius()*18/10+32; - // algebraic form of (get_celsius' formula)*18/10+32 - // I was lazy and pasted (x*((40.05-0)/(275-153)) - 50)*18/10+32 into wolfram alpha - return .590902*thermal_sensor_value-58; -} - -// If the ambient temperature is above your max temp, your light is going to be pretty dim... - -void hexbright::detect_overheating() { - unsigned int temperature = get_thermal_sensor(); - - max_light_level = max_light_level+(OVERHEAT_TEMPERATURE-temperature); - // min, max levels... - max_light_level = max_light_level > MAX_LEVEL ? MAX_LEVEL : max_light_level; - max_light_level = max_light_level < MIN_OVERHEAT_LEVEL ? MIN_OVERHEAT_LEVEL : max_light_level; -#if (DEBUG==DEBUG_TEMP) - static float printed_temperature = 0; - static float average_temperature = -1; - if(average_temperature < 0) { - average_temperature = temperature; - Serial.println("Have you calibrated your thermometer?"); - Serial.println("Instructions are in get_celsius."); - } - average_temperature = (average_temperature*4+temperature)/5; - if (abs(printed_temperature-average_temperature)>1) { - printed_temperature = average_temperature; - Serial.print(millis()); - Serial.print(" ms, average reading: "); - Serial.print(printed_temperature); - Serial.print(" (celsius: "); - Serial.print(get_celsius()); - Serial.print(") (fahrenheit: "); - Serial.print(get_fahrenheit()); - Serial.println(")"); - } -#endif -} - - -/////////////////////////////////////////////// -////////////////AVR VOLTAGE//////////////////// -/////////////////////////////////////////////// - -int band_gap_reading = 0; -int lowest_band_gap_reading = 1000; - -void hexbright::read_avr_voltage() { - band_gap_reading = read_adc(APIN_BAND_GAP); - if(get_charge_state()==BATTERY) - lowest_band_gap_reading = band_gap_reading < lowest_band_gap_reading ? band_gap_reading : lowest_band_gap_reading; -} - -int hexbright::get_avr_voltage() { - // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 - // this is the only place we actually convert to voltage, reducing the space used for most programs. - return ((long)1023*1100) / band_gap_reading; -} - -BOOL hexbright::low_voltage_state() { - static BOOL low = false; - // lower band gap value corresponds to a higher voltage, trigger - // low voltage state if band gap value goes too high. - // I have a value of 2 for this to work (with a 150 ms delay in read_adc). - // tighter control means earlier detection of low battery state - if (band_gap_reading > lowest_band_gap_reading+2) { - low = true; - } - return low; -} - -void hexbright::detect_low_battery() { - if (low_voltage_state() == true && max_light_level>500) { - max_light_level = 500; - } -} - - -/////////////////////////////////////////////// -//////////////////CHARGING///////////////////// -/////////////////////////////////////////////// - -// we store the last two charge readings in charge_state, and due to the specific -// values chosen for the #defines, we greatly decrease the possibility of returning -// BATTERY when we are still connected over USB. This costs 14 bytes. - -// BATTERY is the median value, which is only returned if /both/ halves are BATTERY. -// If one of the two values is CHARGING, we return CHARGING -// If one of the two values is CHARGED (and neither CHARGING), we return CHARGED -// Otherwise, BATTERY is both values and is returned -unsigned char charge_state = BATTERY; - -void hexbright::read_charge_state() { - unsigned int charge_value = read_adc(APIN_CHARGE); -#if (DEBUG==DEBUG_CHARGE) - Serial.print("Current charge reading: "); - Serial.println(charge_value); -#endif - // <128 charging, >768 charged, battery - charge_state <<= 4; - if(charge_value<128) - charge_state += CHARGING; - else if (charge_value>768) - charge_state += CHARGED; - else - charge_state += BATTERY; -} - -unsigned char hexbright::get_charge_state() { - // see more details on how this works at the top of this section - return charge_state & (charge_state>>4); -} - -void hexbright::print_charge(unsigned char led) { - unsigned char charge_state = get_charge_state(); - if(charge_state == CHARGING && get_led_state(led) == LED_OFF) { - set_led(led, 350, 350); - } else if (charge_state == CHARGED) { - set_led(led,50); - } -} - - -/////////////////////////////////////////////// -//////////////////SHUTDOWN///////////////////// -/////////////////////////////////////////////// - -void hexbright::shutdown() { -#if (DEBUG!=DEBUG_OFF) - Serial.println("don't use shutdown, use set_light(,OFF_LEVEL,)"); -#endif - set_light(MAX_LOW_LEVEL, OFF_LEVEL, NOW); -} - - -/////////////////////////////////////////////// -//KLUDGE BECAUSE ARDUINO DOESN'T SUPPORT CLASS VARIABLES/INSTANTIATION -/////////////////////////////////////////////// -#ifdef ACCELEROMETER -void hexbright::fake_read_accelerometer(int* new_vector) { - next_vector(); - for(int i=0; i<3; i++) { - vector(0)[i] = stdev_filter3(vector(1)[i], new_vector[i]); - //vector(0)[i] = new_vector[i]; - } -} -#endif // ACCELEROMETER diff --git a/libraries/hexbright/hexbright.h b/libraries/hexbright/hexbright.h index f29013d..2630439 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -27,23 +27,35 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#ifndef HEXBRIGHT_H +#define HEXBRIGHT_H + #ifdef __AVR // we're compiling for arduino #include -#include "../twi/twi.h" +#include "twi.h" #include "../digitalWriteFast/digitalWriteFast.h" #define BOOL boolean #else #define BOOL bool #endif + +// Pin assignments +#define DPIN_RLED_SW 2 // both red led and switch. pinMode OUTPUT = led, pinMode INPUT = switch +#define DPIN_GLED 5 +#define DPIN_PWR 8 +#define DPIN_DRV_MODE 9 +#define DPIN_DRV_EN 10 +#define APIN_TEMP 0 +#define APIN_CHARGE 3 +#define APIN_BAND_GAP 14 + + /// Some space-saving options #define LED // comment out save 786 bytes if you don't use the rear LEDs -#define PRINT_NUMBER // comment out to save 626 bytes if you don't need to print numbers (but need the LEDs) #define ACCELEROMETER //comment out to save 1500 bytes if you don't need the accelerometer #define FLASH_CHECKSUM // comment out to save 56 bytes when in debug mode #define FREE_RAM // comment out to save 146 bytes when in debug mode -//#define STROBE // comment out to save 260 bytes (strobe is designed for higher-precision -// // stroboscope code, not general periodic flashing) // The above #defines can help if you are running out of flash. If you are having weird lockups, @@ -69,7 +81,6 @@ either expressed or implied, of the FreeBSD Project. #endif // debugging related definitions -#define DEBUG 0 // Some debug modes set the light. Your control code may reset it, causing weird flashes at startup. #define DEBUG_OFF 0 // no extra code is compiled in #define DEBUG_PRINT 1 // initialize printing only @@ -90,6 +101,8 @@ either expressed or implied, of the FreeBSD Project. #define OVERHEAT_TEMPERATURE 320 // 340 in original code, 320 = 130* fahrenheit/55* celsius (with calibration) #endif +#define UPDATE_DELAY 8.33333333 + /////////////////////////////////// // key points on the light scale // @@ -105,9 +118,6 @@ either expressed or implied, of the FreeBSD Project. #define NOW 1 -// turn off strobe... (aka max unsigned long) -// this is only valid for STROBE, which is disabled by default (see above) -#define STROBE_OFF -1 // led constants #define RLED 0 @@ -140,20 +150,6 @@ class hexbright { // Put update in your loop(). It will block until update_delay has passed. static void update(); - // When plugged in: turn off the light immediately, - // leave the cpu running (as it cannot be stopped) - // When on battery power: turn off the light immediately, - // turn off the cpu in about .5 seconds. - // Loop will run a few more times, and if your code turns - // on the light, shutoff will be canceled. As a result, - // if you do not reset your variables you may get weird - // behavior after turning the light off and on again in - // less than .5 seconds. - - // WARNING: deprecated, do not use - // use set_light(,OFF_LEVEL,); - static void shutdown(); - #ifdef FREE_RAM // freeRam function from: http://playground.arduino.cc/Code/AvailableMemory @@ -181,6 +177,7 @@ class hexbright { // 1000 = MAX_LEVEL, max high power mode // max change time is about 4.5 minutes ((2^15-1)*8.333 milliseconds). // I have had weird issues when passing in 3*60*1000, 180000 works fine though. + // Specific algorithms can be customized via #defines (see set_light_level.h) static void set_light(int start_level, int end_level, long time); // get light level (before overheat protection adjustment) static int get_light_level(); @@ -192,47 +189,6 @@ class hexbright { // hb.set_light(...) static int light_change_remaining(); -#ifdef STROBE -/////// STROBING /////// - - // Strobing features, limitations, and usage notes: - // - not compatible with set_light - // (turn off light with set_light(0,0,NOW) before using strobes - // turn off strobe with set_strobe_delay(STROBE_OFF) before using set_light) - // - the strobe will not drift over time (hooray!) - // - strobes will occur within a 25 microsecond window of their update time or not at all - // (flicker indicates that we missed our 25 microsecond window) - // - strobes occur during the update function; the longer your code takes to execute, the less strobes will occur. - // - strobes close to a multiple of 8333 will cause update() to occasionally take slightly - // longer to execute, eventually snap back to the start time, something like this: - // 8.33 8.33 8.33 8.7 9.4 9.4 9.4 4.83 8.33 (9.4 = 9400 microsecond delay). - // - strobe code ignores overheating. Under most circumstances this should be ok. - - // delay between strobes in microseconds. - // STROBE_OFF turns off strobing. - void set_strobe_delay(unsigned long delay); - // set duration to a value between 50 and 3000. - // below 50, you will not see the light - // 75-400 work well for strobing; - // above 3000 you will lose some accelerometer samples. - void set_strobe_duration(int duration); - - // convenience functions: - // example usage: - // set_strobe_fpm(59500); - // get_strobe_fpm(); // returns 59523 - // get_strobe_error(); // returns 703 - // you are strobing at 59523 fpm, +- 703 fpm. - // This code requires calibration/tuning - - // fpm accepted is from 0-65k - // the higher the fpm, the higher the error. - void set_strobe_fpm(unsigned int fpm); - // returns the actual fpm you can currently expect - unsigned int get_strobe_fpm(); - // returns the current margin of error in fpm - unsigned int get_strobe_error(); -#endif // STROBE // Button debouncing is handled inside the library. // Returns true if the button is being pressed. @@ -254,18 +210,11 @@ class hexbright { // so that we can catch very fast initial presses. static void press_button(); - // Call in setup loop if you want to use the click counter - // click_time is the maximum time a click can take before the counter resets - static void config_click_count(word click_time); - // The number of clicks <= click_time. Will not return a count until click_time ms after the button release. - // Will return -127 unless returning a valid count. - static char click_count(); - // led = GLED or RLED, // on_time (0-MAXINT) = time in milliseconds before led goes to LED_WAIT state // wait_time (0-MAXINT) = time in ms before LED_WAIT state decays to LED_OFF state. // Defaults to 100 ms. - // brightness (0-255) = brightness of rear led. note that rled brightness only has 2 bits of resolution and has visible flahing at the lowest setting. + // brightness (0-255) = brightness of rear led. note that rled brightness only has 2 bits of resolution and has visible flicker at the lowest setting. // Defaults to 255 (full brightness) // Takes up 16 bytes. static void set_led(unsigned char led, int on_time, int wait_time=100, unsigned char brightness=255); @@ -273,9 +222,6 @@ class hexbright { // returns LED_OFF, LED_WAIT, or LED_ON // Takes up 54 bytes. static unsigned char get_led_state(unsigned char led); - // returns the opposite color from the one passed in - // Takes up 12 bytes. - static unsigned char flip_color(unsigned char color); // Get the raw thermal sensor reading. Takes up 18 bytes. @@ -299,55 +245,10 @@ class hexbright { - // A convenience function that will print the charge state over the led specified - // CHARGING = 350 ms on, 350 ms off. - // CHARGED = solid on - // BATTERY = nothing. - // If you are using print_number, call it before this function if possible. - // I recommend the following (if using print_number()): - // ...code that may call print number... - // if(!printing_number()) - // print_charge(GLED); - // ...end of loop... - // See also: print_power - static void print_charge(unsigned char led); // returns CHARGING, CHARGED, or BATTERY static unsigned char get_charge_state(); - // prints a number through the rear leds - // 120 = 1 red flashes, 2 green flashes, one long red flash (0), 2 second delay. - // the largest printable value is +/-999,999,999, as the left-most digit is reserved. - // negative numbers begin with a leading long flash. - static void print_number(long number); - // currently printing a number - static BOOL printing_number(); - // reset printing; this immediately terminates the currently printing number. - static void reset_print_number(); - - // reads a value between min_digit to max_digit-1, see hb-examples/numeric_input - // Twist the light to change the value. When the current value changes, - // the green LED will flash, and the number that is currently being printed - // will be reset. Suppose you are at 2 and you want to go to 5. Rotate - // clockwise 3 green flashes, and you'll be there. - // The current value is printed through the rear leds. - // Get the result with get_input_digit. - static void input_digit(unsigned int min_digit, unsigned int max_digit); - // grab the value that is currently selected (based on twist orientation) - static unsigned int get_input_digit(); - - // prints charge state (using print_charge). - // if in a low battery state, flashes red for 50 ms, followed by a 1 second delay - // If you are using print_number, call it before this function if possible. - // I recommend the following (if using print_number()): - // ...code that may call print number... - // if(!printing_number()) - // print_charge(GLED); - // ...end of loop... - // see also print_charge for usage - static void print_power(); - - #ifdef ACCELEROMETER // accepts things like ACC_REG_TILT // TILT is now read by default in the private method, at the cost of 12 bytes. @@ -465,13 +366,10 @@ class hexbright { private: static void adjust_light(); - static void set_light_level(unsigned long level); static void apply_max_light_level(); static void detect_overheating(); static void detect_low_battery(); - static void update_number(); - // controls actual led hardware set. // As such, state = HIGH or LOW static void _set_led(unsigned char led, unsigned char state); @@ -498,3 +396,950 @@ class hexbright { // but it can't be because arduino doesn't support class variables static void fake_read_accelerometer(int* new_vector); }; + +#endif // HEXBRIGHT_H + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +///////////////////////////// hexbright.cpp ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + + +#ifdef BUILD_HACK + +#include "update_spin.h" +#include + +#ifndef __AVR // we're not compiling for arduino (probably testing), use these stubs +#include "pc_stubs.h" +#else +#include "read_adc.h" +#include "../digitalWriteFast/digitalWriteFast.h" +#endif + + +/// set #define-based options + +// select the appropriate set_light_level based on #defines... +#include "set_light_level.h" + +// default to no debug mode if no override has been set +#ifndef DEBUG +#define DEBUG DEBUG_OFF +#endif + + +/////////////////////////////////////////////// +/////////////HARDWARE INIT, UPDATE///////////// +/////////////////////////////////////////////// + +const float update_delay = UPDATE_DELAY; // in lock-step with the accelerometer + + +hexbright::hexbright() { +} + +#ifdef FLASH_CHECKSUM +int hexbright::flash_checksum() { + int checksum = 0; + for(int i=0; i<16384; i++) { + checksum+=pgm_read_byte(i); + } + return checksum; +} +#endif + +void hexbright::init_hardware() { + // These next 8 commands are for reference and cost nothing, + // as we are initializing the values to their default state. + pinModeFast(DPIN_PWR, OUTPUT); + digitalWriteFast(DPIN_PWR, LOW); + pinModeFast(DPIN_RLED_SW, INPUT); + pinModeFast(DPIN_GLED, OUTPUT); + pinModeFast(DPIN_DRV_MODE, OUTPUT); + pinModeFast(DPIN_DRV_EN, OUTPUT); + digitalWriteFast(DPIN_DRV_MODE, LOW); + digitalWriteFast(DPIN_DRV_EN, LOW); + +#if (DEBUG!=DEBUG_OFF) + // Initialize serial busses + Serial.begin(9600); + twi_init(); // for accelerometer + Serial.println("DEBUG MODE ON"); +#endif + +#if (DEBUG!=DEBUG_OFF && DEBUG!=DEBUG_PRINT) +#ifdef FREE_RAM + Serial.print("Ram available: "); + Serial.print(freeRam()); + Serial.println("/1024 bytes"); +#endif +#ifdef FLASH_CHECKSUM + Serial.print("Flash checksum: "); + Serial.println(flash_checksum()); +#endif +#endif //(DEBUG!=DEBUG_OFF && DEBUG!=DEBUG_PRINT) + +#ifdef ACCELEROMETER + enable_accelerometer(); +#endif + + // was this power on from battery? if so, it was a button press, even if it was too fast to register. + read_charge_state(); + if(get_charge_state()==BATTERY) + press_button(); +} + +word loopCount; +void hexbright::update() { + loopCount++; + + update_spin(); + + // power saving modes described here: http://www.atmel.com/Images/2545s.pdf + //run overheat protection, time display, track battery usage + +#ifdef LED + // regardless of desired led state, turn it off so we can read the button + _led_off(RLED); + delayMicroseconds(50); // let the light stabilize... + read_button(); + // turn on (or off) the leds, if appropriate + adjust_leds(); +#ifdef PRINT_NUMBER_H + update_number(); +#endif +#else + read_button(); +#endif + + read_thermal_sensor(); // takes about .2 ms to execute (fairly long, relative to the other steps) + read_charge_state(); + read_avr_voltage(); + +#ifdef ACCELEROMETER + read_accelerometer(); + find_down(); +#endif + detect_overheating(); + detect_low_battery(); + apply_max_light_level(); + + // change light levels as requested + adjust_light(); + +} + + +#ifdef FREE_RAM +//// freeRam function from: http://playground.arduino.cc/Code/AvailableMemory +int hexbright::freeRam () { + extern int __heap_start, *__brkval; + int v; + return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); +} +#endif + + +/////////////////////////////////////////////// +///////////////////Filters///////////////////// +/////////////////////////////////////////////// + +#ifdef ACCELEROMETER + +inline int hexbright::low_pass_filter(int last_estimate, int current_reading) { + // The sum of these two constant multipliers (which equals the divisor), + // should not exceed 210 (to avoid integer overflow) + // the individual values selected do effect the resulting sketch size :/ + return (2*last_estimate + 3*current_reading)/5; +} + +inline int hexbright::stdev_filter(int last_estimate, int current_reading) { + float stdev = 3.3; // our standard deviation due to noise (pre calculated using accelerometer data at rest) + int diff = -abs(last_estimate-current_reading); + float deviation = diff/stdev; + float probability = exp(deviation)/1.25; // 2.5 ~= M_SQRT2PI, /2 because cdf is only one way + // exp by itself takes 400-500 bytes. This isn't good. + return probability*last_estimate + (1-probability)*current_reading; +} + + +inline int hexbright::stdev_filter2(int last_estimate, int current_reading) { + // uses 1/deviation^2 for our cdf approximation + float stdev = 3.5; // our standard deviation due to noise (pre calculated using accelerometer data at rest) + int diff = abs(last_estimate-current_reading); + float deviation = diff/stdev; + float probability; + if(deviation>1) // estimate where we fall on the cdf + probability = 1/(deviation*deviation); + else + probability = .65; + // exp by itself takes 400-500 bytes. This isn't good. + return probability*last_estimate + (1-probability)*current_reading; +} + +inline int hexbright::stdev_filter3(int last_estimate, int current_reading) { + // uses 1/deviation^2 for our cdf approximation, switched to ints + int stdev = 3.5*10; // our standard deviation due to noise (pre calculated using accelerometer data at rest) + int diff = abs(last_estimate-current_reading)*10; + // differences < 4 have a deviation of 0 + int deviation = diff/stdev; + int probability; + if(deviation>1) // estimate where we fall on the cdf + probability = 100/(deviation*deviation); + else + probability = 70; + return (probability*last_estimate + (100-probability)*current_reading)/100; +} + +#endif // ACCELEROMETER + +/////////////////////////////////////////////// +////////////////LIGHT CONTROL////////////////// +/////////////////////////////////////////////// + +// Light level must be sufficiently precise for quality low-light brightness and accurate power adjustment at high brightness. +// light level should be converted to logarithmic, square root or cube root values (from lumens), so as to be perceptually linear... +// http://www.candlepowerforums.com/vb/newreply.php?p=3889844 +// This is handled inside of set_light_level. + + +int start_light_level = 0; +int end_light_level = OFF_LEVEL; // go to OFF_LEVEL once change_duration expires (unless set_light overrides) +int change_duration = 5000/update_delay; // stay on for 5 seconds +int change_done = 0; + +int max_light_level = MAX_LEVEL; + + +void hexbright::set_light(int start_level, int end_level, long time) { + // duration ranges from 1-MAXINT + // light_level can be from 0-1000 + int current_level = get_light_level(); + start_light_level = start_level == CURRENT_LEVEL ? current_level : start_level; + end_light_level = end_level == CURRENT_LEVEL ? current_level : end_level; + + change_duration = ((float)time)/update_delay; + change_done = 0; +#if (DEBUG==DEBUG_LIGHT) + Serial.print("Light adjust requested, start level: "); + Serial.println(start_light_level); + Serial.print("Over "); + Serial.print(change_duration); + Serial.println(" updates"); +#endif + +} + +int hexbright::get_light_level() { + if(change_done>=change_duration) + return end_light_level; + else + return (end_light_level-start_light_level)*((float)change_done/change_duration) +start_light_level; +} + +int hexbright::get_max_light_level() { + int light_level = get_light_level(); + + if(light_level>max_light_level) + return max_light_level; + return light_level; +} + +int hexbright::light_change_remaining() { + // change_done ends up at -1, add one to counter + // return (change_duration-change_done+1)*update_delay; + int tmp = change_duration-change_done; + if(tmp<=0) + return 0; + return tmp*update_delay; +} + +void hexbright::adjust_light() { + if(change_done<=change_duration) { + int light_level = hexbright::get_max_light_level(); + set_light_level(light_level); + + change_done++; + } +} + +void hexbright::apply_max_light_level() { + // if max_light_level has changed, guarantee a light adjustment: + // the second test guarantees that we won't turn on if we are + // overheating and just shut down + if(max_light_level < MAX_LEVEL && get_light_level()>MIN_OVERHEAT_LEVEL) { +#if (DEBUG!=DEBUG_OFF && DEBUG!=DEBUG_PRINT) + Serial.print("Max light level: "); + Serial.println(max_light_level); +#endif + change_done = change_done < change_duration ? change_done : change_duration; + } +} + + +/////////////////////////////////////////////// +///////////////////LED CONTROL///////////////// +/////////////////////////////////////////////// + +#ifdef LED + +// >0 = countdown, 0 = change state, -1 = state changed +int led_wait_time[2] = {-1, -1}; +int led_on_time[2] = {-1, -1}; +unsigned char led_brightness[2] = {0, 0}; +byte rledMap[4] = {0b0001, 0b0101, 0b0111, 0b1111}; + +void hexbright::set_led(unsigned char led, int on_time, int wait_time, unsigned char brightness) { +#if (DEBUG==DEBUG_LED) + Serial.println("activate led"); +#endif + led_on_time[led] = on_time/update_delay; + led_wait_time[led] = wait_time/update_delay; + led_brightness[led] = brightness; +} + + +unsigned char hexbright::get_led_state(unsigned char led) { + //returns true if the LED is on + if(led_on_time[led]>=0) { + return LED_ON; + } else if(led_wait_time[led]>0) { + return LED_WAIT; + } else { + return LED_OFF; + } +} + +inline void hexbright::_led_on(unsigned char led) { + if(led == RLED) { // DPIN_RLED_SW + pinModeFast(DPIN_RLED_SW, OUTPUT); + + byte l = rledMap[led_brightness[RLED]>>6]; + byte r = 1<<(loopCount & 0b11); + if(l & r) { + digitalWriteFast(DPIN_RLED_SW, HIGH); + } else { + digitalWriteFast(DPIN_RLED_SW, LOW); + } + } else { // DPIN_GLED + analogWrite(DPIN_GLED, led_brightness[GLED]); + } +} + +inline void hexbright::_led_off(unsigned char led) { + if(led == RLED) { // DPIN_RLED_SW + digitalWriteFast(DPIN_RLED_SW, LOW); + pinModeFast(DPIN_RLED_SW, INPUT); + } else { // DPIN_GLED + // digitalWriteFast doesn't work when set to a non-digital value, + // using analogWrite. digitalWrite could be used, but would increase + // complexity in porting to straight AVR. + analogWrite(DPIN_GLED, LOW); + } +} + +inline void hexbright::adjust_leds() { + // turn off led if it's expired +#if (DEBUG==DEBUG_LED) + if(led_on_time[GLED]>=0) { + Serial.print("green on countdown: "); + Serial.println(led_on_time[GLED]*update_delay); + } else if (led_on_time[GLED]<0 && led_wait_time[GLED]>=0) { + Serial.print("green wait countdown: "); + Serial.println((led_wait_time[GLED])*update_delay); + } + if(led_on_time[RLED]>=0) { + Serial.print("red on countdown: "); + Serial.println(led_on_time[RLED]*update_delay); + } else if (led_on_time[RLED]<0 && led_wait_time[RLED]>=0) { + Serial.print("red wait countdown: "); + Serial.println((led_wait_time[RLED])*update_delay); + } +#endif + + int i=0; + for(i=0; i<2; i++) { + if(led_on_time[i]>0) { + _led_on(i); + led_on_time[i]--; + } else if(led_on_time[i]==0) { + _led_off(i); + led_on_time[i]--; + } else if (led_wait_time[i]>=0) { + led_wait_time[i]--; + } + } +} + +#endif + + +/////////////////////////////////////////////// +/////////////////////BUTTON//////////////////// +/////////////////////////////////////////////// + +#define BUTTON_FILTER 7 // 00000111 (this is applied at read time) +#define BUTTON_JUST_OFF(state) (state==4) // 100 = just off +#define BUTTON_JUST_ON(state) (state==1) // 001 = just on +#define BUTTON_STILL_OFF(state) (state==0) // 000 = still off +// 010, 011, 101, 110, 111 = still on +#define BUTTON_STILL_ON(state) !(BUTTON_JUST_OFF(state) | BUTTON_JUST_ON(state) | BUTTON_STILL_OFF(state)) +#define BUTTON_ON(state) (state & 3) // either of right most bits is on: 00000011 (not 100 or 000) +#define BUTTON_OFF(state) !BUTTON_ON(state) + +// button state could hold a history of the last 8 switch values, 1 == on, 0 == off. +// for debouncing, we only need the most recent 3, so BUTTON_FILTER=7, 00000111 +// This is applied during the read process. +unsigned char button_state = 0; + +unsigned long time_last_pressed = 0; // the time that button was last pressed +unsigned long time_last_released = 0; // the time that the button was last released + +byte press_override = false; + +void hexbright::press_button() { + press_override = true; +} + +BOOL hexbright::button_pressed() { + return BUTTON_ON(button_state); +} + +BOOL hexbright::button_just_pressed() { + return BUTTON_JUST_ON(button_state); +} + +BOOL hexbright::button_just_released() { + return BUTTON_JUST_OFF(button_state); +} + +int hexbright::button_pressed_time() { + if(BUTTON_ON(button_state) || BUTTON_JUST_OFF(button_state)) { + return millis()-time_last_pressed; + } else { + return time_last_released - time_last_pressed; + } +} + +int hexbright::button_released_time() { + if(BUTTON_ON(button_state)) { + return time_last_pressed-time_last_released; + } else { + return millis()-time_last_released; + } +} + +void hexbright::read_button() { + if(BUTTON_JUST_OFF(button_state)) { + // we update time_last_released before the read, so that the very first time through after a release, + // button_released_time() returns the /previous/ button_released_time. + time_last_released=millis(); +#if (DEBUG==DEBUG_BUTTON) + Serial.println("Button just released"); + Serial.print("Time spent pressed (ms): "); + Serial.println(time_last_released-time_last_pressed); +#endif + } + + /* READ THE BUTTON!!! + button_state = button_state << 1; // make space for the new value + button_state = button_state | digitalReadFast(DPIN_RLED_SW); // add the new value + button_state = button_state & BUTTON_FILTER; // remove excess values */ + // Doing the three commands above on one line saves 2 bytes. We'll take it! + byte read_value = digitalReadFast(DPIN_RLED_SW); + if(press_override) { + read_value = 1; + press_override = false; + } + button_state = ((button_state<<1) | read_value) & BUTTON_FILTER; + + if(BUTTON_JUST_ON(button_state)) { + time_last_pressed=millis(); +#if (DEBUG==DEBUG_BUTTON) + Serial.println("Button just pressed"); + Serial.print("Time spent released (ms): "); + Serial.println(time_last_pressed-time_last_released); +#endif + } +} + +/////////////////////////////////////////////// +////////////////ACCELEROMETER////////////////// +/////////////////////////////////////////////// + +#ifdef ACCELEROMETER + +// return degrees of movement? +// Possible things to work with: +//MOVE_TYPE, value returned from a successful detect_movement +#define ACCEL_NONE 0 // nothing +#define ACCEL_TWIST 1 // return degrees - light axis remains constant +#define ACCEL_TURN 2 // return degrees - light axis changes +#define ACCEL_DROP 3 // return change of velocity - period of no acceleration before impact? +#define ACCEL_TAP 4 // return change of velocity - acceleration before impact + +// I considered operating with bytes to save space, but the savings were +// offset by still needing to do /some/ things as ints. I've not completely +// tested which is more compact, but preliminary work suggests difficulty +// in the implementation with little to no benefit. + + +unsigned char tilt = 0; +int vectors[] = {0,0,0, 0,0,0, 0,0,0, 0,0,0}; +int current_vector = 0; +unsigned char num_vectors = 4; +int down_vector[] = {0,0,0}; + +/// SETUP/MANAGEMENT + +void hexbright::enable_accelerometer() { + // Configure accelerometer + byte config[] = { + ACC_REG_INTS, // First register (see next line) + 0xE4, // Interrupts: shakes, taps + 0x00, // Mode: not enabled yet + 0x00, // Sample rate: 120 Hz (see datasheet page 19) + 0x0F, // Tap threshold + 0x05 // Tap debounce samples + }; + twi_writeTo(ACC_ADDRESS, config, sizeof(config), true /*wait*/, true /*send stop*/); + + // Enable accelerometer + byte enable[] = {ACC_REG_MODE, 0x01}; // Mode: active! + twi_writeTo(ACC_ADDRESS, enable, sizeof(enable), true /*wait*/, true /*send stop*/); + + // pinModeFast(DPIN_ACC_INT, INPUT); + // digitalWriteFast(DPIN_ACC_INT, HIGH); +} + +void hexbright::read_accelerometer() { + /*unsigned long time = 0; + if((millis()-init_time)>*/ + // advance which vector is considered the first + next_vector(); + char read=0; + while(read!=4) { + byte reg = ACC_REG_XOUT; + twi_writeTo(ACC_ADDRESS, ®, sizeof(reg), true /*wait*/, true /*send stop*/); + byte acc_data[4]; + twi_readFrom(ACC_ADDRESS, acc_data, sizeof(acc_data), true /*send stop*/); + read = 0; + int i = 0; + for(i; i<4; i++) { + char tmp = acc_data[i]; + if (tmp & 0x40) { // Bx1xxxxxx, + // invalid data, re-read per data sheet page 14 + continue; // continue, so we finish flushing the read buffer. + } + if(i==3){ //read tilt register + tilt = tmp; + } else { // read vector + if(tmp & 0x20) // Bxx1xxxxx, it's negative + tmp |= 0xC0; // extend to B111xxxxx + vectors[current_vector+i] = stdev_filter3(vector(1)[i], tmp*(100/21.3)); + } + read++; // successfully read. + } + } +} + +unsigned char hexbright::read_accelerometer(unsigned char acc_reg) { + if (!digitalReadFast(DPIN_ACC_INT)) { + byte acc_data; + twi_writeTo(ACC_ADDRESS, &acc_reg, sizeof(acc_reg), true /*wait*/, true /*send stop*/); + twi_readFrom(ACC_ADDRESS, &acc_data, sizeof(acc_data), true /*send stop*/); + return acc_data; + } + return 0; +} + + +inline void hexbright::find_down() { + // currently, we're just averaging the last four data points. + // Down is the strongest constant acceleration we experience + // (assuming we're not dropped). Heuristics to only find down + // under specific circumstances have been tried, but they only + // tell us when down is less certain, not where it is... + copy_vector(down_vector, vector(0)); // copy first vector to down + double magnitudes = magnitude(vector(0)); + for(int i=1; i>2; // shift us all the way to the right + // PoLa: 5, 6 = horizontal, 1 = up, 2 = down, 0 = unknown + if(tmp & 0x04) + return TILT_HORIZONTAL; + return tmp; +} + +char hexbright::get_tilt_rotation() { + static unsigned char last = 0; + unsigned char current = tilt & 0x1F; // filter out tap/shake registers + // 21,22,26,25 (in order, rotating when horizontal) + switch(current ) { + case 21: // 10101 + current = 1; + break; + case 22: // 10110 + current = 2; + break; + case 26: // 11010 + current = 3; + break; + case 25: // 11001 + current = 4; + break; + default: // we can't determine orientation with this reading + last = 0; + return 0; + } + + if(last==0) { // previous reading wasn't usable + last = current; + return 0; + } + + // we have two valid values, calculate! + char retval = last-current; + last = current; + if(retval*retval>1) { // switching from a 4 to a 1 or vice-versa + retval = -(retval%2); + } +#if (DEBUG==DEBUG_ACCEL) + if(retval!=0) { + Serial.print("tilt rotation: "); + Serial.println((int)retval); + } +#endif + return retval; +} + +/// some sample functions using vector operations + +double hexbright::angle_change() { + int* vec1 = vector(0); + int* vec2 = vector(1); + return angle_difference(dot_product(vec1, vec2), + magnitude(vec1), + magnitude(vec2)); +} + +void hexbright::absolute_vector(int* out_vector, int* in_vector) { + sub_vectors(out_vector, in_vector, down_vector); +} + +double hexbright::difference_from_down() { + int light_axis[3] = {0, -100, 0}; + return (angle_difference(dot_product(light_axis, down_vector), 100, 100)); +} + +BOOL hexbright::stationary(int tolerance) { + // low acceleration vectors + return abs(magnitude(vector(0))-100)tolerance; +} + +char hexbright::get_spin() { + // quick formula: + //(atan2(vector(1)[0], vector(1)[2]) - atan2(vector(0)[0], vector(0)[2]))*32; + // we cache the last position, because it takes up less space. + static char last_spin = 0; + // 2*PI*32 = 200 + return atan2(down()[0], down()[2])*32 - atan2(vector(0)[0], vector(0)[2])*32; + char spin = atan2(vector(0)[0], vector(0)[2])*32; + if(abs(last_spin)-10<0) { + + } + + return spin; +} + +/// VECTOR TOOLS +int* hexbright::vector(unsigned char back) { + return vectors+((current_vector/3+back)%num_vectors)*3; +} + +int* hexbright::down() { + return down_vector; +} + +void hexbright::next_vector() { + current_vector=((current_vector/3+(num_vectors-1))%num_vectors)*3; +} + +double hexbright::angle_difference(int dot_product, double magnitude1, double magnitude2) { + // multiply to account for 100 being equal to one for our vectors + double tmp = dot_product*100/(magnitude1*magnitude2); + return acos(tmp)/3.14159; +} + +int hexbright::dot_product(int* vector1, int* vector2) { + // the max value sum could hit is technically > 2^16. + // However, the only instance where this is possible is if both vectors have + // experienced maximum acceleration on all axes - with 4 of the 6 being the max in the negative + // direction (-32 min, 31 max), (31*100/21.3) = 145, (-32*100/21.3) = -150 + // 3*(150^2) = 67500 (not safe) + // (150*150)+(150*150)+(145*145) = 66025 (not safe) + // (150*150)+(150*145)+(145*145) = 65275 (safe) + // 3*(145^2) = 63075 (safe) + // Given the difficulty of triggering this condition, I'm using an int. + // This could also be avoided by /100 inside the loop, costing 12 bytes. + // We should revisit this decision when drop detect code has been written. + // An alternate solution would be to convert using READING*100/21.65, giving + // a max of 147. 3*(147^2) = 64827 (safe). A max of 148 would be safe so + // long as no more than 5 values are -32. + unsigned int sum = 0; // max value is about 3*(150^2), a tad over the maximum unsigned int + for(int i=0;i<3;i++) { + //sum+=vector1[i]*vector2[i]/100; // avoids overflow, but costs space + sum+=vector1[i]*vector2[i]; + } + return sum/100; // small danger of overflow +} + +void hexbright::cross_product(int * axes_rotation, + int* in_vector1, + int* in_vector2, + double angle_difference) { + for(int i=0; i<3; i++) { + axes_rotation[i] = (in_vector1[(i+1)%3]*in_vector2[(i+2)%3] \ + - in_vector1[(i+2)%3]*in_vector2[(i+1)%3]); + //axes_rotation[i] /= magnitude(in_vector1)*magnitude(in_vector2); + //axes_rotation[i] /= asin(angle_difference*3.14159); + } +} + +double hexbright::magnitude(int* vector) { + // This has a small possibility of failure, see the comments at the start + // of dot_product. A long would cost another 28 bytes, a double 42. + // A cheaper solution (costing 20 bytes) is to divide the squaring by 100, + // then multiply the sqrt by 10 (commented out). + unsigned int result = 0; + for(int i=0; i<3;i++) { + result += vector[i]*vector[i]; // vector[i]*vector[i]/100; + } + return sqrt(result); // sqrt(result)*10; +} + +void hexbright::normalize(int* out_vector, int* in_vector, double magnitude) { + for(int i=0; i<3; i++) { + // normalize to 100, not 1 + out_vector[i] = in_vector[i]/magnitude*100; + } +} + +void hexbright::sum_vectors(int* out_vector, int* in_vector1, int* in_vector2) { + for(int i=0; i<3;i++) { + out_vector[i] = in_vector1[i]+in_vector2[i]; + } +} + +void hexbright::sub_vectors(int* out_vector, int* in_vector1, int* in_vector2) { + for(int i=0; i<3;i++) { + out_vector[i] = in_vector1[i]-in_vector2[i]; + } +} + +void hexbright::copy_vector(int* out_vector, int* in_vector) { + for(int i=0; i<3;i++) { + out_vector[i] = in_vector[i]; + } +} + +void hexbright::print_vector(int* vector, const char* label) { +#if (DEBUG!=DEBUG_OFF) + for(int i=0; i<3; i++) { + Serial.print(vector[i]); + Serial.print("/"); + } + Serial.println(label); +#endif +} + +#endif + + +/////////////////////////////////////////////// +////////////////TEMPERATURE//////////////////// +/////////////////////////////////////////////// + +int thermal_sensor_value = 0; +void hexbright::read_thermal_sensor() { + // do not call this directly. Call get_temperature() + // read temperature setting + // device data sheet: http://ww1.microchip.com/downloads/en/devicedoc/21942a.pdf + + thermal_sensor_value = read_adc(APIN_TEMP); +} + +int hexbright::get_thermal_sensor() { + return thermal_sensor_value; +} + +int hexbright::get_celsius() { + // 0C ice water bath for 20 minutes: 153. + // 40C water bath for 20 minutes (measured by medical thermometer): 275 + // intersection with 0: 50 = (40C-0C)/(275-153)*153 + + // 40.05 is to force the division to floating point. The extra parenthesis are to + // tell the compiler to pre-evaluate the expression. + return thermal_sensor_value * ((40.05-0)/(275-153)) - 50; +} + + +int hexbright::get_fahrenheit() { + //return get_celsius()*18/10+32; + // algebraic form of (get_celsius' formula)*18/10+32 + // I was lazy and pasted (x*((40.05-0)/(275-153)) - 50)*18/10+32 into wolfram alpha + return .590902*thermal_sensor_value-58; +} + +// If the ambient temperature is above your max temp, your light is going to be pretty dim... + +void hexbright::detect_overheating() { + unsigned int temperature = get_thermal_sensor(); + + max_light_level = max_light_level+(OVERHEAT_TEMPERATURE-temperature); + // min, max levels... + max_light_level = max_light_level > MAX_LEVEL ? MAX_LEVEL : max_light_level; + max_light_level = max_light_level < MIN_OVERHEAT_LEVEL ? MIN_OVERHEAT_LEVEL : max_light_level; +#if (DEBUG==DEBUG_TEMP) + static float printed_temperature = 0; + static float average_temperature = -1; + if(average_temperature < 0) { + average_temperature = temperature; + Serial.println("Have you calibrated your thermometer?"); + Serial.println("Instructions are in get_celsius."); + } + average_temperature = (average_temperature*4+temperature)/5; + if (abs(printed_temperature-average_temperature)>1) { + printed_temperature = average_temperature; + Serial.print(millis()); + Serial.print(" ms, average reading: "); + Serial.print(printed_temperature); + Serial.print(" (celsius: "); + Serial.print(get_celsius()); + Serial.print(") (fahrenheit: "); + Serial.print(get_fahrenheit()); + Serial.println(")"); + } +#endif +} + + +/////////////////////////////////////////////// +////////////////AVR VOLTAGE//////////////////// +/////////////////////////////////////////////// + +int band_gap_reading = 0; +int lowest_band_gap_reading = 1000; + +void hexbright::read_avr_voltage() { + band_gap_reading = read_adc(APIN_BAND_GAP); + if(get_charge_state()==BATTERY) + lowest_band_gap_reading = band_gap_reading < lowest_band_gap_reading ? band_gap_reading : lowest_band_gap_reading; +} + +int hexbright::get_avr_voltage() { + // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 + // this is the only place we actually convert to voltage, reducing the space used for most programs. + return ((long)1023*1100) / band_gap_reading; +} + +BOOL hexbright::low_voltage_state() { + static BOOL low = false; + // lower band gap value corresponds to a higher voltage, trigger + // low voltage state if band gap value goes too high. + // I have a value of 2 for this to work (with a 150 ms delay in read_adc). + // tighter control means earlier detection of low battery state + if (band_gap_reading > lowest_band_gap_reading+2) { + low = true; + } + return low; +} + +void hexbright::detect_low_battery() { + if (low_voltage_state() == true && max_light_level>500) { + max_light_level = 500; + } +} + + +/////////////////////////////////////////////// +//////////////////CHARGING///////////////////// +/////////////////////////////////////////////// + +// we store the last two charge readings in charge_state, and due to the specific +// values chosen for the #defines, we greatly decrease the possibility of returning +// BATTERY when we are still connected over USB. This costs 14 bytes. + +// BATTERY is the median value, which is only returned if /both/ halves are BATTERY. +// If one of the two values is CHARGING, we return CHARGING +// If one of the two values is CHARGED (and neither CHARGING), we return CHARGED +// Otherwise, BATTERY is both values and is returned +unsigned char charge_state = BATTERY; + +void hexbright::read_charge_state() { + unsigned int charge_value = read_adc(APIN_CHARGE); +#if (DEBUG==DEBUG_CHARGE) + Serial.print("Current charge reading: "); + Serial.println(charge_value); +#endif + // <128 charging, >768 charged, battery + charge_state <<= 4; + if(charge_value<128) + charge_state += CHARGING; + else if (charge_value>768) + charge_state += CHARGED; + else + charge_state += BATTERY; +} + +unsigned char hexbright::get_charge_state() { + // see more details on how this works at the top of this section + return charge_state & (charge_state>>4); +} + + +/////////////////////////////////////////////// +//KLUDGE BECAUSE ARDUINO DOESN'T SUPPORT CLASS VARIABLES/INSTANTIATION +/////////////////////////////////////////////// +#ifdef ACCELEROMETER +void hexbright::fake_read_accelerometer(int* new_vector) { + next_vector(); + for(int i=0; i<3; i++) { + vector(0)[i] = stdev_filter3(vector(1)[i], new_vector[i]); + //vector(0)[i] = new_vector[i]; + } +} +#endif // ACCELEROMETER + +#endif //BUILD_HACK diff --git a/libraries/hexbright/includes/NotArduino.h b/libraries/hexbright/pc_stubs.h similarity index 100% rename from libraries/hexbright/includes/NotArduino.h rename to libraries/hexbright/pc_stubs.h diff --git a/libraries/hexbright/includes/pin_interface.h b/libraries/hexbright/read_adc.h similarity index 100% rename from libraries/hexbright/includes/pin_interface.h rename to libraries/hexbright/read_adc.h diff --git a/libraries/hexbright/set_light_level.cpp b/libraries/hexbright/set_light_level.cpp new file mode 100644 index 0000000..97c6657 --- /dev/null +++ b/libraries/hexbright/set_light_level.cpp @@ -0,0 +1,57 @@ +#include "hexbright.h" + +void set_light_level_linear(unsigned long level) { + // sets actual light level, altering value to be perceptually linear, based on steven's area brightness (cube root) + + // LOW 255 approximately equals HIGH 48/49. There is a color change. + // Values < 4 do not provide any light. + // I don't know about relative power draw. + + // look at linearity_test.ino for more detail on these algorithms. + + digitalWriteFast(DPIN_PWR, HIGH); + if(level == 0) { + // lowest possible power, but cpu still running (DPIN_PWR still high) + digitalWriteFast(DPIN_DRV_MODE, LOW); + analogWrite(DPIN_DRV_EN, 0); + } else if(level == OFF_LEVEL) { + // power off (DPIN_PWR LOW) + digitalWriteFast(DPIN_PWR, LOW); + digitalWriteFast(DPIN_DRV_MODE, LOW); + analogWrite(DPIN_DRV_EN, 0); + } else { + byte value; + if(level<=500) { + digitalWriteFast(DPIN_DRV_MODE, LOW); + value = (byte)(.000000633*(level*level*level)+.000632*(level*level)+.0285*level+3.98); + } else { + level -= 500; + digitalWriteFast(DPIN_DRV_MODE, HIGH); + value = (byte)(.00000052*(level*level*level)+.000365*(level*level)+.108*level+44.8); + } + analogWrite(DPIN_DRV_EN, value); + } +} + + +void set_light_level_simple(unsigned long level) { + // Values < 4 do not provide any light. + digitalWriteFast(DPIN_PWR, HIGH); + if(level == OFF_LEVEL) { + // power off (DPIN_PWR LOW) + digitalWriteFast(DPIN_PWR, LOW); + digitalWriteFast(DPIN_DRV_MODE, LOW); + analogWrite(DPIN_DRV_EN, 0); + } else { + byte value; + if(level<=500) { + digitalWriteFast(DPIN_DRV_MODE, LOW); + value = level/1.96078431373; // costs about 40 bytes more than a bit-shift + } else { + level -= 500; + value = level/1.96078431373; + digitalWriteFast(DPIN_DRV_MODE, HIGH); + } + analogWrite(DPIN_DRV_EN, value); + } +} diff --git a/libraries/hexbright/set_light_level.h b/libraries/hexbright/set_light_level.h new file mode 100644 index 0000000..08a4dbf --- /dev/null +++ b/libraries/hexbright/set_light_level.h @@ -0,0 +1,43 @@ +#ifndef SET_LIGHT_LEVEL_H +#define SET_LIGHT_LEVEL_H + +// All set_light_level versions accept values from -1 to 1000 (-1==OFF_LEVEL); +// the specific version chosen is called internal to the hexbright library. +// Calling directly should work, but you may have some weird bugs. + +// A perceptually linear light output function. +// Sets actual light level, altering value to +// be perceptually linear, based on steven's +// area brightness (cube root) +extern void set_light_level_linear(unsigned long level); + +// WIP +// A perceptually linear light output function. +// Sets actual light level, altering value to +// be perceptually linear, based on steven's +// area brightness (cube root) +// Also tries to avoid glitches as we transition +// between low/high output by carefully timing +// pwm transitions +extern void set_light_level_smooth(unsigned long level); + +// A simple light output function +// 0-500: 0-256 low +// 501-1000: 0-256 high +extern void set_light_level_simple(unsigned long level); + + + +////// actually do selection... +// set_light_level_simple requested? +#ifdef SET_LIGHT_LEVEL_SIMPLE +#define set_light_level set_light_level_simple +#endif + +// default to set_light_level_linear +#ifndef set_light_level +#define set_light_level set_light_level_linear +#endif + + +#endif // SET_LIGHT_LINEAR_H diff --git a/libraries/twi/twi.c b/libraries/hexbright/twi.c similarity index 100% rename from libraries/twi/twi.c rename to libraries/hexbright/twi.c diff --git a/libraries/twi/twi.h b/libraries/hexbright/twi.h similarity index 100% rename from libraries/twi/twi.h rename to libraries/hexbright/twi.h diff --git a/libraries/hexbright/update_spin.h b/libraries/hexbright/update_spin.h new file mode 100644 index 0000000..ddbc8bc --- /dev/null +++ b/libraries/hexbright/update_spin.h @@ -0,0 +1,95 @@ +#ifdef BUILD_HACK +/////////////////////////////////////////////// +///////////overrideable functions////////////// +/////////////////////////////////////////////// + + +#ifndef UPDATE_SPIN +#define UPDATE_SPIN +// default update_spin, only added if an alternative is not requested. + +void update_spin() { + static unsigned long continue_time=0; + unsigned long now; + +#if (DEBUG==DEBUG_LOOP) + unsigned long start_time=micros(); +#endif + + do { + now = micros(); + } while ((signed long)(continue_time - now) > 0); // not ready for update + + // if we're in debug mode, let us know if our loops are too large +#if (DEBUG!=DEBUG_OFF && DEBUG!=DEBUG_PRINT) + static int i=0; +#if (DEBUG==DEBUG_LOOP) + static unsigned long last_time = 0; + if(!i) { + Serial.print("Time used: "); + Serial.print(start_time-last_time); + Serial.println("/8333"); + } + last_time = now; +#endif + if(now-continue_time>5000 && !i) { + // This may be caused by too much processing for our update_delay, or by too many print statements) + // If you're triggering this, your button and light will react more slowly, and some accelerometer + // data is being missed. + Serial.println("WARNING: code is too slow"); + } + if (!i) + i=(1000/UPDATE_DELAY); // display loop output every second + else + i--; +#endif + + // advance time at the same rate as values are changed in the accelerometer. + // advance continue_time here, so the first run through short-circuits, + // meaning we will read hardware immediately after power on. + continue_time = continue_time+(int)(1000*UPDATE_DELAY); +} + +#endif // UPDATE_SPIN + + + +#ifdef STROBE_H + +/// we provide an alternative update_spin function so we can strobe in the background +void update_spin() { + static unsigned long continue_time=0; + unsigned long now; + + while (true) { + do { + now = micros(); + } while (next_strobe > now && // not ready for strobe + continue_time > now); // not ready for update + if (next_strobe <= now) { + if (now - next_strobe <26) { + digitalWriteFast(DPIN_DRV_EN, HIGH); + delayMicroseconds(strobe_duration); + digitalWriteFast(DPIN_DRV_EN, LOW); + } + next_strobe += strobe_delay; + } + if(continue_time <= now) { + if(strobe_delay>UPDATE_DELAY*1000 && // we strobe less than once every 8333 microseconds + next_strobe-continue_time < 4000) // and the next strobe is within 4000 microseconds (may occur before we return) + continue; + else + break; + } + } // do nothing... (will short circuit once every 70 minutes (micros maxint)) + + + // advance time at the same rate as values are changed in the accelerometer. + // advance continue_time here, so the first run through short-circuits, + // meaning we will read hardware immediately after power on. + continue_time = continue_time+(int)(1000*UPDATE_DELAY); +} + +#endif // STROBE_H + +#endif // BUILD_HACK diff --git a/programs/BikeLight/BikeLight.ino b/programs/BikeLight/BikeLight.ino new file mode 100644 index 0000000..b3231c4 --- /dev/null +++ b/programs/BikeLight/BikeLight.ino @@ -0,0 +1,109 @@ +/* +Copyright (c) 2012, "David Hilton" +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ +#include +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK +#include + +#define OFF_MODE 0 +#define BLINKY_MODE 1 +#define CYCLE_MODE 2 +#define OFF_TIME 650 // milliseconds before going off on the next normal button press +#define BRIGHTNESS_OFF 0 + +unsigned long short_press_time = 0; + + +int mode = 0; + + +hexbright hb; + +void setup() { + hb.init_hardware(); +} + +void loop() { + hb.update(); + static int brightness_level = 1; + randomSeed(analogRead(0)); + int levels[] = {500,1000}; + + //// Button actions to recognize, one-time actions to take as a result + if(hb.button_just_released()) { + if(hb.button_pressed_time()<300) { //<300 milliseconds + + if(short_press_time+OFF_TIME1000) { // if held for over 1000 milliseconds (whether or not it's been released), go to OFF mode + mode = OFF_MODE; + hb.set_light(CURRENT_LEVEL, OFF_LEVEL, NOW); + // in case we are under usb power, reset state + brightness_level = 1; + } + + + //// Actions over time for a given mode + if(mode == BLINKY_MODE) { // random blink + static int i = 0; + if(!i) { + // fade from max to 0 over a random time btwn 30 and 350 milliseconds (length flash "on") + hb.set_light(MAX_LEVEL,0,random(30,350)); + // only light up every random number of times btwn 6 and 60 through + // which should equate to 50 - 500 ms (length flash "off") + i=random(50,500)/8.3333; + } + i--; + } else if (mode == CYCLE_MODE) { // print the current flashlight temperature + if(!printing_number()) { + print_number(hb.get_fahrenheit()); + } + } else if (mode == OFF_MODE) { // charging, or turning off + if(!printing_number()) { + print_charge(GLED); + } + } +} diff --git a/programs/BikeLight/README.md b/programs/BikeLight/README.md new file mode 100644 index 0000000..ffa98ea --- /dev/null +++ b/programs/BikeLight/README.md @@ -0,0 +1,17 @@ +Based on Functional combined with tactical. +--- + +I wanted to have a couple of brightness settings, a special random blinking mode, and have the button turn directly OFF after a short time during which the button chooses the mode. + +Bike functions +* random period flashing for maximum visibility + * the flashes use the interesting blinky mode from "functional" which brings the light on (to full) and then reduces it to 0 over a random (short) length of time. + * I added the random periods - random on length and random off period. +* two other modes - medium and high - for night illumination of the road as a normal light. + +Use +* First press medium brightness +* subsequent short presses change between medium and high brightness +* short press after 650 ms results in OFF +* medium long press (>300ms and <1000ms) results in random blinky mode +* really long press >100ms results in OFF \ No newline at end of file diff --git a/programs/action_timer/action_timer.ino b/programs/action_timer/action_timer.ino index 312d3e6..74bf7cb 100755 --- a/programs/action_timer/action_timer.ino +++ b/programs/action_timer/action_timer.ino @@ -27,8 +27,13 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include +#include +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include #include hexbright hb; @@ -69,8 +74,8 @@ void loop() { // nothing's happening, turn off hb.set_light(CURRENT_LEVEL, OFF_LEVEL, NOW); // or print charge state if we're plugged in - if(!hb.printing_number()) { - hb.print_charge(GLED); + if(!printing_number()) { + print_charge(GLED); } } else if (action_mode == WAIT_MODE) { // we are waiting to do something @@ -79,9 +84,9 @@ void loop() { action_mode=OFF_MODE; } else { // display our current wait time... - if(!hb.printing_number()) { + if(!printing_number()) { // print hours and minutes remaining - hb.print_number(action_time_remaining(action_time)); + print_number(action_time_remaining(action_time)); } } } @@ -130,9 +135,9 @@ void loop() { } break; case SET_ACTION_MODE: - hb.input_digit(action_time*10, action_time*10+time[place]); + input_digit(action_time*10, action_time*10+time[place]); if(hb.button_just_released() && hb.button_pressed_time()>300) { - action_time = hb.get_input_digit(); + action_time = get_input_digit(); place++; } else if (hb.button_just_released() && hb.button_pressed_time()<300) { // use the value from the last timer (0 on the first run) @@ -169,4 +174,4 @@ int action_time_remaining(int action_time) { minutes_remaining+=60; } return hours_remaining*100+minutes_remaining; -} +} diff --git a/programs/functional/functional.ino b/programs/functional/functional.ino old mode 100644 new mode 100755 index 71ddf60..689478a --- a/programs/functional/functional.ino +++ b/programs/functional/functional.ino @@ -26,11 +26,13 @@ The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include +#include +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include - #define OFF_MODE 0 #define BLINKY_MODE 1 #define CYCLE_MODE 2 @@ -76,12 +78,12 @@ void loop() { } i--; } else if (mode == CYCLE_MODE) { // print the current flashlight temperature - if(!hb.printing_number()) { - hb.print_number(hb.get_fahrenheit()); + if(!printing_number()) { + print_number(hb.get_fahrenheit()); } } else if (mode == OFF_MODE) { // charging, or turning off - if(!hb.printing_number()) { - hb.print_charge(GLED); + if(!printing_number()) { + print_charge(GLED); } } -} +} diff --git a/programs/set_and_remember/README.md b/programs/set_and_remember/README.md new file mode 100644 index 0000000..340e6b6 --- /dev/null +++ b/programs/set_and_remember/README.md @@ -0,0 +1,46 @@ + +A modification to the excellent everyday program written by [wbattestilli](https://github.com/wbattestilli/hexbright). + +------------------------------------------------ + +Set_and_Remember +================ + +Set_and_Remember is intended to be an everyday, useful program. It requires Dave Hilton's awesome hexbright library. It is basically a copy of up_n_down with a several changes. Those changes are: + +* 1. Instead of setting the brightness when the light is first turned on based on the level to which it is pointing, it reads a previously stored level from EEPROM. +* 2. Added code to solve the initial lock mode problem. +* 3. Nightlight mode glows the tailcap green and turns it off when the main light is on. +* 4. When entering or exiting locked mode, the tailcap is flashed 3 times to confirm. While in locked mode, any click besides unlock results in the tail flashing red 2 times. +* 5. The implementation for morse code signalling has been rewritten to save space. It also uses the brightness stored in mode 1 for the flashes. + +Basic Operation +---------------- +* Short (<350ms) clicks change the mode. Mode can only be changed from the off state. A simple click will always turn off the flashlight when it is on. The modes are as follows: + +* 1 Click-Normal flashlight mode + * Will turn the light on a stored light level. The level is stored in EEPROM so it is remembered between runs. + * Once on, you can hold the button for more than 350ms and change its angle in relation to the ground to adjust the brightness. Let go of the button to lock in the current brightness. Pointing at the ground is the lowest setting and horizontal is the brightest setting. + +* 2 Clicks-Blinking mode + * If the flashlight is pointing at the ground it will start blinking at about 0.1Hz + * If it is raised 45 degrees from the ground, it will start blinking at about 1.6Hz + * If it is horizontal or angled up, it will start blinking at about 15Hz + * Holding the button and changing the angle of the flashlight with the ground allows you to alter the flash rate as described in turning it on. + +* 3 Clicks-Nightlight mode + * When sitting still, the tailcap glows green. When picked up, the main light turns on to the saved nightlight setting and the tail cap turns off. + * If not moved for 5 seconds the main light dims to off and the tailcap starts to glow green again. + * Holding the button while the main light is on will allow adjustment of the brightness in the same manner as mode 1. This nightlight brightness will be saved in EEPROM so it is remembered between runs. + +* 4 Clicks-SOS mode + * Light blinks the morse code for SOS using the brightness level set in mode 1. + +* 5 Clicks-Lock mode + * When locked, the light will not turn on. Any clicks while locked will cause the tail cap to flash red 2 times. Clicking 5 times when locked will unlock the light. After locking or unlocking the tail cap leds will flash 3 times as confirmation. Red for entering locked mode and green for exiting locked mode. + +Extras +---------------- +* Holding the button for longer than 350 ms will cause the light to blink at about 15 Hz while the button is held unless you are pointing at the ground. + +* While pointing at the ground, holding the light for 3 seconds or more will cause the button to glow green even when the button is released. diff --git a/programs/set_and_remember/set_and_remember.ino b/programs/set_and_remember/set_and_remember.ino new file mode 100644 index 0000000..a8cdb2e --- /dev/null +++ b/programs/set_and_remember/set_and_remember.ino @@ -0,0 +1,470 @@ +/* +Copyright (c) 2013, "Whitney Battestilli" + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* +Changes and modifications are Copyright (c) 2014, "Matthew Sargent" + + This code is a direct modification of the code up_n_down as copyrighted above. + + Description: + + 1 click: Turn light on (from off) at the saved level. + 2 clicks: Blink + 3 clicks: Nightlight mode, movement turns on the main light and turns off the green tailcap. + Sitting still does the opposite. + 4 clicks: SOS using the stored brightness from Mode 1. + 5 clicks: lock/unlock, flashes the switch LEDs (Red or Green) 3 times; Red when entering locked, + green when exiting it. + + Other Features: + Click and Hold (while ON): + Set level; point down = lowest, point horizon = highest, save this level. + In Nightlight Mode; point down lowest, point horizon highest, save this as nightlight level. + + Click and Hold (while OFF) + If pointing straight down after 3 seconds: Enter glow mode - the tailcap glows when the light is off. + If not pointing straight down: Start Flashing + + */ + +#include +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK +#include +#include + +#if (DEBUG==DEBUG_PROGRAM) +#define DBG(a) a +#else +#define DBG(a) +#endif + +#define EEPROM_LOCKED 0 +#define EEPROM_NIGHTLIGHT_BRIGHTNESS 1 +#define EEPROM_STORED_BRIGHTNESS 2 +#define EEPROM_SIGNATURE_LOC 3 + +// Modes +#define MODE_OFF 0 +#define MODE_LEVEL 1 +#define MODE_BLINK 2 +#define MODE_NIGHTLIGHT 3 +#define MODE_SOS 4 +#define MODE_LOCKED 5 + +// Constants +static const int glow_mode_time = 3000; +static const int click = 350; // maximum time for a "short" click +static const int nightlight_timeout = 5000; // timeout before nightlight powers down after any movement +static const unsigned char nightlight_sensitivity = 20; // measured in 100's of a G. +static const byte sos_pattern[] = { + 0xA8, 0xEE, 0xE2, 0xA0}; +static const int singleMorseBeat = 150; +static const word blink_freq_map[] = { 70, 650, 10000}; // in ms +static const unsigned GLOW_MODE=0; +static const unsigned GLOW_MODE_JUST_CHANGED=1; +static const unsigned QUICKSTROBE=2; + +// EEPROM signature used to verify EEPROM has been initialize +static const char* EEPROM_Signature = "Set_and_Remember"; + +// State +static unsigned long treg1=0; + +// Submode Storage +static int bitreg=0; + +static char mode = MODE_OFF; +static char new_mode = MODE_OFF; +static unsigned tailflashesLeft = 0; +static unsigned shutdowndelay = 250; //prevent an immediate shutdown when the unit starts up... + +static word nightlight_brightness; +static word stored_brightness; + +static word blink_frequency; // in ms; + +static byte locked = false; +static int sos_cursor = 0; +unsigned long time; + +hexbright hb; + +int adjustLED() { + if(hb.button_pressed() && hb.button_pressed_time()>click) { + double d = hb.difference_from_down(); // (0,1) + int di = (int)(d*400.0); // (0,400) + int i = (di)/10; // (0,40) + i *= 50; // (0,2000) + i = i>1000 ? 1000 : i; + i = i<=0 ? 1 : i; + + hb.set_light(CURRENT_LEVEL, i, 100); + return i; + } + return -1; +} + +//Write the bytes contained in the parameter array to the location in +//EEPROM starting at startAddr. Write numBytes number of bytes. +void writebytesEEPROM(int startAddr, const byte* array, int numBytes) { + int i; + for (i = 0; i < numBytes; i++) { + updateEEPROM(startAddr+i,array[i]); + } +} + +//Update the EEPROM, but check to make sure we actually need to update it first. +//This saves writing to the EEPROM when it is not necessary (important). +byte updateEEPROM(word location, byte value) { + byte c = EEPROM.read(location); + if(c!=value) { + DBG(Serial.print("Write to EEPROM:"); Serial.print(value); Serial.print(" to loc: "); Serial.println(location)); + EEPROM.write(location, value); + } + return value; +} + +//Check the EEPROM to see if the signature has been written, if found, return true +boolean checkEEPROMInited(word location) { + //read starting at location and look for the signature. + int i = 0; + for (i = 0; i < strlen(EEPROM_Signature); i++) { + if (EEPROM_Signature[i] != EEPROM.read(location+i)) + return false; //EEPROM does not match Signature, so EEPROM not initialized + } + return true; //The signature was found, so the EEPROM is initialized... +} + +void setup() { + + // We just powered on! That means either we got plugged + // into USB, or the user is pressing the power button. + hb = hexbright(); + hb.init_hardware(); + + //The following code detects if the EEPROM has been initialized + //If it has not, initialize it. + if(!checkEEPROMInited(EEPROM_SIGNATURE_LOC)) { + + updateEEPROM(EEPROM_LOCKED,0); + updateEEPROM(EEPROM_NIGHTLIGHT_BRIGHTNESS, 100); + updateEEPROM(EEPROM_STORED_BRIGHTNESS, 150); + + //now store the signature so we know the EEPROM has been initialized + int length = strlen(EEPROM_Signature) + 1; //this will cause the nul to be written too. + writebytesEEPROM(EEPROM_SIGNATURE_LOC,(const byte*)EEPROM_Signature, length); + } + + //Initialize variables using the values stored in the EEPROM + //DBG(Serial.println("Reading defaults from EEPROM")); + locked = EEPROM.read(EEPROM_LOCKED); + //DBG(Serial.print("Locked: "); Serial.println(locked)); + nightlight_brightness = EEPROM.read(EEPROM_NIGHTLIGHT_BRIGHTNESS)*4; + nightlight_brightness = nightlight_brightness==0 ? 1000 : nightlight_brightness; + //DBG(Serial.print("Nightlight Brightness: "); Serial.println(nightlight_brightness)); + + //Fetch the stored brightness from EEPROM and use it when turning on. + stored_brightness = EEPROM.read(EEPROM_STORED_BRIGHTNESS)*4; + stored_brightness = stored_brightness==0 ? 1000 : stored_brightness; //If zero, default to 1000 + //DBG(Serial.print("Stored Brightness: "); Serial.println(stored_brightness)); + + DBG(Serial.println("Powered up!")); + + config_click_count(click); +} + +void loop() { + time = millis(); + + hb.update(); + +#ifdef PRINTING_NUMBER: + if(!hb.printing_number()) +#endif + { + // Charging + print_charge(GLED); + + // Low battery + if(mode != MODE_OFF && hb.low_voltage_state()) + if(hb.get_led_state(RLED)==LED_OFF) + hb.set_led(RLED,50,1000,1); + } + + + ///////////////////////////////////////////////////////////////// + // Check for the requested new mode and do the mode switching activities + ///////////////////////////////////////////////////////////////// + // Get the click count + new_mode = click_count(); + + //if the new mode is good... + if(new_mode>=MODE_OFF) { + DBG(Serial.print("New mode: "); + Serial.println((int)new_mode)); + + //If the light is on now, any new mode request is a MODE_OFF. + //i.e. while the light is on, you can not switch it into another mode, it just goes off. + if(mode!=MODE_OFF) { + DBG(Serial.println("Forcing MODE_OFF")); + new_mode=MODE_OFF; + + //If the light is currently off, the following keeps the light off and locked, + //unless the locked mode was received. + //Flash the tailcap 2 times to indicate the light is locked. + } else if (locked && new_mode!=MODE_LOCKED){ + new_mode=MODE_OFF; + DBG(Serial.println("Locked, flash tailcap 2 times.")); + tailflashesLeft = 2; + } + } + + //Process if this is really a mode change. + if(new_mode>=MODE_OFF && new_mode!=mode) { + double d; + + //The user might have switched to a new mode while the tail was flashing so + //it may not yet be zeroed so clear them out + if (new_mode != MODE_OFF) + tailflashesLeft = 0; + + // Do the preliminary work when switching to a new mode first + switch(new_mode) { + + case MODE_LOCKED: + locked=!locked;//toggle the locked mode + updateEEPROM(EEPROM_LOCKED,locked); //remember if we are locked or not. + new_mode=MODE_OFF; //keep the light off when entering or exiting lock mode. + //Setup the tail light to flash... + tailflashesLeft = 3; + break; + + case MODE_OFF: + //Turn the light off right away, if a shutdown is needed it will be done inthe mode maint. + hb.set_light(CURRENT_LEVEL, 0, NOW); + break; + + case MODE_LEVEL: + //Just turn on the light to the saved level + hb.set_light(CURRENT_LEVEL, stored_brightness, NOW); + break; + + case MODE_NIGHTLIGHT: + DBG(Serial.print("Nightlight Brightness: "); Serial.println(nightlight_brightness)); +#ifdef PRINTING_NUMBER: + if(!hb.printing_number()) +#endif + hb.set_led(GLED, 100, 0, 64); + break; + + case MODE_SOS: + //Entering SOS mode, so set the cursor at the beginning of the pattern + sos_cursor = 0; + + //Give a light change command with time of NOW so it finishes right away + //making the SOS pattern start without much delay. + hb.set_light(0, 0, NOW); + break; + + case MODE_BLINK: + d = hb.difference_from_down(); + blink_frequency = blink_freq_map[0]; + if(d <= 0.40) { + if(d <= 0.10) + blink_frequency = blink_freq_map[2]; + else + blink_frequency = blink_freq_map[1]; + } + hb.set_light(MAX_LEVEL, 0, 20); + break; + } + mode=new_mode; + } + + ///////////////////////////////////////////////////////////////// + // Done with new mode activities. Now do the + // stuff that we do while in a mode. + ///////////////////////////////////////////////////////////////// + int i; + + switch(mode) { + + case MODE_OFF: + if (tailflashesLeft > 0) { //flashing tail + hb.set_light(CURRENT_LEVEL, 0, NOW); + if (hb.get_led_state(locked?RLED:GLED)==LED_OFF) { + tailflashesLeft--; + hb.set_led(locked?RLED:GLED, 300, 300, 255); + DBG(Serial.print("Tail Flashed"); Serial.println()); + } + } + else if(BIT_CHECK(bitreg,GLOW_MODE)) { //glow mode + hb.set_led(GLED, 100, 100, 64); + hb.set_light(CURRENT_LEVEL, 0, NOW); + DBG(Serial.print("Glow Mode - light off, processor on"); Serial.println()); + } + else if (shutdowndelay > 0) { //This is the delay that lets the device run for a little while... + shutdowndelay--; + } + else { //Do the shutdown now... + DBG(Serial.print("Nightlight Brightness Saved: "); Serial.println(nightlight_brightness)); + updateEEPROM(EEPROM_NIGHTLIGHT_BRIGHTNESS, nightlight_brightness/4); + + //Save the stored_brightness in the EEPROM so we can use it next time + DBG(Serial.print("Brightness Saved: "); + Serial.println(stored_brightness)); + updateEEPROM(EEPROM_STORED_BRIGHTNESS, stored_brightness/4); + + DBG(Serial.print("Light off, processor off"); Serial.println()); + hb.set_light(CURRENT_LEVEL, OFF_LEVEL, NOW); + } + + // holding the button + if(hb.button_pressed() && !locked) { + double d = hb.difference_from_down(); + if(BIT_CHECK(bitreg,QUICKSTROBE) + || (hb.button_pressed_time() > click + && d > 0.10 )) { + BIT_SET(bitreg,QUICKSTROBE); + if(treg1+blink_freq_map[0] < time) { + treg1 = time; + hb.set_light(MAX_LEVEL, 0, 20); + } + } + if(hb.button_pressed_time() >= glow_mode_time + &&d <= 0.1 + && !BIT_CHECK(bitreg,GLOW_MODE_JUST_CHANGED) + && !BIT_CHECK(bitreg,QUICKSTROBE) ) { + BIT_TOGGLE(bitreg,GLOW_MODE); + BIT_SET(bitreg,GLOW_MODE_JUST_CHANGED); + } + } + if(hb.button_just_released()) { + BIT_CLEAR(bitreg,GLOW_MODE_JUST_CHANGED); + BIT_CLEAR(bitreg,QUICKSTROBE); + } + + break; + + + case MODE_LEVEL: + i = adjustLED(); //Adjust the led and save it + if(i>0) { + DBG(Serial.print("New Stored Brightness: "); Serial.println(i)); + stored_brightness = i; + } + break; + + case MODE_NIGHTLIGHT: + if(hb.moved(nightlight_sensitivity)) { + DBG(Serial.println("Nightlight Moved")); + treg1 = time; + hb.set_light(CURRENT_LEVEL, nightlight_brightness, 1000); + hb.set_led(GLED, 0, 0, 0); //turn off the LED while the light is on... + } + else if(time > treg1 + nightlight_timeout) { + hb.set_light(CURRENT_LEVEL, 0, 1000); + hb.set_led(GLED, 1000, 0, 64); + } + + i = adjustLED(); + if(i>0) { + DBG(Serial.print("Nightlight Brightness: "); Serial.println(i)); + nightlight_brightness = i; + } + break; + + case MODE_BLINK: + if(hb.button_pressed()) { + if( hb.button_pressed_time()> click) { + double d = hb.difference_from_down(); + if(d>=0 && d<=0.99) { + if(d>=0.25) { + d = d>0.5 ? 0.5 : d; + blink_frequency = blink_freq_map[0] + (word)((blink_freq_map[1] - blink_freq_map[0]) * 4 * (0.5-d)); + } + else { + blink_frequency = blink_freq_map[1] + (word)((blink_freq_map[2] - blink_freq_map[1]) * 4 * (0.25-d)); + } + DBG(Serial.print("Blink Freq: "); Serial.println(blink_frequency)); + } + } + } + if(treg1+blink_frequency < time) { + treg1 = time; + hb.set_light(MAX_LEVEL, 0, 20); + } + break; + + case MODE_SOS: + //Send the international distress signal. + //... --- ... ... --- ... + //Dit 150 = 1 beat + //Dah 450 = 3 beats + //Inter dit/dah = 1 beat + //Inter character = 3 beats = + //Inter word = 7 beats = + + //From left to right, the pattern is encoded with bits, each bit being + //a singleMorseBeat long (except the last one, as in: + //<-S-> <----O----> <-S-> <-inter word-> + //10101 000 11101110111 000 10101 0xxx + // + //10101000 11101110 11100010 1010xxxx + // 0xA8 0xEE 0xE2 0xA0 + + //At the end of each completed light change, move to the next bit. + //In this case the light change will be from ON to ON or from Off to Off, so + //that is how we get our flashing. This should happen every singleMorseBeat, except + //on the last bit where we cause the delay to be longer (x7) to mark the between word space + if(hb.light_change_remaining()==0){ + int whichByte = sos_cursor/8; //0/8 = 0 1/8=0... + int whichBit = 7 - sos_cursor%8; //7 - 0%8 = 7; 7-30%8 = 6 + int onOffAmount = BIT_CHECK(sos_pattern[whichByte],whichBit)?stored_brightness:0; + + //if this is the last bit, then make the delay an interword amount (x7) + int onOrOffTime = sos_cursor<27?singleMorseBeat:singleMorseBeat*7; + + hb.set_light(onOffAmount,onOffAmount, onOrOffTime); + + DBG(Serial.print("SOS Data; byte#/bit#: "); Serial.print(whichByte); Serial.print("/"); Serial.println(whichBit)); + DBG(Serial.print("SOS Data; onOffAmount#: "); Serial.println(onOffAmount)); + DBG(Serial.print("SOS Data; onOrOffTime: "); Serial.println(onOrOffTime)); + sos_cursor++; + + if (sos_cursor>27 || sos_cursor<0) { + sos_cursor = 0; + } + } + break; + } +} + + + + diff --git a/programs/spin_level/README.md b/programs/spin_level/README.md new file mode 100644 index 0000000..00c11b4 --- /dev/null +++ b/programs/spin_level/README.md @@ -0,0 +1,8 @@ +Button press toggles on or off (30 ms minimum to avoid hyper-sensitivity). + +Using as a rotary dial slowly turns up or down brightness. + +Spinning in circles rapidly turns up or down brightness. + + +Video of use: www.youtube.com/watch?v=SV6-1KUU9g8 diff --git a/hb-examples/spin_level/spin_level.ino b/programs/spin_level/spin_level.ino old mode 100644 new mode 100755 similarity index 72% rename from hb-examples/spin_level/spin_level.ino rename to programs/spin_level/spin_level.ino index 4ec0123..b8d0988 --- a/hb-examples/spin_level/spin_level.ino +++ b/programs/spin_level/spin_level.ino @@ -27,9 +27,11 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ -#include +#include -#include +// These next two lines must come after all other library #includes +#define BUILD_HACK +#include hexbright hb; @@ -41,21 +43,33 @@ void setup() { #define SPIN_LEVEL_MODE 1 int mode = OFF_MODE; -static int brightness_level = 0; +int brightness_level = 0; + +int press_time = 0; +int press_used = false; + void loop() { hb.update(); - if(hb.button_just_released() && hb.button_pressed_time()<300) { - brightness_level = 1; - hb.set_light(CURRENT_LEVEL, brightness_level, NOW); - mode = SPIN_LEVEL_MODE; - } else if (hb.button_pressed_time()>300) { - hb.set_light(CURRENT_LEVEL, OFF_LEVEL, NOW); - mode = OFF_MODE; - } + if(!hb.button_pressed()) + press_used = false; + + if(!press_used && hb.button_pressed() && hb.button_pressed_time()>30) { + if(mode==OFF_MODE) { + brightness_level = 1; + hb.set_light(CURRENT_LEVEL, brightness_level, NOW); + mode = SPIN_LEVEL_MODE; + press_used = true; + } else if (mode==SPIN_LEVEL_MODE) { + hb.set_light(CURRENT_LEVEL, OFF_LEVEL, NOW); + mode = OFF_MODE; + press_used = true; + } + } + if(mode==SPIN_LEVEL_MODE) { - if(abs(hb.difference_from_down()-.5)<.35) { // not pointing up or down + if(abs(hb.difference_from_down()-.5)<.35) { // acceleration is not along the light axis, where noise causes random fluctuations. char spin = hb.get_spin(); brightness_level = brightness_level + spin; brightness_level = brightness_level>1000 ? 1000 : brightness_level; @@ -63,5 +77,5 @@ void loop() { hb.set_light(CURRENT_LEVEL, brightness_level, 100); } } - hb.print_power(); -} + print_power(); +} diff --git a/programs/tactical/tactical.ino b/programs/tactical/tactical.ino index bc95260..3b1aa88 100755 --- a/programs/tactical/tactical.ino +++ b/programs/tactical/tactical.ino @@ -27,8 +27,11 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include hexbright hb; @@ -74,6 +77,6 @@ void loop() { // to max brightness on low to off. } } - hb.print_power(); + print_power(); } - + diff --git a/programs/temperature_calibration/temperature_calibration.ino b/programs/temperature_calibration/temperature_calibration.ino index d509a50..ff7da58 100755 --- a/programs/temperature_calibration/temperature_calibration.ino +++ b/programs/temperature_calibration/temperature_calibration.ino @@ -27,8 +27,11 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include // Usage notes are in the readme file in this same directory. hexbright hb; @@ -48,9 +51,9 @@ void loop() { hb.set_light(CURRENT_LEVEL, brightness, 50); } - if(!hb.printing_number()) { - hb.print_number(hb.get_thermal_sensor()); + if(!printing_number()) { + print_number(hb.get_thermal_sensor()); // hb.print_number(hb.get_fahrenheit()); } } - + diff --git a/programs/up_n_down/README.md b/programs/up_n_down/README.md index 833fc6f..8e285ab 100644 --- a/programs/up_n_down/README.md +++ b/programs/up_n_down/README.md @@ -33,7 +33,7 @@ Basic Operation * Light blinks SOS repeatededly in morse code * 5 Clicks-Lock mode - * When locked, the light will not turn on. 5 clicks when locked will unlock the light. + * When locked, the light will not turn on. Any clicks while locked will cause the tail cap to flash red 2 times. Clicking 5 time when locked will unlock the light. After locking or unlocking the tail cap leds will flash 3 times as confirmation. Red for entering locked mode and green for exiting locked mode. Extras ---------------- diff --git a/programs/up_n_down/up_n_down.ino b/programs/up_n_down/up_n_down.ino old mode 100644 new mode 100755 index 5f9906d..7c2ebf6 --- a/programs/up_n_down/up_n_down.ino +++ b/programs/up_n_down/up_n_down.ino @@ -1,5 +1,6 @@ /* -Copyright (c) 2013, "Whitney Battestilli" +Copyright (c) 2014, "Whitney Battestilli" +Portions Copyright (c) 2014, "Matthew Sargent" All rights reserved. Redistribution and use in source and binary forms, with or without @@ -23,8 +24,12 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include #include #if (DEBUG==DEBUG_PROGRAM) @@ -35,6 +40,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define EEPROM_LOCKED 0 #define EEPROM_NIGHTLIGHT_BRIGHTNESS 1 +#define EEPROM_SIGNATURE_LOC 2 // through 12 + +// EEPROM signature used to verify EEPROM has been initialize +static const char* EEPROM_Signature = "UP_N_DOWN"; // Modes #define MODE_OFF 0 @@ -47,21 +56,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Defaults static const int glow_mode_time = 3000; static const int click = 350; // maximum time for a "short" click -static const int nightlight_timeout = 10000; // timeout before nightlight powers down after any movement +static const int nightlight_timeout = 5000; // timeout before nightlight powers down after any movement static const unsigned char nightlight_sensitivity = 20; // measured in 100's of a G. +static const byte sos_pattern[] = {0xA8, 0xEE, 0xE2, 0xA0}; +static const int singleMorseBeat = 150; // State static unsigned long treg1=0; static int bitreg=0; -const unsigned BLOCK_TURNING_OFF=0; -const unsigned GLOW_MODE=1; -const unsigned GLOW_MODE_JUST_CHANGED=2; -const unsigned QUICKSTROBE=3; +const unsigned GLOW_MODE=0; +const unsigned GLOW_MODE_JUST_CHANGED=1; +const unsigned QUICKSTROBE=2; static char mode = MODE_OFF; static char new_mode = MODE_OFF; -static char submode; +static unsigned tailflashesLeft = 0; static word nightlight_brightness; @@ -69,6 +79,8 @@ const word blink_freq_map[] = {70, 650, 10000}; // in ms static word blink_frequency; // in ms; static byte locked; +static int sos_cursor = 0; +unsigned long time; hexbright hb; int adjustLED() { @@ -81,14 +93,21 @@ int adjustLED() { i = i<=0 ? 1 : i; hb.set_light(CURRENT_LEVEL, i, 100); - BIT_SET(bitreg,BLOCK_TURNING_OFF); - return i; } return -1; } +//Write the bytes contained in the parameter array to the location in +//EEPROM starting at startAddr. Write numBytes number of bytes. +void writebytesEEPROM(int startAddr, const byte* array, int numBytes) { + int i; + for (i = 0; i < numBytes; i++) { + updateEEPROM(startAddr+i,array[i]); + } +} + byte updateEEPROM(word location, byte value) { byte c = EEPROM.read(location); if(c!=value) { @@ -98,12 +117,34 @@ byte updateEEPROM(word location, byte value) { return value; } +//Check the EEPROM to see if the signature has been written, if found, return true +boolean checkEEPROMInited(word location) { + //read starting at location and look for the signature. + int i = 0; + for (i = 0; i < strlen(EEPROM_Signature); i++) { + if (EEPROM_Signature[i] != EEPROM.read(location+i)) + return false; //EEPROM does not match Signature, so EEPROM not initialized + } + return true; //The signature was found, so the EEPROM is initialized... +} + void setup() { // We just powered on! That means either we got plugged // into USB, or the user is pressing the power button. hb = hexbright(); hb.init_hardware(); + //The following code detects if the EEPROM has been initialized + //If it has not, initialize it. + if(!checkEEPROMInited(EEPROM_SIGNATURE_LOC)) { + + updateEEPROM(EEPROM_LOCKED,0); + updateEEPROM(EEPROM_NIGHTLIGHT_BRIGHTNESS, 100); + + //now store the signature so we know the EEPROM has been initialized + int length = strlen(EEPROM_Signature) + 1; //this will cause the nul to be written too. + writebytesEEPROM(EEPROM_SIGNATURE_LOC,(const byte*)EEPROM_Signature, length); + } //DBG(Serial.println("Reading defaults from EEPROM")); locked = EEPROM.read(EEPROM_LOCKED); @@ -114,11 +155,11 @@ void setup() { DBG(Serial.println("Powered up!")); - hb.config_click_count(click); + config_click_count(click); } void loop() { - const unsigned long time = millis(); + time = millis(); hb.update(); @@ -127,7 +168,7 @@ void loop() { #endif { // Charging - hb.print_charge(GLED); + print_charge(GLED); // Low battery if(mode != MODE_OFF && hb.low_voltage_state()) @@ -136,30 +177,51 @@ void loop() { } // Get the click count - new_mode = hb.click_count(); - if(new_mode>=MODE_OFF) { - DBG(Serial.print("New mode: "); Serial.println((int)new_mode)); - if(mode!=MODE_OFF || (locked && new_mode!=MODE_LOCKED) ) { + new_mode = click_count(); + + if(new_mode>=MODE_OFF) { + DBG(Serial.print("New mode: "); + Serial.println((int)new_mode)); + + //If the light is on now, any new mode request is converted to a MODE_OFF. + //While the light is on, you can not switch it into another mode, it just goes off. + if(mode!=MODE_OFF) { DBG(Serial.println("Forcing MODE_OFF")); new_mode=MODE_OFF; - } + + //The following keeps the light locked and off until the light is unlocked. + //Flash the tailcap 2 times to indicate the light is locked. + } else if (locked && new_mode!=MODE_LOCKED){ + new_mode=MODE_OFF; + DBG(Serial.println("Locked, flash tailcap 2 times.")); + tailflashesLeft = 2; + } } // Do the actual mode change if(new_mode>=MODE_OFF && new_mode!=mode) { double d; int i; + //Clear tailflashesLeft if mode is not OFF. + //If the user switched to a new mode while the tail was flashing, it may not yet be zeroed + if (new_mode != MODE_OFF) + tailflashesLeft = 0; + // we're changing mode switch(new_mode) { case MODE_LOCKED: locked=!locked; updateEEPROM(EEPROM_LOCKED,locked); new_mode=MODE_OFF; + //Setup the tail light to flash... + tailflashesLeft = 3; /* fall through */ case MODE_OFF: - if(BIT_CHECK(bitreg,GLOW_MODE)) { - hb.set_light(CURRENT_LEVEL, 0, NOW); - DBG(Serial.println("Stay alive")); + //While in Glow mode or while flashing the tail, do not shutdown the uProcessor, + //this is done by using a level of 0 instead of the OFF_LEVEL + DBG(BIT_CHECK(bitreg,GLOW_MODE)||tailflashesLeft>0?Serial.println("Stay alive"):Serial.println("Shut down")); + if (BIT_CHECK(bitreg,GLOW_MODE)||tailflashesLeft>0) { + hb.set_light(CURRENT_LEVEL, 0, NOW); } else { hb.set_light(CURRENT_LEVEL, OFF_LEVEL, NOW); DBG(Serial.println("Shut down")); @@ -188,6 +250,14 @@ void loop() { #endif hb.set_led(RLED, 100, 0, 1); break; + case MODE_SOS: + //Entering SOS mode, so set the cursor at the beginning of the pattern + sos_cursor = 0; + + //Give a light change command with time of NOW so it finishes right away + //making the SOS pattern start without much delay. + hb.set_light(0, 0, NOW); + break; case MODE_BLINK: d = hb.difference_from_down(); blink_frequency = blink_freq_map[0]; @@ -206,8 +276,14 @@ void loop() { // Check for mode and do in-mode activities switch(mode) { case MODE_OFF: + if (tailflashesLeft > 0) { //flashing tail + if (hb.get_led_state(locked?RLED:GLED)==LED_OFF) { + tailflashesLeft--; + hb.set_led(locked?RLED:GLED, 300, 300, 255); + } + } // glow mode - if(BIT_CHECK(bitreg,GLOW_MODE)) { + else if(BIT_CHECK(bitreg,GLOW_MODE)) { hb.set_led(GLED, 100, 100, 64); hb.set_light(CURRENT_LEVEL, 0, NOW); } else if(BIT_CHECK(bitreg,GLOW_MODE_JUST_CHANGED)) { @@ -267,7 +343,6 @@ void loop() { blink_frequency = blink_freq_map[1] + (word)((blink_freq_map[2] - blink_freq_map[1]) * 4 * (0.25-d)); } //DBG(Serial.print("Blink Freq: "); Serial.println(blink_frequency)); - BIT_SET(bitreg,BLOCK_TURNING_OFF); } } } @@ -277,65 +352,47 @@ void loop() { } break; case MODE_SOS: - if(!hb.light_change_remaining()) - { // we're not currently doing anything, start the next step - static int current_character = 0; - static char symbols_remaining = 0; - static byte pattern = 0; - const char message[] = "SOS"; - const word morse[] = { 0x0300, // S ... - 0x0307, // O --- - 0x0300, // S ... - }; - const int millisPerBeat = 150; + //Send the international distress signal. + //... --- ... ... --- ... + //Dit 150 = 1 beat + //Dah 450 = 3 beats + //Inter dit/dah = 1 beat + //Inter character = 3 beats = + //Inter word = 7 beats = + + //From left to right, the pattern is encoded with bits, each bit being + //a singleMorseBeat long (except the last one, as in: + //<-S-> <----O----> <-S-> <-inter word-> + //10101 000 11101110111 000 10101 0xxx + // + //10101000 11101110 11100010 1010xxxx + // 0xA8 0xEE 0xE2 0xA0 + + //At the end of each completed light change, move to the next bit. + //In this case the light change will be from ON to ON or from Off to Off, so + //that is how we get our flashing. This should happen every singleMorseBeat, except + //on the last bit where we cause the delay to be longer (x7) to mark the between word space + if(hb.light_change_remaining()==0){ + int whichByte = sos_cursor/8; //0/8 = 0 1/8=0... + int whichBit = 7 - sos_cursor%8; //7 - 0%8 = 7; 7-30%8 = 6 + int onOffAmount = BIT_CHECK(sos_pattern[whichByte],whichBit)?1000:0; + + //if this is the last bit, then make the delay an interword amount (x7) + int onOrOffTime = sos_cursor<27?singleMorseBeat:singleMorseBeat*7; - if(current_character>=sizeof(message)) - { // we've hit the end of message, turn off. - //mode = MODE_OFF; - // reset the current_character, so if we're connected to USB, next printing will still work. - current_character = 0; - // return now to skip the following code. - break; - } - - if(symbols_remaining <= 0) // we're done printing our last character, get the next! - { - // Extract the symbols and length - pattern = morse[current_character] & 0x00FF; - symbols_remaining = morse[current_character] >> 8; - // we count space (between dots/dashes) as a symbol to be printed; - symbols_remaining *= 2; - current_character++; - } + hb.set_light(onOffAmount,onOffAmount, onOrOffTime); - if (symbols_remaining<=0) - { // character was unrecognized, treat it as a space - // 7 beats between words, but 3 have already passed - // at the end of the last character - hb.set_light(0,0, millisPerBeat * 4); - } - else if (symbols_remaining==1) - { // last symbol in character, long pause - hb.set_light(0, 0, millisPerBeat * 3); - } - else if(symbols_remaining%2==1) - { // even symbol, print space! - hb.set_light(0,0, millisPerBeat); - } - else if (pattern & 1) - { // dash, 3 beats - hb.set_light(MAX_LEVEL, MAX_LEVEL, millisPerBeat * 3); - pattern >>= 1; - } - else - { // dot, by elimination - hb.set_light(MAX_LEVEL, MAX_LEVEL, millisPerBeat); - pattern >>= 1; + DBG(Serial.print("SOS Data; byte#/bit#: "); Serial.print(whichByte); Serial.print("/"); Serial.println(whichBit)); + DBG(Serial.print("SOS Data; onOffAmount#: "); Serial.println(onOffAmount)); + DBG(Serial.print("SOS Data; onOrOffTime: "); Serial.println(onOrOffTime)); + sos_cursor++; + + if (sos_cursor>27 || sos_cursor<0) { + sos_cursor = 0; } - symbols_remaining--; } break; } } - + diff --git a/programs/wand/wand.ino b/programs/wand/wand.ino old mode 100644 new mode 100755 index 1ec697e..f91f56f --- a/programs/wand/wand.ino +++ b/programs/wand/wand.ino @@ -1,6 +1,7 @@ -#include +#include -// uncomment #ACCELEROMETER in hexbright.h +// These next two lines must come after all other library #includes +#define BUILD_HACK #include hexbright hb; @@ -48,6 +49,6 @@ void loop() { } last_dp = dp; } else if (mode==OFF_MODE) { - hb.print_power(); + print_power(); } -} +} diff --git a/tests/accelerometer_record/accelerometer_record.ino b/tests/accelerometer_record/accelerometer_record.ino index 31cc1d0..339c10e 100755 --- a/tests/accelerometer_record/accelerometer_record.ino +++ b/tests/accelerometer_record/accelerometer_record.ino @@ -1,5 +1,6 @@ +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include #include hexbright hb; @@ -106,4 +107,4 @@ void read_vector(int * vector) { vector[i] = tmp*(100/21.3); } } -} +} diff --git a/tests/accelerometer_test/accelerometer_test.ino b/tests/accelerometer_test/accelerometer_test.ino index 327f89e..7753e6a 100755 --- a/tests/accelerometer_test/accelerometer_test.ino +++ b/tests/accelerometer_test/accelerometer_test.ino @@ -1,5 +1,7 @@ -#include -// uncomment '#define ACCELEROMETER' in hexbright.h +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include @@ -52,23 +54,7 @@ void loop() { -void print_binary(int value) { - String s = String(value, BIN); - - while(s.length()<16) { - s = "0"+s; - } - Serial.println(s); -} - -void print_binary(byte value) { - String s = String(value, BIN); - while(s.length()<8) { - s = "0"+s; - } - Serial.println(s); -} /*double sqrt_sqr(int vector) { @@ -83,4 +69,4 @@ double something() { return dot_product()/(sqrt_sqr(0)*sqrt_sqr(1)); }*/ - + diff --git a/tests/avr_voltage_test/avr_voltage_test.ino b/tests/avr_voltage_test/avr_voltage_test.ino old mode 100644 new mode 100755 index b2db840..0369f2b --- a/tests/avr_voltage_test/avr_voltage_test.ino +++ b/tests/avr_voltage_test/avr_voltage_test.ino @@ -27,9 +27,12 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ -#include +#include +#include -#include +// These next two lines must come after all other library #includes +#define BUILD_HACK +#include // number of milliseconds between updates #define OFF_MODE 0 @@ -78,12 +81,12 @@ void loop() { } i--; } else if (mode == CYCLE_MODE) { // print the current avr voltage - if(!hb.printing_number()) { - hb.print_number(hb.get_avr_voltage()); + if(!printing_number()) { + print_number(hb.get_avr_voltage()); } } else if (mode == OFF_MODE) { // charging, or turning off - if(!hb.printing_number()) { - hb.print_charge(GLED); + if(!printing_number()) { + print_charge(GLED); } } -} +} diff --git a/tests/led_test/led_test.ino b/tests/led_test/led_test.ino index 9a7d712..a169ed4 100755 --- a/tests/led_test/led_test.ino +++ b/tests/led_test/led_test.ino @@ -1,5 +1,5 @@ -#include - +// These next two lines must come after all other library #includes +#define BUILD_HACK #include hexbright hb; @@ -35,4 +35,4 @@ void switch_test() { hb.set_led(RLED, 250, 250); } } -} +} diff --git a/tests/light_test/light_test.ino b/tests/light_test/light_test.ino old mode 100755 new mode 100644 index badc0ba..12dd975 --- a/tests/light_test/light_test.ino +++ b/tests/light_test/light_test.ino @@ -1,5 +1,12 @@ +#include // include DEBUG definitions + +// alter the default behavior of the hexbright library +#define SET_LIGHT_LEVEL_SIMPLE +#define DEBUG DEBUG_LIGHT + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include hexbright hb; @@ -24,3 +31,4 @@ void loop() { } } } + diff --git a/tests/linearity_test/linearity_test.ino b/tests/linearity_test/linearity_test.ino old mode 100644 new mode 100755 index 979b414..484701d --- a/tests/linearity_test/linearity_test.ino +++ b/tests/linearity_test/linearity_test.ino @@ -30,8 +30,6 @@ either expressed or implied, of the FreeBSD Project. #include -#include - // Pin assignments #define DPIN_RLED_SW 2 // both red led and switch. pinMode OUTPUT = led, pinMode INPUT = switch @@ -183,4 +181,4 @@ void loop() { last_time = time; } } - + diff --git a/tests/print_number_test/print_number_test.ino b/tests/print_number_test/print_number_test.ino old mode 100644 new mode 100755 index 4748cba..2937900 --- a/tests/print_number_test/print_number_test.ino +++ b/tests/print_number_test/print_number_test.ino @@ -27,8 +27,11 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include + +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include // Usage notes are in the readme file in this same directory. hexbright hb; @@ -44,9 +47,9 @@ int number = 0; void loop() { hb.update(); - if(!hb.printing_number()) { - hb.print_number(numbers[number]); + if(!printing_number()) { + print_number(numbers[number]); number = (number+1)%COUNT; } } - + diff --git a/tests/stroboscope_test/stroboscope_test.ino b/tests/stroboscope_test/stroboscope_test.ino index 670485e..0da1195 100755 --- a/tests/stroboscope_test/stroboscope_test.ino +++ b/tests/stroboscope_test/stroboscope_test.ino @@ -1,5 +1,6 @@ +// These next two lines must come after all other library #includes +#define BUILD_HACK #include -#include hexbright hb; @@ -134,4 +135,4 @@ void setPwmFrequency(int pin, int divisor) { } TCCR2B = TCCR2B & 0b11111000 | mode; } -} +}