From 24d3284058ab0472ab43985b2f4118e5cccbb304 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Fri, 20 Sep 2013 18:30:57 -0600 Subject: [PATCH 01/41] initial framework branch, moving click_counter to its own file. --- libraries/hexbright/click_counter.cpp | 49 + libraries/hexbright/click_counter.h | 20 + libraries/hexbright/hexbright.cpp | 1288 ------------------------- libraries/hexbright/hexbright.h | 1232 ++++++++++++++++++++++- 4 files changed, 1294 insertions(+), 1295 deletions(-) create mode 100644 libraries/hexbright/click_counter.cpp create mode 100644 libraries/hexbright/click_counter.h delete mode 100755 libraries/hexbright/hexbright.cpp diff --git a/libraries/hexbright/click_counter.cpp b/libraries/hexbright/click_counter.cpp new file mode 100644 index 0000000..654c16a --- /dev/null +++ b/libraries/hexbright/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/hexbright/click_counter.h b/libraries/hexbright/click_counter.h new file mode 100644 index 0000000..41f8201 --- /dev/null +++ b/libraries/hexbright/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/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..f701cc1 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -27,6 +27,9 @@ 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" @@ -254,13 +257,6 @@ 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. @@ -498,3 +494,1225 @@ 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 + +#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 + } +} + +/////////////////////////////////////////////// +////////////////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 + +#endif //BUILD_HACK From 2c4f36802ffa712a03a5416c02bb779a95e1c360 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Fri, 20 Sep 2013 20:08:56 -0600 Subject: [PATCH 02/41] library: split off utilities, input_digit, print_number --- libraries/hexbright/hexbright.h | 183 +----------------- libraries/hexbright/input_digit.cpp | 21 ++ libraries/hexbright/input_digit.h | 19 ++ libraries/hexbright/print_number.cpp | 76 ++++++++ libraries/hexbright/print_number.h | 22 +++ libraries/hexbright/utilities.cpp | 22 +++ libraries/hexbright/utilities.h | 35 ++++ programs/tactical/tactical.ino | 5 +- .../temperature_calibration.ino | 7 +- programs/up_n_down/up_n_down.ino | 10 +- 10 files changed, 215 insertions(+), 185 deletions(-) create mode 100644 libraries/hexbright/input_digit.cpp create mode 100644 libraries/hexbright/input_digit.h create mode 100644 libraries/hexbright/print_number.cpp create mode 100644 libraries/hexbright/print_number.h create mode 100644 libraries/hexbright/utilities.cpp create mode 100644 libraries/hexbright/utilities.h mode change 100755 => 100644 programs/tactical/tactical.ino mode change 100755 => 100644 programs/temperature_calibration/temperature_calibration.ino diff --git a/libraries/hexbright/hexbright.h b/libraries/hexbright/hexbright.h index f701cc1..b28d8af 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -41,7 +41,6 @@ either expressed or implied, of the FreeBSD Project. /// 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 @@ -93,6 +92,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 // @@ -269,9 +270,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. @@ -295,55 +293,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. @@ -466,8 +419,6 @@ class hexbright { 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); @@ -532,7 +483,7 @@ class hexbright { /////////////HARDWARE INIT, UPDATE///////////// /////////////////////////////////////////////// -const float update_delay = 8.3333333; // in lock-step with the accelerometer +const float update_delay = UPDATE_DELAY; // in lock-step with the accelerometer unsigned long continue_time; #ifdef STROBE @@ -671,7 +622,7 @@ void hexbright::update() { read_button(); // turn on (or off) the leds, if appropriate adjust_leds(); -#ifdef PRINT_NUMBER +#ifdef PRINT_NUMBER_H update_number(); #endif #else @@ -1422,123 +1373,6 @@ void hexbright::print_vector(int* vector, const char* label) { #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//////////////////// /////////////////////////////////////////////// @@ -1680,15 +1514,6 @@ unsigned char hexbright::get_charge_state() { 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///////////////////// diff --git a/libraries/hexbright/input_digit.cpp b/libraries/hexbright/input_digit.cpp new file mode 100644 index 0000000..6bb0fba --- /dev/null +++ b/libraries/hexbright/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/hexbright/input_digit.h b/libraries/hexbright/input_digit.h new file mode 100644 index 0000000..4cdfcc2 --- /dev/null +++ b/libraries/hexbright/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/hexbright/print_number.cpp b/libraries/hexbright/print_number.cpp new file mode 100644 index 0000000..a26c302 --- /dev/null +++ b/libraries/hexbright/print_number.cpp @@ -0,0 +1,76 @@ +#include "print_number.h" + +long _number = 0; +unsigned char _color = GLED; +int print_wait_time = 0; + + +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/hexbright/print_number.h b/libraries/hexbright/print_number.h new file mode 100644 index 0000000..0bbefbb --- /dev/null +++ b/libraries/hexbright/print_number.h @@ -0,0 +1,22 @@ +#ifndef PRINT_NUMBER_H +#define PRINT_NUMBER_H + +#include "utilities.h" +#include "hexbright.h" + +// 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/hexbright/utilities.cpp b/libraries/hexbright/utilities.cpp new file mode 100644 index 0000000..34a184d --- /dev/null +++ b/libraries/hexbright/utilities.cpp @@ -0,0 +1,22 @@ +#include "utilities.h" + +unsigned char flip_color(unsigned char color) { + return (color+1)%2; +} + +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/hexbright/utilities.h b/libraries/hexbright/utilities.h new file mode 100644 index 0000000..13e8024 --- /dev/null +++ b/libraries/hexbright/utilities.h @@ -0,0 +1,35 @@ +#ifndef UTILITIES_H +#define UTILITIES_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); + +// 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_charge(GLED); +// ...end of loop... +// see also print_charge for usage +extern void print_power(); + + +#endif // UTILITIES_H diff --git a/programs/tactical/tactical.ino b/programs/tactical/tactical.ino old mode 100755 new mode 100644 index bc95260..6ea05e9 --- a/programs/tactical/tactical.ino +++ b/programs/tactical/tactical.ino @@ -27,6 +27,9 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include + +#define BUILD_HACK #include #include @@ -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 old mode 100755 new mode 100644 index d509a50..4f0e846 --- a/programs/temperature_calibration/temperature_calibration.ino +++ b/programs/temperature_calibration/temperature_calibration.ino @@ -27,6 +27,9 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include + +#define BUILD_HACK // always put this right before including hexbright.h, after including libraries #include #include @@ -48,8 +51,8 @@ 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/up_n_down.ino b/programs/up_n_down/up_n_down.ino index 5f9906d..da97ac4 100644 --- a/programs/up_n_down/up_n_down.ino +++ b/programs/up_n_down/up_n_down.ino @@ -23,6 +23,10 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include + +#define BUILD_HACK #include #include #include @@ -114,7 +118,7 @@ void setup() { DBG(Serial.println("Powered up!")); - hb.config_click_count(click); + config_click_count(click); } void loop() { @@ -127,7 +131,7 @@ void loop() { #endif { // Charging - hb.print_charge(GLED); + print_charge(GLED); // Low battery if(mode != MODE_OFF && hb.low_voltage_state()) @@ -136,7 +140,7 @@ void loop() { } // Get the click count - new_mode = hb.click_count(); + new_mode = 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) ) { From 3aa53a4c7e4f01764682fb5ab67d1dccfdea5475 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Sun, 22 Sep 2013 19:31:25 -0600 Subject: [PATCH 03/41] library: moved utilities to libraries/hb_utilities --- libraries/{hexbright => hb_utilities}/click_counter.cpp | 0 libraries/{hexbright => hb_utilities}/click_counter.h | 0 libraries/{hexbright => hb_utilities}/input_digit.cpp | 0 libraries/{hexbright => hb_utilities}/input_digit.h | 0 libraries/{hexbright => hb_utilities}/print_number.cpp | 0 libraries/{hexbright => hb_utilities}/print_number.h | 0 .../{hexbright/utilities.cpp => hb_utilities/print_power.cpp} | 0 libraries/{hexbright/utilities.h => hb_utilities/print_power.h} | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename libraries/{hexbright => hb_utilities}/click_counter.cpp (100%) rename libraries/{hexbright => hb_utilities}/click_counter.h (100%) rename libraries/{hexbright => hb_utilities}/input_digit.cpp (100%) rename libraries/{hexbright => hb_utilities}/input_digit.h (100%) rename libraries/{hexbright => hb_utilities}/print_number.cpp (100%) rename libraries/{hexbright => hb_utilities}/print_number.h (100%) rename libraries/{hexbright/utilities.cpp => hb_utilities/print_power.cpp} (100%) rename libraries/{hexbright/utilities.h => hb_utilities/print_power.h} (100%) diff --git a/libraries/hexbright/click_counter.cpp b/libraries/hb_utilities/click_counter.cpp similarity index 100% rename from libraries/hexbright/click_counter.cpp rename to libraries/hb_utilities/click_counter.cpp diff --git a/libraries/hexbright/click_counter.h b/libraries/hb_utilities/click_counter.h similarity index 100% rename from libraries/hexbright/click_counter.h rename to libraries/hb_utilities/click_counter.h diff --git a/libraries/hexbright/input_digit.cpp b/libraries/hb_utilities/input_digit.cpp similarity index 100% rename from libraries/hexbright/input_digit.cpp rename to libraries/hb_utilities/input_digit.cpp diff --git a/libraries/hexbright/input_digit.h b/libraries/hb_utilities/input_digit.h similarity index 100% rename from libraries/hexbright/input_digit.h rename to libraries/hb_utilities/input_digit.h diff --git a/libraries/hexbright/print_number.cpp b/libraries/hb_utilities/print_number.cpp similarity index 100% rename from libraries/hexbright/print_number.cpp rename to libraries/hb_utilities/print_number.cpp diff --git a/libraries/hexbright/print_number.h b/libraries/hb_utilities/print_number.h similarity index 100% rename from libraries/hexbright/print_number.h rename to libraries/hb_utilities/print_number.h diff --git a/libraries/hexbright/utilities.cpp b/libraries/hb_utilities/print_power.cpp similarity index 100% rename from libraries/hexbright/utilities.cpp rename to libraries/hb_utilities/print_power.cpp diff --git a/libraries/hexbright/utilities.h b/libraries/hb_utilities/print_power.h similarity index 100% rename from libraries/hexbright/utilities.h rename to libraries/hb_utilities/print_power.h From 56aa02d7b3b47d36557b2401e02e7e51180c4bf8 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Sun, 22 Sep 2013 19:33:02 -0600 Subject: [PATCH 04/41] library: utilities.* changed to print_power.* --- libraries/hb_utilities/print_power.cpp | 2 +- libraries/hb_utilities/print_power.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/hb_utilities/print_power.cpp b/libraries/hb_utilities/print_power.cpp index 34a184d..3666832 100644 --- a/libraries/hb_utilities/print_power.cpp +++ b/libraries/hb_utilities/print_power.cpp @@ -1,4 +1,4 @@ -#include "utilities.h" +#include "print_power.h" unsigned char flip_color(unsigned char color) { return (color+1)%2; diff --git a/libraries/hb_utilities/print_power.h b/libraries/hb_utilities/print_power.h index 13e8024..31ca869 100644 --- a/libraries/hb_utilities/print_power.h +++ b/libraries/hb_utilities/print_power.h @@ -1,5 +1,5 @@ -#ifndef UTILITIES_H -#define UTILITIES_H +#ifndef PRINT_POWER_H +#define PRINT_POWER_H #include "hexbright.h" @@ -32,4 +32,4 @@ extern void print_charge(unsigned char led); extern void print_power(); -#endif // UTILITIES_H +#endif // PRINT_POWER_H From 62fa960a630f1037dcf80f0082884f042bd6a137 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Sun, 22 Sep 2013 19:37:19 -0600 Subject: [PATCH 05/41] library: split off stroboscope code into hb_utilities --- hb-examples/stroboscope/stroboscope.ino | 23 ++-- libraries/hb_utilities/strobe.cpp | 33 +++++ libraries/hb_utilities/strobe.h | 66 +++++++++ libraries/hexbright/hexbright.h | 172 +++--------------------- libraries/hexbright/update_spin.h | 95 +++++++++++++ 5 files changed, 224 insertions(+), 165 deletions(-) create mode 100644 libraries/hb_utilities/strobe.cpp create mode 100644 libraries/hb_utilities/strobe.h create mode 100644 libraries/hexbright/update_spin.h diff --git a/hb-examples/stroboscope/stroboscope.ino b/hb-examples/stroboscope/stroboscope.ino index 95d6d15..9a717ee 100755 --- a/hb-examples/stroboscope/stroboscope.ino +++ b/hb-examples/stroboscope/stroboscope.ino @@ -1,4 +1,7 @@ -// uncomment (delete // before) '#define STROBE' in hexbright.h +#include +#include + +#define BUILD_HACK #include #include @@ -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/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/hexbright/hexbright.h b/libraries/hexbright/hexbright.h index b28d8af..08fb7cc 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -39,13 +39,23 @@ either expressed or implied, of the FreeBSD Project. #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 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, @@ -109,9 +119,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 @@ -196,47 +203,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. @@ -457,8 +423,10 @@ class hexbright { /////////////////////////////////////////////////////////////////////////////// + #ifdef BUILD_HACK +#include "update_spin.h" #include #ifndef __AVR // we're not compiling for arduino (probably testing), use these stubs @@ -468,15 +436,6 @@ class hexbright { #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 /////////////////////////////////////////////// @@ -484,13 +443,7 @@ class hexbright { /////////////////////////////////////////////// const float update_delay = UPDATE_DELAY; // 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() { } @@ -544,74 +497,14 @@ void hexbright::init_hardware() { 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 - - + update_spin(); + // power saving modes described here: http://www.atmel.com/Images/2545s.pdf //run overheat protection, time display, track battery usage @@ -644,12 +537,9 @@ void hexbright::update() { // 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 () { @@ -833,34 +723,6 @@ void hexbright::apply_max_light_level() { } -#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///////////////// /////////////////////////////////////////////// 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 From ab4b72aaad96f2abad98874134d9ad61b94d1dc7 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Sun, 22 Sep 2013 19:41:18 -0600 Subject: [PATCH 06/41] programs: tactical, up_n_down now work with print_power --- programs/tactical/tactical.ino | 4 ++-- programs/up_n_down/up_n_down.ino | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) mode change 100644 => 100755 programs/tactical/tactical.ino mode change 100644 => 100755 programs/up_n_down/up_n_down.ino diff --git a/programs/tactical/tactical.ino b/programs/tactical/tactical.ino old mode 100644 new mode 100755 index 6ea05e9..a147403 --- a/programs/tactical/tactical.ino +++ b/programs/tactical/tactical.ino @@ -27,7 +27,7 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ -#include +#include #define BUILD_HACK #include @@ -79,4 +79,4 @@ void loop() { } print_power(); } - + 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 da97ac4..8db6a99 --- a/programs/up_n_down/up_n_down.ino +++ b/programs/up_n_down/up_n_down.ino @@ -24,7 +24,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include -#include +#include #define BUILD_HACK #include @@ -342,4 +342,4 @@ void loop() { } } - + From ef571c702dcd3b5ebd1779289dce9ff91f6d70d3 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Sun, 22 Sep 2013 19:41:57 -0600 Subject: [PATCH 07/41] hb_utilities: flip_color moved from print_power to print_number --- libraries/hb_utilities/print_number.cpp | 4 ++++ libraries/hb_utilities/print_number.h | 5 ++++- libraries/hb_utilities/print_power.cpp | 4 ---- libraries/hb_utilities/print_power.h | 4 ---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/libraries/hb_utilities/print_number.cpp b/libraries/hb_utilities/print_number.cpp index a26c302..c9d3a3a 100644 --- a/libraries/hb_utilities/print_number.cpp +++ b/libraries/hb_utilities/print_number.cpp @@ -5,6 +5,10 @@ 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; } diff --git a/libraries/hb_utilities/print_number.h b/libraries/hb_utilities/print_number.h index 0bbefbb..239f523 100644 --- a/libraries/hb_utilities/print_number.h +++ b/libraries/hb_utilities/print_number.h @@ -1,9 +1,12 @@ #ifndef PRINT_NUMBER_H #define PRINT_NUMBER_H -#include "utilities.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. diff --git a/libraries/hb_utilities/print_power.cpp b/libraries/hb_utilities/print_power.cpp index 3666832..a42fb98 100644 --- a/libraries/hb_utilities/print_power.cpp +++ b/libraries/hb_utilities/print_power.cpp @@ -1,9 +1,5 @@ #include "print_power.h" -unsigned char flip_color(unsigned char color) { - return (color+1)%2; -} - void print_power() { print_charge(GLED); if (hexbright::low_voltage_state() && hexbright::get_led_state(RLED) == LED_OFF) { diff --git a/libraries/hb_utilities/print_power.h b/libraries/hb_utilities/print_power.h index 31ca869..28290a2 100644 --- a/libraries/hb_utilities/print_power.h +++ b/libraries/hb_utilities/print_power.h @@ -3,10 +3,6 @@ #include "hexbright.h" -// returns the opposite color from the one passed in -// Takes up 12 bytes. -extern unsigned char flip_color(unsigned char color); - // A convenience function that will print the charge state over the led specified // CHARGING = 350 ms on, 350 ms off. // CHARGED = solid on From da325011ad485e3ea74119ce404e3d96a69829c7 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Sun, 22 Sep 2013 19:50:38 -0600 Subject: [PATCH 08/41] library: hexbright/includes moved to hexbright, renamed to pc_stubs, read_adc --- libraries/hexbright/hexbright.h | 4 ++-- libraries/hexbright/{includes/NotArduino.h => pc_stubs.h} | 0 libraries/hexbright/{includes/pin_interface.h => read_adc.h} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename libraries/hexbright/{includes/NotArduino.h => pc_stubs.h} (100%) rename libraries/hexbright/{includes/pin_interface.h => read_adc.h} (100%) diff --git a/libraries/hexbright/hexbright.h b/libraries/hexbright/hexbright.h index 08fb7cc..32c24ce 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -430,9 +430,9 @@ class hexbright { #include #ifndef __AVR // we're not compiling for arduino (probably testing), use these stubs -#include "includes/NotArduino.h" +#include "pc_stubs.h" #else -#include "includes/pin_interface.h" +#include "read_adc.h" #include "../digitalWriteFast/digitalWriteFast.h" #endif 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 From 2d503a5f342c2e458e59441a8f3ec932aad3057a Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Sun, 22 Sep 2013 19:57:02 -0600 Subject: [PATCH 09/41] twi: moved to hexbright/twi, so it is automatically pulled in by including hexbright.h --- hb-examples/alarm_clock/alarm_clock.ino | 3 +-- hb-examples/down_light/down_light.ino | 4 +--- hb-examples/numeric_input/numeric_input.ino | 3 +-- hb-examples/spin_level/spin_level.ino | 4 +--- hb-examples/stroboscope/stroboscope.ino | 1 - hb-examples/unplugged_action/unplugged_action.ino | 3 +-- libraries/{twi => hexbright}/Readme.md | 0 libraries/hexbright/hexbright.h | 2 +- libraries/{twi => hexbright}/twi.c | 0 libraries/{twi => hexbright}/twi.h | 0 programs/action_timer/action_timer.ino | 3 +-- programs/functional/functional.ino | 4 +--- programs/tactical/tactical.ino | 1 - programs/temperature_calibration/temperature_calibration.ino | 3 +-- programs/up_n_down/up_n_down.ino | 1 - programs/wand/wand.ino | 5 +---- tests/accelerometer_record/accelerometer_record.ino | 3 +-- tests/accelerometer_test/accelerometer_test.ino | 4 +--- tests/avr_voltage_test/avr_voltage_test.ino | 4 +--- tests/led_test/led_test.ino | 4 +--- tests/light_test/light_test.ino | 3 +-- tests/linearity_test/linearity_test.ino | 4 +--- tests/print_number_test/print_number_test.ino | 3 +-- tests/stroboscope_test/stroboscope_test.ino | 3 +-- 24 files changed, 18 insertions(+), 47 deletions(-) mode change 100644 => 100755 hb-examples/alarm_clock/alarm_clock.ino mode change 100644 => 100755 hb-examples/down_light/down_light.ino mode change 100644 => 100755 hb-examples/spin_level/spin_level.ino rename libraries/{twi => hexbright}/Readme.md (100%) rename libraries/{twi => hexbright}/twi.c (100%) rename libraries/{twi => hexbright}/twi.h (100%) mode change 100644 => 100755 programs/functional/functional.ino mode change 100644 => 100755 programs/temperature_calibration/temperature_calibration.ino mode change 100644 => 100755 programs/wand/wand.ino mode change 100644 => 100755 tests/avr_voltage_test/avr_voltage_test.ino mode change 100644 => 100755 tests/linearity_test/linearity_test.ino mode change 100644 => 100755 tests/print_number_test/print_number_test.ino 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..77c21d2 --- a/hb-examples/alarm_clock/alarm_clock.ino +++ b/hb-examples/alarm_clock/alarm_clock.ino @@ -1,5 +1,4 @@ #include -#include #include hexbright hb; @@ -80,4 +79,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..45edd10 --- a/hb-examples/down_light/down_light.ino +++ b/hb-examples/down_light/down_light.ino @@ -29,8 +29,6 @@ either expressed or implied, of the FreeBSD Project. #include -#include - hexbright hb; void setup() { @@ -74,4 +72,4 @@ void loop() { } } hb.print_power(); -} +} diff --git a/hb-examples/numeric_input/numeric_input.ino b/hb-examples/numeric_input/numeric_input.ino index 798c1f4..d74a04f 100755 --- a/hb-examples/numeric_input/numeric_input.ino +++ b/hb-examples/numeric_input/numeric_input.ino @@ -1,5 +1,4 @@ #include -#include hexbright hb; @@ -26,4 +25,4 @@ void loop() { value = 0; } hb.input_digit(value*10, value*10+10); -} +} diff --git a/hb-examples/spin_level/spin_level.ino b/hb-examples/spin_level/spin_level.ino old mode 100644 new mode 100755 index 4ec0123..89c5e0e --- a/hb-examples/spin_level/spin_level.ino +++ b/hb-examples/spin_level/spin_level.ino @@ -29,8 +29,6 @@ either expressed or implied, of the FreeBSD Project. #include -#include - hexbright hb; void setup() { @@ -64,4 +62,4 @@ void loop() { } } hb.print_power(); -} +} diff --git a/hb-examples/stroboscope/stroboscope.ino b/hb-examples/stroboscope/stroboscope.ino index 9a717ee..61da716 100755 --- a/hb-examples/stroboscope/stroboscope.ino +++ b/hb-examples/stroboscope/stroboscope.ino @@ -3,7 +3,6 @@ #define BUILD_HACK #include -#include hexbright hb; diff --git a/hb-examples/unplugged_action/unplugged_action.ino b/hb-examples/unplugged_action/unplugged_action.ino index f36b982..92ccd15 100755 --- a/hb-examples/unplugged_action/unplugged_action.ino +++ b/hb-examples/unplugged_action/unplugged_action.ino @@ -1,5 +1,4 @@ #include -#include hexbright hb; @@ -46,4 +45,4 @@ void loop() { } } } - + 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.h b/libraries/hexbright/hexbright.h index 32c24ce..6d5c479 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -32,7 +32,7 @@ either expressed or implied, of the FreeBSD Project. #ifdef __AVR // we're compiling for arduino #include -#include "../twi/twi.h" +#include "twi.h" #include "../digitalWriteFast/digitalWriteFast.h" #define BOOL boolean #else 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/programs/action_timer/action_timer.ino b/programs/action_timer/action_timer.ino index 312d3e6..40723a8 100755 --- a/programs/action_timer/action_timer.ino +++ b/programs/action_timer/action_timer.ino @@ -28,7 +28,6 @@ either expressed or implied, of the FreeBSD Project. */ #include -#include #include hexbright hb; @@ -169,4 +168,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..11b6c57 --- a/programs/functional/functional.ino +++ b/programs/functional/functional.ino @@ -29,8 +29,6 @@ either expressed or implied, of the FreeBSD Project. #include -#include - #define OFF_MODE 0 #define BLINKY_MODE 1 #define CYCLE_MODE 2 @@ -84,4 +82,4 @@ void loop() { hb.print_charge(GLED); } } -} +} diff --git a/programs/tactical/tactical.ino b/programs/tactical/tactical.ino index a147403..9769e7c 100755 --- a/programs/tactical/tactical.ino +++ b/programs/tactical/tactical.ino @@ -31,7 +31,6 @@ either expressed or implied, of the FreeBSD Project. #define BUILD_HACK #include -#include hexbright hb; diff --git a/programs/temperature_calibration/temperature_calibration.ino b/programs/temperature_calibration/temperature_calibration.ino old mode 100644 new mode 100755 index 4f0e846..1c5ec8d --- a/programs/temperature_calibration/temperature_calibration.ino +++ b/programs/temperature_calibration/temperature_calibration.ino @@ -31,7 +31,6 @@ either expressed or implied, of the FreeBSD Project. #define BUILD_HACK // always put this right before including hexbright.h, after including libraries #include -#include // Usage notes are in the readme file in this same directory. hexbright hb; @@ -56,4 +55,4 @@ void loop() { // hb.print_number(hb.get_fahrenheit()); } } - + diff --git a/programs/up_n_down/up_n_down.ino b/programs/up_n_down/up_n_down.ino index 8db6a99..119908e 100755 --- a/programs/up_n_down/up_n_down.ino +++ b/programs/up_n_down/up_n_down.ino @@ -28,7 +28,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define BUILD_HACK #include -#include #include #if (DEBUG==DEBUG_PROGRAM) diff --git a/programs/wand/wand.ino b/programs/wand/wand.ino old mode 100644 new mode 100755 index 1ec697e..6045aee --- a/programs/wand/wand.ino +++ b/programs/wand/wand.ino @@ -1,6 +1,3 @@ -#include - -// uncomment #ACCELEROMETER in hexbright.h #include hexbright hb; @@ -50,4 +47,4 @@ void loop() { } else if (mode==OFF_MODE) { hb.print_power(); } -} +} diff --git a/tests/accelerometer_record/accelerometer_record.ino b/tests/accelerometer_record/accelerometer_record.ino index 31cc1d0..18d4f55 100755 --- a/tests/accelerometer_record/accelerometer_record.ino +++ b/tests/accelerometer_record/accelerometer_record.ino @@ -1,5 +1,4 @@ #include -#include #include hexbright hb; @@ -106,4 +105,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..e7a9f8c 100755 --- a/tests/accelerometer_test/accelerometer_test.ino +++ b/tests/accelerometer_test/accelerometer_test.ino @@ -1,5 +1,3 @@ -#include -// uncomment '#define ACCELEROMETER' in hexbright.h #include @@ -83,4 +81,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..ca87ca4 --- a/tests/avr_voltage_test/avr_voltage_test.ino +++ b/tests/avr_voltage_test/avr_voltage_test.ino @@ -29,8 +29,6 @@ either expressed or implied, of the FreeBSD Project. #include -#include - // number of milliseconds between updates #define OFF_MODE 0 #define BLINKY_MODE 1 @@ -86,4 +84,4 @@ void loop() { hb.print_charge(GLED); } } -} +} diff --git a/tests/led_test/led_test.ino b/tests/led_test/led_test.ino index 9a7d712..b04e413 100755 --- a/tests/led_test/led_test.ino +++ b/tests/led_test/led_test.ino @@ -1,5 +1,3 @@ -#include - #include hexbright hb; @@ -35,4 +33,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 index badc0ba..8bbb675 100755 --- a/tests/light_test/light_test.ino +++ b/tests/light_test/light_test.ino @@ -1,5 +1,4 @@ #include -#include hexbright hb; @@ -23,4 +22,4 @@ void loop() { mode = OFF_MODE; } } -} +} 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..f69c5a2 --- a/tests/print_number_test/print_number_test.ino +++ b/tests/print_number_test/print_number_test.ino @@ -28,7 +28,6 @@ either expressed or implied, of the FreeBSD Project. */ #include -#include // Usage notes are in the readme file in this same directory. hexbright hb; @@ -49,4 +48,4 @@ void loop() { number = (number+1)%COUNT; } } - + diff --git a/tests/stroboscope_test/stroboscope_test.ino b/tests/stroboscope_test/stroboscope_test.ino index 670485e..5b2e93f 100755 --- a/tests/stroboscope_test/stroboscope_test.ino +++ b/tests/stroboscope_test/stroboscope_test.ino @@ -1,5 +1,4 @@ #include -#include hexbright hb; @@ -134,4 +133,4 @@ void setPwmFrequency(int pin, int divisor) { } TCCR2B = TCCR2B & 0b11111000 | mode; } -} +} From aae542a9aa81e39b9d1300a4e5d9f6f8aa94f64e Mon Sep 17 00:00:00 2001 From: David Hilton Date: Sun, 22 Sep 2013 20:43:00 -0600 Subject: [PATCH 10/41] library: added readme --- libraries/Readme.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 libraries/Readme.md diff --git a/libraries/Readme.md b/libraries/Readme.md new file mode 100644 index 0000000..a54cd3b --- /dev/null +++ b/libraries/Readme.md @@ -0,0 +1,2 @@ +Planned structure in moving to a framework: + From 3fce0cd8b869e2efae481db1f92508c92cee2ad7 Mon Sep 17 00:00:00 2001 From: David Hilton Date: Sun, 22 Sep 2013 20:52:29 -0600 Subject: [PATCH 11/41] library/readme: added description of 3 key directories --- libraries/Readme.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libraries/Readme.md b/libraries/Readme.md index a54cd3b..5703aa6 100644 --- a/libraries/Readme.md +++ b/libraries/Readme.md @@ -1,2 +1,11 @@ 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
+hexbright - core library, plus .h files for overridable options/behavior (low_battery, overheat)
+ +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. From c8af028a0f2cc886f607d230b0515c6bf638efe3 Mon Sep 17 00:00:00 2001 From: David Hilton Date: Mon, 23 Sep 2013 17:28:51 -0600 Subject: [PATCH 12/41] Update README.md --- experiments/power_draw/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 From 64fbc38eae8ad8661e546303b0d10ea103eaeb38 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Fri, 27 Sep 2013 10:21:04 -0600 Subject: [PATCH 13/41] updated all programs to work with the current framework version --- hb-examples/alarm_clock/alarm_clock.ino | 19 ++++++++++++------- hb-examples/down_light/down_light.ino | 6 ++++-- hb-examples/numeric_input/numeric_input.ino | 11 +++++++---- hb-examples/spin_level/spin_level.ino | 7 +++++-- .../unplugged_action/unplugged_action.ino | 7 +++++-- programs/action_timer/action_timer.ino | 19 ++++++++++++------- programs/functional/functional.ino | 13 ++++++++----- programs/wand/wand.ino | 7 +++++-- .../accelerometer_record.ino | 3 ++- .../accelerometer_test/accelerometer_test.ino | 3 ++- tests/avr_voltage_test/avr_voltage_test.ino | 14 +++++++++----- tests/led_test/led_test.ino | 3 ++- tests/light_test/light_test.ino | 3 ++- tests/print_number_test/print_number_test.ino | 9 ++++++--- tests/stroboscope_test/stroboscope_test.ino | 3 ++- 15 files changed, 83 insertions(+), 44 deletions(-) mode change 100755 => 100644 hb-examples/alarm_clock/alarm_clock.ino mode change 100755 => 100644 hb-examples/down_light/down_light.ino mode change 100755 => 100644 hb-examples/numeric_input/numeric_input.ino mode change 100755 => 100644 hb-examples/spin_level/spin_level.ino mode change 100755 => 100644 hb-examples/unplugged_action/unplugged_action.ino mode change 100755 => 100644 programs/action_timer/action_timer.ino mode change 100755 => 100644 programs/functional/functional.ino mode change 100755 => 100644 programs/wand/wand.ino mode change 100755 => 100644 tests/accelerometer_record/accelerometer_record.ino mode change 100755 => 100644 tests/accelerometer_test/accelerometer_test.ino mode change 100755 => 100644 tests/avr_voltage_test/avr_voltage_test.ino mode change 100755 => 100644 tests/led_test/led_test.ino mode change 100755 => 100644 tests/light_test/light_test.ino mode change 100755 => 100644 tests/print_number_test/print_number_test.ino mode change 100755 => 100644 tests/stroboscope_test/stroboscope_test.ino diff --git a/hb-examples/alarm_clock/alarm_clock.ino b/hb-examples/alarm_clock/alarm_clock.ino old mode 100755 new mode 100644 index 77c21d2..6d47350 --- a/hb-examples/alarm_clock/alarm_clock.ino +++ b/hb-examples/alarm_clock/alarm_clock.ino @@ -1,3 +1,8 @@ +#include +#include +#include + +#define BUILD_HACK #include #include @@ -34,14 +39,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) { @@ -59,9 +64,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; @@ -79,4 +84,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 100755 new mode 100644 index 45edd10..716bab3 --- a/hb-examples/down_light/down_light.ino +++ b/hb-examples/down_light/down_light.ino @@ -26,7 +26,9 @@ 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 +#define BUILD_HACK #include hexbright hb; @@ -71,5 +73,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 old mode 100755 new mode 100644 index d74a04f..bf424c2 --- a/hb-examples/numeric_input/numeric_input.ino +++ b/hb-examples/numeric_input/numeric_input.ino @@ -1,3 +1,6 @@ +#include + +#define BUILD_HACK #include hexbright hb; @@ -15,14 +18,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/spin_level/spin_level.ino b/hb-examples/spin_level/spin_level.ino old mode 100755 new mode 100644 index 89c5e0e..53a17a8 --- a/hb-examples/spin_level/spin_level.ino +++ b/hb-examples/spin_level/spin_level.ino @@ -27,6 +27,9 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include + +#define BUILD_HACK #include hexbright hb; @@ -61,5 +64,5 @@ void loop() { hb.set_light(CURRENT_LEVEL, brightness_level, 100); } } - hb.print_power(); -} + print_power(); +} diff --git a/hb-examples/unplugged_action/unplugged_action.ino b/hb-examples/unplugged_action/unplugged_action.ino old mode 100755 new mode 100644 index 92ccd15..fadba51 --- a/hb-examples/unplugged_action/unplugged_action.ino +++ b/hb-examples/unplugged_action/unplugged_action.ino @@ -1,3 +1,6 @@ +#include + +#define BUILD_HACK #include hexbright hb; @@ -37,7 +40,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) { @@ -45,4 +48,4 @@ void loop() { } } } - + diff --git a/programs/action_timer/action_timer.ino b/programs/action_timer/action_timer.ino old mode 100755 new mode 100644 index 40723a8..c5f46ba --- a/programs/action_timer/action_timer.ino +++ b/programs/action_timer/action_timer.ino @@ -27,6 +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 + +#define BUILD_HACK #include #include @@ -68,8 +73,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 @@ -78,9 +83,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)); } } } @@ -129,9 +134,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) @@ -168,4 +173,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 100755 new mode 100644 index 11b6c57..b9356e1 --- a/programs/functional/functional.ino +++ b/programs/functional/functional.ino @@ -26,7 +26,10 @@ 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 +#define BUILD_HACK #include #define OFF_MODE 0 @@ -74,12 +77,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/wand/wand.ino b/programs/wand/wand.ino old mode 100755 new mode 100644 index 6045aee..a80cef6 --- a/programs/wand/wand.ino +++ b/programs/wand/wand.ino @@ -1,3 +1,6 @@ +#include + +#define BUILD_HACK #include hexbright hb; @@ -45,6 +48,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 old mode 100755 new mode 100644 index 18d4f55..68831cf --- a/tests/accelerometer_record/accelerometer_record.ino +++ b/tests/accelerometer_record/accelerometer_record.ino @@ -1,3 +1,4 @@ +#define BUILD_HACK #include #include @@ -105,4 +106,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 old mode 100755 new mode 100644 index e7a9f8c..3d4cb2c --- a/tests/accelerometer_test/accelerometer_test.ino +++ b/tests/accelerometer_test/accelerometer_test.ino @@ -1,3 +1,4 @@ +#define BUILD_HACK #include @@ -81,4 +82,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 100755 new mode 100644 index ca87ca4..4756fd5 --- a/tests/avr_voltage_test/avr_voltage_test.ino +++ b/tests/avr_voltage_test/avr_voltage_test.ino @@ -27,6 +27,10 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include +#include + +#define BUILD_HACK #include // number of milliseconds between updates @@ -76,12 +80,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 old mode 100755 new mode 100644 index b04e413..bdb0450 --- a/tests/led_test/led_test.ino +++ b/tests/led_test/led_test.ino @@ -1,3 +1,4 @@ +#define BUILD_HACK #include hexbright hb; @@ -33,4 +34,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 8bbb675..5c15958 --- a/tests/light_test/light_test.ino +++ b/tests/light_test/light_test.ino @@ -1,3 +1,4 @@ +#define BUILD_HACK #include hexbright hb; @@ -22,4 +23,4 @@ void loop() { mode = OFF_MODE; } } -} +} diff --git a/tests/print_number_test/print_number_test.ino b/tests/print_number_test/print_number_test.ino old mode 100755 new mode 100644 index f69c5a2..2138a35 --- a/tests/print_number_test/print_number_test.ino +++ b/tests/print_number_test/print_number_test.ino @@ -27,6 +27,9 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +#include + +#define BUILD_HACK #include // Usage notes are in the readme file in this same directory. @@ -43,9 +46,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 old mode 100755 new mode 100644 index 5b2e93f..04b0e7c --- a/tests/stroboscope_test/stroboscope_test.ino +++ b/tests/stroboscope_test/stroboscope_test.ino @@ -1,3 +1,4 @@ +#define BUILD_HACK #include hexbright hb; @@ -133,4 +134,4 @@ void setPwmFrequency(int pin, int divisor) { } TCCR2B = TCCR2B & 0b11111000 | mode; } -} +} From 43508b3c0460e5e808843ca29466d37d17bc4df7 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Fri, 27 Sep 2013 17:09:22 -0600 Subject: [PATCH 14/41] hb_utilities: added print_binary, which prints binary representations of integers over Serial. --- libraries/hb_utilities/print_binary.cpp | 19 ++++++++++++++++++ libraries/hb_utilities/print_binary.h | 12 +++++++++++ libraries/hexbright/hexbright.h | 7 ------- .../accelerometer_test/accelerometer_test.ino | 20 +++---------------- 4 files changed, 34 insertions(+), 24 deletions(-) create mode 100644 libraries/hb_utilities/print_binary.cpp create mode 100644 libraries/hb_utilities/print_binary.h mode change 100644 => 100755 tests/accelerometer_test/accelerometer_test.ino 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..498d0ec --- /dev/null +++ b/libraries/hb_utilities/print_binary.h @@ -0,0 +1,12 @@ +#ifndef PRINT_BINARY_H +#define PRINT_BINARY_H + +#include +#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/hexbright/hexbright.h b/libraries/hexbright/hexbright.h index 6d5c479..7ecf269 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -957,14 +957,7 @@ void hexbright::enable_accelerometer() { // 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)>*/ diff --git a/tests/accelerometer_test/accelerometer_test.ino b/tests/accelerometer_test/accelerometer_test.ino old mode 100644 new mode 100755 index 3d4cb2c..9552501 --- a/tests/accelerometer_test/accelerometer_test.ino +++ b/tests/accelerometer_test/accelerometer_test.ino @@ -1,3 +1,5 @@ +#include + #define BUILD_HACK #include @@ -51,23 +53,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) { @@ -82,4 +68,4 @@ double something() { return dot_product()/(sqrt_sqr(0)*sqrt_sqr(1)); }*/ - + From 8c822e806e0de3c8017114436330a6d3493fbfd5 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Fri, 27 Sep 2013 18:33:40 -0600 Subject: [PATCH 15/41] library: removed shutdown (deprecated method) --- libraries/hexbright/hexbright.h | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/libraries/hexbright/hexbright.h b/libraries/hexbright/hexbright.h index 7ecf269..b751f18 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -151,20 +151,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 @@ -1370,18 +1356,6 @@ unsigned char hexbright::get_charge_state() { } -/////////////////////////////////////////////// -//////////////////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 /////////////////////////////////////////////// From d409effebbdc0cb2f68052e4933bb83c201cbe36 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Tue, 8 Oct 2013 07:24:06 -0600 Subject: [PATCH 16/41] spin_level: moved to programs, added a readme, slightly altered behavior --- programs/spin_level/README.md | 8 +++++ .../spin_level/spin_level.ino | 34 +++++++++++++------ 2 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 programs/spin_level/README.md rename {hb-examples => programs}/spin_level/spin_level.ino (76%) mode change 100644 => 100755 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 76% rename from hb-examples/spin_level/spin_level.ino rename to programs/spin_level/spin_level.ino index 53a17a8..e9c0b65 --- a/hb-examples/spin_level/spin_level.ino +++ b/programs/spin_level/spin_level.ino @@ -42,21 +42,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; @@ -65,4 +77,4 @@ void loop() { } } print_power(); -} +} From 938568cbaee8988996776135cc35c06f27b1dfc9 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Sun, 10 Nov 2013 10:29:28 -0700 Subject: [PATCH 17/41] all programs: added inline comment documenting BUILD_HACK --- hb-examples/alarm_clock/alarm_clock.ino | 3 ++- hb-examples/down_light/down_light.ino | 3 ++- hb-examples/numeric_input/numeric_input.ino | 3 ++- hb-examples/stroboscope/stroboscope.ino | 1 + hb-examples/unplugged_action/unplugged_action.ino | 3 ++- programs/action_timer/action_timer.ino | 3 ++- programs/functional/functional.ino | 3 ++- programs/spin_level/spin_level.ino | 1 + programs/tactical/tactical.ino | 1 + programs/temperature_calibration/temperature_calibration.ino | 3 ++- programs/up_n_down/up_n_down.ino | 1 + programs/wand/wand.ino | 3 ++- tests/accelerometer_record/accelerometer_record.ino | 3 ++- tests/accelerometer_test/accelerometer_test.ino | 1 + tests/avr_voltage_test/avr_voltage_test.ino | 3 ++- tests/led_test/led_test.ino | 3 ++- tests/light_test/light_test.ino | 3 ++- tests/print_number_test/print_number_test.ino | 3 ++- tests/stroboscope_test/stroboscope_test.ino | 3 ++- 19 files changed, 33 insertions(+), 14 deletions(-) mode change 100644 => 100755 hb-examples/alarm_clock/alarm_clock.ino mode change 100644 => 100755 hb-examples/down_light/down_light.ino mode change 100644 => 100755 hb-examples/numeric_input/numeric_input.ino mode change 100644 => 100755 hb-examples/unplugged_action/unplugged_action.ino mode change 100644 => 100755 programs/action_timer/action_timer.ino mode change 100644 => 100755 programs/functional/functional.ino mode change 100644 => 100755 programs/wand/wand.ino mode change 100644 => 100755 tests/accelerometer_record/accelerometer_record.ino mode change 100644 => 100755 tests/avr_voltage_test/avr_voltage_test.ino mode change 100644 => 100755 tests/led_test/led_test.ino mode change 100644 => 100755 tests/light_test/light_test.ino mode change 100644 => 100755 tests/print_number_test/print_number_test.ino mode change 100644 => 100755 tests/stroboscope_test/stroboscope_test.ino 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 6d47350..a4ba3b3 --- a/hb-examples/alarm_clock/alarm_clock.ino +++ b/hb-examples/alarm_clock/alarm_clock.ino @@ -2,6 +2,7 @@ #include #include +// These next two lines must come after all other library #includes #define BUILD_HACK #include #include @@ -84,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 716bab3..5e8d6f4 --- a/hb-examples/down_light/down_light.ino +++ b/hb-examples/down_light/down_light.ino @@ -28,6 +28,7 @@ either expressed or implied, of the FreeBSD Project. */ #include +// These next two lines must come after all other library #includes #define BUILD_HACK #include @@ -74,4 +75,4 @@ void loop() { } } print_power(); -} +} diff --git a/hb-examples/numeric_input/numeric_input.ino b/hb-examples/numeric_input/numeric_input.ino old mode 100644 new mode 100755 index bf424c2..7a56adb --- a/hb-examples/numeric_input/numeric_input.ino +++ b/hb-examples/numeric_input/numeric_input.ino @@ -1,5 +1,6 @@ #include +// These next two lines must come after all other library #includes #define BUILD_HACK #include @@ -28,4 +29,4 @@ void loop() { value = 0; } input_digit(value*10, value*10+10); -} +} diff --git a/hb-examples/stroboscope/stroboscope.ino b/hb-examples/stroboscope/stroboscope.ino index 61da716..48fa88e 100755 --- a/hb-examples/stroboscope/stroboscope.ino +++ b/hb-examples/stroboscope/stroboscope.ino @@ -1,6 +1,7 @@ #include #include +// These next two lines must come after all other library #includes #define BUILD_HACK #include diff --git a/hb-examples/unplugged_action/unplugged_action.ino b/hb-examples/unplugged_action/unplugged_action.ino old mode 100644 new mode 100755 index fadba51..c8bbbd3 --- a/hb-examples/unplugged_action/unplugged_action.ino +++ b/hb-examples/unplugged_action/unplugged_action.ino @@ -1,5 +1,6 @@ #include +// These next two lines must come after all other library #includes #define BUILD_HACK #include @@ -48,4 +49,4 @@ void loop() { } } } - + diff --git a/programs/action_timer/action_timer.ino b/programs/action_timer/action_timer.ino old mode 100644 new mode 100755 index c5f46ba..74bf7cb --- a/programs/action_timer/action_timer.ino +++ b/programs/action_timer/action_timer.ino @@ -31,6 +31,7 @@ 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 @@ -173,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 b9356e1..689478a --- a/programs/functional/functional.ino +++ b/programs/functional/functional.ino @@ -29,6 +29,7 @@ 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 @@ -85,4 +86,4 @@ void loop() { print_charge(GLED); } } -} +} diff --git a/programs/spin_level/spin_level.ino b/programs/spin_level/spin_level.ino index e9c0b65..b8d0988 100755 --- a/programs/spin_level/spin_level.ino +++ b/programs/spin_level/spin_level.ino @@ -29,6 +29,7 @@ either expressed or implied, of the FreeBSD Project. #include +// These next two lines must come after all other library #includes #define BUILD_HACK #include diff --git a/programs/tactical/tactical.ino b/programs/tactical/tactical.ino index 9769e7c..3b1aa88 100755 --- a/programs/tactical/tactical.ino +++ b/programs/tactical/tactical.ino @@ -29,6 +29,7 @@ either expressed or implied, of the FreeBSD Project. #include +// These next two lines must come after all other library #includes #define BUILD_HACK #include diff --git a/programs/temperature_calibration/temperature_calibration.ino b/programs/temperature_calibration/temperature_calibration.ino index 1c5ec8d..ff7da58 100755 --- a/programs/temperature_calibration/temperature_calibration.ino +++ b/programs/temperature_calibration/temperature_calibration.ino @@ -29,7 +29,8 @@ either expressed or implied, of the FreeBSD Project. #include -#define BUILD_HACK // always put this right before including hexbright.h, after including libraries +// These next two lines must come after all other library #includes +#define BUILD_HACK #include // Usage notes are in the readme file in this same directory. diff --git a/programs/up_n_down/up_n_down.ino b/programs/up_n_down/up_n_down.ino index 119908e..c6f6f90 100755 --- a/programs/up_n_down/up_n_down.ino +++ b/programs/up_n_down/up_n_down.ino @@ -26,6 +26,7 @@ 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 diff --git a/programs/wand/wand.ino b/programs/wand/wand.ino old mode 100644 new mode 100755 index a80cef6..f91f56f --- a/programs/wand/wand.ino +++ b/programs/wand/wand.ino @@ -1,5 +1,6 @@ #include +// These next two lines must come after all other library #includes #define BUILD_HACK #include @@ -50,4 +51,4 @@ void loop() { } else if (mode==OFF_MODE) { print_power(); } -} +} diff --git a/tests/accelerometer_record/accelerometer_record.ino b/tests/accelerometer_record/accelerometer_record.ino old mode 100644 new mode 100755 index 68831cf..339c10e --- a/tests/accelerometer_record/accelerometer_record.ino +++ b/tests/accelerometer_record/accelerometer_record.ino @@ -1,3 +1,4 @@ +// These next two lines must come after all other library #includes #define BUILD_HACK #include #include @@ -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 9552501..7753e6a 100755 --- a/tests/accelerometer_test/accelerometer_test.ino +++ b/tests/accelerometer_test/accelerometer_test.ino @@ -1,5 +1,6 @@ #include +// These next two lines must come after all other library #includes #define BUILD_HACK #include 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 4756fd5..0369f2b --- a/tests/avr_voltage_test/avr_voltage_test.ino +++ b/tests/avr_voltage_test/avr_voltage_test.ino @@ -30,6 +30,7 @@ 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 @@ -88,4 +89,4 @@ void loop() { print_charge(GLED); } } -} +} diff --git a/tests/led_test/led_test.ino b/tests/led_test/led_test.ino old mode 100644 new mode 100755 index bdb0450..a169ed4 --- a/tests/led_test/led_test.ino +++ b/tests/led_test/led_test.ino @@ -1,3 +1,4 @@ +// These next two lines must come after all other library #includes #define BUILD_HACK #include @@ -34,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 100644 new mode 100755 index 5c15958..c0976f5 --- a/tests/light_test/light_test.ino +++ b/tests/light_test/light_test.ino @@ -1,3 +1,4 @@ +// These next two lines must come after all other library #includes #define BUILD_HACK #include @@ -23,4 +24,4 @@ void loop() { mode = OFF_MODE; } } -} +} 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 2138a35..2937900 --- a/tests/print_number_test/print_number_test.ino +++ b/tests/print_number_test/print_number_test.ino @@ -29,6 +29,7 @@ either expressed or implied, of the FreeBSD Project. #include +// These next two lines must come after all other library #includes #define BUILD_HACK #include @@ -51,4 +52,4 @@ void loop() { number = (number+1)%COUNT; } } - + diff --git a/tests/stroboscope_test/stroboscope_test.ino b/tests/stroboscope_test/stroboscope_test.ino old mode 100644 new mode 100755 index 04b0e7c..0da1195 --- a/tests/stroboscope_test/stroboscope_test.ino +++ b/tests/stroboscope_test/stroboscope_test.ino @@ -1,3 +1,4 @@ +// These next two lines must come after all other library #includes #define BUILD_HACK #include @@ -134,4 +135,4 @@ void setPwmFrequency(int pin, int divisor) { } TCCR2B = TCCR2B & 0b11111000 | mode; } -} +} From 6e363b300fa87a63beef2b724cf4970effec90d4 Mon Sep 17 00:00:00 2001 From: David Hilton Date: Mon, 25 Nov 2013 23:29:43 -0700 Subject: [PATCH 18/41] hb_utilities/print_binary.h: commented out dependency on Serial.h and String.h --- libraries/hb_utilities/print_binary.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/hb_utilities/print_binary.h b/libraries/hb_utilities/print_binary.h index 498d0ec..55f21fa 100644 --- a/libraries/hb_utilities/print_binary.h +++ b/libraries/hb_utilities/print_binary.h @@ -2,8 +2,16 @@ #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); From 3d711f62aed1cf15ae9b7205ba8dd376b7907242 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Mon, 3 Feb 2014 22:06:58 -0700 Subject: [PATCH 19/41] hexbright.h: allow DEBUG to be permanantly set on a per-program basis --- libraries/hexbright/hexbright.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/hexbright/hexbright.h b/libraries/hexbright/hexbright.h index b751f18..fcab4f1 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -81,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 @@ -422,7 +421,10 @@ class hexbright { #include "../digitalWriteFast/digitalWriteFast.h" #endif - +// default to no debug mode if no override has been set +#ifndef DEBUG +#define DEBUG DEBUG_OFF +#endif /////////////////////////////////////////////// /////////////HARDWARE INIT, UPDATE///////////// From e139051d60063b80b7695c69e4d692f7167dbdc1 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Mon, 3 Feb 2014 22:09:29 -0700 Subject: [PATCH 20/41] hexbright.h: split out set_light_level, allow for #define selection of alternatives --- libraries/Readme.md | 7 ++- libraries/hexbright/hexbright.h | 45 ++++--------------- libraries/hexbright/set_light_level.cpp | 57 +++++++++++++++++++++++++ libraries/hexbright/set_light_level.h | 43 +++++++++++++++++++ tests/light_test/light_test.ino | 9 +++- 5 files changed, 121 insertions(+), 40 deletions(-) create mode 100644 libraries/hexbright/set_light_level.cpp create mode 100644 libraries/hexbright/set_light_level.h mode change 100755 => 100644 tests/light_test/light_test.ino diff --git a/libraries/Readme.md b/libraries/Readme.md index 5703aa6..f66a881 100644 --- a/libraries/Readme.md +++ b/libraries/Readme.md @@ -3,8 +3,11 @@ 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
-hexbright - core library, plus .h files for overridable options/behavior (low_battery, overheat)
+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. diff --git a/libraries/hexbright/hexbright.h b/libraries/hexbright/hexbright.h index fcab4f1..5ccf6ce 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -177,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(); @@ -365,7 +366,6 @@ 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(); @@ -421,11 +421,18 @@ class hexbright { #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///////////// /////////////////////////////////////////////// @@ -652,43 +659,7 @@ int hexbright::light_change_remaining() { 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); 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/tests/light_test/light_test.ino b/tests/light_test/light_test.ino old mode 100755 new mode 100644 index c0976f5..12dd975 --- a/tests/light_test/light_test.ino +++ b/tests/light_test/light_test.ino @@ -1,3 +1,9 @@ +#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 @@ -24,4 +30,5 @@ void loop() { mode = OFF_MODE; } } -} +} + From e2a45fe9d7d116a87e8ece3b9f43c5c03a9aa1f0 Mon Sep 17 00:00:00 2001 From: David Triendl Date: Fri, 21 Feb 2014 08:00:29 +0100 Subject: [PATCH 21/41] Fix two small errors in comments/documentation --- libraries/hb_utilities/print_power.h | 2 +- libraries/hexbright/hexbright.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/hb_utilities/print_power.h b/libraries/hb_utilities/print_power.h index 28290a2..1956ea5 100644 --- a/libraries/hb_utilities/print_power.h +++ b/libraries/hb_utilities/print_power.h @@ -22,7 +22,7 @@ extern void print_charge(unsigned char led); // I recommend the following (if using print_number()): // ...code that may call print number... // if(!printing_number()) -// print_charge(GLED); +// print_power(); // ...end of loop... // see also print_charge for usage extern void print_power(); diff --git a/libraries/hexbright/hexbright.h b/libraries/hexbright/hexbright.h index 5ccf6ce..2630439 100755 --- a/libraries/hexbright/hexbright.h +++ b/libraries/hexbright/hexbright.h @@ -214,7 +214,7 @@ class hexbright { // 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); From 211620fbd966606e31cc81264287b57b6c0e22c5 Mon Sep 17 00:00:00 2001 From: David P Hilton Date: Fri, 7 Mar 2014 13:21:55 -0700 Subject: [PATCH 22/41] updated note on power consumption --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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! From 7b66f42307dda4fd4556dcd5fe3d42881236dce0 Mon Sep 17 00:00:00 2001 From: andyseubert Date: Fri, 28 Mar 2014 15:13:53 -0700 Subject: [PATCH 23/41] added Bike Light program --- programs/BikeLight/BikeLight.ino | 106 +++++++++++++++++++++++++++++++ programs/BikeLight/README.md | 17 +++++ 2 files changed, 123 insertions(+) create mode 100644 programs/BikeLight/BikeLight.ino create mode 100644 programs/BikeLight/README.md diff --git a/programs/BikeLight/BikeLight.ino b/programs/BikeLight/BikeLight.ino new file mode 100644 index 0000000..d29ef75 --- /dev/null +++ b/programs/BikeLight/BikeLight.ino @@ -0,0 +1,106 @@ +/* +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 700 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) { + hb.set_light(MAX_LEVEL,0,random(30,350)); // fade from max to 0 over a random time btwn 30 and 350 milliseconds (length flash "on") + i=random(50,500)/8.3333; // only light up every random number of times btwn 6 and 60 through; which should equate to 50 - 500 ms (length flash "off") + } + 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 From 7784bd1d99e2a2d7f0f0fa5f5b9a68a97c88706d Mon Sep 17 00:00:00 2001 From: andyseubert Date: Fri, 28 Mar 2014 15:19:17 -0700 Subject: [PATCH 24/41] fixed some comments Bike Light program --- programs/BikeLight/BikeLight.ino | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/programs/BikeLight/BikeLight.ino b/programs/BikeLight/BikeLight.ino index d29ef75..b3231c4 100644 --- a/programs/BikeLight/BikeLight.ino +++ b/programs/BikeLight/BikeLight.ino @@ -78,7 +78,7 @@ void loop() { mode = BLINKY_MODE; } } - if(hb.button_pressed_time()>1000) { // if held for over 700 milliseconds (whether or not it's been released), go to OFF mode + if(hb.button_pressed_time()>1000) { // 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 @@ -89,9 +89,12 @@ void loop() { //// Actions over time for a given mode if(mode == BLINKY_MODE) { // random blink static int i = 0; - if(!i) { - hb.set_light(MAX_LEVEL,0,random(30,350)); // fade from max to 0 over a random time btwn 30 and 350 milliseconds (length flash "on") - i=random(50,500)/8.3333; // only light up every random number of times btwn 6 and 60 through; which should equate to 50 - 500 ms (length flash "off") + 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 From d8e154d5c3037e3424e721c133635c58612c35a7 Mon Sep 17 00:00:00 2001 From: David Hilton Date: Mon, 7 Apr 2014 10:09:48 -0600 Subject: [PATCH 25/41] added new boards.txt for Arduino 1.5 compatibility. Thanks to Josh Goebel (github: yyyc514) --- hardware/hexbright/avr/boards.txt | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 hardware/hexbright/avr/boards.txt 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 + + From 95570e6426330e438bea0df856f52a9886cd2c85 Mon Sep 17 00:00:00 2001 From: mcsarge Date: Fri, 8 Aug 2014 14:47:00 -0400 Subject: [PATCH 26/41] Add new program that is similar to up_n_down Similar to up_n_down, but adjustment of the light only happens when button is held, not when it is first turned on, so the saved brightness is used. Holding the button while on set the brightness and then it is used until reset again. --- .../set_and_remember/set_and_remember.ino | 408 ++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 programs/set_and_remember/set_and_remember.ino 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..755409b --- /dev/null +++ b/programs/set_and_remember/set_and_remember.ino @@ -0,0 +1,408 @@ +/* +Copyright (c) 2014, "Matthew Sargent" + +This code is a deriviative of the code up_n_down as compyrighted below: +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. +*/ + +/* +Description: + +Single click: turn on to previously stored level. +1 click: Turn light on (from off) at the saved level. +2 clicks: Blink +3 clicks: Nightlight mode, movement turns on using nightlight stored level +4 clicks: SOS using remembered level +5 clicks: locking + +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. + +*/ + + +#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 + +// 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 + + // 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 unsigned char nightlight_sensitivity = 20; // measured in 100's of a G. + +// 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; + +static char mode = MODE_OFF; +static char new_mode = MODE_OFF; +static char submode; + +static word nightlight_brightness; + +const word blink_freq_map[] = {70, 650, 10000}; // in ms +static word blink_frequency; // in ms; + +static byte locked; + +static word stored_brightness; + +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); + BIT_SET(bitreg,BLOCK_TURNING_OFF); + return i; + } + return -1; +} + +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; +} + +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(); + + + //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 and flashing, etc. + 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() { + const unsigned long 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(new_mode>=MODE_OFF) { + DBG(Serial.print("New mode: "); Serial.println((int)new_mode)); + + //The following causes the command after a paunse to switch the + //light OFF, even a valid one. For instance, while the light is on, you can not switch it into + //another mode, it just goes off. + //Also, the rest of the IF says if the light is locked, for it to remain locked and in mode OFF. + if(mode!=MODE_OFF || (locked && new_mode!=MODE_LOCKED) ) { + DBG(Serial.println("Forcing MODE_OFF")); + new_mode=MODE_OFF; + } + } + + // Do the actual mode change + if(new_mode>=MODE_OFF && new_mode!=mode) { // has the mdoe actually changed? + double d; + int i; + + // we're changing mode + switch(new_mode) { + + case MODE_LOCKED: + locked=!locked;//toggle the locked mode + updateEEPROM(EEPROM_LOCKED,locked); //remember if we are locked. + new_mode=MODE_OFF; //turn off the light even when exiting the locked mode. + /* fall through */ + case MODE_OFF: + //Glow mode means that we 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)?Serial.println("Stay alive"):Serial.println("Shut down")); + hb.set_light(CURRENT_LEVEL, BIT_CHECK(bitreg,GLOW_MODE)?0:OFF_LEVEL, NOW); + + //save the brightness that should be used when the light returns on from + //nightlight mode. + if(mode==MODE_NIGHTLIGHT) { + //DBG(Serial.print("Nightlight Brightness Saved: "); Serial.println(nightlight_brightness)); + updateEEPROM(EEPROM_NIGHTLIGHT_BRIGHTNESS, nightlight_brightness/4); + } + + //Turning off, so 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); + break; + + //remove this action because we want to light to just come on at the STORED level. + case MODE_LEVEL: //turning on the light while held at some angle. + //d = hb.difference_from_down(); + //i = MAX_LEVEL; + //if(d <= 0.40) { + //if(d <= 0.10) + // i = 1; + //else + // i = MAX_LOW_LEVEL; + //} + //hb.set_light(CURRENT_LEVEL, i, NOW); + 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(RLED, 100, 0, 1); + 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; + } + + int i = 0; + + + ///////////////////////////////////////////////////////////////// + // Check for mode and do in-mode activities + ///////////////////////////////////////////////////////////////// + switch(mode) { + case MODE_OFF: + // glow mode + 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)) { + 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: + //adjustLED(); + i = adjustLED(); + if(i>0) { + DBG(Serial.print("Stored Brightness: "); Serial.println(i)); + stored_brightness = i; + //The above stored brightness will be saved to EEPROM when we switch off the light + } + break; + + case MODE_NIGHTLIGHT: { + if(!hb.low_voltage_state()) + hb.set_led(RLED, 100, 0, 1); + if(hb.moved(nightlight_sensitivity)) { + //Serial.println("Nightlight Moved"); + treg1 = time; + hb.set_light(CURRENT_LEVEL, nightlight_brightness, 1000); + } else if(time > treg1 + nightlight_timeout) { + hb.set_light(CURRENT_LEVEL, 0, 1000); + } + 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)); + BIT_SET(bitreg,BLOCK_TURNING_OFF); + } + } + } + if(treg1+blink_frequency < time) { + treg1 = time; + hb.set_light(MAX_LEVEL, 0, 20); + } + 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; + + 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++; + } + + 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, stored_brightness, millisPerBeat * 3); + pattern >>= 1; + } + else + { // dot, by elimination + hb.set_light(MAX_LEVEL, stored_brightness, millisPerBeat); + pattern >>= 1; + } + symbols_remaining--; + } + break; + } + +} + From 9f7abb0b698236d4393a90bbe72451f0bba1a1b1 Mon Sep 17 00:00:00 2001 From: mcsarge Date: Sat, 9 Aug 2014 14:36:31 -0400 Subject: [PATCH 27/41] Near final change to the new program Added code to detect if the EEPROM has ever been initialized and initialize it if it has not. --- programs/set_and_remember/README.md | 40 +++++++++++ .../set_and_remember/set_and_remember.ino | 69 +++++++++++++------ 2 files changed, 88 insertions(+), 21 deletions(-) create mode 100644 programs/set_and_remember/README.md diff --git a/programs/set_and_remember/README.md b/programs/set_and_remember/README.md new file mode 100644 index 0000000..203ce36 --- /dev/null +++ b/programs/set_and_remember/README.md @@ -0,0 +1,40 @@ + +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 a everyday, useful program. It requires Dave Hilton's awesome hexbright library. I has additional code in it to solve the problem where it will go into lock mode if this is the first time it has ever been programmed. + +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 the stored light level. The level is stored in EEPROM so it remeber between runs. + * Once on, you can hold the button for more than 350ms and change its angle with the ground to adjust the brightness. Let go the button to lock in the current brightness and save it. 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 point and changing the angle of the light with the ground you can speed up or slow down the frequency of the light. + +* 3 Clicks-Nightlight mode + * When sitting still, the tailcap glows red. When picked up, the light turns on to the brightest setting. + * If not moved for 10 seconds it dims and the tailcap glows red again. + * Holding the button will allow adjustment of the brightness of the light. This brightness will be saved in EEPROM and be remembered when the light is turned off. + +* 4 Clicks-SOS mode + * 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. + +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. + +* If 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 index 755409b..4550dc2 100644 --- a/programs/set_and_remember/set_and_remember.ino +++ b/programs/set_and_remember/set_and_remember.ino @@ -1,7 +1,4 @@ /* -Copyright (c) 2014, "Matthew Sargent" - -This code is a deriviative of the code up_n_down as compyrighted below: Copyright (c) 2013, "Whitney Battestilli" All rights reserved. @@ -27,9 +24,12 @@ 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: -Single click: turn on to previously stored level. 1 click: Turn light on (from off) at the saved level. 2 clicks: Blink 3 clicks: Nightlight mode, movement turns on using nightlight stored level @@ -60,6 +60,7 @@ Click and Hold (while ON): #define EEPROM_LOCKED 0 #define EEPROM_NIGHTLIGHT_BRIGHTNESS 1 #define EEPROM_STORED_BRIGHTNESS 2 +#define EEPROM_SIGNATURE_LOC 3 // Modes #define MODE_OFF 0 @@ -72,8 +73,9 @@ Click and Hold (while ON): // 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 char* EEPROM_Signature = "Set_and_Remember"; // State static unsigned long treg1=0; @@ -93,7 +95,7 @@ static word nightlight_brightness; const word blink_freq_map[] = {70, 650, 10000}; // in ms static word blink_frequency; // in ms; -static byte locked; +static byte locked = false; static word stored_brightness; @@ -115,6 +117,16 @@ int adjustLED() { return -1; } +//Write the bytes contained in the array to the location in EEPROM starting +//at startAddr. WWrite numBytes number of bytes. +boolean writebytesEEPROM(int startAddr, const byte* array, int numBytes) { + int i; + for (i = 0; i < numBytes; i++) { + updateEEPROM(startAddr+i,array[i]); + } + return true; +} + byte updateEEPROM(word location, byte value) { byte c = EEPROM.read(location); if(c!=value) { @@ -124,13 +136,39 @@ 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; //Does not match EEPROM not initialized + } + + return true; //The signature was found... +} + 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 EEPRONM 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 mark the firsrun as false + 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); //DBG(Serial.print("Locked: "); Serial.println(locked)); @@ -138,13 +176,11 @@ void setup() { 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 and flashing, etc. 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); @@ -219,17 +255,8 @@ void loop() { updateEEPROM(EEPROM_STORED_BRIGHTNESS, stored_brightness/4); break; - //remove this action because we want to light to just come on at the STORED level. - case MODE_LEVEL: //turning on the light while held at some angle. - //d = hb.difference_from_down(); - //i = MAX_LEVEL; - //if(d <= 0.40) { - //if(d <= 0.10) - // i = 1; - //else - // i = MAX_LOW_LEVEL; - //} - //hb.set_light(CURRENT_LEVEL, i, NOW); + case MODE_LEVEL: + //just turn on the light to the saved level hb.set_light(CURRENT_LEVEL, stored_brightness, NOW); break; @@ -294,9 +321,9 @@ void loop() { BIT_CLEAR(bitreg,QUICKSTROBE); } break; + case MODE_LEVEL: - //adjustLED(); - i = adjustLED(); + i = adjustLED(); //Adjust the led and save it if(i>0) { DBG(Serial.print("Stored Brightness: "); Serial.println(i)); stored_brightness = i; From 9f5d92fc1e379c30731e01995a8e6949302aeafa Mon Sep 17 00:00:00 2001 From: mcsarge Date: Sun, 10 Aug 2014 09:17:43 -0400 Subject: [PATCH 28/41] Update comments and minor changes Fix some spelling errors and other minor things in the README. Additional comments and spelling changes in the program. --- programs/set_and_remember/README.md | 12 ++-- .../set_and_remember/set_and_remember.ino | 56 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/programs/set_and_remember/README.md b/programs/set_and_remember/README.md index 203ce36..eb1b8af 100644 --- a/programs/set_and_remember/README.md +++ b/programs/set_and_remember/README.md @@ -6,15 +6,15 @@ A modification to the excellent everyday program written by [wbattestilli](https Set_and_Remember ================ -Set_and_Remember is intended to be a everyday, useful program. It requires Dave Hilton's awesome hexbright library. I has additional code in it to solve the problem where it will go into lock mode if this is the first time it has ever been programmed. +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 couple of changes. Instead of setting the brightness when it is first turned on, it utilizes a stored level that was set and stored previously using the held click maneuver. It also has additional code in it to solve the problem where the lock is engaged the first time it is used after having been programmed. 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 the stored light level. The level is stored in EEPROM so it remeber between runs. - * Once on, you can hold the button for more than 350ms and change its angle with the ground to adjust the brightness. Let go the button to lock in the current brightness and save it. Pointing at the ground is the lowest setting and horizontal is the brightest setting. + * Will turn the light on the stored light level. The level is stored in EEPROM so it remember 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 the button to lock in the current brightness and save it. 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 @@ -28,13 +28,13 @@ Basic Operation * Holding the button will allow adjustment of the brightness of the light. This brightness will be saved in EEPROM and be remembered when the light is turned off. * 4 Clicks-SOS mode - * Light blinks SOS repeatededly in morse code + * Light blinks SOS repeatedly in Morse code * 5 Clicks-Lock mode * When locked, the light will not turn on. 5 clicks when locked will unlock the light. 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. +* 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. -* If 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. +* 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 index 4550dc2..374ff63 100644 --- a/programs/set_and_remember/set_and_remember.ino +++ b/programs/set_and_remember/set_and_remember.ino @@ -37,12 +37,11 @@ Description: 5 clicks: locking Click and Hold (while ON): - Set level; point down lowest, point horizon highest, save this level. + 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. */ - #include #include @@ -75,6 +74,8 @@ 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. + +// EEPROM signature used to verify EEPROM has been initialize static const char* EEPROM_Signature = "Set_and_Remember"; // State @@ -91,14 +92,13 @@ static char new_mode = MODE_OFF; static char submode; static word nightlight_brightness; +static word stored_brightness; const word blink_freq_map[] = {70, 650, 10000}; // in ms static word blink_frequency; // in ms; static byte locked = false; -static word stored_brightness; - hexbright hb; int adjustLED() { @@ -117,16 +117,17 @@ int adjustLED() { return -1; } -//Write the bytes contained in the array to the location in EEPROM starting -//at startAddr. WWrite numBytes number of bytes. -boolean writebytesEEPROM(int startAddr, const byte* array, int numBytes) { +//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]); } - return true; } +//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) { @@ -136,17 +137,16 @@ byte updateEEPROM(word location, byte value) { return value; } -//Check the EEPROM to see if the signature has been written, if found return true +//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; //Does not match EEPROM not initialized + return false; //EEPROM does not match Signature, so EEPROM not initialized } - - return true; //The signature was found... + return true; //The signature was found, so the EEPROM is initialized... } void setup() { @@ -156,19 +156,21 @@ void setup() { hb = hexbright(); hb.init_hardware(); - //The following code detects if the EEPRONM has been initialized - //if it has not, initialize it. + //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 mark the firsrun as false + //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)); @@ -176,10 +178,10 @@ void setup() { 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 and flashing, etc. + //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.print("Stored Brightness: "); Serial.println(stored_brightness)); DBG(Serial.println("Powered up!")); @@ -227,15 +229,14 @@ void loop() { // Do the actual mode change if(new_mode>=MODE_OFF && new_mode!=mode) { // has the mdoe actually changed? double d; - int i; // we're changing mode switch(new_mode) { case MODE_LOCKED: locked=!locked;//toggle the locked mode - updateEEPROM(EEPROM_LOCKED,locked); //remember if we are locked. - new_mode=MODE_OFF; //turn off the light even when exiting 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. /* fall through */ case MODE_OFF: //Glow mode means that we do not shutdown the uProcessor, this is done by @@ -281,14 +282,14 @@ void loop() { break; } mode=new_mode; - } - - int i = 0; - + } ///////////////////////////////////////////////////////////////// // Check for mode and do in-mode activities ///////////////////////////////////////////////////////////////// + + int i; + switch(mode) { case MODE_OFF: // glow mode @@ -341,6 +342,7 @@ void loop() { } else if(time > treg1 + nightlight_timeout) { hb.set_light(CURRENT_LEVEL, 0, 1000); } + i = adjustLED(); if(i>0) { //DBG(Serial.print("Nightlight Brightness: "); Serial.println(i)); @@ -430,6 +432,4 @@ void loop() { } break; } - -} - +} From 33699e722ad4b6bd04d01cbcd21634559595f958 Mon Sep 17 00:00:00 2001 From: mcsarge Date: Sun, 10 Aug 2014 16:58:39 -0400 Subject: [PATCH 29/41] Auto formatted --- .../set_and_remember/set_and_remember.ino | 238 ++++++++++-------- 1 file changed, 128 insertions(+), 110 deletions(-) diff --git a/programs/set_and_remember/set_and_remember.ino b/programs/set_and_remember/set_and_remember.ino index 374ff63..44555ca 100644 --- a/programs/set_and_remember/set_and_remember.ino +++ b/programs/set_and_remember/set_and_remember.ino @@ -1,46 +1,46 @@ /* 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. -*/ + 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 using nightlight stored level -4 clicks: SOS using remembered level -5 clicks: locking - -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. - -*/ + + 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 using nightlight stored level + 4 clicks: SOS using remembered level + 5 clicks: locking + + 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. + + */ #include #include @@ -69,7 +69,7 @@ Click and Hold (while ON): #define MODE_SOS 4 #define MODE_LOCKED 5 - // Defaults +// Defaults 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 @@ -94,7 +94,8 @@ static char submode; static word nightlight_brightness; static word stored_brightness; -const word blink_freq_map[] = {70, 650, 10000}; // in ms +const word blink_freq_map[] = { + 70, 650, 10000}; // in ms static word blink_frequency; // in ms; static byte locked = false; @@ -131,7 +132,10 @@ void writebytesEEPROM(int startAddr, const byte* array, int numBytes) { 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)); + DBG(Serial.print("Write to EEPROM:"); + Serial.print(value); + Serial.print("to loc: "); + Serial.println(location)); EEPROM.write(location, value); } return value; @@ -192,7 +196,7 @@ void loop() { const unsigned long time = millis(); hb.update(); - + #ifdef PRINTING_NUMBER: if(!hb.printing_number()) #endif @@ -203,7 +207,7 @@ void loop() { // 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); + hb.set_led(RLED,50,1000,1); } @@ -212,9 +216,10 @@ void loop() { ///////////////////////////////////////////////////////////////// // Get the click count new_mode = click_count(); - + if(new_mode>=MODE_OFF) { - DBG(Serial.print("New mode: "); Serial.println((int)new_mode)); + DBG(Serial.print("New mode: "); + Serial.println((int)new_mode)); //The following causes the command after a paunse to switch the //light OFF, even a valid one. For instance, while the light is on, you can not switch it into @@ -247,10 +252,10 @@ void loop() { //save the brightness that should be used when the light returns on from //nightlight mode. if(mode==MODE_NIGHTLIGHT) { - //DBG(Serial.print("Nightlight Brightness Saved: "); Serial.println(nightlight_brightness)); - updateEEPROM(EEPROM_NIGHTLIGHT_BRIGHTNESS, nightlight_brightness/4); + //DBG(Serial.print("Nightlight Brightness Saved: "); Serial.println(nightlight_brightness)); + updateEEPROM(EEPROM_NIGHTLIGHT_BRIGHTNESS, nightlight_brightness/4); } - + //Turning off, so 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); @@ -260,23 +265,24 @@ void loop() { //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)); + DBG(Serial.print("Nightlight Brightness: "); + Serial.println(nightlight_brightness)); #ifdef PRINTING_NUMBER: if(!hb.printing_number()) #endif - hb.set_led(RLED, 100, 0, 1); + hb.set_led(RLED, 100, 0, 1); 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]; + 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; @@ -287,7 +293,7 @@ void loop() { ///////////////////////////////////////////////////////////////// // Check for mode and do in-mode activities ///////////////////////////////////////////////////////////////// - + int i; switch(mode) { @@ -296,26 +302,30 @@ void loop() { 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)) { + } + else if(BIT_CHECK(bitreg,GLOW_MODE_JUST_CHANGED)) { 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); - } - + 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); @@ -326,44 +336,49 @@ void loop() { case MODE_LEVEL: i = adjustLED(); //Adjust the led and save it if(i>0) { - DBG(Serial.print("Stored Brightness: "); Serial.println(i)); + DBG(Serial.print("Stored Brightness: "); + Serial.println(i)); stored_brightness = i; //The above stored brightness will be saved to EEPROM when we switch off the light } break; - case MODE_NIGHTLIGHT: { - if(!hb.low_voltage_state()) - hb.set_led(RLED, 100, 0, 1); - if(hb.moved(nightlight_sensitivity)) { - //Serial.println("Nightlight Moved"); - treg1 = time; - hb.set_light(CURRENT_LEVEL, nightlight_brightness, 1000); - } else if(time > treg1 + nightlight_timeout) { - hb.set_light(CURRENT_LEVEL, 0, 1000); - } + case MODE_NIGHTLIGHT: + { + if(!hb.low_voltage_state()) + hb.set_led(RLED, 100, 0, 1); + if(hb.moved(nightlight_sensitivity)) { + //Serial.println("Nightlight Moved"); + treg1 = time; + hb.set_light(CURRENT_LEVEL, nightlight_brightness, 1000); + } + else if(time > treg1 + nightlight_timeout) { + hb.set_light(CURRENT_LEVEL, 0, 1000); + } - i = adjustLED(); - if(i>0) { - //DBG(Serial.print("Nightlight Brightness: "); Serial.println(i)); - nightlight_brightness = i; + i = adjustLED(); + if(i>0) { + //DBG(Serial.print("Nightlight Brightness: "); Serial.println(i)); + nightlight_brightness = i; + } + break; } - 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)); - BIT_SET(bitreg,BLOCK_TURNING_OFF); - } + 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)); + BIT_SET(bitreg,BLOCK_TURNING_OFF); + } } } if(treg1+blink_frequency < time) { @@ -379,12 +394,13 @@ void loop() { static char symbols_remaining = 0; static byte pattern = 0; const char message[] = "SOS"; - const word morse[] = { 0x0300, // S ... - 0x0307, // O --- - 0x0300, // S ... + const word morse[] = { + 0x0300, // S ... + 0x0307, // O --- + 0x0300, // S ... }; const int millisPerBeat = 150; - + if(current_character>=sizeof(message)) { // we've hit the end of message, turn off. //mode = MODE_OFF; @@ -393,17 +409,17 @@ void loop() { // 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; + // 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++; } - + if (symbols_remaining<=0) { // character was unrecognized, treat it as a space // 7 beats between words, but 3 have already passed @@ -433,3 +449,5 @@ void loop() { break; } } + + From 88d49ab191c0c38657aadd594ea3f76a0df7c36b Mon Sep 17 00:00:00 2001 From: mcsarge Date: Wed, 13 Aug 2014 17:55:01 -0400 Subject: [PATCH 30/41] Tail Flash Added a tail flash when entering and exiting locked mode so you know if you got it right. --- .../set_and_remember/set_and_remember.ino | 70 ++++++++++++------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/programs/set_and_remember/set_and_remember.ino b/programs/set_and_remember/set_and_remember.ino index 44555ca..4255441 100644 --- a/programs/set_and_remember/set_and_remember.ino +++ b/programs/set_and_remember/set_and_remember.ino @@ -34,7 +34,7 @@ Changes and modifications are Copyright (c) 2014, "Matthew Sargent" =MODE_OFF && new_mode!=mode) { // has the mdoe actually changed? double d; + //Clear tailflash 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) + tailflash = 0; + // we're changing mode switch(new_mode) { @@ -242,23 +246,32 @@ void loop() { 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... + tailflash = 3; + /* fall through */ case MODE_OFF: - //Glow mode means that we 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)?Serial.println("Stay alive"):Serial.println("Shut down")); - hb.set_light(CURRENT_LEVEL, BIT_CHECK(bitreg,GLOW_MODE)?0:OFF_LEVEL, NOW); - - //save the brightness that should be used when the light returns on from - //nightlight mode. - if(mode==MODE_NIGHTLIGHT) { - //DBG(Serial.print("Nightlight Brightness Saved: "); Serial.println(nightlight_brightness)); - updateEEPROM(EEPROM_NIGHTLIGHT_BRIGHTNESS, nightlight_brightness/4); - } + //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)||tailflash>0?Serial.println("Stay alive"):Serial.println("Shut down")); + if (BIT_CHECK(bitreg,GLOW_MODE)||tailflash>0) + hb.set_light(CURRENT_LEVEL, 0, NOW); + else { + //We are shutting down, so save off the values + //If we are in nightlight node, save the brightness that should be used when + //the light returns on from nightlight mode. + if(mode==MODE_NIGHTLIGHT) { + //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); - //Turning off, so 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); + //Shut her down... + hb.set_light(CURRENT_LEVEL, OFF_LEVEL, NOW); + } break; case MODE_LEVEL: @@ -298,8 +311,13 @@ void loop() { switch(mode) { case MODE_OFF: - // glow mode - if(BIT_CHECK(bitreg,GLOW_MODE)) { + if (tailflash > 0) { //flashing tail + if (hb.get_led_state(locked?RLED:GLED)==LED_OFF) { + tailflash--; + hb.set_led(locked?RLED:GLED, 300, 300, 255); + } + } + else if(BIT_CHECK(bitreg,GLOW_MODE)) { //glow mode hb.set_led(GLED, 100, 100, 64); hb.set_light(CURRENT_LEVEL, 0, NOW); } @@ -333,6 +351,7 @@ void loop() { } break; + case MODE_LEVEL: i = adjustLED(); //Adjust the led and save it if(i>0) { @@ -436,12 +455,12 @@ void loop() { } else if (pattern & 1) { // dash, 3 beats - hb.set_light(MAX_LEVEL, stored_brightness, millisPerBeat * 3); + hb.set_light(MAX_LEVEL, MAX_LEVEL, millisPerBeat * 3); pattern >>= 1; } else { // dot, by elimination - hb.set_light(MAX_LEVEL, stored_brightness, millisPerBeat); + hb.set_light(MAX_LEVEL, MAX_LEVEL, millisPerBeat); pattern >>= 1; } symbols_remaining--; @@ -451,3 +470,4 @@ void loop() { } + From e2a3721423b899214b46d75dc82971b4e0f121bf Mon Sep 17 00:00:00 2001 From: Matt Sargent Date: Wed, 13 Aug 2014 20:08:10 -0400 Subject: [PATCH 31/41] Update README.md --- programs/set_and_remember/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/set_and_remember/README.md b/programs/set_and_remember/README.md index eb1b8af..d9f46f4 100644 --- a/programs/set_and_remember/README.md +++ b/programs/set_and_remember/README.md @@ -31,7 +31,7 @@ Basic Operation * Light blinks SOS repeatedly 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. 5 clicks when locked will unlock the light. After locking or unlocking the switch leds will flash 3 times. Red for locked and green for unlocked. Extras ---------------- From fe1edacb3f5277a745a772230b45c43579f78337 Mon Sep 17 00:00:00 2001 From: mcsarge Date: Thu, 21 Aug 2014 15:33:44 -0400 Subject: [PATCH 32/41] SOS Rewrite Re write the way that SOS is flashed. Changed the nightlight to turn off the tail cap LED when the main light is on. Switch nightlight to use the green tail cap LED instead of the red. --- .../set_and_remember/set_and_remember.ino | 125 +++++++++--------- 1 file changed, 60 insertions(+), 65 deletions(-) diff --git a/programs/set_and_remember/set_and_remember.ino b/programs/set_and_remember/set_and_remember.ino index 4255441..4788627 100644 --- a/programs/set_and_remember/set_and_remember.ino +++ b/programs/set_and_remember/set_and_remember.ino @@ -30,15 +30,22 @@ Changes and modifications are Copyright (c) 2014, "Matthew Sargent" treg1 + nightlight_timeout) { hb.set_light(CURRENT_LEVEL, 0, 1000); + hb.set_led(GLED, 1000, 0, 64); } i = adjustLED(); @@ -407,63 +425,40 @@ 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; - - 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++; - } - - 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; + //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: + //<-S-> <----O-----> <-S-> <-iw-----> + //10101 000 11101110111 000 10101 00000 + // + //10101000 11101110 11100010 10100000 + // 0xA8 0xEE 0xE2 0xA0 + + //See if the last command to the main light is complete. + //This should happen every singleMorseBeat, except on the last bit + 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 a bit longer + int onOrOffTime = sos_cursor<31?singleMorseBeat:singleMorseBeat*4; + + 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; onOff#: "); Serial.println(onOff)); + sos_cursor++; + + if (sos_cursor>31 || sos_cursor<0) { + sos_cursor = 0; } - symbols_remaining--; } break; } From 199ebbb604a6e88cac453f55386571f5b46baa1a Mon Sep 17 00:00:00 2001 From: Matt Sargent Date: Thu, 21 Aug 2014 15:48:42 -0400 Subject: [PATCH 33/41] Update README.md --- programs/set_and_remember/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/programs/set_and_remember/README.md b/programs/set_and_remember/README.md index d9f46f4..888e272 100644 --- a/programs/set_and_remember/README.md +++ b/programs/set_and_remember/README.md @@ -23,15 +23,15 @@ Basic Operation * Holding the button and point and changing the angle of the light with the ground you can speed up or slow down the frequency of the light. * 3 Clicks-Nightlight mode - * When sitting still, the tailcap glows red. When picked up, the light turns on to the brightest setting. - * If not moved for 10 seconds it dims and the tailcap glows red again. - * Holding the button will allow adjustment of the brightness of the light. This brightness will be saved in EEPROM and be remembered when the light is turned off. + * When sitting still, the tailcap glows green. When picked up, the light turns on to the saved nightlight setting and the tail cap stops glowing. + * If not moved for 5 seconds the main light dims and the tailcap glows green again. + * Holding the button will allow adjustment of the brightness of the light. This brightness will be saved in EEPROM and be remembered when the light enters this mode again or is moved again. The level saved level is stored in EEPROM so it is used from run to run. * 4 Clicks-SOS mode - * Light blinks SOS repeatedly in Morse code + * Light blinks the morce code for SOS * 5 Clicks-Lock mode - * When locked, the light will not turn on. 5 clicks when locked will unlock the light. After locking or unlocking the switch leds will flash 3 times. Red for locked and green for unlocked. + * When locked, the light will not turn on. 5 clicks when locked will unlock the light. After locking or unlocking the tail cap leds will flash 3 times. Red for locked and green for unlocked. Extras ---------------- From ed950ba871994732b8c3a2a981d9b72a7965d388 Mon Sep 17 00:00:00 2001 From: mcsarge Date: Fri, 22 Aug 2014 17:03:45 -0400 Subject: [PATCH 34/41] More tuning to Mode SOS Changed the inter-word delay to be an exactly 7 beats and updated the comments to explain how the bitmap is used to create the dits and dahs. Also re-ordered all the constants at the top of the program. Removed unused setting of bit BLOCK_TURNING_OFF as it is never read by the program. Move the declaration of time to the start of the program and simple reset the time at each run through the loop. Replaced some of the DBG code so it will be compiled in when the appropriate define is set. Renamed tailflash to tailflashesLeft because it tells what the variable is really doing. --- .../set_and_remember/set_and_remember.ino | 101 +++++++++--------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/programs/set_and_remember/set_and_remember.ino b/programs/set_and_remember/set_and_remember.ino index 4788627..9740f4d 100644 --- a/programs/set_and_remember/set_and_remember.ino +++ b/programs/set_and_remember/set_and_remember.ino @@ -69,22 +69,24 @@ Changes and modifications are Copyright (c) 2014, "Matthew Sargent" =MODE_OFF && new_mode!=mode) { // has the mdoe actually changed? double d; - //Clear tailflash if mode is not OFF. + //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) - tailflash = 0; + tailflashesLeft = 0; - // we're changing mode + // Do the preliminary work when switching to a new mode first switch(new_mode) { case MODE_LOCKED: @@ -258,14 +254,14 @@ void loop() { 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... - tailflash = 3; + tailflashesLeft = 3; /* fall through */ case MODE_OFF: //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)||tailflash>0?Serial.println("Stay alive"):Serial.println("Shut down")); - if (BIT_CHECK(bitreg,GLOW_MODE)||tailflash>0) + 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 { //We are shutting down, so save off the values @@ -301,9 +297,11 @@ void loop() { 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(); @@ -321,16 +319,16 @@ void loop() { } ///////////////////////////////////////////////////////////////// - // Check for mode and do in-mode activities + // Done with new mode activities. Now do the + // stuff that we do while in a mode. ///////////////////////////////////////////////////////////////// - int i; switch(mode) { case MODE_OFF: - if (tailflash > 0) { //flashing tail + if (tailflashesLeft > 0) { //flashing tail if (hb.get_led_state(locked?RLED:GLED)==LED_OFF) { - tailflash--; + tailflashesLeft--; hb.set_led(locked?RLED:GLED, 300, 300, 255); } } @@ -372,8 +370,7 @@ void loop() { case MODE_LEVEL: i = adjustLED(); //Adjust the led and save it if(i>0) { - DBG(Serial.print("Stored Brightness: "); - Serial.println(i)); + DBG(Serial.print("Stored Brightness: "); Serial.println(i)); stored_brightness = i; //The above stored brightness will be saved to EEPROM when we switch off the light } @@ -381,9 +378,8 @@ void loop() { case MODE_NIGHTLIGHT: { - if(hb.moved(nightlight_sensitivity)) { - //Serial.println("Nightlight Moved"); + 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... @@ -395,7 +391,7 @@ void loop() { i = adjustLED(); if(i>0) { - //DBG(Serial.print("Nightlight Brightness: "); Serial.println(i)); + DBG(Serial.print("Nightlight Brightness: "); Serial.println(i)); nightlight_brightness = i; } break; @@ -413,8 +409,7 @@ void loop() { 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)); - BIT_SET(bitreg,BLOCK_TURNING_OFF); + DBG(Serial.print("Blink Freq: "); Serial.println(blink_frequency)); } } } @@ -433,30 +428,34 @@ void loop() { //Inter character = 3 beats = //Inter word = 7 beats = - //From left to right, the pattern is encoded: - //<-S-> <----O-----> <-S-> <-iw-----> - //10101 000 11101110111 000 10101 00000 + //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 10100000 + //10101000 11101110 11100010 1010xxxx // 0xA8 0xEE 0xE2 0xA0 - //See if the last command to the main light is complete. - //This should happen every singleMorseBeat, except on the last bit + //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 a bit longer - int onOrOffTime = sos_cursor<31?singleMorseBeat:singleMorseBeat*4; + //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; onOff#: "); Serial.println(onOff)); + DBG(Serial.print("SOS Data; onOffAmount#: "); Serial.println(onOffAmount)); + DBG(Serial.print("SOS Data; onOrOffTime: "); Serial.println(onOrOffTime)); sos_cursor++; - if (sos_cursor>31 || sos_cursor<0) { + if (sos_cursor>27 || sos_cursor<0) { sos_cursor = 0; } } From b2e1807292cf969a2da83079fcc7e965f7a972fd Mon Sep 17 00:00:00 2001 From: mcsarge Date: Fri, 29 Aug 2014 15:03:51 -0400 Subject: [PATCH 35/41] New feature of Locked Mode and documentation updates. Added code that causes the light to flash the tail cap LED 2 times if the light is in locked mode and the button is pressed. --- programs/set_and_remember/README.md | 23 +++++++++++-------- .../set_and_remember/set_and_remember.ino | 16 +++++++++---- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/programs/set_and_remember/README.md b/programs/set_and_remember/README.md index 888e272..d253870 100644 --- a/programs/set_and_remember/README.md +++ b/programs/set_and_remember/README.md @@ -6,32 +6,37 @@ A modification to the excellent everyday program written by [wbattestilli](https 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 couple of changes. Instead of setting the brightness when it is first turned on, it utilizes a stored level that was set and stored previously using the held click maneuver. It also has additional code in it to solve the problem where the lock is engaged the first time it is used after having been programmed. +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 turn 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. 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 the stored light level. The level is stored in EEPROM so it remember 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 the button to lock in the current brightness and save it. Pointing at the ground is the lowest setting and horizontal is the brightest setting. + * 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 point and changing the angle of the light with the ground you can speed up or slow down the frequency of the light. + * 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 light turns on to the saved nightlight setting and the tail cap stops glowing. - * If not moved for 5 seconds the main light dims and the tailcap glows green again. - * Holding the button will allow adjustment of the brightness of the light. This brightness will be saved in EEPROM and be remembered when the light enters this mode again or is moved again. The level saved level is stored in EEPROM so it is used from run to run. + * 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 morce code for SOS + * 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. 5 clicks when locked will unlock the light. After locking or unlocking the tail cap leds will flash 3 times. Red for locked and green for unlocked. + * 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/set_and_remember/set_and_remember.ino b/programs/set_and_remember/set_and_remember.ino index 9740f4d..2350292 100644 --- a/programs/set_and_remember/set_and_remember.ino +++ b/programs/set_and_remember/set_and_remember.ino @@ -228,13 +228,19 @@ void loop() { DBG(Serial.print("New mode: "); Serial.println((int)new_mode)); - //The following causes any number of clicks to switch the light OFF when it is currently ON. - //For instance, while the light is on, you can not switch it into another mode, it just goes off. - //Also, this if tests if the light is currently locked, if it is, remain locked and in mode OFF. - if(mode!=MODE_OFF || (locked && new_mode!=MODE_LOCKED) ) { + //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 From 4cf173de8f07eda3a1e947928f4520d10b70ccce Mon Sep 17 00:00:00 2001 From: Matt Sargent Date: Fri, 29 Aug 2014 15:29:28 -0400 Subject: [PATCH 36/41] Update README.md --- programs/set_and_remember/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/programs/set_and_remember/README.md b/programs/set_and_remember/README.md index d253870..7360dfc 100644 --- a/programs/set_and_remember/README.md +++ b/programs/set_and_remember/README.md @@ -7,11 +7,12 @@ 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 turn 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. + +* 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 turn 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. Basic Operation ---------------- From 57e730a5a6535629837cac07731a46281f904079 Mon Sep 17 00:00:00 2001 From: Matt Sargent Date: Fri, 29 Aug 2014 15:34:31 -0400 Subject: [PATCH 37/41] Various Documentation Updates --- programs/set_and_remember/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/set_and_remember/README.md b/programs/set_and_remember/README.md index 7360dfc..722ff8b 100644 --- a/programs/set_and_remember/README.md +++ b/programs/set_and_remember/README.md @@ -10,9 +10,9 @@ Set_and_Remember is intended to be an everyday, useful program. It requires Dav * 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 turn it off when the main light is on. +* 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. +* 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 ---------------- From 2e42d1f2da56e6279d3f422729aefe83a300924a Mon Sep 17 00:00:00 2001 From: Whitney Battestilli Date: Sat, 30 Aug 2014 23:41:02 -0400 Subject: [PATCH 38/41] port new lock mode and sos mode from set_and_remember --- programs/up_n_down/README.md | 2 +- programs/up_n_down/up_n_down.ino | 199 +++++++++++++++++++------------ 2 files changed, 127 insertions(+), 74 deletions(-) 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 index c6f6f90..7c2ebf6 100755 --- 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 @@ -39,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 @@ -51,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; @@ -73,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() { @@ -85,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) { @@ -102,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); @@ -122,7 +159,7 @@ void setup() { } void loop() { - const unsigned long time = millis(); + time = millis(); hb.update(); @@ -141,29 +178,50 @@ void loop() { // Get the click count new_mode = 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) ) { + + 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")); @@ -192,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]; @@ -210,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)) { @@ -271,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); } } } @@ -281,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; } } - + From cab50c3c02719beac6f9dc3f9fe5e8cc52559fd2 Mon Sep 17 00:00:00 2001 From: mcsarge Date: Tue, 9 Sep 2014 00:42:32 -0400 Subject: [PATCH 39/41] Fixed the shutdown error Moved the shutdown code to the mode maintenance so that shutdown will happen after the flashes finish. Needed to add a shutdown delay upon startup to keep the unit from shutting down right after waking up. --- .../set_and_remember/set_and_remember.ino | 97 +++++++++---------- 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/programs/set_and_remember/set_and_remember.ino b/programs/set_and_remember/set_and_remember.ino index 2350292..a8cdb2e 100644 --- a/programs/set_and_remember/set_and_remember.ino +++ b/programs/set_and_remember/set_and_remember.ino @@ -33,16 +33,16 @@ Changes and modifications are Copyright (c) 2014, "Matthew Sargent" if(mode != MODE_OFF && hb.low_voltage_state()) if(hb.get_led_state(RLED)==LED_OFF) hb.set_led(RLED,50,1000,1); @@ -224,18 +223,20 @@ void loop() { // 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 converted to a MODE_OFF. - //While the light is on, you can not switch it into another mode, it just goes off. + //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; - - //The following keeps the light locked and off until the light is unlocked. - //Flash the tailcap 2 times to indicate the light is locked. + + //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.")); @@ -243,12 +244,12 @@ void loop() { } } - // Do the actual mode change - if(new_mode>=MODE_OFF && new_mode!=mode) { // has the mdoe actually changed? + //Process if this is really a mode change. + if(new_mode>=MODE_OFF && new_mode!=mode) { double d; - //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 + //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; @@ -261,34 +262,15 @@ void loop() { new_mode=MODE_OFF; //keep the light off when entering or exiting lock mode. //Setup the tail light to flash... tailflashesLeft = 3; + break; - /* fall through */ case MODE_OFF: - //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 { - //We are shutting down, so save off the values - //If we are in nightlight node, save the brightness that should be used when - //the light returns on from nightlight mode. - if(mode==MODE_NIGHTLIGHT) { - //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); - - //Shut her down... - hb.set_light(CURRENT_LEVEL, OFF_LEVEL, NOW); - } + //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 + //Just turn on the light to the saved level hb.set_light(CURRENT_LEVEL, stored_brightness, NOW); break; @@ -303,7 +285,7 @@ void loop() { 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); @@ -331,18 +313,34 @@ void loop() { 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(BIT_CHECK(bitreg,GLOW_MODE_JUST_CHANGED)) { + 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); } @@ -351,7 +349,7 @@ void loop() { double d = hb.difference_from_down(); if(BIT_CHECK(bitreg,QUICKSTROBE) || (hb.button_pressed_time() > click - && d > 0.10 )) { + && d > 0.10 )) { BIT_SET(bitreg,QUICKSTROBE); if(treg1+blink_freq_map[0] < time) { treg1 = time; @@ -370,20 +368,19 @@ void loop() { 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("Stored Brightness: "); Serial.println(i)); + DBG(Serial.print("New Stored Brightness: "); Serial.println(i)); stored_brightness = i; - //The above stored brightness will be saved to EEPROM when we switch off the light } break; case MODE_NIGHTLIGHT: - { if(hb.moved(nightlight_sensitivity)) { DBG(Serial.println("Nightlight Moved")); treg1 = time; @@ -401,7 +398,6 @@ void loop() { nightlight_brightness = i; } break; - } case MODE_BLINK: if(hb.button_pressed()) { @@ -453,9 +449,9 @@ void loop() { //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)); @@ -471,3 +467,4 @@ void loop() { + From 5a22333ea3e4fd5d505952a75acb147225a75752 Mon Sep 17 00:00:00 2001 From: mcsarge Date: Tue, 9 Sep 2014 00:45:14 -0400 Subject: [PATCH 40/41] Documentation Simple documentation update. --- programs/set_and_remember/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/set_and_remember/README.md b/programs/set_and_remember/README.md index 722ff8b..340e6b6 100644 --- a/programs/set_and_remember/README.md +++ b/programs/set_and_remember/README.md @@ -37,7 +37,7 @@ Basic Operation * 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 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. + * 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 ---------------- From 6360910e79a25f00fa628da029adf63cd4744c33 Mon Sep 17 00:00:00 2001 From: DiHydro Date: Sun, 11 Jan 2026 13:44:30 -0600 Subject: [PATCH 41/41] Upload MCU pinout Found the MCU pinout on the hexbright website using the Wayback machine. Uploading here for posterity. --- hardware/hexbright/HexBright-Schematic-MCU.jpg | Bin 0 -> 152583 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 hardware/hexbright/HexBright-Schematic-MCU.jpg diff --git a/hardware/hexbright/HexBright-Schematic-MCU.jpg b/hardware/hexbright/HexBright-Schematic-MCU.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8fd55ee7d70a797fc51e04a3afdc07f6f5ab24f2 GIT binary patch literal 152583 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