Add debouncing for FINDA
parent
c4b181e842
commit
019f74d6f2
|
|
@ -191,6 +191,7 @@ target_sources(
|
||||||
src/hal/adc.cpp
|
src/hal/adc.cpp
|
||||||
src/modules/protocol.cpp
|
src/modules/protocol.cpp
|
||||||
src/modules/buttons.cpp
|
src/modules/buttons.cpp
|
||||||
|
src/modules/debouncer.cpp
|
||||||
src/modules/finda.cpp
|
src/modules/finda.cpp
|
||||||
src/modules/leds.cpp
|
src/modules/leds.cpp
|
||||||
src/modules/motion.cpp
|
src/modules/motion.cpp
|
||||||
|
|
|
||||||
|
|
@ -3,43 +3,9 @@
|
||||||
namespace modules {
|
namespace modules {
|
||||||
namespace buttons {
|
namespace buttons {
|
||||||
|
|
||||||
uint16_t Buttons::tmpTiming = 0;
|
Buttons buttons;
|
||||||
|
|
||||||
// original idea from: https://www.eeweb.com/debouncing-push-buttons-using-a-state-machine-approach
|
uint16_t Buttons::tmpTiming = 0;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int8_t Buttons::Sample(uint16_t rawADC) {
|
int8_t Buttons::Sample(uint16_t rawADC) {
|
||||||
// decode 3 buttons' levels from one ADC
|
// decode 3 buttons' levels from one ADC
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "../hal/adc.h"
|
#include "../hal/adc.h"
|
||||||
|
#include "debouncer.h"
|
||||||
|
|
||||||
/// Buttons are built on top of the raw ADC API
|
/// Buttons are built on top of the raw ADC API
|
||||||
/// This layer should contain debouncing of buttons and their logical interpretation
|
/// This layer should contain debouncing of buttons and their logical interpretation
|
||||||
|
|
@ -9,43 +10,13 @@
|
||||||
namespace modules {
|
namespace modules {
|
||||||
namespace buttons {
|
namespace buttons {
|
||||||
|
|
||||||
struct Button {
|
struct Button : public debounce::Debouncer {
|
||||||
inline constexpr Button()
|
inline constexpr Button()
|
||||||
: timeLastChange(0) {}
|
: debounce::Debouncer(debounce) {}
|
||||||
|
|
||||||
/// @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);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// time interval for debouncing @@TODO specify units
|
/// time interval for debouncing @@TODO specify units
|
||||||
constexpr static const uint16_t debounce = 100;
|
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 {
|
class Buttons {
|
||||||
|
|
@ -71,5 +42,7 @@ private:
|
||||||
static int8_t Sample(uint16_t rawADC);
|
static int8_t Sample(uint16_t rawADC);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern Buttons buttons;
|
||||||
|
|
||||||
} // namespace buttons
|
} // namespace buttons
|
||||||
} // namespace modules
|
} // 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;
|
FINDA finda;
|
||||||
|
|
||||||
uint8_t FINDA::Status() const {
|
void FINDA::Step(uint16_t time) {
|
||||||
// we can read ADC directly
|
debounce::Debouncer::Step(time, hal::adc::ReadADC(1) > adcDecisionLevel);
|
||||||
return hal::adc::ReadADC(1) > 512;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FINDA::Step() {
|
|
||||||
// in this implementation FINDA doesn't need any stepping
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace finda
|
} // namespace finda
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "debouncer.h"
|
||||||
|
|
||||||
namespace modules {
|
namespace modules {
|
||||||
namespace finda {
|
namespace finda {
|
||||||
|
|
||||||
enum { On,
|
class FINDA : protected debounce::Debouncer {
|
||||||
Off };
|
|
||||||
|
|
||||||
class FINDA {
|
|
||||||
public:
|
public:
|
||||||
inline FINDA() = default;
|
inline FINDA()
|
||||||
uint8_t Status() const;
|
: debounce::Debouncer(debounce) {};
|
||||||
void Step();
|
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;
|
extern FINDA finda;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@
|
||||||
add_custom_target(tests)
|
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
|
# 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
|
# pointers so the unit tests may be compiled 64bit afterall set(CMAKE_C_FLAGS -m32)
|
||||||
set(CMAKE_C_FLAGS -m32)
|
# set(CMAKE_CXX_FLAGS -m32)
|
||||||
set(CMAKE_CXX_FLAGS -m32)
|
|
||||||
|
|
||||||
# include catch_discover_tests function from Catch2
|
# include catch_discover_tests function from Catch2
|
||||||
include(${Catch2_SOURCE_DIR}/contrib/Catch.cmake)
|
include(${Catch2_SOURCE_DIR}/contrib/Catch.cmake)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
# define the test executable
|
# 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
|
# define required search paths
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue