Void Loop stalls

Hi Arduino Forum,
I have frequently visited but never posted before. I have a water tank and I want to know when it's not filling or the water level is getting low. Board of choice nano 33 IOT, a non contact level sensor, US sensor, a hall effect flow sensor, 3 LEDs and a connection to MQTT broker. It's grown from a more simple but not working system over the last 3+ years. I have divided the code into functions. I have had an interesting learning experience and am grateful to loads of instructional info out there. I now have something that all but works. Only I have two (known) sticking points: Void LEDs() stalls after all LEDs are HIGH after a number of loops, When there is no stall here it stalls in void mqtt() after Serial.println of the error code if it is -1. In advance thank you.

#include <Arduino_ConnectionHandler.h>  //connection handeler library is used to check WiFi connection and reconnect
#include "arduino_secrets.h"            //loads the arduino_secrets.h file (note "" v <>)
#include <ArduinoMqttClient.h>

WiFiConnectionHandler conn(SECRET_SSID, SECRET_PASS); //Arduino_ConnectionHandler.h declaring the login info.
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);

//Mqtt setup
const char broker[]              = "192.168.0.50"; //my raspberry pi Zero W with mosqitto broker
int        port                  = 1883;           //brokers port
const char topicflow[]           = "flow";         //publishes to topic "tank/flow"
const char topiclevel[]          = "level";        //publishes to topic "tank\level"
unsigned long mqttMillis         = 0;              //mqtt - store for curent mqttMillis
unsigned long mqttPrevMillis     = 0;              //mqtt - store for prior mqttMillis value
const unsigned long mqttPeriod   = 60000;          //mqtt - period between mqtt broadcasts

//Hall effect flow sensor
int flowPin                      = 2;              //Sets pin 2 to be 'int & flowPin'  (which is an intrrupt pin)
double flowRate                  = 0;              //flow - calculated flow value
volatile int count               = 0;              //flow - counts number of hall effect occurences (needs to be volatile as it is used in the intrrupt)
int flowDelay                    = 10000;          //flow - period of intrupt and count

//non contact level sensor
int levelPin                     = 3;              //Sets pin 3 to be 'int & levelPin'                      
int fullLevel                    = 0;              //level status 1 = full / 0 = not full sets fullLevel to 0
unsigned long levelMillis        = 0;              //level - store for curent levelMillis  
unsigned long levelPrevMillis    = 0;              //level - store for prior levelMillis value
const unsigned long levelPeriod  = 3000;           //level - period between FullLevel readings

//ultrasound
int trigPin                      = 8;              //sets pin 8 to be 'int & trigPin'
int echoPin                      = 9;              //sets pin 8 to be 'int & echoPin'
int remaining                    = 0;              //sets variable for the remaining water
unsigned long usMillis           = 0;              //us - store for curent usMillis
unsigned long usPrevMillis       = 0;              //us - store for prior usMillis value
const unsigned long usPeriod     = 3000;           //us - period between TOF readings
int duration                     = 0;              //us - TOF
int distance                     = 0;              //distance calculated from TOF


//LED
int ledhi                        = 4;              //sets pin 4 to be 'int & ledhi' the green LED
int ledmed                       = 5;              //sets pin 5 to be 'int & ledmed' the yellow LDE
int ledlow                       = 6;              //sets pin 6 to be 'int & ledlow' red LDE
//unsigned long LEDMillis         = 0;     //LED - store for curent LDEMillis
//unsigned long LEDPrevMillis     = 0;     //LED - store for prior LDEMillis value
//const unsigned long             = 3000;  //LED - period between LED update
const unsigned long flashPeriod  = 100000;//LED - period between the red (ledlow) will flash when flow <= 10 l/h 

void setup() {
  //serial setup
  Serial.begin(9600);
  while (!Serial) {
    ;   //waits for the USB serial to connect only needed for debugging
  }
  //connects to the WiFi
  Serial.print("Attempting to connect to WPA SSID: ");
  Serial.println(SECRET_SSID);  
  while (WiFi.begin(SECRET_SSID, SECRET_PASS) != WL_CONNECTED) {
    // failed, retry
    Serial.print(".");
    delay(5000);
  }
  
  //hall effect flow sensor setup
  pinMode(flowPin, INPUT);                                 //sets flowPin to input
  attachInterrupt(digitalPinToInterrupt(2), Flow, RISING); //configurees inerrupt 0 (apparently pin 2 on nano / uno?) to run funcion 'Flow', the counter

  //non contact level sensor setup
  pinMode(levelPin,INPUT);  //sets levelPin (pin 3) to input

  //ultrasound
  pinMode(trigPin, OUTPUT); //sets trigPin (pin 8) to output
  pinMode(echoPin, INPUT_PULLUP);  //sets echoPin (pin 9) to input
  
  //LEDs
  pinMode(ledhi,  OUTPUT);  //sets ledhi  (pin 4) to output
  pinMode(ledmed, OUTPUT);  //sets ledmed (pin 5) to output
  pinMode(ledlow, OUTPUT);  //sets ledlow (pin 6) to output
}

void loop() {
  
  //checkWiFi();
  flowCal();
  Full();
  Ultrasound();
  mqtt();
  LEDs(); 
}
void LEDs() {

  Serial.println(fullLevel, DEC);

  //check LEDs
  digitalWrite(ledhi, HIGH);
  digitalWrite(ledmed, HIGH);
  digitalWrite(ledlow, HIGH);
  delay(10000);               //****why is this shorter than expected 10000 millis is 10 s****

  //****the codeappears to stall here.  The last serial monitor text is the Serial.println above and the LEDs all remain HIGH**** 

  digitalWrite(ledhi, LOW);
  digitalWrite(ledmed, LOW);
  digitalWrite(ledlow, LOW);

  Serial.print("This is the flowRate value used to set the LEDs flashing, or not = ");
  Serial.println(flowRate);

  //set the flow status (flashing red if <= 10 l/h)
  if (flowRate < 10) {
    digitalWrite(ledlow, LOW);
    delay(flashPeriod);
    digitalWrite(ledlow, HIGH);
    delay(flashPeriod);
    digitalWrite(ledlow, LOW);
    delay(flashPeriod);
    digitalWrite(ledlow, HIGH);
    delay(flashPeriod);
    digitalWrite(ledlow, LOW);
    delay(flashPeriod);
    digitalWrite(ledlow, HIGH);
    delay(flashPeriod);
    digitalWrite(ledlow, LOW);
    delay(flashPeriod);
    digitalWrite(ledlow, HIGH);
    delay(flashPeriod);
    digitalWrite(ledlow, LOW);
    delay(flashPeriod);
    digitalWrite(ledlow, HIGH);
    delay(flashPeriod);
    digitalWrite(ledlow, LOW);
    delay(flashPeriod);
    digitalWrite(ledlow, HIGH);
    delay(flashPeriod);
    digitalWrite(ledlow, LOW);
    delay(flashPeriod);
    digitalWrite(ledlow, HIGH);
    delay(flashPeriod);
    digitalWrite(ledlow, LOW);
    delay(flashPeriod);
    digitalWrite(ledlow, HIGH);
    delay(flashPeriod);
    digitalWrite(ledlow, LOW);
    delay(flashPeriod);
    digitalWrite(ledlow, HIGH);
    delay(flashPeriod);
    digitalWrite(ledlow, LOW);
    delay(flashPeriod);
    digitalWrite(ledlow, HIGH);
    delay(flashPeriod);
    delay(flashPeriod);
  }

  //set the RAG level status LEDs
  if (fullLevel == 1) {
    remaining = 100;
  } else {  //this else will need amending to read the US sensor reading in
    remaining = 0;
  }
  Serial.print("This is the value for fullLevel used to set the RAG = ");
  Serial.println(fullLevel);
  Serial.print("This is the value for remaining used to set the RAG = ");
  Serial.println(remaining);

  if (remaining == 100) {
    digitalWrite(ledhi, HIGH);
    digitalWrite(ledmed, LOW);
    digitalWrite(ledlow, LOW);
  }
  if (remaining < 100) {
    digitalWrite(ledhi, LOW);
    digitalWrite(ledmed, HIGH);
    digitalWrite(ledlow, LOW);
  }
  if (remaining < 50) {
    digitalWrite(ledhi, LOW);
    digitalWrite(ledmed, LOW);
    digitalWrite(ledlow, HIGH);
  }
}
void mqtt() {

  mqttClient.poll();  // call poll() regularly to allow the library to send MQTT keep alives which avoids being disconnected by the broker

  mqttMillis = millis();                              //takes Millis and stores it
  if ((mqttMillis - mqttPrevMillis) >= mqttPeriod) {  //time check for the mqtt message broadcast
    mqttPrevMillis = mqttMillis;                      // save the time a message is sent

    conn.check();                                     //This runs a WiFi connection check reconnecting if connection is lost -Arduino_ConnectionHandler.h
    Serial.print("Checking WiFi connection ");
    Serial.println();

    //is there a need for a pause while the WiFi is checked and connection is up?  Would need to look in the libarary and see if that is possible

    Serial.print("Attempting to connect to the MQTT broker: ");
    Serial.println(broker);

    if (!mqttClient.connect(broker, port)) {
      Serial.print("MQTT connection failed! Error code = ");
      Serial.println(mqttClient.connectError());      //****I think this stalls the loop if code is -1 ****

      //could add and LED alarm state here if the MQTT broker fails to connect

      while (1)
        ;
    }
    Serial.println("You're connected to the MQTT broker!");



    Serial.print("Sending message to topic: ");
    Serial.println("tank/flow");
    Serial.println(flowRate);

    // send message, the Print interface can be used to set the message contents
    mqttClient.beginMessage(topicflow);
    mqttClient.print(flowRate);
    mqttClient.endMessage();

    Serial.print("Sending message to topic: ");
    Serial.println("tank/level");
    Serial.print(fullLevel, DEC);

    // send message, the Print interface can be used to set the message contents
    mqttClient.beginMessage(topiclevel);
    mqttClient.print(fullLevel, DEC);
    mqttClient.endMessage();

    Serial.println();
  }
}
//this counts once the itrrupt is called
void Flow() {
  count++;  //counts pulses during the intrupt
}
//the flowCal function
void flowCal() {
  count = 0;         //resets the counter
  interrupts();      //enable interrupts on the arduino
  delay(flowDelay);  //waits for time = "flowDelay"
  noInterrupts();    //Disables the inturrupts on arduino

  //calculation
  flowRate = (23 * count / flowDelay);  //l/min (23  the conversion factor on the sensor) I don't know if this is the correct formular for this sensor
  flowRate = (flowRate * 60);           //l/h

  Serial.print("Water flow rate = ");
  Serial.print(flowRate);
  Serial.println(" l/h");
}
void Full() {
  levelMillis = millis();           //takes Millis and stores it
  if ((levelMillis - levelPrevMillis) >= levelPeriod) {
    levelPrevMillis = levelMillis;  //updates levelPrevMillis
    fullLevel = digitalRead(3);     //updates fullLevel variable with sensor reading

    Serial.print("water level = ");
    Serial.println(fullLevel, DEC);
  }
}
void Ultrasound() {
  usMillis = millis();
  if (fullLevel == 0) {
    if ((usMillis - usPrevMillis) >= usPeriod) {
      usPrevMillis = usMillis;            //updates usPrevMillis
      digitalWrite(trigPin, LOW);         //sets trigger pin is LOW double check
      delayMicroseconds(5);               //waits
      digitalWrite(trigPin, HIGH);        //sets trigger pin HIGH starts US ping
      delayMicroseconds(10);              //waits, there is some question as to how long
      digitalWrite(trigPin, LOW);         //sets triver pin LOW
      duration = pulseIn(echoPin, HIGH);  //sets duration as TOF
      distance = duration * 0.034 / 2;    //calculates distance from TOF, ****check this
    }
  }
}

I'd have to say it's the way your using interrupts..
can't turn them off and only turn them on when you need them..
you should instead only turn them off briefly to copy the count to local var and reset it to 0 then turn interrupts back on..

I'd lose all use of the delay function, a state machine is needed for driving the LEDs..

good luck.. ~q

If your MQTT connection fails for whatever reason, your code most definitely will loop here forever, a.k.a. stall. If that is not what you want, then you will have to remove the infinite while loop and replace it with what you do what.

I also agree with @qubits-us that you need to leave interrupts on and only briefly disable them. You have it the other way around. With interrupts off, timekeeping no longer works.

Except back in March 2020, FWIW

last post was 5 years ago.

Discorse, you're dubnk!

Use polling if you don't really need interrupts.

So to figure out whats going on. Change the interrupts to polling, even if you
need interrupts .

That will take any interrupt issues out of the equation.

How is your project powered ?

Did I.

The project is currently on the bench and powered out the back of a Raspberry Pi. When it's installed it will be powered by a USB socket on a mains socket.

Apparently

wow that dates how long I have been dipping in and out of this project. It was installed for a bit but only using the US sensor which was no use when the tank was full as the water level was too high and in its' sensory dead zone. I also had to remember to look out at the LEDs which was only just a bit better than remembering to look in the tank. Thanks for reminding me of that post, I'd completely forgotten. I'll try and work out how to get the bit of text / message I'm replying to included too.

Thanks, blh64 so basically that bit sends it down a hole it has no means to climb out of. I'll have to put a bit in to reconnect to the broker.

Thank you noweare, I'll look into replacing the interrupt with polling. I wasn't overly sure what was going on in the interrupt, evidently as as it's pointed out I'm effectively using the interrupt in reverse!!!

Highlight it and click on "Quote"

1 Like

Thank you qubits-us, I wasn't all that happy putting the 'delays' in after finding out about 'millis'. I'll get rid of them, they did appear a quick and I guess dirty way to control the LEDs but I noted the delay for all 3 LEDs just wasn't actually using the value I popped in so I was obviously doing something incorrect there.

I've just done a little / quick scan on polling. with a view to swapping out the interrupt. In the example google kindly provided, it was a matter of looking at a button LOW / HIGH. I'm, as I understand it, looking at the number of events in a period of time. This is why I have favored the interrupt. I thought I was turning on interrupts pausing the loop to listen to pin 3 and count the number of HIGHs over the 'flowDelay', 10000 millis. Then after that turning off the interrupt to get on with the rest of the loop. I'm not sure a poll will do that as the probability of that part of the loop aligning with a pulse from the sensor. Now this where I may have the the sensor wrong. I did open one up, I butchered one trying to lengthen the tails on it. I've binned in now so I don't have it to inspect but, as I recall, simply sent pulses.

It sure does! For 10 seconds, in fact. Your use of delay(10000); is known as a blocking delay; that is, nothing else happens for that duration.

I get the blocking delay. I was happy for everything to stop for a period with the LDEs on as a test that they were still functioning and a visual indicator that every thing was still looping around (all be it paused). I didn't understand though, why the delay was not 10 s. It was shorter and I upped the delay by a few orders of magnitude and got the same result which was odd!

What is odder, to me, is that the setup is on the desk next to me now and it has got to that block (delay) and it's stopped. Been like it for 10s of min, until I press reset. The LEDs are HIGH and the serial monitor has stopped with the last print being "Serial.println(fullLevel, DEC);". I don't understand why it has stopped the next command/s is/are to pull all three LEDs LOW.

Check out this great tutorial by Adafruit.

You're welcome..

Interrupts is like the nervous system, disabling them causes paralysis..

started toying with this a bit then got busy on the phone..

maybe this gives you some ideas..

//global added before setup..
byte stateLeds = 0;
unsigned long lastLed = 0;
unsigned long ledInterval;
unsigned long now = 0;


void LEDs() {

  switch (stateLeds) {
    case 0: Serial.println(fullLevel, DEC);
      //check LEDs
      digitalWrite(ledhi, HIGH);
      digitalWrite(ledmed, HIGH);
      digitalWrite(ledlow, HIGH);
      lastLed = now;
      ledInterval = 10000;
      stateLeds++; //advance to next state
      break;
    case 1: if (now - lastLed >= ledInterval) {
        digitalWrite(ledhi, LOW);
        digitalWrite(ledmed, LOW);
        digitalWrite(ledlow, LOW);
        Serial.print("This is the flowRate value used to set the LEDs flashing, or not = ");
        Serial.println(flowRate);
        stateLeds++;
      }
      break;
    case 2: if (flowRate < 10) {
        if (now - lastLed >= flashPeriod) {
          lastLed = now;
          digitalWrite(ledlow, !digitalRead(ledlow));
        }
      } else {
        digitalWrite(ledlow, LOW);
        stateLeds++;
      }
      break;
    case 3:  //set the RAG level status LEDs
      if (fullLevel == 1) {
        remaining = 100;
      } else {  //this else will need amending to read the US sensor reading in
        remaining = 0;
      }
      Serial.print("This is the value for fullLevel used to set the RAG = ");
      Serial.println(fullLevel);
      Serial.print("This is the value for remaining used to set the RAG = ");
      Serial.println(remaining);
      stateLeds++;
      break;
    case 4:  if (remaining == 100) {
        digitalWrite(ledhi, HIGH);
        digitalWrite(ledmed, LOW);
        digitalWrite(ledlow, LOW);
      }
      if (remaining < 100) {
        digitalWrite(ledhi, LOW);
        digitalWrite(ledmed, HIGH);
        digitalWrite(ledlow, LOW);
      }
      if (remaining < 50) {
        digitalWrite(ledhi, LOW);
        digitalWrite(ledmed, LOW);
        digitalWrite(ledlow, HIGH);
      }
      stateLeds = 0;
      break;
  }
}

simulated here..
you should also wrap a millis timer around flowCal like is done in full, except have it fire off every 10 seconds..

good luck.. ~q

2 Likes

Thank you that's some food for thought. I will have a go with the suggestions here and see if I can get out the hole I have dug.