#include <Button_SL.hpp>
#include <Streaming.h>
#include <LiquidCrystal_I2C.h>
Print &cout {Serial};
// #define SERIAL_OUT // Remove comment character if an output is to be made on the serial console.
using namespace Btn;
//////////////////////////////////////////////////////////////////////////////
/// @brief Define an object that assigns an LED and a sound to a switch.
/// This class is derived from a button object.
/// The button object queries switches/buttons (debounced).
//////////////////////////////////////////////////////////////////////////////
class Select : public Button {
public:
// Constructor
Select(byte switchPin, byte ledPin, unsigned int note) : Button {switchPin}, ledPin {ledPin}, note {note} {}
// Methodes
void begin() {
pinMode(ledPin, OUTPUT);
Button::begin();
}
void ledOn() {
if (LOW == digitalRead(ledPin)) {
digitalWrite(ledPin, HIGH);
++counter;
}
}
void ledOff() const {
if (HIGH == digitalRead(ledPin)) { digitalWrite(ledPin, LOW); }
}
bool isLedOn() const { return digitalRead(ledPin) ? true : false; }
unsigned int getNote() const { return note; }
unsigned int getCount() const { return counter; }
// Attributes
private:
byte ledPin;
unsigned int note;
unsigned int counter {0};
};
constexpr char const *COLORS[4] {"gruen", "blau", "orange", "rot"};
// Define the frequencies for the desired note
constexpr unsigned int NOTE_G3 {196};
constexpr unsigned int NOTE_C4 {262};
constexpr unsigned int NOTE_E4 {330};
constexpr unsigned int NOTE_G5 {784};
constexpr unsigned int SOUND_LENGTH_MS {200}; // in milliseconds
constexpr byte BUZZER_PIN {8};
constexpr byte I2C_ADDR {0x27};
constexpr byte LCD_COLUMNS {16};
constexpr byte LCD_LINES {2};
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES); // Create display object
Print &lcdOut {lcd}; // Use Printstream for the output
// Create an array of selection objects with the appropriate initialization values
Select selections[] {
{4, 12, NOTE_G3}, // Switch Pin Nr., LED Pin Nr, Note value
{5, 11, NOTE_C4},
{6, 10, NOTE_E4},
{7, 9, NOTE_G5}
};
//////////////////////////////////////////////////////////////////////////////
/// @brief Check which of the connected switches are activated/pressed.
///
/// @tparam (&sel)[N] Array with objects of type Select
/// @return byte Bit mask that corresponds to the activated switches.
//////////////////////////////////////////////////////////////////////////////
template <size_t N> byte checkSwitch(Select (&sel)[N]) {
byte bitMask {0};
for (byte i = 0; i < N; ++i) {
if (true == sel[i].tick()) { bitMask |= (1 << i); } // For instance: 0000 1111 = four switchs are active
}
return bitMask;
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Switches an LED assigned to the switch on/off and plays a sound
/// when it is switched on.
///
/// @tparam (&sel)[N] Array with objects of type Select
/// @param buzzerPin The buzzer is connected to this pin
/// @param mask Bit mask of all active switches
/// @param isOnIdx Index on the selected object whose LED is switched on
/// @return byte Return the index to the active selection object
//////////////////////////////////////////////////////////////////////////////
template <size_t N> byte toggleSwitch(Select (&sel)[N], byte buzzerPin, const byte mask, byte isOnIdx) {
for (byte i = 0; i < N; ++i) {
if (isOnIdx != i && mask == (1 << i)) { // Search for the first active switch using the bit mask
if (isOnIdx != 0xFF) { // isOnIndex = 0xFF => no led is On
sel[isOnIdx].ledOff();
isOnIdx = 0xFF;
}
sel[i].ledOn();
playSound(buzzerPin, sel[i].getNote(), SOUND_LENGTH_MS);
isOnIdx = i;
}
}
// If a bit assigned to a switch is not set, then switch off the corresponding LED
if (not(mask & (1 << isOnIdx))) {
sel[isOnIdx].ledOff();
isOnIdx = 0xFF;
}
return isOnIdx;
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Plays a short sound.
///
/// @param pin Pin to which the buzzer is connected
/// @param note Tone frequency (corresponds to a specific note)
/// @param length Playing duration of the note in ms
//////////////////////////////////////////////////////////////////////////////
void playSound(byte pin, unsigned int note, unsigned int length) {
tone(pin, note);
delay(length);
noTone(pin);
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Displays which LED has been switched on how often (Serial console)
///
/// @tparam (&sel)[N] Array with objects of type Select
//////////////////////////////////////////////////////////////////////////////
template <size_t N> void printCount(Select (&sel)[N]) {
byte idx = 0;
for (auto &sel : selections) {
cout << _FMT("LED %", COLORS[idx]) << _PAD(6 - strlen(COLORS[idx]), ' ')
<< _FMT(": % mal angeschaltet\n", _WIDTH(sel.getCount(), 3));
++idx;
}
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Display of color abbreviations (lcd)
///
//////////////////////////////////////////////////////////////////////////////
void lcdMainScreen() {
lcd.setCursor(0, 0);
lcdOut << F("Gr:");
lcd.setCursor(8, 0);
lcdOut << F("Bl:");
lcd.setCursor(0, 1);
lcdOut << F("Or:");
lcd.setCursor(8, 1);
lcdOut << F("Re:");
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Display of the number of times an LED has been switched on (lcd)
///
/// @param sel Pointer to the array with the selection objects
//////////////////////////////////////////////////////////////////////////////
void lcdPrintCount(Select *sel) {
lcd.setCursor(4, 0);
lcdOut << _WIDTH(sel[0].getCount(), 3);
lcd.setCursor(12, 0);
lcdOut << _WIDTH(sel[1].getCount(), 3);
lcd.setCursor(4, 1);
lcdOut << _WIDTH(sel[2].getCount(), 3);
lcd.setCursor(12, 1);
lcdOut << _WIDTH(sel[3].getCount(), 3);
}
void setup() {
#ifdef SERIAL_OUT
Serial.begin(115200);
cout << "Program started: " << __FILE__ << endl;
#endif
for (auto &sel : selections) { sel.begin(); } // Initialize the selection objects
lcd.init();
lcd.backlight();
lcdMainScreen();
lcdPrintCount(selections);
}
void loop() {
static byte lastSwitchMask {0};
static byte isOnIdx {0xFF};
auto switchMask = checkSwitch(selections);
if (switchMask != lastSwitchMask) { // Only carry out the switching process if a switch position has changed
// cout << "Mask: " << _WIDTHZ(_BIN(switchMask), 4) << endl;
isOnIdx = toggleSwitch(selections, BUZZER_PIN, switchMask, isOnIdx);
lastSwitchMask = switchMask;
if (isOnIdx != 0xFF) {
lcdPrintCount(selections);
#ifdef SERIAL_OUT
printCount(selections);
cout << _PAD(32, '-') << endl;
#endif
}
}
}