#include <RotaryEncoder.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include "PwmTimer1.hpp"
#include "Button_SL.hpp"
constexpr byte MAX_HERTZ{15};
constexpr byte MIN_HERTZ{1};
constexpr byte DEFAULT_DUTY{50};
constexpr byte pin_in1{2};
constexpr byte pin_in2{3};
constexpr byte pin_btn{4};
constexpr byte pin_led{13};
#ifndef LCD20x4
constexpr byte I2C_ADDR{0x27};
constexpr byte LCD_COLUMNS{16};
constexpr byte LCD_LINES{2};
#else
constexpr byte I2C_ADDR{0x3F};
constexpr byte LCD_COLUMNS{20};
constexpr byte LCD_LINES{4};
#endif
enum class ProgState : byte { adjust = 0, start, enable, running, idle };
enum class InputState : byte { hertz = 0, duty, save };
struct EncoderData {
byte hertz;
byte dutyc;
};
constexpr byte INDICATOR_CHAR{'@'};
constexpr int EE_BASE_ADDRESS{0};
struct EEPROMData {
const byte ichar{INDICATOR_CHAR};
byte hertz;
byte dutyc;
};
using namespace Btn;
ButtonSL btn{pin_btn};
// RotaryEncoder encoder(pin_in1, pin_in2, RotaryEncoder::LatchMode::FOUR0);
// RotaryEncoder encoder(pin_in1, pin_in2, RotaryEncoder::LatchMode::TWO03);
RotaryEncoder encoder(pin_in1, pin_in2, RotaryEncoder::LatchMode::FOUR3);
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
EncoderData encData{MIN_HERTZ, DEFAULT_DUTY};
void readEEPROM(int, EncoderData &);
void writeEEPROM(int, EncoderData &);
bool askEncoder(EncoderData &, InputState);
void outputLcd(char *, EncoderData &, InputState);
void setup() {
Serial.begin(115200);
Serial.println("Start");
btn.begin();
btn.releaseOn();
btn.setDebounceTime_ms(150);
lcd.init();
lcd.backlight();
readEEPROM(EE_BASE_ADDRESS, encData);
Pwm.begin(encData.hertz, encData.dutyc);
}
void loop() {
static bool lastBtnAction{false};
static char buffer[LCD_COLUMNS + 1]; // Achtung: buffer an Textlänge + 1 anpassen. Sonst gibt es komische Effekte.
static ProgState state{ProgState::adjust};
static InputState inState{InputState::hertz};
if (askEncoder(encData, inState)) { state = ProgState::adjust; }
switch (state) {
case ProgState::adjust:
Pwm.stop();
buffer[0] = ' ';
outputLcd(buffer, encData, inState);
state = ProgState::idle;
break;
case ProgState::start:
buffer[0] = 'R';
lcd.setCursor(0, 1);
lcd.print(buffer[0]);
state = ProgState::enable;
[[fallthrough]] // https://en.cppreference.com/w/cpp/language/attributes/fallthrough
case ProgState::enable:
Pwm.setHz(encData.hertz);
Pwm.setDutyCycle(encData.dutyc);
Pwm.start();
state = ProgState::running;
break;
default: break;
}
// Tasterabfrage
switch (btn.tick()) {
case ButtonState::longPressed:
if (inState == InputState::save) {
writeEEPROM(EE_BASE_ADDRESS, encData);
inState = InputState::hertz;
outputLcd(buffer, encData, inState);
} else {
lastBtnAction = !lastBtnAction;
state = (lastBtnAction) ? ProgState::start : ProgState::adjust; // Will switch PWM on/off
}
break;
case ButtonState::shortPressed:
switch (inState) {
case InputState::hertz: inState = InputState::duty; break;
case InputState::duty: inState = (state != ProgState::running) ? InputState::save : InputState::hertz; break;
case InputState::save: inState = InputState::hertz; break;
default: break;
}
outputLcd(buffer, encData, inState);
break;
default: break;
}
}
bool askEncoder(EncoderData &data, InputState inState) {
encoder.tick();
RotaryEncoder::Direction direction = encoder.getDirection();
// Wenn der Encoder gar nicht bewegt wurde, Funktion sofort wieder verlassen.
if (direction == RotaryEncoder::Direction::NOROTATION) {
return false;
}
// Ansonsten den jeweils ausgewählten Wert einstellen.
switch (inState) {
case InputState::hertz:
if (direction == RotaryEncoder::Direction::CLOCKWISE) {
data.hertz = (++data.hertz > MAX_HERTZ) ? MIN_HERTZ : data.hertz;
} else if (direction == RotaryEncoder::Direction::COUNTERCLOCKWISE) {
data.hertz = (--data.hertz < MIN_HERTZ) ? MAX_HERTZ : data.hertz;
}
break;
case InputState::duty:
if (direction == RotaryEncoder::Direction::CLOCKWISE) {
data.dutyc = (++data.dutyc > 100) ? 0 : data.dutyc; // 100% or less
} else if (direction == RotaryEncoder::Direction::COUNTERCLOCKWISE) {
data.dutyc = (--data.dutyc > 100) ? 100 : data.dutyc; // Unsigned! -1 becomes 255
}
break;
default: break;
}
return true;
}
void outputLcd(char *text, EncoderData &ed, InputState is) {
bool isRunning = (text[0] == 'R') ? true : false;
sprintf(text, "%3d Hz Tv: %3d%%", ed.hertz, ed.dutyc);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(text);
memset(text, 32, LCD_COLUMNS); // 32 = Ascii code for the space character
text[LCD_COLUMNS + 1] = '\0';
switch (is) {
case InputState::hertz: text[2] = '^'; break;
case InputState::duty: text[14] = '^'; break;
case InputState::save: text[0] = 'S'; break;
default: break;
}
if (isRunning) { text[0] = 'R'; }
lcd.setCursor(0, 1);
lcd.print(text);
}
void readEEPROM(int eeAddr, EncoderData &ed) {
EEPROMData readData;
if (EEPROM.read(eeAddr) != readData.ichar) { // Executed if no data has ever been saved.
readData.hertz = MIN_HERTZ;
readData.dutyc = DEFAULT_DUTY;
EEPROM.put(eeAddr, readData);
} else {
EEPROM.get(eeAddr, readData); // If data has been saved, read it out.
}
ed.hertz = readData.hertz;
ed.dutyc = readData.dutyc;
}
void writeEEPROM(int eeAddr, EncoderData &ed) {
EEPROMData readData;
readData.hertz = ed.hertz;
readData.dutyc = ed.dutyc;
EEPROM.put(eeAddr, readData);
}
Kurzer Tastendruck -> Schalten zwischen den Optionen
Langer Tastendruckt Auswahl/Start
Drehen = Wert Ändern
S = Werte Speichern
R = Running