#include <Servo.h>
#include <Button_SL.hpp>
//------------------------------------------------------------------
// Typalias(es)
//------------------------------------------------------------------
using Millis_t = decltype(millis());
using SegmentPins_t = uint8_t[7];
using PwmDataTable_t = uint8_t[64];
//------------------------------------------------------------------
// Global Constants
//------------------------------------------------------------------
namespace gc {
// https://www.mikrocontroller.net/articles/LED-Fading
constexpr PwmDataTable_t PwmDataTable {0, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,
5, 5, 6, 6, 7, 7, 8, 8, 9, 10, 11, 12, 13, 14, 15, 17,
18, 20, 22, 24, 26, 28, 30, 33, 36, 39, 43, 47, 51, 55, 60, 65,
71, 78, 84, 92, 100, 109, 119, 129, 141, 153, 167, 181, 198, 215, 234, 255};
// Index = Ziffer 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
constexpr uint8_t SegmentMasks[] {0b1110111, 0b0010001, 0b1101011, 0b0111011, 0b0011101,
0b0111110, 0b1111110, 0b0010011, 0b1111111, 0b0111111};
// Segment B A F G C D E
constexpr SegmentPins_t SegmentPins {13, 12, 11, 10, 9, 8, 7};
constexpr uint8_t LedPin {6};
constexpr uint8_t ServoPin {5};
constexpr uint8_t ButtonPin {4};
constexpr Millis_t FadeDelay_ms {30};
constexpr Millis_t GateOpenDelay_ms {3000};
constexpr Millis_t CountDownDelay_ms {1000}; // One Second! SeconCounter depends on this value
constexpr int8_t SecondCounter {4};
} // namespace gc
//
// Class for enabling non-blocking timing
//
class Timer {
public:
Timer() : TimeStamp(0), Reached(true) {}
void start() {
TimeStamp = millis();
Reached = false;
}
void reset() { Reached = true; }
bool operator()(const Millis_t Duration) {
if (!Reached) { Reached = millis() - TimeStamp >= Duration; }
return Reached;
}
private:
Millis_t TimeStamp;
bool Reached;
};
//
// Class for controlling the dimming of an LED
//
class Fader {
public:
Fader(uint8_t Pin) : Pin {Pin} {}
void operator()(const PwmDataTable_t& Data) {
if (Idx > -1 && Idx < static_cast<int>(sizeof(PwmDataTable_t))) {
analogWrite(Pin, Data[Idx]);
} else {
Step *= -1;
}
Idx += Step;
}
void reset() {
Step = 1;
Idx = 0;
analogWrite(Pin, 0);
}
private:
uint8_t Pin;
int Step {1};
int Idx {0};
};
enum class GateStates : byte { Closed, Wait, Opened };
//------------------------------------------------------------------
// Global Objects/Variables
//------------------------------------------------------------------
Servo SDevice;
Btn::ButtonSL Button {gc::ButtonPin, 500};
Fader Fade(gc::LedPin);
Timer FadeTimer;
Timer CountDownTimer;
//------------------------------------------------------------------
// Functions
//------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
/// \brief Displaying a digit on a 7-segment display
/// The mask passed corresponds to the digit to be displayed
///
/// \param Mask The bitmask specifies which pins are set to HIGH or LOW.
/// \param Pins An array of pins connected to the display's segment LEDs.
//////////////////////////////////////////////////////////////////////////////
void switchSegments(uint8_t Mask, const SegmentPins_t& Pins) {
for (size_t I {0}; I < sizeof(SegmentPins_t); ++I) { digitalWrite(Pins[I], bit_is_set(Mask, I)); }
}
//////////////////////////////////////////////////////////////////////////////
/// \brief Main function for controlling the gate
/// Control is handled by a finite-state machine https://www.mikrocontroller.net/articles/Statemachine
///
//////////////////////////////////////////////////////////////////////////////
void gateControl() {
static GateStates States {GateStates::Closed};
static int8_t Counter {gc::SecondCounter};
Btn::ButtonState BtnState = Button.tick();
if (States == GateStates::Closed &&
(BtnState == Btn::ButtonState::shortPressed || BtnState == Btn::ButtonState::longPressed)) {
States = GateStates::Wait;
Fade.reset(); // Switch LED off
CountDownTimer.start();
}
switch (States) {
case GateStates::Closed:
if (FadeTimer(gc::FadeDelay_ms)) {
FadeTimer.start();
Fade(gc::PwmDataTable); // Fade LED by one step
}
break;
case GateStates::Wait: // Button was pressed, wait for "GateOpenDelay_ms" before open the gate
if (CountDownTimer(gc::GateOpenDelay_ms)) {
SDevice.write(90);
States = GateStates::Opened;
}
break;
case GateStates::Opened: // Gate is open for gc::SecondCounter * gc::CountDownDelay_ms millisconds
if (CountDownTimer(gc::CountDownDelay_ms)) {
CountDownTimer.start();
if (Counter < 0) { // if the countdown is over, reset all data, close the gate.
switchSegments(0, gc::SegmentPins); // 0 switches all segments off
States = GateStates::Closed;
Counter = gc::SecondCounter;
SDevice.write(0);
} else {
// Counter+1 because 0 should not be displayed
switchSegments(gc::SegmentMasks[Counter + 1], gc::SegmentPins);
--Counter;
}
}
break;
}
}
void setup() {
Serial.begin(115200);
for (const auto& Pin : gc::SegmentPins) { pinMode(Pin, OUTPUT); }
SDevice.attach(gc::ServoPin);
SDevice.write(0);
Button.begin();
Button.releaseOn(); // https://github.com/DoImant/Button_SL#methods
switchSegments(0, gc::SegmentPins);
}
void loop() { gateControl(); }