tinyfinger/code/code.ino
Klemens Schölhorn fe6fe8ea52 Disable led display after 10 seconds to save energy
Going to sleep and waking on an interrupt generated by the button would
save even more energy, but it our current chip unfortunately has problems
waking up reliably.
2018-02-19 23:37:53 +01:00

199 lines
4.8 KiB
C++

// Conflicts with std c++ headers
#undef min
#undef max
#include "button.h"
#include "debounce.h"
#include "lcg_rng.h"
#include "segment.h"
#include "uniform_int_dist.h"
#define BUTTON_PIN 1
#define DISPLAY_ENABLE 3
lcg_rng rng;
std::uniform_int_distribution<uint8_t> distribution(1,16);
SegmentDisplay<4, 2, 0> display;
using MainButton = Button<150, 150>;
Debounce<20> debounce;
MainButton button;
bool activeGroups[16] = { false };
uint8_t activeCount = 0;
#define NUM_SLOWDOWN 12
const uint16_t PROGMEM SLOWDOWN[NUM_SLOWDOWN] = {
35, 32,
20, 64,
8, 125,
4, 250,
2, 500,
2, 1000
};
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(DISPLAY_ENABLE, OUTPUT);
digitalWrite(DISPLAY_ENABLE, 0);
display.setDash();
display.write();
resetGroups();
}
enum class State : uint8_t {
Sleep,
Idle,
Menu,
Counting,
Slowing
};
auto state = State::Idle;
uint16_t timeout;
void loop() {
rng();
bool buttonPressed = !digitalRead(PIN1);
auto event = button.event(debounce.event(buttonPressed));
switch(state) {
case State::Sleep:
if(buttonPressed) {
digitalWrite(DISPLAY_ENABLE, 0);
state = State::Idle;
timeout = millis();
}
case State::Idle:
if(event == ButtonEvent::Press) {
state = State::Slowing;
} else if(event == ButtonEvent::LongPress) {
state = State::Counting;
timeout = millis();
} else if(event == ButtonEvent::DoublePress) {
state = State::Menu;
} else if((uint16_t) millis() - timeout >= 10000) {
digitalWrite(DISPLAY_ENABLE, 1);
state = State::Sleep;
}
break;
case State::Menu:
displayMenu();
display.setDash();
display.write();
state = State::Idle;
timeout = millis();
break;
case State::Counting:
if(event == ButtonEvent::LongPress) {
// stay in this state
if((uint16_t) millis() - timeout >= 25) {
display.set(generateNextNumber());
display.write();
timeout = millis();
}
} else {
state = State::Slowing;
}
break;
case State::Slowing:
slowdown();
state = State::Idle;
timeout = millis();
break;
}
}
uint8_t generateNextNumber() {
uint8_t randomNumber = distribution(rng);
uint8_t i;
for(i = 0; i < 16 && randomNumber != 0; i += 1) {
if(activeGroups[i]) {
randomNumber -= 1;
}
}
// already offset by 1 because of the loop postincrement
return i;
}
void slowdown() {
uint8_t currentNumber;
for(size_t i = 0; i < NUM_SLOWDOWN; i += 2) {
for(size_t j = 0; j < pgm_read_word_near(SLOWDOWN + i); ++j) {
currentNumber = generateNextNumber();
display.set(currentNumber);
display.write();
delay(pgm_read_word_near(SLOWDOWN + i + 1));
}
}
for(uint8_t i = 0; i < 2; ++i) {
display.setOff();
display.write();
delay(250);
display.set(currentNumber);
display.write();
delay(250);
}
}
void resetGroups() {
for(uint8_t i = 0; i < 16; i += 1) {
activeGroups[i] = true;
activeCount += 1;
}
}
void displayMenu() {
uint8_t currentGroup = 0;
uint16_t timeout = millis();
constexpr uint16_t longPressDelay = 300;
Button<longPressDelay, 150> menuButton;
for(;;) {
bool buttonPressed = !digitalRead(PIN1);
auto event = menuButton.event(debounce.event(buttonPressed));
display.set(currentGroup + 1);
display.setDP(activeGroups[currentGroup]);
display.write();
switch(event) {
case ButtonEvent::LongPress:
// rotate through groups
if((uint16_t) millis() - timeout >= longPressDelay) {
currentGroup = (currentGroup + 1) % 16;
timeout = millis();
}
break;
case ButtonEvent::Press:
// (de)activate group
activeGroups[currentGroup] = !activeGroups[currentGroup];
break;
case ButtonEvent::DoublePress:
// exit menu
activeCount = 0;
for(uint8_t i = 0; i < 16; i += 1) {
activeCount += (uint8_t) activeGroups[i];
}
if(activeCount == 0) {
resetGroups();
}
distribution = std::uniform_int_distribution<uint8_t>(1,activeCount);
return;
}
}
}