Configurable GPIO buttons (#274)

* Default behavior changed: Click to enter menus, long-press to leave menus, double-click to go to the home screen (configurable)
* Buttons can have click, double click and long press events
* Changed default config, added new options and renamed EncoderPinSwitch to ButtonPinShortcut

Co-authored-by: Kevin <diyelectromusic@gmail.com>
Co-authored-by: Stephen Brown <steve@fig14.com>
pull/287/head^2
probonopd 2 years ago committed by GitHub
parent 0a5ec7d529
commit 50e9b7b8e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .gitignore
  2. 2
      src/Makefile
  3. 87
      src/config.cpp
  4. 38
      src/config.h
  5. 2
      src/mididevice.cpp
  6. 20
      src/minidexed.ini
  7. 392
      src/uibuttons.cpp
  8. 154
      src/uibuttons.h
  9. 101
      src/userinterface.cpp
  10. 5
      src/userinterface.h

4
.gitignore vendored

@ -41,3 +41,7 @@ MiniDexed*
sdcard sdcard
*.zip *.zip
*.img *.img
# Editor related files
*.swp
*.swo

@ -9,7 +9,7 @@ CMSIS_DIR = ../CMSIS_5/CMSIS
OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \
mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \
sysexfileloader.o performanceconfig.o perftimer.o \ sysexfileloader.o performanceconfig.o perftimer.o \
effect_compressor.o effect_platervbstereo.o effect_compressor.o effect_platervbstereo.o uibuttons.o
OPTIMIZE = -O3 OPTIMIZE = -O3

@ -81,10 +81,25 @@ void CConfig::Load (void)
m_nLCDPinData7 = m_Properties.GetNumber ("LCDPinData7", 25); m_nLCDPinData7 = m_Properties.GetNumber ("LCDPinData7", 25);
m_nLCDI2CAddress = m_Properties.GetNumber ("LCDI2CAddress", 0); m_nLCDI2CAddress = m_Properties.GetNumber ("LCDI2CAddress", 0);
m_nButtonPinPrev = m_Properties.GetNumber ("ButtonPinPrev", 0);
m_nButtonPinNext = m_Properties.GetNumber ("ButtonPinNext", 0);
m_nButtonPinBack = m_Properties.GetNumber ("ButtonPinBack", 11);
m_nButtonPinSelect = m_Properties.GetNumber ("ButtonPinSelect", 11);
m_nButtonPinHome = m_Properties.GetNumber ("ButtonPinHome", 11);
m_nButtonPinShortcut = m_Properties.GetNumber ("ButtonPinShortcut", 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_nDoubleClickTimeout = m_Properties.GetNumber ("DoubleClickTimeout", 400);
m_nLongPressTimeout = m_Properties.GetNumber ("LongPressTimeout", 600);
m_bEncoderEnabled = m_Properties.GetNumber ("EncoderEnabled", 0) != 0; m_bEncoderEnabled = m_Properties.GetNumber ("EncoderEnabled", 0) != 0;
m_nEncoderPinClock = m_Properties.GetNumber ("EncoderPinClock", 10); m_nEncoderPinClock = m_Properties.GetNumber ("EncoderPinClock", 10);
m_nEncoderPinData = m_Properties.GetNumber ("EncoderPinData", 9); m_nEncoderPinData = m_Properties.GetNumber ("EncoderPinData", 9);
m_nEncoderPinSwitch = m_Properties.GetNumber ("EncoderPinSwitch", 11);
m_bMIDIDumpEnabled = m_Properties.GetNumber ("MIDIDumpEnabled", 0) != 0; m_bMIDIDumpEnabled = m_Properties.GetNumber ("MIDIDumpEnabled", 0) != 0;
m_bProfileEnabled = m_Properties.GetNumber ("ProfileEnabled", 0) != 0; m_bProfileEnabled = m_Properties.GetNumber ("ProfileEnabled", 0) != 0;
@ -180,6 +195,71 @@ unsigned CConfig::GetLCDI2CAddress (void) const
return m_nLCDI2CAddress; return m_nLCDI2CAddress;
} }
unsigned CConfig::GetButtonPinPrev (void) const
{
return m_nButtonPinPrev;
}
unsigned CConfig::GetButtonPinNext (void) const
{
return m_nButtonPinNext;
}
unsigned CConfig::GetButtonPinBack (void) const
{
return m_nButtonPinBack;
}
unsigned CConfig::GetButtonPinSelect (void) const
{
return m_nButtonPinSelect;
}
unsigned CConfig::GetButtonPinHome (void) const
{
return m_nButtonPinHome;
}
unsigned CConfig::GetButtonPinShortcut (void) const
{
return m_nButtonPinShortcut;
}
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();
}
unsigned CConfig::GetDoubleClickTimeout (void) const
{
return m_nDoubleClickTimeout;
}
unsigned CConfig::GetLongPressTimeout (void) const
{
return m_nLongPressTimeout;
}
bool CConfig::GetEncoderEnabled (void) const bool CConfig::GetEncoderEnabled (void) const
{ {
return m_bEncoderEnabled; return m_bEncoderEnabled;
@ -195,11 +275,6 @@ unsigned CConfig::GetEncoderPinData (void) const
return m_nEncoderPinData; return m_nEncoderPinData;
} }
unsigned CConfig::GetEncoderPinSwitch (void) const
{
return m_nEncoderPinSwitch;
}
bool CConfig::GetMIDIDumpEnabled (void) const bool CConfig::GetMIDIDumpEnabled (void) const
{ {
return m_bMIDIDumpEnabled; return m_bMIDIDumpEnabled;

@ -87,13 +87,32 @@ public:
unsigned GetLCDPinData6 (void) const; unsigned GetLCDPinData6 (void) const;
unsigned GetLCDPinData7 (void) const; unsigned GetLCDPinData7 (void) const;
unsigned GetLCDI2CAddress (void) const; unsigned GetLCDI2CAddress (void) const;
// GPIO Button Navigation
// GPIO pin numbers are chip numbers, not header positions
unsigned GetButtonPinPrev (void) const;
unsigned GetButtonPinNext (void) const;
unsigned GetButtonPinBack (void) const;
unsigned GetButtonPinSelect (void) const;
unsigned GetButtonPinHome (void) const;
unsigned GetButtonPinShortcut (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;
// Timeouts for button events in milliseconds
unsigned GetDoubleClickTimeout (void) const;
unsigned GetLongPressTimeout (void) const;
// KY-040 Rotary Encoder // KY-040 Rotary Encoder
// GPIO pin numbers are chip numbers, not header positions // GPIO pin numbers are chip numbers, not header positions
bool GetEncoderEnabled (void) const; bool GetEncoderEnabled (void) const;
unsigned GetEncoderPinClock (void) const; unsigned GetEncoderPinClock (void) const;
unsigned GetEncoderPinData (void) const; unsigned GetEncoderPinData (void) const;
unsigned GetEncoderPinSwitch (void) const;
// Debug // Debug
bool GetMIDIDumpEnabled (void) const; bool GetMIDIDumpEnabled (void) const;
@ -122,11 +141,26 @@ private:
unsigned m_nLCDPinData6; unsigned m_nLCDPinData6;
unsigned m_nLCDPinData7; unsigned m_nLCDPinData7;
unsigned m_nLCDI2CAddress; unsigned m_nLCDI2CAddress;
unsigned m_nButtonPinPrev;
unsigned m_nButtonPinNext;
unsigned m_nButtonPinBack;
unsigned m_nButtonPinSelect;
unsigned m_nButtonPinHome;
unsigned m_nButtonPinShortcut;
std::string m_ButtonActionPrev;
std::string m_ButtonActionNext;
std::string m_ButtonActionBack;
std::string m_ButtonActionSelect;
std::string m_ButtonActionHome;
unsigned m_nDoubleClickTimeout;
unsigned m_nLongPressTimeout;
bool m_bEncoderEnabled; bool m_bEncoderEnabled;
unsigned m_nEncoderPinClock; unsigned m_nEncoderPinClock;
unsigned m_nEncoderPinData; unsigned m_nEncoderPinData;
unsigned m_nEncoderPinSwitch;
bool m_bMIDIDumpEnabled; bool m_bMIDIDumpEnabled;
bool m_bProfileEnabled; bool m_bProfileEnabled;

@ -470,6 +470,6 @@ void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const unsigned nCable
for(Iterator = s_DeviceMap.begin(); Iterator != s_DeviceMap.end(); ++Iterator) for(Iterator = s_DeviceMap.begin(); Iterator != s_DeviceMap.end(); ++Iterator)
{ {
Iterator->second->Send (voicedump, sizeof(voicedump)*sizeof(uint8_t)); 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());
} }
} }

@ -27,11 +27,29 @@ LCDPinData6=24
LCDPinData7=25 LCDPinData7=25
LCDI2CAddress=0x00 LCDI2CAddress=0x00
# GPIO Button Navigation
# Any buttons set to 0 will be ignored
ButtonPinPrev=0
ButtonActionPrev=
ButtonPinNext=0
ButtonActionNext=
ButtonPinBack=11
ButtonActionBack=longpress
ButtonPinSelect=11
ButtonActionSelect=click
ButtonPinHome=11
ButtonActionHome=doubleclick
ButtonPinShortcut=11
# (Shortcut doesn't have an action)
# Timeouts in milliseconds for double click and long press
DoubleClickTimeout=400
LongPressTimeout=400
# KY-040 Rotary Encoder # KY-040 Rotary Encoder
EncoderEnabled=1 EncoderEnabled=1
EncoderPinClock=10 EncoderPinClock=10
EncoderPinData=9 EncoderPinData=9
EncoderPinSwitch=11
# Debug # Debug
MIDIDumpEnabled=0 MIDIDumpEnabled=0

@ -0,0 +1,392 @@
//
// uibuttons.cpp
//
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
// Copyright (C) 2022 The MiniDexed Team
//
// Original author of this class:
// R. Stange <rsta2@o2online.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include "uibuttons.h"
#include <circle/logger.h>
#include <assert.h>
#include <circle/timer.h>
#include <string.h>
LOGMODULE ("uibuttons");
CUIButton::CUIButton (void)
: m_pinNumber (0),
m_pin (0),
m_lastValue (1),
m_timer (0),
m_debounceTimer (0),
m_numClicks (0),
m_clickEvent(BtnEventNone),
m_doubleClickEvent(BtnEventNone),
m_longPressEvent(BtnEventNone),
m_doubleClickTimeout(0),
m_longPressTimeout(0)
{
}
CUIButton::~CUIButton (void)
{
if (m_pin)
{
delete m_pin;
}
}
void CUIButton::reset (void)
{
m_timer = m_longPressTimeout;
m_numClicks = 0;
}
boolean CUIButton::Initialize (unsigned pinNumber, unsigned doubleClickTimeout, unsigned longPressTimeout)
{
assert (!m_pin);
assert(longPressTimeout >= doubleClickTimeout);
m_pinNumber = pinNumber;
m_doubleClickTimeout = doubleClickTimeout;
m_longPressTimeout = longPressTimeout;
// Initialise timing values
m_timer = m_longPressTimeout;
m_debounceTimer = DEBOUNCE_TIME;
if (m_pinNumber != 0)
{
m_pin = new CGPIOPin (m_pinNumber, GPIOModeInputPullUp);
}
return TRUE;
}
void CUIButton::setClickEvent(BtnEvent clickEvent)
{
m_clickEvent = clickEvent;
}
void CUIButton::setDoubleClickEvent(BtnEvent doubleClickEvent)
{
m_doubleClickEvent = doubleClickEvent;
}
void CUIButton::setLongPressEvent(BtnEvent longPressEvent)
{
m_longPressEvent = longPressEvent;
}
unsigned CUIButton::getPinNumber(void)
{
return m_pinNumber;
}
CUIButton::BtnTrigger CUIButton::ReadTrigger (void)
{
if (!m_pin)
{
// Always return "not pressed" if not configured
return BtnTriggerNone;
}
unsigned value = m_pin->Read();
if (m_timer < m_longPressTimeout) {
m_timer++;
if (m_timer == m_doubleClickTimeout && 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();
return BtnTriggerClick;
}
if (m_timer == m_longPressTimeout) {
if (m_lastValue == 0 && m_numClicks == 1) {
// Single long press
reset();
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 (value == 0)
{
if (m_lastValue == 0) {
// 0 -> 0 : Button is pressed, was already pressed
}
else {
// 1 -> 0 : Button was not pressed but is now pressed
m_lastValue = 0;
m_debounceTimer = 0;
if (m_numClicks == 0) {
// No clicks recorded - start a new timer
m_timer = 0;
}
if (m_numClicks < 2) {
m_numClicks++;
}
}
}
else
{
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 (it was released)
m_lastValue = 1;
m_debounceTimer = 0;
if (m_numClicks == 1 &&
(m_doubleClickEvent == BtnEventNone ||
m_timer >= m_doubleClickTimeout && m_timer < m_longPressTimeout)
) {
// Either the user released the button when there is no double
// click mapped
// OR:
// The user released the button after the double click
// timeout, but before the long press timeout
reset();
return BtnTriggerClick;
}
else if (m_numClicks == 2) {
// This is the second release in a short period of time
reset();
return BtnTriggerDoubleClick;
}
}
}
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;
}
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,
unsigned doubleClickTimeout, unsigned longPressTimeout
)
: m_doubleClickTimeout(doubleClickTimeout),
m_longPressTimeout(longPressTimeout),
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)
{
}
CUIButtons::~CUIButtons (void)
{
}
boolean CUIButtons::Initialize (void)
{
// First sanity check and convert the timeouts:
// Internally values are in tenths of a millisecond, but config values
// are in milliseconds
unsigned doubleClickTimeout = m_doubleClickTimeout * 10;
unsigned longPressTimeout = m_longPressTimeout * 10;
if (longPressTimeout < doubleClickTimeout) {
// This is invalid - long press must be longest timeout
LOGERR("LongPressTimeout (%u) should not be shorter than DoubleClickTimeout (%u)",
m_longPressTimeout,
m_doubleClickTimeout);
// Just make long press as long as double click
longPressTimeout = doubleClickTimeout;
}
// 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::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], doubleClickTimeout, longPressTimeout);
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::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_eventHandler);
m_eventHandler = handler;
assert (m_eventHandler);
m_eventParam = param;
}
void CUIButtons::Update (void)
{
assert (m_eventHandler);
// 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;
}
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);
}
}
}
void CUIButtons::ResetButton (unsigned pinNumber)
{
for (unsigned i=0; i<MAX_BUTTONS; i++) {
if (m_buttons[i].getPinNumber() == pinNumber) {
m_buttons[i].reset();
}
}
}

@ -0,0 +1,154 @@
//
// uibuttons.h
//
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
// Copyright (C) 2022 The MiniDexed Team
//
// Original author of this class:
// R. Stange <rsta2@o2online.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#ifndef _uibuttons_h
#define _uibuttons_h
#include <circle/gpiopin.h>
#include <circle/types.h>
#include "config.h"
#define BUTTONS_UPDATE_NUM_TICKS 100
#define DEBOUNCE_TIME 100
#define MAX_BUTTONS 5
class CUIButtons;
class CUIButton
{
public:
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);
void reset (void);
boolean Initialize (unsigned pinNumber, unsigned doubleClickTimeout, unsigned longPressTimeout);
void setClickEvent(BtnEvent clickEvent);
void setDoubleClickEvent(BtnEvent doubleClickEvent);
void setLongPressEvent(BtnEvent longPressEvent);
unsigned getPinNumber(void);
BtnTrigger ReadTrigger (void);
BtnEvent Read (void);
static BtnTrigger triggerTypeFromString(const char* triggerString);
private:
// Pin number
unsigned m_pinNumber;
// GPIO pin
CGPIOPin *m_pin;
// The value of the pin at the end of the last loop
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;
// 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;
// Timeout for double click in tenths of a millisecond
unsigned m_doubleClickTimeout;
// Timeout for long press in tenths of a millisecond
unsigned m_longPressTimeout;
};
class CUIButtons
{
public:
typedef void BtnEventHandler (CUIButton::BtnEvent Event, void *param);
public:
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,
unsigned doubleClickTimeout, unsigned longPressTimeout
);
~CUIButtons (void);
boolean Initialize (void);
void RegisterEventHandler (BtnEventHandler *handler, void *param = 0);
void Update (void);
void ResetButton (unsigned pinNumber);
private:
// Array of 5 buttons
CUIButton m_buttons[MAX_BUTTONS];
// Timeout for double click in tenths of a millisecond
unsigned m_doubleClickTimeout;
// Timeout for long press in tenths of a millisecond
unsigned m_longPressTimeout;
// 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;
void bindButton(unsigned pinNumber, CUIButton::BtnTrigger trigger, CUIButton::BtnEvent event);
};
#endif

@ -34,6 +34,7 @@ CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManag
m_pConfig (pConfig), m_pConfig (pConfig),
m_pLCD (0), m_pLCD (0),
m_pLCDBuffered (0), m_pLCDBuffered (0),
m_pUIButtons (0),
m_pRotaryEncoder (0), m_pRotaryEncoder (0),
m_bSwitchPressed (false), m_bSwitchPressed (false),
m_Menu (this, pMiniDexed) m_Menu (this, pMiniDexed)
@ -43,6 +44,7 @@ CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManag
CUserInterface::~CUserInterface (void) CUserInterface::~CUserInterface (void)
{ {
delete m_pRotaryEncoder; delete m_pRotaryEncoder;
delete m_pUIButtons;
delete m_pLCDBuffered; delete m_pLCDBuffered;
delete m_pLCD; delete m_pLCD;
} }
@ -85,11 +87,34 @@ bool CUserInterface::Initialize (void)
LOGDBG ("LCD initialized"); LOGDBG ("LCD initialized");
} }
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->GetButtonActionSelect (),
m_pConfig->GetButtonPinHome (),
m_pConfig->GetButtonActionHome (),
m_pConfig->GetDoubleClickTimeout (),
m_pConfig->GetLongPressTimeout () );
assert (m_pUIButtons);
if (!m_pUIButtons->Initialize ())
{
return false;
}
m_pUIButtons->RegisterEventHandler (UIButtonsEventStub, this);
LOGDBG ("Button User Interface initialized");
if (m_pConfig->GetEncoderEnabled ()) if (m_pConfig->GetEncoderEnabled ())
{ {
m_pRotaryEncoder = new CKY040 (m_pConfig->GetEncoderPinClock (), m_pRotaryEncoder = new CKY040 (m_pConfig->GetEncoderPinClock (),
m_pConfig->GetEncoderPinData (), m_pConfig->GetEncoderPinData (),
m_pConfig->GetEncoderPinSwitch (), m_pConfig->GetButtonPinShortcut (),
m_pGPIOManager); m_pGPIOManager);
assert (m_pRotaryEncoder); assert (m_pRotaryEncoder);
@ -114,6 +139,10 @@ void CUserInterface::Process (void)
{ {
m_pLCDBuffered->Update (); m_pLCDBuffered->Update ();
} }
if (m_pUIButtons)
{
m_pUIButtons->Update();
}
} }
void CUserInterface::ParameterChanged (void) void CUserInterface::ParameterChanged (void)
@ -197,25 +226,26 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event)
break; break;
case CKY040::EventClockwise: case CKY040::EventClockwise:
m_Menu.EventHandler (m_bSwitchPressed ? CUIMenu::MenuEventPressAndStepUp if (m_bSwitchPressed) {
: CUIMenu::MenuEventStepUp); // We must reset the encoder switch button to prevent events from being
break; // triggered after the encoder is rotated
m_pUIButtons->ResetButton(m_pConfig->GetButtonPinShortcut());
case CKY040::EventCounterclockwise: m_Menu.EventHandler(CUIMenu::MenuEventPressAndStepUp);
m_Menu.EventHandler (m_bSwitchPressed ? CUIMenu::MenuEventPressAndStepDown
: CUIMenu::MenuEventStepDown);
break;
case CKY040::EventSwitchClick:
m_Menu.EventHandler (CUIMenu::MenuEventBack);
break;
case CKY040::EventSwitchDoubleClick: }
m_Menu.EventHandler (CUIMenu::MenuEventSelect); else {
m_Menu.EventHandler(CUIMenu::MenuEventStepUp);
}
break; break;
case CKY040::EventSwitchTripleClick: case CKY040::EventCounterclockwise:
m_Menu.EventHandler (CUIMenu::MenuEventHome); if (m_bSwitchPressed) {
m_pUIButtons->ResetButton(m_pConfig->GetButtonPinShortcut());
m_Menu.EventHandler(CUIMenu::MenuEventPressAndStepDown);
}
else {
m_Menu.EventHandler(CUIMenu::MenuEventStepDown);
}
break; break;
case CKY040::EventSwitchHold: case CKY040::EventSwitchHold:
@ -239,3 +269,40 @@ void CUserInterface::EncoderEventStub (CKY040::TEvent Event, void *pParam)
pThis->EncoderEventHandler (Event); pThis->EncoderEventHandler (Event);
} }
void CUserInterface::UIButtonsEventHandler (CUIButton::BtnEvent Event)
{
switch (Event)
{
case CUIButton::BtnEventPrev:
m_Menu.EventHandler (CUIMenu::MenuEventStepDown);
break;
case CUIButton::BtnEventNext:
m_Menu.EventHandler (CUIMenu::MenuEventStepUp);
break;
case CUIButton::BtnEventBack:
m_Menu.EventHandler (CUIMenu::MenuEventBack);
break;
case CUIButton::BtnEventSelect:
m_Menu.EventHandler (CUIMenu::MenuEventSelect);
break;
case CUIButton::BtnEventHome:
m_Menu.EventHandler (CUIMenu::MenuEventHome);
break;
default:
break;
}
}
void CUserInterface::UIButtonsEventStub (CUIButton::BtnEvent Event, void *pParam)
{
CUserInterface *pThis = static_cast<CUserInterface *> (pParam);
assert (pThis != 0);
pThis->UIButtonsEventHandler (Event);
}

@ -22,6 +22,7 @@
#include "config.h" #include "config.h"
#include "uimenu.h" #include "uimenu.h"
#include "uibuttons.h"
#include <sensor/ky040.h> #include <sensor/ky040.h>
#include <display/hd44780device.h> #include <display/hd44780device.h>
#include <circle/gpiomanager.h> #include <circle/gpiomanager.h>
@ -55,6 +56,8 @@ private:
void EncoderEventHandler (CKY040::TEvent Event); void EncoderEventHandler (CKY040::TEvent Event);
static void EncoderEventStub (CKY040::TEvent Event, void *pParam); static void EncoderEventStub (CKY040::TEvent Event, void *pParam);
void UIButtonsEventHandler (CUIButton::BtnEvent Event);
static void UIButtonsEventStub (CUIButton::BtnEvent Event, void *pParam);
private: private:
CMiniDexed *m_pMiniDexed; CMiniDexed *m_pMiniDexed;
@ -64,6 +67,8 @@ private:
CHD44780Device *m_pLCD; CHD44780Device *m_pLCD;
CWriteBufferDevice *m_pLCDBuffered; CWriteBufferDevice *m_pLCDBuffered;
CUIButtons *m_pUIButtons;
CKY040 *m_pRotaryEncoder; CKY040 *m_pRotaryEncoder;
bool m_bSwitchPressed; bool m_bSwitchPressed;

Loading…
Cancel
Save