Tea Timer not working

I am currently trying to program a Tea Timer based on this project: GitHub - lluisgl7/tea-timer
So far I tested the components in single sketches, so I'm sure that the OLED, servo, button and potentiometer are working.
However, if I try to put everything together in one project, my D1 mini is just blinking but not showing anything on the display



#include <U8g2lib.h>


#include <Servo.h>

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // All Boards without Reset of the Display

//state identifiers:
#define MENU 0
#define INPROCESS 1
#define DONE 2

const int buttonPin = D8;
const int servoPin = D6;
const int potPin = A0; // selection potentiometer
const int servoHighPosition = 150;
const int servoLowPosition = 70;
const int servoSpeedDelay = 20; // decrease this value to increase the servo speed

unsigned long steepingTime;
unsigned long startTime;
long timeLeft;

volatile int state;

Servo servo;

void setup() {

  pinMode(buttonPin, INPUT_PULLUP);

  attachInterrupt(0, buttonISR, CHANGE);
  servo.attach(servoPin);


  state = MENU;


  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFontDirection(0);

}

void loop() {

  switch (state) {

    case MENU:       moveServoTo(servoHighPosition);

      u8g2.clearBuffer();
      u8g2.setFont(u8g2_font_fur11_tf);
      u8g2.setCursor(1, 15);
      u8g2.print("Start");




      while (state == MENU) {

        steepingTime = 30000 * map(analogRead(potPin), 0, 1023, 1, 20);



        u8g2.setCursor(10, 15);
        u8g2.print(millisToTime(steepingTime));
        u8g2.sendBuffer();
        delay(200);
      }
      break;

    case INPROCESS:  moveServoTo(servoLowPosition);
      startTime = millis();

      u8g2.clearBuffer();
      u8g2.setCursor(1, 15);
      u8g2.print("Stop");



      while (state == INPROCESS) {

        timeLeft = steepingTime - (millis() - startTime);

        if (timeLeft > 0) {

          u8g2.setCursor(10, 15);
          u8g2.print(millisToTime(timeLeft));
          u8g2.sendBuffer();


        }
        else state = DONE;

        delay(500);
      }
      break;

    case DONE:       u8g2.clearBuffer();
      u8g2.setCursor(1, 15);
      u8g2.print("Ready");
      u8g2.setCursor(10, 15);
      u8g2.sendBuffer();



      moveServoTo(servoHighPosition);

      u8g2.clearBuffer();
      u8g2.setCursor(1, 15);
      u8g2.print("Menu");
      u8g2.setCursor(10, 15);
      u8g2.sendBuffer();

      while (state == DONE);
      break;
  }
}


void buttonISR() {

  static unsigned long lastInterruptTime = 0; //used to debounce button input
  unsigned long interruptTime = millis();

  if (interruptTime - lastInterruptTime > 500) { //long debounce time to allow long presses

    switch (state) {

      case MENU:       state = INPROCESS;

        break;

      case INPROCESS:  state = MENU;

        break;

      case DONE:        state = MENU;

        break;
    }
  }
  lastInterruptTime = interruptTime;
}


void moveServoTo(int finalPosition) { //move the servo slowly to the desired position

  int currentPosition = servo.read();

  if (finalPosition > currentPosition) {

    for (int i = currentPosition; i <= finalPosition; i++) {

      servo.write(i);
      delay(servoSpeedDelay);
    }
  }
  else if (finalPosition < currentPosition) {

    for (int i = currentPosition; i >= finalPosition; i--) {

      servo.write(i);
      delay(servoSpeedDelay);
    }
  }
}

String millisToTime(long milliseconds) {

  unsigned long minutes = (milliseconds / 1000) / 60;
  unsigned long seconds = (milliseconds / 1000) % 60;

  String minutesString = String(minutes);
  String secondsString = String(seconds);

  if (minutes < 10) minutesString = "0" + minutesString;

  if (seconds < 10) secondsString = "0" + secondsString;

  return minutesString + ":" + secondsString;
}


If "D1 mini " means a ESP8266 WeMos D1 mini this type of microcontroller needs the IRAM_ATTR for the interrupt-service routine.

Though in this application using an interrupt is way oversised.

There are two basic ways to get your project running:

If you refuse to learn programming:
use the exactly same components as desripted on GitHub.
Only with the exact same components the code will upload and run

If you are interested in learning programming:

start with something like this
Take a look into this tutorial:

Arduino Programming Course

It is easy to understand and has a good mixture between explaining important concepts and example-codes to get you going. So give it a try and report your opinion about this tutorial.

There is not much inbetween.

Without learning programming you will always depend on somebody elses grace to get your code modified.

best regards Stefan

If state is ever equal to MENU, you will be stuck in this loop for a while, a very long while.

I haven’t looked to see if that could possibly be what you want.

a7

actually I am trying to learn arduino programming for a couple of months now.
I just dont understand the mistake in the code...
As mentioned, my current workflow is to test every component in single codes to check if it is working. Then I merge everything together and in the last projects it worked correctly. The current project is also a part of learning programming for me. I just dont understand what I did wrong, I did not change much but the display commands (oled instead of LCD).

Hi orange,

post each and every testcode that you have tested and that is working.
to ask explicitly:

Did you test the OLED and the OLED did show what you expected?
If yes post this working code

best regards Stefan

additionally:
you should use one of the testcodes as the base.
Expand on that testcode. Throwing multiple things together needs a lot of experience you do not have to make it work instantly.

sure, here is the working code for a display output.
however I used the display in many other projects with the u8g2 library (with firstpage function though not sendbuffer) and it always worked

#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);  
void setup(void) {
  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFontDirection(0);
}
void loop(void) {
  u8g2.clearBuffer();
  u8g2.setCursor(0, 11);           
  u8g2.setFont(u8g2_font_unifont_t_latin);      
  u8g2.print("Test Line 1");              

  u8g2.setCursor(0, 32);                  
  u8g2.print("Test line 2");                 
 u8g2.sendBuffer();
  
  delay(1000);
}

Why do I have to ask multiple times for each detail?
What

EXACT

type of microcontroller are you using?

it is a D1 Mini - ESP8266, this product: D1 MINI: D1 Mini - ESP8266, v2.0 bei reichelt elektronik

best regards Stefan

1 Like

thanks a lot Stefan! I finally get an output on the screen. It now shows "Start" and the time. The time changes when I use the potentiometer, which is very good.
However, it is stuck on that screen... It doesnt seem to change the state when the button is pushed.
I already checked if the button is working with the code:

const int BUTTON_PIN = D7; 

void setup() {
  Serial.begin(9600);
  // initialize the pushbutton pin as an pull-up input
  // the pull-up input pin will be HIGH when the switch is open and LOW when the switch is closed.
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop() {
  int buttonState = digitalRead(BUTTON_PIN);
  Serial.println(buttonState);
  delay (500);
}

and I can see in the serial monitor that when the button is not pushed, it shows 1 and when it is pushed it shows 0. But as soon as I upload the whole project, it does not switch to the inprocess state :frowning:

The current code is this:

#include <U8g2lib.h>

#include <Servo.h>

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // All Boards without Reset of the Display

//state identifiers:
#define MENU 0
#define INPROCESS 1
#define DONE 2

const int buttonPin = D7;
const int servoPin = D6;
const int potPin = A0; // selection potentiometer
const int servoHighPosition = 150;
const int servoLowPosition = 70;
const int servoSpeedDelay = 20; // decrease this value to increase the servo speed

unsigned long steepingTime;
unsigned long startTime;
long timeLeft;

volatile int state;

Servo servo;

void setup() {

  pinMode(buttonPin, INPUT_PULLUP);

  attachInterrupt(0, buttonISR, CHANGE);
  servo.attach(servoPin);


  state = MENU;


  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFontDirection(0);

}

void loop() {

  switch (state) {

    case MENU:       moveServoTo(servoHighPosition);

      u8g2.clearBuffer();
      u8g2.setFont(u8g2_font_fur11_tf);
      u8g2.setCursor(1, 15);
      u8g2.print("Start");

      while (state == MENU) {

        steepingTime = 30000 * map(analogRead(potPin), 0, 1024, 1, 20);
        u8g2.setCursor(1, 32);
        u8g2.print(millisToTime(steepingTime));
        u8g2.sendBuffer();
        delay(200);
      }
      break;

    case INPROCESS:  moveServoTo(servoLowPosition);
      startTime = millis();

      u8g2.clearBuffer();
      u8g2.setCursor(1, 15);
      u8g2.print("Stop");



      while (state == INPROCESS) {

        timeLeft = steepingTime - (millis() - startTime);

        if (timeLeft > 0) {

          u8g2.setCursor(10, 15);
          u8g2.print(millisToTime(timeLeft));
          u8g2.sendBuffer();


        }
        else state = DONE;

        delay(500);
      }
      break;

    case DONE:       u8g2.clearBuffer();
      u8g2.setCursor(1, 15);
      u8g2.print("Ready");
   
      moveServoTo(servoHighPosition);

      u8g2.setCursor(1, 30);
      u8g2.print("Menu");
      u8g2.sendBuffer();

      while (state == DONE);
      break;
  }
}


ICACHE_RAM_ATTR void buttonISR() {

  static unsigned long lastInterruptTime = 0; //used to debounce button input
  unsigned long interruptTime = millis();

  if (interruptTime - lastInterruptTime > 500) { //long debounce time to allow long presses

    switch (state) {

      case MENU:       state = INPROCESS;

        break;

      case INPROCESS:  state = MENU;

        break;

      case DONE:        state = MENU;

        break;
    }
  }
  lastInterruptTime = interruptTime;
}


void moveServoTo(int finalPosition) { //move the servo slowly to the desired position

  int currentPosition = servo.read();

  if (finalPosition > currentPosition) {

    for (int i = currentPosition; i <= finalPosition; i++) {

      servo.write(i);
      delay(servoSpeedDelay);
    }
  }
  else if (finalPosition < currentPosition) {

    for (int i = currentPosition; i >= finalPosition; i--) {

      servo.write(i);
      delay(servoSpeedDelay);
    }
  }
}

String millisToTime(long milliseconds) {

  unsigned long minutes = (milliseconds / 1000) / 60;
  unsigned long seconds = (milliseconds / 1000) % 60;

  String minutesString = String(minutes);
  String secondsString = String(seconds);

  if (minutes < 10) minutesString = "0" + minutesString;

  if (seconds < 10) secondsString = "0" + secondsString;

  return minutesString + ":" + secondsString;
}

Reading the documentary I thought I might try change
attachInterrupt(0, buttonISR, CHANGE);
from 0 to 1 which did not work so I switched it back.
I cant imagine that the problem is inside the void loop() area of the code, it must be either still in the are of the ICACHE_RAM_ATTR void buttonISR() or it might be a problem with the timer?

I would put some print statements in each of your states just to see where the code is hung up. I do not see anything that stands out that looks to be the problem.

arent there already?

MENU contains print "start"
INPROCESS contains "stop"
DONE contains "ready"

interrupt-type change invokes one interrupt on pressing the button
and another interrupt on release of the button

You should add serial debug-output to your code to make visible what is happening inside the code
But there is one exception

never

do serial output in the interrupt-function
there are a lot of crappy demo-examples that do that.
serial-outpiut inside an interrupt-function is a BIG source of bugs

here is a code-version with serial-debug-output

// start of macros dbg and dbgi
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a 
// Serial.print is executed
// end of macros dbg and dbgi

#include <U8g2lib.h>

#include <Servo.h>

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // All Boards without Reset of the Display

//state identifiers:
#define MENU 0
#define INPROCESS 1
#define DONE 2

const int buttonPin = 13;//D7;
const int servoPin = 12;//D6;
const int potPin = A0; // selection potentiometer
const int servoHighPosition = 150;
const int servoLowPosition = 70;
const int servoSpeedDelay = 20; // decrease this value to increase the servo speed

unsigned long steepingTime;
unsigned long startTime;
long timeLeft;

volatile int state;

Servo servo;

void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  
  pinMode(buttonPin, INPUT_PULLUP);

  attachInterrupt(0, buttonISR, CHANGE);
  servo.attach(servoPin);


  state = MENU;


  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFontDirection(0);

}

void loop() {

  dbgi("top of loop",state,1000);
  
  switch (state) {

    case MENU:       moveServoTo(servoHighPosition);

      u8g2.clearBuffer();
      u8g2.setFont(u8g2_font_fur11_tf);
      u8g2.setCursor(1, 15);
      u8g2.print("Start");

      while (state == MENU) {
        dbgi("while state == MENU",state,1000);
        
        steepingTime = 30000 * map(analogRead(potPin), 0, 1024, 1, 20);
        u8g2.setCursor(1, 32);
        u8g2.print(millisToTime(steepingTime));
        u8g2.sendBuffer();
        delay(200);
      }
      break;

    case INPROCESS:  moveServoTo(servoLowPosition);
      startTime = millis();

      u8g2.clearBuffer();
      u8g2.setCursor(1, 15);
      u8g2.print("Stop");



      while (state == INPROCESS) {

        dbgi("while state == INPROCESS",state,1000);
        timeLeft = steepingTime - (millis() - startTime);

        if (timeLeft > 0) {

          u8g2.setCursor(10, 15);
          u8g2.print(millisToTime(timeLeft));
          u8g2.sendBuffer();


        }
        else state = DONE;

        delay(500);
      }
      break;

    case DONE:       u8g2.clearBuffer();
      u8g2.setCursor(1, 15);
      u8g2.print("Ready");
   
      moveServoTo(servoHighPosition);

      u8g2.setCursor(1, 30);
      u8g2.print("Menu");
      u8g2.sendBuffer();

      while (state == DONE) {
        dbgi("while state == DONE",state,1000);
      }
      break;
  }
}


ICACHE_RAM_ATTR void buttonISR() {

  static unsigned long lastInterruptTime = 0; //used to debounce button input
  unsigned long interruptTime = millis();

  if (interruptTime - lastInterruptTime > 500) { //long debounce time to allow long presses

    switch (state) {

      case MENU:       state = INPROCESS;

        break;

      case INPROCESS:  state = MENU;

        break;

      case DONE:        state = MENU;

        break;
    }
  }
  lastInterruptTime = interruptTime;
}


void moveServoTo(int finalPosition) { //move the servo slowly to the desired position

  int currentPosition = servo.read();

  if (finalPosition > currentPosition) {

    for (int i = currentPosition; i <= finalPosition; i++) {

      servo.write(i);
      delay(servoSpeedDelay);
    }
  }
  else if (finalPosition < currentPosition) {

    for (int i = currentPosition; i >= finalPosition; i--) {

      servo.write(i);
      delay(servoSpeedDelay);
    }
  }
}

String millisToTime(long milliseconds) {

  unsigned long minutes = (milliseconds / 1000) / 60;
  unsigned long seconds = (milliseconds / 1000) % 60;

  String minutesString = String(minutes);
  String secondsString = String(seconds);

  if (minutes < 10) minutesString = "0" + minutesString;

  if (seconds < 10) secondsString = "0" + secondsString;

  return minutesString + ":" + secondsString;
}

best regards Stefan

Your printing is to the OLED-display
if the OLED does not work serial-output does work in 99,99% of all cases

This is the most used debug-technique
best regards Stefan

I tried the sketch, the long line with the wird symbols is from when connecting or resetting the controller (when it does the first servo move before showing the menu text on the display)

after that it only shows "while state == MENU" state=0
it does not react if I use the potentiometer (although the screen does react), neither to the push of the button

You should post serial output

not as picture

but as code-section

Ctrl-A into the serialoutput
Ctrl-C
change to posting
click the </>-Button
then press Ctrl-V

The weird symbols are the boot-phase of the ESP8266 which has 74880 baud
the rest shows where your code is stuck
simply search in this code-version for

while state == MENU
this is where you code is executing
variable "state" is not changed

possible reasons are
button is connected to a different IO-pin than the program expects

You changed from
const int buttonPin = D8;

to

const int buttonPin = D7;

There might be something wrong with the interrupt number
take a close look at the demo-code at random nerd tutorials
best regards Stefan

1 Like

I was thinking placing a serial print in the while loops.
I would suggest going back and adding one thing at a time.
For example if the interrupt is working then add the servos, then the timer functionality
something like that. That way you can see what is breaking the program.

1 Like

I put so much time into testing the connected devices and forgot to test the interrupt itsself...
If anyone finds this topic and has a similar problem, here is what I finally did:
I deleted everything except the interrupt and came to this code:


//state identifiers:
#define MENU 0
#define INPROCESS 1

const int buttonPin = D7;

volatile int state;


void setup() {

  Serial.begin(9600);

  pinMode(buttonPin, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(buttonPin), buttonISR, CHANGE);

  state = MENU;
}

void loop() {

  switch (state) {

    case MENU:

      Serial.println("Menu");


      while (state == MENU) {

        Serial.println("While Menu");
        delay(500);
      }
      break;

    case INPROCESS:
      Serial.println("Prozess");




      while (state == INPROCESS) {

        Serial.println("while Prozess");
        delay(500);

      }

      delay(500);
      break;
  }


}



ICACHE_RAM_ATTR void buttonISR() {
  // ICACHE_RAM_ATTR nur bei D1 MINI, nicht bei arduino!

  static unsigned long lastInterruptTime = 0; //used to debounce button input
  unsigned long interruptTime = millis();

  if (interruptTime - lastInterruptTime > 500) { //long debounce time to allow long presses

    switch (state) {

      case MENU:       state = INPROCESS;

        break;

      case INPROCESS:  state = MENU;

        break;

    }
  }
  lastInterruptTime = interruptTime;
}

I changed some stuff until it worked and then rebuild the original sketch, which loooks like this now:



#include <U8g2lib.h>

#include <Servo.h>

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // All Boards without Reset of the Display

//state identifiers:
#define MENU 0
#define INPROCESS 1
#define DONE 2

const int buttonPin = D7;
const int servoPin = D6;
const int potPin = A0; // selection potentiometer
const int servoHighPosition = 150;
const int servoLowPosition = 70;
const int servoSpeedDelay = 20; // decrease this value to increase the servo speed

unsigned long steepingTime;
unsigned long startTime;
long timeLeft;

volatile int state;

Servo servo;

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(buttonPin), buttonISR, CHANGE);
  servo.attach(servoPin);


  state = MENU;


  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFontDirection(0);

}

void loop() {

  switch (state) {

    case MENU:       moveServoTo(servoHighPosition);
      Serial.println("Menu");
      u8g2.clearBuffer();
      u8g2.setFont(u8g2_font_fur11_tf);
      u8g2.setCursor(1, 15);
      u8g2.print("Start");

      while (state == MENU) {
        Serial.println("While Menu");
        steepingTime = 30000 * map(analogRead(potPin), 0, 1024, 1, 20);
        u8g2.setCursor(1, 32);
        u8g2.print(millisToTime(steepingTime));
        u8g2.sendBuffer();
        delay(200);
      }
      break;

    case INPROCESS:  moveServoTo(servoLowPosition);
      Serial.println("Prozess");
      startTime = millis();

      u8g2.clearBuffer();
      u8g2.setCursor(1, 15);
      u8g2.print("Stop");



      while (state == INPROCESS) {

        timeLeft = steepingTime - (millis() - startTime);

        if (timeLeft > 0) {

          u8g2.setCursor(1, 32);
          u8g2.print(millisToTime(timeLeft));
          u8g2.sendBuffer();


        }
        else state = DONE;

        delay(500);
      }
      break;

    case DONE:       u8g2.clearBuffer();
      u8g2.setCursor(1, 15);
      u8g2.print("Ready");

      moveServoTo(servoHighPosition);

      u8g2.setCursor(1, 32);
      u8g2.print("Menu");
      u8g2.sendBuffer();

      while (state == DONE);
      break;
  }
}


ICACHE_RAM_ATTR void buttonISR() { // ICACHE_RAM_ATTR nur bei D1 MINI, nicht bei arduino!

  static unsigned long lastInterruptTime = 0; //used to debounce button input
  unsigned long interruptTime = millis();

  if (interruptTime - lastInterruptTime > 500) { //long debounce time to allow long presses

    switch (state) {

      case MENU:       state = INPROCESS;

        break;

      case INPROCESS:  state = MENU;

        break;

      case DONE:        state = MENU;

        break;
    }
  }
  lastInterruptTime = interruptTime;
}


void moveServoTo(int finalPosition) { //move the servo slowly to the desired position

  int currentPosition = servo.read();

  if (finalPosition > currentPosition) {

    for (int i = currentPosition; i <= finalPosition; i++) {

      servo.write(i);
      delay(servoSpeedDelay);
    }
  }
  else if (finalPosition < currentPosition) {

    for (int i = currentPosition; i >= finalPosition; i--) {

      servo.write(i);
      delay(servoSpeedDelay);
    }
  }
}

String millisToTime(long milliseconds) {

  unsigned long minutes = (milliseconds / 1000) / 60;
  unsigned long seconds = (milliseconds / 1000) % 60;

  String minutesString = String(minutes);
  String secondsString = String(seconds);

  if (minutes < 10) minutesString = "0" + minutesString;

  if (seconds < 10) secondsString = "0" + secondsString;

  return minutesString + ":" + secondsString;
}

everything works perfectly fine! :slight_smile:
Thanks @StefanL38 and @noweare

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.