Add debouncing for FINDA

pull/20/head
D.R.racer 2021-06-02 13:07:28 +02:00 committed by DRracer
parent c4b181e842
commit 019f74d6f2
9 changed files with 124 additions and 86 deletions

View File

@ -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
src/modules/motion.cpp

View File

@ -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

View File

@ -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

43
src/modules/debouncer.cpp Normal file
View File

@ -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

53
src/modules/debouncer.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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(