Add initial optional rotary encoder support (#40)

* Add driver for KY-040 rotary encoder

This driver has been taken from the Circle develop branch and can be
removed here, when the driver made it up into a circle-stdlib release.

* Add initial optional rotary encoder support

* Can change between voice and bank select mode by clicking the switch
* Turn the knob to select the voice or bank according to current mode
* Hold the switch for 3 seconds to reboot the system
* Program change from MIDI is only displayed in voice select mode
* Configuration for KY-040 added to CConfig and minidexed.ini
* Fix: m_I2CMaster has not been initialized in CKernel
pull/42/head
Rene Stange 3 years ago committed by GitHub
parent 53ece29eff
commit 7707f90d46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Makefile
  2. 25
      src/config.cpp
  3. 12
      src/config.h
  4. 17
      src/kernel.cpp
  5. 2
      src/kernel.h
  6. 315
      src/ky040.cpp
  7. 153
      src/ky040.h
  8. 28
      src/minidexed.cpp
  9. 14
      src/minidexed.h
  10. 6
      src/minidexed.ini
  11. 30
      src/sysexfileloader.cpp
  12. 3
      src/sysexfileloader.h
  13. 123
      src/userinterface.cpp
  14. 25
      src/userinterface.h

@ -7,7 +7,7 @@ SYNTH_DEXED_DIR = ../Synth_Dexed/src
OBJS = main.o kernel.o minidexed.o config.o userinterface.o \
mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \
sysexfileloader.o perftimer.o \
sysexfileloader.o perftimer.o ky040.o \
$(SYNTH_DEXED_DIR)/synth_dexed.o
INCLUDE += -I $(SYNTH_DEXED_DIR)

@ -52,6 +52,11 @@ void CConfig::Load (void)
m_nLCDPinData6 = m_Properties.GetNumber ("LCDPinData6", 24);
m_nLCDPinData7 = m_Properties.GetNumber ("LCDPinData7", 25);
m_bEncoderEnabled = m_Properties.GetNumber ("EncoderEnabled", 0) != 0;
m_nEncoderPinClock = m_Properties.GetNumber ("EncoderPinClock", 5);
m_nEncoderPinData = m_Properties.GetNumber ("EncoderPinData", 6);
m_nEncoderPinSwitch = m_Properties.GetNumber ("EncoderPinSwitch", 26);
m_bMIDIDumpEnabled = m_Properties.GetNumber ("MIDIDumpEnabled", 0) != 0;
m_bProfileEnabled = m_Properties.GetNumber ("ProfileEnabled", 0) != 0;
}
@ -121,6 +126,26 @@ unsigned CConfig::GetLCDPinData7 (void) const
return m_nLCDPinData7;
}
bool CConfig::GetEncoderEnabled (void) const
{
return m_bEncoderEnabled;
}
unsigned CConfig::GetEncoderPinClock (void) const
{
return m_nEncoderPinClock;
}
unsigned CConfig::GetEncoderPinData (void) const
{
return m_nEncoderPinData;
}
unsigned CConfig::GetEncoderPinSwitch (void) const
{
return m_nEncoderPinSwitch;
}
bool CConfig::GetMIDIDumpEnabled (void) const
{
return m_bMIDIDumpEnabled;

@ -67,6 +67,13 @@ public:
unsigned GetLCDPinData6 (void) const;
unsigned GetLCDPinData7 (void) const;
// KY-040 Rotary Encoder
// GPIO pin numbers are chip numbers, not header positions
bool GetEncoderEnabled (void) const;
unsigned GetEncoderPinClock (void) const;
unsigned GetEncoderPinData (void) const;
unsigned GetEncoderPinSwitch (void) const;
// Debug
bool GetMIDIDumpEnabled (void) const;
bool GetProfileEnabled (void) const;
@ -90,6 +97,11 @@ private:
unsigned m_nLCDPinData6;
unsigned m_nLCDPinData7;
bool m_bEncoderEnabled;
unsigned m_nEncoderPinClock;
unsigned m_nEncoderPinData;
unsigned m_nEncoderPinSwitch;
bool m_bMIDIDumpEnabled;
bool m_bProfileEnabled;
};

@ -30,6 +30,7 @@ CKernel *CKernel::s_pThis = 0;
CKernel::CKernel (void)
: CStdlibAppStdio ("minidexed"),
m_Config (&mFileSystem),
m_GPIOManager (&mInterrupt),
m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE),
m_pDexed (0)
{
@ -52,6 +53,16 @@ bool CKernel::Initialize (void)
mLogger.RegisterPanicHandler (PanicHandler);
if (!m_GPIOManager.Initialize ())
{
return FALSE;
}
if (!m_I2CMaster.Initialize ())
{
return FALSE;
}
m_Config.Load ();
// select the sound device
@ -60,19 +71,19 @@ bool CKernel::Initialize (void)
{
LOGNOTE ("I2S mode");
m_pDexed = new CMiniDexedI2S (&m_Config, &mInterrupt, &m_I2CMaster);
m_pDexed = new CMiniDexedI2S (&m_Config, &mInterrupt, &m_GPIOManager, &m_I2CMaster);
}
else if (strcmp (pSoundDevice, "hdmi") == 0)
{
LOGNOTE ("HDMI mode");
m_pDexed = new CMiniDexedHDMI (&m_Config, &mInterrupt);
m_pDexed = new CMiniDexedHDMI (&m_Config, &mInterrupt, &m_GPIOManager);
}
else
{
LOGNOTE ("PWM mode");
m_pDexed = new CMiniDexedPWM (&m_Config, &mInterrupt);
m_pDexed = new CMiniDexedPWM (&m_Config, &mInterrupt, &m_GPIOManager);
}
assert (m_pDexed);

@ -21,6 +21,7 @@
#define _kernel_h
#include "circle_stdlib_app.h"
#include <circle/gpiomanager.h>
#include <circle/i2cmaster.h>
#include "config.h"
#include "minidexed.h"
@ -48,6 +49,7 @@ private:
private:
// do not change this order
CConfig m_Config;
CGPIOManager m_GPIOManager;
CI2CMaster m_I2CMaster;
CMiniDexed *m_pDexed;

@ -0,0 +1,315 @@
//
// ky040.cpp
//
// Circle - A C++ bare metal environment for Raspberry Pi
// Copyright (C) 2022 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 "ky040.h"
#include <assert.h>
static const unsigned SwitchDebounceDelayMillis = 50;
static const unsigned SwitchTickDelayMillis = 500;
CKY040::TState CKY040::s_NextState[StateUnknown][2][2] =
{
// {{CLK=0/DT=0, CLK=0/DT=1}, {CLK=1/DT=0, CLK=1/DT=1}}
{{StateInvalid, StateCWStart}, {StateCCWStart, StateStart}}, // StateStart
{{StateCWBothLow, StateCWStart}, {StateInvalid, StateStart}}, // StateCWStart
{{StateCWBothLow, StateInvalid}, {StateCWFirstHigh, StateInvalid}}, // StateCWBothLow
{{StateInvalid, StateInvalid}, {StateCWFirstHigh, StateStart}}, // StateCWFirstHigh
{{StateCCWBothLow, StateInvalid}, {StateCCWStart, StateStart}}, // StateCCWStart
{{StateCCWBothLow, StateCCWFirstHigh}, {StateInvalid, StateInvalid}}, // StateCCWBothLow
{{StateInvalid, StateCCWFirstHigh}, {StateInvalid, StateStart}}, // StateCCWFirstHigh
{{StateInvalid, StateInvalid}, {StateInvalid, StateStart}} // StateInvalid
};
CKY040::TEvent CKY040::s_Output[StateUnknown][2][2] =
{
// {{CLK=0/DT=0, CLK=0/DT=1}, {CLK=1/DT=0, CLK=1/DT=1}}
{{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}}, // StateStart
{{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}}, // StateCWStart
{{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}}, // StateCWBothLow
{{EventUnknown, EventUnknown}, {EventUnknown, EventClockwise}}, // StateCWFirstHigh
{{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}}, // StateCCWStart
{{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}}, // StateCCWBothLow
{{EventUnknown, EventUnknown}, {EventUnknown, EventCounterclockwise}}, // StateCCWFirstHigh
{{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}} // StateInvalid
};
CKY040::TSwitchState CKY040::s_NextSwitchState[SwitchStateUnknown][SwitchEventUnknown] =
{
// {SwitchEventDown, SwitchEventUp, SwitchEventTick}
{SwitchStateDown, SwitchStateStart, SwitchStateStart}, // SwitchStateStart
{SwitchStateDown, SwitchStateClick, SwitchStateHold}, // SwitchStateDown
{SwitchStateDown2, SwitchStateClick, SwitchStateStart}, // SwitchStateClick
{SwitchStateDown2, SwitchStateClick2, SwitchStateInvalid}, // SwitchStateDown2
{SwitchStateDown3, SwitchStateClick2, SwitchStateStart}, // SwitchStateClick2
{SwitchStateDown3, SwitchStateClick3, SwitchStateInvalid}, // SwitchStateDown3
{SwitchStateInvalid, SwitchStateClick3, SwitchStateStart}, // SwitchStateClick3
{SwitchStateHold, SwitchStateStart, SwitchStateHold}, // SwitchStateHold
{SwitchStateInvalid, SwitchStateStart, SwitchStateInvalid} // SwitchStateInvalid
};
CKY040::TEvent CKY040::s_SwitchOutput[SwitchStateUnknown][SwitchEventUnknown] =
{
// {SwitchEventDown, SwitchEventUp, SwitchEventTick}
{EventUnknown, EventUnknown, EventUnknown}, // SwitchStateStart
{EventUnknown, EventUnknown, EventSwitchHold}, // SwitchStateDown
{EventUnknown, EventUnknown, EventSwitchClick}, // SwitchStateClick
{EventUnknown, EventUnknown, EventUnknown}, // SwitchStateDown2
{EventUnknown, EventUnknown, EventSwitchDoubleClick}, // SwitchStateClick2
{EventUnknown, EventUnknown, EventUnknown}, // SwitchStateDown3
{EventUnknown, EventUnknown, EventSwitchTripleClick}, // SwitchStateClick3
{EventUnknown, EventUnknown, EventSwitchHold}, // SwitchStateHold
{EventUnknown, EventUnknown, EventUnknown} // SwitchStateInvalid
};
CKY040::CKY040 (unsigned nCLKPin, unsigned nDTPin, unsigned nSWPin, CGPIOManager *pGPIOManager)
: m_CLKPin (nCLKPin, GPIOModeInputPullUp, pGPIOManager),
m_DTPin (nDTPin, GPIOModeInputPullUp, pGPIOManager),
m_SWPin (nSWPin, GPIOModeInputPullUp, pGPIOManager),
m_bPollingMode (!pGPIOManager),
m_bInterruptConnected (FALSE),
m_pEventHandler (nullptr),
m_State (StateStart),
m_hDebounceTimer (0),
m_hTickTimer (0),
m_nLastSWLevel (HIGH),
m_bDebounceActive (FALSE),
m_SwitchState (SwitchStateStart),
m_nSwitchLastTicks (0)
{
}
CKY040::~CKY040 (void)
{
if (m_bInterruptConnected)
{
m_pEventHandler = nullptr;
m_CLKPin.DisableInterrupt2 ();
m_CLKPin.DisableInterrupt ();
m_CLKPin.DisconnectInterrupt ();
m_DTPin.DisableInterrupt2 ();
m_DTPin.DisableInterrupt ();
m_DTPin.DisconnectInterrupt ();
m_SWPin.DisableInterrupt2 ();
m_SWPin.DisableInterrupt ();
m_SWPin.DisconnectInterrupt ();
}
if (m_hDebounceTimer)
{
CTimer::Get ()->CancelKernelTimer (m_hDebounceTimer);
}
if (m_hTickTimer)
{
CTimer::Get ()->CancelKernelTimer (m_hTickTimer);
}
}
boolean CKY040::Initialize (void)
{
if (!m_bPollingMode)
{
assert (!m_bInterruptConnected);
m_bInterruptConnected = TRUE;
m_CLKPin.ConnectInterrupt (EncoderInterruptHandler, this);
m_DTPin.ConnectInterrupt (EncoderInterruptHandler, this);
m_SWPin.ConnectInterrupt (SwitchInterruptHandler, this);
m_CLKPin.EnableInterrupt (GPIOInterruptOnFallingEdge);
m_CLKPin.EnableInterrupt2 (GPIOInterruptOnRisingEdge);
m_DTPin.EnableInterrupt (GPIOInterruptOnFallingEdge);
m_DTPin.EnableInterrupt2 (GPIOInterruptOnRisingEdge);
m_SWPin.EnableInterrupt (GPIOInterruptOnFallingEdge);
m_SWPin.EnableInterrupt2 (GPIOInterruptOnRisingEdge);
}
return TRUE;
}
void CKY040::RegisterEventHandler (TEventHandler *pHandler, void *pParam)
{
assert (!m_pEventHandler);
m_pEventHandler = pHandler;
assert (m_pEventHandler);
m_pEventParam = pParam;
}
unsigned CKY040::GetHoldSeconds (void) const
{
return m_nHoldCounter / 2;
}
void CKY040::Update (void)
{
assert (m_bPollingMode);
EncoderInterruptHandler (this);
// handle switch
unsigned nTicks = CTimer::GetClockTicks ();
unsigned nSW = m_SWPin.Read ();
if (nSW != m_nLastSWLevel)
{
m_nLastSWLevel = nSW;
m_bDebounceActive = TRUE;
m_nDebounceLastTicks = CTimer::GetClockTicks ();
}
else
{
if ( m_bDebounceActive
&& nTicks - m_nDebounceLastTicks >= SwitchDebounceDelayMillis * (CLOCKHZ / 1000))
{
m_bDebounceActive = FALSE;
m_nSwitchLastTicks = nTicks;
if (m_pEventHandler)
{
(*m_pEventHandler) (nSW ? EventSwitchUp : EventSwitchDown,
m_pEventParam);
}
HandleSwitchEvent (nSW ? SwitchEventUp : SwitchEventDown);
}
if (nTicks - m_nSwitchLastTicks >= SwitchTickDelayMillis * (CLOCKHZ / 1000))
{
m_nSwitchLastTicks = nTicks;
HandleSwitchEvent (SwitchEventTick);
}
}
}
// generates the higher level switch events
void CKY040::HandleSwitchEvent (TSwitchEvent SwitchEvent)
{
assert (SwitchEvent < SwitchEventUnknown);
TEvent Event = s_SwitchOutput[m_SwitchState][SwitchEvent];
TSwitchState NextState = s_NextSwitchState[m_SwitchState][SwitchEvent];
if (NextState == SwitchStateHold)
{
if (m_SwitchState != SwitchStateHold)
{
m_nHoldCounter = 0;
}
m_nHoldCounter++;
}
m_SwitchState = NextState;
if ( Event != EventUnknown
&& (Event != EventSwitchHold || !(m_nHoldCounter & 1)) // emit hold event each second
&& m_pEventHandler)
{
(*m_pEventHandler) (Event, m_pEventParam);
}
}
void CKY040::EncoderInterruptHandler (void *pParam)
{
CKY040 *pThis = static_cast<CKY040 *> (pParam);
assert (pThis != 0);
unsigned nCLK = pThis->m_CLKPin.Read ();
unsigned nDT = pThis->m_DTPin.Read ();
assert (nCLK <= 1);
assert (nDT <= 1);
assert (pThis->m_State < StateUnknown);
TEvent Event = s_Output[pThis->m_State][nCLK][nDT];
pThis->m_State = s_NextState[pThis->m_State][nCLK][nDT];
if ( Event != EventUnknown
&& pThis->m_pEventHandler)
{
(*pThis->m_pEventHandler) (Event, pThis->m_pEventParam);
}
}
void CKY040::SwitchInterruptHandler (void *pParam)
{
CKY040 *pThis = static_cast<CKY040 *> (pParam);
assert (pThis != 0);
if (pThis->m_hDebounceTimer)
{
CTimer::Get ()->CancelKernelTimer (pThis->m_hDebounceTimer);
}
pThis->m_hDebounceTimer =
CTimer::Get ()->StartKernelTimer (MSEC2HZ (SwitchDebounceDelayMillis),
SwitchDebounceHandler, pThis, 0);
}
void CKY040::SwitchDebounceHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext)
{
CKY040 *pThis = static_cast<CKY040 *> (pParam);
assert (pThis != 0);
pThis->m_hDebounceTimer = 0;
if (pThis->m_hTickTimer)
{
CTimer::Get ()->CancelKernelTimer (pThis->m_hTickTimer);
}
pThis->m_hTickTimer = CTimer::Get ()->StartKernelTimer (MSEC2HZ (SwitchTickDelayMillis),
SwitchTickHandler, pThis, 0);
unsigned nSW = pThis->m_SWPin.Read ();
if (pThis->m_pEventHandler)
{
(*pThis->m_pEventHandler) (nSW ? EventSwitchUp : EventSwitchDown,
pThis->m_pEventParam);
}
pThis->HandleSwitchEvent (nSW ? SwitchEventUp : SwitchEventDown);
}
void CKY040::SwitchTickHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext)
{
CKY040 *pThis = static_cast<CKY040 *> (pParam);
assert (pThis != 0);
pThis->m_hTickTimer = CTimer::Get ()->StartKernelTimer (MSEC2HZ (SwitchTickDelayMillis),
SwitchTickHandler, pThis, 0);
pThis->HandleSwitchEvent (SwitchEventTick);
}

@ -0,0 +1,153 @@
//
// ky040.h
//
// Circle - A C++ bare metal environment for Raspberry Pi
// Copyright (C) 2022 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 _sensor_ky040_h
#define _sensor_ky040_h
#include <circle/gpiomanager.h>
#include <circle/gpiopin.h>
#include <circle/timer.h>
#include <circle/types.h>
/// \note This driver supports an interrupt mode and a polling mode.
class CKY040 /// Driver for KY-040 rotary encoder module
{
public:
enum TEvent
{
EventClockwise,
EventCounterclockwise,
EventSwitchDown,
EventSwitchUp,
EventSwitchClick,
EventSwitchDoubleClick,
EventSwitchTripleClick,
EventSwitchHold, ///< generated each second
EventUnknown
};
typedef void TEventHandler (TEvent Event, void *pParam);
public:
/// \param nCLKPin GPIO pin number of clock pin (encoder pin A)
/// \param nDTPin GPIO pin number of data pin (encoder pin B)
/// \param nSWPin GPIO pin number of switch pin
/// \param pGPIOManager Pointer to GPIO manager object (0 enables polling mode)
CKY040 (unsigned nCLKPin, unsigned nDTPin, unsigned nSWPin, CGPIOManager *pGPIOManager = 0);
~CKY040 (void);
/// \brief Operation successful?
boolean Initialize (void);
/// \brief Register a handler, to be called on an event from the encoder
/// \param pHandler Pointer to the handler
/// \param pParam Optional user parameter, handed over to the handler
void RegisterEventHandler (TEventHandler *pHandler, void *pParam = 0);
/// \return Number of seconds, the switch is hold down
/// \note Only valid, when EventSwitchHold has been received.
unsigned GetHoldSeconds (void) const;
/// \brief Has to be called very frequently in polling mode
void Update (void);
private:
enum TState
{
StateStart,
StateCWStart,
StateCWBothLow,
StateCWFirstHigh,
StateCCWStart,
StateCCWBothLow,
StateCCWFirstHigh,
StateInvalid,
StateUnknown
};
enum TSwitchState
{
SwitchStateStart,
SwitchStateDown,
SwitchStateClick,
SwitchStateDown2,
SwitchStateClick2,
SwitchStateDown3,
SwitchStateClick3,
SwitchStateHold,
SwitchStateInvalid,
SwitchStateUnknown
};
enum TSwitchEvent
{
SwitchEventDown,
SwitchEventUp,
SwitchEventTick,
SwitchEventUnknown
};
private:
void HandleSwitchEvent (TSwitchEvent SwitchEvent);
static void EncoderInterruptHandler (void *pParam);
static void SwitchInterruptHandler (void *pParam);
static void SwitchDebounceHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext);
static void SwitchTickHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext);
private:
CGPIOPin m_CLKPin;
CGPIOPin m_DTPin;
CGPIOPin m_SWPin;
boolean m_bPollingMode;
boolean m_bInterruptConnected;
TEventHandler *m_pEventHandler;
void *m_pEventParam;
// encoder
TState m_State;
static TState s_NextState[StateUnknown][2][2];
static TEvent s_Output[StateUnknown][2][2];
// switch low level
TKernelTimerHandle m_hDebounceTimer;
TKernelTimerHandle m_hTickTimer;
unsigned m_nLastSWLevel;
boolean m_bDebounceActive;
unsigned m_nDebounceLastTicks;
// switch higher level
TSwitchState m_SwitchState;
unsigned m_nSwitchLastTicks;
unsigned m_nHoldCounter;
static TSwitchState s_NextSwitchState[SwitchStateUnknown][SwitchEventUnknown];
static TEvent s_SwitchOutput[SwitchStateUnknown][SwitchEventUnknown];
};
#endif

@ -24,10 +24,10 @@
LOGMODULE ("minidexed");
CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt)
CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, CGPIOManager *pGPIOManager)
: CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()),
m_pConfig (pConfig),
m_UI (this, pConfig),
m_UI (this, pGPIOManager, pConfig),
m_PCKeyboard (this),
m_SerialMIDI (this, pInterrupt, pConfig),
m_bUseSerial (false),
@ -89,6 +89,11 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated)
}
}
CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void)
{
return &m_SysExFileLoader;
}
void CMiniDexed::BankSelectLSB (unsigned nBankLSB)
{
if (nBankLSB > 127)
@ -96,10 +101,9 @@ void CMiniDexed::BankSelectLSB (unsigned nBankLSB)
return;
}
// MIDI numbering starts with 0, user interface with 1
printf ("Select voice bank %u\n", nBankLSB+1);
m_SysExFileLoader.SelectVoiceBank (nBankLSB);
m_UI.BankSelected (nBankLSB);
}
void CMiniDexed::ProgramChange (unsigned nProgram)
@ -118,8 +122,9 @@ void CMiniDexed::ProgramChange (unsigned nProgram)
//// PWM //////////////////////////////////////////////////////////////////////
CMiniDexedPWM::CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt)
: CMiniDexed (pConfig, pInterrupt),
CMiniDexedPWM::CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager)
: CMiniDexed (pConfig, pInterrupt, pGPIOManager),
CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (),
pConfig->GetChunkSize ())
{
@ -169,8 +174,8 @@ unsigned CMiniDexedPWM::GetChunk (u32 *pBuffer, unsigned nChunkSize)
//// I2S //////////////////////////////////////////////////////////////////////
CMiniDexedI2S::CMiniDexedI2S (CConfig *pConfig, CInterruptSystem *pInterrupt,
CI2CMaster *pI2CMaster)
: CMiniDexed (pConfig, pInterrupt),
CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster)
: CMiniDexed (pConfig, pInterrupt, pGPIOManager),
CI2SSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (),
pConfig->GetChunkSize (), false, pI2CMaster,
pConfig->GetDACI2CAddress ())
@ -218,8 +223,9 @@ unsigned CMiniDexedI2S::GetChunk (u32 *pBuffer, unsigned nChunkSize)
//// HDMI /////////////////////////////////////////////////////////////////////
CMiniDexedHDMI::CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt)
: CMiniDexed (pConfig, pInterrupt),
CMiniDexedHDMI::CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager)
: CMiniDexed (pConfig, pInterrupt, pGPIOManager),
CHDMISoundBaseDevice (pInterrupt, pConfig->GetSampleRate (),
pConfig->GetChunkSize ())
{

@ -31,6 +31,7 @@
#include <stdint.h>
#include <circle/types.h>
#include <circle/interrupt.h>
#include <circle/gpiomanager.h>
#include <circle/i2cmaster.h>
#include <circle/pwmsoundbasedevice.h>
#include <circle/i2ssoundbasedevice.h>
@ -39,12 +40,15 @@
class CMiniDexed : public CDexedAdapter
{
public:
CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt);
CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager);
virtual bool Initialize (void);
void Process (bool bPlugAndPlayUpdated);
CSysExFileLoader *GetSysExFileLoader (void);
void BankSelectLSB (unsigned nBankLSB);
void ProgramChange (unsigned nProgram);
@ -69,7 +73,8 @@ protected:
class CMiniDexedPWM : public CMiniDexed, public CPWMSoundBaseDevice
{
public:
CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt);
CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager);
bool Initialize (void);
@ -82,7 +87,7 @@ class CMiniDexedI2S : public CMiniDexed, public CI2SSoundBaseDevice
{
public:
CMiniDexedI2S (CConfig *pConfig, CInterruptSystem *pInterrupt,
CI2CMaster *pI2CMaster);
CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster);
bool Initialize (void);
@ -94,7 +99,8 @@ public:
class CMiniDexedHDMI : public CMiniDexed, public CHDMISoundBaseDevice
{
public:
CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt);
CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager);
bool Initialize (void);

@ -21,6 +21,12 @@ LCDPinData5=23
LCDPinData6=24
LCDPinData7=25
# KY-040 Rotary Encoder
EncoderEnabled=1
EncoderPinClock=5
EncoderPinData=6
EncoderPinSwitch=26
# Debug
MIDIDumpEnabled=1
ProfileEnabled=1

@ -22,6 +22,7 @@
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <assert.h>
#include <circle/logger.h>
#include "voices.c"
@ -80,7 +81,7 @@ void CSysExFileLoader::Load (void)
size_t nLen = strlen (pEntry->d_name);
if ( nLen < 5 // "[NNNN]N[_name].syx"
|| strcmp (&pEntry->d_name[nLen-4], ".syx") != 0
|| strcasecmp (&pEntry->d_name[nLen-4], ".syx") != 0
|| sscanf (pEntry->d_name, "%u", &nBank) != 1)
{
LOGWARN ("%s: Invalid filename format", pEntry->d_name);
@ -119,6 +120,8 @@ void CSysExFileLoader::Load (void)
&& m_pVoiceBank[nBank]->StatusEnd == 0xF7)
{
LOGDBG ("Bank #%u successfully loaded", nBank);
m_BankFileName[nBank] = pEntry->d_name;
}
else
{
@ -140,6 +143,31 @@ void CSysExFileLoader::Load (void)
closedir (pDirectory);
}
std::string CSysExFileLoader::GetBankName (unsigned nBankID)
{
if (nBankID <= MaxVoiceBankID)
{
std::string Result = m_BankFileName[nBankID];
size_t nLen = Result.length ();
if (nLen > 4)
{
Result.resize (nLen-4); // remove file extension
unsigned nBank;
char BankName[30+1];
if (sscanf (Result.c_str (), "%u_%30s", &nBank, BankName) == 2)
{
Result = BankName;
return Result;
}
}
}
return "NO NAME";
}
void CSysExFileLoader::SelectVoiceBank (unsigned nBankID)
{
if (nBankID <= MaxVoiceBankID)

@ -56,6 +56,8 @@ public:
void Load (void);
std::string GetBankName (unsigned nBankID); // 0 .. 127
void SelectVoiceBank (unsigned nBankID); // 0 .. 127
void GetVoice (unsigned nVoiceID, // 0 .. 31
@ -68,6 +70,7 @@ private:
std::string m_DirName;
TVoiceBank *m_pVoiceBank[MaxVoiceBankID+1];
std::string m_BankFileName[MaxVoiceBankID+1];
unsigned m_nBankID;

@ -21,22 +21,30 @@
#include "minidexed.h"
#include <circle/logger.h>
#include <circle/string.h>
#include <circle/startup.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <assert.h>
LOGMODULE ("ui");
CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CConfig *pConfig)
CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CConfig *pConfig)
: m_pMiniDexed (pMiniDexed),
m_pGPIOManager (pGPIOManager),
m_pConfig (pConfig),
m_pLCD (0),
m_pLCDBuffered (0)
m_pLCDBuffered (0),
m_pRotaryEncoder (0),
m_UIMode (UIModeVoiceSelect),
m_nBank (0),
m_nProgram (0)
{
}
CUserInterface::~CUserInterface (void)
{
delete m_pRotaryEncoder;
delete m_pLCDBuffered;
delete m_pLCD;
}
@ -70,6 +78,24 @@ bool CUserInterface::Initialize (void)
LOGDBG ("LCD initialized");
}
if (m_pConfig->GetEncoderEnabled ())
{
m_pRotaryEncoder = new CKY040 (m_pConfig->GetEncoderPinClock (),
m_pConfig->GetEncoderPinData (),
m_pConfig->GetEncoderPinSwitch (),
m_pGPIOManager);
assert (m_pRotaryEncoder);
if (!m_pRotaryEncoder->Initialize ())
{
return false;
}
m_pRotaryEncoder->RegisterEventHandler (EncoderEventStub, this);
LOGDBG ("Rotary encoder initialized");
}
return true;
}
@ -81,8 +107,31 @@ void CUserInterface::Process (void)
}
}
void CUserInterface::BankSelected (unsigned nBankLSB)
{
assert (nBankLSB < 128);
m_nBank = nBankLSB;
assert (m_pMiniDexed);
std::string BankName = m_pMiniDexed->GetSysExFileLoader ()->GetBankName (nBankLSB);
// MIDI numbering starts with 0, user interface with 1
printf ("Select voice bank %u: \"%s\"\n", nBankLSB+1, BankName.c_str ());
if (m_UIMode == UIModeBankSelect)
{
CString String;
String.Format ("\n\r%-12uBANK%s", nBankLSB+1, BankName.c_str ());
LCDWrite (String);
}
}
void CUserInterface::ProgramChanged (unsigned nProgram)
{
assert (nProgram < 128);
m_nProgram = nProgram;
nProgram++; // MIDI numbering starts with 0, user interface with 1
// fetch program name from Dexed instance
@ -93,10 +142,14 @@ void CUserInterface::ProgramChanged (unsigned nProgram)
printf ("Loading voice %u: \"%s\"\n", nProgram, ProgramName);
if (m_UIMode == UIModeVoiceSelect)
{
CString String;
String.Format ("\n\r%u\n\r%s", nProgram, ProgramName);
String.Format ("\n\r%-11uVOICE%s", nProgram, ProgramName);
LCDWrite (String);
}
}
void CUserInterface::LCDWrite (const char *pString)
{
@ -105,3 +158,67 @@ void CUserInterface::LCDWrite (const char *pString)
m_pLCDBuffered->Write (pString, strlen (pString));
}
}
void CUserInterface::EncoderEventHandler (CKY040::TEvent Event)
{
int nStep = 0;
switch (Event)
{
case CKY040::EventClockwise:
nStep = 1;
break;
case CKY040::EventCounterclockwise:
nStep = -1;
break;
case CKY040::EventSwitchClick:
m_UIMode = static_cast <TUIMode> (m_UIMode+1);
if (m_UIMode == UIModeUnknown)
{
m_UIMode = UIModeStart;
}
break;
case CKY040::EventSwitchHold:
if (m_pRotaryEncoder->GetHoldSeconds () >= 3)
{
delete m_pLCD; // reset LCD
reboot ();
}
return;
default:
return;
}
switch (m_UIMode)
{
case UIModeBankSelect:
if (m_nBank + nStep < 128)
{
m_pMiniDexed->BankSelectLSB (m_nBank + nStep);
}
break;
case UIModeVoiceSelect:
if (m_nProgram + nStep < 32)
{
m_pMiniDexed->ProgramChange (m_nProgram + nStep);
}
break;
default:
break;
}
}
void CUserInterface::EncoderEventStub (CKY040::TEvent Event, void *pParam)
{
CUserInterface *pThis = static_cast<CUserInterface *> (pParam);
assert (pThis != 0);
pThis->EncoderEventHandler (Event);
}

@ -21,7 +21,9 @@
#define _userinterface_h
#include "config.h"
#include "ky040.h"
#include <display/hd44780device.h>
#include <circle/gpiomanager.h>
#include <circle/writebuffer.h>
class CMiniDexed;
@ -29,24 +31,45 @@ class CMiniDexed;
class CUserInterface
{
public:
CUserInterface (CMiniDexed *pMiniDexed, CConfig *pConfig);
CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CConfig *pConfig);
~CUserInterface (void);
bool Initialize (void);
void Process (void);
void BankSelected (unsigned nBankLSB); // 0 .. 127
void ProgramChanged (unsigned nProgram); // 0 .. 127
private:
void LCDWrite (const char *pString); // Print to optional HD44780 display
void EncoderEventHandler (CKY040::TEvent Event);
static void EncoderEventStub (CKY040::TEvent Event, void *pParam);
private:
enum TUIMode
{
UIModeStart,
UIModeVoiceSelect = UIModeStart,
UIModeBankSelect,
UIModeUnknown
};
private:
CMiniDexed *m_pMiniDexed;
CGPIOManager *m_pGPIOManager;
CConfig *m_pConfig;
CHD44780Device *m_pLCD;
CWriteBufferDevice *m_pLCDBuffered;
CKY040 *m_pRotaryEncoder;
TUIMode m_UIMode;
unsigned m_nBank;
unsigned m_nProgram;
};
#endif

Loading…
Cancel
Save