Klemens Schölhorn
fe6fe8ea52
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.
199 lines
4.8 KiB
C++
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;
|
|
}
|
|
}
|
|
}
|