So, here's what I've come up with. It works 98%.
ImprovedButton.h (based on GitHub - JChristensen/JC_Button: Arduino library to debounce button switches, detect presses, releases, and long presses)
#ifndef _IMPROVEDBUTTON_h
#define _IMPROVEDBUTTON_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "arduino.h"
#else
#include "WProgram.h"
#endif
#include "delykLooperSettings.h"
class Button
{
private:
uint8_t _multiBool;
uint8_t _pin; // Arduino pin number
uint32_t _time; // time of current state (all times are in ms)
uint32_t _lastChange; // time of last state change
public:
Button();
Button(uint8_t);
uint8_t read(const uint32_t);
uint8_t isPressed();
uint8_t isReleased();
uint8_t wasPressed();
uint8_t wasReleased();
uint8_t pressedFor(const uint32_t);
};
#endif
ImprovedButton.cpp
#include "ImprovedButton.h"
#define STATE_BIT 0
#define LAST_STATE_BIT 1
#define CHANGED_BIT 2
Button::Button() { }
Button::Button(uint8_t pin) {
_multiBool = 0;
_pin = pin;
_time = _lastChange = millis();
pinMode(pin, INPUT_PULLUP);
uint8_t state = !digitalRead(pin);
bitWrite(_multiBool, STATE_BIT, state);
bitWrite(_multiBool, LAST_STATE_BIT, state);
bitWrite(_multiBool, CHANGED_BIT, false);
}
uint8_t Button::read(const uint32_t ms = millis()) {
_time = ms;
if (ms - _lastChange < BUTTON_DEBOUNCE_TIME) {
bitWrite(_multiBool, CHANGED_BIT, false);
}
else {
bitWrite(_multiBool, LAST_STATE_BIT, bitRead(_multiBool, STATE_BIT));
bitWrite(_multiBool, STATE_BIT, !digitalRead(_pin));
if (bitRead(_multiBool, STATE_BIT) != bitRead(_multiBool, LAST_STATE_BIT)) {
_lastChange = ms;
bitWrite(_multiBool, CHANGED_BIT, true);
}
else {
bitWrite(_multiBool, CHANGED_BIT, false);
}
}
return bitRead(_multiBool, STATE_BIT);
}
uint8_t Button::isPressed() {
return bitRead(_multiBool, STATE_BIT);
}
uint8_t Button::isReleased() {
return !bitRead(_multiBool, STATE_BIT);
}
uint8_t Button::wasPressed() {
return bitRead(_multiBool, STATE_BIT) && bitRead(_multiBool, CHANGED_BIT);
}
uint8_t Button::wasReleased() {
return !bitRead(_multiBool, STATE_BIT) && bitRead(_multiBool, CHANGED_BIT);
}
uint8_t Button::pressedFor(const uint32_t ms) {
return (isPressed() && _time - _lastChange >= ms);
}
delykStateMachine.h
#include "ImprovedButton.h"
#include "delykLooperSettings.h"
#ifndef _DELYKSTATEMACHINE_h
#define _DELYKSTATEMACHINE_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "arduino.h"
#else
#include "WProgram.h"
#endif
class delykStateMachine {
private:
Button * _buttons[7];
// State Machine's State
byte _currentState;
// Button's States
byte _longPressState;
byte _wasReleasedState;
byte _isReleasedState;
byte _isPressedState;
void _checkNeedRelease();
void _checkLongPress();
void(*_onShortPress)(uint8_t, uint8_t);
void(*_onLongPress)(uint8_t, uint8_t);
void(*_onMultiShortPress)(uint8_t, uint8_t);
void(*_onEnterEditMode)();
void(*_onCancelEditMode)();
public:
enum {
WAITING,
IN_LONG_PRESS,
ENTERING_EDIT_MODE,
EDIT_MODE,
EDIT_IN_LONG_PRESS,
NEED_RELEASE,
CANCELLING_EDIT_MODE
};
delykStateMachine();
void loop(const uint32_t);
void attachShortPressHandler(void(*)(uint8_t, uint8_t));
void attachLongPressHandler(void(*)(uint8_t, uint8_t));
void attachMultiShortPressHandler(void(*)(uint8_t, uint8_t));
void attachEnterEditModeHandler(void(*)());
void attachCancelEditModeHandler(void(*)());
};
#endif
delykStateMachine.cpp
#include "delykStateMachine.h"
Button * _buttons[7] = {
new Button(SWITCH_A),
new Button(SWITCH_B),
new Button(SWITCH_C),
new Button(SWITCH_D),
new Button(SWITCH_BANK_DOWN),
new Button(SWITCH_BANK_UP),
new Button(SWITCH_TAP_TEMPO)
};
delykStateMachine::delykStateMachine() {
_currentState = WAITING;
_longPressState = 0;
_wasReleasedState = 0;
_isReleasedState = 0;
_isPressedState = 0;
this->_onCancelEditMode = NULL;
this->_onEnterEditMode = NULL;
this->_onLongPress = NULL;
this->_onMultiShortPress = NULL;
this->_onShortPress = NULL;
}
void delykStateMachine::_checkNeedRelease() {
if (_longPressState > 0) {
_currentState = IN_LONG_PRESS;
_checkLongPress();
}
else if (_isReleasedState == 0b00001111) {
_currentState = WAITING;
//Serial.println(F("RELEASE --> WAITING"));
}
}
void delykStateMachine::_checkLongPress() {
if (_longPressState == 0b0001011) {
_currentState = ENTERING_EDIT_MODE;
_onEnterEditMode();
}
else if (_isReleasedState == 0b00001111) {
_currentState = WAITING;
}
}
void delykStateMachine::loop(const uint32_t ms) {
byte idx;
for (idx = 0; idx < 7; idx++) {
_buttons[idx]->read(ms);
bitWrite(_longPressState, idx, _buttons[idx]->pressedFor(1000));
bitWrite(_wasReleasedState, idx, _buttons[idx]->wasReleased());
bitWrite(_isReleasedState, idx, _buttons[idx]->isReleased());
bitWrite(_isPressedState, idx, _buttons[idx]->isPressed());
}
switch (_currentState) {
case WAITING:
if (_longPressState > 0) {
_currentState = IN_LONG_PRESS;
_checkLongPress();
}
else if (_wasReleasedState > 0) {
_onShortPress(_wasReleasedState, _currentState);
}
else if (__builtin_popcount(_isPressedState) > 1) {
_currentState = NEED_RELEASE;
_checkNeedRelease();
_onMultiShortPress(_isPressedState, _currentState);
}
break;
case NEED_RELEASE:
_checkNeedRelease();
break;
case IN_LONG_PRESS:
_checkLongPress();
break;
case ENTERING_EDIT_MODE:
if (_isReleasedState == 0b00001111) {
_currentState = EDIT_MODE;
}
break;
case EDIT_MODE:
if (_longPressState == 0b00001101) {
_onCancelEditMode();
_currentState = CANCELLING_EDIT_MODE;
}
else if (_longPressState > 0) {
// TODO: Transitional state?
_onLongPress(_wasReleasedState, _currentState);
}
else if (_wasReleasedState > 0) {
_onShortPress(_wasReleasedState, _currentState);
}
// TODO: Commit changes?
break;
case CANCELLING_EDIT_MODE:
if (_isReleasedState == 0b00001111) {
_currentState = WAITING;
}
break;
}
}
void delykStateMachine::attachShortPressHandler(void(*handler)(uint8_t, uint8_t)) {
_onShortPress = handler;
}
void delykStateMachine::attachLongPressHandler(void(*handler)(uint8_t, uint8_t)) {
_onLongPress = handler;
}
void delykStateMachine::attachMultiShortPressHandler(void(*handler)(uint8_t, uint8_t)) {
_onMultiShortPress = handler;
}
void delykStateMachine::attachEnterEditModeHandler(void(*handler)()) {
_onEnterEditMode = handler;
}
void delykStateMachine::attachCancelEditModeHandler(void(*handler)()) {
_onCancelEditMode = handler;
}
main.ino
#include "delykLooperSettings.h"
#include "delykStateMachine.h"
unsigned long ms;
delykStateMachine dsm;
void setup() {
dsm.attachCancelEditModeHandler(onCancelEditMode);
dsm.attachEnterEditModeHandler(onEnterEditMode);
dsm.attachLongPressHandler(onLongPress);
dsm.attachMultiShortPressHandler(onMultiShortPress);
dsm.attachShortPressHandler(onShortPress);
}
void loop() {
ms = millis();
dsm.loop(ms);
}
// State Machine/Button Events
void onShortPress(byte wasReleasedState, byte currentState) {
if (currentState == delykStateMachine::EDIT_MODE) {
//Serial.print(F("Short Enable/Disable Loop"));
//Serial.println(blah21);
}
else {
//Serial.print(F("Patch "));
//Serial.println(blah21);
}
}
void onLongPress(byte wasReleasedState, byte currentState) {
if (currentState == delykStateMachine::EDIT_MODE) {
//Serial.print(F("Long Enable/Disable Loop"));
//Serial.println(blah21);
}
}
void onEnterEditMode() {
//Serial.println(F("EDIT MODE"));
}
void onCancelEditMode() {
//Serial.println(F("CANCELLED"));
}
void onMultiShortPress(byte isPressedState, byte currentState) {
if (isPressedState == 0b00001100) {
//Serial.println(F("BANK UP"));
}
else if (isPressedState == 0b00000011) {
//Serial.println(F("BANK DOWN"));
}
}
It appears to compile with a RAM Usage of 66 bytes. Can I trim anymore fat? Please keep in mind I'm a C# dev, so some of this may not be proper C++.
The only scenario that doesn't work right is when I want to long push to get into EDIT MODE. If I don't push all three fast enough, it thinks I might be doing a BANK UP. I'm sure I can come up with some way to ignore the BANK UP, but I ran out of time last night.
Any tips or suggestions/code review is appreciated.