Skip to content
Snippets Groups Projects
Commit 8ede515a authored by Alberto Nidasio's avatar Alberto Nidasio
Browse files

[PinObserver] Updated implementation following the new ButtonHandler

parent 310d04dd
Branches
Tags
No related merge requests found
......@@ -97,6 +97,7 @@ foreach(OPT_BOARD ${BOARDS})
# Utils
${SBS_BASE}/src/shared/utils/AeroUtils/AeroUtils.cpp
${SBS_BASE}/src/shared/utils/ButtonHandler/ButtonHandler.cpp
${SBS_BASE}/src/shared/utils/PinObserver/PinObserver.cpp
${SBS_BASE}/src/shared/utils/SkyQuaternion/SkyQuaternion.cpp
${SBS_BASE}/src/shared/utils/Stats/Stats.cpp
${SBS_BASE}/src/shared/utils/TestUtils/TestHelper.cpp
......
......@@ -44,6 +44,11 @@ bool ButtonHandler::registerButtonCallback(miosix::GpioPin pin,
return false;
}
bool ButtonHandler::unregisterButtonCallback(miosix::GpioPin pin)
{
return callbacks.erase(pin) != 0;
}
bool ButtonHandler::start() { return scheduler.start(); }
void ButtonHandler::stop() { scheduler.stop(); }
......@@ -63,12 +68,12 @@ void ButtonHandler::periodicButtonValueCheck(miosix::GpioPin pin)
// Retrieve the pin information
const ButtonCallback &callback = std::get<0>(callbacks[pin]);
bool &wasPressed = std::get<1>(callbacks[pin]);
int &pressedTicks = std::get<2>(callbacks[pin]);
unsigned int &pressedTicks = std::get<2>(callbacks[pin]);
// Read the current button status
// Note: The button is assumed to be pressed if the pin value is low
// (pulldown)
bool isNowPressed = !pin.value();
const bool isNowPressed = !pin.value();
if (isNowPressed)
{
......
......@@ -23,8 +23,8 @@
#pragma once
#include <Singleton.h>
#include <miosix.h>
#include <scheduler/TaskScheduler.h>
#include <utils/GpioPinCompare.h>
#include <map>
......@@ -33,24 +33,10 @@ namespace Boardcore
enum class ButtonEvent
{
PRESSED, // Called as soon as the button is pressed
SHORT_PRESS, // Called as soon as the button is released
LONG_PRESS, // Called as soon as the button is released
VERY_LONG_PRESS // Called as soon as the button is released
};
/**
* @brief Comparison operator between GpioPins used for std::map.
*/
struct GpioPinCompare
{
bool operator()(const miosix::GpioPin& lhs,
const miosix::GpioPin& rhs) const
{
if (lhs.getPort() == rhs.getPort())
return lhs.getNumber() < rhs.getNumber();
return lhs.getPort() < rhs.getPort();
}
PRESSED, ///< The button is pressed.
SHORT_PRESS, ///< The button is released before LONG_PRESS_TICKS.
LONG_PRESS, ///< The button is released before VERY_LONG_PRESS_TICKS.
VERY_LONG_PRESS ///< The button is released after VERY_LONG_PRESS_TICKS.
};
/**
......@@ -122,17 +108,12 @@ private:
/**
* @brief Map of all the callbacks registered in the ButtonHandler.
*
* The key is the GpioPin for which the callback is registered. To used
* GpioPin as a map key, the GpioPinCompare operator was defined as
* explained here:
* https://stackoverflow.com/questions/1102392/how-can-i-use-stdmaps-with-user-defined-types-as-key
*
* The type stored is a tuple containing:
* - The button callback function;
* - Whether or not the button was pressed in the last check iteration;
* - The relative tick of the last pin value change.
*/
std::map<miosix::GpioPin, std::tuple<ButtonCallback, bool, int>,
std::map<miosix::GpioPin, std::tuple<ButtonCallback, bool, unsigned int>,
GpioPinCompare>
callbacks;
};
......
/* Copyright (c) 2022 Skyward Experimental Rocketry
* Authors: Alberto Nidasio
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <miosix.h>
namespace Boardcore
{
/**
* @brief Comparison operator between GpioPins used for std::map.
*
* This function was implemented to use GpioPin as a map key. Check here for
* more explanation:
* https://stackoverflow.com/questions/1102392/how-can-i-use-stdmaps-with-user-defined-types-as-key
*/
struct GpioPinCompare
{
bool operator()(const miosix::GpioPin& lhs,
const miosix::GpioPin& rhs) const
{
if (lhs.getPort() == rhs.getPort())
return lhs.getNumber() < rhs.getNumber();
return lhs.getPort() < rhs.getPort();
}
};
} // namespace Boardcore
/* Copyright (c) 2018 Skyward Experimental Rocketry
* Author: Luca Erbetta
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <diagnostic/StackLogger.h>
#include <miosix.h>
#include <functional>
#include <map>
#include <utility>
#include "ActiveObject.h"
using miosix::FastMutex;
using miosix::GpioPin;
using miosix::Lock;
using miosix::Thread;
using miosix::Unlock;
using std::function;
using std::map;
using std::pair;
namespace Boardcore
{
/**
* Class used to call a callback after a pin performs a specific transition
* (RISING or FALLING edge) and stays in the new state for a specific amount of
* time. Useful if you want to monitor pin transitions but you want to avoid
* spurious state changes.
*
* A callback to monitor each state change no matter the thresold or the
* transition is also available, in order to be able to observe the current
* state of the pin
*/
class PinObserver : public ActiveObject
{
public:
/**
* @brief Pin transition
* Actual enumaration value represents the stat of the pin after the
* corresponding transition has occured.
*/
enum class Transition : int
{
FALLING_EDGE = 0,
RISING_EDGE = 1
};
using OnStateChangeCallback =
function<void(unsigned int, unsigned char, int)>;
using OnTransitionCallback = function<void(unsigned int, unsigned char)>;
/**
* @brief Construct a new Pin Observer object
*
* @param pollIntervalMs Pin transition polling interval
*/
PinObserver(unsigned int pollIntervalMs = 20) : pollInterval(pollIntervalMs)
{
}
/**
* Observe a pin for a specific transition, and optionally for every
* single state change.
*
* The @param transitionCb function is called only if the two following
* conditions are verified:
* 1. The transition specified in the @param transition is detected
* 2. The pin stays in the new state for at least detection_threshols
* samples.
*
* The @param onstatechangeCb function [optional] is called at each state
* change, both rising and falling edge, regardless of the @param
* detectionThreshold
*
* @param p GPIOA_BASE, GPIOB_BASE ...
* @param n Which pin (0 to 15)
* @param transition What transition to detect (RISING or FALLING edge)
* @param transitionCb Function to call when the transition is detected and
* the pin stays in the new configuration for at least @param
* detectionThreshold samples
* @param detectionThreshold How many times the pin should be observed in
* the post-transition state to trigger the actual transition callback.
* @param onstatechangeCb Function to be called at each pin state change,
* no matter the threshold or the transition
*/
void observePin(unsigned int p, unsigned char n, Transition transition,
OnTransitionCallback transitionCb,
unsigned int detectionThreshold = 1,
OnStateChangeCallback onstatechangeCb = nullptr)
{
Lock<FastMutex> lock(mtxMap);
observedPins.insert(
std::make_pair(pair<unsigned int, unsigned char>({p, n}),
ObserverData{GpioPin{p, n}, transition, transitionCb,
onstatechangeCb, detectionThreshold}));
}
/**
* @brief Stop monitoring the specified pin
*
* @param p GPIOA_BASE, GPIOB_BASE ...
* @param n Which pin (0 to 15)
*/
void removeObservedPin(unsigned int p, unsigned char n)
{
Lock<FastMutex> lock(mtxMap);
observedPins.erase({p, n});
}
protected:
void run()
{
while (true)
{
{
Lock<FastMutex> lock(mtxMap);
for (auto it = observedPins.begin(); it != observedPins.end();
it++)
{
pair<int, int> key = it->first;
ObserverData& pinData = it->second;
int oldState = pinData.state;
int newState = pinData.pin.value();
// Save current state in the struct
pinData.state = newState;
// Are we in a post-transition state?
if (pinData.state == static_cast<int>(pinData.transition))
{
++pinData.detectedCount;
}
else
{
pinData.detectedCount = 0;
}
// Pre-calcualate conditions in order to unlock the mutex
// only one time
bool stateChange = pinData.onstatechangeCallback &&
oldState != pinData.state;
bool pinTriggered =
pinData.detectedCount == pinData.detectionThreshold;
{
Unlock<FastMutex> unlock(lock);
if (stateChange)
{
pinData.onstatechangeCallback(key.first, key.second,
newState);
}
if (pinTriggered)
{
pinData.transitionCallback(key.first, key.second);
}
}
}
}
StackLogger::getInstance().updateStack(THID_PIN_OBS);
Thread::sleep(pollInterval);
}
}
private:
struct ObserverData
{
GpioPin pin;
Transition transition;
OnTransitionCallback transitionCallback;
OnStateChangeCallback onstatechangeCallback;
unsigned int detectionThreshold;
unsigned int detectedCount;
int state; // 1 if HIGH, 0 if LOW
ObserverData(GpioPin pin, Transition transition,
OnTransitionCallback transitionCallback,
OnStateChangeCallback onstatechangeCallback,
unsigned int detectionThreshold)
: pin(pin), transition(transition),
transitionCallback(transitionCallback),
onstatechangeCallback(onstatechangeCallback),
detectionThreshold(detectionThreshold),
// Set to this value to avoid detection if the pin is already in
// the ending state of the "trigger" transition
detectedCount(detectionThreshold + 1), state(0)
{
}
};
map<pair<unsigned int, unsigned char>, ObserverData> observedPins;
FastMutex mtxMap;
unsigned int pollInterval;
bool stopped = true;
};
} // namespace Boardcore
/* Copyright (c) 2018-2022 Skyward Experimental Rocketry
* Author: Luca Erbetta, Alberto Nidasio
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "PinObserver.h"
#include <functional>
namespace Boardcore
{
bool PinObserver::registerPinCallback(miosix::GpioPin pin, PinCallback callback,
unsigned int detectionThreshold)
{
// Try to insert the callback
auto result =
callbacks.insert({pin, {callback, detectionThreshold, pin.value(), 0}});
// Check if the insertion took place
if (result.second)
{
return scheduler.addTask(
std::bind(&PinObserver::periodicPinValueCheck, this, pin),
SAMPLE_PERIOD, TaskScheduler::Policy::SKIP);
}
return false;
}
bool PinObserver::unregisterPinCallback(miosix::GpioPin pin)
{
return callbacks.erase(pin) != 0;
}
bool PinObserver::start() { return scheduler.start(); }
void PinObserver::stop() { scheduler.stop(); }
PinObserver::PinObserver() { scheduler.start(); }
void PinObserver::periodicPinValueCheck(miosix::GpioPin pin)
{
// Make sure the pin informations are still present
if (callbacks.find(pin) == callbacks.end())
return;
// Retrieve the pin information
const PinCallback &callback = std::get<0>(callbacks[pin]);
const unsigned int detectionThreshold = std::get<1>(callbacks[pin]);
bool &previousState = std::get<2>(callbacks[pin]);
unsigned int &detectedCount = std::get<3>(callbacks[pin]);
// Read the current pin status
const bool newState = pin.value();
// Are we in a transition?
if (previousState != newState)
{
detectedCount = 0; // Yes, reset the counter
}
else
{
detectedCount++; // No, continue to increment
// If the count reaches the threshold, then trigger the event
if (detectedCount > detectionThreshold)
{
if (newState)
callback(PinTransition::RISING_EDGE);
else
callback(PinTransition::FALLING_EDGE);
}
}
// Save the current pin status
previousState = newState;
}
} // namespace Boardcore
/* Copyright (c) 2018-2022 Skyward Experimental Rocketry
* Author: Luca Erbetta, Alberto Nidasio
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <Singleton.h>
#include <scheduler/TaskScheduler.h>
#include <utils/GpioPinCompare.h>
#include <map>
namespace Boardcore
{
/**
* @brief Pin transition.
*/
enum class PinTransition
{
FALLING_EDGE, ///< The pin goes from high to low.
RISING_EDGE ///< The pin goes from low to high.
};
/**
* Class used to call a callback after a pin performs a specific transition
* (RISING or FALLING edge) and stays in the new state for a specific amount of
* time. Useful if you want to monitor pin transitions but you want to avoid
* spurious state changes.
*
* A callback to monitor each state change no matter the threshold or the
* transition is also available, in order to be able to observe the current
* state of the pin.
*/
class PinObserver : public Singleton<PinObserver>
{
friend Singleton<PinObserver>;
static constexpr uint32_t SAMPLE_PERIOD = 100; // 10Hz
public:
using PinCallback = std::function<void(PinTransition)>;
/**
* Observe a pin for a specific transition, and optionally for every
* single state change.
*
* @param pin Pin to listen to.
* @param callback Function to call on button events.
* @param detectionThreshold How many times the pin should be observed in
* the post-transition state to trigger the actual transition callback,
* defaults to 1.
* @return False if another callback was already registered for the pin.
*/
bool registerPinCallback(miosix::GpioPin pin, PinCallback callback,
unsigned int detectionThreshold = 1);
/**
* @brief Unregisters the callback associated with the specified pin, if
* any.
*
* @param pin Pin whose callback function is to be removed.
* @return True if a callback was present and removed for the given pin.
*/
bool unregisterPinCallback(miosix::GpioPin pin);
/**
* @brief Starts the PinObserver's task scheduler.
*
* Note that the scheduler is started as soon as the PinObserver is first
* used.
*
* @return Whether the task scheduler was started or not.
*/
bool start();
/**
* @brief Stops the PinObserver's task scheduler.
*/
void stop();
private:
/**
* @brief Construct a new PinObserver object.
*
* @param pollInterval Pin transition polling interval, defaults to 20 [ms].
*/
PinObserver();
/**
* @brief This function is added to the scheduler for every pin registered
* in the PinObserver.
*
* @param pin Pin whose value need to be checked.
*/
void periodicPinValueCheck(miosix::GpioPin pin);
TaskScheduler scheduler;
/**
* @brief Map of all the callbacks registered in the PinObserver.
* The type stored is a tuple containing:
* - The button callback function;
* - Detection threshold: number of periods to trigger an event
* - The last pin status;
* - Number of periods the pin values stayed the same;
*/
std::map<miosix::GpioPin,
std::tuple<PinCallback, unsigned int, bool, unsigned int>,
GpioPinCompare>
callbacks;
};
} // namespace Boardcore
......@@ -21,10 +21,14 @@
*/
#include <miosix.h>
#include <utils/PinObserver.h>
#include <utils/PinObserver/PinObserver.h>
#include <functional>
using namespace Boardcore;
using namespace miosix;
using namespace std;
using namespace std::placeholders;
static constexpr unsigned int POLL_INTERVAL = 20;
......@@ -35,60 +39,38 @@ static constexpr unsigned char PIN1_PIN = 5;
static constexpr unsigned int PIN2_PORT = GPIOE_BASE;
static constexpr unsigned char PIN2_PIN = 6;
void onStateChange(unsigned int p, unsigned char n, int state)
{
if (p == BTN_PORT && n == BTN_PIN)
{
printf("BTN state change: %d\n", state);
}
if (p == PIN1_PORT && n == PIN1_PIN)
{
printf("PIN1 state change: %d\n", state);
}
if (p == PIN2_PORT && n == PIN2_PIN)
void onTransition(GpioPin pin, PinTransition transition)
{
printf("PIN2 state change: %d\n", state);
}
}
if (pin.getPort() == BTN_PORT && pin.getNumber() == BTN_PIN)
printf("BTN transition: ");
if (pin.getPort() == PIN1_PORT && pin.getNumber() == PIN1_PIN)
printf("PIN1 transition: ");
if (pin.getPort() == PIN2_PORT && pin.getNumber() == PIN2_PIN)
printf("PIN2 transition: ");
void onTransition(unsigned int p, unsigned char n)
{
if (p == BTN_PORT && n == BTN_PIN)
{
printf("BTN transition.\n");
}
if (p == PIN1_PORT && n == PIN1_PIN)
{
printf("PIN1 transition.\n");
}
if (p == PIN2_PORT && n == PIN2_PIN)
{
printf("PIN2 transition.\n");
}
if (transition == PinTransition::FALLING_EDGE)
printf("FALLING_EDGE\n");
else
printf("RISING_EDGE\n");
}
int main()
{
GpioPin(BTN_PORT, BTN_PIN).mode(Mode::INPUT);
GpioPin(PIN1_PORT, PIN1_PIN).mode(Mode::INPUT_PULL_DOWN);
GpioPin(PIN2_PORT, PIN2_PIN).mode(Mode::INPUT_PULL_UP);
auto btn = GpioPin(BTN_PORT, BTN_PIN);
auto pin1 = GpioPin(PIN1_PORT, PIN1_PIN);
auto pin2 = GpioPin(PIN2_PORT, PIN2_PIN);
PinObserver observer;
btn.mode(Mode::INPUT);
pin1.mode(Mode::INPUT_PULL_DOWN);
pin2.mode(Mode::INPUT_PULL_UP);
observer.observePin(BTN_PORT, BTN_PIN, PinObserver::Transition::RISING_EDGE,
&onTransition, 1, onStateChange);
PinObserver::getInstance().registerPinCallback(
btn, std::bind(onTransition, btn, _1), 10);
PinObserver::getInstance().registerPinCallback(
pin1, std::bind(onTransition, pin1, _1), 10);
PinObserver::getInstance().registerPinCallback(
pin2, std::bind(onTransition, pin2, _1), 10);
observer.observePin(PIN1_PORT, PIN1_PIN,
PinObserver::Transition::FALLING_EDGE, &onTransition,
1000 / POLL_INTERVAL, onStateChange);
observer.observePin(PIN2_PORT, PIN2_PIN,
PinObserver::Transition::RISING_EDGE, &onTransition,
1000 / POLL_INTERVAL, onStateChange);
observer.start();
for (;;)
{
while (true)
Thread::sleep(10000);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment