Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui] button_group: public state-bitfields and some refactor #864

Merged
merged 2 commits into from
May 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 1 addition & 43 deletions src/modm/ui/button.lb
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,7 @@

def init(module):
module.name = ":ui:button"
module.description = """\
# Debouncing Buttons

The `modm::ButtonGroup` class is able to debounce eight buttons at the same time.
The buttons have to be low-active. If this isn't the case invert their signal
before passing it to the `update()` method.

The `update()` method needs to be called periodically for example
every 10ms. Preferred in a timer interrupt function.

The detection for long or repeated presses works only correctly for
one key at a time. This constraint only applies to buttons listed in the
`mask` variable."""r"""

Mode 1:

```
Timeline ---->
__ _________________ __
getState() ____/ \____/ \____/ \____
isPressed() ----X-------X----------------------X-------
isRepeated() --------------------X--X--X--X-------------
isReleased() -------X----------------------X-------X----
| |__|__|
|_______| \ /
\ interval
timeout
```

Mode 2:

```
__ _________________ __
getState() ____/ \____/ \____/ \____
isPressedShort() -------X------------------------------X----
isPressedLong() --------------------X----------------------
isReleased() -------X----------------------X-------X----
```

This implementation is based on the C functions written
by Peter Dannegger (see http://www.mikrocontroller.net/topic/48465).
"""
module.description = FileReader("button.md")

def prepare(module, options):
module.depends(":architecture:atomic")
Expand All @@ -64,4 +23,3 @@ def build(env):
env.copy("button.hpp")
env.copy("button_impl.hpp")
env.copy("button_group.hpp")
env.copy("button_group_impl.hpp")
68 changes: 68 additions & 0 deletions src/modm/ui/button.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Debouncing Buttons

The `modm::ButtonGroup` class is able to debounce eight buttons at the same time.
The buttons have to be low-active. If this isn't the case invert their signal
before passing it to the `update()` method.

The `update()` method needs to be called periodically for example every 10ms.
Preferred in a timer interrupt function.

The detection for long or repeated presses works only correctly for one key at a
time. This constraint only applies to buttons listed in the `mask` variable.

## Mode 1
```
Timeline ---->
__ _________________ __
getState() ____/ \____/ \____/ \____
isPressed() ----X-------X----------------------X-------
isRepeated() --------------------X--X--X--X-------------
isReleased() -------X----------------------X-------X----
| |__|__|
|_______| \ /
\ interval
timeout
```

## Mode 2
```
Timeline ---->
__ _________________ __
getState() ____/ \____/ \____/ \____
isPressedShort() -------X------------------------------X----
isPressedLong() --------------------X----------------------
isReleased() -------X----------------------X-------X----
```

## Naming Buttons

To name buttons, declare an enum with a bitmask for each button:

```cpp
#include <modm/math/utils/bit_constants.hpp>

enum ButtonIdentifier
{
NONE = 0x00,
BUTTON0 = modm::Bit0,
BUTTON1 = modm::Bit1,
BUTTON2 = modm::Bit2,
BUTTON3 = modm::Bit3,
BUTTON4 = modm::Bit4,
BUTTON5 = modm::Bit5,
BUTTON6 = modm::Bit6,
BUTTON7 = modm::Bit7,
ALL = 0xFF,
};
```

Pass a `ButtonIdentifier` to any of `ButtonGroup::is**()` like so

```cpp
if(buttongroup_instance.isPressed(BUTTON0)) {
// Do stuff
}
```

This implementation is based on the C functions written by Peter Dannegger
(see http://www.mikrocontroller.net/topic/48465).
179 changes: 108 additions & 71 deletions src/modm/ui/button_group.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Copyright (c) 2012, Sascha Schade
* Copyright (c) 2012-2013, 2016, Niklas Hauser
* Copyright (c) 2015, Daniel Krebs
* Copyright (c) 2022, Thomas Sommer
*
* This file is part of the modm project.
*
Expand All @@ -12,10 +13,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// ----------------------------------------------------------------------------
#pragma once

#ifndef MODM_BUTTON_GROUP_HPP
#define MODM_BUTTON_GROUP_HPP

#include <concepts>
#include <stdint.h>

#include <modm/architecture/utils.hpp>
Expand All @@ -24,57 +24,48 @@
namespace modm
{
/**
* @tparam T
* Container type: `uint8_t` for eight buttons or `uint16_t` for
* up to 16 buttons
* @tparam T Storage type for Button states. Each button requires one bit.
*
* @ingroup modm_ui_button
*/
template <typename T = uint8_t>
template <std::unsigned_integral T = uint8_t>
class ButtonGroup
{
public:
/**
* Button masks.
*
* Provided for convenience only.
* Normally it is best to define your own meaningful names for the buttons.
*/
enum Identifier
{
NONE = 0x00,
BUTTON0 = 0x01,
BUTTON1 = 0x02,
BUTTON2 = 0x04,
BUTTON3 = 0x08,
BUTTON4 = 0x10,
BUTTON5 = 0x20,
BUTTON6 = 0x40,
BUTTON7 = 0x80,
ALL = 0xFF,
struct State {
T value{0};

bool read(T mask) const {
return mask & value;
}

bool read_and_clear(T mask) {
mask &= value;
value ^= mask;
return mask;
}
};

public:
State pressed;
State released;
State repeated;

/**
* Constructor
*
* @param mask
* Repeat mask, only buttons listed here can be used with the methods
* isRepeated(), isPressedShort() and isPressedLong()
* @param timeout
* Timeout for the repeat operation (number of update cycles)
* @param interval
* Repeat interval (number of update cycles)
* @param repeatmask only buttons listed here can be used with the methods
* isRepeated(), isPressedShort() and isPressedLong()
* @param timeout Button press period (number of update cycles) until begin of repeat
* @param interval Button press period (number of update cycles) for follow up repeats
*/
ButtonGroup(T mask, uint16_t timeout = 50, uint16_t interval = 20);
ButtonGroup(T repeatmask, uint16_t timeout = 50, uint16_t interval = 20)
: repeatmask(repeatmask), timeout(timeout), interval(interval), repeatCounter(timeout)
{}

/// Get the current (debounced) state of a key
T
getState(T mask) const;

/// Check if a key has been released
T
isReleased(T mask);
getState(T mask) const {
atomic::Lock lock;
return debounced.read(mask);
}

/**
* Check if a key has been pressed.
Expand All @@ -91,8 +82,18 @@ class ButtonGroup
* }
* @endcode
*/
T
isPressed(T mask);
bool
isPressed(T mask) {
atomic::Lock lock;
return pressed.read_and_clear(mask);
}

/// Check if a key has been released
bool
isReleased(T mask) {
atomic::Lock lock;
return released.read_and_clear(mask);
}

/**
* Check if a key has been pressed long enough such that the key repeat
Expand All @@ -117,8 +118,11 @@ class ButtonGroup
*
* @see isPressed()
*/
T
isRepeated(T mask);
bool
isRepeated(T mask) {
atomic::Lock lock;
return repeated.read_and_clear(mask);
}

/**
* Get buttons which were pressed short.
Expand All @@ -142,8 +146,18 @@ class ButtonGroup
*
* @see isPressedLong()
*/
T
isPressedShort(T mask);
bool
isPressedShort(T mask) {
atomic::Lock lock;

// get all keys which were pressed but are currently not pressed. This
// must be a short press then, otherwise the isPressedLong() method
// would have reseted pressed.
mask = mask & pressed.value & ~debounced.value;
pressed.value ^= mask;

return mask;
}

/**
* Get buttons which were pressed long
Expand All @@ -152,38 +166,61 @@ class ButtonGroup
* `isPressedShort()`, otherwise it will not work correctly!
* @see isPressedShort()
*/
T
isPressedLong(T mask);
bool
isPressedLong(T mask) {
atomic::Lock lock;

// get all keys which are long enough pressed so that the repeated
// variable was set
mask = mask & repeated.value;
repeated.value ^= mask;
mask = mask & pressed.value;
pressed.value ^= mask;

return mask;
}

/**
* Update internal state.
*
* Call this function periodically every 5 to 10ms
* @brief Update internal debounced. Call this function periodically every 5 to 10ms
*
* @param input
* input signals
* @param input Inverted input signals
*/
void
update(T input);
update(T input) {
// key changed?
T i = debounced.value ^ ~input;
// reset or count ct0
ct0 = ~(ct0 & i);
// reset or count ct1
ct1 = ct0 ^ (ct1 & i);
// count until roll over?
i &= ct0 & ct1;

// then toggle debounced
debounced.value ^= i;
// 0->1: key press detected
pressed.value |= debounced.value & i;
// 0->1: key release detected
released.value |= ~debounced.value & i;

if ((debounced.value & repeatmask) == 0) {
repeatCounter = timeout;
}
if (--repeatCounter == 0) {
repeatCounter = interval;
repeated.value |= debounced.value & repeatmask;
}
}

private:
const T repeatmask;
State debounced;

protected:
const uint16_t timeout;
const uint16_t interval;
const T repeatMask;
uint16_t repeatCounter;

T state; ///< debounced and inverted key state:
///< bit = 1: key pressed
T pressState;
T releaseState;
T repeatState;

private:
T ct0;
T ct1;
T ct0{0};
T ct1{0};
};
}

#include "button_group_impl.hpp"

#endif // MODM_BUTTON_GROUP_HPP
}
Loading