/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* BAPhysicalControls is a general purpose class for handling an array of
* pots, switches, rotary encoders and outputs (for LEDs or relays).
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*****************************************************************************/
#ifndef __BAPHYSICALCONTROLS_H
#define __BAPHYSICALCONTROLS_H
#include
#include
#include
namespace BALibrary {
constexpr bool SWAP_DIRECTION = true; ///< Use when specifying direction should be swapped
constexpr bool NOSWAP_DIRECTION = false; ///< Use when specifying direction should not be swapped
/// Convenience class for handling a digital output with regard to setting and toggling stage
class DigitalOutput {
public:
DigitalOutput() = delete; // delete the default constructor
/// Construct an object to control the specified pin
/// @param pin the "Logical Arduino" pin number (not the physical pin number
DigitalOutput(uint8_t pin) : m_pin(pin) {}
/// Set the output value
/// @param val when zero output is low, otherwise output his high
void set(int val);
/// Toggle the output value current state
void toggle(void);
private:
uint8_t m_pin; ///< store the pin associated with this output
int m_val = 0; ///< store the value to support toggling
};
/// Convenience class for handling digital inputs. This wraps Bounce with the abilty
/// to invert the polarity of the pin.
class DigitalInput : public Bounce {
public:
/// Create an input where digital low is return true. Most switches ground when pressed.
DigitalInput() : m_isPolarityInverted(true) {}
/// Create an input and specify if input polarity is inverted.
/// @param isPolarityInverted when false, a high voltage on the pin returns true, 0V returns false.
DigitalInput(bool isPolarityInverted) : m_isPolarityInverted(isPolarityInverted) {}
/// Read the state of the pin according to the polarity
/// @returns true when the input should be interpreted as the switch is closed, else false.
bool read() { return Bounce::read() != m_isPolarityInverted; } // logical XOR to conditionally invert polarity
/// Set whether the input pin polarity
/// @param polarity when true, a low voltage on the pin is considered true by read(), else false.
void setPolarityInverted(bool polarity) { m_isPolarityInverted = polarity; }
private:
bool m_isPolarityInverted;
};
/// Convenience class for handling an analog pot as a control. When calibrated,
/// returns a float between 0.0 and 1.0.
class Potentiometer {
public:
/// Calibration data for a pot includes it's min and max value, as well as whether
/// direction should be swapped. Swapping depends on the physical orientation of the
/// pot.
struct Calib {
unsigned min; ///< The value from analogRead() when the pot is fully counter-clockwise (normal orientation)
unsigned max; ///< The value from analogRead() when the pot is fully clockwise (normal orientation)
bool swap; ///< when orientation is such that fully clockwise would give max reading, swap changes it to the min
};
Potentiometer() = delete; // delete the default constructor
/// Construction requires the Arduino analog pin number, as well as calibration values.
/// @param analogPin The analog Arduino pin literal. This the number on the Teensy pinout preceeeded by an A in the local pin name. E.g. "A17".
/// @param minCalibration See Potentiometer::calibrate()
/// @param maxCalibration See Potentiometer::calibrate()
/// @param swapDirection Optional param. See Potentiometer::calibrate()
Potentiometer(uint8_t analogPin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection = false)
: m_pin(analogPin), m_swapDirection(swapDirection), m_minCalibration(minCalibration), m_maxCalibration(maxCalibration) {}
/// Get new value from the pot.
/// @param value reference to a float, the new value will be written here. Value is between 0.0 and 1.0f.
/// @returns true if the value has changed, false if it has not
bool getValue(float &value);
/// Get the raw int value directly from analogRead()
/// @returns an integer between 0 and 1023.
int getRawValue();
/// Adjust the calibrate threshold. This is a factor that shrinks the calibration range slightly to
/// ensure the full range from 0.0f to 1.0f can be obtained.
/// @details temperature change can slightly alter the calibration values. This factor causes the value
/// to hit min or max just before the end of the pots travel to compensate.
/// @param thresholdFactor typical value is 0.01f to 0.05f
void adjustCalibrationThreshold(float thresholdFactor);
/// Set the amount of feedback in the IIR filter used to smooth the pot readings
/// @details actual filter reponse deptnds on the rate you call getValue()
/// @param filterValue typical values are 0.80f to 0.95f
void setFeedbackFitlerValue(float fitlerValue);
/// Call this static function before creating the object to obtain calibration data. The sequence
/// involves prompts over the Serial port.
/// @details E.g. call Potentiometer::calibrate(PIN). See BAExpansionCalibrate.ino in the library examples.
/// @param analogPin the Arduino analog pin number connected to the pot you wish to calibraate.
/// @returns populated Potentiometer::Calib structure
static Calib calibrate(uint8_t analogPin);
private:
uint8_t m_pin; ///< store the Arduino pin literal, e.g. A17
bool m_swapDirection; ///< swap when pot orientation is upside down
unsigned m_minCalibration; ///< stores the min pot value
unsigned m_maxCalibration; ///< stores the max pot value
unsigned m_lastValue = 0; ///< stores previous value
float m_feedbackFitlerValue = 0.9f; ///< feedback value for POT filter
};
/// Convenience class for rotary (quadrature) encoders. Uses Arduino Encoder under the hood.
class RotaryEncoder : public Encoder {
public:
RotaryEncoder() = delete; // delete default constructor
/// Constructor an encoder with the specified pins. Optionally swap direction and divide down the number of encoder ticks
/// @param pin1 the Arduino logical pin number for the 'A' on the encoder.
/// @param pin2 the Arduino logical pin number for the 'B' on the encoder.
/// @param swapDirection (OPTIONAL) set to true or false to obtain clockwise increments the counter-clockwise decrements
/// @param divider (OPTIONAL) controls the sensitivity of the divider. Use powers of 2. E.g. 1, 2, 4, 8, etc.
RotaryEncoder(uint8_t pin1, uint8_t pin2, bool swapDirection = false, int divider = 1) : Encoder(pin1,pin2), m_swapDirection(swapDirection), m_divider(divider) {}
/// Get the delta (as a positive or negative number) since last call
/// @returns an integer representing the net change since last call
int getChange();
/// Set the divider on the internal counter. High resolution encoders without detents can be overly sensitive.
/// This will helf reduce sensisitive by increasing the divider. Default = 1.
/// @pram divider controls the sensitivity of the divider. Use powers of 2. E.g. 1, 2, 4, 8, etc.
void setDivider(int divider);
private:
bool m_swapDirection; ///< specifies if increment/decrement should be swapped
int32_t m_lastPosition = 0; ///< store the last recorded position
int32_t m_divider; ///< divides down the magnitude of change read by the encoder.
};
/// Specifies the type of control
enum class ControlType : unsigned {
SWITCH_MOMENTARY = 0, ///< a momentary switch, which is only on when pressed.
SWITCH_LATCHING = 1, ///< a latching switch, which toggles between on and off with each press and release
ROTARY_KNOB = 2, ///< a rotary encoder knob
POT = 3, ///< an analog potentiometer
UNDEFINED = 255 ///< undefined or uninitialized
};
/// Tjis class provides convenient interface for combinary an arbitrary number of controls of different types into
/// one object. Supports switches, pots, encoders and digital outputs (useful for LEDs, relays).
class BAPhysicalControls {
public:
BAPhysicalControls() = delete;
/// Construct an object and reserve memory for the specified number of controls. Encoders and outptus are optional params.
/// @param numSwitches the number of switches or buttons
/// @param numPots the number of analog potentiometers
/// @param numEncoders the number of quadrature encoders
/// @param numOutputs the number of digital outputs. E.g. LEDs, relays, etc.
BAPhysicalControls(unsigned numSwitches, unsigned numPots, unsigned numEncoders = 0, unsigned numOutputs = 0);
~BAPhysicalControls() {}
/// add a rotary encoders to the controls
/// @param pin1 the pin number corresponding to 'A' on the encoder
/// @param pin2 the pin number corresponding to 'B' on the encoder
/// @param swapDirection When true, reverses which rotation direction is positive, and which is negative
/// @param divider optional, for encoders with high resolution this divides down the rotation measurement.
/// @returns the index in the encoder vector the new encoder was placed at.
unsigned addRotary(uint8_t pin1, uint8_t pin2, bool swapDirection = false, int divider = 1);
/// add a switch to the controls
/// @param pin the pin number connected to the switch
/// @param intervalMilliseconds, optional, specifies the filtering time to debounce a switch
/// @returns the index in the switch vector the new switch was placed at.
unsigned addSwitch(uint8_t pin, unsigned long intervalMilliseconds = 10);
/// add a pot to the controls
/// @param pin the pin number connected to the wiper of the pot
/// @param minCalibration the value corresponding to lowest pot setting
/// @param maxCalibration the value corresponding to the highest pot setting
unsigned addPot(uint8_t pin, unsigned minCalibration, unsigned maxCalibration);
/// add a pot to the controls
/// @param pin the pin number connected to the wiper of the pot
/// @param minCalibration the value corresponding to lowest pot setting
/// @param maxCalibration the value corresponding to the highest pot setting
/// @param swapDirection reverses the which direction is considered pot minimum value
/// @param range the pot raw value will be mapped into a range of 0 to range
unsigned addPot(uint8_t pin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection);
/// add an output to the controls
/// @param pin the pin number connected to the Arduino output
/// @returns a handle (unsigned) to the added output. Use this to access the output.
unsigned addOutput(uint8_t pin);
/// Set the output specified by the provided handle
/// @param handle the handle that was provided previously by calling addOutput()
/// @param val the value to set the output. 0 is low, not zero is high.
void setOutput(unsigned handle, int val);
/// Set the output specified by the provided handle
/// @param handle the handle that was provided previously by calling addOutput()
/// @param val the value to set the output. True is high, false is low.
void setOutput(unsigned handle, bool val);
/// Toggle the output specified by the provided handle
/// @param handle the handle that was provided previously by calling addOutput()
void toggleOutput(unsigned handle);
/// Retrieve the change in position on the specified rotary encoder
/// @param handle the handle that was provided previously by calling addRotary()
/// @returns an integer value. Positive is clockwise, negative is counter-clockwise rotation.
int getRotaryAdjustUnit(unsigned handle);
/// Check if the pot specified by the handle has been updated.
/// @param handle the handle that was provided previously by calling addPot()
/// @param value a reference to a float, the pot value will be written to this variable.
/// @returns true if the pot value has changed since previous check, otherwise false
bool checkPotValue(unsigned handle, float &value);
/// Check if the switch has been toggled since last call
/// @param handle the handle that was provided previously by calling addSwitch()
/// @returns true if the switch changed state, otherwise false
bool isSwitchToggled(unsigned handle);
/// Check if the switch is currently being pressed (held)
/// @param handle the handle that was provided previously by calling addSwitch()
/// @returns true if the switch is held in a pressed or closed state
bool isSwitchHeld(unsigned handle);
/// Get the value of the switch
/// @param handle the handle that was provided previously by calling addSwitch()
/// @returns the value at the switch pin, either 0 or 1.
int getSwitchValue(unsigned handle);
private:
std::vector m_pots; ///< a vector of all added pots
std::vector m_encoders; ///< a vector of all added encoders
std::vector m_switches; ///< a vector of all added switches
std::vector m_outputs; ///< a vector of all added outputs
};
} // BALibrary
#endif /* __BAPHYSICALCONTROLS_H */