diff --git a/README.md b/README.md index 03ce1e2..03999ea 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,14 @@ FlexRingGauge is a device that aims to be a meter with a display and a configura While in theory the device is not tied to SimHub, this firmware emulates an Arduino board that has been flashed with the SimHub firmware. -## Required components +## Components - 24 RGB LED Ring - aka "NeoPixel" - 1.3" OLED Screen with the SH1106 driver that can be wired for I2C (usually these have 4 pins only) - D1 Mini (ESP8266) +- (optional) 2 push buttons (Normally open) The ESP866 was selected over any Arduino board due to cost, form factor available memory AND performance. In the past I tested a similar meter stand alone framework and the affordable Arduino boards struggled to keep up, while the ESP8266 was passed with flying colors. -The SSH1106 screen was picked due to its availability, size and cost. +The SH1106 screen was picked due to its availability, size and cost. Given that SimHub doesn't support neither the ESP board nor the SH1106 driver for the screen, this repository exists. ## Circuit diff --git a/diagram.png b/diagram.png index 6a4e271..70349b3 100644 Binary files a/diagram.png and b/diagram.png differ diff --git a/lib/Button/SHButton.h b/lib/Button/SHButton.h new file mode 100644 index 0000000..6df1d71 --- /dev/null +++ b/lib/Button/SHButton.h @@ -0,0 +1,72 @@ +#ifndef __SHBUTTON_H__ +#define __SHBUTTON_H__ + +#include +#include "SHFastIO.h" + +typedef void(*SHButtonChanged) (int, byte); + +class SHButton { +private: + + FastDigitalPin button; + uint8_t buttonState; + int buttonLastState = -1; + bool vccToPinWiring; + unsigned long buttonLastStateChanged; + byte id; + SHButtonChanged shButtonChangedCallback; + int logicMode; + + int getState(int value) { + int res = 0; + if (!vccToPinWiring) { + res = value == HIGH ? 0 : 1; + } + else { + res = value == HIGH ? 1 : 0; + } + if (logicMode == 1) { + res = res ? 0 : 1; + } + return res; + } + +public: + + void begin(byte buttonid, uint8_t buttonPin, SHButtonChanged changedcallback, bool vccToPinWiring, int logicMode) { + button.begin(buttonPin); + this->vccToPinWiring = vccToPinWiring; + this->logicMode = logicMode; + + if (buttonPin > 0) { + if (!vccToPinWiring) { + pinMode(buttonPin, INPUT_PULLUP); + } + else { + pinMode(buttonPin, INPUT); + } + } + id = buttonid; + buttonLastState = getState(button.digitalReadPin()); + shButtonChangedCallback = changedcallback; + + if (buttonLastState) + shButtonChangedCallback(id, buttonLastState); + } + + uint8_t getPressed() { + return !buttonLastState; + } + + void read() { + buttonState = getState(button.digitalReadPin()); + if (buttonState != buttonLastState && buttonLastStateChanged - millis() > 50) { + shButtonChangedCallback(id, buttonState); + buttonLastState = buttonState; + buttonLastStateChanged = millis(); + } + } +}; + +#endif diff --git a/lib/Button/SHDebouncer.h b/lib/Button/SHDebouncer.h new file mode 100644 index 0000000..c691c0f --- /dev/null +++ b/lib/Button/SHDebouncer.h @@ -0,0 +1,31 @@ +#ifndef __SHDEBOUNCER_H__ +#define __SHDEBOUNCER_H__ + +#include + +class SHDebouncer { +private: + unsigned long lastPoll = 0; + unsigned long debounceDelay; +public: + SHDebouncer(byte delay) { + debounceDelay = (unsigned long)delay; + } + + SHDebouncer() { + } + + void begin(byte delay) { + debounceDelay = delay; + } + + bool Debounce() { + if (millis() - lastPoll > debounceDelay) { + lastPoll = millis(); + return true; + } + return false; + } +}; + +#endif \ No newline at end of file diff --git a/lib/Button/SHFastIO.h b/lib/Button/SHFastIO.h new file mode 100644 index 0000000..4dfd4ae --- /dev/null +++ b/lib/Button/SHFastIO.h @@ -0,0 +1,31 @@ +#ifndef __SHFASTIO_H__ +#define __SHFASTIO_H__ +#include + +class FastDigitalPin { +private: + uint8_t pin = -1; + bool pinIsValid = false; +public: + + bool isValid() { + return true; + } + + void begin(int pin) { + if (pin < 0) { + pinIsValid = false; + } + else { + pinIsValid = true; + this->pin = pin; + } + } + + int digitalReadPin() + { + return digitalRead(this->pin); + } +}; + +#endif \ No newline at end of file diff --git a/src/SHCommands.h b/src/SHCommands.h index 5b46ed9..ecebea3 100644 --- a/src/SHCommands.h +++ b/src/SHCommands.h @@ -16,7 +16,7 @@ void Command_SetBaudrate() void Command_ButtonsCount() { - FlowSerialWrite((byte)(0)); + FlowSerialWrite((byte)(ENABLED_BUTTONS_COUNT)); FlowSerialFlush(); } diff --git a/src/main.cpp b/src/main.cpp index 32bf8f8..3a7d919 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,6 +15,7 @@ #define LEDRING_SHIFT_BY_180_DEGREES 1 // If set to 1, uses an alternative mapping for leds that's shifted 180 degrees #define FASTLED_ESP8266_RAW_PIN_ORDER // fastLED will try to guess your pin number if not set up and won't work #define LED_BUILTIN D4 // Pin number to activate to control the built in LED +#define INCLUDE_BUTTONS // SimHub visible only buttons (no gamepad) /** ***************************************** * @@ -28,6 +29,8 @@ #include #include #include +#include +#include #if PLUS > 0 # include @@ -41,13 +44,42 @@ unsigned long lastSerialActivity = 0; SHGLCD_I2COLED screen; SHRGBLedsNeoPixelFastLeds shRGBLedsWS2812B; + +#ifdef INCLUDE_BUTTONS +#define ENABLED_BUTTONS_COUNT 2 // How many SimHub visible only buttons to add (no gamepad) +#define BUTTON_PIN_1 D3 // Button 1 Pin (to GND, not inverted) +#define BUTTON_PIN_2 D7 // Button 2 Pin (to GND, not inverted) +int BUTTON_PINS[] = { BUTTON_PIN_1, BUTTON_PIN_2 }; +SHButton button1, button2; +SHButton* BUTTONS[] = { &button1, &button2 }; +#endif + +SHDebouncer ButtonsDebouncer(10); + #if PLUS > 0 PlusController plus(&screen, &shRGBLedsWS2812B); #endif #include -void idle(bool critical) {} + +void buttonStatusChanged(int buttonId, byte Status) { + arqserial.CustomPacketStart(0x03, 2); + arqserial.CustomPacketSendByte(buttonId); + arqserial.CustomPacketSendByte(Status); + arqserial.CustomPacketEnd(); +} + +void idle(bool critical) { + if (ButtonsDebouncer.Debounce()) { + bool changed = false; +#ifdef INCLUDE_BUTTONS + for (int btnIdx = 0; btnIdx < ENABLED_BUTTONS_COUNT; btnIdx++) { + BUTTONS[btnIdx]->read(); + } +#endif + } +} void setup() { @@ -55,6 +87,14 @@ void setup() shRGBLedsWS2812B.begin(WS2812B_RGBLEDCOUNT, WS2812B_RIGHTTOLEFT, PLUS == 0); screen.init(PLUS == 0); arqserial.setIdleFunction(idle); + +#ifdef INCLUDE_BUTTONS + // EXTERNAL BUTTONS INIT + for (int btnIdx = 0; btnIdx < ENABLED_BUTTONS_COUNT; btnIdx++) { + BUTTONS[btnIdx]->begin(btnIdx + 1, BUTTON_PINS[btnIdx], buttonStatusChanged, 0, 0); // notice the hardcoded zeroes + } +#endif + #if PLUS > 0 plus.init(); #endif