Go Down

Topic: Demonstration code for several things at the same time (Read 115104 times) previous topic - next topic

el_supremo

Hey, no, don't give up on switches!
'if' statements can go haywire too if you forget an 'else'! :)

Pete
Don't send me technical questions via Private Message.

Robin2

@CodingBadly and @PeterH,

I think I now see the point you are making. I wrote a couple of spreadsheets to experiment with numbers.

I then set out to modify my sketch but I've run into a very strange problem. All of the functions work fine your way ( += interval) EXCEPT the servo function. After a short period it goes haywire, yet it works perfectly using " = currentMillis".

I think the problem may be due to the much shorter interval (or may be showing up sooner because of the shorter interval). And I think the problem arises because at some stage prevMillis exceeds currentMillis so that currentMillis - prevMillis gives a "negative" number or, since it is an unsigned long, a very large positive number which always satisfies the if statement.

I've had enough for today. I will experiment more tomorrow.

I suspect that the error associated with doing it the "wrong" way (i.e. "= currentMillis") depends on the ratio between the interval between successive values of millis() and the size of interval for the blinks (or whatever). If the blink interval is relatively large the error may not matter. I haven't explored this with my spreadsheet yet.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

PeterH


I suspect that the error associated with doing it the "wrong" way (i.e. "= currentMillis") depends on the ratio between the interval between successive values of millis() and the size of interval for the blinks (or whatever). If the blink interval is relatively large the error may not matter. I haven't explored this with my spreadsheet yet.


The basic problem is that the code may not evaluate the condition at the precise instant that the interval has elapsed. Suppose for the sake of argument that you were trying to take some action every 100ms and your loop took 5ms to execute. The original code would take up to 5ms to notice that the interval had elapsed. Since the next interval is calculated based on the value of millis() at the point where we notice the interval elapsed, this means the next interval will be delayed by up to 5ms too. In effect, any latency noticing that the condition is true will cause all subsequent processing to slip.

The preferred approach always schedules the next event based on when the current event became due, not when the code noticed that it had occurred. This avoids slippage when there is any latency noticing the event.
I only provide help via the forum - please do not contact me for private consultancy.

Robin2

I eventually figured out the problem while lying in bed.

The problem arises in the servo function because I change the interval after every sweep. As initially written "prevMillis = currentMillis" is at the bottom of the function after the interval has been changed. And when the revised version "prevMillis += interval" is used at the same location it erroneously adds the revised interval. When there is a switch from a short to a long interval that means the prevMillis is actually greater than the next version of currentMillis which gives a "negative" answer to the subtraction (actually a large +ve number) which always evaluates to true.

The solution to that problem is to move "prevMillis += interval" to a position before the interval is changed.

HOWEVER .... (and I'm delighted that this problem gave me time to look at the bigger picture)

FIRST ... @CodingBadly said in Reply #11 that I was assuming that delta (the difference between successive values of millis()) was 1. Of course, as he suspected, I hadn't thought about that at all. BUT ... millis() does in fact increment by 1.

SECOND ... (though, I had forgotten) The code in "BlinkWithouDelay" uses "previousMillis = currentMillis; "

MY CONCLUSION ... is that this is a discussion about angels on the head of a pin. So, in the absence of evidence of a real problem I will not post an updated version of my demo sketch. The present version works and is consistent with BlinkWithoutDelay.

I am wondering if I might write a short accompanying essay that covers the points raised here (this one, the use of Switch and the maintaining State in static local variables).

As always, comments are welcome.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Coding Badly

#19
Mar 11, 2014, 08:45 am Last Edit: Mar 11, 2014, 08:49 am by Coding Badly Reason: 1
BUT ... millis() does in fact increment by 1.


There you go again making assumptions.  This time you assume I made a mistake (and failed to test your assumption).

What about when the processor is running at 8 MHz?  What about when the fractional part reaches a whole number?

Coding Badly

MY CONCLUSION ... is that this is a discussion about angels on the head of a pin.


Uh huh.  Except, of course, when a drifting clock is undesirable.

Robin2


BUT ... millis() does in fact increment by 1.


There you go again making assumptions.  This time you assume I made a mistake (and failed to test your assumption).

What about when the processor is running at 8 MHz?  What about when the fractional part reaches a whole number?




I made no assumptions, certainly not about your coding ability. I wrote a short sketch to test millis() on my Uno. I have another 328 which I could (maybe already have) set up for 8MHz but I haven't tested that. I also have some Attiny 45's that I could set up for 1MHz. Most beginners for whom my demo may be useful are likely to be using an Uno at 16MHz.

If you know what different increments of millis() are possible in different circumstances I would appreciate it if you would tell me, rather than teasing with questions.

As regards the drifting clock - I started this to produce a more extensive example of "BlinkWithoutDelay" - nothing more. I expect that someone who needs better performance will also know how to get it, or will start a new Thread about it. I don't see any need to complicate matters for newbies. It would be another thing entirely if my code did not do what it claims (and I believe it does).

None of this means that I have not been interested in your comments - I will certainly bear them in mind for my own projects.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

PeterH


MY CONCLUSION ... is that this is a discussion about angels on the head of a pin. So, in the absence of evidence of a real problem I will not post an updated version of my demo sketch. The present version works and is consistent with BlinkWithoutDelay.


I disagree completely - your conclusion is wrong. The technique used in your example is flawed and is vulnerable to slip. The classic 'blink without delay' makes the same mistake (as well as others). It is quite a common mistake, and the fact that it's taking so long to explain it to you demonstrates that it's quite a subtle problem - but it is still a problem.

The problem is that the timing will be subject to cumulative slip when there is any latency between the timed condition becoming true, and the code to detect that executing. As soon as that latency exceeds 1ms, the time of the next scheduled event will be wrong, and so on for all subsequent events. The timing will have slipped by a small amount. It will keep doing this every time there is any latency. There may be some situations where that doesn't matter, and it's even possible to design a sketch that relies on that behaviour, but as a general approach for making something happen at regular intervals this is a problem.

Fortunately the solution is easy: schedule the next event based on when the current event was due, not when it was detected.
I only provide help via the forum - please do not contact me for private consultancy.

Coding Badly

#23
Mar 11, 2014, 06:23 pm Last Edit: Mar 11, 2014, 06:26 pm by Coding Badly Reason: 1
If you know what different increments of millis() are possible in different circumstances I would appreciate it if you would tell me, rather than teasing with questions.


Uno running at 16 MHz...

Code: [Select]

void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "Five seconds to lift off..." ) );
}

static unsigned long Mark;
static unsigned long Previous;
static unsigned short Histogram[256];

void loop()
{
  unsigned long Current;
  unsigned long Delta;
 
  Current = millis();

  Delta = Current - Previous;

  if ( Delta != 0 )
  {
    Previous = Current;

    if ( Delta < 256 )
    {
      ++Histogram[(unsigned char)Delta];
    }
    else
    {
      ++Histogram[255];
    }
   
    return;
  }

  if ( Current - Mark >= 5000ul )
  {
    for ( int i = 0; i < 256; ++i )
    {
      Serial.print( i );
      Serial.write( '\t' );
      Serial.println( Histogram[i] );
    }
    Serial.println();
    while ( true );
  }
}


Code: [Select]
Five seconds to lift off...
0 0
1 4766
2 117
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0
11 0
12 0
13 0
14 0
15 0
16 0
17 0
18 0
19 0
20 0
21 0
22 0
23 0
24 0
25 0
26 0
27 0
28 0
29 0
30 0
31 0
32 0
33 0
34 0
35 0
36 0
37 0
38 0
39 0
40 0
41 0
42 0
43 0
44 0
45 0
46 0
47 0
48 0
49 0
50 0
51 0
52 0
53 0
54 0
55 0
56 0
57 0
58 0
59 0
60 0
61 0
62 0
63 0
64 0
65 0
66 0
67 0
68 0
69 0
70 0
71 0
72 0
73 0
74 0
75 0
76 0
77 0
78 0
79 0
80 0
81 0
82 0
83 0
84 0
85 0
86 0
87 0
88 0
89 0
90 0
91 0
92 0
93 0
94 0
95 0
96 0
97 0
98 0
99 0
100 0
101 0
102 0
103 0
104 0
105 0
106 0
107 0
108 0
109 0
110 0
111 0
112 0
113 0
114 0
115 0
116 0
117 0
118 0
119 0
120 0
121 0
122 0
123 0
124 0
125 0
126 0
127 0
128 0
129 0
130 0
131 0
132 0
133 0
134 0
135 0
136 0
137 0
138 0
139 0
140 0
141 0
142 0
143 0
144 0
145 0
146 0
147 0
148 0
149 0
150 0
151 0
152 0
153 0
154 0
155 0
156 0
157 0
158 0
159 0
160 0
161 0
162 0
163 0
164 0
165 0
166 0
167 0
168 0
169 0
170 0
171 0
172 0
173 0
174 0
175 0
176 0
177 0
178 0
179 0
180 0
181 0
182 0
183 0
184 0
185 0
186 0
187 0
188 0
189 0
190 0
191 0
192 0
193 0
194 0
195 0
196 0
197 0
198 0
199 0
200 0
201 0
202 0
203 0
204 0
205 0
206 0
207 0
208 0
209 0
210 0
211 0
212 0
213 0
214 0
215 0
216 0
217 0
218 0
219 0
220 0
221 0
222 0
223 0
224 0
225 0
226 0
227 0
228 0
229 0
230 0
231 0
232 0
233 0
234 0
235 0
236 0
237 0
238 0
239 0
240 0
241 0
242 0
243 0
244 0
245 0
246 0
247 0
248 0
249 0
250 0
251 0
252 0
253 0
254 0
255 0


2.4% of the time a two was returned otherwise a one is returned.  Which passes a basic stink test: 4766 + 2*117 = 5000.

Coding Badly

As regards the drifting clock - I started this to produce a more extensive example of "BlinkWithoutDelay" - nothing more. I expect that someone who needs better performance will also know how to get it, or will start a new Thread about it. I don't see any need to complicate matters for newbies. It would be another thing entirely if my code did not do what it claims (and I believe it does).


Perfect.  My only concern was that your audience knows what they are getting and, with that, they do.

Robin2

Thanks for your code @CodingBadly. I will try it and then I also want to reply to @PeterH.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Msquare

Me sits here feeling slightly miffed :smiley-eek-blue: that my explanation (reply#8)
of the reason why timer += increment is the better way is quietly ignored by Robin2
-- only to be repeated by PeterH (reply#17)  ;)  ;)  ;) (and I am sure PeterH invented his own).

Robin2 -
Code: [Select]
void loop() {
  delay(12) ;
  Serial.println(millis()) ;
  }

millis() now increases by 12 in each loop ! Eh?! And sometimes by 13. Eh eh!?!?

Quote
SECOND ... (though, I had forgotten) The code in "BlinkWithouDelay" uses "previousMillis = currentMillis; "

Unfortunatly there are several things missing, misleading and sometimes plain wrong in the reference/tutorials.
These have been pointed out, but the Arduino team has choosen not to use advice from this forum on that matter.

Quote
MY CONCLUSION ... is that this is a discussion about angels on the head of a pin

You're entitled to your point of view. However, this is not a religous point - it is mathematically deterministically provable true.
It may be for some applications the slippage/jittery is of no consequence, but it is there.

PeterH said it succinctly "the fact that it's taking so long to explain it to you demonstrates that it's quite a subtle problem - but it is still a problem."

Robin2

11 Mar 2012

@CodingBadly, I modified your code a bit to explore the phenomenon further. It seems that millis() increments by 2 every 41 or 42 steps. My code is below. Let me know if I have misrepresented your ideas. It could be interesting to try to figure out what give rise to the 2 step, and whether the frequency varies.

@PeterH, Based on the data from that sketch I think I can see the problem you refer to. For example, suppose the required interval is 41 millisecs. Let's also assume that on the previous iteration millis() was 40 (so the event did not happen). On the next iteration millis() will be 42 (because it jumped 2 places) and then the event will be triggered a millisecond late. If I use that new value of millis() as the base for the next event it should happen when millis() becomes 83. Whereas if I calculated from the originally intended event time (41) I would schedule the next event for 82. On the other hand if the required interval doesn't coincide with the millis() jump there won't be any error.

I think what has been confusing me (maybe still does) is your phrase "when there is any latency between the timed condition becoming true, and the code to detect that executing". I believe that by "saving" millis() at the start of each iteration of loop() it doesn't matter when the code actually gets around to executing the event. And the timing of the subsequent event is not linked to the actual time of execution of the previous event. Instead it is linked to the value of millis() at the time the loop() began its iteration.

Bearing in mind the limited purpose of my sketch and its intended audience I still think the problem is small enough that the balance of advantage lies with being consistent with the usage in Blink-Without-Delay rather than trying to explain this problem to newbies.

As I said earlier, I am thinking of writing a short additional note to cover this point and the two others that have been raised. Your comment has helped to clarify my understanding, and may make a description easier to write. Do feel free to write a few explanatory sentences/paragraphs on the subject yourself to save me the bother.

@Msquare, your post just came in as I finished this. Sorry if I was too thick to see your point. You, also, are invited to write a suitable explanation for newbies.

...R

Code: [Select]

static unsigned long Mark;
static unsigned long Previous;
static unsigned short Histogram[256];
static unsigned short OverOne[256];


void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "Five seconds to lift off..." ) );
 
  unsigned long Current;
  unsigned long Delta;
 
  int count = 0;
 
  while (count < 256) {
    Current = millis();
    Delta = Current - Previous;
    if (Delta > 0) {
        Previous = Current;
      Histogram[count] = (unsigned char)Delta;
      if (Delta > 1) {
        OverOne[count] = count;
      }


      count++;
    }
  }
  for ( int i = 1; i < 256; ++i )
    {
      Serial.print( i );
      Serial.write( '\t' );
      Serial.print( Histogram[i] );
      Serial.write( '\t' );
      Serial.println( OverOne[i] );
    }
  Serial.println();
}



void loop()
{
 
}
Two or three hours spent thinking and reading documentation solves most programming problems.

CrossRoads

Robin2,
This was how AWOL or CodingBadly or PaulStoffregen or maybe PaulS explained accurate time keeping to me:
Code: [Select]

void loop(){
currentMicros = micros();  // I have found micros to keep more accurate time
// likely because 16 clocks is 1uS, and millis has some "fudge factor" in it.
elapsedMicros = currentMicros - nextMicros; 
// I had trouble with this evaluation in an if early on, so now I do it outside
if (elapsedMicros >= duration){
// here's the key:
nextMicros = nextMicros + duration; // set up time for next comparison
/* nextMicros is kept independent of currentMicros -
so if currentMicros capture was delayed for any reason,
the delay does not impact when the next time comparison will happen.
the comparison will still be at a multiple of "duration".
the actual real-world time between comparisons may then wiggle some, this will be   
independent of that wiggle. */

// do the timed event
  } // end time check
} // end loop
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

Coding Badly

It seems that millis() increments by 2 every 41 or 42 steps.


125 / 3.  That's the fractional part for 16 Mhz.  I believe it's different for other speeds.

Go Up
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy