user88623: I have two Arduinos that communicate over serial. Each arduino runs a TDMA algorithm where each arduino is assigned one timeslot. The arduinos should transmit during their timeslot, which does work fine. However, over time, say 10-15 minutes the arduinos start to overlap their timeslots. To keep time I am using the millis() function to determine when to send a message. I have tried both delay() and manually setting up counters but getting the same drifting behavior. On one of the iterations of the code I had a Serial.readStringUntil() function that was being called during every timeslot and the arduinos stayed in sync for over 13 hours with no detected drift, with an exact timeslot of 1000ms. I now am aware that the delay I was seeing is caused by the timeout parameter, which defaults to 1000ms, in the readStringUntil function, but it does not make sense that there would be no drift when the arduino was delayed in this way. I know that the stability of the ceramic resonator on each arduino would cause drift.
Why would delay(), millis(), and manually configuring timer1 cause drift, but the timeout functionality of the readStringUntil function seemingly does not drift?
Also, is there a better way to setup the timing functionality, the goal is to have the nodes go as long as possible without needing a resync?
The following psuedo is how the timing is currently setup.
count = 0;

if(millis() - previous_action >= TIME_SLOT) {
   count++;
   if(correct timeslot) {
       TRANSMIT();
   }
   previous_action = millis();
   loop_back_to_check;
}

EDIT:
RTC and other devices that can keep the devices sync'd are not in the scope of the project.

user14095: You can use one of the many RTC (Real Time Clock) modules, they are crystal controlled where your Arduino is not. There are several varieties, pick the one you like. There is a large difference in stability between the Arduino timing and the RTC timing. You can update the clocks on a regular basis. Using NTP (Network Time Protocol) which would be very accurate and reliable. You can do the NTP on a regular basis on one unit and have it update the others. Side benefit they will not drift much as the temperature changes.

user7508: Any fee-running clock will drift. Worse yet: its drift rate will not be
constant. This is a fact of life, and there is nothing you can do about
it. There is no software solution, although your software can certainly
make things worse. What you can do is:

Synchronize the clocks of both Arduinos to an external time source.
The time source doesn't have be accurate if you only care about the
relative drift between the Arduinos. This could be the receiver of
the messages sending an acknowledgement at a specific time, or the
same receiver sending a pulse on a dedicated line, or an NTP server
if your Arduinos are networked, or a radio-broadcast time signal, or
a GPS receiver...

Use clocks that are so good that their drift becomes irrelevant. The
drift rate has to be less than the timing tolerance of your TDMA
protocol divided by the expected service life of your devices. A good
RTC can give you a drift rate less than about 2 ppm (i.e.
2×10−6). If that is not good enough, you may consider an
OCXO (oven-controlled crystal oscillator) or an atomic clock (the
rubidium-based ones are the most affordable).

Calibrate your clocks. As the variations of the drift rate are
typically much less than the average drift rate, you can remove most
of the drift by first measuring it and then removing in software the
value you measured. You are then left with the random variations of
the drift rate (“frequency wander”). I suggest you read Arduino
clock frequency accuracy to get an idea of the kind of
accuracy you can expect.

Use temperature control: keep you clocks at constant temperature, as
temperature variations are often the main cause of frequency wander.
Alternatively, use temperature compensation: measure the drift rate
at multiple temperatures, fit a continuous curve through these
measurements and use this curve (and continuous temperature
measurements) to correct the drift in software.

Avoid making things worse with your software: update
previous_action with
previous_action += TIME_SLOT;

rather than assigning millis() to it, as this approach is
guaranteed to cause drift.



Why [...] the timeout functionality of the readStringUntil function
seemingly does not drift?

Nobody can answer this question without a careful examination of both
the specific version of the software you used and the physical setup. It
could be that you accidentally implemented a synchronization mechanism.
It could be that your measurements were not accurate enough to reveal
the drift.

88623: <p>I have two Arduinos that communicate over serial. Each arduino runs a TDMA algorithm where each arduino is assigned one timeslot. The arduinos should transmit during their timeslot, which does work fine. However, over time, say 10-15 minutes the arduinos start to overlap their timeslots. To keep time I am using the millis() function to determine when to send a message. I have tried both delay() and manually setting up counters but getting the same drifting behavior. On one of the iterations of the code I had a Serial.readStringUntil() function that was being called during every timeslot and the arduinos stayed in sync for over 13 hours with no detected drift, with an exact timeslot of 1000ms. I now am aware that the delay I was seeing is caused by the timeout parameter, which defaults to 1000ms, in the readStringUntil function, but it does not make sense that there would be no drift when the arduino was delayed in this way. I know that the stability of the ceramic resonator on each arduino would cause drift.</p>
<p>Why would delay(), millis(), and manually configuring timer1 cause drift, but the timeout functionality of the readStringUntil function seemingly does not drift?</p>
<p>Also, is there a better way to setup the timing functionality, the goal is to have the nodes go as long as possible without needing a resync?</p>
<p>The following psuedo is how the timing is currently setup.</p>
<pre><code>count = 0;

if(millis() - previous_action &gt;= TIME_SLOT) {
   count++;
   if(correct timeslot) {
       TRANSMIT();
   }
   previous_action = millis();
   loop_back_to_check;
}
</code></pre>
<p>EDIT:
RTC and other devices that can keep the devices sync'd are not in the scope of the project.</p>
 14095: <p>You can use one of the many RTC (Real Time Clock) modules, they are crystal controlled where your Arduino is not. There are several varieties, pick the one you like. There is a large difference in stability between the Arduino timing and the RTC timing. You can update the clocks on a regular basis. Using NTP (Network Time Protocol) which would be very accurate and reliable. You can do the NTP on a regular basis on one unit and have it update the others. Side benefit they will not drift much as the temperature changes.</p>
 7508: <p>Any fee-running clock will drift. Worse yet: its drift rate will not be
constant. This is a fact of life, and there is nothing you can do about
it. There is no software solution, although your software can certainly
make things <em>worse</em>. What you can do is:</p>
<ol>
<li><p>Synchronize the clocks of both Arduinos to an external time source.
The time source doesn't have be accurate if you only care about the
relative drift between the Arduinos. This could be the receiver of
the messages sending an acknowledgement at a specific time, or the
same receiver sending a pulse on a dedicated line, or an NTP server
if your Arduinos are networked, or a radio-broadcast time signal, or
a GPS receiver...</p>
</li>
<li><p>Use clocks that are so good that their drift becomes irrelevant. The
drift rate has to be less than the timing tolerance of your TDMA
protocol divided by the expected service life of your devices. A good
RTC can give you a drift rate less than about 2 ppm (i.e.
2×10<sup>−6</sup>). If that is not good enough, you may consider an
OCXO (oven-controlled crystal oscillator) or an atomic clock (the
rubidium-based ones are the most affordable).</p>
</li>
<li><p>Calibrate your clocks. As the variations of the drift rate are
typically much less than the average drift rate, you can remove most
of the drift by first measuring it and then removing in software the
value you measured. You are then left with the random variations of
the drift rate (“frequency wander”). I suggest you read <a href="http://jorisvr.nl/article/arduino-frequency" rel="nofollow noreferrer">Arduino
clock frequency accuracy</a> to get an idea of the kind of
accuracy you can expect.</p>
</li>
<li><p>Use temperature control: keep you clocks at constant temperature, as
temperature variations are often the main cause of frequency wander.
Alternatively, use temperature compensation: measure the drift rate
at multiple temperatures, fit a continuous curve through these
measurements and use this curve (and continuous temperature
measurements) to correct the drift in software.</p>
</li>
<li><p>Avoid making things worse with your software: update
<code>previous_action</code> with</p>
<pre class="lang-cpp prettyprint-override"><code>previous_action += TIME_SLOT;
</code></pre>
<p>rather than assigning <code>millis()</code> to it, as this approach is
<em>guaranteed</em> to cause drift.</p>
</li>
</ol>
<blockquote>
<p>Why [...] the timeout functionality of the readStringUntil function
seemingly does not drift?</p>
</blockquote>
<p>Nobody can answer this question without a careful examination of both
the specific version of the software you used and the physical setup. It
could be that you accidentally implemented a synchronization mechanism.
It could be that your measurements were not accurate enough to reveal
the drift.</p>
