Demonstration code for several things at the same time

Robin2:
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.

Robin2:
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...

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 );
  }
}
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.

Robin2:
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.

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

...R

Me sits here feeling slightly miffed :fearful: 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) :wink: :wink: :wink: (and I am sure PeterH invented his own).

Robin2 -

void loop() {
  delay(12) ;
  Serial.println(millis()) ;
  }

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

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.

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."

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

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() 
{
  
}

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

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
2 Likes

Robin2:
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.

1 Like

Robin2:
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.

I think you have understood the problem correctly.

The scope for latency within a specific sketch can be affected by the placement of the call to millis() relative to the call checking whether the time has elapsed, and by how frequently the loop executes, but the latency can never be reduced to zero.

This is a problem that has varying severity and varying impact. Arguably, in many cases it can be ignored. However, the issue of how to control multiple things independently, including controlling independently timed things, crops up so frequently, and is so fundamentally important for newcomers to understand, that I think having a really good example of 'best practice', together with an explanation of why that is the best practice, would be extremely useful. It's a great shame, and a lost opportunity, that the 'blink without delay' example is so poorly explained and so mediocre in implementation. This is why I'm so keen for you to develop your example to the point where it really does represent best practice, and not just 'good enough for simple examples'.

A word of caution about the basic "mile marker" approach: an overrun will wreak havoc. A while loop overcomes the problem.

An example from CrossRoads' snippet...

void setup()
{
  Serial.begin( 115200 );
}

void loop()
{
  unsigned long currentMicros;
  unsigned long elapsedMicros;
  static unsigned long nextMicros;
  const unsigned long duration = 1000000ul;

  currentMicros = micros();
  elapsedMicros = currentMicros - nextMicros;  
  if ( elapsedMicros >= duration )
  {
    nextMicros = nextMicros + duration;
    Serial.println( currentMicros );
  }
  // Pretend loop takes too long to execute
  delay( 1001 );
}

Change the if to a while.

1 Like

This is all provin' very stimulatin' to the few remaining grey cells ...

@CrossRoads, thank you, but I am not concerned with accuracy greater than you get from millis(). This is just intended as a simple learning device for newbies.

@PeterH and @CodingBadly, I hope you will have realized that my only objection to doing it "properly" was the risk that explaining the change would so confuse a newbie that all value from the demo code would be lost. I think I have now figured out a simple way to link the "+= interval" version to the "= currentMillis" version that's in Blink-Without-Delay without the need for a complex explanation.

I will modify my example and maybe post the updated version later today if I feel the change will be sufficiently unobtrusive. Edit to add ... I have now updated the code attached to the original post and the code in Reply #1.

@CrossRoads, I don't understand your use of the term "mile marker".

...R

"mile marker" refers to the event happening at regular intervals:

the comparison will still be at a multiple of "duration".

as I indicated in my code comments.

Sorry @Crossroads, but I still don't see how your comments (or @CodingBadly's comments on your comments) to relate to my demo sketch. I thought you were recommending using micros instead of millis for more accurate timing.

...R

Robin2:
I have prepared the attached example sketch in the hope that it will be a
suitable model. I have tested it on an Uno and a Mega. I have called it
"SeveralThingsAtTheSameTime.ino". (It seems to be too long to show the
code directly in this post)
...
It also uses the "state machine" concept to manage the various activities
and enable the different functions to determine what to do.
...
Comments are welcome.

Thank you for this, I will be studying it.

I'm new to Arduino coding and learning what and how can be fit in Arduino
IDE code. My starting approach was to leverage this SimpleTimer code:

http://playground.arduino.cc/Code/SimpleTimer#.UyIPih_f9ic

Here is a code fragment where I do periodic Serial writes. This not
really equivalent to what your code is doing, but this is my starting
point.

...
#include <SimpleTimer.h>
...
SimpleTimer timer;
...
void setup()
{
...
  timer.setInterval((5*1000), timerWrite);
...
}
void loop()
{
...
  timer.run();
...
}
// Periodic write to Serial to test RPi <-> Arduino
void timerWrite()
{
  // Code here that queues up some text to write on Serial
}

@cootcraig, thank you for your kind words.

If you want get advice about the SimpleTimer library (about which I know nothing) I would appreciate it if you would start a new Thread as I would like to keep this thread to the one subject so as not to confuse newcomers.

...R

Robin2:
If you want get advice about the SimpleTimer library (about which I know nothing) I would appreciate it if you would start a new Thread as I would like to keep this thread to the one subject so as not to confuse newcomers.

...R

I'm not looking for help with SimpleTimer, sorry. I'm learning how to multiple things on an Arduino myself., just not starting with the same demo. I will continue watching this thread.

hey guys,

i took your example and tried to apply it to a binary thermometer that i had already done
i got it to comply but it dosnt function like it should

Moderator edit: offensive phrase remove

any ideas?

evancleary:
any ideas?

I don't know about the code, but you have a serial.print() line in the loop() function that I would like to encourage you to edit, or the moderators may do it for you.

/remember, this forum caters to many age groups and such; let's try to keep it somewhat in line with that...

cr0sh:
...or the moderators may do it for you.

Too late.

/remember, this forum caters to many age groups and such; let's try to keep it somewhat in line with that...

Well stated @cr0sh. Thank you for your post.

@evancleary, I make you this promise: I will not waste my time again cleaning up your mess.

I take it that no input is required from me until there is some code to look at?

...R