Variable not changing unless I use Serial.print

Hey there,

I’ve been working on this project which is a automatic cocktail mixer. I’ve chosen a valve and flow meter system where 4 valves and 4 flow meters are used for 4 ingredients. So basically, when I ask for 2oz of the first ingredients, the command a2 is sent through the serial port to the Arduino. a = valve 1, b = valve 2, etc… the number afterwards is the amount of liquid I want in oz.

Here is the problem I’m having: my function that opens the valve, waits for the correct amount to flow through and closes the valve doesn’t work unless I use Serial.print. More precisely, my variable that saves the total milliliters poured does not change value unless I print it during the while() loop. So the function works as intended when the Serial.print is there.

The expected result is as followed:
I give it a amount in milliliters, which was converted from oz before the function, with a character representing the valve that needs to be opened. When that valve opens and liquid starts flowing, I start counting the pulses from the flow meter with an interrupt on pin 2. Once the amount desired is poured, I close the valve and send a 0 through serial communication to indicate success.

This is what I get when Serial.Print is used:

This is what happens when Serial.Print isn’t used:

So the result is at the end of the function, the totalMilliliters is still at 0.

Sorry if some french words made it into the coding, I’m studying at a french school so our stuff has to be done in french. Will be converting it from English to French when it works.

int FlowMeterAmmount(float ammount, char valve)
{
    if (UTILISE_PRINT == true)
    {
        Serial.print("Demande: ");
        Serial.print(ammount);
        Serial.println("ml");
    }
    
    unsigned long totalMilliLitres = 0;
    //This variables purpose is to check if 3 seconds has passed since there has been no flow of liquid. The FlowMeterAmmount function will then close the valve if that's the case.
    unsigned long debitNullTemps = millis();

    pulseCount = 0;
    oldPulseCount = 0;

    //Since I'm using multiplexing for my 4 flow meters, this function is to select the multiplex output to correspond with the valve I'm opening.
    choisirDebitMetre(valve);

    //If I'm using the glass detector feature for my project, then attach the interrupt to the pin. For my tests, I will not be using the glass detector.
    if (UTILISE_VERRE)
    {
        attachInterrupt(sensorInterruptVerre, verrifierVerre, FALLING);
    }

    //Start the interrupt to start counting the pulses.
    attachInterrupt(sensorInterrupt, pulseCounter, FALLING);

    //Keep at it until the ammount of liquid desired has been reached.
    while (totalMilliLitres <= ammount)
    {
        //Function to open the correct valve.
        ControllerValve(valve, OPEN);

        //This variable is initialised at the beginning and serves to enable or disable features of my project. In this case, a glass detector will be installed where the liquid gets poured out. For my tests, it isn't used.
        if (UTILISE_VERRE == true)
        {
            verrifierVerre(valve);
        }

        //If there's been an increment in the number of pulses, reset the no flow time variable to the current millis();.
        if (pulseCount > oldPulseCount)
        {
            debitNullTemps = millis();
        }


        //Multiply the number of pulses by 3.30. This is because the flow meters I'm working with have about 3.30ml of liquid per pulse.
        totalMilliLitres = pulseCount * 3.30;
        oldPulseCount = pulseCount;
        
        //Another variable is initialised at the beginning and serves to enable or disable the use of Serial.Print. For my project, it needs to be disabled because I'm using a serial communication between a raspberry pi and an arduino. But for my test, I'm using the serial monitor included in the arduino interface on my Laptop.
        if (UTILISE_PRINT == true)
        {
           Serial.print(totalMilliLitres);
           Serial.println("ml"); 
        }

        //If there hasn't been any flow for the pass 3 seconds.
        if((millis() - debitNullTemps) > 3000)
        {
            stopValves();

            
            if (UTILISE_PRINT == true) {
                Serial.println("Flow Rate at 0, shutting all valves");
                Serial.println("Sending 1 to Serial Port");
                Serial.print("Pulses: ");
                Serial.println(pulseCount);
            }

            //Verification for how much liquid has been poured before the error.
            Serial.print("total millilitres: ");
            Serial.println(totalMilliLitres);
            //Function to send the error codes to the serial port for the raspberry pi.
            RetourCodeErreur('1');
            return 1;

        }
        //A small delay to be able to rack up some pulses from the flow meters.
        delay(250);
    }

    if (UTILISE_PRINT == true) {
        Serial.println("Sending 0 to Serial Port");
        Serial.print("Pulses: ");
        Serial.println(pulseCount);
    }

    //Close all valves
    stopValves();
    //Stop counting pulses.
    detachInterrupt(sensorInterrupt);

    if (UTILISE_VERRE)
    {
    //Arrêter de vérifier pour le verre
    detachInterrupt(sensorInterruptVerre);
    }
    //Send a confirmation that the liquid has been poured correctly.
    RetourCodeErreur('0');
    return 0;
}

Thank you in advance for reading into my problem!

Zach

Since the links for the images didn't work, here are the links:

Not using Serial.Print: SerialMonitorResult.PNG - Google Drive

Using Serial.Print: SerialMonitorExpected.PNG - Google Drive

Please post your complete code.

Haven't had time to clean up the whole thing for it all to look nice but here it is. Also it will be posted in 2 different post since it exceeds the maximum allowed length for messages.

Part 1:

#define ValveA 4
#define ValveB 5
#define ValveC 6
#define ValveD 7

#define LEDValveA A0
#define LEDValveB A1
#define LEDValveC A2
#define LEDValveD A3

#define LEDVerre A4

#define OPEN HIGH
#define CLOSE LOW

#define UTILISE_VERRE false
#define UTILISE_PRINT true

//Pins pour la selection du débitmètre
byte selectionDebitA = 10;
byte selectionDebitB = 9;
byte selectionDebitC = 8;

//Variables pour le contrôle des valves.
bool first_valve_state = false;
bool second_valve_state = false;
bool third_valve_state = false;
bool fourth_valve_state = false;

//Bool qui indique si un ingredient est en train d'être servi.
bool etatDeService;

//Variables pour la communication serielle
char CommandeSerielle;
float MontantSerielle;
char codeErreurRecu;

float ammount_ml;

//Variables pour le Flow Meter.
byte statusLed    = 13;

byte sensorInterrupt = 0;  // 0 = digital pin 2
byte sensorPin       = 2;


byte sensorInterruptVerre = 1;  // 0 = digital pin 2
byte sensorPinVerre       = 3;


// The hall-effect flow sensor outputs approximately 4.5 pulses per second per
// litre/minute of flow.
float calibrationFactor = 4.3;

int pulseCount;
int oldPulseCount;

float flowRate;




//******************************************************************************************************
//INITIALISATION
//******************************************************************************************************

void setup() {
    // put your setup code here, to run once:
    pinMode(ValveA, OUTPUT);
    pinMode(ValveB, OUTPUT);
    pinMode(ValveC, OUTPUT);
    pinMode(ValveD, OUTPUT);
    Serial.begin(9600);

    //Initialiser les pins pour les leds.
    pinMode(LEDValveA, OUTPUT);
    pinMode(LEDValveB, OUTPUT);
    pinMode(LEDValveC, OUTPUT);
    pinMode(LEDValveD, OUTPUT);

    pinMode(LEDVerre, OUTPUT);

    //Initialiser les pins à utiliser pour la selection du Débitmètre dans le multiplexer.
    pinMode(selectionDebitA, OUTPUT);
    pinMode(selectionDebitB, OUTPUT);
    pinMode(selectionDebitC, OUTPUT);

    // Set up the status LED line as an output
    pinMode(statusLed, OUTPUT);
    digitalWrite(statusLed, HIGH);  // We have an active-low LED attached

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

    //Initialiser la sélection des débitmètres pour le débitmètre A
    digitalWrite(selectionDebitA,HIGH);
    digitalWrite(selectionDebitB,HIGH);
    digitalWrite(selectionDebitC,LOW);

    //pinMode(sensorPinVerre, INPUT);
    //digitalWrite(sensorPinVerre, HIGH);

    pulseCount        = 0;
    codeErreurRecu    = '0';

    // The Hall-effect sensor is connected to pin 2 which uses interrupt 0.
    // Configured to trigger on a FALLING state change (transition from HIGH
    // state to LOW state)
    attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
    if (UTILISE_VERRE == true)
    {
        attachInterrupt(sensorInterruptVerre, verrifierVerre, FALLING);
    }
    if (UTILISE_PRINT == true)
    {
        Serial.println("Initialisation termine");
    }
}



//******************************************************************************************************
//LOOP
//******************************************************************************************************

void loop() {
    //Serial.println(digitalRead(sensorPinVerre));

    detachInterrupt(sensorInterruptVerre);
    //S'assurer que le compteur pour le  montant me millilitre versé soit initialiser à 0 avant chaque commande.
    if (Serial.available())
    {
        CommandeSerielle = Serial.read();
        MontantSerielle = Serial.parseInt();
        CommandeValve(CommandeSerielle, MontantSerielle);
    }
}

//======================================================================================================
//Envoyer les codes d'erreurs au Raspberry Pi.
//======================================================================================================
void RetourCodeErreur(char code)
{
    Serial.write(code);
}


//======================================================================================================
//Analyser la commande reçu et agir correctement.
//======================================================================================================

void CommandeValve(char commande, float MontantOunces)
{
    if (commande == 'a' || commande == 'b' || commande == 'c' || commande == 'd')
    {


        MontantOunces = MontantOunces/0.033814;

        etatDeService = true;
        FlowMeterAmmount(MontantOunces, commande);
        stopValves();
        etatDeService = false;
    }

    if (commande == 'v')
    {
        if (MontantOunces == 0)
        {
            if (UTILISE_PRINT == true) {
                Serial.println("Valves seront vidées");
            }

            FlowMeterEmpty('a');
            ControllerValve('a', CLOSE);

            FlowMeterEmpty('b');
            ControllerValve('b', CLOSE);

            FlowMeterEmpty('c');
            ControllerValve('c', CLOSE);

            FlowMeterEmpty('d');
            ControllerValve('d', CLOSE);
        }
        if (MontantOunces == 1)
        {
            if (UTILISE_PRINT == true) {
                Serial.println("Valve A sera vidée");
            }
            ControllerValve('a', OPEN);
            FlowMeterEmpty('a');
            ControllerValve('a', CLOSE);
        }
        if (MontantOunces == 2)
        {
            if (UTILISE_PRINT == true) {
                Serial.println("Valve B sera vidée");
            }
            ControllerValve('b', OPEN);
            FlowMeterEmpty('b');
            ControllerValve('b', CLOSE);
        }
        if (MontantOunces == 3)
        {
            if (UTILISE_PRINT == true) {
                Serial.println("Valve C sera vidée");
            }
            ControllerValve('c', OPEN);
            FlowMeterEmpty('c');
            ControllerValve('c', CLOSE);
        }
        if (MontantOunces == 4)
        {
            if (UTILISE_PRINT == true) {
                Serial.println("Valve D sera vidée");
            }
            ControllerValve('d', OPEN);
            FlowMeterEmpty('d');
            ControllerValve('d', CLOSE);
        }
    }
}

Part 2

//------------------------------------------------------------------------------------------------------
//Arrêter valves
//------------------------------------------------------------------------------------------------------
void stopValves()
{
    digitalWrite(ValveA, CLOSE);
    digitalWrite(ValveB, CLOSE);
    digitalWrite(ValveC, CLOSE);
    digitalWrite(ValveD, CLOSE);
}

//------------------------------------------------------------------------------------------------------
//Controller les valves
//------------------------------------------------------------------------------------------------------

void ControllerValve(char valve, bool commande)
{
    if (valve == 'a')
    {
        stopValves();
        digitalWrite(ValveA, commande);
        digitalWrite(LEDValveA, commande);
    }
    else if (valve == 'b')
    {
        stopValves();
        digitalWrite(ValveB, commande);
        digitalWrite(LEDValveB, commande);
    }
    else if (valve == 'c')
    {
        stopValves();
        digitalWrite(ValveC, commande);
        digitalWrite(LEDValveC, commande);
    }
    else if (valve == 'd')
    {
        stopValves();
        digitalWrite(ValveD, commande);
        digitalWrite(LEDValveD, commande);
    }
}

//======================================================================================================
//Flow Meter Ammount - Faire couler un ingredient
//======================================================================================================

int FlowMeterAmmount(float ammount, char valve)
{
    if (UTILISE_PRINT == true)
    {
        Serial.print("Demande: ");
        Serial.print(ammount);
        Serial.println("ml");
    }
    
    unsigned long totalMilliLitres = 0;
    //This variables purpose is to check if 3 seconds has passed since there has been no flow of liquid. The FlowMeterAmmount function will then close the valve if that's the case.
    unsigned long debitNullTemps = millis();

    pulseCount = 0;
    oldPulseCount = 0;

    //Since I'm using multiplexing for my 4 flow meters, this function is to select the multiplex output to correspond with the valve I'm opening.
    choisirDebitMetre(valve);

    //If I'm using the glass detector feature for my project, then attach the interrupt to the pin. For my tests, I will not be using the glass detector.
    if (UTILISE_VERRE)
    {
        attachInterrupt(sensorInterruptVerre, verrifierVerre, FALLING);
    }

    //Start the interrupt to start counting the pulses.
    attachInterrupt(sensorInterrupt, pulseCounter, FALLING);

    //Keep at it until the ammount of liquid desired has been reached.
    while (totalMilliLitres <= ammount)
    {
        //Function to open the correct valve.
        ControllerValve(valve, OPEN);

        //This variable is initialised at the beginning and serves to enable or disable features of my project. In this case, a glass detector will be installed where the liquid gets poured out. For my tests, it isn't used.
        if (UTILISE_VERRE == true)
        {
            verrifierVerre(valve);
        }

        //If there's been an increment in the number of pulses, reset the no flow time variable to the current millis();.
        if (pulseCount > oldPulseCount)
        {
            debitNullTemps = millis();
        }


        //Multiply the number of pulses by 3.30. This is because the flow meters I'm working with have about 3.30ml of liquid per pulse.
        totalMilliLitres = pulseCount * 3.30;
        oldPulseCount = pulseCount;
        
        //Another variable is initialised at the beginning and serves to enable or disable the use of Serial.Print. For my project, it needs to be disabled because I'm using a serial communication between a raspberry pi and an arduino. But for my test, I'm using the serial monitor included in the arduino interface on my Laptop.
        if (UTILISE_PRINT == true)
        {
           Serial.print(totalMilliLitres);
           Serial.println("ml"); 
        }

        //If there hasn't been any flow for the pass 3 seconds.
        if((millis() - debitNullTemps) > 3000)
        {
            stopValves();

            
            if (UTILISE_PRINT == true) {
                Serial.println("Flow Rate at 0, shutting all valves");
                Serial.println("Sending 1 to Serial Port");
                Serial.print("Pulses: ");
                Serial.println(pulseCount);
            }

            //Verification for how much liquid has been poured before the error.
            Serial.print("total millilitres: ");
            Serial.println(totalMilliLitres);
            //Function to send the error codes to the serial port for the raspberry pi.
            RetourCodeErreur('1');
            return 1;

        }
        //A small delay to be able to rack up some pulses from the flow meters.
        delay(250);
    }

    if (UTILISE_PRINT == true) {
        Serial.println("Sending 0 to Serial Port");
        Serial.print("Pulses: ");
        Serial.println(pulseCount);
    }

    //Close all valves
    stopValves();
    //Stop counting pulses.
    detachInterrupt(sensorInterrupt);

    if (UTILISE_VERRE)
    {
    //Arrêter de vérifier pour le verre
    detachInterrupt(sensorInterruptVerre);
    }
    //Send a confirmation that the liquid has been poured correctly.
    RetourCodeErreur('0');
    return 0;
}





//======================================================================================================
//Vider un ingredient.
//======================================================================================================

int FlowMeterEmpty(char valve)
{
    
}

/*
 * Interrupt Service Routine
 */


//------------------------------------------------------------------------------------------------------
//Interruption du verre
//------------------------------------------------------------------------------------------------------
//Returns 0 if continue drink pouring and 1 to cancel

int verrifierVerre(char valve)
{
    digitalWrite(LEDVerre, HIGH);
    stopValves();
    bool uneFois = false;
    //RetourCodeErreur('2');
    delay(1000);
    while (digitalRead(sensorPinVerre) == LOW)
    {
        if (uneFois == false)
        {
            uneFois = true;
            if (UTILISE_PRINT == true) {
                Serial.println("SVP remettre verre");
            }
        }
    }
    digitalWrite(LEDVerre, LOW);
    if (UTILISE_PRINT == true) {
        Serial.print("Verre remis, retour a la recette");
    }
}

//Compter un pulse
void pulseCounter()
{
    pulseCount++;
}


void choisirDebitMetre(char valve)
{
    if (valve == 'd')
    {
        digitalWrite(selectionDebitA,LOW);
        digitalWrite(selectionDebitB,LOW);
        digitalWrite(selectionDebitC,LOW);
        //Serial.println("Débitmètre D choisi");
    }
    if (valve == 'c')
    {
        digitalWrite(selectionDebitA,HIGH);
        digitalWrite(selectionDebitB,LOW);
        digitalWrite(selectionDebitC,LOW);
        //Serial.println("Débitmètre C choisi");
    }
    if (valve == 'b')
    {
        digitalWrite(selectionDebitA,LOW);
        digitalWrite(selectionDebitB,HIGH);
        digitalWrite(selectionDebitC,LOW);
        //Serial.println("Débitmètre B choisi");
    }
    if (valve == 'a')
    {
        digitalWrite(selectionDebitA,HIGH);
        digitalWrite(selectionDebitB,HIGH);
        digitalWrite(selectionDebitC,LOW);
        //Serial.println("Débitmètre A choisi");
    }
}

Any global variable that is accessed from both ISR and non-ISR code must be declared volatile. Otherwise the compiler might optimize you into trouble.

Also, if you're running this on an 8-bit processor, your accesses of 16-bit (and larger) volatile variables in non-ISR code won't be atomic. You most protect those accesses with noInterrupt() / interrupt() pairs. The best thing to do is copy them to a local variable and then get the interrupts back on.

Also, you're using this function as an ISR:

int verrifierVerre(char valve) {
.
.
}

An ISR should neither take nor return a value.

Just as bad, you're using delay() within the ISR!!!

Declaring my pulseCount as a volatile variable worked! Thanks!

Also thanks for pointing out flaws with my other function for the other ISR. I haven't worked on that function too much so far now but knowing these things will help for when I get to it!

I just started working with interrupts with programming, so I'll go do some research so I don't make these kind of mistakes again.

Thanks.