/*
* moui_v2_menu.c
*
* Created: 21.04.2014 12:42:34
* Author: fritz
*/
/*
* License: Free for private, non commercial use
*
* use Fuses:
* low 0x75
* high 0xFF
* Compiler optimization set to '-Os' (optimize for size)
* 1016 Bytes used
*
* Some comments:
*
* The UI is similar to the one from NovaTac lights (with some additional features)
*
* click (c): pressing the switch for not more than 'presstime' * 5ms (default 250ms)
* press (p): pressing the switch for more than 'presstime' * ms (default 250ms)
* ('presstime' defined below)
*
* This UI features a BURST mode and the light will dim from 'BURST' to 'mode4' after 'BURSTtime' * 1.28 seconds
* (Only if constant ON BURST, at momentary ON BURST the light will not dim down.)
* If you do not want a BURST mode just set 'BURST' the same as 'mode4'
*
*
* UI description:
* From OFF:
* 'click' switches the light on to 'mode1'
* With a 'press' you get momentary 'mode1'
* With 'click' and 'press' you get momentary 'BURST'
*
* When ON:
* You can toggle between 'mode1' and mode2' with 'click''click'
* With 'click''click''click' you get to 'mode3'
* From 'mode3' you get back to 'mode1' with 'click''click'
*
* From any mode you reach momentary 'BURST' with 'press'. As soon as you release the switch you get back to your last mode.
*
* You reach constand on 'BURST' with 'click''press' from any mode
* You get back to 'mode1' with 'click''click'
*
*
*
* To do:
*
* (0) Bugfixes
* Done! (more or less...)
*
* While my usage there did not occour any bugs, so please tell me, if you find some.
*
* (1) Set Attiny to sleep mode, when light was turned of for more than 250ms
* Done! Consumes when switched OFF from .2 to 6 µA (depends on locator flash ON or OFF)
*
* I added a locator flash. It flashes every 8 seconds for .01 second at brightness 'locator' (default 5)
* calculate time until battery is empty (draintime in years):
* first calculate
* drain current drc:
*
* WDTint = Watchdog Interrupt time (8 sec by default)
* BattAmp = maximal BATTERY Current
*
* drc = [WDTint * 6 * 10^-6 + 0.01 * BattAmp * locator/255]/8.01
*
* BattC = Battery Capacity in Ah
*
* draintime = BattC/(drc * 24 * 365)
*
* Example: 2.8A driver, 18650 Battery with 3Ah Capacity, locator = 5
* drc = 0.000075A
* draintime > 4 years
*
* If you do NOT want a locator flash you can disable it in the programming menu
* See the point "programming menu" below.
* When the locator flash is disabled an 18650 battery lasts for more than 500 years...
*
* (2) Implement programming menu
* Done!
*
* You can change the brightness of any mode by switching to the mode and then 'click', 'click', 'click' and 'press'.
* After you release the switch you can ramp up by a single 'click' and save your selected brightness by a single 'press'
*
* Example:
* (c) --> Mode1 (c)(c)(c)(p) --> programming menu for Mode1
* (c)(c)(c)(c)(c)(c) (6 clicks) --> 7 of 255 selected (p) --> Mode1 is from now on 7 until the next Battery change.
* Light goes back to Mode1
*
* By clicking the light 8 or more times you get to the programming menu.
* Here you can switch the locator flash ON/ OFF, enable or disable mode memory and store your programmed modes in the
* EEPROM so that they do not get deleted after a battery change.
*
* Once you entered the programming menu you can select what you want to do by clicking (1) to (3) times and then select the option by a single press.
* After you pressed the switch the light will flash 1 to 3 times to indicate, which option is/ was selected.
*
* (1) Toggle locator flash ON or OFF (the light will flash once)
* (2) Toggle mode memory ON or OFF (the light will flash twice)
* (3) Save your programmed modes so that they are not deleted after a battery change (the light will flash three times)
*
* If you click more than three times you leave the programming menu
*
*
*
* The following data has to be stored in the EEPROM:
*
* reg0: 5 = 0x05 <-- brightness for 'mode1'
* reg1: 20 = 0x14 <-- brigthness for 'mode2'
* reg2: 90 = 0x5A <-- brightness for 'mode3'
* reg3: 165 = 0xA5 <-- brightness for 'mode4'
* reg4: 255 = 0xFF <-- brightness for 'BURST'
*
* reg5: 1 = 0x01 <-- Locatorflash default ON (0 for default OFF)
* reg6: 1 = 0x01 <-- Memory default OFF (0 for default ON)
*
* reg7: 1 = 0x01
* reg8: 2 = 0x02
* reg9: 3 = 0x03
* reg10: 4 = 0x04
* reg11: 5 = 0x05 <-- default 'mode1'
* reg12: 7 = 0x07
* reg13: 9 = 0x09
* reg14: 14 = 0x0E
* reg15: 20 = 0x14 <-- default 'mode2'
* reg16: 30 = 0x1E
* reg17: 45 = 0x2D
* reg18: 60 = 0x3C
* reg19: 75 = 0x4B
* reg20: 90 = 0x5A <-- default 'mode3'
* reg21: 105 = 0x69
* reg22: 125 = 0x7D
* reg23: 145 = 0x91
* reg24: 165 = 0xA5 <-- default 'mode4'
* reg25: 185 = 0xB9
* reg26: 205 = 0xCD
* reg27: 230 = 0xE6
* reg28: 255 = 0xFF
*
* you can create a .hex file with the following content:
:10000000051446A5FF0101010203040507090E14AA
:100010001E2D3C4B5A697D91A5B9CDE6FFFFFFFF30
:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
:10003000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0
:00000001FF
*/
#include <avr/interrupt.h> // Include some headers...
#include <avr/sleep.h>
#include <avr/io.h>
#define F_CPU 4800000 // Prescaler is set
#include <util/delay.h>
#include <avr/wdt.h>
#define WDTflag 0 // Bit for Watchdog interrupt
#define PCINTflag 1 // Bit for Pin Change interrupt
/*
* 'OFFtime * 5ms' is the the time, which the light has to be turned ON or OFF to reset 'clicked'
* If you hold the switch for longer than 'presstime * 5ms' --> 'press', otherwise 'click'
* Both values can be changed (should be in [30, ..., 100])
* Default 250ms for both
*/
#define OFFtime 50
#define presstime 50
/*
* Set 'BURSTtime', here. If you do not want a Burst mode set 'BURST' to the same as 'mode4' (by writing different data in the hex file for the EEPROM or by programming the brightness)
*/
#define BURSTtime 11 // (BURSTtime + 1) * 1.28sec (default 15.4 seconds)
/*
* define 'locator' to your preferred brightness in [1, ..., 255]. 4 > is save for 7135 drivers
*/
#define locator 5
/*
* The address for mode1, -2, -3, -4 and BURST in the EEPROM are defined here. Do NOT change.
*/
#define mode1addr 0
#define mode2addr 1
#define mode3addr 2
#define mode4addr 3
#define BURSTaddr 4
uint8_t lastmode = 0; // Memorize the last mode
volatile uint8_t INTflag = 0; // watchdog flag for the locator flash
uint8_t modes[] = {0, 0, 0, 0, 0}; // Mode Array, do NOT change this
uint8_t click(); // Function declaration for click (tests if you click of press the switch)
void momentary(); // Function declaration for momentary
void wait_5ms(); // Name says it all...
void wait_ms(uint8_t time); // Wait for 'time' * 5ms.
void setmode(uint8_t mode); // Sets OCR0B to 'modes[mode]'
void bedtime(); // Sends ATtiny to sleep mode
void ramping(uint8_t mode); // Ramps up and sets new brightness to 'modes[]'
void blink(); // Blinks...
uint8_t EEPROM_read(uint8_t EEPROM_address); // EEPROM read
void EEPROM_write(uint8_t EEPROM_adress); // EEPROM write
void programming(); // Programming menu
void blinkblinkblink();
ISR(PCINT0_vect)
{
INTflag = (1 << PCINTflag); // Set 'INTflag' to 2 (Important for programming menu)
}
ISR(WDT_vect) // Watchdog Timer Interrupt (locatorflash)
{
INTflag = (1 << WDTflag); // Set 'INTflag' to 1
}
int main(void)
{
uint8_t time = 0; // Measure ON/ OFF time and some other things
uint8_t clicked = 0; // Count how many clicks have been made
uint8_t memory = 0; // Memorizes last mode for programming
uint8_t bursttimer = 0; // Used to count time to ramp down
uint8_t memory_ON_OFF = 0;
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Use Power Down Sleep mode. Wakes up with Pin Change Interrupt or Watchdog Interrupt
DDRB = (1 << DDB1); // Define PB1 as output
PORTB = (1 << PINB3); // Define pull-up switch ON
GIMSK = (1 << PCIE); // Pin Change Interrupt Enable (switch is pressed --> interrupt)
PCMSK = (1 << PCINT3); // Enable Pin Change Interrupt at PINB3
TCCR0A = 0b00100001;
TCCR0B = 0b00000001; // PWM setup
/*
* Watchdog Interrupt interval
*
* WDP3 WDP2 WDP1 WDP0 timer ~seconds
* 0 1 0 1 512 mS .5
* 0 1 1 0 1024 mS 1
* 0 1 1 1 2048 mS 2
* 1 0 0 0 4096 mS 4
* 1 0 0 1 8192 mS 8
*/
WDTCR |= (1 << WDP3) | (1 << WDP0); // Set Watchdog prescaler to 8 sec
if ((EEPROM_read(5) & 1) == 1)
WDTCR |= (1 << WDTIE); // Enable Watchdog Interrupts if Locatorflash enabled...
modes[mode1addr] = EEPROM_read(mode1addr); // Mode, when light is switched ON
modes[mode2addr] = EEPROM_read(mode2addr); // Mode accessed with two clicks
modes[mode3addr] = EEPROM_read(mode3addr); // Mode accessed with three clicks
modes[mode4addr] = EEPROM_read(mode4addr); // Mode accessed when switch is hold while the light is ON
modes[BURSTaddr] = EEPROM_read(BURSTaddr); // Burst mode. The light will step down from 'BURST' to 'mode4' after 'BURSTtime' * 1.28sec
OCR0B = 0; // 'Switch on brightness' after getting getting power.
// Change if you want 'Power ON' (useful for lights with two switches)
while(1)
{
memory_ON_OFF = EEPROM_read(6);
if ((PINB & 8) == 0) // When the button is pressed (PINB3 pulled down)
{
if (OCR0B == 0) // When light was switched OFF
{
if (memory_ON_OFF == 1) // Mode memory switched OFF
OCR0B = modes[mode1addr]; // Set brightness to 'mode1', do not use setmode(mode1) otherwise it will not switch to 'mode2' after a 2 clicks
else
OCR0B = modes[memory];
clicked += click(); // Test if 'click' or 'press'
if (clicked == 0) // Press
momentary(); // --> momentary ON with 'mode1' (or 'memory' if memory enabled)
if (clicked == 1) // Light was switched OFF and button is pressed
{
if (memory_ON_OFF == 1) // Mode memory switched OFF
lastmode = mode1addr; // Set 'lastmode' to mode1 that it will switch to 'mode2' after 2 clicks
else
lastmode = memory; // Set 'lastmode' to 'memory' (so that it will switch to 'mode1' after two clicks)
if ((PINB & 8) == 0) // If switch is still pressed
{
setmode(BURSTaddr); // Set brightness to 'BURST'
momentary(); // But you have to release the switch sometime, therefore is momentary()
OCR0B = modes[BURSTaddr]; // momentary() sets brightness to 0 --> set it back to 'BURST'
}
}
if (clicked == 2) // Light was switched OFF and ON again
{
if ((PINB & 8) == 0) // If switch is still pressed
{
OCR0B = modes[BURSTaddr]; // Momentary 'BURST'
momentary();
clicked = 0;
}
else
{
if (lastmode == mode1addr) // Decide which mode and set new mode...
setmode(mode2addr);
else
setmode(mode1addr);
}
}
}
else // Light is already switched ON
{
clicked +=click();
if (clicked == 0) // Light is ON and switch is pressed
{
OCR0B = modes[BURSTaddr]; // Brightness is set to 'BURST'
momentary(); // While button is pressed...
OCR0B = modes[lastmode]; // Brightness is set to 'lastmode' again
}
if (clicked == 1) // Switch was pressed (and perhaps released)
{
OCR0B = modes[BURSTaddr]; // Momentary 'BURST'
momentary(); // --> switch light OFF
}
if (clicked == 2 || (clicked > 3))
OCR0B = 0; // --> switch light OFF
if (clicked == 3) // Access 'mode3' or ramping mode
{
setmode(mode3addr);
if ((PINB & 8) == 0) // If switch is still pressed -> ramping mode is entered
{
blinkblinkblink();
ramping(memory); // Set new brightness for the selected mode
clicked = 0; // Reset clicked
momentary(); // Wait until switch is release
OCR0B = modes[memory]; // And set brightness to the new mode
}
}
if (clicked > 7) // Save new modes in EEPROM
{
INTflag = 0;
blinkblinkblink(); // Blink three times to indicate entering the programming menu
OCR0B = 0;
programming(); // Enter programming menu
clicked = 0; // Reset click counter
}
}
time = 0; // Set 'time' to 0 to tell programm the light just was switched ON or OFF
bursttimer = 0; // Reset 'bursttimer'
}
wait_5ms();
if (time > OFFtime) // More than OFFtime * 5ms without doing anything passed
{
memory = lastmode;
clicked = 0; // Reset 'click' (counter) and
if (OCR0B == 0) // If light is switched OFF
bedtime(); // --> send ATtiny to sleep mode
if (time > 254)
bursttimer++; // Increase Burst timer when time is overflowing
}
if ((bursttimer > BURSTtime) && (lastmode == BURSTaddr))
setmode(mode4addr); // Ramp down
if ((INTflag & (1 << WDTflag)) == 1) // WDT Interrupt occurred
{
INTflag = 0; // Set 'INTflag' to 0 again
OCR0B = locator; // Set Brightness to 'locator'
wait_5ms();
wait_5ms();
OCR0B = 0; // After 10ms switch light OFF again
time = OFFtime; // Set 'time' to OFFtime to enter sleepmode again
}
time++; // Reset 'time' not needed, just creates an overflox. Important for BURSTtimer
}
}
uint8_t click() // Decide if click or press...
{ // if click --> return 1
uint8_t press_timer = 0;
while(press_timer < presstime) // if press --> return 0 (pressed for more than 50 * 5ms)
{
if ((PINB & 8) == 0) // When the button is pressed (PINB3 pulled down)
{
press_timer++; // Increase 'time' while switch is pressed
wait_5ms(); // And wait 5ms
}
else
return 1; // Switch was clicked
}
return 0; // Switch is pressed
}
void momentary() // Allows momentary function.
{
while((PINB & 8) == 0) // While switch is pressed wait
wait_5ms(); // Wait 5ms for debouncing
OCR0B = 0; // When switch is released set brightness to 0
}
void wait_5ms() // Waits for 5ms
{ // This way you use less memory than
_delay_ms(5); // Always writing _delay_ms(double argument)
}
void setmode(uint8_t mode) // Sets OCR0B to 'modes[mode]'
{ // Also uses less memory than always writing
OCR0B = modes[mode]; // OCR0B = 'modes[mode]';
lastmode = mode; // lastmode = 'mode';
}
void bedtime() // Send ATtiny to sleep mode
{
sei(); // Enable global interrupts
sleep_mode(); // Go to sleep and wait for interrupt...
cli(); // Disable global interrupts
}
void ramping(uint8_t mode) // Ramping mode
{
uint8_t new_brightness = 0; // Define some new variables
uint8_t click_counter = 0;
uint8_t dummy = 0;
momentary(); // Wait until the switch is released
while (1) // Go into a never ending loop except you reach the mode you like and press
{
if (new_brightness > 22) // 22 brightness settings are stored in the EEPROM
{
new_brightness = 0; // This avoids getting a wrong adress
click_counter = 0;
}
OCR0B = EEPROM_read(new_brightness + 7); // OCR0B is set to the current brightness
if ((PINB & 8) == 0) // If switch is pressed
{
dummy = click_counter;
click_counter += click(); // If switch is clicked the light will continue ramping up
new_brightness = click_counter;
if (dummy == new_brightness) // if dummy == new_brightness then the switch was pressed
{
blinkblinkblink(); // Blink three times to indicate that the new mode is set
click_counter = EEPROM_read(new_brightness + 7);
modes[mode] = click_counter;
return; // Return to main
}
}
wait_5ms();
}
}
void blink() // Blinks with OFFtime 250ms, ontime 250ms and brightness 20
{
OCR0B = 0; // Set brightness to '0'
wait_ms(50);
OCR0B = 20; // Set brightness to 20
wait_ms(50);
}
uint8_t EEPROM_read(uint8_t EEPROM_address) // For more information about this function look in the datasheet
{
while(EECR & (1 << 1)); // Wait for completion of previous write
EEARL = EEPROM_address; // Set up address register
EECR |= (1 << EERE); // Start eeprom read by writing EERE
return EEDR; // Return data from data register
}
void EEPROM_write(uint8_t EEPROM_adress) // For more information about this function look in the datasheet
{
while(EECR & (1 << 1)); // Wait for completion of previous write
EECR = (0 << EEPM1) | (0 >> EEPM0); // Set Programming mode
EEARL = EEPROM_adress; // Set up address and data registers
if (EEPROM_adress == 5)
EEDR = EEPROM_read(5) ^ 1;
if (EEPROM_adress == 6)
EEDR = EEPROM_read(6) ^ 1;
if (EEPROM_adress < 5)
EEDR = modes[EEPROM_adress];
EECR |= (1 << 2); // Write logical one to EEMPE
EECR |= (1 << 1); // Start eeprom write by setting EEPE
}
void wait_ms(uint8_t time) // Wait time * 5ms with using wait_5ms()
{
while ((time > 0) && ((INTflag & (1 << PCINTflag)) == 0))
{
wait_5ms();
time--;
}
}
void programming()
{
uint8_t counter = 0; // Define some new variables
uint8_t click_counter = 0;
uint8_t dummy = 0;
while (click_counter < 4) // Leave programming menu after 4 clicks
{
INTflag = 0;
sei();
wait_ms(50);
while ((counter > 0) && (INTflag & (1 << PCINTflag)) == 0)
{
blink();
counter--;
}
cli();
OCR0B = 0;
if ((PINB & 8) == 0) // If switch is pressed
{
dummy = click_counter;
click_counter += click(); // Decide if switch is clicked or pressed
counter = click_counter;
if (dummy == counter) // Switch was pressed
{
momentary();
if (dummy == 1)
{
EEPROM_write(5); // Toggle Locatorflash ON/ OFF
}
if (dummy == 2)
{
EEPROM_write(6); // Toggle mode memory ON/ OFF
}
if (dummy == 3)
{
EEPROM_write(mode1addr); // Update 'mode1'
EEPROM_write(mode2addr); // Update 'mode2'
EEPROM_write(mode3addr); // Update 'mode3'
EEPROM_write(mode4addr); // Update 'mode4'
EEPROM_write(BURSTaddr); // Update 'BURST'
}
}
}
wait_5ms();
}
}
void blinkblinkblink() // Blinks three times...
{
blink();
blink();
blink();
}