Skip to content

Instantly share code, notes, and snippets.

@alufers
Created March 5, 2023 10:05
Show Gist options
  • Save alufers/68d267f2f1cf51c086e538594a69c656 to your computer and use it in GitHub Desktop.
Save alufers/68d267f2f1cf51c086e538594a69c656 to your computer and use it in GitHub Desktop.
Horseplate
class Button
{
public:
int pin;
int debounceDelay;
int lastButtonState = HIGH;
unsigned long lastDebounceTime;
bool isPressed = false;
Button(int pin, unsigned int debounceDelay)
{
this->pin = pin;
this->debounceDelay = debounceDelay;
lastDebounceTime = millis();
// pinMode(pin, INPUT_PULLUP);
}
void process()
{
int buttonState = digitalRead(pin);
if (buttonState != lastButtonState)
{
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay)
{
isPressed = buttonState == LOW;
}
lastButtonState = buttonState;
}
};
/**************************************************************************
This is an example for our Monochrome OLEDs based on SSD1306 drivers
Pick one up today in the adafruit shop!
------> http://www.adafruit.com/category/63_98
This example is for a 128x64 pixel display using I2C to communicate
3 pins are required to interface (two I2C and one reset).
Adafruit invests time and resources providing this open
source code, please support Adafruit and open-source
hardware by purchasing products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries,
with contributions from the open source community.
BSD license, check license.txt for more information
All text above, and the splash screen below must be
included in any redistribution.
**************************************************************************/
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "Button.h"
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library.
// On an arduino UNO: A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO: 2(SDA), 3(SCL), ...
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3c ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define HEATER_PIN A1
#define PLUS_BUTTON_PIN 5
#define MINUS_BUTTON_PIN 9
#define OK_BUTTON_PIN 7
#define BUTTON_DEBOUNCE_DELAY 50
#define THERMISTOR_PIN A2
#define THERMISTOR_PULLUP_RESISTOR 10000
#define THERMISTOR_NOMINAL_RESISTANCE 100000
#define THERMISTOR_B 3950
#define THERMISTOR_VCC 5
#define THERMISTOR_T0 273.15 + 25
Button plusButton(PLUS_BUTTON_PIN, BUTTON_DEBOUNCE_DELAY);
Button minusButton(MINUS_BUTTON_PIN, BUTTON_DEBOUNCE_DELAY);
Button okButton(OK_BUTTON_PIN, BUTTON_DEBOUNCE_DELAY);
const char **mainMenuItems = (const char *[]){
"30 C",
"200 C",
"300 C",
"PELNA KURWA",
};
const char **workMenuItems = (const char *[]){
"Powrot",
"Ustaw temperature",
"Wykres",
"Wylacz",
};
enum class Screens
{
MainMenu,
Countdown,
Work,
WorkMenu,
AdjustTemperature,
ExitCountdown
};
Screens currentScreen = Screens::MainMenu;
#define SS_BOX_SIZE 5
int ss_x = 0;
int ss_y = 0;
int ss_vx = 1;
int ss_vy = 1;
unsigned long lastInteractionTime = 0;
float readThermistor()
{
int thermistorValue = analogRead(THERMISTOR_PIN);
float voltage = (thermistorValue * THERMISTOR_VCC) / 1024.0;
float thermistorResistance = voltage * THERMISTOR_PULLUP_RESISTOR / (THERMISTOR_VCC - voltage);
float ln = log(thermistorResistance / THERMISTOR_NOMINAL_RESISTANCE);
float tx = (1 / ((ln / THERMISTOR_B) + (1 / (THERMISTOR_T0))));
float tempC = tx - 273.15;
return tempC;
}
void drawScreenSaver()
{
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.fillRect(ss_x, ss_y, ss_x + SS_BOX_SIZE, ss_y + SS_BOX_SIZE, WHITE);
ss_x += ss_vx;
ss_y += ss_vy;
if (ss_x < 0 || ss_x + SS_BOX_SIZE > SCREEN_WIDTH)
{
ss_vx = -ss_vx;
}
if (ss_y < 0 || ss_y + SS_BOX_SIZE > SCREEN_HEIGHT)
{
ss_vy = -ss_vy;
}
if (ss_vy > -4 && ss_vy < 4)
{
ss_vy += random(1, 50) / 50;
}
if (ss_vx > -4 && ss_vx < 4)
{
ss_vx += random(1, 50) / 50;
}
if (okButton.isPressed || minusButton.isPressed || plusButton.isPressed)
{
lastInteractionTime = millis();
}
float temp = readThermistor();
if (temp > 20.0)
{
display.setTextColor(SSD1306_INVERSE);
display.setTextSize(3);
display.setCursor(0, 15);
display.print(temp);
display.println(" C");
}
display.display();
}
int menuSelectedOption = 0;
unsigned long ticker100ms = 0;
unsigned long lastTicker100ms = 0;
#define MENU_MAX_WIDTH 8
int drawMenu(const char **items, int count)
{
if (millis() - lastInteractionTime > 10000)
{
drawScreenSaver();
return -1;
}
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
for (int i = 0; i < count; i++)
{
display.setTextColor(SSD1306_WHITE);
display.print((i == menuSelectedOption) ? ">" : " ");
int horizScrollOffset = 0;
if (strlen(items[i]) >= MENU_MAX_WIDTH && i == menuSelectedOption)
{
horizScrollOffset = (ticker100ms / 5) % (strlen(items[i]));
}
for (int c = 0; c < MENU_MAX_WIDTH; c++)
{
if (items[i][c + horizScrollOffset] == '\0')
break;
display.print(items[i][c + horizScrollOffset]);
}
for (int c = MENU_MAX_WIDTH - strlen(items[i]) + horizScrollOffset; c > 0; c--)
{
display.print(" ");
}
display.println((i == menuSelectedOption) ? "<" : " ");
}
display.display();
if (plusButton.isPressed && millis() - lastInteractionTime > 300)
{
menuSelectedOption = (menuSelectedOption + 1) % count;
lastInteractionTime = millis();
}
if (minusButton.isPressed && millis() - lastInteractionTime > 300)
{
menuSelectedOption = (menuSelectedOption + count - 1) % count;
lastInteractionTime = millis();
}
if (okButton.isPressed && millis() - lastInteractionTime > 300)
{
lastInteractionTime = millis();
int tmp = menuSelectedOption;
menuSelectedOption = 0;
return tmp;
}
return -1;
}
unsigned long countdownStart = 0;
int countownLength = 9;
const char *cancelText = "Dowolny przycisk aby anulowac";
const char *confirmExitText = "Nacisnij minus jeszcze raz aby wyjsc";
int drawCountdown()
{
int timeLeft = countownLength - (millis() - countdownStart) / 1000;
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(2, 0);
if (currentScreen == Screens::Countdown)
{
display.println(" START ZA");
display.setTextSize(3);
display.print(" ");
display.print(timeLeft);
display.println("s");
}
else
{
display.println("Wyjsc?\n\n");
display.fillRect(0, 32, 128 * (1.0 - ((float)(millis() - countdownStart) / 1000.0 / (float)countownLength)), 10, SSD1306_WHITE);
}
display.setTextSize(2);
char *textToDisplay = (currentScreen == Screens::Countdown) ? cancelText : confirmExitText;
int vertScrollOffset = ((millis() - countdownStart) / 200) % (strlen(textToDisplay));
for (int i = 0; i < 10; i++)
{
if (textToDisplay[i + vertScrollOffset] == '\0')
break;
display.print(textToDisplay[i + vertScrollOffset]);
}
display.display();
if ((okButton.isPressed || minusButton.isPressed || plusButton.isPressed) && millis() - lastInteractionTime > 300)
{
lastInteractionTime = millis();
return minusButton.isPressed ? 1 : 2;
}
if (timeLeft == 0)
{
return 0;
}
return -1;
}
bool heaterEnabled = false;
float targetTemp = 0;
float pwmPower = 0;
int drawWork()
{
display.clearDisplay();
display.setTextSize(3);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print(readThermistor());
display.println(" C");
display.setTextSize(2);
display.print("->");
display.print(targetTemp);
display.println(" C");
display.print("PWM: ");
display.print(pwmPower);
display.display();
if (okButton.isPressed && millis() - lastInteractionTime > 300)
{
lastInteractionTime = millis();
return 1;
}
if (minusButton.isPressed && millis() - lastInteractionTime > 300)
{
lastInteractionTime = millis();
return 0;
}
if (plusButton.isPressed && millis() - lastInteractionTime > 300)
{
lastInteractionTime = millis();
return 2;
}
return -1;
}
float editedTargetTemp = 0;
int drawTemperatureAdjust()
{
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("USTAW");
display.setTextSize(3);
display.print(" ");
display.println(editedTargetTemp);
display.setTextSize(2);
display.print("- +");
display.display();
if (okButton.isPressed && millis() - lastInteractionTime > 300)
{
lastInteractionTime = millis();
targetTemp = editedTargetTemp;
return 1;
}
int delta = 1;
// reuse the variable XD
if(countdownStart > 3) {
delta = 10;
}
if (minusButton.isPressed && millis() - lastInteractionTime > 300)
{
lastInteractionTime = millis();
editedTargetTemp -= delta;
countdownStart++;
return -1;
}
else if (plusButton.isPressed && millis() - lastInteractionTime > 300)
{
lastInteractionTime = millis();
editedTargetTemp += delta;
countdownStart++;
return -1;
}
else
{
if(millis() - lastInteractionTime > 370) {
countdownStart = 0;
}
}
if (millis() - lastInteractionTime > 7500)
{
return 0;
}
return -1;
}
void thermistorError()
{
for (;;)
{
digitalWrite(HEATER_PIN, LOW);
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("BLAD"));
display.println(F("TERMISTORA"));
display.print(F("T: "));
display.print(readThermistor());
display.println(F("C"));
display.print(F("P: -"));
display.print(digitalRead(MINUS_BUTTON_PIN));
display.print(F(" +"));
display.print(digitalRead(PLUS_BUTTON_PIN));
display.print(F(" "));
display.print(digitalRead(OK_BUTTON_PIN));
display.println(F(""));
display.display();
}
}
unsigned long lastThermistorPWMCycleStart = 0;
float kp = 20;
float ki = 5;
float kd = 10;
float previousError = 0;
unsigned long previousPIDCycleTime = 0;
void processHeater()
{
if (!heaterEnabled)
{
digitalWrite(HEATER_PIN, LOW);
return;
}
if (millis() - lastThermistorPWMCycleStart > 1000)
{
lastThermistorPWMCycleStart = millis();
}
unsigned long cyclePos = (millis() - lastThermistorPWMCycleStart) % 1000;
Serial.println(pwmPower);
Serial.print("cyclePos: ");
Serial.println(cyclePos);
if (cyclePos < pwmPower * 1000)
{
Serial.println("heating");
digitalWrite(HEATER_PIN, HIGH);
}
else
{
Serial.println("off");
digitalWrite(HEATER_PIN, LOW);
}
float elapsedTime = (millis() - previousPIDCycleTime) / 1000.0;
float error = targetTemp - readThermistor();
float dt = (millis() - previousPIDCycleTime) / 1000.0;
float p = error * kp;
float i = error * ki * dt;
float d = (error - previousError) / dt * kd;
pwmPower = p + i + d;
if (pwmPower > 1)
{
pwmPower = 1;
}
if (pwmPower < 0)
{
pwmPower = 0;
}
}
void setup()
{
Serial.begin(115200);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
{
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
display.setRotation(2);
pinMode(PLUS_BUTTON_PIN, INPUT_PULLUP);
pinMode(MINUS_BUTTON_PIN, INPUT_PULLUP);
pinMode(OK_BUTTON_PIN, INPUT_PULLUP);
pinMode(THERMISTOR_PIN, INPUT);
pinMode(HEATER_PIN, OUTPUT);
// T0 = 25 + 273.15;
// testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
if (readThermistor() < -50)
{
thermistorError();
}
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("HORSE PLATE"));
display.display();
delay(300);
}
void loop()
{
plusButton.process();
minusButton.process();
okButton.process();
processHeater();
if (millis() - lastTicker100ms > 100)
{
lastTicker100ms = millis();
ticker100ms++;
}
if (currentScreen == Screens::MainMenu)
{
heaterEnabled = false;
int result = drawMenu(mainMenuItems, 4);
if (result != -1)
{
currentScreen = Screens::Countdown;
countdownStart = millis();
if (result == 0)
{
targetTemp = 30;
}
else if (result == 1)
{
targetTemp = 200;
}
else if (result == 2)
{
targetTemp = 300;
}
else if (result == 3)
{
targetTemp = 999;
}
}
}
else if (currentScreen == Screens::Countdown)
{
heaterEnabled = false;
countownLength = 9;
int result = drawCountdown();
if (result == 0)
{
currentScreen = Screens::Work;
}
else if (result >= 0)
{
currentScreen = Screens::MainMenu;
}
}
else if (currentScreen == Screens::Work)
{
heaterEnabled = true;
int result = drawWork();
if (result == 0)
{
currentScreen = Screens::ExitCountdown;
countdownStart = millis();
}
if (result == 1)
{
currentScreen = Screens::WorkMenu;
}
else if (result == 2)
{
currentScreen = Screens::AdjustTemperature;
editedTargetTemp = targetTemp;
}
}
else if (currentScreen == Screens::ExitCountdown)
{
heaterEnabled = true;
countownLength = 5;
int result = drawCountdown();
if (result == 1)
{
currentScreen = Screens::MainMenu;
}
else if (result >= 0)
{
currentScreen = Screens::Work;
}
}
else if (currentScreen == Screens::WorkMenu)
{
heaterEnabled = true;
int result = drawMenu(workMenuItems, 4);
switch (result)
{
case 0:
currentScreen = Screens::Work;
break;
case 1:
currentScreen = Screens::AdjustTemperature;
break;
case 2:
currentScreen = Screens::Work;
break;
case 3:
currentScreen = Screens::MainMenu;
break;
}
}
else if (currentScreen == Screens::AdjustTemperature)
{
heaterEnabled = true;
int result = drawTemperatureAdjust();
if (result != -1)
{
currentScreen = Screens::Work;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment