/* ATtiny sketch for controlling a New Year lights setup with 4 LED colors
* and one button for switching modes.
*
* Short presses of the button double the delay between the LED changes
* until the maximum allowed delay and then loops back to the start.
*
* Long presses do the same for a selection of different operation modes.
* Unlike the delay, the mode changes immediately once the button is held
* down for the required one second time, without waiting for release.
*
* This is based on Timer0 running at approximately 1024 steps per second.
*/
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
// MCU SETTINGS
#if defined (__AVR_ATtiny85__) || defined (__AVR_ATtiny45__) || defined (__AVR_ATtiny25__)
#define TIMER_DIVISOR 122 // 8 MHz / 64 / 1024
#elif defined(__AVR_ATtiny13__)
#define TIMER_DIVISOR 146 // 9.6 MHz / 64 / 1024
#define TIMSK TIMSK0
#else
#error Unknown MCU
#endif
// SETTINGS
#define BUTTON_PIN PB4
#define LED_PIN_MASK 0xF
#define SECOND_LENGTH 4
#define MIN_BLINK_DELAY (SECOND_LENGTH >> 1)
#define MAX_BLINK_DELAY (MIN_BLINK_DELAY << 4)
#define LONG_PRESS_TIME SECOND_LENGTH
#define BUTTON_DEBOUNCE_SAMPLES 2
#define BUTTON_DEBOUNCE_MASK ((1 << BUTTON_DEBOUNCE_SAMPLES) - 1)
// EEPROM
uint8_t EEMEM ee_blink_mode;
uint8_t EEMEM ee_blink_delay;
uint16_t EEMEM ee_random_seed;
// VARIABLES
typedef enum BlinkMode {
BLINK_MODE_RANDOM,
BLINK_MODE_WAVE1,
BLINK_MODE_WAVE2,
BLINK_MODE_WAVE3,
BLINK_MODE_ALL_ON,
// Last enum entry for range checks
BLINK_MODE_LAST
} BlinkMode;
typedef union {
uint16_t count;
uint8_t bytes[2];
} counter16_t;
counter16_t blink_timer;
uint8_t blink_delay = MIN_BLINK_DELAY;
uint8_t blink_mode = 0;
uint8_t blink_step = 0;
uint8_t button_log = 0; // shift register of button state for debouncing
uint8_t button_down = 0;
counter16_t button_timer; // button down timer
uint8_t button_long_pressed = 0;
/* Simple random generator. */
uint16_t random_seed;
void next_random(int8_t cnt)
{
// Every LFSR step only generates one new unique bit.
while (cnt-- > 0)
random_seed = (random_seed >> 1) ^ (-(random_seed & 1) & 0xB400);
}
#define RANDOM_BITS(n) (next_random(n), random_seed & ((1 << n) - 1))
void init_random()
{
random_seed = eeprom_read_word(&ee_random_seed);
next_random(16);
eeprom_write_word(&ee_random_seed, random_seed);
}
void blink()
{
uint8_t status = PORTB & LED_PIN_MASK;
switch (blink_mode) {
case BLINK_MODE_RANDOM:
if (blink_step == 0) {
status = RANDOM_BITS(4);
blink_step = 10;
}
else {
uint8_t bit, new_status;
// flip random bit, but make sure it's different from the
// previous step, and the result isn't all on or off.
do {
do {
bit = RANDOM_BITS(2);
}
while (bit == blink_step - 1);
new_status = status ^ (1 << bit);
}
while (new_status == 0 || new_status == LED_PIN_MASK);
status = new_status;
blink_step = 1 + bit;
}
break;
case BLINK_MODE_ALL_ON:
status = LED_PIN_MASK;
break;
case BLINK_MODE_WAVE1:
status = 1 << (blink_step++ & 3);
break;
case BLINK_MODE_WAVE2:
status = (1 << (blink_step & 3)) | (1 << ((blink_step+1) & 3));
blink_step++;
break;
case BLINK_MODE_WAVE3:
status = ~(1 << (blink_step++ & 3));
break;
}
PORTB = (PORTB & ~LED_PIN_MASK) | (status & LED_PIN_MASK);
}
void next_delay()
{
blink_delay <<= 1;
if (blink_delay > MAX_BLINK_DELAY)
blink_delay = MIN_BLINK_DELAY;
eeprom_write_byte(&ee_blink_delay, blink_delay);
}
void next_mode()
{
if (++blink_mode >= BLINK_MODE_LAST)
blink_mode = 0;
eeprom_write_byte(&ee_blink_mode, blink_mode);
blink_step = 0;
blink();
}
ISR(TIM0_COMPA_vect)
{
// Update the LEDs
blink_timer.count++;
if(blink_timer.bytes[1] >= blink_delay)
{
blink();
blink_timer.count = 0;
}
// Store a history of the input pin state; 1 == up
button_log = (button_log << 1) | ((PINB >> BUTTON_PIN) & 1);
// Convert ...0000 or ...1111 into 1 or 0. Other values mean unstable state.
uint8_t button_down_cur = (button_log + 1) & BUTTON_DEBOUNCE_MASK;
if (button_down_cur <= 1 && button_down_cur != button_down) {
button_down = button_down_cur;
if (button_down) {
button_long_pressed = 0;
button_timer.count = 0;
}
else if (!button_long_pressed) {
next_delay();
}
}
else if (button_down && !button_long_pressed) {
button_timer.count++;
if (button_timer.bytes[1] >= LONG_PRESS_TIME) {
button_long_pressed = 1;
button_timer.count = 0;
next_mode();
}
}
}
int main(void)
{
// Ensure no clock divider
CLKPR = _BV(CLKPCE);
CLKPR = 0;
// Set up the timer to fire ~1024 times a second
TCCR0A = _BV(WGM01); // CTC mode
TCCR0B = _BV(CS01) | _BV(CS00); // clock frequency / 64
OCR0A = TIMER_DIVISOR - 1; // Output compare
OCR0B = 0;
TCNT0 = 0; // Set counter 0 to zero
TIMSK = _BV(OCIE0A); // Enable overflow interrupt
// First 4 pins to output, PB4 pull up
PORTB = _BV(BUTTON_PIN);
DDRB = LED_PIN_MASK;
// Restore EEPROM settings
uint8_t old_blink_mode = eeprom_read_byte(&ee_blink_mode);
if (old_blink_mode < BLINK_MODE_LAST)
blink_mode = old_blink_mode;
uint8_t old_blink_delay = eeprom_read_byte(&ee_blink_delay);
if (old_blink_delay >= MIN_BLINK_DELAY && old_blink_delay <= MAX_BLINK_DELAY)
blink_delay = old_blink_delay;
init_random();
// Update the LEDs
blink();
sei(); //Enable global interrupts
// Sleep in the main loop - the work happens in the timer interrupt
MCUCR = _BV(SE);
for (;;)
sleep_cpu();
}