Using LCD and Interrupts Together in a Sketch

I’m new to the Arduino world but am semi-familiar with C/C++. I’m working an a project that will use multiple sensors and a LCD display to show the gathered info. With simple sensors, such as RB-Wav-39, SEN0189 or DS18S20, I’ve got no issue. However, when I try using interrupts with a YF-S201 (a hall-effect water flow sensor) to count pulses, I just get a blank screen on the LCD. Below is a portion of my code for these two items. Any thoughts or suggestions on why this happens?

//INCLUDES
#include <LiquidCrystal.h>
#include <Wire.h>

//PIN ASSIGNMENTS
#define Pin_FlowSens1 2       //Flow Sensor (YF-S201) to digital pin 2
#define Pin_FlowSens2 3       //Flow Sensor (YF-S201) to digital pin 3
#define Pin_LCDBkltGreen 5    //LCD (LCD-10862) Green Backlight to digital pin 5, PWM freq=980 Hz
#define Pin_LCDBkltBlue 6     //LCD (LCD-10862) Blue Backlight to digital pin 6, PWM freq=980 Hz
#define Pin_LCDrs 7           //LCD (LCD-10862) RS to digital pin 7
#define Pin_LCDen 8           //LCD (LCD-10862) Enable to digital pin 8
#define Pin_LCDBkltRed 9      //LCD (LCD-10862) Red Backlight to digital pin 9, PWM freq=490 Hz
#define Pin_LCDd4 10          //LCD (LCD-10862) D4 to digital pin 10
#define Pin_LCDd5 11          //LCD (LCD-10862) D5 to digital pin 11
#define Pin_LCDd6 12          //LCD (LCD-10862) D6 to digital pin 12
#define Pin_LCDd7 13          //LCD (LCD-10862) D7 to digital pin 13

LiquidCrystal lcd(Pin_LCDrs, Pin_LCDen, Pin_LCDd4, Pin_LCDd5, Pin_LCDd6, Pin_LCDd7); // Configure LCD Display Pins
int brightness = 255; //Change the overall brightness by range 0 -> 255
volatile int FlowCnt1; //measuring the rising edges of the signal
volatile int FlowCnt2; //measuring the rising edges of the signal

void setup(void) {
 Serial.begin(9600);
 lcd.begin(16, 2); //Initialize LCD and set up the number of columns and rows
 pinMode(Pin_LCDBkltRed, OUTPUT);
 pinMode(Pin_LCDBkltGreen, OUTPUT);
 pinMode(Pin_LCDBkltBlue, OUTPUT);
 setBacklight(0, 0, 0);
 pinMode(Pin_FlowSens1, INPUT);
 attachInterrupt(digitalPinToInterrupt(Pin_FlowSens1), rpm1, RISING);
 pinMode(Pin_FlowSens2, INPUT);
 attachInterrupt(digitalPinToInterrupt(Pin_FlowSens2), rpm2, RISING);
} //End Setup

void loop(void) {
 float flow =0;
 FlowCnt1 = 0; //Set NbTops to 0 ready for calculations
 sei(); //Enables interrupts
 delay (1000); //Wait 1 second
 cli(); //Disable interrupts
 flow = (FlowCnt1 * 60 / 7.5); //(Pulse frequency x 60) / 7.5Q, = flow rate in L / hour
 delay (5000);
 lcd.setCursor(0, 0); //Set the cursor to the top left
 lcd.print("Flow Rate:");
 lcd.setCursor(5, 1); //Set the cursor to the top left
 lcd.print(flow); //Prints the number calculated above
 Serial.println(flow, DEC);
 lcd.print(" L/hr"); //Prints "L/hour" and returns a new line

 delay(1000); //delay in between sensor reads to be able to read
 lcd.clear(); //clear LCD screen to prepare for next value
} //End Main Loop

//-------------------------------------------------------------


void setBacklight(uint8_t r, uint8_t g, uint8_t b) {
 // normalize the red LED - its brighter than the rest!
 //  r = map(r, 0, 255, 0, 100);
 //  g = map(g, 0, 255, 0, 150);

 r = map(r, 0, 255, 0, brightness * 1.5);
 g = map(g, 0, 255, 0, brightness);
 b = map(b, 0, 255, 0, brightness);

 analogWrite(Pin_LCDBkltRed, r);
 analogWrite(Pin_LCDBkltGreen, g);
 analogWrite(Pin_LCDBkltBlue, b);
} //End setBacklight

void rpm1 () //
{
 FlowCnt1++; //called by interupt, Measures the rising and falling edge of the hall effect sensors signal
}

void rpm2 () //
{
 FlowCnt2++; //called by interupt, Measures the rising and falling edge of the hall effect sensors signal
}

BTW, I'm using an Arduino UNO

The delays you have in the code are probably each a recipe for disaster. This only deals with one flowmeter but may be a start.

Sample code for the sensor itself is below. It works beautifully.

// reading liquid flow rate using Seeeduino and Water Flow Sensor from Seeedstudio.com
// Code adapted by Charles Gantt from PC Fan RPM code written by Crenn@thebestcasescenario.com
// http:/themakersworkbench.com http://thebestcasescenario.com http://seeedstudio.com

volatile int NbTopsFan; //measuring the rising edges of the signal
int Calc;
int hallsensor = 2; //The pin location of the sensor

void rpm () //This is the function that the interupt calls
{
NbTopsFan++; //This function measures the rising and falling edge of the hall effect sensors signal
}

void setup() //
{
Serial.begin(9600); //This is the setup function where the serial port is  initialised,
pinMode(hallsensor, INPUT); //initializes digital pin 2 as an input

//attachInterrupt(0, rpm, RISING); //and the interrupt is attached
attachInterrupt(digitalPinToInterrupt(hallsensor), rpm, RISING); //and the interrupt is attached

}

void loop ()
{
NbTopsFan = 0; //Set NbTops to 0 ready for calculations
sei(); //Enables interrupts
delay (1000); //Wait 1 second
cli(); //Disable interrupts
Calc = (NbTopsFan * 60 / 7.5); //(Pulse frequency x 60) / 7.5Q, = flow rate in L / hour
Serial.print (Calc, DEC); //Prints the number calculated above
Serial.print (" L/hour\r\n"); //Prints "L/hour" and returns a new line
}

Nick_Pyner:
The delays you have in the code are probably each a recipe for disaster. This only deals with one flowmeter but may be a start.

Thanks for the example Nick! Looking at it now…

Nick_Pyner: The delays you have in the code are probably each a recipe for disaster. This only deals with one flowmeter but may be a start.

Two questions:

1) why would you write to an input?

pinMode(Pin_FlowSens1, INPUT); digitalWrite(Pin_FlowSens1, HIGH);

2) Why attachInterrupt in setup and then at the end of loop instead of attachInterrupt at start of loop and detach at end?

rmknepp: Two questions:

1) why would you write to an input?

pinMode(Pin_FlowSens1, INPUT); digitalWrite(Pin_FlowSens1, HIGH);

I think it is just ensuring the counter is clear.

2) Why attachInterrupt in setup and then at the end of loop instead of attachInterrupt at start of loop and detach at end?

The detach do other stuff re-attach

is in the millis time window. I guess the attach in setup is the start of the first count.

I think it is just ensuring the counter is clear.

Writing to an INPUT pin turns the internal pullup resistor on or off. It does nothing to ensure that any counter is clear.

If it did, I'd use it to clean all the cluttered counters at my house. 8)

2) Why attachInterrupt in setup and then at the end of loop instead of attachInterrupt at start of loop and detach at end?

In which code does that happen?

PaulS: Writing to an INPUT pin turns the internal pullup resistor on or off. It does nothing to ensure that any counter is clear.

If it did, I'd use it to clean all the cluttered counters at my house. 8) In which code does that happen?

Paul, this link that Nick posted

I’ve modified the code to remove setting the pull-up because I have a external one. Also, I rearranged the attached/detach interrupts as I think they will cut down redundancy. (see attached)

JaycarWaterFlowGauge_mod.ino (4.99 KB)

What do you think of this sort of approach to your task? No delays, shouldn’t cause problems with the LCD etc…

I have the flow sensor updating basically continuously and the display being updated once per second. You could use the rapidity of the flow sensing to add in averaging etc.

//INCLUDES
#include <LiquidCrystal.h>
#include <Wire.h>

//PIN ASSIGNMENTS
#define Pin_FlowSens1 2       //Flow Sensor (YF-S201) to digital pin 2
#define Pin_FlowSens2 3       //Flow Sensor (YF-S201) to digital pin 3
#define Pin_LCDBkltGreen 5    //LCD (LCD-10862) Green Backlight to digital pin 5, PWM freq=980 Hz
#define Pin_LCDBkltBlue 6     //LCD (LCD-10862) Blue Backlight to digital pin 6, PWM freq=980 Hz
#define Pin_LCDrs 7           //LCD (LCD-10862) RS to digital pin 7
#define Pin_LCDen 8           //LCD (LCD-10862) Enable to digital pin 8
#define Pin_LCDBkltRed 9      //LCD (LCD-10862) Red Backlight to digital pin 9, PWM freq=490 Hz
#define Pin_LCDd4 10          //LCD (LCD-10862) D4 to digital pin 10
#define Pin_LCDd5 11          //LCD (LCD-10862) D5 to digital pin 11
#define Pin_LCDd6 12          //LCD (LCD-10862) D6 to digital pin 12
#define Pin_LCDd7 13          //LCD (LCD-10862) D7 to digital pin 13

LiquidCrystal lcd(Pin_LCDrs, Pin_LCDen, Pin_LCDd4, Pin_LCDd5, Pin_LCDd6, Pin_LCDd7); // Configure LCD Display Pins
int brightness = 255; //Change the overall brightness by range 0 -> 255

volatile int FlowCnt1; //measuring the rising edges of the signal
volatile int FlowCnt2; //measuring the rising edges of the signal
float
    flow;
    
void setup(void) 
{
    Serial.begin(9600);
    lcd.begin(16, 2); //Initialize LCD and set up the number of columns and rows
    pinMode(Pin_LCDBkltRed, OUTPUT);
    pinMode(Pin_LCDBkltGreen, OUTPUT);
    pinMode(Pin_LCDBkltBlue, OUTPUT);
    setBacklight(0, 0, 0);
    pinMode(Pin_FlowSens1, INPUT);
    attachInterrupt(digitalPinToInterrupt(Pin_FlowSens1), rpm1, RISING);
    pinMode(Pin_FlowSens2, INPUT);
    attachInterrupt(digitalPinToInterrupt(Pin_FlowSens2), rpm2, RISING);

} //End Setup

void UpdateFlow( void )
{
    static byte
        stateUF = 0;
    static unsigned long
        timerUF;
    unsigned long
        timeNow;

    switch( stateUF )
    {
        case    0:
            noInterrupts();
            FlowCnt1 = 0;
            interrupts();
            timerUF = micros();
            stateUF++;
            
        break;

        case    1:
            //sample sensor for 1-sec (1,000,000 microseconds)
            if( (micros() - timerUF) < 1000000ul )
                return;
            
            flow = (float)FlowCnt1 * 60.0 / 7.5;

            stateUF = 0;

        break;
            
    }//switch
    
}//UpdateFlow

void UpdateDisplay( void )
{
    static unsigned long
        timeDisplay = 0;
    unsigned long
        timeNow;

    timeNow = millis();
    if( (timeNow - timeDisplay) <= 1000 )
        return;
    timeDisplay = timeNow;

    lcd.setCursor( 0, 0 ); //Set the cursor to the top left
    lcd.print( "Flow Rate (L/hr)" );
    lcd.setCursor( 5, 1 ); //Set the cursor to the top left
    lcd.print( "          " ); //clears the flow line
    lcd.setCursor( 5, 1 ); //reset the cursor the position on the line and print flow
    lcd.print( flow ); //Prints the number calculated above

    Serial.println(flow, DEC);
    
}//UpdateDisplay

void loop(void) 
{
    UpdateFlow();
    UpdateDisplay();

} //End Main Loop

//-------------------------------------------------------------


void setBacklight(uint8_t r, uint8_t g, uint8_t b) 
{
    // normalize the red LED - its brighter than the rest!
    //  r = map(r, 0, 255, 0, 100);
    //  g = map(g, 0, 255, 0, 150);

    r = map(r, 0, 255, 0, brightness * 1.5);
    g = map(g, 0, 255, 0, brightness);
    b = map(b, 0, 255, 0, brightness);

    analogWrite(Pin_LCDBkltRed, r);
    analogWrite(Pin_LCDBkltGreen, g);
    analogWrite(Pin_LCDBkltBlue, b);

} //End setBacklight

void rpm1( void )
{
    //called by interrupt; counts the rising edges of the hall effect flow sensor 1
    FlowCnt1++;
     
}//rpm1

void rpm2( void )
{
    //called by interrupt; counts the rising edges of the hall effect flow sensor 2
    FlowCnt2++;
     
}//rpm2

Blackfin:
What do you think of this sort of approach to your task? No delays, shouldn’t cause problems with the LCD etc…

I have the flow sensor updating basically continuously and the display being updated once per second. You could use the rapidity of the flow sensing to add in averaging etc.

//INCLUDES

#include <LiquidCrystal.h>
#include <Wire.h>

//PIN ASSIGNMENTS
#define Pin_FlowSens1 2      //Flow Sensor (YF-S201) to digital pin 2
#define Pin_FlowSens2 3      //Flow Sensor (YF-S201) to digital pin 3
#define Pin_LCDBkltGreen 5    //LCD (LCD-10862) Green Backlight to digital pin 5, PWM freq=980 Hz
#define Pin_LCDBkltBlue 6    //LCD (LCD-10862) Blue Backlight to digital pin 6, PWM freq=980 Hz
#define Pin_LCDrs 7          //LCD (LCD-10862) RS to digital pin 7
#define Pin_LCDen 8          //LCD (LCD-10862) Enable to digital pin 8
#define Pin_LCDBkltRed 9      //LCD (LCD-10862) Red Backlight to digital pin 9, PWM freq=490 Hz
#define Pin_LCDd4 10          //LCD (LCD-10862) D4 to digital pin 10
#define Pin_LCDd5 11          //LCD (LCD-10862) D5 to digital pin 11
#define Pin_LCDd6 12          //LCD (LCD-10862) D6 to digital pin 12
#define Pin_LCDd7 13          //LCD (LCD-10862) D7 to digital pin 13

LiquidCrystal lcd(Pin_LCDrs, Pin_LCDen, Pin_LCDd4, Pin_LCDd5, Pin_LCDd6, Pin_LCDd7); // Configure LCD Display Pins
int brightness = 255; //Change the overall brightness by range 0 → 255

volatile int FlowCnt1; //measuring the rising edges of the signal
volatile int FlowCnt2; //measuring the rising edges of the signal
float
    flow;
   
void setup(void)
{
    Serial.begin(9600);
    lcd.begin(16, 2); //Initialize LCD and set up the number of columns and rows
    pinMode(Pin_LCDBkltRed, OUTPUT);
    pinMode(Pin_LCDBkltGreen, OUTPUT);
    pinMode(Pin_LCDBkltBlue, OUTPUT);
    setBacklight(0, 0, 0);
    pinMode(Pin_FlowSens1, INPUT);
    attachInterrupt(digitalPinToInterrupt(Pin_FlowSens1), rpm1, RISING);
    pinMode(Pin_FlowSens2, INPUT);
    attachInterrupt(digitalPinToInterrupt(Pin_FlowSens2), rpm2, RISING);

} //End Setup

void UpdateFlow( void )
{
    static byte
        stateUF = 0;
    static unsigned long
        timerUF;
    unsigned long
        timeNow;

switch( stateUF )
    {
        case    0:
            noInterrupts();
            FlowCnt1 = 0;
            interrupts();
            timerUF = micros();
            stateUF++;
           
        break;

case    1:
            //sample sensor for 1-sec (1,000,000 microseconds)
            if( (micros() - timerUF) < 1000000ul )
                return;
           
            flow = (float)FlowCnt1 * 60.0 / 7.5;

stateUF = 0;

break;
           
    }//switch
   
}//UpdateFlow

void UpdateDisplay( void )
{
    static unsigned long
        timeDisplay = 0;
    unsigned long
        timeNow;

timeNow = millis();
    if( (timeNow - timeDisplay) <= 1000 )
        return;
    timeDisplay = timeNow;

lcd.setCursor( 0, 0 ); //Set the cursor to the top left
    lcd.print( “Flow Rate (L/hr)” );
    lcd.setCursor( 5, 1 ); //Set the cursor to the top left
    lcd.print( "          " ); //clears the flow line
    lcd.setCursor( 5, 1 ); //reset the cursor the position on the line and print flow
    lcd.print( flow ); //Prints the number calculated above

Serial.println(flow, DEC);
   
}//UpdateDisplay

void loop(void)
{
    UpdateFlow();
    UpdateDisplay();

} //End Main Loop

//-------------------------------------------------------------

void setBacklight(uint8_t r, uint8_t g, uint8_t b)
{
    // normalize the red LED - its brighter than the rest!
    //  r = map(r, 0, 255, 0, 100);
    //  g = map(g, 0, 255, 0, 150);

r = map(r, 0, 255, 0, brightness * 1.5);
    g = map(g, 0, 255, 0, brightness);
    b = map(b, 0, 255, 0, brightness);

analogWrite(Pin_LCDBkltRed, r);
    analogWrite(Pin_LCDBkltGreen, g);
    analogWrite(Pin_LCDBkltBlue, b);

} //End setBacklight

void rpm1( void )
{
    //called by interrupt; counts the rising edges of the hall effect flow sensor 1
    FlowCnt1++;
   
}//rpm1

void rpm2( void )
{
    //called by interrupt; counts the rising edges of the hall effect flow sensor 2
    FlowCnt2++;
   
}//rpm2

I am not the OP, but I don’t like it because:

The fixed text of the display should be written only once and that done in the setup.

The variables should only be written to the LCD display if they have changed from the last time the function was executed.

Paul

Paul_KD7HB: I am not the OP, but I don't like it because:

The fixed text of the display should be written only once and that done in the setup.

The variables should only be written to the LCD display if they have changed from the last time the function was executed.

Paul

Fair enough. Easy things to change if they bug the op. Thanks for your opinion.

Blackfin: What do you think of this sort of approach to your task? No delays, shouldn't cause problems with the LCD etc...

I have the flow sensor updating basically continuously and the display being updated once per second. You could use the rapidity of the flow sensing to add in averaging etc.

Blackfin,

Thank you for your input. I appreciate the different spin on the implementation of my code. However, I don't think it always for the other sensors I have. My initial plan was to cycle through readings from each sensor (about 6 in total).

The only negative with this plan is it only shows the instantaneous readings, which isn't a problem at this time (I'm just trying to get things integrated; I can get "fancy" later). Maybe I should display the average flow rate instead...

I think I'm going to go with something like I posted back in post #9, based on Nick's post.

With that said though, I'll be using delays instead of the millis counter. Is this really an issue?

Thanks again,

Ryan

rmknepp: My initial plan was to cycle through readings from each sensor (about 6 in total).

If you mean six flow meters, I think you need to move to a Mega. Uno has only two interrupt pins.

The only negative with this plan is it only shows the instantaneous readings, which isn't a problem at this time (I'm just trying to get things integrated; I can get "fancy" later). Maybe I should display the average flow rate instead...

Probably a good idea. One instantaneous reading a second is unmanageable, six will be impossible.

With that said though, I'll be using delays instead of the millis counter. Is this really an issue?

very likely, and probably fatal. I can't think of a worse type of project to use delay in.

rmknepp: Blackfin,

Thank you for your input. I appreciate the different spin on the implementation of my code. However, I don't think it always for the other sensors I have. My initial plan was to cycle through readings from each sensor (about 6 in total).

There's no reason the state machine couldn't be modified to cycle through numerous sensors.

I'll be using delays instead of the millis counter. Is this really an issue?

It's up to you. delay() is a convenient way to, say, allow beginners blink an LED but is generally frowned upon otherwise. If you're doing something really simple and limited, don't expect to do multiple things at the same time etc, it can be fine. But it's an easy tool to misuse. As programs become more complex the blocking nature of delay() will become problematic.

Good luck.

Nick_Pyner:
If you mean six flow meters, I think you need to move to a Mega. Uno has only two interrupt pins.

2 flow meters, 2 temperature sensors, 1 level sensor, 1 turbidity sensor (all permanently connected) and an pH sensor (connected occasionally)

Nick_Pyner:
Probably a good idea. One instantaneous reading a second is unmanageable, six will be impossible.

This will just be 2. My end use is an aquarium; the flow sensors are for the input and output of a canister filter. So, i’ll get 2 separate readings from sensor.

Nick_Pyner:
very likely, and probably fatal.

I’ll use the if statement approach used in the above examples then.

Blackfin:
There’s no reason the state machine couldn’t be modified to cycle through numerous sensors.

This is true, I’ll look into it! :slight_smile:

Blackfin:
It’s up to you. delay() is a convenient way to, say, allow beginners blink an LED but is generally frowned upon otherwise. If you’re doing something really simple and limited, don’t expect to do multiple things at the same time etc, it can be fine. But it’s an easy tool to misuse. As programs become more complex the blocking nature of delay() will become problematic.

Like I said, I’m a noob so still figuring out the do’s and don’ts. Also, I was only planning on having it read one sensor at a time.

2 flow meters, 2 temperature sensors, 1 level sensor, 1 turbidity sensor (all permanently connected) and an pH sensor (connected occasionally)

The pin count should be OK so, if you already have a Uno, try it. As said before, you may run out of memory (well before you run out of pins).

This will just be 2.

Two is probably two too many, and average over ten seconds is probably a lot more sensible, particulalrly as rate can hardly be mission-critical here.

With that said though, I'll be using delays instead of the millis counter. Is this really an issue?

Yes.

sei(); //Enables interrupts
delay (1000); //Wait 1 second
cli(); //Disable interrupts

This code which enables global interrupts, reads an interrupt count over a time period set by the delay(), and then disables global interrupts is a very poor approach. You may into difficulty with the Serial output, which relys on interrupts. You would be well to review Nick Gammon's tutorial on interrupts.

Blackfin has shown you the correct method. Here's some very simple code which demonstrates the proper approach

//pulse counts changed within count_ISR's
volatile unsigned int count1;
volatile unsigned int count2;

//variables for protected copies of counts
unsigned int copyCount1;
unsigned int copyCount2;

void setup() {

  Serial.begin(115200);

  /*
  //send test signal to interrupt pins
  analogWrite(5,127);//980 hz timer 0 jumper pin 5 to pin 2
  analogWrite(9,127);//490 hz timer 1 jumper pin 9 to pin 3
  */

  attachInterrupt(digitalPinToInterrupt(2), count1_ISR, RISING); 
  attachInterrupt(digitalPinToInterrupt(3), count2_ISR, RISING); 
  
}

void loop() {
  static unsigned long lastSecond;
  if (micros() - lastSecond >= 1000000L)
  {
    lastSecond += 1000000;
    getCount();
    Serial.println(copyCount1);
    Serial.println(copyCount2);
    Serial.println();
  }

}

void count1_ISR()
{
  count1++;
}

void count2_ISR()
{
  count2++;
}

unsigned int getCount()
{
  noInterrupts();
  copyCount1 = count1;
  count1 = 0;
  copyCount2 = count2;
  count2 = 0;
  interrupts();
}

Thank you all for your assistance! I've got my device working with almost all the sensors and displaying correctly on the LCD screen.