Buttons can have click, double click and long press events

pull/274/head
Stephen Brown 3 years ago
parent a894f948f8
commit 4179d07688
  1. 37
      src/config.cpp
  2. 13
      src/config.h
  3. 2
      src/mididevice.cpp
  4. 20
      src/minidexed.ini
  5. 347
      src/uibuttons.cpp
  6. 113
      src/uibuttons.h
  7. 21
      src/userinterface.cpp
  8. 4
      src/userinterface.h

@ -83,9 +83,15 @@ void CConfig::Load (void)
m_nButtonPinPrev = m_Properties.GetNumber ("ButtonPinPrev", 0);
m_nButtonPinNext = m_Properties.GetNumber ("ButtonPinNext", 0);
m_nButtonPinBack = m_Properties.GetNumber ("ButtonPinBack", 0);
m_nButtonPinSelect = m_Properties.GetNumber ("ButtonPinSelect", 0);
m_nButtonPinHome = m_Properties.GetNumber ("ButtonPinHome", 0);
m_nButtonPinBack = m_Properties.GetNumber ("ButtonPinBack", 11);
m_nButtonPinSelect = m_Properties.GetNumber ("ButtonPinSelect", 11);
m_nButtonPinHome = m_Properties.GetNumber ("ButtonPinHome", 11);
m_ButtonActionPrev = m_Properties.GetString ("ButtonActionPrev", "");
m_ButtonActionNext = m_Properties.GetString ("ButtonActionNext", "");
m_ButtonActionBack = m_Properties.GetString ("ButtonActionBack", "doubleclick");
m_ButtonActionSelect = m_Properties.GetString ("ButtonActionSelect", "click");
m_ButtonActionHome = m_Properties.GetString ("ButtonActionHome", "longpress");
m_bEncoderEnabled = m_Properties.GetNumber ("EncoderEnabled", 0) != 0;
m_nEncoderPinClock = m_Properties.GetNumber ("EncoderPinClock", 10);
@ -211,6 +217,31 @@ unsigned CConfig::GetButtonPinHome (void) const
return m_nButtonPinHome;
}
const char *CConfig::GetButtonActionPrev (void) const
{
return m_ButtonActionPrev.c_str();
}
const char *CConfig::GetButtonActionNext (void) const
{
return m_ButtonActionNext.c_str();
}
const char *CConfig::GetButtonActionBack (void) const
{
return m_ButtonActionBack.c_str();
}
const char *CConfig::GetButtonActionSelect (void) const
{
return m_ButtonActionSelect.c_str();
}
const char *CConfig::GetButtonActionHome (void) const
{
return m_ButtonActionHome.c_str();
}
bool CConfig::GetEncoderEnabled (void) const
{
return m_bEncoderEnabled;

@ -96,6 +96,13 @@ public:
unsigned GetButtonPinSelect (void) const;
unsigned GetButtonPinHome (void) const;
// Action type for buttons: "click", "doubleclick", "longpress", ""
const char *GetButtonActionPrev (void) const;
const char *GetButtonActionNext (void) const;
const char *GetButtonActionBack (void) const;
const char *GetButtonActionSelect (void) const;
const char *GetButtonActionHome (void) const;
// KY-040 Rotary Encoder
// GPIO pin numbers are chip numbers, not header positions
bool GetEncoderEnabled (void) const;
@ -137,6 +144,12 @@ private:
unsigned m_nButtonPinSelect;
unsigned m_nButtonPinHome;
std::string m_ButtonActionPrev;
std::string m_ButtonActionNext;
std::string m_ButtonActionBack;
std::string m_ButtonActionSelect;
std::string m_ButtonActionHome;
bool m_bEncoderEnabled;
unsigned m_nEncoderPinClock;
unsigned m_nEncoderPinData;

@ -470,6 +470,6 @@ void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const unsigned nCable
for(Iterator = s_DeviceMap.begin(); Iterator != s_DeviceMap.end(); ++Iterator)
{
Iterator->second->Send (voicedump, sizeof(voicedump)*sizeof(uint8_t));
LOGDBG("Send SYSEX voice dump %u to \"%s\"",nVoice,Iterator->first.c_str());
// LOGDBG("Send SYSEX voice dump %u to \"%s\"",nVoice,Iterator->first.c_str());
}
}

@ -28,17 +28,17 @@ LCDPinData7=25
LCDI2CAddress=0x00
# GPIO Button Navigation
# There are two suggested button schemes:
# 5 Buttons based on an "arrow keypad" with a "home"
# 2 Buttons "back" and "select" for use in addition to a rotary encoder
#
# Any buttons set to 0 will be ignored
#
ButtonPinPrev=16
ButtonPinNext=20
ButtonPinBack=5
ButtonPinSelect=6
ButtonPinHome=12
ButtonPinPrev=0
ButtonActionPrev=
ButtonPinNext=0
ButtonActionNext=
ButtonPinBack=11
ButtonActionBack=doubleclick
ButtonPinSelect=11
ButtonActionSelect=click
ButtonPinHome=11
ButtonActionHome=longpress
# KY-040 Rotary Encoder
EncoderEnabled=1

@ -23,81 +23,219 @@
#include "uibuttons.h"
#include <circle/logger.h>
#include <assert.h>
#include <circle/timer.h>
#include <string.h>
LOGMODULE ("uibuttons");
CUIButton::CUIButton (unsigned nPin)
: m_nPin (nPin),
m_pPin (0),
m_nLastValue (0)
{
CUIButton::CUIButton (void)
: m_pinNumber (0),
m_pin (0),
m_lastValue (1),
m_timer (LONG_PRESS_TIME),
m_debounceTimer (DEBOUNCE_TIME),
m_numClicks (0),
m_clickEvent(BtnEventNone),
m_doubleClickEvent(BtnEventNone),
m_longPressEvent(BtnEventNone) {
}
CUIButton::~CUIButton (void)
{
if (m_pPin)
if (m_pin)
{
delete m_pPin;
delete m_pin;
}
}
boolean CUIButton::Initialize (void)
void CUIButton::reset (void)
{
assert (!m_pPin);
m_timer = LONG_PRESS_TIME;
m_numClicks = 0;
}
boolean CUIButton::Initialize (unsigned pinNumber)
{
assert (!m_pin);
m_pinNumber = pinNumber;
if (m_nPin != 0)
if (m_pinNumber != 0)
{
m_pPin = new CGPIOPin (m_nPin, GPIOModeInputPullUp);
m_pin = new CGPIOPin (m_pinNumber, GPIOModeInputPullUp);
}
return TRUE;
}
#define DEBOUNCER 50
void CUIButton::setClickEvent(BtnEvent clickEvent)
{
m_clickEvent = clickEvent;
}
void CUIButton::setDoubleClickEvent(BtnEvent doubleClickEvent)
{
m_doubleClickEvent = doubleClickEvent;
}
void CUIButton::setLongPressEvent(BtnEvent longPressEvent)
{
m_longPressEvent = longPressEvent;
}
boolean CUIButton::Read (void)
unsigned CUIButton::getPinNumber(void)
{
if (!m_pPin)
return m_pinNumber;
}
CUIButton::BtnTrigger CUIButton::ReadTrigger (void)
{
if (!m_pin)
{
// Always return "not pressed" if not configured
return FALSE;
return BtnTriggerNone;
}
unsigned nValue = m_pPin->Read();
unsigned value = m_pin->Read();
// TODO: handle long press
if (m_timer < LONG_PRESS_TIME) {
m_timer++;
if (m_timer == DOUBLE_CLICK_TIME && m_lastValue == 1 && m_numClicks == 1) {
// The user has clicked and released the button once within the
// timeout - this must be a single click
reset();
LOGDBG ("Click");
return BtnTriggerClick;
}
if (m_timer == LONG_PRESS_TIME) {
if (m_lastValue == 0 && m_numClicks == 1) {
// Single long press
reset();
LOGDBG ("Long Press");
return BtnTriggerLongPress;
}
else {
// Just reset it - we've run out of possible interactions
reset();
}
}
}
// Debounce here - we don't need to do anything if the debounce timer is active
if (m_debounceTimer < DEBOUNCE_TIME) {
m_debounceTimer++;
return BtnTriggerNone;
}
// Buttons in PULL UP mode are "active low"
if (nValue == 0)
if (value == 0)
{
// Some simple debouncing...
if (m_nLastValue < DEBOUNCER)
{
m_nLastValue++;
if (m_lastValue == 0) {
// 0 -> 0 : Button is pressed, was already pressed
}
else if (m_nLastValue == DEBOUNCER)
{
m_nLastValue++;
return TRUE;
}
else
{
// Do nothing until reset/cleared
else {
// 1 -> 0 : Button was not pressed but is now pressed
m_lastValue = 0;
m_debounceTimer = 0;
LOGDBG ("Down");
if (m_numClicks == 0) {
// No clicks recorded - start a new timer
m_timer = 0;
}
if (m_numClicks < 2) {
m_numClicks++;
}
if (m_numClicks == 2) {
reset();
LOGDBG ("Double Click");
return BtnTriggerDoubleClick;
}
}
}
else
{
m_nLastValue = 0;
if (m_lastValue == 1) {
// 1 -> 1 : Button is not pressed, was already not pressed
}
else {
// 0 -> 1 : Button was pressed but is now not pressed
m_lastValue = 1;
m_debounceTimer = 0;
LOGDBG ("Up");
if (m_timer >= DOUBLE_CLICK_TIME && m_timer < LONG_PRESS_TIME && m_numClicks == 1) {
// The user released the button after the double click
// timeout, but before the long press timeout
reset();
LOGDBG ("Click");
return BtnTriggerClick;
}
}
}
return FALSE;
return BtnTriggerNone;
}
CUIButton::BtnEvent CUIButton::Read (void) {
BtnTrigger trigger = ReadTrigger();
if (trigger == BtnTriggerClick) {
return m_clickEvent;
}
else if (trigger == BtnTriggerDoubleClick) {
return m_doubleClickEvent;
}
else if (trigger == BtnTriggerLongPress) {
return m_longPressEvent;
}
assert (trigger == BtnTriggerNone);
return BtnEventNone;
}
CUIButtons::CUIButtons (unsigned nPrevPin, unsigned nNextPin, unsigned nBackPin, unsigned nSelectPin, unsigned nHomePin)
: m_PrevButton (nPrevPin),
m_NextButton (nNextPin),
m_BackButton (nBackPin),
m_SelectButton (nSelectPin),
m_HomeButton (nHomePin),
m_pEventHandler (0)
CUIButton::BtnTrigger CUIButton::triggerTypeFromString(const char* triggerString)
{
if (strcmp(triggerString, "") == 0 || strcmp(triggerString, "none") == 0) {
return BtnTriggerNone;
}
else if (strcmp(triggerString, "click") == 0) {
return BtnTriggerClick;
}
else if (strcmp(triggerString, "doubleclick") == 0) {
return BtnTriggerDoubleClick;
}
else if (strcmp(triggerString, "longpress") == 0) {
return BtnTriggerLongPress;
}
LOGERR("Invalid action: %s", triggerString);
return BtnTriggerNone;
}
CUIButtons::CUIButtons (
unsigned prevPin, const char *prevAction,
unsigned nextPin, const char *nextAction,
unsigned backPin, const char *backAction,
unsigned selectPin, const char *selectAction,
unsigned homePin, const char *homeAction
)
: m_prevPin(prevPin),
m_prevAction(CUIButton::triggerTypeFromString(prevAction)),
m_nextPin(nextPin),
m_nextAction(CUIButton::triggerTypeFromString(nextAction)),
m_backPin(backPin),
m_backAction(CUIButton::triggerTypeFromString(backAction)),
m_selectPin(selectPin),
m_selectAction(CUIButton::triggerTypeFromString(selectAction)),
m_homePin(homePin),
m_homeAction(CUIButton::triggerTypeFromString(homeAction)),
m_eventHandler (0),
m_lastTick (0)
{
}
@ -107,51 +245,116 @@ CUIButtons::~CUIButtons (void)
boolean CUIButtons::Initialize (void)
{
m_PrevButton.Initialize ();
m_NextButton.Initialize ();
m_BackButton.Initialize ();
m_SelectButton.Initialize ();
m_HomeButton.Initialize ();
// Each button can be assigned up to 3 actions: click, doubleclick and
// longpress. We may not initialise all of the buttons
unsigned pins[MAX_BUTTONS] = {
m_prevPin, m_nextPin, m_backPin, m_selectPin, m_homePin
};
CUIButton::BtnTrigger triggers[MAX_BUTTONS] = {
m_prevAction, m_nextAction, m_backAction, m_selectAction, m_homeAction
// CUIButton::BtnTriggerNone,
// CUIButton::BtnTriggerNone,
// CUIButton::BtnTriggerDoubleClick,
// CUIButton::BtnTriggerClick,
// CUIButton::BtnTriggerLongPress
};
CUIButton::BtnEvent events[MAX_BUTTONS] = {
CUIButton::BtnEventPrev,
CUIButton::BtnEventNext,
CUIButton::BtnEventBack,
CUIButton::BtnEventSelect,
CUIButton::BtnEventHome
};
for (unsigned i=0; i<MAX_BUTTONS; i++) {
// if this pin is 0 it means it's disabled - so continue
if (pins[i] == 0) {
continue;
}
// Search through buttons and see if there is already a button with this
// pin number. If we find a 0 then the button is not initialised and is
// ready for this pin
for (unsigned j=0; j<MAX_BUTTONS; j++) {
if (m_buttons[j].getPinNumber() == pins[i]) {
// This pin is already assigned
break;
}
else if (m_buttons[j].getPinNumber() == 0) {
// This is un-initialised so can be assigned
m_buttons[j].Initialize(pins[i]);
break;
}
}
}
// All of the buttons are now initialised, they just need to have their
// events assigned to them
for (unsigned i=0; i<MAX_BUTTONS; i++) {
bindButton(pins[i], triggers[i], events[i]);
}
return TRUE;
}
void CUIButtons::RegisterEventHandler (TBtnEventHandler *pHandler, void *pParam)
void CUIButtons::bindButton(unsigned pinNumber, CUIButton::BtnTrigger trigger, CUIButton::BtnEvent event)
{
// First find the button
bool found = false;
for (unsigned i=0; i<MAX_BUTTONS; i++) {
if (m_buttons[i].getPinNumber() == pinNumber) {
// This is the one!
found = true;
if (trigger == CUIButton::BtnTriggerClick) {
m_buttons[i].setClickEvent(event);
}
else if (trigger == CUIButton::BtnTriggerDoubleClick) {
m_buttons[i].setDoubleClickEvent(event);
}
else if (trigger == CUIButton::BtnTriggerLongPress) {
m_buttons[i].setLongPressEvent(event);
}
else {
assert (trigger == CUIButton::BtnTriggerNone);
}
break;
}
}
assert(found);
}
void CUIButtons::RegisterEventHandler (BtnEventHandler *handler, void *param)
{
assert (!m_pEventHandler);
m_pEventHandler = pHandler;
assert (m_pEventHandler);
m_pEventParam = pParam;
assert (!m_eventHandler);
m_eventHandler = handler;
assert (m_eventHandler);
m_eventParam = param;
}
void CUIButtons::Update (void)
{
assert (m_pEventHandler);
assert (m_eventHandler);
if (m_PrevButton.Read ())
{
LOGDBG ("Prev");
(*m_pEventHandler) (BtnEventPrev, m_pEventParam);
}
if (m_NextButton.Read ())
{
LOGDBG ("Next");
(*m_pEventHandler) (BtnEventNext, m_pEventParam);
// Don't update unless enough time has elapsed.
// Get the current time in microseconds
unsigned currentTick = CTimer::GetClockTicks();
if (currentTick - m_lastTick < BUTTONS_UPDATE_NUM_TICKS) {
// Not enough time has passed, just return
return;
}
if (m_BackButton.Read ())
{
LOGDBG ("Back");
(*m_pEventHandler) (BtnEventBack, m_pEventParam);
}
if (m_SelectButton.Read ())
{
LOGDBG ("Select");
(*m_pEventHandler) (BtnEventSelect, m_pEventParam);
}
if (m_HomeButton.Read ())
{
LOGDBG ("Home");
(*m_pEventHandler) (BtnEventHome, m_pEventParam);
m_lastTick = currentTick;
for (unsigned i=0; i<MAX_BUTTONS; i++) {
CUIButton::BtnEvent event = m_buttons[i].Read();
if (event != CUIButton::BtnEventNone) {
LOGDBG("Event: %u", event);
(*m_eventHandler) (event, m_eventParam);
}
}
}

@ -27,56 +27,117 @@
#include <circle/types.h>
#include "config.h"
#define BUTTONS_UPDATE_NUM_TICKS 100
#define DEBOUNCE_TIME 50
#define DOUBLE_CLICK_TIME 1700
#define LONG_PRESS_TIME 4500
#define MAX_BUTTONS 5
class CUIButton
{
public:
CUIButton (unsigned nPin);
enum BtnTrigger
{
BtnTriggerNone = 0,
BtnTriggerClick = 1,
BtnTriggerDoubleClick = 2,
BtnTriggerLongPress = 3
};
enum BtnEvent
{
BtnEventNone = 0,
BtnEventPrev = 1,
BtnEventNext = 2,
BtnEventBack = 3,
BtnEventSelect = 4,
BtnEventHome = 5,
BtnEventUnknown = 6
};
CUIButton (void);
~CUIButton (void);
boolean Initialize (void);
void reset (void);
boolean Initialize (unsigned pinNumber);
void setClickEvent(BtnEvent clickEvent);
void setDoubleClickEvent(BtnEvent doubleClickEvent);
void setLongPressEvent(BtnEvent longPressEvent);
unsigned getPinNumber(void);
boolean Read (void);
BtnTrigger ReadTrigger (void);
BtnEvent Read (void);
static BtnTrigger triggerTypeFromString(const char* triggerString);
private:
unsigned m_nPin;
CGPIOPin *m_pPin;
unsigned m_nLastValue;
// Pin number
unsigned m_pinNumber;
// GPIO pin
CGPIOPin *m_pin;
unsigned m_lastValue;
// Set to 0 on press, increment each read, use to trigger events
uint16_t m_timer;
// Debounce timer
uint16_t m_debounceTimer;
// Number of clicks recorded since last timer reset
uint8_t m_numClicks;
public:
// Event to fire on click
BtnEvent m_clickEvent;
// Event to fire on double click
BtnEvent m_doubleClickEvent;
// Event to fire on long press
BtnEvent m_longPressEvent;
// The value of the pin at the end of the last loop
};
class CUIButtons
{
public:
enum TBtnEvent
{
BtnEventPrev,
BtnEventNext,
BtnEventBack,
BtnEventSelect,
BtnEventHome,
BtnEventUnknown
};
typedef void TBtnEventHandler (TBtnEvent Event, void *pParam);
typedef void BtnEventHandler (CUIButton::BtnEvent Event, void *param);
public:
CUIButtons (unsigned nPrevPin = 0, unsigned nNextPin = 0, unsigned nBackPin = 0, unsigned nSelectPin = 0, unsigned nHomePin = 0);
CUIButtons (
unsigned prevPin, const char *prevAction,
unsigned nextPin, const char *nextAction,
unsigned backPin, const char *backAction,
unsigned selectPin, const char *selectAction,
unsigned homePin, const char *homeAction
);
~CUIButtons (void);
boolean Initialize (void);
void RegisterEventHandler (TBtnEventHandler *pHandler, void *pParam = 0);
void RegisterEventHandler (BtnEventHandler *handler, void *param = 0);
void Update (void);
private:
CUIButton m_PrevButton;
CUIButton m_NextButton;
CUIButton m_BackButton;
CUIButton m_SelectButton;
CUIButton m_HomeButton;
// Up to 5 buttons can be defined
CUIButton m_buttons[5];
// Configuration for buttons
unsigned m_prevPin;
CUIButton::BtnTrigger m_prevAction;
unsigned m_nextPin;
CUIButton::BtnTrigger m_nextAction;
unsigned m_backPin;
CUIButton::BtnTrigger m_backAction;
unsigned m_selectPin;
CUIButton::BtnTrigger m_selectAction;
unsigned m_homePin;
CUIButton::BtnTrigger m_homeAction;
BtnEventHandler *m_eventHandler;
void *m_eventParam;
unsigned m_lastTick;
TBtnEventHandler *m_pEventHandler;
void *m_pEventParam;
void bindButton(unsigned pinNumber, CUIButton::BtnTrigger trigger, CUIButton::BtnEvent event);
};
#endif

@ -88,10 +88,15 @@ bool CUserInterface::Initialize (void)
}
m_pUIButtons = new CUIButtons ( m_pConfig->GetButtonPinPrev (),
m_pConfig->GetButtonActionPrev (),
m_pConfig->GetButtonPinNext (),
m_pConfig->GetButtonActionNext (),
m_pConfig->GetButtonPinBack (),
m_pConfig->GetButtonActionBack (),
m_pConfig->GetButtonPinSelect (),
m_pConfig->GetButtonPinHome ());
m_pConfig->GetButtonActionSelect (),
m_pConfig->GetButtonPinHome (),
m_pConfig->GetButtonActionHome ());
assert (m_pUIButtons);
if (!m_pUIButtons->Initialize ())
@ -262,27 +267,27 @@ void CUserInterface::EncoderEventStub (CKY040::TEvent Event, void *pParam)
pThis->EncoderEventHandler (Event);
}
void CUserInterface::UIButtonsEventHandler (CUIButtons::TBtnEvent Event)
void CUserInterface::UIButtonsEventHandler (CUIButton::BtnEvent Event)
{
switch (Event)
{
case CUIButtons::BtnEventPrev:
case CUIButton::BtnEventPrev:
m_Menu.EventHandler (CUIMenu::MenuEventStepDown);
break;
case CUIButtons::BtnEventNext:
case CUIButton::BtnEventNext:
m_Menu.EventHandler (CUIMenu::MenuEventStepUp);
break;
case CUIButtons::BtnEventBack:
case CUIButton::BtnEventBack:
m_Menu.EventHandler (CUIMenu::MenuEventBack);
break;
case CUIButtons::BtnEventSelect:
case CUIButton::BtnEventSelect:
m_Menu.EventHandler (CUIMenu::MenuEventSelect);
break;
case CUIButtons::BtnEventHome:
case CUIButton::BtnEventHome:
m_Menu.EventHandler (CUIMenu::MenuEventHome);
break;
@ -291,7 +296,7 @@ void CUserInterface::UIButtonsEventHandler (CUIButtons::TBtnEvent Event)
}
}
void CUserInterface::UIButtonsEventStub (CUIButtons::TBtnEvent Event, void *pParam)
void CUserInterface::UIButtonsEventStub (CUIButton::BtnEvent Event, void *pParam)
{
CUserInterface *pThis = static_cast<CUserInterface *> (pParam);
assert (pThis != 0);

@ -56,8 +56,8 @@ private:
void EncoderEventHandler (CKY040::TEvent Event);
static void EncoderEventStub (CKY040::TEvent Event, void *pParam);
void UIButtonsEventHandler (CUIButtons::TBtnEvent Event);
static void UIButtonsEventStub (CUIButtons::TBtnEvent Event, void *pParam);
void UIButtonsEventHandler (CUIButton::BtnEvent Event);
static void UIButtonsEventStub (CUIButton::BtnEvent Event, void *pParam);
private:
CMiniDexed *m_pMiniDexed;

Loading…
Cancel
Save