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) {
   if(correct timeslot) {
   previous_action = millis();

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

  • what device do the arduinos communicate to?
    – jsotola
    Commented Jan 26, 2023 at 3:25
  • @jsotola they send messages to a receiver which collects packets and prints to a file. Commented Jan 26, 2023 at 3:27
  • What kind of Arduino are you using? Is it clocked off a crystal or a ceramic resonator? Commented Jan 26, 2023 at 8:47
  • Similar to @NickS below. Modify the TDM algorithm running on both devices so that the transmissions can be used also to keep the devices in sync. For example, if each transmission starts exactly 100ms into the timeslot, the other side can adjust its clock to match. In this case, the next full second rollover should be in 900ms.
    – 6v6gt
    Commented Jan 26, 2023 at 14:00

3 Answers 3


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:

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

  2. 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).

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

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

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


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.

  • See edit. The goal of this is to avoid RTC and only use the internal clock of the arduino to keep time of the device Commented Jan 26, 2023 at 6:35
  • 2
    @roberthayek then it sounds like you'll have to expand on your TDMA algorithm and have the two devices generate overhead and maybe have one be the "server" and the other a "client" - the client device will be doing all the scheduling and the server device will have to adjust its time slots based on the information provided by the client. The two devices have different clocks and thus different drifts, you really can't just fix that.
    – Nick S.
    Commented Jan 26, 2023 at 8:12

According to my experience, they are the clients that should follow the server's timing. On the client side:

  1. On each server transmission, calculate the ETA (Estimated Time of Arrival and ATA (Actual Time of Arrival) of the message, based on your time slot scheduling and adjust your local clock accordingly.
  2. It would be nice to treat all transmissions as timing messages, no matter the destination, just ignore the payload.
  3. It is obvious that the client should be a little overhead timed, in case the server's clock is a little faster.
  4. For long inactive periods, the server should broadcast a periodic timing message, so that the error will be kept to a minimum.
  5. It would be convenient to add a "timing preamble" in each transmission.

With all that in mind, the readStringUntil function is your friend in many ways. By waiting for the timing preamble, you can determine easily the timing error. By waiting for a known postamble, you read "late" messages (don't forget the timeout value!).

Use a bi-numbering scheme for each transmission, one for each client's message and one for each broadcast (total transmissions). Calculate the minimum convenient modulo (overflow) values. Each client will know if there's something missing. (usually, a too-early transmission or a collision)

Although it is communications-medium agile, I would recommend a preamble like:

$FF $00 $FF $00 AA BB CC DD EE $FF $FF FF with PAYLOAD following, where:

AA = 1 byte, the destination addr, $00 = Broadcast

BB = 1 byte, message type (optional, $00 for timing msg)

CC = 1..2 bytes, msg no. to this dest.

DD = 1..2 (3?) bytes, total msg no., no matter the dest. 

EE = 1 byte, payload length, if variable. (EE<$FF, $00 for timing msg)

FF = 1 byte checksum (optional). (Why should you place it AFTER the "$FF 
$FF" combination?)

Use the time you receive the "$FF $FF" combination as a time-mark to calculate ATA & ETA. (It is common sense to treat your values so that this combination cannot show before the end of the preamble)

Consider using a postamble, with redundant info & End-Of-Message mark.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.