Using interrupt (or another solution) to display sensor data

Hi everyone! I am new to Arduino and I am currently working on an air quality station. It uses temp, humidity, CCS811 and PM sensors. It also has a buzzer, display, and button.

My plan is to read the sensors in loop every ~5 minutes. The display shows constantly the Air quality and temperature (for example). This is updated in the loop every 5 minutes.

When the user presses the button, the display shows individual readings, i.e. he presses the button once and the display shows the last measured temperature, he presses it again and the display shows the humidity, etc.

Here's where I'm not sure, if I am on the right track. I am using interrupt with the button. In my work in progress, when pressing the button, the display shows the last measured value by the PM sensor. However, I would like this value to be displayed for a certain amount of time, for example 5 seconds (then the display returns to showing the air quality). I am wondering how to achieve this? To my knowledge I can't use delay in the interrupt. Any ideas?

/*Definitions--------------------------------------------------------------------------------------------------------------------------------------------*/

/*Buzzer-----------------------------------------------------*/
int buzzer = 2;

/*Button-----------------------------------------------------*/
//const byte interruptPin = 3;
//volatile int buttonState = 0;
const byte interruptPin = 3;
volatile byte state = LOW;
//int buttonState = 0;

/*Display-----------------------------------------------------*/
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 8, 9, 10, 11, 12); 

/*T&H-----------------------------------------------------*/
#include <Wire.h>
#include "DFRobot_SHT20.h"
DFRobot_SHT20 sht20;
int t; //new
int h; //new

/*CO2-----------------------------------------------------*/
//DFRobot_CCS811 CCS811(&Wire, /*IIC_ADDRESS=*/0x5A);
#include "DFRobot_CCS811.h"
DFRobot_CCS811 CCS811;
#include <Wire.h>
int co2; //new
int tvoc; //new

/*Dust-----------------------------------------------------*/
#define        COV_RATIO                       0.2            //ug/mmm / mv
#define        NO_DUST_VOLTAGE                 400            //mv
#define        SYS_VOLTAGE                     5000           

const int iled = 4;                                            //drive the led of sensor
const int vout = 0;                                            //analog input
int pm; //new

float density, voltage;
int   adcvalue;

int Filter(int m)
{
  static int flag_first = 0, _buff[10], sum;
  const int _buff_max = 10;
  int i;
  
  if(flag_first == 0)
  {
    flag_first = 1;
    for(i = 0, sum = 0; i < _buff_max; i++)
    {
      _buff[i] = m;
      sum += _buff[i];
    }
    return m;
  }
  else
  {
    sum -= _buff[0];
    for(i = 0; i < (_buff_max - 1); i++)
    {
      _buff[i] = _buff[i + 1];
    }
    _buff[9] = m;
    sum += _buff[9];
    
    i = sum / 10.0;
    return i;
  }
}

/*Setup--------------------------------------------------------------------------------------------------------------------------------------------*/
 void setup()
{

Serial.begin(9600);

/*Button-----------------------------------------------------*/
/*pinMode(interruptPin, INPUT);
attachInterrupt(0, pin_ISR, CHANGE);*/
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin),buttonPress , FALLING); // trigger when button pressed, but not when released.
  
/*T&H-----------------------------------------------------*/
    sht20.initSHT20();                         // Init SHT20 Sensor
    delay(100);
    sht20.checkSHT20();                        // Check SHT20 Sensor
 
  /*CO2-----------------------------------------------------*/
    while(CCS811.begin() != 0){
        Serial.println("failed to init chip, please check if the chip connection is fine");
        delay(1000);
    }
 
   /*dust-----------------------------------------------------*/
     pinMode(iled, OUTPUT);
  digitalWrite(iled, LOW);                                     //iled default closed
  
  Serial.begin(9600);                                         //send and receive at 9600 baud
}

/*LOOP--------------------------------------------------------------------------------------------------------------------------------------------*/
void loop()
{
  /*T&C-----------------------------------------------------*/
  
    float humd = sht20.readHumidity();         // Read Humidity
    float temp = sht20.readTemperature();      // Read Temperature
    Serial.print(" Temperature: ");
    Serial.print(temp, 1);
    Serial.print("C");
    Serial.print("\t Humidity: ");
    Serial.print(humd, 1);
    Serial.println("%");
    t=(temp, 1); //new
    h= (humd, 1); //new
    
 /*CO2-----------------------------------------------------*/
    if(CCS811.checkDataReady() == true){
       Serial.print("CO2: ");
        Serial.print(CCS811.getCO2PPM());
        Serial.print("ppm, TVOC: ");
        Serial.print(CCS811.getTVOCPPB());
        Serial.println("ppb");

    } else {
        Serial.println("Data is not ready!");
    }

co2=(CCS811.getCO2PPM()); //new
tvoc=(CCS811.getTVOCPPB()); //new

if ((((CCS811.getCO2PPM()) >=500) && ((CCS811.getCO2PPM())<= 1000)) || (((CCS811.getTVOCPPB()) >=50) && ((CCS811.getTVOCPPB())<= 750))) /*/Alarm 4 - notification || means logical OR */

{
        tone (buzzer, 600);
        delay (180);
        tone (buzzer, 400);
        delay (150);
        noTone (buzzer);
}

    delay(1000);
 

/*Dust-----------------------------------------------------*/
  digitalWrite(iled, HIGH);
  delayMicroseconds(280);
  adcvalue = analogRead(vout);
  digitalWrite(iled, LOW);
  adcvalue = Filter(adcvalue);
  voltage = (SYS_VOLTAGE / 1024.0) * adcvalue * 11;
  if(voltage >= NO_DUST_VOLTAGE)
  {
    voltage -= NO_DUST_VOLTAGE;
    density = voltage * COV_RATIO;
  }
  else
    density = 0;

  Serial.print("The current dust concentration is: ");
  Serial.print(density);
  Serial.print(" ug/m3\n"); 
  pm = (density);//new


/*if ((pm >=75) && (pm<= 115)) //Alarm 4 - notification

{
        tone (buzzer, 600);
        delay (180);
        tone (buzzer, 400);
        delay (150);
        noTone (buzzer);
}
if ((pm >115) && (pm<= 150)) //Alarm 3 - notification

{
        tone (buzzer, 600);
        delay (100);
        noTone (buzzer);
        delay (60);
         tone (buzzer, 600);
        delay (120);
        noTone (buzzer);
        delay (60);
         tone (buzzer, 600);
        delay (140);
        noTone (buzzer);
        delay (60);
        tone (buzzer, 400);
        delay (200);
        noTone (buzzer);*/

/*Display-----------------------------------------------------*/

  lcd.begin(8, 2); //column and row
  lcd.print("Air Q");
 /* lcd.setCursor(0,1);//column and row
  lcd.print(temp,1);
  delay(2000);


  lcd.begin(8, 2); //column and row
  lcd.print("Humidity");
  lcd.setCursor(0,1);//column and row
  lcd.print(humd, 1);
  delay(2000);
  
  lcd.begin(8, 2); //column and row
  lcd.print("CO2 level (ppm)");
  lcd.setCursor(0,1);//column and row
  lcd.print(CCS811.getCO2PPM());
  delay(1000);

  */
  delay(1000);
}

/*Press button and show different values--------------------------------------------------------------------------------------------------------------------------------------------

 lcd.begin(8, 2); //column and row
  lcd.print("Temp (C)");
  lcd.setCursor(0,1);//column and row
  lcd.print(temp,1);
  delay(2000);
    */
    void buttonPress() {
  lcd.begin(8, 2); //column and row
  lcd.print(density);
} 

What's that?

Why?
What else is the processor doing?

There is not a reason in the world to use an interrupt to handle a button to be pressed (VERY rarely, in comupter terms) by a human. If your code is properly written, you can easily handle it using polling in loop().

t=(temp, 1);
This comes directly form an Arduino example. I have not made any modifications and tbh I don't understand it entirely. Nevertheless, the sensor works and I can see the data in the serial monitor.

To your second question, my understanding is that if I don't use interrupt, I need to check the button state in the loop, meaning that it could only be checked at certain times. Not sure if my logic is correct.

Can you provide a link, please?

If nothing else is holding up the processor, you could be checking the state of the button many thousands of times a second.
For a human, that's near-instantaneous.

Thanks, Ray. I'm not familiar with polling. I'll look into it.

Polling here.

I can't find it on the web right now, but this was the code as I found it:

/*
modified on Apr 10, 2021
Modified by MehranMaleki from Arduino Examples
Home
*/

#include <Wire.h>
#include "DFRobot_SHT20.h"

DFRobot_SHT20 sht20;

void setup()
{
    Serial.begin(9600);
    Serial.println("SHT20 Example!");
    sht20.initSHT20();                         // Init SHT20 Sensor
    delay(100);
    sht20.checkSHT20();                        // Check SHT20 Sensor
}

void loop()
{
    float humd = sht20.readHumidity();         // Read Humidity
    float temp = sht20.readTemperature();      // Read Temperature
    Serial.print(" Temperature: ");
    Serial.print(temp, 1);
    Serial.print("C");
    Serial.print("\t Humidity: ");
    Serial.print(humd, 1);
    Serial.println("%");
    delay(10000);
}

I have a long delay between the different instances of reading the sensor. I thought that this would be an issue for checking the state of the button, but I feel that I've been wrong.

I highly doubt that.

You're right. It's from a modified Arduino example. I can't find the link any longer, but I have saved the the code as I found it and it does say that it's modified from an example.

I can't find it, but I just saw that it was a modified example.

Ok, thanks. I'll read a bit more and try to find out how to do this.

I'll post an update once I find a solution. It could help other novice users.

To start, you'll have to get rid of all the delay() calls. Instead use the methods outlined here in the "General design" section on doing several things at once:

Useful links - check here for reference posts / tutorials - Using Arduino / Programming Questions - Arduino Forum

1 Like

If you take nothing else away from this, you should find that you shouldn't be doing time-consuming operations, like LCD updates, in interrupt context.

1 Like

Well these tutorials about blink without understanding (ehm sorry ) wwithout delay
state-machines etc. are all suffering from what I call the

experts blindness for beginner difficulties.

WIth this in mind I wrote two tutorials

about non-blocking timing

and about state-machines

I'm very interested in beginner-questions to improve the easyness to understand the tutorials even more, because beginners are the best experts about "easyness to understand"

best regards Stefan

1 Like

Thanks for the links, StefanL38.

I've already found a solution, thanks to the replies in this topic. It turns out that my issue was much more basic - I didn't quite get that I can have functions outside the loop... (In my defence I come from mechanical engineering and this is all completely new to me).

Anyway, here's what I came up with and it works as expected - two sensors, a button and an LCD. One sensor is being read every 4 seconds, the other every 8 seconds. If the button is pressed once, the value of the first sensor is shown on the LCD for 5 seconds and then the LCD returns to the initial message. If the button is pressed again during this period of 5 seconds, the LCD shows the value of the second sensor. This is the logic, that I wanted to achieve. The code works (sort of) now, but I've used "delay" in order to succeed with showing a second message, when the button is pressed again. Using "delay" doesn't seem to be the best solution, but it will do for starters.

/*Definitions------------------------------------------------------------------------------*/
const int buttonPin = 3; //button connected to digital pin 3

#include <LiquidCrystal.h> //include library for LCD
LiquidCrystal lcd(7, 8, 9, 10, 11, 12); 

#include <Wire.h> //include libraries for reading SHT20 (temperature and humidity)
#include "DFRobot_SHT20.h";
DFRobot_SHT20 sht20;
int t; //used to save the last measured value of t

#include "DFRobot_CCS811.h" //inlude libraries to read CCS811 (eCO2 and TVOC)
DFRobot_CCS811 CCS811;
int co2; //used to save the last measured value of CO2

const long T_interval = 4000; //T sensor will be read every 4000 ms
const long CO2_interval = 8000; //CO2 sensor will be read every 8000 ms
const int butt_interval = 100; // number of millisecs between button readings
const int lcd_duration = 5000; //Data will be displayed on LCD for 5000 ms
const int butt_delay = 5000; //used to define max time between two individual button presses to call another action

// Variables holding the timer value so far. One for each "Timer"
unsigned long T_timer = 0; //for SHT20 sensor
unsigned long CO2_timer = 0; //for CCS811 sensor
unsigned long butt_timer = 0;// time when button press last checked
unsigned long butt_timer_press = 0; //time when the button was first pressed
unsigned long butt_timer_2 = 0; //first press

/*Setup----------------------------------------------------------------------------------------*/
void setup()
{
  Serial.begin(9600);
  
  T_timer = millis ();
  CO2_timer = millis ();

  pinMode(buttonPin, INPUT);  // set the button pin as input
  
  sht20.initSHT20(); // Init SHT20 Sensor
  delay(100);
  sht20.checkSHT20(); // Check SHT20 Sensor
 
  while(CCS811.begin() != 0){ //Init CCS811
  Serial.println("failed to init chip, please check if the chip connection is fine");
  delay(1000);
  }
}

/*FUNCTIONS------------------------------------------------------------------------------------*/
void loop()
{
  if ( (millis () - T_timer) >= T_interval) //if the preset time has elapsed, the temperature sensor will be checked
     checkT ();

  if ( (millis () - CO2_timer) >= CO2_interval) //if the preset time has elapsed, the eCO2 sensor will be checked
    checkCO2 ();

  if (millis() - butt_timer >= butt_interval) //if the preset time has elapsed, the button will be checked
    checkButton ();
}


void checkButton () //check if button is pressed function
{
  butt_timer = millis (); //Time when button state was checked (updates every time in loop)

  if (digitalRead(buttonPin) == HIGH){ //button is pressed
     butt_timer_press = millis (); //remember when it is pressed
     
     while (millis()-butt_timer <= lcd_duration){//show message for the specified duration
        if (digitalRead(buttonPin) == HIGH){//button is pressed again
          lcd.begin(8, 2);
          lcd.print ("First");}
          delay (1000);
          checkButton_2 (); //check if button is pressed again, whilst the message First was being displayed
     }
  }
  else
   {lcd.begin(8, 2);
    lcd.print ("Monitor"); //if the button was not pressed and after the "lcd_duration" has elapsed, show Monitor
   }
}

void checkButton_2 ()
{
    butt_timer_2 = millis (); //Time when button state was checked
    if (digitalRead(buttonPin) == HIGH){ //button is pressed (for the second time)
    while (millis()-butt_timer_2 <= lcd_duration){ //show message for the specified time
    lcd.begin(8, 2);
    lcd.print ("Second");
    }
  }
}


void checkT () //function to read temperature
  {
    float temp = sht20.readTemperature(); 
    Serial.print(" Temperature: ");
    Serial.print(temp);
    Serial.print("C");
    t=(temp, 1);
    T_timer = millis ();
  }  

void checkCO2 () //function to read CO2
{
if(CCS811.checkDataReady() == true){
       Serial.print("CO2: ");
        Serial.print(CCS811.getCO2PPM());
    } else {
        Serial.println("Data is not ready!");
    }
  co2=(CCS811.getCO2PPM()); 
  CO2_timer = millis ();   
}

Hi @vinghen ,

You are another victim of this poorly written blink without understand-example
of the arduino-IDE. You coded a more or less weird distributed version of assigning involved variables.

Using while-loops is blocking again.

This part of your code means

keep the microcontroller 101% busy inside the while-loop stop all other code-execution until lcd_duration is over

If you really want non-blocking code the one and only loop that is looping is

void loop(). Nothing else.

all other functions must work in a

  • quickly jump in
  • quickly jump out

manner

If things shall only happen from time to time you do a very often compairing of a
certain point in time the starting Time and calculate how much time has passed by
but without using an extra while-loop!

if a certain amount of time HAS passed by then
doAction(); // but only once!

If this action shall be repeated after a regular period of time you simply update the starting Time

I'm interested in your 102% honest opinion about the tutorial I have written about non-blocking timing.

If you were confused by reading it I would like to read the truth
If you add the questions that you have this would be really great because beginners are the
best experts about the aspect easyness to understand.

best regards Stefan

perhaps you should go back to the start and draw up a requirements specification stating what you want the system to do?
also what your current system does not do

As a first step to explain the non-blocking concept
look at this pseudo-code

void loop() {
  // jump in - read temperature, 
  // check time to print new? 
  //   yes ? quickly print, quickly jump out
  //   no? - quickly jump out (without printing)
  checkT(); 

  // jump in - read CO2, 
  // check time to print new? 
  //   yes ? quickly print, quickly jump out
  //   no? - quickly jump out (without printing)
  checkCO2();

  // jump in - read button, 
  // check time button pressed long enough ? 
  //   yes ? set Flag DisplayExtra-Value
  //   no? - quickly jump out (WITHOUT setting Flag )
  checkButton();


  // jump in - check flag DisplayExtra-Value set?
  //   yes ? quickly print, quickly jump out
  //   no? - quickly jump out (without printing)  
  DisplayExtraValue();
}

This gives a first impression how it works

In this pseudo-code there is still a flaw

displaying extra-value and "standard-values" are in competition to each other
which should not be.

Here comes in what could be called multi_Operation_Modes:

Your device has different modes of operation

  • read temperature
  • read CO2
  • display standard values
  • check for button-presses
  • display extra value

you must not do two things in the exact same millisecond
it is enough to these things in sequence but fast one after the other
As long as you read temperature you must not rwad CO2, etc.

So it is enough to execute

  • read temperature
    or
  • read CO2
    or
  • display standard values
    or
  • check for button-presses
    or
  • display extra value

as long as all modes get activated each after a short time.

This is what multi_Operation_Modes is.
in each mode it is enough to mutually exclusive execute a part of the code
only that code that belongs to one operation

This is what the switch-case-state-break-statement does.
The

break

is printed bolded because the break is very important

in case read temperature: only execute code for read temperature
break;
.
in case read CO2: only execute code for read CO2
break;
.
in case display standard values: only execute code for display standard values
break;
.
in case check for button-presses: only execute code for check for button-presses
break;
.
in case display extra value: only execute code for display extra value
break;
.

What is really essential is the

  • quickly jump in
  • quickly jump out

manner

this makes sure that all things are running smoothly

Now take a look again into

best regards Stefan

@StefanL38, thanks again for the explanations.

My experience with your tutorials:

  • Timing based on mills - The start and the updates are hard to follow. Once I got to the pizza example, things started to makes sense and are easy to follow.
    I don't quite get the "TimePeriodIsOver() " definition, as apparently I miss some basic knowledge about functions.
    Generally the example is clear, the pseudo-code helps and I figured that I need to read more about functions, in order to fully understand it. Also, your code from post #3 helps furhter.

For me, the naming in the original blink without delay example is not that confusing. The problem comes form not fully understanding how to use functions, how to define pins, constant and variables. I've read a bit about these basics, but as it seems, not enough.

  • Switch case state machine - SImple and easy to follow. Where I have difficulties is once again, due to my limited knowledge of data types, definitions, etc.

Hi @vinghen,

thank you very much for your feedback.
I'm not yet sure if I understand what you mean with

would you mind quoting that part or doing a screenshot of that part?

If you have questions just ask them. Questions are a very good guide to improve explanations.

There is a tutorial that explains the basics pretty good
Part 2 says some basic things about functions
Part 3 about variables
and Part 15 explains functions
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.

best regards Stefan