moUI - programmable firmware for electronic switches

Hello,

since I don't use my NovaTac 120P anymore because it is outdated I really miss the UI.

In my opinion it is the best UI for a momentary electronic switch.

Because of that I started writing my own firmware for our beloved ATtiny13 and since there are already 10000 firmwares I prouldy present you firmware No. 10001:

It is a firmware for use with an electronic switch. The firmware also features

- four modes with one mode (high) always accessable,

- a Locator Flash (can be disabled),

- a burst Mode,

- a programming menu,

- mode memory (can be disabled)

- and each mode of the four modes and BURST can be individually programmed.

Sounds quiete complicated but it is actually really easy.

Here is the source code and the UI explained in the comment on top:

/*
 * 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 &lt;&lt; WDP3) | (1 &lt;&lt; WDP0);                     // Set Watchdog prescaler to 8 sec
if ((EEPROM_read(5) &amp; 1) == 1)
    WDTCR |= (1 &lt;&lt; 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();
}

I am very sorry for the messy formatting but it is impossible to get everything right in this online code editor...

Perhaps I could attach a .hex file and a .c file if that is possible?

Also that is wether a good nor a pretty code but since this is my first time programming an µc I am already happy that it is working...

Well, I hope you like the ui and I am looking forward to your feedback. If you find any bugs please tell me.

Regards,

Fritz

Why clock divider ?

At first I had not implemented the sleep mode. Therefore I set the Clock to 600khz to get a low power consumption.
Now I changed it back to 4.8Mhz

Regards
Fritz

subcribed.