// #define digitalPinToPinChangeInterrupt
// #include <MUIU8g2.h>
#include <U8g2lib.h>
// #include <U8x8lib.h>
#include <EEPROM.h>
#include <PinChangeInterrupt.h>
#include <TimerOne.h>
#define COUNT_INPUT_PIN 2 // only pins 2 and 3 may be used for the count and reset inputs, as they are the only ones to support dedicated interrupts for rising edges
#define RESET_INPUT_PIN 3
#define OUTPUT_PIN 14
#define P_PIN 4
#define R_PIN 5
#define LEFT_PIN 6
#define RIGHT_PIN 7
#define UP_PIN 8
#define DOWN_PIN 9
#define POWER_LOSS_DETECTION_PIN 15 // An input for the main supply voltage (before any conversion to Arduino supply voltage) through a voltage divider.
// This is important, so that the program knows it has to store the current values to the EEPROM as a loss of supply voltage is imminent.
#define COUNT_DEBOUNCE_MILLIS 3 // time between bounces
#define RESET_DEBOUNCE_MILLIS 4 // time between bounces
// #define RESET_MINIMUM_PULSE_DURATION_MILLIS 20
#define ADJUST_SCREEN_TIMEOUT 10000
#define EEPROM_ADDRESS_PRESCALER 0
#define EEPROM_ADDRESS_PRESET EEPROM_ADDRESS_PRESCALER + 3
#define EEPROM_ADDRESS_COUNT EEPROM_ADDRESS_PRESET + 4
// Output indicator icon ("✓")
#define tick_width 16 // width of the symbol "✓"
#define tick_height 16 // height of the symbol "✓"
// bitmap for the "✓" output indicator symbol
const uint8_t tick_bits[] U8X8_PROGMEM = {
0xFE,
0x7F,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xEF,
0xFF,
0xE7,
0xFF,
0xF3,
0xF7,
0xF9,
0xE7,
0xFC,
0x4F,
0xFE,
0x1F,
0xFF,
0xBF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFE,
0x7F,
};
template <typename T>
class Fix4
{
template <typename U>
friend class Fix4;
protected:
int16_t fPart; // fractional part
T iPart; // integer part
char string[10];
char formatString[20];
void getFmtStr() {};
/* struct
{
uint8_t displayDigits = 6;
uint8_t decimalPlaces = 0;
bool leadingZeros = false;
} options;
*/
public:
Fix4() : iPart(0), fPart(0) {}
// constructor for double
explicit Fix4(double d)
{
iPart = d; // iPart = only integral part
d -= iPart; // d = only fractional part
for (size_t i = 0; i < 4; i++)
{
fPart *= 10; // shift fPart left
d *= 10.0; // shift d left
fPart += d; // put digit into fPart
d -= fPart % 10; // remove digit from d
}
}
Fix4(T i, int32_t f) : iPart(i), fPart(f) {}
Fix4(const Fix4<T> &i) : iPart(i.iPart), fPart(i.fPart) {}
Fix4(int8_t i) : fPart(0), iPart(i) {}
Fix4(int16_t i) : fPart(0), iPart(i) {}
Fix4(int32_t i) : fPart(0), iPart(i) {}
Fix4(int64_t i) : fPart(0), iPart(i) {}
Fix4(uint8_t i) : fPart(0), iPart(i) {}
Fix4(uint16_t i) : fPart(0), iPart(i) {}
Fix4(uint32_t i) : fPart(0), iPart(i) {}
Fix4(uint64_t i) : fPart(0), iPart(i) {}
// constructor for int
// Fix4(T i) : fPart(0), iPart(i) {}
Fix4(T i, int16_t f) : iPart(i), fPart(f) {}
void clear()
{
iPart = 0;
fPart = 0;
}
T getIPart()
{
return iPart;
}
int16_t getFPart()
{
return fPart;
}
void setIPart(T in)
{
iPart = in;
}
void setFPart(int16_t in)
{
fPart = in;
}
T *getIPartPtr()
{
return &iPart;
}
int16_t *getFPartPtr()
{
return &fPart;
}
char *getStrPtr()
{
if (formatString == "")
getFmtStr();
sprintf(string, formatString, iPart, fPart);
return string;
}
double getDouble()
{
double d = iPart;
d + (fPart / 10000);
return d;
}
#pragma region operators
Fix4<T> operator-() const
{
return Fix4<T>(-iPart, -fPart);
}
/* compound assignments */
template <typename U>
Fix4<T> &operator+=(const Fix4<U> &other)
{
fPart += other.fPart;
iPart += other.iPart + (fPart / 10000); // fpart is negative or 0
fPart %= 10000;
return *this;
}
template <typename U>
Fix4<T> operator-=(const Fix4<U> &other)
{
if (*this >= other) // initial number is positive and will stay positive (is greater than or equal to other number or other number is negative)
{
fPart -= other.fPart;
if (fPart < 0) // correcting fPart to be positive
{
fPart += 10000;
iPart -= 1;
}
if (other.sign) // other number is positive
{
iPart -= other.iPart; // fPart can not exceed -9999(see assumption e) because other number is positive
}
else // other number is negative
{
iPart -= other.iPart + fPart / 10000; // fPart can not exceed -9999(see assumption e) but can exceed +9999 because other number is negative
fPart %= 10000;
}
}
else if (*this < other) // initial number is positive and will become negative (is less than other number)
{
fPart -= other.fPart;
if (fPart > 0) // correcting fPart to be negative
{
fPart -= 10000;
iPart += 1;
}
iPart -= other.iPart; // fPart can not exceed -9999(see assumption e) and carry from fPart correction has already been applied if applicable
}
return *this;
}
// assignment
template <typename U>
Fix4<T> operator=(const Fix4<U> &a)
{
fPart = a.fPart;
iPart = static_cast<T>(a.iPart);
setSign();
return *this;
}
/* comparators against Fix4 */
template <typename U>
bool Fix4<T>::operator>=(const Fix4<U> &a)
{
if (iPart > a.iPart)
return true;
else if (iPart == a.iPart && fPart >= a.fPart)
return true;
else
return false;
}
template <typename U>
bool operator<=(const Fix4<U> &a)
{
if (iPart < a.iPart)
return true;
else if (iPart == a.iPart && fPart <= a.fPart)
return true;
else
return false;
}
template <typename U>
bool operator>(const Fix4<U> &a)
{
if (iPart > a.iPart)
return true;
else if (iPart == a.iPart && fPart > a.fPart)
return true;
else
return false;
}
template <typename U>
bool operator<(const Fix4<U> &a)
{
if (iPart < a.iPart)
return true;
else if (iPart == a.iPart && fPart < a.fPart)
return true;
else
return false;
}
template <typename U>
bool operator==(const Fix4<U> &a)
{
return (iPart == a.iPart && fPart == a.fPart);
}
/* comparators against other types */
template <typename U>
bool operator>=(const U &a)
{
if (iPart > a)
return true;
else if (iPart == a && fPart >= 0)
return true;
else
return false;
}
template <typename U>
bool operator<=(const U &a)
{
if (iPart < a)
return true;
else if (iPart == a && fPart <= 0)
return true;
else
return false;
}
template <typename U>
bool operator>(const U &a)
{
if (iPart > a)
return true;
else if (iPart == a && fPart > 0)
return true;
else
return false;
}
template <typename U>
bool operator<(const U &a)
{
if (iPart < a)
return true;
else if (iPart == a && fPart < 0)
return true;
else
return false;
}
// tests if the integer part of the Fix4 number is equal to the other value
template <typename U>
bool operator==(const U &a)
{
return (iPart == a.iPart && fPart == fPart % 5000);
}
/* comparators against double */
bool operator>=(const double &a)
{
return (this >= Fix4<T>(a));
}
bool operator<=(const double &a)
{
return (this <= Fix4<T>(a));
}
bool operator>(const double &a)
{
return (this > Fix4<T>(a));
}
bool operator<(const double &a)
{
return (this < Fix4<T>(a));
}
bool operator==(const double &a)
{
return (this == Fix4<T>(a));
}
#pragma endregion operators
};
struct eepromCount
{
uint32_t iPart;
int16_t fPart;
};
struct eepromPrescaler
{
uint8_t iPart;
int16_t fPart;
};
#pragma region more stuff for Fix4
template <typename T, typename U>
Fix4<T> operator+(Fix4<T> a, const Fix4<U> &b)
{
a += b;
return a;
}
template <typename T, typename U>
Fix4<T> operator-(Fix4<T> a, const Fix4<U> &b)
{
a -= b;
return a;
}
/* template <> \
void Fix4<uint8_t>::getFmtStr() \
{ \
String fmt = String("%%"); \
fmt.reserve(15); \
if (options.leadingZeros) \
fmt += String("0"); \
fmt += String(options.displayDigits - options.decimalPlaces); \
fmt += String("hhu"); \
if (options.decimalPlaces > 0) \
{ \
fmt += String("%%.0"); \
fmt += String(options.decimalPlaces); \
fmt += String("i"); \
} \
for (uint8_t i = 0; i < sizeof(formatString) / sizeof(formatString[0]); i++) \
{ \
formatString[i] = fmt.c_str()[i]; \
} \
} \
\ \
template <> \
void Fix4<uint16_t>::getFmtStr() \
{ \
String fmt = String("%%"); \
fmt.reserve(15); \
if (options.leadingZeros) \
fmt += String("0"); \
fmt += String(options.displayDigits - options.decimalPlaces); \
fmt += String("u"); \
if (options.decimalPlaces > 0) \
{ \
fmt += String("%%.0"); \
fmt += String(options.decimalPlaces); \
fmt += String("i"); \
} \
for (uint8_t i = 0; i < sizeof(formatString) / sizeof(formatString[0]); i++) \
{ \
formatString[i] = fmt.c_str()[i]; \
} \
} \
\ \
template <> \
void Fix4<uint32_t>::getFmtStr() \
{ \
String fmt = String("%%"); \
fmt.reserve(15); \
if (options.leadingZeros) \
fmt += String("0"); \
fmt += String(options.displayDigits - options.decimalPlaces); \
fmt += String("lu"); \
if (options.decimalPlaces > 0) \
{ \
fmt += String("%%.0"); \
fmt += String(options.decimalPlaces); \
fmt += String("i"); \
} \
for (uint8_t i = 0; i < sizeof(formatString) / sizeof(formatString[0]); i++) \
{ \
formatString[i] = fmt.c_str()[i]; \
} \
} \
\ \
template <> \
void Fix4<uint64_t>::getFmtStr() \
{ \
String fmt = String("%%"); \
fmt.reserve(15); \
if (options.leadingZeros) \
fmt += String("0"); \
fmt += String(options.displayDigits - options.decimalPlaces); \
fmt += String("llu"); \
if (options.decimalPlaces > 0) \
{ \
fmt += String("%%.0"); \
fmt += String(options.decimalPlaces); \
fmt += String("i"); \
} \
for (uint8_t i = 0; i < sizeof(formatString) / sizeof(formatString[0]); i++) \
{ \
formatString[i] = fmt.c_str()[i]; \
} \
} \
\ \
template <> \
void Fix4<int8_t>::getFmtStr() \
{ \
String fmt = String("%%"); \
fmt.reserve(15); \
if (options.leadingZeros) \
fmt += String("0"); \
fmt += String(options.displayDigits - options.decimalPlaces); \
fmt += String("hhi"); \
if (options.decimalPlaces > 0) \
{ \
fmt += String("%%.0"); \
fmt += String(options.decimalPlaces); \
fmt += String("i"); \
} \
for (uint8_t i = 0; i < sizeof(formatString) / sizeof(formatString[0]); i++) \
{ \
formatString[i] = fmt.c_str()[i]; \
} \
} \
\ \
template <> \
void Fix4<int16_t>::getFmtStr() \
{ \
String fmt = String("%%"); \
fmt.reserve(15); \
if (options.leadingZeros) \
fmt += String("0"); \
fmt += String(options.displayDigits - options.decimalPlaces); \
fmt += String("i"); \
if (options.decimalPlaces > 0) \
{ \
fmt += String("%%.0"); \
fmt += String(options.decimalPlaces); \
fmt += String("i"); \
} \
for (uint8_t i = 0; i < sizeof(formatString) / sizeof(formatString[0]); i++) \
{ \
formatString[i] = fmt.c_str()[i]; \
} \
} \
\ \
template <> \
void Fix4<int32_t>::getFmtStr() \
{ \
String fmt = String("%%"); \
fmt.reserve(15); \
if (options.leadingZeros) \
fmt += String("0"); \
fmt += String(options.displayDigits - options.decimalPlaces); \
fmt += String("li"); \
if (options.decimalPlaces > 0) \
{ \
fmt += String("%%.0"); \
fmt += String(options.decimalPlaces); \
fmt += String("i"); \
} \
for (uint8_t i = 0; i < sizeof(formatString) / sizeof(formatString[0]); i++) \
{ \
formatString[i] = fmt.c_str()[i]; \
} \
} \
\ \
template <> \
void Fix4<int64_t>::getFmtStr() \
{ \
String fmt = String("%%"); \
fmt.reserve(15); \
if (options.leadingZeros) \
fmt += String("0"); \
fmt += String(options.displayDigits - options.decimalPlaces); \
fmt += String("lli"); \
if (options.decimalPlaces > 0) \
{ \
fmt += String("%%.0"); \
fmt += String(options.decimalPlaces); \
fmt += String("i"); \
} \
for (uint8_t i = 0; i < sizeof(formatString) / sizeof(formatString[0]); i++) \
{ \
formatString[i] = fmt.c_str()[i]; \
} \
} \
*/ \
#pragma endregion
class Prescaler
{
private:
Fix4<uint8_t> value;
Fix4<uint8_t> adjustValue;
// int8_t adjustArray[6];
char adjustString[8];
public:
Prescaler() {};
Prescaler(double d) : value(d) {};
Prescaler(Fix4<uint8_t> a) : value(a) {}
// increase the digit at the specified place (starts at 0, right to left)
void increaseDigit(uint8_t place)
{
if (place < 4) // place is in fpart
{
// get the number that is there
int16_t fPart = adjustValue.getFPart();
if ((fPart / (uint16_t)round(pow(10, place)) % 10) < 9)
fPart += (uint16_t)round(pow(10, place));
else
fPart -= 9 * (uint16_t)round(pow(10, place));
adjustValue.setFPart(fPart);
}
else if (place < 6) // place is in ipart
{
uint8_t iPart = adjustValue.getIPart();
if ((iPart / (uint16_t)round(pow(10, place - 4 /* because place 4 is place 0 of iPart */)) % 10) < 9)
iPart += (uint16_t)round(pow(10, place - 4));
else
iPart -= 9 * (uint16_t)round(pow(10, place - 4));
adjustValue.setIPart(iPart);
}
}
void decreaseDigit(uint8_t place)
{
if (place < 4) // place is in fpart
{
// get the number that is there
int16_t fPart = adjustValue.getFPart();
if ((fPart / (uint16_t)round(pow(10, place)) % 10) > 0)
fPart -= (uint16_t)round(pow(10, place));
else
fPart += 9 * (uint16_t)round(pow(10, place));
adjustValue.setFPart(fPart);
}
else /* if (place < 6) */ // place is in ipart
{
uint8_t iPart = adjustValue.getIPart();
Serial.println(pow(10, place - 4));
Serial.println(round(pow(10, place - 4)));
Serial.println(round(pow(10, place - 4)) % 10);
Serial.println((iPart / (uint16_t)round(pow(10, place - 4)) % 10));
if ((iPart / (uint16_t)round(pow(10, place - 4) /* because place 4 is place 0 of iPart */) % 10) > 0)
{
iPart -= (uint16_t)(round(pow(10, place - 4)));
Serial.println(F("subtract"));
}
else
{
iPart += 9 * (uint16_t)(round(pow(10, place - 4)));
Serial.println(F("add"));
}
adjustValue.setIPart(iPart);
}
}
void loadAdjust()
{
adjustValue = value;
}
void saveAdjust()
{
value = adjustValue;
}
// returns the position in the string that the number with the place in the argument is in
uint8_t placeInString(uint8_t place)
{
if (place < 4)
return place;
else
return place + 1;
}
// char *getAdjustString()
void getAdjustString()
{
sprintf(adjustString, "%02hhu.%04i", adjustValue.getIPart(), adjustValue.getFPart());
}
void constructAdjustStringIn(char *s)
{
sprintf(s, "%02hhu.%04i", adjustValue.getIPart(), adjustValue.getFPart());
}
Fix4<uint8_t> *getValuePtr()
{
return &value;
}
Fix4<uint8_t> *getAdjustValuePtr()
{
return &adjustValue;
}
Fix4<uint8_t> getAdjustValue()
{
return adjustValue;
}
double getDouble()
{
return value.getDouble();
}
eepromPrescaler getStructureForEeprom()
{
eepromPrescaler structure;
structure.iPart = value.getIPart();
structure.fPart = value.getFPart();
return structure;
}
void loadFromEeprom(eepromPrescaler structure)
{
value.setIPart(structure.iPart);
value.setFPart(structure.fPart);
}
};
class Count
{
// friend class Counter;
private:
Fix4<uint32_t> countValue;
// uint32_t maxCount = 999999;
public:
Count() : countValue() {}
Count(Fix4<uint32_t> c) : countValue(c) {}
void reset()
{
countValue = 0;
}
Fix4<uint32_t> *getValuePtr()
{
return &countValue;
}
void setCount(Fix4<uint32_t> c)
{
countValue = c;
}
// If max is greater than countValue, reduces it to max. Otherwise keeps the current value.
void clamp(uint32_t max)
{
if (*countValue.getIPartPtr() > max)
{
*countValue.getIPartPtr() = max;
*countValue.getFPartPtr() = 0;
}
}
Count &operator+=(Prescaler &other)
{
countValue += *other.getValuePtr();
return *this;
}
uint32_t getRoundedValue()
{
if (countValue.getFPart() < 5000)
return countValue.getIPart();
else
return countValue.getIPart() + 1;
}
void constructRoundedStringIn(char *s)
{
sprintf(s, "%lum", (*countValue.getFPartPtr() < 5000) ? (*countValue.getIPartPtr()) : ((*countValue.getIPartPtr()) + 1));
}
double getDouble()
{
return countValue.getDouble();
}
eepromCount getStructureForEeprom()
{
eepromCount structure;
structure.iPart = countValue.getIPart();
structure.fPart = countValue.getFPart();
return structure;
}
void loadFromEeprom(eepromCount structure)
{
countValue.setIPart(structure.iPart);
countValue.setFPart(structure.fPart);
}
};
class Preset
{
// friend class Counter;
private:
uint32_t value;
uint32_t adjustValue;
public:
Preset() {}
Preset(uint32_t p) : value(p) {}
uint32_t getValue()
{
return value;
}
uint32_t *getValuePtr()
{
return &value;
}
uint32_t getAdjustValue()
{
return adjustValue;
}
uint32_t *getAdjustValuePtr()
{
return &adjustValue;
}
void loadAdjust()
{
adjustValue = value;
}
void saveAdjust()
{
value = adjustValue;
}
void setValue(uint32_t v)
{
value = v;
}
void setAdjust(uint32_t v)
{
adjustValue = v;
}
void constructStringIn(char *s)
{
sprintf(s, "%lum", value);
}
void constructAdjustStringIn(char *s)
{
sprintf(s, "%06lum", adjustValue);
}
void increaseDigit(uint8_t digit)
{
if ((adjustValue / (uint32_t)round(pow(10, digit)) % 10) < 9) // get the number that is there
adjustValue += (uint32_t)round(pow(10, digit)); // increase it up to 9
else //
adjustValue -= 9 * (uint32_t)round(pow(10, digit)); // then go back around to 0
}
void decreaseDigit(uint8_t digit)
{
if ((adjustValue / (uint32_t)round(pow(10, digit)) % 10) > 0) // get the number that is there
{
Serial.println(F("registered as > 0, subtracting 1"));
adjustValue -= (uint32_t)round(pow(10, digit)); // decrease down to 0
}
else
{
Serial.println(F("registered as <= 0, adding 9"));
adjustValue += 9 * (uint32_t)round(pow(10, digit)); // then go back around to 9
}
}
uint32_t getValueForEeprom()
{
return value;
}
void loadFromEeprom(uint32_t eepromValue)
{
value = eepromValue;
}
};
class Counter
{
private:
uint32_t maxCount = 999999;
public:
Count count;
Preset preset;
Prescaler prescaler;
uint8_t timeCounter[11];
unsigned long estimatorTimestamp;
uint8_t currentTimeCounter;
bool targetReached;
Counter() : count(0), preset(0), prescaler(1.0) {}
Counter(Fix4<uint32_t> c, uint32_t prst) : count(c), preset(prst), prescaler(1.0) {}
Counter(Fix4<uint32_t> c, uint32_t prst, Fix4<uint8_t> prscl) : count(c), preset(prst), prescaler(prscl) {}
void setMaxCount(uint32_t i)
{
maxCount = i;
}
void countUp()
{
count += prescaler;
count.clamp(maxCount);
if (*count.getValuePtr() >= *preset.getValuePtr())
{
targetReached = true;
digitalWrite(OUTPUT_PIN, true);
}
// if (millis() - estimatorTimestamp >= 500)
// {
// currentTimeCounter = (currentTimeCounter + 1) % 11;
// timeCounter[currentTimeCounter] = 0;
// }
// timeCounter[currentTimeCounter]++;
}
// uint32_t timeCounterCheck()
// {
// uint16_t totalTimeCounter = 0;
// for (uint8_t i = 0; i < 10; i++)
// {
// totalTimeCounter += timeCounter[currentTimeCounter + 1 + i];
// }
// if (totalTimeCounter != 0)
// return ((preset.getValue() - count.getDouble()) * 2) / totalTimeCounter;
// else
// return 0;
// }
bool check()
{
return *count.getValuePtr() >= *preset.getValuePtr();
}
void refreshOutput()
{
targetReached = *count.getValuePtr() >= *preset.getValuePtr();
digitalWrite(OUTPUT_PIN, targetReached);
}
void reset()
{
count.reset();
refreshOutput();
}
};
Counter counter;
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, 10, 9);
class Button
{
private:
unsigned long lastActive;
bool inputInvert = false;
uint8_t pin;
uint8_t mode;
unsigned long holdDelay = 3000;
unsigned long riseTimestamp;
unsigned long debounceMillis = 10;
enum /* class state */
{
off, // button has been released, debounce timer is running (but is reset if button is pressed again) to return to ready state
ready, // button is not pressed and ready to be activated
risingEdge, // start of button press
isPressed, // button is pressed
shortPressed, // button has been pressed, delay for long press is running, releasing will trigger signal for short press
longPressed, // button is held down after long press has been triggered
} buttonState;
public:
enum class signal // output
{
off,
shortPressStart,
shortPressed,
longPressed
};
Button() {};
Button(uint8_t pin, uint8_t mode, bool normallyClosed) : pin(pin), mode(mode), inputInvert(normallyClosed) {
pinMode(pin, mode);
};
void initialize()
{
pinMode(pin, mode);
}
bool refresh()
{
switch (buttonState)
{
case off:
{
if (inputInvert != digitalRead(pin)) // button is pressed
lastActive = millis();
else if (millis() - lastActive >= debounceMillis)
buttonState = ready;
break;
}
case ready:
{
if (inputInvert != digitalRead(pin))
{
riseTimestamp = millis();
buttonState = risingEdge;
}
break;
}
case risingEdge:
{
if (inputInvert != digitalRead(pin))
{
buttonState = isPressed;
}
else
{
buttonState = shortPressed;
}
break;
}
case isPressed:
{
if (millis() - riseTimestamp >= holdDelay)
{
buttonState = longPressed;
lastActive = millis();
}
else if (inputInvert == digitalRead(pin)) // button is not pressed
{
buttonState = shortPressed;
}
break;
}
case shortPressed:
{
buttonState = off;
break;
}
case longPressed:
{
if (inputInvert != digitalRead(pin))
lastActive = millis();
else
buttonState = off;
break;
}
}
return inputInvert != digitalRead(pin);
}
bool startPress()
{
return buttonState == risingEdge;
}
bool shortPress()
{
return buttonState == shortPressed;
}
bool longPress()
{
return buttonState == longPressed;
}
bool pressed()
{
return buttonState == isPressed;
}
bool released()
{
return buttonState == shortPressed || buttonState == longPressed;
}
void cancel()
{
buttonState = off;
}
};
Button pButton(P_PIN, INPUT_PULLUP, true);
Button rButton(R_PIN, INPUT_PULLUP, true);
Button leftButton(LEFT_PIN, INPUT_PULLUP, true);
Button rightButton(RIGHT_PIN, INPUT_PULLUP, true);
Button upButton(UP_PIN, INPUT_PULLUP, true);
Button downButton(DOWN_PIN, INPUT_PULLUP, true);
class UI
{
public:
enum class screen
{
main,
preset,
prescaler,
powerLoss
};
screen selectedScreen;
private:
u8g2_uint_t screenWidth;
u8g2_uint_t screenHeight;
U8G2 &u8g2;
char displayString1[10];
char displayString2[10];
screen currentScreen;
uint8_t currentDigit;
uint8_t selectedDigit;
uint8_t progress = 0;
bool cursorOn;
unsigned long blinkStart;
unsigned long timeoutStart;
struct
{
struct
{
struct
{
uint8_t x;
uint8_t width;
char text[5] = "IST:";
} countLabel;
struct
{
uint8_t x;
uint8_t width;
char text[6] = "SOLL:";
} presetLabel;
struct
{
struct
{
uint8_t x;
uint8_t y;
uint8_t width = 90;
uint8_t height = 10;
} frame;
struct
{
uint8_t x;
uint8_t y;
uint8_t widthFull;
uint8_t height;
uint8_t width(uint32_t valueNow, uint32_t valueFull)
{
return valueNow * widthFull / valueFull;
}
} bar;
} progressBar;
} main;
struct
{
struct
{
uint8_t x;
uint8_t width;
char text[6] = "SOLL:";
} presetLabel;
struct
{
uint8_t x;
uint8_t width;
char text[5] = "IST:";
} countLabel;
} preset;
struct
{
struct
{
uint8_t x;
uint8_t width;
char text[11] = "Prescaler:";
} prescalerLabel;
struct
{
uint8_t x;
uint8_t y;
uint8_t width;
uint8_t height;
const uint8_t *font;
char text[17] = "Meter pro Impuls";
} prescalerUnit;
} prescaler;
struct
{
struct
{
uint8_t y;
const uint8_t *font;
uint8_t height;
} firstLabel;
struct
{
uint8_t y;
const uint8_t *font;
uint8_t height;
} secondLabel;
struct
{
uint8_t y;
const uint8_t *font;
uint8_t widthMax;
uint8_t height;
} firstValue;
struct
{
uint8_t y;
const uint8_t *font;
uint8_t widthMax;
uint8_t height;
} secondValue;
struct
{
uint8_t firstValueToSecondValue = 5;
uint8_t labelToValue = 2;
uint8_t secondValueToProgressbar = 5;
uint8_t targetReachedIndicatorIconToText = 2;
uint8_t valueToCursor = 1;
} spacing;
struct
{
uint8_t y;
uint8_t width;
uint8_t height = 2;
} cursor;
struct
{
struct
{
uint8_t x;
uint8_t y;
uint8_t width = tick_width;
uint8_t height = tick_height;
} icon;
struct
{
const uint8_t *font;
uint8_t x;
uint8_t y;
uint8_t width;
uint8_t height = 2;
char text[7] = "FERTIG";
} text;
} indicatorTargetReached;
} all;
} layout;
bool refreshButtons()
{
return (pButton.refresh() || rButton.refresh() || leftButton.refresh() || rightButton.refresh() || upButton.refresh() || downButton.refresh());
}
void cursorLeft()
{
selectedDigit = (selectedDigit + 1) % 6;
}
void cursorRight()
{
selectedDigit = (selectedDigit + 6 - 1) % 6;
}
void mainButtons()
{
refreshButtons();
if (pButton.shortPress())
{
counter.preset.loadAdjust();
selectedScreen = screen::preset;
selectedDigit = 0;
blinkStart = millis();
timeoutStart = millis();
}
if (pButton.longPress())
{
counter.prescaler.loadAdjust();
selectedScreen = screen::prescaler;
selectedDigit = 4;
blinkStart = millis();
timeoutStart = millis();
}
if (rButton.startPress())
counter.reset();
}
void presetButtons()
{
if (refreshButtons())
timeoutStart = millis();
if (millis() - timeoutStart > ADJUST_SCREEN_TIMEOUT)
selectedScreen = screen::main;
if (pButton.shortPress())
{
pButton.cancel();
counter.preset.saveAdjust();
counter.refreshOutput();
selectedScreen = screen::main;
}
if (rButton.shortPress())
{
counter.preset.loadAdjust();
}
if (rButton.longPress())
{
counter.reset();
}
if (leftButton.startPress())
{
cursorLeft();
}
if (rightButton.startPress())
{
cursorRight();
}
if (upButton.startPress())
{
counter.preset.increaseDigit(selectedDigit);
}
if (downButton.startPress())
{
counter.preset.decreaseDigit(selectedDigit);
}
}
void prescalerButtons()
{
if (refreshButtons())
timeoutStart = millis();
if (millis() - timeoutStart > ADJUST_SCREEN_TIMEOUT)
selectedScreen = screen::main;
if (pButton.shortPress())
{
pButton.cancel();
counter.prescaler.saveAdjust();
selectedScreen = screen::main;
}
if (rButton.shortPress())
{
counter.prescaler.loadAdjust();
}
if (rButton.longPress())
{
counter.preset.setAdjust(0);
}
if (leftButton.startPress())
{
cursorLeft();
}
if (rightButton.startPress())
{
cursorRight();
}
if (upButton.startPress())
{
counter.prescaler.increaseDigit(selectedDigit);
}
if (downButton.startPress())
{
counter.prescaler.decreaseDigit(selectedDigit);
}
}
void drawMain()
{
u8g2.setFont(layout.all.firstLabel.font);
u8g2.drawStr(layout.main.countLabel.x, layout.all.firstLabel.y, layout.main.countLabel.text);
u8g2.setFont(layout.all.firstValue.font);
u8g2.drawStr(screenWidth - u8g2.getStrWidth(displayString1), layout.all.firstValue.y, displayString1);
u8g2.setFont(layout.all.secondLabel.font);
u8g2.drawStr(layout.main.presetLabel.x, layout.all.secondLabel.y, layout.main.presetLabel.text);
u8g2.setFont(layout.all.secondValue.font);
u8g2.drawStr(screenWidth - u8g2.getStrWidth(displayString2), layout.all.secondValue.y, displayString2);
if (!counter.targetReached)
{
u8g2.drawFrame(layout.main.progressBar.frame.x, layout.main.progressBar.frame.y, layout.main.progressBar.frame.width, layout.main.progressBar.frame.height);
u8g2.drawBox(layout.main.progressBar.bar.x, layout.main.progressBar.bar.y, min(layout.main.progressBar.bar.widthFull, layout.main.progressBar.bar.width(counter.count.getRoundedValue(), counter.preset.getValue())), layout.main.progressBar.bar.height);
}
else
{
u8g2.setFont(layout.all.indicatorTargetReached.text.font);
u8g2.drawXBMP(layout.all.indicatorTargetReached.icon.x, layout.all.indicatorTargetReached.icon.y, tick_width, tick_height, tick_bits);
u8g2.drawStr(layout.all.indicatorTargetReached.text.x, layout.all.indicatorTargetReached.text.y, layout.all.indicatorTargetReached.text.text);
}
}
void drawPreset()
{
u8g2.setFont(layout.all.firstLabel.font);
u8g2.drawStr(layout.preset.presetLabel.x, layout.all.firstLabel.y, layout.preset.presetLabel.text);
u8g2.setFont(layout.all.firstValue.font);
u8g2.drawStr(screenWidth - u8g2.getStrWidth(displayString1), layout.all.firstValue.y, displayString1);
u8g2.setFont(layout.all.secondLabel.font);
u8g2.drawStr(layout.preset.countLabel.x, layout.all.secondLabel.y, layout.preset.countLabel.text);
u8g2.setFont(layout.all.secondValue.font);
u8g2.drawStr(screenWidth - u8g2.getStrWidth(displayString2), layout.all.secondValue.y, displayString2);
if (counter.targetReached)
{
u8g2.setFont(layout.all.indicatorTargetReached.text.font);
u8g2.drawXBMP(layout.all.indicatorTargetReached.icon.x, layout.all.indicatorTargetReached.icon.y, tick_width, tick_height, tick_bits);
u8g2.drawStr(layout.all.indicatorTargetReached.text.x, layout.all.indicatorTargetReached.text.y, layout.all.indicatorTargetReached.text.text);
}
u8g2.setFont(layout.all.firstValue.font);
if (cursorOn)
u8g2.drawBox(screenWidth - (u8g2.getStrWidth("0") * (currentDigit + 2)), layout.all.cursor.y, layout.all.cursor.width, layout.all.cursor.height);
}
void drawPrescaler()
{
u8g2.setFont(layout.all.firstLabel.font);
u8g2.drawStr(layout.prescaler.prescalerLabel.x, layout.all.firstLabel.y, layout.prescaler.prescalerLabel.text);
u8g2.setFont(layout.all.firstValue.font);
u8g2.drawStr(screenWidth - u8g2.getStrWidth(displayString1), layout.all.firstValue.y, displayString1);
u8g2.setFont(layout.prescaler.prescalerUnit.font);
u8g2.drawStr(layout.prescaler.prescalerUnit.x, layout.prescaler.prescalerUnit.y, layout.prescaler.prescalerUnit.text);
if (counter.targetReached)
{
u8g2.setFont(layout.all.indicatorTargetReached.text.font);
u8g2.drawXBMP(layout.all.indicatorTargetReached.icon.x, layout.all.indicatorTargetReached.icon.y, tick_width, tick_height, tick_bits);
u8g2.drawStr(layout.all.indicatorTargetReached.text.x, layout.all.indicatorTargetReached.text.y, layout.all.indicatorTargetReached.text.text);
}
u8g2.setFont(layout.all.firstValue.font);
if (cursorOn)
u8g2.drawBox(screenWidth - (u8g2.getStrWidth("0") * (counter.prescaler.placeInString(currentDigit) + 1)), layout.all.cursor.y, layout.all.cursor.width, layout.all.cursor.height);
}
void drawPowerloss()
{
u8g2.setFont(u8g2_font_profont11_mr);
u8g2.drawStr((screenWidth / 2) - (u8g2.getStrWidth("Spannungsausfall") / 2), (screenHeight / 2) - (u8g2.getAscent() / 2), "Spannungsausfall");
}
public:
void refreshScreen()
{
currentScreen = selectedScreen;
currentDigit = selectedDigit;
progress = (counter.count.getRoundedValue() / counter.preset.getValue());
cursorOn = (millis() - blinkStart) % 1000 < 500;
switch (selectedScreen)
{
case screen::main:
counter.count.constructRoundedStringIn(displayString1);
counter.preset.constructStringIn(displayString2);
break;
case screen::preset:
counter.preset.constructAdjustStringIn(displayString1);
counter.count.constructRoundedStringIn(displayString2);
break;
case screen::prescaler:
counter.prescaler.constructAdjustStringIn(displayString1);
break;
}
}
void drawCurrentScreen()
{
u8g2.drawFrame(-1, -1, 104, 66);
switch (currentScreen)
{
case screen::main:
mainButtons();
drawMain();
break;
case screen::preset:
presetButtons();
drawPreset();
break;
case screen::prescaler:
prescalerButtons();
drawPrescaler();
break;
case screen::powerLoss:
drawPowerloss();
break;
}
}
// UI(u8g2_uint_t width, u8g2_uint_t height):screenWidth(width), screenHeight(height), main(width, height, &u8g2), preset(width, height, &u8g2), prescaler(width, height, &u8g2) {}
UI(u8g2_uint_t width, u8g2_uint_t height, U8G2 &u8g2) : screenWidth(width), screenHeight(height), u8g2(u8g2)
{
currentScreen = screen::main;
layout.all.firstLabel.font = u8g2_font_profont10_mr;
layout.all.secondLabel.font = u8g2_font_profont10_mr;
layout.all.firstValue.font = u8g2_font_profont22_mr;
layout.all.secondValue.font = u8g2_font_profont17_mr;
layout.all.indicatorTargetReached.text.font = u8g2_font_profont10_mr;
layout.prescaler.prescalerUnit.font = u8g2_font_profont10_mr;
u8g2.setFont(layout.all.firstValue.font);
layout.all.firstValue.widthMax = u8g2.getStrWidth("000000m");
layout.all.firstValue.height = u8g2.getAscent();
layout.all.cursor.width = u8g2.getStrWidth("0") - 2;
u8g2.setFont(layout.all.secondValue.font);
layout.all.secondValue.widthMax = u8g2.getStrWidth("000000m");
layout.all.secondValue.height = u8g2.getAscent();
u8g2.setFont(layout.all.firstLabel.font);
layout.main.countLabel.width = u8g2.getStrWidth(layout.main.countLabel.text);
layout.preset.presetLabel.width = u8g2.getStrWidth(layout.preset.presetLabel.text);
layout.prescaler.prescalerLabel.width = u8g2.getStrWidth(layout.prescaler.prescalerLabel.text);
layout.all.firstLabel.height = u8g2.getAscent();
u8g2.setFont(layout.all.secondLabel.font);
layout.main.presetLabel.width = u8g2.getStrWidth(layout.main.presetLabel.text);
layout.preset.countLabel.width = u8g2.getStrWidth(layout.preset.countLabel.text);
layout.all.secondLabel.height = u8g2.getAscent();
u8g2.setFont(layout.all.indicatorTargetReached.text.font);
layout.all.indicatorTargetReached.text.width = u8g2.getStrWidth(layout.all.indicatorTargetReached.text.text);
layout.all.indicatorTargetReached.text.height = u8g2.getAscent();
u8g2.setFont(layout.prescaler.prescalerUnit.font);
layout.prescaler.prescalerUnit.width = u8g2.getStrWidth(layout.prescaler.prescalerUnit.text);
layout.prescaler.prescalerUnit.height = u8g2.getAscent();
layout.all.firstLabel.y = layout.all.firstLabel.height;
layout.all.firstValue.y = layout.all.firstLabel.y + layout.all.firstValue.height + layout.all.spacing.labelToValue;
layout.main.countLabel.x = screenWidth - layout.main.countLabel.width;
layout.main.presetLabel.x = screenWidth - layout.main.presetLabel.width;
layout.preset.presetLabel.x = screenWidth - layout.preset.presetLabel.width;
layout.preset.countLabel.x = screenWidth - layout.preset.countLabel.width;
layout.prescaler.prescalerLabel.x = screenWidth - layout.prescaler.prescalerLabel.width;
layout.prescaler.prescalerUnit.x = screenWidth - layout.prescaler.prescalerUnit.width;
layout.prescaler.prescalerUnit.y = layout.all.firstValue.y + layout.prescaler.prescalerUnit.height + layout.all.spacing.firstValueToSecondValue;
layout.all.secondLabel.y = layout.all.firstValue.y + layout.all.spacing.firstValueToSecondValue + layout.all.secondLabel.height;
layout.all.secondValue.y = layout.all.secondLabel.y + layout.all.spacing.labelToValue + layout.all.secondValue.height;
layout.main.progressBar.frame.y = layout.all.secondValue.y + layout.all.spacing.secondValueToProgressbar;
layout.main.progressBar.frame.x = (screenWidth - layout.main.progressBar.frame.width) / 2;
layout.main.progressBar.bar.x = layout.main.progressBar.frame.x + 2;
layout.main.progressBar.bar.y = layout.main.progressBar.frame.y + 2;
layout.main.progressBar.bar.height = layout.main.progressBar.frame.height - 4;
layout.main.progressBar.bar.widthFull = layout.main.progressBar.frame.width - 4;
layout.all.indicatorTargetReached.text.x = 0;
layout.all.indicatorTargetReached.text.y = screenHeight;
layout.all.indicatorTargetReached.icon.x = layout.all.indicatorTargetReached.text.x + ((layout.all.indicatorTargetReached.text.width / 2) - layout.all.indicatorTargetReached.icon.width / 2);
layout.all.indicatorTargetReached.icon.y = layout.all.indicatorTargetReached.text.y - layout.all.indicatorTargetReached.text.height - layout.all.indicatorTargetReached.icon.height - layout.all.spacing.targetReachedIndicatorIconToText;
layout.all.cursor.y = layout.all.firstValue.y + layout.all.spacing.valueToCursor;
}
};
UI ui(102, 64, u8g2);
void interruptHandlerSetup()
{
pinMode(COUNT_INPUT_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(COUNT_INPUT_PIN), inputInterrupt, CHANGE);
pinMode(RESET_INPUT_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(RESET_INPUT_PIN), inputInterrupt, CHANGE);
pinMode(POWER_LOSS_DETECTION_PIN, INPUT);
attachPCINT(digitalPinToPCINT(POWER_LOSS_DETECTION_PIN), powerLossInterruptHandler, CHANGE);
}
void storeToEeprom()
{
// store prescaler
// value is Fix4<uint8_t>, so 8 bit + 16 bit = 24 bit = 3 bytes
EEPROM.put(EEPROM_ADDRESS_PRESCALER, counter.prescaler.getStructureForEeprom());
// store preset
// value is uint32_t, so 32 bit = 4 bytes
EEPROM.put(EEPROM_ADDRESS_PRESET, counter.preset.getValueForEeprom());
// store count
// value is Fix4<uint32_t>, so 32 bit + 16 bit = 48 bit = 6 bytes
EEPROM.put(EEPROM_ADDRESS_COUNT, counter.count.getStructureForEeprom());
}
void loadFromEeprom()
{
eepromPrescaler prescalerStructure;
EEPROM.get(EEPROM_ADDRESS_PRESCALER, prescalerStructure);
if (prescalerStructure.iPart != 255 || prescalerStructure.fPart != -1)
counter.prescaler.loadFromEeprom(prescalerStructure);
uint32_t presetValue;
EEPROM.get(EEPROM_ADDRESS_PRESET, presetValue);
if (presetValue != 0xFFFFFFFF)
counter.preset.loadFromEeprom(presetValue);
eepromCount countStructure;
EEPROM.get(EEPROM_ADDRESS_COUNT, countStructure);
if (countStructure.iPart != 0xFFFFFFFF || countStructure.fPart != -1)
counter.count.loadFromEeprom(countStructure);
}
void powerLossInterruptHandler()
{
if (!digitalRead(POWER_LOSS_DETECTION_PIN))
{
// stop counter operation
detachInterrupt(digitalPinToInterrupt(COUNT_INPUT_PIN));
detachInterrupt(digitalPinToInterrupt(RESET_INPUT_PIN));
storeToEeprom();
ui.selectedScreen = UI::screen::powerLoss;
}
else
{
// reenable counting
attachInterrupt(digitalPinToInterrupt(COUNT_INPUT_PIN), inputInterrupt, RISING);
attachInterrupt(digitalPinToInterrupt(RESET_INPUT_PIN), inputInterrupt, CHANGE);
ui.selectedScreen = UI::screen::main;
}
}
#ifndef PIND
#error "PIND undefined"
#define PIND 5 // arbitrary value, this is only to satisfy IntelliSense
#endif
volatile unsigned long countDebounceTimestamp;
void countPinRise()
{
if (millis() - countDebounceTimestamp >= COUNT_DEBOUNCE_MILLIS)
{
counter.countUp();
}
}
// void countPinFall()
// {
// if (millis() - countDebounceTimestamp >= COUNT_DEBOUNCE_MILLIS)
// {
// }
// }
volatile unsigned long resetDebounceTimestamp;
void resetPinRise()
{
if (millis() - resetDebounceTimestamp >= RESET_DEBOUNCE_MILLIS)
{
counter.reset();
}
}
// void resetPinFall()
// {
// if (millis() - resetDebounceTimestamp >= RESET_DEBOUNCE_MILLIS)
// {
// }
// }
volatile uint8_t oldPinStates;
void inputInterrupt()
{
uint8_t pinStates = PIND;
uint8_t posEdge = pinStates & ~oldPinStates;
uint8_t negEdge = ~pinStates & oldPinStates;
if (posEdge & (1 << 2))
{
countPinRise();
countDebounceTimestamp = millis();
}
if (negEdge & (1 << 2))
{
// countPinFall();
countDebounceTimestamp = millis();
}
if (posEdge & (1 << 3))
{
resetPinRise();
resetDebounceTimestamp = millis();
}
if (negEdge & (1 << 3))
{
// resetPinFall();
resetDebounceTimestamp = millis();
}
oldPinStates = PIND;
}
void setup()
{
u8g2.begin();
pButton.initialize();
rButton.initialize();
leftButton.initialize();
rightButton.initialize();
upButton.initialize();
downButton.initialize();
Serial.begin(115200);
loadFromEeprom();
interruptHandlerSetup();
u8g2.setFontMode(1);
counter.refreshOutput();
}
void loop()
{
ui.refreshScreen();
u8g2.firstPage();
do
{
ui.drawCurrentScreen();
} while (u8g2.nextPage());
}