From db2aca00ffe7b3cb1b9ec99705c2d4373163dfef Mon Sep 17 00:00:00 2001 From: mike-s123 Date: Mon, 31 Dec 2012 13:57:14 -0800 Subject: [PATCH] Create ms44 Click = quick (<0.2s) press (on/off) Press = brief (0.2-1s) press (change to next mode) Long press = 1-2s press, light will fast blink (set brightness for this mode, when off switch sets) Extended = 2+ seconds, light will slow blink (start with this mode) Click turn on/off, starts with last saved setting. When on, Extended press saves current mode as startup mode. Long press adjusts brightness (hold horizontally, twist, click to set). There are two mode sets - constant (e.g. different brightness levels), and dynamic (e.g. different flashing patterns). To get into the opposite set (constant vs. dynamic), Long press from off. Constant on set (high uses high current mode): Press to cycle through low, moon, medium, and high modes. Dynamic set (these always use high current mode): Press to cycle through dazzle, blink (2 Hz), beacon (0,1 Hz, blinks red tailcap LED 1 Hz), and variable* modes. *Variable mode gets bright when the flashlight is held horizontally, dim when vertical. --- ms44 | 821 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 821 insertions(+) create mode 100644 ms44 diff --git a/ms44 b/ms44 new file mode 100644 index 0000000..33e0a72 --- /dev/null +++ b/ms44 @@ -0,0 +1,821 @@ +/* + + MS44 firmware for HexBright FLEX + v1.5 Dec 31, 2012 + + MS program. + + Click = quick (<0.2s) press (on/off) + Press = brief (0.2-1s) press (change to next mode) + Long press = 1-2s press, light will fast blink (set brightness for this mode, when off switch sets) + Extended = 2+ seconds, light will slow blink (start with this mode) + + Click turn on/off, starts with last saved setting. When on, Extended press saves + current mode as startup mode. Long press adjusts brightness + (hold horizontally, twist, click to set). + + There are two mode sets - constant (e.g. different + brightness levels), and dynamic (e.g. different flashing patterns). To get into + the opposite set (constant vs. dynamic), Long press from off. + + Constant on set (high uses high current mode): + Press to cycle through low, moon, medium, and high modes. + + Dynamic set (these always use high current mode): + Press to cycle through dazzle, blink (2 Hz), beacon + (0,1 Hz, blinks red tailcap LED 1 Hz), and variable* modes. + + *Variable mode gets bright when the flashlight is held horizontally, dim when vertical. + + 1.5 Added variable mode. + 1.4 Can do settings in all modes, sets switch with startmode. + 1.3 Accel back off when not used (needed to wait before use). Allow setting dynamic as startmode. + 1.2 Added flashing for setting mode, moved code around for memory efficiency, escape prints + with DEBUG define. Accel now on all the time (only ~0.25 mA). Glowing charge light. + 1.1 EEPROM signature, turn off accel when not used. + 1.0 Initial +*/ +/* + * BOF preprocessor bug prevent + * insert me on top of your arduino-code + */ +#define nop() __asm volatile ("nop") +#if 1 +nop(); +#endif +/* + * EOF preprocessor bug prevent +*/ + +//#define DEBUG 1 +//#define MEMCHK 1 + +#include +#include +#include + +// Settings + +#define OVERTEMP 340 +// Accelerometer +#define ACC_ADDRESS 0x4C +#define ACC_REG_XOUT 0 +#define ACC_REG_YOUT 1 +#define ACC_REG_ZOUT 2 +#define ACC_REG_TILT 3 +#define ACC_REG_SRST 4 +#define ACC_REG_INTS 6 +#define ACC_REG_MODE 7 +#define ACC_REG_SR 8 +// Pin assignments +#define DPIN_RLED_SW 2 +#define DPIN_GLED 5 +#define DPIN_PWR 8 // to latch power on +#define DPIN_DRV_CURRENT 9 // high or low current mode +#define DPIN_DRV_EN 10 // modulate this for brightness +#define DPIN_ACC_INT 3 +#define APIN_TEMP 0 +#define APIN_CHARGE 3 +// Modes, init modes must be even +#define MODE_OFF 0 +#define MODE_L1_INIT 2 +#define MODE_L1 3 +#define MODE_L2_INIT 4 +#define MODE_L2 5 +#define MODE_L3_INIT 6 +#define MODE_L3 7 +#define MODE_HIGH_INIT 8 +#define MODE_HIGH 9 +#define SEQ_DYN_START 10 //first dynamic mode +#define MODE_DAZZLE_INIT 10 +#define MODE_DAZZLE 11 +#define MODE_BLINKING_INIT 12 +#define MODE_BLINKING 13 +#define MODE_BEACON_INIT 14 +#define MODE_BEACON 15 +#define MODE_VAR_INIT 16 +#define MODE_VAR 17 +// press lengths in ms +#define PRESS_S 200 //short press +#define PRESS_L 1000 //long press +#define PRESS_XL 2000 //extra long press +#define PRESS_RESET 10000 +// Default brightness and modes +#define DEF_L1 64 // 50 lm, 30 hr +#define DEF_L2 8 // guess - around 10 lm, 240 hr +#define DEF_L3 127 // 255 is 150 lm, 8 hr +#define DEF_HIGH 255 // 500 lm, 1 hr +#define DEF_DZ 255 +#define DEF_BL 255 +#define DEF_BE 255 +#define DEF_CONSTANT MODE_L1 +#define DEF_DYNAMIC MODE_DAZZLE +// EEPROM +#define EE_CKSUM 0 +#define EE_STARTMODE 511 +#define EE_L1 1 +#define EE_L2 2 +#define EE_L3 3 +#define EE_HIGH 4 +#define EE_DZ 5 +#define EE_BL 6 +#define EE_BE 7 +#define EE_SIG1 509 +#define SIG1 'M' +#define EE_SIG2 510 +#define SIG2 'S' + + +#if MEMCHK +int freeRam () { + extern int __heap_start, *__brkval; + int v; + return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); +} +#endif + +// State +byte mode = MODE_OFF, bright_L1 = DEF_L1, bright_L2 = DEF_L2; +byte bright_L3 = DEF_L3, bright_high = DEF_HIGH, bright_dz = DEF_DZ; +byte bright_bl = DEF_BL, bright_be = DEF_BE, bright_curr; +byte startMode = MODE_L1, newMode; +unsigned long btnTime = 0, time, bright_var; +boolean btnDown, btnDownBefore; + +void setup() +{ + // We just powered on! That means either we got plugged + // into USB, or the user is pressing the power button. + + pinMode(DPIN_PWR, INPUT); + digitalWrite(DPIN_PWR, LOW); + + // Initialize GPIO + pinMode(DPIN_RLED_SW, INPUT); + pinMode(DPIN_GLED, OUTPUT); + pinMode(DPIN_DRV_CURRENT, OUTPUT); + pinMode(DPIN_DRV_EN, OUTPUT); + digitalWrite(DPIN_DRV_CURRENT, LOW); + digitalWrite(DPIN_DRV_EN, LOW); + + // Initialize serial busses +#if MEMCHK +Serial.begin(9600); +#endif +// #if DEBUG +Serial.begin(9600); +// #endif + + Wire.begin(); + + btnTime = millis(); + mode = MODE_OFF; + + // get saved values from EEPROM + byte sig1 = EEPROM.read(EE_SIG1); + byte sig2 = EEPROM.read(EE_SIG2); + byte eeChk = EEPROM.read(EE_CKSUM); + byte chk =0; + for (int i = 1; i < 512; i++) { + chk = chk + EEPROM.read(i); + } + // init eeprom if checksum or signature doesn't match + if ( eeChk != chk || sig1 != SIG1 || sig2 != SIG2 ) { + // clear eeprom + ee_init(); + } else { + bright_L1 = EEPROM.read(EE_L1); + bright_L2 = EEPROM.read(EE_L2); + bright_L3 = EEPROM.read(EE_L3); + bright_high = EEPROM.read(EE_HIGH); + bright_dz = EEPROM.read(EE_DZ); + bright_bl = EEPROM.read(EE_BL); + bright_be = EEPROM.read(EE_BE); + startMode = EEPROM.read(EE_STARTMODE); +#if DEBUG + Serial.print("Read EEPROM OK"); +#endif + } + + // Configure accelerometer + byte config[] = { + ACC_REG_INTS, // First register (see next line) + 0x00, // no interrupts +// 0xE4, // Interrupts: shakes, taps + 0x00, // Mode: not enabled yet + 0x00, // Sample rate: 120 Hz + 0x0F, // Tap threshold + 0x10 // Tap debounce samples + }; + Wire.beginTransmission(ACC_ADDRESS); + Wire.write(config, sizeof(config)); + Wire.endTransmission(); + // Leave disabled until needed +// TCCR1B = TCCR1B & 0b11111011; + mode = MODE_OFF; + btnDownBefore = false; + Serial.println("Poweron"); +} + +void loop() +{ + static unsigned long lastTempTime, lastTime, lastAccTime; + static int angleY; + byte bright; + static boolean seqSwitch=false; + + /* Check for mode changes, btnDown is the current button state, + btnDownBefore is what it was last time through. + So, + (btnDownBefore && btnDown) button still pressed + (!btnDownBefore && !btnDown) button still not pressed + (btnDownBefore && !btnDown) button just released + (!btnDownBefore && btnDown) button just pressed + */ + time = millis(); + newMode = mode; + btnDown = digitalRead(DPIN_RLED_SW); + +// handle on/off for all modes + if (mode == MODE_OFF) { // off, turn on? + if (btnDownBefore && !btnDown ) { + // turn on upon release + newMode = startMode; + } + if (btnDownBefore && btnDown && (time-btnTime)>PRESS_L) { + // button being held, switch between constant/dynamic sequences + seqSwitch = true; + if (startMode < SEQ_DYN_START) { + // switch between constant & dynamic + newMode = DEF_DYNAMIC; + } else { + newMode = DEF_CONSTANT; + } + } + } else { // on, so turn off + if (btnDownBefore && !btnDown && (time-btnTime)PRESS_L && (time-btnTime)PRESS_XL && (time-btnTime)PRESS_S-1 && (time-btnTime)PRESS_L-1 && (time-btnTime)PRESS_XL-1 ) { + setStartmode(MODE_L1); + } + break; + + case MODE_L2_INIT: + // This mode exists just to ignore this button release. + if (!btnDown) { + newMode = MODE_L2; + } + break; + case MODE_L2: + if (!btnDown && (time-btnTime)>PRESS_S && (time-btnTime)PRESS_L-1 && (time-btnTime)PRESS_XL-1 ) { + setStartmode(MODE_L2); + } + break; + + case MODE_L3_INIT: + // This mode exists just to ignore this button release. + if (!btnDown) { + newMode = MODE_L3; + } + break; + case MODE_L3: + if (!btnDown && (time-btnTime)>PRESS_S-1 && (time-btnTime)PRESS_L-1 && (time-btnTime)PRESS_XL-1 ) { + setStartmode(MODE_L3); + } + break; + + case MODE_HIGH_INIT: + // This mode exists just to ignore this button release. + if (!btnDown) { + newMode = MODE_HIGH; + } + if (!btnDown && (time-btnTime)>PRESS_XL ) { + setStartmode(MODE_L3); + } + break; + case MODE_HIGH: + if (!btnDown && (time-btnTime)>PRESS_S-1 && (time-btnTime)PRESS_L-1 && (time-btnTime)PRESS_XL ) { + setStartmode(MODE_HIGH); + } + break; + +// +// blinky modes +// + + case MODE_DAZZLE_INIT: + // This mode exists just to ignore this button release. + if (!btnDown) { + newMode = MODE_DAZZLE; + } + if (!btnDown && (time-btnTime)>PRESS_L-1 && (time-btnTime)PRESS_XL ) { + setStartmode(MODE_VAR); + } + break; + case MODE_DAZZLE: + if (btnDown && (time-btnTime)>PRESS_S) + newMode = MODE_BLINKING_INIT; + break; + + case MODE_BLINKING_INIT: + // This mode exists just to ignore this button release. + if (!btnDown) { + newMode = MODE_BLINKING; + } + if (!btnDown && (time-btnTime)>PRESS_L-1 && (time-btnTime)PRESS_XL ) { // button held, set startMode to previous + setStartmode(MODE_DAZZLE); + } + break; + + case MODE_BLINKING: + if (btnDown && (time-btnTime)>PRESS_S) + newMode = MODE_BEACON_INIT; + break; + + case MODE_BEACON_INIT: + // This mode exists just to ignore this button release. + if (!btnDown) { + newMode = MODE_BEACON; + } + if (!btnDown && (time-btnTime)>PRESS_L-1 && (time-btnTime)PRESS_XL ) { + setStartmode(MODE_BLINKING); + } + break; + case MODE_BEACON: + if (btnDown && (time-btnTime)>PRESS_S) + newMode = MODE_VAR_INIT; + break; + + case MODE_VAR_INIT: + // This mode exists just to ignore this button release. + if (!btnDown) { + newMode = MODE_VAR; + } + if (!btnDown && (time-btnTime)>PRESS_L-1 && (time-btnTime)PRESS_XL ) { + setStartmode(MODE_BEACON); + } + break; + case MODE_VAR: + if (btnDown && (time-btnTime)>PRESS_S) + newMode = MODE_DAZZLE_INIT; + break; + + } // switch(mode) + } // if(!seqSwitch) + } // if(btnDownBefore) + + // + // Do dynamic modes + // + if ( !btnDown || (time-btnTime)angleY) angleY++ ; + if (i20) angleY = 20; + if (angleY<1) angleY = 1; + analogWrite(DPIN_DRV_EN, map(angleY,20,0,4,255)); + delay(60); + break; + } //switch(mode) - dynamic + } + + // Do the mode transitions + if (newMode != mode) { + switch (newMode) { + case MODE_OFF: +#if DEBUG + Serial.println("Mode=off"); +#endif + pinMode(DPIN_PWR, OUTPUT); + digitalWrite(DPIN_PWR, LOW); + digitalWrite(DPIN_DRV_CURRENT, LOW); + digitalWrite(DPIN_DRV_EN, LOW); + break; + case MODE_L1_INIT: + case MODE_L1: +#if DEBUG + Serial.println("Mode=L1"); +#endif + pinMode(DPIN_PWR, OUTPUT); + digitalWrite(DPIN_PWR, HIGH); + digitalWrite(DPIN_DRV_CURRENT, LOW); + analogWrite(DPIN_DRV_EN, bright_L1); + bright_curr = bright_L1; + break; + case MODE_L2: +#if DEBUG + Serial.println("Mode=L2"); +#endif + pinMode(DPIN_PWR, OUTPUT); + digitalWrite(DPIN_PWR, HIGH); + digitalWrite(DPIN_DRV_CURRENT, LOW); + analogWrite(DPIN_DRV_EN, bright_L2); + bright_curr = bright_L2; + break; + case MODE_L3: +#if DEBUG + Serial.println("Mode=L3"); +#endif + pinMode(DPIN_PWR, OUTPUT); + digitalWrite(DPIN_PWR, HIGH); + digitalWrite(DPIN_DRV_CURRENT, LOW); + analogWrite(DPIN_DRV_EN, bright_L3); + bright_curr = bright_L3; + break; + case MODE_HIGH: +#if DEBUG + Serial.println("Mode=HI"); +#endif + pinMode(DPIN_PWR, OUTPUT); + digitalWrite(DPIN_PWR, HIGH); + digitalWrite(DPIN_DRV_CURRENT, HIGH); + analogWrite(DPIN_DRV_EN, bright_high); + bright_curr = bright_high; + break; + case MODE_DAZZLE: + case MODE_DAZZLE_INIT: +#if DEBUG + Serial.println("Mode=DZ"); +#endif + pinMode(DPIN_PWR, OUTPUT); + digitalWrite(DPIN_PWR, HIGH); + analogWrite(DPIN_DRV_CURRENT, bright_dz); + bright_curr = bright_dz; + break; + case MODE_BLINKING: + case MODE_BLINKING_INIT: +#if DEBUG + Serial.println("Mode=BL"); +#endif + pinMode(DPIN_PWR, OUTPUT); + digitalWrite(DPIN_PWR, HIGH); + analogWrite(DPIN_DRV_CURRENT, bright_bl); + bright_curr = bright_bl; + break; + case MODE_BEACON: + case MODE_BEACON_INIT: +#if DEBUG + Serial.println("Mode=BE"); +#endif + pinMode(DPIN_PWR, OUTPUT); + digitalWrite(DPIN_PWR, HIGH); + analogWrite(DPIN_DRV_CURRENT, bright_be); + bright_curr = bright_be; + break; + case MODE_VAR: + case MODE_VAR_INIT: +//#if DEBUG + Serial.println("Mode=VA"); +//#endif + pinMode(DPIN_PWR, OUTPUT); + digitalWrite(DPIN_PWR, HIGH); + digitalWrite(DPIN_DRV_CURRENT, LOW); + analogWrite(DPIN_DRV_EN, 4); + bright_curr = 127; + break; + } //switch + mode = newMode; + } // if newMode change + + // Check the state of the charge controller + int chargeState = analogRead(APIN_CHARGE); + if (chargeState < 128) { // Low - charging + analogWrite(DPIN_GLED,int(map(time%2000,0,2000,20,200))); + } + else if (chargeState > 768) { // High - charged + digitalWrite(DPIN_GLED, HIGH); + } + else { // Hi-Z - shutdown + digitalWrite(DPIN_GLED, LOW); + } + + // Check the temperature sensor + if (time-lastTempTime > 10000) { + lastTempTime = time; + int temperature = analogRead(APIN_TEMP); +#if MEMCHK + Serial.println("\n[memCheck]"); + Serial.println(freeRam()); +#endif +#if DEBUG + Serial.print("Temp:"); + Serial.println(temperature); +#endif + if (temperature > OVERTEMP && mode != MODE_OFF) { +#if DEBUG + Serial.println("Overheat!"); +#endif + digitalWrite(DPIN_DRV_CURRENT, LOW); + mode = MODE_L1; + } + } + + // Check if the accelerometer wants to interrupt + // not used for now +/* byte tapped = 0, shaked = 0; + accelOnOff(true); // make sure it's powered up + if (!digitalRead(DPIN_ACC_INT)) { + Wire.beginTransmission(ACC_ADDRESS); + Wire.write(ACC_REG_TILT); + Wire.endTransmission(false); // End, but do not stop! + Wire.requestFrom(ACC_ADDRESS, 1); // This one stops. + byte tilt = Wire.read(); + if (time-lastAccTime > 500) { + lastAccTime = time; + tapped = !!(tilt & 0x20); + shaked = !!(tilt & 0x80); + if (tapped) Serial.println("Tap!"); + if (shaked) Serial.println("Shake!"); + } + } */ + + // Periodically pull down the button's pin, since + // in certain hardware revisions it can float. + pinMode(DPIN_RLED_SW, OUTPUT); + pinMode(DPIN_RLED_SW, INPUT); + // Remember button state so we can detect transitions, + // this should be the last thing we do in loop() + if (btnDown != btnDownBefore) { // state changed + if (!btnDown) seqSwitch=false; + btnTime = time; + btnDownBefore = btnDown; + delay(50); // debounce + } +} // loop + +void ee_init() { + for (int i = 0; i < 512; i++) { + EEPROM.write(i,0); + } + EEPROM.write(EE_L1,bright_L1); + EEPROM.write(EE_L2,bright_L2); + EEPROM.write(EE_L3,bright_L3); + EEPROM.write(EE_HIGH,bright_high); + EEPROM.write(EE_DZ,bright_dz); + EEPROM.write(EE_BL,bright_bl); + EEPROM.write(EE_BE,bright_be); + EEPROM.write(EE_SIG1,SIG1); + EEPROM.write(EE_SIG2,SIG2); + EEPROM.write(EE_STARTMODE,MODE_L1); + ee_write_cksum(); + Serial.println("Init EEPROM"); +} + +void ee_write_cksum() { + byte chk = 0; + for (int i = 1; i < 512; i++) { + chk = chk + EEPROM.read(i); + } + EEPROM.write(EE_CKSUM,chk); +} + +int readAccelAngleY() +{ + char acc[3]; + readAccel(acc); + return acc[1]; +} + +float readAccelAngleXZ() +{ + char acc[3]; + readAccel(acc); + return atan2(acc[0], acc[2]); +} + +void readAccel(char *acc) { + accelOnOff(true); + while (1) + { + Wire.beginTransmission(ACC_ADDRESS); + Wire.write(ACC_REG_XOUT); + Wire.endTransmission(false); // End, but do not stop! + Wire.requestFrom(ACC_ADDRESS, 3); // This one stops. + + for (int i = 0; i < 3; i++) + { + if (!Wire.available()) + continue; + acc[i] = Wire.read(); + if (acc[i] & 0x40) // Indicates failed read; redo! + continue; + if (acc[i] & 0x20) // Sign-extend + acc[i] |= 0xC0; + } + break; + } + accelOnOff(false); +} + +void blinkoff (byte times) +// briefly flash light off +{ for (byte i=0;i 1) { // multiple requestors, reduce by 1 + OnOff--; + return; + } + // first req on or last request off, handle it + byte enable[] = {ACC_REG_MODE, byte(state)}; + Wire.beginTransmission(ACC_ADDRESS); + Wire.write(enable, sizeof(enable)); + Wire.endTransmission(); + OnOff = byte(state); + delay(21); // time to wake up, 12+1/data rate +} + +byte setBright (byte bMin, byte bMax) { + float zAngle, angle, oldAngle, twist; + byte myBtn, bright, oldbright, range; +#if DEBUG + Serial.println("setBright"); +#endif + accelOnOff(true); + myBtn = digitalRead(DPIN_RLED_SW); + zAngle = readAccelAngleXZ(); + angle = 0; + oldAngle = 0; + while (myBtn == digitalRead(DPIN_RLED_SW)) { + angle = -(readAccelAngleXZ() - zAngle); + if (angle > PI) angle -= 2.0*PI; + if (angle < -PI) angle += 2.0*PI; + // crude debouncing + if (abs(angle-oldAngle) < .5 || abs(angle-oldAngle) > 6) { +// Serial.print("Ang = "); +// Serial.println(angle); + // angle is now 0, going negative for CCW rotation + range = bMax - bMin; + // only 2.35 radians either side of center (~270 degrees total) + if (angle > 2.35) angle = 2.35; + if (angle < -2.35) angle = -2.35; + // log func, about 0+ to 1+, depends on angle + twist = ( pow(2, angle) / 4.9) ; + bright = (bMin + (twist * range) - (range/20)); + // bright = map(( pow(2, angle) / 4.9),0,1,bMin*.95,bMax*1.05); + if (bright < bMin) bright = bMin; + if (bright > bMax) bright = bMax; + if (bright != oldbright) { +#if DEBUG + Serial.print("bright="); + Serial.println(bright); +#endif + analogWrite(DPIN_DRV_EN, bright); + oldbright = bright; + } // if oldbright + } // if old Angle + oldAngle = angle; + } // while switch not pressed + accelOnOff(false); +/* Serial.print("Ang = "); + Serial.print(angle); + Serial.print("\tTwist = "); + Serial.print(twist); + Serial.print("\tBright = "); + Serial.println(bright); +*/ + blinkoff(6); + return(bright); +} + +void setCleanup(byte ee, byte br) { + analogWrite(DPIN_DRV_EN, br); + EEPROM.write(ee,br); + ee_write_cksum(); + time=millis(); + btnTime=time; + btnDown = digitalRead(DPIN_RLED_SW); + btnDownBefore = btnDown; +} + +void setStartmode(byte sm) { + startMode = sm; + newMode = MODE_OFF; + // only remember mode if changed to save wear on EEPROM + byte ee_sm = EEPROM.read(EE_STARTMODE); + if (ee_sm != startMode) { + EEPROM.write(EE_STARTMODE,startMode); + ee_write_cksum(); + } + //flash goodbye + blinkoff(3); +} +