Add debouncing for FINDA
parent
88ba5e061b
commit
e2803c66ef
|
|
@ -191,6 +191,7 @@ target_sources(
|
|||
src/hal/adc.cpp
|
||||
src/modules/protocol.cpp
|
||||
src/modules/buttons.cpp
|
||||
src/modules/debouncer.cpp
|
||||
src/modules/finda.cpp
|
||||
src/modules/leds.cpp
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,43 +3,9 @@
|
|||
namespace modules {
|
||||
namespace buttons {
|
||||
|
||||
uint16_t Buttons::tmpTiming = 0;
|
||||
Buttons buttons;
|
||||
|
||||
// original idea from: https://www.eeweb.com/debouncing-push-buttons-using-a-state-machine-approach
|
||||
void Button::Step(uint16_t time, bool press) {
|
||||
switch (f.state) {
|
||||
case State::Waiting:
|
||||
if (press) {
|
||||
f.state = State::Detected;
|
||||
timeLastChange = time;
|
||||
f.tmp = press;
|
||||
}
|
||||
break;
|
||||
case State::Detected:
|
||||
if (f.tmp == press) {
|
||||
if (time - timeLastChange > debounce) {
|
||||
f.state = State::WaitForRelease;
|
||||
}
|
||||
} else {
|
||||
f.state = State::Waiting;
|
||||
}
|
||||
break;
|
||||
case State::WaitForRelease:
|
||||
if (!press) {
|
||||
f.state = State::Update;
|
||||
}
|
||||
break;
|
||||
case State::Update:
|
||||
f.state = State::Waiting;
|
||||
timeLastChange = time;
|
||||
f.tmp = false;
|
||||
break;
|
||||
default:
|
||||
f.state = State::Waiting;
|
||||
timeLastChange = time;
|
||||
f.tmp = false;
|
||||
}
|
||||
}
|
||||
uint16_t Buttons::tmpTiming = 0;
|
||||
|
||||
int8_t Buttons::Sample(uint16_t rawADC) {
|
||||
// decode 3 buttons' levels from one ADC
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <stdint.h>
|
||||
#include "../hal/adc.h"
|
||||
#include "debouncer.h"
|
||||
|
||||
/// Buttons are built on top of the raw ADC API
|
||||
/// This layer should contain debouncing of buttons and their logical interpretation
|
||||
|
|
@ -9,43 +10,13 @@
|
|||
namespace modules {
|
||||
namespace buttons {
|
||||
|
||||
struct Button {
|
||||
struct Button : public debounce::Debouncer {
|
||||
inline constexpr Button()
|
||||
: timeLastChange(0) {}
|
||||
|
||||
/// @returns true if button is currently considered as pressed
|
||||
inline bool Pressed() const { return f.state == State::WaitForRelease; }
|
||||
|
||||
/// State machine stepping routine
|
||||
void Step(uint16_t time, bool press);
|
||||
: debounce::Debouncer(debounce) {}
|
||||
|
||||
private:
|
||||
/// time interval for debouncing @@TODO specify units
|
||||
constexpr static const uint16_t debounce = 100;
|
||||
|
||||
/// States of the debouncing automaton
|
||||
/// Intentionally not modeled as an enum class
|
||||
/// as it would impose additional casts which do not play well with the struct Flags
|
||||
/// and would make the code less readable
|
||||
enum State { Waiting = 0,
|
||||
Detected,
|
||||
WaitForRelease,
|
||||
Update };
|
||||
|
||||
/// The sole purpose of this data struct is to save RAM by compressing several flags into one byte on the AVR
|
||||
struct Flags {
|
||||
uint8_t state : 2; ///< state of the button
|
||||
uint8_t tmp : 1; ///< temporary state of button before the debouncing state machine finishes
|
||||
inline constexpr Flags()
|
||||
: state(State::Waiting)
|
||||
, tmp(false) {}
|
||||
};
|
||||
|
||||
/// Flags and state of the debouncing automaton
|
||||
Flags f;
|
||||
|
||||
/// Timestamp of the last change of ADC state for this button
|
||||
uint16_t timeLastChange;
|
||||
};
|
||||
|
||||
class Buttons {
|
||||
|
|
@ -71,5 +42,7 @@ private:
|
|||
static int8_t Sample(uint16_t rawADC);
|
||||
};
|
||||
|
||||
extern Buttons buttons;
|
||||
|
||||
} // namespace buttons
|
||||
} // namespace modules
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
#include "debouncer.h"
|
||||
|
||||
namespace modules {
|
||||
namespace debounce {
|
||||
|
||||
// original idea from: https://www.eeweb.com/debouncing-push-buttons-using-a-state-machine-approach
|
||||
void Debouncer::Step(uint16_t time, bool press) {
|
||||
switch (f.state) {
|
||||
case State::Waiting:
|
||||
if (press) {
|
||||
f.state = State::Detected;
|
||||
timeLastChange = time;
|
||||
f.tmp = press;
|
||||
}
|
||||
break;
|
||||
case State::Detected:
|
||||
if (f.tmp == press) {
|
||||
if (time - timeLastChange > debounceTimeout) {
|
||||
f.state = State::WaitForRelease;
|
||||
}
|
||||
} else {
|
||||
f.state = State::Waiting;
|
||||
}
|
||||
break;
|
||||
case State::WaitForRelease:
|
||||
if (!press) {
|
||||
f.state = State::Update;
|
||||
}
|
||||
break;
|
||||
case State::Update:
|
||||
f.state = State::Waiting;
|
||||
timeLastChange = time;
|
||||
f.tmp = false;
|
||||
break;
|
||||
default:
|
||||
f.state = State::Waiting;
|
||||
timeLastChange = time;
|
||||
f.tmp = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace debounce
|
||||
} // namespace modules
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/// A generic debouncing algorithm
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
namespace modules {
|
||||
namespace debounce {
|
||||
|
||||
/// Implements debouncing on 2-state logic variables (true/false, high/low, on/off, pressed/unpressed)
|
||||
/// Intentionally not modelled as a template to avoid code bloat
|
||||
class Debouncer {
|
||||
public:
|
||||
inline constexpr Debouncer(uint8_t debounceTimeout)
|
||||
: timeLastChange(0)
|
||||
, debounceTimeout(debounceTimeout) {}
|
||||
|
||||
/// @returns true if debounced value is currently considered as pressed
|
||||
inline bool Pressed() const { return f.state == State::WaitForRelease; }
|
||||
|
||||
/// State machine stepping routine
|
||||
void Step(uint16_t time, bool press);
|
||||
|
||||
private:
|
||||
/// States of the debouncing automaton
|
||||
/// Intentionally not modeled as an enum class
|
||||
/// as it would impose additional casts which do not play well with the struct Flags
|
||||
/// and would make the code less readable
|
||||
enum State {
|
||||
Waiting = 0,
|
||||
Detected,
|
||||
WaitForRelease,
|
||||
Update
|
||||
};
|
||||
|
||||
/// The sole purpose of this data struct is to save RAM by compressing several flags into one byte on the AVR
|
||||
struct Flags {
|
||||
uint8_t state : 2; ///< state of the debounced variable
|
||||
uint8_t tmp : 1; ///< temporary state of variable before the debouncing state machine finishes
|
||||
inline constexpr Flags()
|
||||
: state(State::Waiting)
|
||||
, tmp(false) {}
|
||||
};
|
||||
|
||||
/// Flags and state of the debouncing automaton
|
||||
Flags f;
|
||||
|
||||
/// Timestamp of the last change of raw input state for this variable
|
||||
uint16_t timeLastChange;
|
||||
uint8_t debounceTimeout;
|
||||
};
|
||||
|
||||
} // namespace debounce
|
||||
} // namespace modules
|
||||
|
|
@ -6,13 +6,8 @@ namespace finda {
|
|||
|
||||
FINDA finda;
|
||||
|
||||
uint8_t FINDA::Status() const {
|
||||
// we can read ADC directly
|
||||
return hal::adc::ReadADC(1) > 512;
|
||||
}
|
||||
|
||||
void FINDA::Step() {
|
||||
// in this implementation FINDA doesn't need any stepping
|
||||
void FINDA::Step(uint16_t time) {
|
||||
debounce::Debouncer::Step(time, hal::adc::ReadADC(1) > adcDecisionLevel);
|
||||
}
|
||||
|
||||
} // namespace finda
|
||||
|
|
|
|||
|
|
@ -1,17 +1,22 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "debouncer.h"
|
||||
|
||||
namespace modules {
|
||||
namespace finda {
|
||||
|
||||
enum { On,
|
||||
Off };
|
||||
|
||||
class FINDA {
|
||||
class FINDA : protected debounce::Debouncer {
|
||||
public:
|
||||
inline FINDA() = default;
|
||||
uint8_t Status() const;
|
||||
void Step();
|
||||
inline FINDA()
|
||||
: debounce::Debouncer(debounce) {};
|
||||
void Step(uint16_t time);
|
||||
using debounce::Debouncer::Pressed;
|
||||
|
||||
private:
|
||||
/// time interval for debouncing @@TODO specify units
|
||||
constexpr static const uint16_t debounce = 100;
|
||||
/// ADC decision level when a FINDA is considered pressed/not pressed
|
||||
constexpr static const uint16_t adcDecisionLevel = 512;
|
||||
};
|
||||
|
||||
extern FINDA finda;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
add_custom_target(tests)
|
||||
|
||||
# this had a reason on the Buddy FW, but on the AVR we cannot get close to having 16bit ints and
|
||||
# pointers so the unit tests may be compiled 64bit afterall
|
||||
set(CMAKE_C_FLAGS -m32)
|
||||
set(CMAKE_CXX_FLAGS -m32)
|
||||
# pointers so the unit tests may be compiled 64bit afterall set(CMAKE_C_FLAGS -m32)
|
||||
# set(CMAKE_CXX_FLAGS -m32)
|
||||
|
||||
# include catch_discover_tests function from Catch2
|
||||
include(${Catch2_SOURCE_DIR}/contrib/Catch.cmake)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
# define the test executable
|
||||
add_executable(buttons_tests ../../../../src/modules/buttons.cpp stub_adc.cpp test_buttons.cpp)
|
||||
add_executable(
|
||||
buttons_tests ../../../../src/modules/buttons.cpp ../../../../src/modules/debouncer.cpp
|
||||
stub_adc.cpp test_buttons.cpp
|
||||
)
|
||||
|
||||
# define required search paths
|
||||
target_include_directories(
|
||||
|
|
|
|||
Loading…
Reference in New Issue