Problem exiting a while loop of a subroutine/function

Hi there folks. I'm still an amateur programmer and new into Arduino. I have written a a code to read the serial data from the serial port and display it on an LCD. I wrote the 'read' as a function so it will be easier to see and debug. However, there is a 'while-loop' inside the function and I could not find a way to exit that loop so that I return to the main loop(). I have tried few things like putting a return but it doesn't work properly either(it return to main loop but does not read the serial port or vice versa). Besides, I have tried by inserting 'if' to detect a push button so that it jumps to another function but I still failed to get the desired output.

Anyway, here is my code. Any improvement, comments or suggestion is really welcome but of course with a little guide though since I take time to learn new things. I won't ask for a direct solution that you give me the code I need, but an example or two will hopefully enlighten me more.

Thanks!

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 3, 4, 5, 6, 7);

String barcode;
volatile float total = 0.00f;
const byte button = 2;
const byte button2 = 13;
const byte button3 = 12;
int button2state = 0;
int button3state = 0;

void total1 ()
{ 
  lcd.clear();
  lcd.setCursor(0, 1);
  lcd.print("TOTAL: ");
  lcd.setCursor(7, 1);
  lcd.print(total);
}

void checkout ()
{
    lcd.clear();
    lcd.setCursor(0,1);
    lcd.print("CHECKOUT");
          delay(1000);
}

void shopping ()
{

   while(!Serial.available())
  {
     barcode = Serial.readString();  
      if ( barcode == "123")
         {
           float val = 3.99f;
           total = total + val;
    
           lcd.clear();
           lcd.setCursor(0, 2);
           lcd.print("MEGI 400g  RM3.99");
           lcd.setCursor(0, 1);
           lcd.print(val);
         }
    
      else if ( barcode == "122" )
         {
           float val = 4.20f;
           total = total + val;
           
           lcd.clear();
           lcd.setCursor(0, 2);
           lcd.print("COLGATE 320g  RM4.20");
           lcd.setCursor(0, 1);
           lcd.print(val);
         }
         
    else if ( barcode == "124")
         {
           float val = 5.00f;
           total = total + val;
           
           lcd.clear();
           lcd.setCursor(0, 2);
           lcd.print("SOFTLAN 400g  RM5.00");
           lcd.setCursor(0, 1);
           lcd.print(val);
           button2state = digitalRead(button2);
         }
          
      }     
} 


void setup()
{
  pinMode(button3, INPUT);
  pinMode(button2, INPUT);
  pinMode(button, INPUT);
  attachInterrupt(digitalPinToInterrupt(button), total1, CHANGE);
  lcd.begin(20,4);
  lcd.clear();
  delay(1000);
  lcd.setCursor(0,0);
  lcd.print("SUPERMARKET!");
  Serial.begin(9600);
}

void loop()
{
   button2state = digitalRead(button2);
   button3state = digitalRead(button3);
    if (button2state == CHANGE )
    {
     checkout();
    }
    else if (button3state == CHANGE)
    {
      shopping();
    }
  
}
  while ([b][color=red]![/color][/b]Serial.available())

means while nothing is available on the Serial port... then the first thing you try to do is barcode = Serial.readString();?

also

  button2state = digitalRead(button2);
...
  if (button2state == [color=red]CHANGE[/color] )

that does not work like this. a pin will be HIGH or LOW. it does not tell you if it changed. you need to manage this in your code

There seems to be a common denominator to your prices. Scrap the floats for money. Use pennies as the unit, right up to displaying the data on the LCD.

You do NOT need to use Strings. You do NOT need to use Serial.readString(). Learn to use strings (NULL terminated arrays of chars, instead.

Serial input basics - Updated

There is NO reason to use interrupts to read switch states when switches are pressed by humans.

button, button2, and button3 are dumb names. I have no idea what the switches connected to those pins are supposed to do.

  button2state = digitalRead(button2);

    if (button2state == CHANGE )
    {

The state will be HIGH or it will be LOW.

You need to completely restructure your code. You are either shopping, or not. If you are, call shopping() on every pass through loop(), to see if something has been added to the cart. Do NOT use a while loop in shipping, except to read serial data.

You start shopping if the shopping switch BECAME pressed. You start checking out if the checkup switch BECAME pressed.

Look at the state change detection example.

J-M-L:

  while ([b][color=red]![/color][/b]Serial.available())

means while nothing is available on the Serial port... then the first thing you try to do is barcode = Serial.readString();?

I am not sure, but it works that way so that if only I fully scan the barcode, then there will be an output.

also

  button2state = digitalRead(button2);

...
  if (button2state == CHANGE )




that does not work like this. a pin will be HIGH or LOW. it does not tell you if it changed. you need to manage this in your code

But the push button is set to active high in my circuit so if I write it like this

if (button2state == [color=red]HIGH[/color] )[/tt]

Then, wouldn't that I need to continuously press the button so that it stays HIGH?

PaulS:
There seems to be a common denominator to your prices. Scrap the floats for money. Use pennies as the unit, right up to displaying the data on the LCD.

I don't really get this. Care to explain a bit more?

You do NOT need to use Strings. You do NOT need to use Serial.readString(). Learn to use strings (NULL terminated arrays of chars, instead.

Serial input basics - Updated

There is NO reason to use interrupts to read switch states when switches are pressed by humans.

These are merely for testing purpose and not my final codes. I will improve on this in the future.

button, button2, and button3 are dumb names. I have no idea what the switches connected to those pins are supposed to do.

  button2state = digitalRead(button2);

if (button2state == CHANGE )
   {



The state will be HIGH or it will be LOW. 

You need to completely restructure your code. You are either shopping, or not. If you are, call shopping() on every pass through loop(), to see if something has been added to the cart. Do NOT use a while loop in shipping, except to read serial data.

You start shopping if the shopping switch BECAME pressed. You start checking out if the checkup switch BECAME pressed.

Look at the state change detection example.

They are indeed stupid name for button but again it is just for testing purpose. I will definitely make better names next time. What do you mean by every pass through loop? I really don't get this one either.

button2state == CHANGE

wtf are you doing here?

wtf is CHANGE? Oh - here it is, it's a #define meant to be passed to attachInterrupt(). If you dig, you find it's just #defined to 1. So that like is the same as

button2state==HIGH

or

button2state==1

Is that what you're trying to achieve? I suspect it's not, but you haven't really described the intended behavior.

Also, what?

while(!Serial.available())
{
barcode = Serial.readString();

The condition for the while loop is while there's nothing in the serial buffer - and in the loop you read the contents of the serial buffer (honestly, I'm not quite sure how readString() works, and the reference doesn't clarify it much; it gives you a String object, and I don't touch the String class with a ten foot pole.

Is that a 16 x 2 character LCD? Be careful of where it splits the lines, 16 characters per line isn't very many.

And yeah - don't use floats if you can avoid it, and certainly don't use them for money. Floats are not perfectly accurate (read wikipedia entry on floating point numbers), and doing math with them pulls in code and makes the sketch bigger (floating point math on AVRs is a software implementation).

DrAzzy:
button2state == CHANGE

wtf are you doing here?

wtf is CHANGE? Oh - here it is, it's a #define meant to be passed to attachInterrupt(). If you dig, you find it's just #defined to 1. So that like is the same as

button2state==HIGH

or

button2state==1

Is that what you're trying to achieve? I suspect it's not, but you haven't really described the intended behavior.

Also, what?

while(!Serial.available())
{
barcode = Serial.readString();

The condition for the while loop is while there's nothing in the serial buffer - and in the loop you read the contents of the serial buffer (honestly, I'm not quite sure how readString() works, and the reference doesn't clarify it much; it gives you a String object, and I don't touch the String class with a ten foot pole.

Is that a 16 x 2 character LCD? Be careful of where it splits the lines, 16 characters per line isn't very many.

What I mean by change is that if 'there is a change in the pulse(Hi to Lo or vice versa)' then the function will be called. I thought that how it always worked?

while(!Serial.available())
 {
    barcode = Serial.readString();

Since it worked just fine to detect the barcode, so I thought it wouldnt be a problem. I haven't read the link given by PaulS but I'm trying to exit the while loop from the shopping(); function.

It is a 20x4 LCD so no worries.

Listen what many participants tell you:

CHANGE does not work for digitalRead(). what you get is LOW or HIGH. that's it. it won't tell you when it has changed.

while(!Serial.available())
{
barcode = Serial.readString();
Since it worked just fine to detect the barcode

it did not work really... what happens is that when there is nothing to read, you enter your while loop and because Serial.readString reads characters from the serial buffer into a string and the function terminates if it times out.. so it waits there for input.. if you barcode, indeed it will read it, but if you don't then it times out and barcode is a crappy value...

J-M-L:
Listen what many participants tell you:

CHANGE does not work for digitalRead(). what you get is LOW or HIGH. that's it. it won't tell you when it has changed.

it did not work really... what happens is that when there is nothing to read, you enter your while loop and because Serial.readString reads characters from the serial buffer into a string and the function terminates if it times out.. so it waits there for input.. if you barcode, indeed it will read it, but if you don't then it times out and barcode is a crappy value...

Okay gotcha. I'm gonna try the Serial Input Basics by Robin and see if that helps me.

I don't really get this. Care to explain a bit more?

Isn't $4.99 the same as 499 pennies?

You can print 499/100.0 on the LCD, but keep the amounts in pennies, so there are no round-off errors.

cyberjupiter:

There seems to be a common denominator to your prices. Scrap the floats for money. Use pennies as the unit, right up to displaying the data on the LCD.

I don't really get this. Care to explain a bit more?

Floating point maths is not normally used for money because pennies can get lost in the rounding. Integer maths preserves every penny.

You know the old adage "Look after the pennies and the pounds will look after themselves"

...R

Hi there again guys. Regarding the floating points, I will deal with them later. Now, my problem is on reading the number on the serial port. I have followed the example given by Robin2 (Serial Input Basics) but it doesn't work when I send anything on the serial monitor with the option 'no line ending'. I works fine with option 'newline' and I can't figure out why. Besides that, I'm also running a simulation in Proteus and there is no output either on the virtual terminal in the Proteus. I will paste the code in case you need to see it.

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 3, 4, 5, 6, 7);
const byte numChars = 32;
char receivedChars[numChars];
int barcode = 0;

boolean newData = false; // an array to store the received data
void setup() {

  lcd.begin(20,4);
  lcd.clear();
  delay(1000);
  lcd.setCursor(0,0);
  lcd.print("SUPERMARKET!");
    Serial.begin(9600);
    Serial.println("<Arduino is ready>");
}

void loop() {
    recvWithEndMarker();
    showNewNumber();
}

void recvWithEndMarker() {
    static byte ndx = 0;
    char endMarker = '\n';
    char rc;
   
    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

            if (rc != endMarker)
            {
            receivedChars[ndx] = rc;
            ndx++;
            if (ndx >= numChars) 
              {
                ndx = numChars - 1;
              }
            }
            else
            {
            receivedChars[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData = true;
            }
        }
    
}

void showNewNumber() {
  if (newData == true)
  {
        barcode = 0;             // new for this version
        barcode = atoi(receivedChars);
        Serial.print(receivedChars);
        if (barcode == 12)
        {
        
        Serial.print("barcode");
        lcd.setCursor(0,1);
        lcd.print("ITEM 1");
        }
        newData = false;
  }
    
}

I believe I would be alright if I get the virtual terminal to display the whatever I input into it. I have spent few hours trying but to no avail. I clearly need help from the community.

Thanks.

but it doesn't work when I send anything on the serial monitor with the option 'no line ending'.

Well, no. That's because the code explicitly says 'ok! Done!" when it sees the endMarker - '\n'. If the bit of software sending bytes to your serial doesn't put that end marker there, then the code won't know that the line has finished.

How should it work? In terms of bytes coming down the pipe, how should the code know that it has finished a chunk?

I see. But the barcode scanner scan a set of interger in one scan. So how do I tell the arduino that it has finished scanning? Would a delay be a good choice?

cyberjupiter:
I see. But the barcode scanner scan a set of interger in one scan. So how do I tell the arduino that it has finished scanning? Would a delay be a good choice?

Post a link to the datasheet for the scanner so we can see what it is doing in detail. I suspect there are start and end markers.

If there is no other indicator the time interval between messages can be used to determine the end of a message - but that does not mean using the delay() function.

If the scanner always sends the same number of characters that can also be helpful.

...R

Hi there Robin2. Thanks for replying.

First of all, I'm sorry for being ignorant from my first post but after hours of searching the forum, I finally understood the code given by Robin2 and what actually being said by PaulMurrayCbr.

So my problem was that the barcode scanner did not have any marker to indicate the start and end of the data. So yeah I tried by using the time interval method based on someone else code in the forum but I forgot his name, Archh something like that(pardon me). And so it worked. I just need to set an amount of time that indicates the end of the number being scanned. However, I haven't tried to 'do' something with the data to see if it actually blocking my other program or not. I will update again after I try. I have a few questions on the moment only.

Initially, my post was about the problem in exiting the while loop. So it was because I was using Serial.readString(); right? It is blocking my code right so thats why I cannot exit the loop? I hope this get answered.

The second question is that, how if I was to write a code that doesn't indicate the end of data being received by the serial port? Aside, from using the time interval, what choice do I have?

The last question is that whats the difference between using 'while' and 'if' on (Serial.available > 0) ?

And thanks to everyone who responded. The community comes in very handy.

cyberjupiter:
So it was because I was using Serial.readString(); right? It is blocking my code right so thats why I cannot exit the loop?

Probably

The second question is that, how if I was to write a code that doesn't indicate the end of data being received by the serial port? Aside, from using the time interval, what choice do I have?

You have not responded to my request to post a link to the datasheet for the scanner so I don't know the answer.

The last question is that whats the difference between using 'while' and 'if' on (Serial.available > 0) ?

Just the ordinary English language difference between WHILE and IF. IF checks once and does or does not do something. WHILE creates a loop that repeats until the check is false.

...R

I am sorry Robin but I do not have datasheet for barcode scanner. So, it was just a random question in case I encounter something like this again. I was just wondering if there is a better choice other than time interval for cases like this.

And in the case of the barcode scanner, would a

while (Serial.available > 0)

works better than the 'if' one. I want the barcode scanner to scan by only having a 1 second timeout so numbers that come after that is ignored.

A suggestion is really welcome.

Thanks again.

Edit:

I have completed a part of my program and bumped into a problem again. I will show my code first.

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 3, 4, 5, 6, 7);

const unsigned long timetowait = 1000;
const byte buffersize = 20;
const byte buttonshop = 12;

int but_shopstate = 0;
int lastButtonState = 0;

char inByte;
char barcode[buffersize + 1];
unsigned long lastchar = 0;
boolean stringstarted = false;
byte index = 0;

int serialbar = 0;

void setup()
{
  pinMode(buttonshop, INPUT);
  lcd.begin(20,4);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("AIMAN");
  delay(500);
  Serial.begin(9600);
  Serial.println("begin");
}

void processbarcode ()
{
   if (stringstarted && millis() - lastchar >= timetowait)
   {
     barcode[index] = '\0';
     serialbar = atoi(barcode);
  
     if (serialbar == 123)
     {
      lcd.clear();
      lcd.setCursor(0,1);
      lcd.print("HELLO WAY");
     }
     
     else if (serialbar == 125)
     {
      lcd.clear();
      lcd.setCursor(0,1);
      lcd.print("HELLO HELL");
     }

    index = 0;
    barcode[0] = '\0';
    stringstarted = false;
   }
}  

void readserial()
{
  while (Serial.available() > 0 )
  {
    inByte = Serial.read();
    stringstarted = true;
    lastchar = millis();
    if(index < buffersize)
    {
      barcode[index++] = inByte;
    }
  }
}

void loop ()
{
  but_shopstate = digitalRead(buttonshop);

  if(but_shopstate != lastButtonState)
  {
     if (but_shopstate == HIGH)
     {
       Serial.print("SHOP");
       readserial();
       processbarcode();   
     }
     else
     {
     }

  delay(50);
  }
  
  lastButtonState = but_shopstate;
  
}

The code works fine if I don't put any state detection change. However when they are included,

readserial();
processbarcode();

are not executed. How to fix this?

Hi there again.

I have fixed the problem. It is executing the function but it needs to be in a while loop.

Then I realized something. I am not actually getting any progress. I am still bugged by the problem posted in my first post.

My code still doesn't break out of the while loop again. Aside from learning to use char terminating strings, I did not really advance my code. I am BAMMED with the same problem over again.

I have spent days fixing the string problem and then nothing actually get fixed. I appreciate that I learn new things even though I am a slow-learner but this doesn't get me anywhere. I am not complaining or anything but just maybe a a clear answer would help.

Thanks again.

My code still doesn't break out of the while loop again.

You've mode code changes. That means that you need to post the current code.