Problems when using the millis() function

Hi guys!

I am trying to implement the following function:

When I press a button for five seconds, a pin is set HIGH and a display should show the word "Degas" for three minutes in addition to a calculated value. Afterwards the word should disappear again and the pin should be set LOW again. Since the program should not pause during this time, I cannot use the delay function. So I tried to use the millis() function. Unfortunately it does not really work.

When I press the button for 5 seconds, I get the confirmation on my serial monitor that it was pressed. However, I get "Degas ended" displayed directly afterwards:

"Button Pressed

Degas ended"

So the program skips the part where it is supposed to show "Degas" on the display for three minutes and confirmes this by giving me "Degas function active" via the serial monitor.

Can anyone give me a hint as to what I am doing wrong?

My code (at least the relevant part) looks like this:

unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
unsigned long degasPreviousMillis = 0;
int buttonTime = 5; //button hold delay in seconds
int displayTime = 180; //Degas display time in seconds
int degasState = 0;
int degasCounter = 0;

void loop() {
//Write Pressure 2 to Display 2 (Penning Gauge IKR-251)
  TCA9548A(3);
  //Turn Display on, only if Gauge is connected

  //If Degas Button is pressed, display "Degas" for X seconds
  if (digitalRead(4) == LOW) {
    currentMillis = millis();
    if (currentMillis - previousMillis >= buttonTime * 1000) { //do the following only after button is pressed for buttonTime
      degasState = 1;
      Serial.println("Button Pressed");
    }
    switch (degasState) {
      case 1:
        degas();
        break;

        previousMillis = currentMillis;
  }
 }
 }

void degas() {
  currentMillis = millis();
  if (currentMillis - degasPreviousMillis >= displayTime * 1000) { //do the following for displayTime
    digitalWrite (2, HIGH);
    Serial.println("Degas function active");
    
    TCA9548A(3);
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(35, 5);
    display.println("Degas");
    display.display();
    
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(45, 50);
    display.println("mbar");

    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(15, 10);
    display.print(sci(pressure_mbar2, 2));
    display.display();
  }
  else {
    degasState == 0;
    degasPreviousMillis = 0;
    digitalWrite (2, LOW);
    Serial.println("Degas ended");
  }
  degasPreviousMillis = currentMillis;
}

You mean, you want the cycle to repeat, if the button is pressed again, during the 3 minute period?

You have two timers, a 5 second timer, and a 3 minute timer. Time stamps for those should be somewhere in your code. I only see one.

As degasPreviousMillis initially is zero, this condition is always satisfied. Afterwards degasPreviousMillis is updated, so that the next time the condition is false and the else part shows the ended message.

Read more about state machines and add required states to degasState, at least waiting=1 and showing=2. Don't forget to initialize all state related variables to meaningful values before entering the first or any next state.

In the best case, the three-minute timer would not restart if I pressed the button again for five seconds. But that would not be relevant.
What exactly do you mean by timestamps? Sorry, I am very new to programming.

This is a time stamp. It stores a millisecond time value.

A programming hint: Mentally translate "held down for 5 seconds" to "the last time it was up was 5 seconds ago, or more".

the code you posted isn't complete and therefore not compilable

consider following which is non-blocking and uses a generic state-machine with timer events
also moves display configuration functions to setup()

#define MyHW
#ifdef MyHW
void TCA9548A (int x) { }
int  sci (int i, int j)  {return 123; }
int  pressure_mbar2  = 100;

enum { WHITE };
struct Display {
    void clearDisplay (void)  { }
    void display (void)  { }
    void setCursor (int x, int y) { }
    void setTextColor (int c) { }
    void setTextSize (int c) { }

    void print (const char *s)  { Serial.print (s); }
    void print (int i)  { Serial.print (i); }

    void println (const char *s)  { Serial.print (s); }
    void println (int i)  { Serial.print (i); }
};
Display display;

const byte OutPin = LED_BUILTIN;
const byte ButPin = A1;

unsigned buttonTime  = 5; //button hold delay in seconds
unsigned displayTime = 3; //Degas display time in seconds

enum { Off = HIGH, On = LOW };

#else
const byte OutPin = 2;
const byte ButPin = 4;

#if 0
int buttonTime = 5; //button hold delay in seconds
int displayTime = 180; //Degas display time in seconds
#else
unsigned buttonTime  = 5; //button hold delay in seconds
unsigned displayTime = 180; //Degas display time in seconds

enum { Off = LOW, On = HIGH };
#endif

#endif

// -----------------------------------------------------------------------------
byte butState;

bool          tmrAct;
unsigned long period;
unsigned long msecLst;
unsigned long msec;


char s [80];

// -----------------------------------------------------------------------------
void disp ()
{
    Serial.println ("Degas function active");
    digitalWrite (OutPin, On);
    TCA9548A (3);

    display.clearDisplay ();
    display.setCursor    (35, 5);
    display.println      ("Degas");
 // display.display      ();

    display.setCursor    (45, 50);

    display.println      ("mbar");
    display.setCursor    (15, 10);
    display.print        (sci(pressure_mbar2, 2));
    display.display      ();
}

// -------------------------------------
void dispClr ()
{
    Serial.println ("dispClr:");
    digitalWrite (OutPin, Off);
    display.clearDisplay ();
}

// -----------------------------------------------------------------------------
enum { ST_IDLE, ST_PEND, ST_DISP};
const char *stStr [] = {
    "ST_IDLE",
    "ST_PEND",
    "ST_DISP",
};

enum { STIM_NONE, STIM_TMR , STIM_PRESS, STIM_RELEASE};
const char *stimStr [] = {
    "STIM_NONE",
    "STIM_TMR",
    "STIM_PRESS",
    "STIM_RELEASE",
};

int state = ST_IDLE;

// -------------------------------------
void
stMach (
    int stim )
{
    sprintf (s, " state %d %s, stim %d %s",
            state, stStr [state], stim, stimStr [stim]);
    Serial.println (s);
    
    switch (state) {
    case ST_IDLE :
        if (STIM_PRESS == stim)  {
            msecLst = msec;
            period  = 1000 * buttonTime;
            tmrAct  = true;
            state   = ST_PEND;
            Serial.println ("ST_PEND");
        }
        break;

    case ST_PEND :
        if (STIM_RELEASE == stim)  {
            tmrAct  = false;
            state   = ST_IDLE;
            Serial.println ("ST_IDLE");
        }
        else if (STIM_TMR == stim)  {
            disp ();
            msecLst = msec;
            period  = 1000 * displayTime;
            tmrAct  = true;
            state   = ST_DISP;
            Serial.println ("ST_ACT");
        }
        break;

    case ST_DISP :
        if (STIM_TMR == stim)  {
            dispClr ();
            tmrAct  = false;
            state   = ST_IDLE;
            Serial.println ("ST_IDLE");
        }
        break;
    }
}


// -----------------------------------------------------------------------------
void loop ()
{
    msec = millis ();
    if (tmrAct && (msec - msecLst) > period) {
        sprintf (s, "loop: %9lu %9lu %9lu", msec, msecLst, period);
        stMach (STIM_TMR);
    }

    //Write Pressure 2 to Display 2 (Penning Gauge IKR-251)
    TCA9548A (3);

    // check for button events
    byte but = digitalRead (ButPin);
    if (butState != but)  {
        butState = but;
        delay (10);         // debounce
        if (LOW == but)
            stMach (STIM_PRESS);
        else
            stMach (STIM_RELEASE);
    }
}

// -----------------------------------------------------------------------------
void
setup (void)
{
    Serial.begin (9600);
    Serial.println  ("ready");

    digitalWrite (OutPin, Off);
    pinMode (OutPin, OUTPUT);

    pinMode (ButPin, INPUT_PULLUP);
    butState = digitalRead (ButPin);

    display.clearDisplay ();
    display.setTextSize (2);
    display.setTextColor (WHITE);

}

Thank you for the helpfull answer! I will take a look at the code and hopefully understand what exactly is happening. Since it is very different from mine, I still hope to see a pattern.
I have decided that I will remove the condition that the button must be pressed for several seconds.
So I only need the function that when the button has been pressed, the void degas () function will run for three minutes while the rest of the code continues to run.
I'm happy to upload all my code here (it's a bit awkward for me, as it's certainly written in a very amateurish way):

//I2C Communication
#include <Wire.h>

//printHelpers
#include "printHelpers.h"

//Display
#include <SPI.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>

//Analog Digital Converter
#include <Adafruit_ADS1X15.h>
Adafruit_ADS1115 ads;

//Display
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

//Voltage reading Channel 1
float truevoltage;
float pressure_mbar;


//Voltage reading Channel 2
float truevoltage2;
float pressure_mbar2;


//Voltage reading Channel 3
float truevoltage3;
float pressure_mbar3;

//Degas
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
unsigned long degasPreviousMillis = 0;
unsigned long buttonPreviousMillis = 0;
int buttonTime = 3; //button hold delay in seconds
int displayTime = 10; //Degas display time in seconds
int degasState = 0;
int degasCounter = 0;


// Select I2C BUS
void TCA9548A(uint8_t bus) {
  Wire.beginTransmission(0x70);  // TCA9548A address
  Wire.write(1 << bus);          // send byte to select bus
  Wire.endTransmission();

}

void setup() {

  {
    float f;
    Serial.begin(9600);
  }

  // Start I2C communication with the Multiplexer
  Wire.begin();

  //Initialise ADS1115 on bus number 2
  TCA9548A(2);
  ads.setGain(GAIN_TWOTHIRDS);
  if (!ads.begin()) {
    Serial.println("Failed to initialize ADS.");
    while (1);
  }

  // Init OLED display on bus number 3
  TCA9548A(3);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  // Clear the buffer
  display.clearDisplay();

  // Init OLED display2 on bus number 4
  TCA9548A(4);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  // Clear the buffer
  display.clearDisplay();

  // Init OLED display3 on bus number 5
  TCA9548A(5);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  // Clear the buffer
  display.clearDisplay();

  //Degas
  pinMode(4, INPUT_PULLUP);

}

void loop() {

  int16_t adc0, adc1, adc2, adc3;
  float volts0, volts1, volts2, volts3;

  //Read Voltage Sensor 1 (Pirani Gauge PSG-500-S)
  TCA9548A(2);
  adc1 = ads.readADC_SingleEnded(1);
  volts1 = ads.computeVolts(adc1);
  truevoltage = volts1 * 2;
  pressure_mbar = pow(10, ((truevoltage - 6.143) / 1.286));

  //Read Voltage Sensor 2 (Penning Gauge IKR-251)
  adc2 = ads.readADC_SingleEnded(2);
  volts2 = ads.computeVolts(adc3);
  truevoltage2 = volts2 * 2;
  pressure_mbar2 = pow(10, ((truevoltage2 - 5.5)));

  //Read Voltage Sensor 3 (Pirani Gauge PCR260)
  adc3 = ads.readADC_SingleEnded(3);
  volts3 = ads.computeVolts(adc2);
  truevoltage3 = volts3 * 2;
  pressure_mbar3 = pow(10, (truevoltage3 - 5.5));



  //Write Pressure 1 to Display 1(Pirani Gauge PSG-500-S)
  TCA9548A(5);
  //Turn Display on, only if Gauge is connected
  if (digitalRead(12) == HIGH) {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(10, 8);
    display.println("PSG-500-S");
    display.setCursor(45, 29);
    display.println("Not");
    display.setCursor(12, 47);
    display.println("Connected");
    display.display();

  }
  else if (digitalRead(6) == LOW) {
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(10, 5);
    display.println("PSG-500-S");
    display.setCursor(15, 15);
    display.println("Gauge Connected");
    display.setTextSize(2);
    display.setCursor(52, 32);
    display.println("No");
    display.setCursor(35, 50);
    display.println("Power");
    display.display();
  }
  else {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(45, 50);
    display.println("mbar");

    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(15, 10);
    display.print(sci(pressure_mbar, 2));
    display.display();
  }




  //Write Pressure 2 to Display 2 (Penning Gauge IKR-251)
  TCA9548A(3);

  //If Degas Button is pressed, display "Degas" for X seconds
  if (digitalRead(4) == LOW) {
    degasState = 1;
    Serial.println("Button Pressed");
  
  switch (degasState) {
    case 1:
    degas();
    break;
  }}

  //Turn Display on, only if Gauge is connected

  else if (digitalRead(11) == HIGH) {

    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(20, 8);
    display.println("IKR-251");
    display.setCursor(45, 29);
    display.println("Not");
    display.setCursor(12, 47);
    display.println("Connected");
    display.display();
  }

  else if (digitalRead(7) == HIGH) {
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(40, 5);
    display.println("IKR 251");
    display.setCursor(15, 15);
    display.println("Gauge Connected");
    display.setTextSize(2);
    display.setCursor(52, 32);
    display.println("No");
    display.setCursor(35, 50);
    display.println("Power");
    display.display();
  }
  else {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(45, 50);
    display.println("mbar");

    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(15, 10);
    display.print(sci(pressure_mbar2, 2));
    display.display();
  }


  //Write Pressure 3 to Display 3 (Pirani Gauge PCR260)
  TCA9548A(4);
  //Turn Display on, only if Gauge is connected
  if (digitalRead(10) == HIGH) {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(30, 8);
    display.println("PCR260");
    display.setCursor(45, 29);
    display.println("Not");
    display.setCursor(12, 47);
    display.println("Connected");
    display.display();

  }
  else if (digitalRead(8) == LOW) {
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(40, 5);
    display.println("PCR 260");
    display.setCursor(15, 15);
    display.println("Gauge Connected");
    display.setTextSize(2);
    display.setCursor(52, 32);
    display.println("No");
    display.setCursor(35, 50);
    display.println("Power");
    display.display();
  }
  else {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(45, 50);
    display.println("mbar");

    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(15, 10);
    display.print(sci(pressure_mbar3, 2));
    display.display();
  }

  delay(200);
}


void degas() {
  currentMillis = millis();
  if (currentMillis - degasPreviousMillis <= displayTime * 1000) { //do the following for displayTime
    digitalWrite (2, HIGH);
    Serial.println("Degas function active");

    TCA9548A(3);
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(35, 5);
    display.println("Degas");
    display.display();

    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(45, 50);
    display.println("mbar");

    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(15, 10);
    display.print(sci(pressure_mbar2, 2));
    display.display();

  }
  else {
    degasState == 0;
    degasPreviousMillis = 0;
    digitalWrite (2, LOW);
    Serial.println("Degas ended");
  }

  degasPreviousMillis = currentMillis;
}

Maybe this code is helpful. Because you didn't post your complete code, the example is only a pattern.

class Timer
{
  public:
    void start() {
      timeStamp = millis();
    }
    boolean operator() (const unsigned long duration) {
      return (millis() - timeStamp >= duration) ?  true : false;
    }
  private:
    unsigned long timeStamp {0};
};

enum class ButtonState : byte {released, pressed, wait};
enum class DegasState : byte {start, isActive, off, stop};

constexpr unsigned int BUTTON_TIMESPAN {5000};    // 5 Sek. pressed delay
constexpr unsigned int DEGAS_TIMESPANE {10000};   // 10 Sek. Degas change ist to 180000
constexpr byte B_PIN {4};
constexpr byte LED_PIN {LED_BUILTIN};             // Use built in LED

Timer bTimer;
Timer dTimer;

ButtonState bState {ButtonState::released};
DegasState dState {DegasState::off};

void button();
bool degas();
void degasAction();

void setup() {
  Serial.begin(115200);
  pinMode(B_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  Serial.println("Start");
}

void loop() {
  button();
  if (degas()) {                // When true do degas for DEGAS_TIMESPAN time
    degasAction(); delay(1000); // Do degas actions here. Remove delay when debug is done
  }
}

void button() {
  if (!digitalRead(B_PIN) && dState != DegasState::isActive) {
    if (bState != ButtonState::wait) bState = ButtonState::pressed;
  } else {
    bState = ButtonState::released;
    if (digitalRead(LED_PIN)) digitalWrite(LED_PIN,LOW);  // Indicate button released or off
  }
  switch (bState) {
    case ButtonState::pressed:
      if(!digitalRead(LED_PIN)) digitalWrite(LED_PIN,HIGH); // Indicate button press
      bTimer.start();
      bState = ButtonState::wait;
      break;
    case ButtonState::wait:
      if (bTimer(BUTTON_TIMESPAN)) {
        dState = DegasState::start;
      }
      break;
    default:
      break;
  }
}

bool degas() {
  bool isActive {false};

  switch (dState) {
    case DegasState::start:
      Serial.println("Degas started");
      dState = DegasState::isActive;
      dTimer.start();
      break;
    case DegasState::isActive:
      isActive = true;
      if (dTimer(DEGAS_TIMESPANE )) {
        dState = DegasState::stop;
      }
      break;
    case DegasState::stop:
      Serial.println("Degas ended");
      digitalWrite (2, LOW);
      dState = DegasState::off;
      break;
    default:
      break;
  }
  return isActive;
}

void degasAction() {
  digitalWrite (2, HIGH);
  Serial.println("Degas function active");
  /*
  TCA9548A(3);
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(35, 5);
  display.println("Degas");
  display.display();

  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(45, 50);
  display.println("mbar");

  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(15, 10);
  display.print(sci(pressure_mbar2, 2));
  display.display();
  */
}

You can play with it here:

Hey thanks for the wokwi demo. It is always nice to be able to run code without, well you know, having to go to too much trouble.

I missed this

    degasAction(); delay(1000); // Do degas actions here. Remove delay when debug is done

at first, if anyone goes to play with it, def remove the delay() right away.

And I put a trick into degasAction(), easy to do in a sketch that makes good use of millis() to avoid blackages - didn't have to even look beyond the function:

void degasAction() {
  digitalWrite (2, HIGH);

  static unsigned long lastSpam;
  unsigned long onw = millis();

  if (onw - lastSpam > 777) {
    Serial.println("Degas function active");
    lastSpam = onw;
  }
}

It ust cuts the printation rate down to size using the familiar millis() pattern.

Now I have to go see how Degas enters into this. :wink:

a7

1 Like

I have added your change to the degasAction() function in the "play code" and removed the delay.

Thank you so much! I was able to get it to work with your code! You definitely made my day :slight_smile:

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