From a569f0e744b98e71c35f6158f9295cec332e9adc Mon Sep 17 00:00:00 2001 From: Rene Stange Date: Mon, 7 Mar 2022 09:46:50 +0100 Subject: [PATCH] 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. --- src/Makefile | 2 +- src/ky040.cpp | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/ky040.h | 153 ++++++++++++++++++++++++ 3 files changed, 469 insertions(+), 1 deletion(-) create mode 100644 src/ky040.cpp create mode 100644 src/ky040.h diff --git a/src/Makefile b/src/Makefile index 7d334d2..0c06d88 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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) diff --git a/src/ky040.cpp b/src/ky040.cpp new file mode 100644 index 0000000..24c040f --- /dev/null +++ b/src/ky040.cpp @@ -0,0 +1,315 @@ +// +// ky040.cpp +// +// Circle - A C++ bare metal environment for Raspberry Pi +// Copyright (C) 2022 R. Stange +// +// 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 . +// +#include "ky040.h" +#include + +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 (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 (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 (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 (pParam); + assert (pThis != 0); + + pThis->m_hTickTimer = CTimer::Get ()->StartKernelTimer (MSEC2HZ (SwitchTickDelayMillis), + SwitchTickHandler, pThis, 0); + + pThis->HandleSwitchEvent (SwitchEventTick); +} diff --git a/src/ky040.h b/src/ky040.h new file mode 100644 index 0000000..052b06c --- /dev/null +++ b/src/ky040.h @@ -0,0 +1,153 @@ +// +// ky040.h +// +// Circle - A C++ bare metal environment for Raspberry Pi +// Copyright (C) 2022 R. Stange +// +// 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 . +// +#ifndef _sensor_ky040_h +#define _sensor_ky040_h + +#include +#include +#include +#include + +/// \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