Notifications
Clear all

Tips for Noobs #2 - Failing for and while loops

12 Posts
4 Users
0 Likes
1,950 Views
NewburyPi
(@dale)
Member
Joined: 5 years ago
Posts: 97
Topic starter  

Just passing on this bit of knowledge, in hopes that I can save some other noob a headache or two.

After sending about an hour wondering if my mind had finally decided to leave me all together. I simply wanted to find out how quickly my Nano could count out a 32 bit integer. The code below worked just fine, but when I took out the time consuming "Serial.print" statement, it failed.

uint32_t i = 1;
uint8_t j;

void setup() {
Serial.begin(9600);
Serial.print("\nStarting at: ");
Serial.println(i, HEX);
while (i != 0) { // Start counting up and see how long it takes
i++;
Serial.println(i, HEX);
}
Serial.print("\nEnding at: ");
Serial.println(i, HEX);
}

void loop() {
}

I thought "well, maybe I need to do something more inside the loop" as I did just take something out. So I added:

  while (i != 0) { // Start counting up and see how long it takes
i++;
j = 2*i;
}

to no avail 🙁      So lets ask the interwebs and see what it has to say.

There were plenty of people who used "=" instead of "==" in there test condition (I still go over any code to check, before I run), and a few others that interpreted the condition as "loop until true" instead of "loop while true" However, nothing that met my circumstances.

Then the magic word came to me. "SCOPE" Was my "i" "in scope." Well.. Yes. But no! As a global variable, it was there to be accessed, but did the variable have the right data. I recently wrote some code that passed a global variable to an interrupt routine. I needed to declare the variable as "volatile," in order for the interrupt to access up-to-date, correct data. Long story shorter, that was the fix (as below)

volatile uint32_t i = 1;

void setup() {
Serial.begin(9600);
Serial.print("\nStarting at: ");
Serial.println(i, HEX);
while (i != 0) { // Start counting up and see how long it takes
i++;
}
Serial.print("\nEnding at: ");
Serial.println(i, HEX);
}

It seems that if you access the variable within the loop it gets updated, but if you test the variable before accessing you get old(?) data.

I just tested the snippet below and It also works, as I access the data before testing.

  do{
i++;
}while (i != 0);

I'm wondering if the increment is performed correctly. Oh well learning is fun.

Hope this helps someone.

--
Dale


   
Quote
(@starnovice)
Member
Joined: 5 years ago
Posts: 110
 

That is puzzling.  I would not think you would have to use volatile and it makes me think the implementation is not correct. Because it works with the Serial.print, I wonder if there is some code optimizing going on without the Serial.print.

Pat Wicker (Portland, OR, USA)


   
ReplyQuote
NewburyPi
(@dale)
Member
Joined: 5 years ago
Posts: 97
Topic starter  

Once my 32 bit timing test completes, I'll run a build in verbose and see if there are any clues. 

--
Dale


   
ReplyQuote
(@starnovice)
Member
Joined: 5 years ago
Posts: 110
 

@dale

Just to be clear by "implementation" I was agreeing with you that it sounds like a compiler bug.

Pat Wicker (Portland, OR, USA)


   
ReplyQuote
NewburyPi
(@dale)
Member
Joined: 5 years ago
Posts: 97
Topic starter  

There seems to be a lot of problems with loops posted on the Arduino Forum, but nothing that seems to match my issue. I continue to hunt.

 

Oh and by the by... the "do{  }while()" part I posted may be a red herring. I can't remember if I removed the volatile 🙁

--
Dale


   
ReplyQuote
NewburyPi
(@dale)
Member
Joined: 5 years ago
Posts: 97
Topic starter  

@starnovice

Yep. I agree. but only from a position of partial ignorance. I've posted to the Arduino forum, and am hoping for clarification. 

--
Dale


   
ReplyQuote
NewburyPi
(@dale)
Member
Joined: 5 years ago
Posts: 97
Topic starter  

FAHC: 2 Hours, 28 Minutes, 34.206 Seconds 👍 

--
Dale


   
ReplyQuote
byron
(@byron)
No Title
Joined: 5 years ago
Posts: 1122
 
Posted by: @dale

but when I took out the time consuming "Serial.print" statement, it failed.

As you increment the variable integer until it overflows, then, I believe the result of this overflow is undefined.  Maybe you have just found what this undefined behaviour does in your program. 😎 


   
ReplyQuote
NewburyPi
(@dale)
Member
Joined: 5 years ago
Posts: 97
Topic starter  

@byron

Yes. I'd agree with you, if it took the two and a half hours to get to the undefined state. However, as seen below, it would have to complete the 4, 294, 967, 295 increments in 34 mS. 

13:58:46.738 -> 
13:58:46.738 -> Starting at: 1
13:58:46.772 ->
13:58:46.772 -> Ending at: 0

That would be asking the Nano clone to have a 1.2 pico-second cycle time 🙂

Also, I rechecked the "do{  }while()" version, and it fails in the same manner. 

--
Dale


   
ReplyQuote
(@jbeazy)
Member
Joined: 5 years ago
Posts: 18
 

This is what I think happened.  Think of the point just before you enter the while loop as State A and just after the loop State B.  If variable i is not being utilized within the loop the compiler says "why bother with millions of clock cycles when the end result state B is the same as one or a few cycles." So it optimizes it out. With your print statement i was being utilized.  With your j=2*i statement j is not being utilized so i isn't either. Again it optimizes.   When you declare volatile it tells the compiler not to get cute and optimize just do what you told it to.  I think that is why we use it with ISRs also because the compiler may think the variable won't get used and optimizes.


   
ReplyQuote
NewburyPi
(@dale)
Member
Joined: 5 years ago
Posts: 97
Topic starter  

@jbeazy

Yes. That is the similar to the answer I received from Arduino. As you say

If variable i is not being utilized

Arduino gave me

the optimizer optimized out the while loop because the variable i wasn't used anywhere that it's value wasn't predictable

So I re-ran the code with a few things added to the loop and

uint32_t i;
uint8_t ctr;

int function(int value){
value *= value;
return(value);
}

void setup() {
Serial.begin(9600);
i = 1;
ctr = 2;
Serial.print("\nBefore loop, i = ");
Serial.println(i, HEX);

do{
i++;
<insert code snippet from below>
}while (i != 0);

Serial.print("Loop has exited and i = ");
Serial.println(i, HEX);
}

Code Snippets:

ctr = function(i);

Per the above sketch, this snippet forces the loop to remain because the value of "i" is "utilized" to determine the output of function(). The details of what the function might do to/with "i" are unknown, therefore we better pass 'i" in to the function. AKA don't optimize out the loop.

ctr = sq(i); 

This snippet also worked. While this is a built in function, I'm assuming that "i" needs to be passed anyway. Maybe because someone may overload the SQ() function.

digitalWrite(3, i); 

Not sure what happened here but the loop did not run (loop optimized out). We are passing "i" to a function again, as in the first two snippets. Perhaps as the function does not return a value, and therefore has no direct impact on software flow, the optimizer assumes it can remove the loop.

i = sq(ctr); 
or
i = !digitalRead(3);
or
i = 1 + random();

OK... These three snippets also worked. In these cases, "i" is not "utilized" by the the snippet. However, the value of "i" is being modified in each case and the modification of the value could change the software flow through the loop. (Per Arduino) The value of "i" was no longer predictable, thus the loop had to remain.

Anyway... That was fun (said the 65 yo nerd). I'm not sure I'll remember these details, but it will come back to me when I run into a problem like this. 

--
Dale


   
ReplyQuote
NewburyPi
(@dale)
Member
Joined: 5 years ago
Posts: 97
Topic starter  

Sorry. Clarification time again. Seems that

digitalWrite(3, i); 

does actually ensure the loop is not optimized away. 

--
Dale


   
ReplyQuote