MicroPython for Microcontrollers Module 5 – Time, timers, interrupts and watchdogs
Time on a chip: ticks, sleeps and the RTC
Time intervals with ticks_ms() and ticks_diff(), wait without blocking or drifting, and keep calendar time in UTC with the RTC and ntptime in MicroPython.
What you will learn
- Measure intervals with ticks_ms(), ticks_us() and ticks_diff(), within the half period each counter allows
- Choose between sleep_ms(), sleep_us() and a deadline loop for a wait
- Schedule periodic work from the previous deadline so that it never drifts
- Set the RTC with a tuple in its own order, and store times in UTC, converting to local time only for display
Before you start
What you’ll learn · Before you start
What you will learn
- Measure intervals with ticks_ms(), ticks_us() and ticks_diff(), within the half period each counter allows
- Choose between sleep_ms(), sleep_us() and a deadline loop for a wait
- Schedule periodic work from the previous deadline so that it never drifts
- Set the RTC with a tuple in its own order, and store times in UTC, converting to local time only for display
Before you start
On this page
A Pico that takes a reading every 10 seconds, with time.sleep(10) after 30 to 50 ms of work, takes 8,606 readings a
day instead of 8,640, and its last reading of the day starts 343 seconds after its slot. After every power-up the same
board’s calendar clock says it is the first second of 2021, so each timestamp it writes is years out until something
sets the clock.
A microcontroller keeps time in two ways, tick counters for intervals and a calendar clock for dates, and each mistake above uses one of them badly. This lesson times intervals with the tick counters and their limits, chooses how to wait, runs a periodic job from deadlines so that it keeps its rhythm, and sets the calendar clock so that the board stores and sends UTC. Every program runs here, on a model of a Pico’s clock built from MicroPython 1.29’s documentation and the rp2 port’s source.
Tick counters time intervals, until half their period
time.ticks_ms() counts milliseconds from a point the documentation calls arbitrary: on a Pico, the moment the board
started. One reading means nothing; the difference between two readings is an interval. ticks_us() counts
microseconds the same way, and ticks_cpu() counts in the finest unit a port offers, which each port decides: on a
Pico it is the same 1 MHz timer as ticks_us(). All three return ticks: The counts that time.ticks_ms(), ticks_us() and ticks_cpu() return: they wrap to 0 at a power of two, 2**30 on 32-bit ports, so two readings are compared only with time.ticks_diff(). More in “Pins: outputs, inputs, pull-ups and Signal” with one period, a
power of two, which MicroPython’s configuration sets to 2³⁰ on 32-bit ports such as rp2 and esp32, so that every
value is a small integer: An integer that MicroPython keeps inside the object word itself, with no heap allocation: on most 32-bit ports one from -2**30 to just below 2**30. Larger integers stay exact but are stored on the heap. More in “Numbers, bytes and text on small chips” and reading the clock never touches the heap.
So ticks_diff(end, start) turns two readings into an interval, never end - start, and ticks_add(t, ms) makes a
deadline. This program does the arithmetic at the two edges of the period, the way MicroPython’s source does it:
ticks/counters.py
"""How long the tick counters of a Pico or an ESP32 run before they wrap, and where ticks_diff() stops being right."""
from ticks import TICKS_PERIOD, ticks_add, ticks_diff
print("TICKS_MAX =", ticks_add(0, -1))
half = TICKS_PERIOD // 2
day_ms = 24 * 60 * 60 * 1000
minute_us = 60 * 1000 * 1000
print("ticks_ms(): wraps after %.2f days, ticks_diff() right up to %.2f days" % (TICKS_PERIOD / day_ms, (half - 1) / day_ms))
print("ticks_us(): wraps after %.2f minutes, ticks_diff() right up to %.2f minutes" % (TICKS_PERIOD / minute_us, (half - 1) / minute_us))
print("ticks_diff(%d, 0) = %d" % (half - 1, ticks_diff(half - 1, 0)))
print("ticks_diff(%d, 0) = %d" % (half, ticks_diff(half, 0)))
try:
ticks_add(0, half)
except OverflowError as e:
print("ticks_add(0, %d): OverflowError: %s" % (half, e)) Output
TICKS_MAX = 1073741823 ticks_ms(): wraps after 12.43 days, ticks_diff() right up to 6.21 days ticks_us(): wraps after 17.90 minutes, ticks_diff() right up to 8.95 minutes ticks_diff(536870911, 0) = 536870911 ticks_diff(536870912, 0) = -536870912 ticks_add(0, 536870912): OverflowError: ticks interval overflow
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 counters.py
ticks.py (the arithmetic) · ticks/ticks.py
"""MicroPython's tick arithmetic, written out for CPython (extmod/modtime.c, v1.29.0).
The rp2 and esp32 ports keep tick values as small integers, so their tick counters wrap at 2**30
(MICROPY_PY_TIME_TICKS_PERIOD in py/mpconfig.h). Not MicroPython: on a board, time.ticks_add() and
time.ticks_diff() are built in. counter_after() is not an API at all: it stands for the hardware counter.
"""
TICKS_PERIOD = 1 << 30
_HALF = TICKS_PERIOD // 2
def ticks_add(ticks, delta):
"""The tick value delta ticks after (or before) ticks."""
if not -_HALF < delta < _HALF:
raise OverflowError("ticks interval overflow")
return (ticks + delta) & (TICKS_PERIOD - 1)
def ticks_diff(ticks1, ticks2):
"""ticks1 - ticks2 as a signed number, right while they are less than half a period apart."""
return ((ticks1 - ticks2 + _HALF) & (TICKS_PERIOD - 1)) - _HALF
def counter_after(ticks, elapsed):
"""What the counter reads elapsed ticks after it read ticks, however long that is."""
return (ticks + elapsed) % TICKS_PERIOD Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
ticks_diff() answers within one period centred on zero, so it is right only while the two readings are less than
half a period apart: 6.21 days for ticks_ms(), 8.95 minutes for ticks_us(). At exactly half a period the answer
turns negative, and ticks_add() refuses to make a deadline that far away. The documentation compares this to a
12-hour clock face: look at it again after 13 hours and it seems that one hour has passed. Here is the same mistake on
a board that checks a one-hour deadline after longer and longer gaps. Step through it and watch late:
Program · ticks/rare_check.py
from ticks import ticks_add
from ticks import ticks_diff
from ticks import counter_after
HOUR = 3_600_000 # ms
DAY = 24 * HOUR
start = 1_000_000 # ticks_ms()
due = ticks_add(start, HOUR)
gaps = [2 * HOUR, 6 * DAY,
7 * DAY, 13 * DAY]
for gap in gaps:
now = counter_after(start, gap)
late = ticks_diff(now, due)
passed = gap // HOUR
hours = round(late / HOUR, 1)
print(passed, "h:", hours) Variables
- just taken by the loop
- changed in this step
- passed by the loop
- read next
- where the loop stopped
Printed so far
Nothing printed yet2 h: 1.0 144 h: 143.0 168 h: -131.3 312 h: 12.7
Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.
The drawing shows the variables when the program ends, and the program's 35 steps are listed below.
Change the program and run it here. Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Every step as text
- Start. No line has run yet, so there are no variables. Python starts with line 1.
- Line 1.
from ticks import ticks_addloadsticks_add. - Line 2.
from ticks import ticks_diffloadsticks_diff. - Line 3.
from ticks import counter_afterloadscounter_after. - Line 5.
HOUR = 3_600_000setsHOURto3600000. - Line 6.
DAY = 24 * HOURsetsDAYto86400000. - Line 7.
start = 1_000_000setsstartto1000000. - Line 8.
due = ticks_add(start, HOUR)setsdueto4600000. due is 4,600,000: one hour, 3,600,000 ms, after the reading at 1,000,000. - Line 9.
gaps = [2 * HOUR, 6 * DAY, 7 * DAY, 13 * DAY]runs. - Line 9.
gaps = [2 * HOUR, 6 * DAY, 7 * DAY, 13 * DAY]makesgapsa list of 4 items. - Line 11.
for gap in gaps:takes the item at position 0 ofgaps:gapis7200000. - Line 12.
now = counter_after(start, gap)setsnowto8200000. - Line 13.
late = ticks_diff(now, due)setslateto3600000. Two hours have passed, so the deadline is one hour overdue: late is 3,600,000 ms. - Line 14.
passed = gap // HOURsetspassedto2. - Line 15.
hours = round(late / HOUR, 1)setshoursto1.0. - Line 16.
print(passed, "h:", hours)prints2 h: 1.0. - Line 11.
for gap in gaps:takes the item at position 1 ofgaps:gapis518400000. - Line 12.
now = counter_after(start, gap)setsnowto519400000(it was8200000). - Line 13.
late = ticks_diff(now, due)setslateto514800000(it was3600000). - Line 14.
passed = gap // HOURsetspassedto144(it was2). - Line 15.
hours = round(late / HOUR, 1)setshoursto143.0(it was1.0). - Line 16.
print(passed, "h:", hours)prints144 h: 143.0. - Line 11.
for gap in gaps:takes the item at position 2 ofgaps:gapis604800000. - Line 12.
now = counter_after(start, gap)setsnowto605800000(it was519400000). - Line 13.
late = ticks_diff(now, due)setslateto-472541824(it was514800000). The bug Seven days are 604,800,000 ms, more than half the period, 536,870,912 ms. ticks_diff() wraps to a negative number and says the deadline is still 131.3 hours away. - Line 14.
passed = gap // HOURsetspassedto168(it was144). - Line 15.
hours = round(late / HOUR, 1)setshoursto-131.3(it was143.0). - Line 16.
print(passed, "h:", hours)prints168 h: -131.3. - Line 11.
for gap in gaps:takes the item at position 3 ofgaps:gapis1123200000. - Line 12.
now = counter_after(start, gap)setsnowto50458176(it was605800000). - Line 13.
late = ticks_diff(now, due)setslateto45858176(it was-472541824). The bug After 13 days the counter has wrapped once, and ticks_diff() says the deadline passed 12.7 hours ago. It passed 311 hours ago. - Line 14.
passed = gap // HOURsetspassedto312(it was168). - Line 15.
hours = round(late / HOUR, 1)setshoursto12.7(it was-131.3). - Line 16.
print(passed, "h:", hours)prints312 h: 12.7. - Line 11.
for gap in gaps:finds no item at position 4 ofgaps(it has 4 items), so the loop ends. That was the last line, so the program ends. The checks after 2 hours and after 6 days were right. The two after more than 6.2 days were wrong, and nothing raised an error.
After two hours and after six days the answers are right. After seven days the deadline seems 131.3 hours away, and after thirteen it seems 12.7 hours overdue, when it is in fact 311 hours overdue. A loop that keeps a ticks deadline must check it well within half a period; a job that may wait for days takes its time from the calendar clock instead.
Keeping ticks across a reset
A tick value saved to a file and compared after a restart compares two counts with different starting points: after
a hard reset a Pico’s ticks_ms() starts again from 0. Use ticks only inside one run of the program, and store
time.time() for anything that must outlive a reset.
Checkpoint
Check: Tick counters and their half period
A sleep stops the loop; a deadline keeps it listening
A sleep is the simplest way to wait and the bluntest. On the rp2 port, time.sleep_ms(500) lets the processor rest
until something happens or the time is up, and gives pending scheduled callbacks their turn: work that timers and
interrupts hand to the main program, which the next two lessons cover. It waits at least 500 ms, and longer when
interrupt handlers or threads need the processor. sleep_us() is for short, exact waits: up to 1,000 µs it is a
busy-wait: A wait in which the processor runs a tight loop that checks the time until it is up, doing nothing else meanwhile. On a Pico, time.sleep_us() waits this way for delays of up to 1,000 microseconds., which keeps the processor busy and runs no scheduled callbacks. sleep() takes
seconds, a fraction too on boards with floats, but keeps only whole milliseconds. This model follows the rp2 port’s
source call by call:
sleeps/sleeps.py
"""What each sleep call does on a Pico: how long it waits, how, and whether scheduled callbacks get their turn."""
import rp2_time as time # on a board: import time
calls = [
("sleep(1.5)", lambda: time.sleep(1.5)),
("sleep(0.0004)", lambda: time.sleep(0.0004)),
("sleep_ms(0)", lambda: time.sleep_ms(0)),
("sleep_ms(-5)", lambda: time.sleep_ms(-5)),
("sleep_us(400)", lambda: time.sleep_us(400)),
("sleep_us(2500)", lambda: time.sleep_us(2500)),
]
print("%-15s %9s %9s %9s %s" % ("call", "waits us", "idle us", "spins us", "callbacks run"))
for text, call in calls:
time.reset()
call()
ran = "yes" if time.callback_runs else "no"
print("%-15s %9d %9d %9d %s" % (text, time.clock_us, time.idle_us, time.busy_us, ran)) Output
call waits us idle us spins us callbacks run sleep(1.5) 1500000 1500000 0 yes sleep(0.0004) 0 0 0 yes sleep_ms(0) 0 0 0 yes sleep_ms(-5) 0 0 0 no sleep_us(400) 400 0 400 no sleep_us(2500) 2500 2000 500 yes
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sleeps.py
rp2_time.py (the model) · sleeps/rp2_time.py
"""time.sleep(), sleep_ms() and sleep_us() of MicroPython's rp2 port (v1.29.0), on a simulated clock.
As extmod/modtime.c and ports/rp2/mphalport.c define them:
- sleep(s) multiplies s by 1000 and keeps the whole milliseconds, then waits like sleep_ms();
- sleep_ms(ms) does nothing for a negative ms; otherwise it runs pending scheduled callbacks, then waits for
events with the CPU idle, again and again, until at least ms milliseconds have passed;
- sleep_us(us) does nothing unless us > 0; over 1,000 us it first waits the whole milliseconds like sleep_ms(),
then it spins in a tight loop until the time is up.
Not MicroPython: on a board these functions are built into the time module.
"""
clock_us = 0 # the board's microsecond timer
idle_us = 0 # time spent waiting for events
busy_us = 0 # time spent spinning
callback_runs = 0 # how often pending callbacks were given their turn
def reset():
global clock_us, idle_us, busy_us, callback_runs
clock_us = idle_us = busy_us = callback_runs = 0
def _wait_for_events(ms):
# mp_event_wait_ms(): run pending callbacks, then wait (WFE) until an event or the timeout
global clock_us, idle_us, callback_runs
callback_runs += 1
clock_us += ms * 1000
idle_us += ms * 1000
def _delay_ms(ms):
start = clock_us
while True: # a do-while loop in the C source
_wait_for_events(ms - (clock_us - start) // 1000)
if (clock_us - start) // 1000 >= ms:
break
def sleep(seconds):
_delay_ms(int(1000 * seconds))
def sleep_ms(ms):
if ms >= 0:
_delay_ms(ms)
def sleep_us(us):
global clock_us, busy_us
if us > 0:
end = clock_us + us
if us > 1000:
_delay_ms(us // 1000)
busy_us += end - clock_us # the tight loop until the end
clock_us = end Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Two lines catch people out: sleep(0.0004) waits no time at all, because 0.4 ms becomes 0, and sleep_us(400) runs
no callbacks while it spins. sleep_us(2500) rests for its 2 whole milliseconds and spins only for the last 500 µs.
While a sleep runs, the loop cannot see a button, a byte on the UART or the end of another job. A deadline loop waits
differently: it makes a deadline with ticks_add(), compares the time with ticks_diff() on every pass, does whatever
else is ready, and rests between passes with machine.idle(), which stops the processor’s clock until the next
interrupt, or for at most 1 ms. Three things about your case decide which wait fits. Answer them in the table:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Wait | Other work meanwhile | Precision | Power while waiting | Scheduled callbacks |
|---|---|---|---|---|
| sleep_ms(n) How it waits: rests until n ms have passed | Poor the loop does nothing else until the wait ends | Mixed at least n ms, and later when handlers or threads need the CPU | Good the CPU waits for events | Good they run while it waits |
| sleep_us(n) How it waits: spins in a tight loop up to 1 ms | Poor nothing else runs, and up to 1,000 µs it spins | Good microseconds, from the 1 MHz timer | Poor the CPU runs flat out for waits of up to 1 ms | Poor none run during a wait of up to 1,000 µs |
| Deadline loop with idle() How it waits: checks a deadline and rests between passes | Good buttons, bytes and other jobs are checked on every pass | Mixed up to about 1 ms late: idle() lasts until an interrupt or 1 ms | Good the CPU clock is gated between passes | Good they run between the bytecodes of the loop |
| Deadline loop that spins How it waits: checks a deadline as fast as it can | Good other work is checked on every pass | Good as fine as one pass of the loop | Poor the CPU never rests | Good they run between the bytecodes of the loop |
How the answers decide
- “Must the program notice anything else, such as a button or a byte, during the wait?” Yes, it must keep listening: rules out sleep_ms(n) and sleep_us(n). A sleep holds the loop until it ends, so a press or a byte that arrives meanwhile waits for it.
- “How long is the wait?” Under 1 ms: rules out sleep_ms(n) and adds a caveat to Deadline loop with idle(). sleep_ms() counts whole milliseconds, and idle() can return up to 1 ms late: a wait of 200 µs needs sleep_us() or a deadline in ticks_us().
- “How long is the wait?” 1 ms or more: adds a caveat to Deadline loop that spins and sleep_us(n). Spinning for tens of milliseconds does nothing useful and burns power; sleep_us() of more than 1 ms rests only for its whole milliseconds.
- “Does the board run on a battery?” Yes: rules out Deadline loop that spins. A spinning CPU draws full current for the whole wait; on a battery, rest with sleep_ms() or idle() between checks.
In short: sleep_ms() when the loop has nothing else to watch, sleep_us() for waits under a millisecond inside a
driver, and a deadline loop with machine.idle() whenever the board must keep listening.
Checkpoint
Check: Sleeps and deadline loops
Run a periodic job from its last deadline, not from now
A loop that sleeps for its period after its work runs a period plus the work on every pass. This program models one day of the 10-second logger from the start of the lesson, with work of 30 to 50 ms a reading, under two loops: one sleeps 10 s after the work, and one waits for a deadline made from the previous one:
drift/drift.py
"""One day of a logger that takes a reading every 10 s, on a simulated millisecond clock.
Each reading's work takes 30 to 50 ms (a seeded random draw, the same in every run). The first loop sleeps 10 s
after the work, as time.sleep(10) would; the second waits for the next deadline, made from the last one.
"""
import random
from ticks import ticks_add, ticks_diff
PERIOD = 10_000 # ms
DAY = 24 * 60 * 60 * 1000 # ms
def work_times():
rng = random.Random(7)
while True:
yield rng.randint(30, 50)
def sleep_after_work():
now, starts, work = 0, [], work_times()
while now < DAY:
starts.append(now)
now += next(work) # the reading
now += PERIOD # time.sleep(10)
return starts
def deadline_loop():
now, due, starts, work = 0, 0, [], work_times()
while now < DAY:
starts.append(now)
now = ticks_add(now, next(work)) # the reading
due = ticks_add(due, PERIOD) # the next slot, from the last one
wait = ticks_diff(due, now)
if wait > 0:
now = ticks_add(now, wait) # time.sleep_ms(wait)
return starts
for name, loop in (("sleep(10) after the work", sleep_after_work), ("deadline from the last slot", deadline_loop)):
starts = loop()
last = len(starts) - 1
behind = (starts[last] - last * PERIOD) / 1000
print("%-28s %5d readings in 24 h, the last one %6.1f s after its slot" % (name, len(starts), behind)) Output
sleep(10) after the work 8606 readings in 24 h, the last one 343.0 s after its slot deadline from the last slot 8640 readings in 24 h, the last one 0.0 s after its slot
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 drift.py
ticks.py (the arithmetic) · drift/ticks.py
"""MicroPython's tick arithmetic, written out for CPython (extmod/modtime.c, v1.29.0).
The rp2 and esp32 ports keep tick values as small integers, so their tick counters wrap at 2**30
(MICROPY_PY_TIME_TICKS_PERIOD in py/mpconfig.h). Not MicroPython: on a board, time.ticks_add() and
time.ticks_diff() are built in. counter_after() is not an API at all: it stands for the hardware counter.
"""
TICKS_PERIOD = 1 << 30
_HALF = TICKS_PERIOD // 2
def ticks_add(ticks, delta):
"""The tick value delta ticks after (or before) ticks."""
if not -_HALF < delta < _HALF:
raise OverflowError("ticks interval overflow")
return (ticks + delta) & (TICKS_PERIOD - 1)
def ticks_diff(ticks1, ticks2):
"""ticks1 - ticks2 as a signed number, right while they are less than half a period apart."""
return ((ticks1 - ticks2 + _HALF) & (TICKS_PERIOD - 1)) - _HALF
def counter_after(ticks, elapsed):
"""What the counter reads elapsed ticks after it read ticks, however long that is."""
return (ticks + elapsed) % TICKS_PERIOD Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The sleeping loop took 34 readings fewer and fell 343 seconds behind; the deadline loop took all 8,640, each on its
slot. Press Edit, set PERIOD to 60_000 and run it again: a logger that reads once a minute drifts in the same way.
The whole difference is one line, due = ticks_add(due, PERIOD): the next deadline is the last one plus the period,
so the work time is never added to the rhythm, and the loop sleeps only for what is left, ticks_diff(due, now). An
error that grows with every pass, as the sleeping loop’s does, is drift: A timing error that grows with every repetition, as when a loop sleeps for its whole period after its work, so that each run starts a little later than the one before..
Making the next deadline from the time now
due = time.ticks_add(time.ticks_ms(), PERIOD) written after the work adds the work time to every period: the
sleeping loop’s drift with more code. Make it from the last deadline, due = time.ticks_add(due, PERIOD).
A deadline loop still has a decision to make when one pass takes longer than a period, for a Wi-Fi reconnect or a slow sensor: run every missed slot at once, or skip them and keep to the grid. Step through a loop that skips them, with readings that take 40, 35, 3,400 and 45 ms:
Program · drift/every_loop.py
from ticks import ticks_add
from ticks import ticks_diff
PERIOD = 1000 # ms between readings
work = [40, 35, 3400, 45] # ms each
now = 0 # ticks_ms()
due = 0
for ms in work:
now = ticks_add(now, ms)
due = ticks_add(due, PERIOD)
while ticks_diff(now, due) > 0:
due = ticks_add(due, PERIOD)
wait = ticks_diff(due, now)
now = ticks_add(now, wait)
print("start at", now) Variables
- just taken by the loop
- changed in this step
- passed by the loop
- read next
- where the loop stopped
Printed so far
Nothing printed yetstart at 1000 start at 2000 start at 6000 start at 7000
Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.
The drawing shows the variables when the program ends, and the program's 42 steps are listed below.
Change the program and run it here. Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Every step as text
- Start. No line has run yet, so there are no variables. Python starts with line 1.
- Line 1.
from ticks import ticks_addloadsticks_add. - Line 2.
from ticks import ticks_diffloadsticks_diff. - Line 4.
PERIOD = 1000setsPERIODto1000. - Line 5.
work = [40, 35, 3400, 45]makesworka list of 4 items. - Line 6.
now = 0setsnowto0. - Line 7.
due = 0setsdueto0. - Line 8.
for ms in work:takes the item at position 0 ofwork:msis40. - Line 9.
now = ticks_add(now, ms)setsnowto40(it was0). - Line 10.
due = ticks_add(due, PERIOD)setsdueto1000(it was0). Key point The next slot is the last slot plus the period, 1000, not the time now plus the period, 1040: the 40 ms of work do not move the rhythm. - Line 11.
while ticks_diff(now, due) > 0:tests its condition: it is false from the start (nowis40anddueis1000), so the body never runs. - Line 13.
wait = ticks_diff(due, now)setswaitto960. - Line 14.
now = ticks_add(now, wait)setsnowto1000(it was40). The loop sleeps for what is left, 960 ms, so the next reading starts at 1000 exactly. - Line 15.
print("start at", now)printsstart at 1000. - Line 8.
for ms in work:takes the item at position 1 ofwork:msis35. - Line 9.
now = ticks_add(now, ms)setsnowto1035(it was1000). - Line 10.
due = ticks_add(due, PERIOD)setsdueto2000(it was1000). - Line 11.
while ticks_diff(now, due) > 0:tests its condition: it is false from the start (nowis1035anddueis2000), so the body never runs. - Line 13.
wait = ticks_diff(due, now)setswaitto965(it was960). - Line 14.
now = ticks_add(now, wait)setsnowto2000(it was1035). - Line 15.
print("start at", now)printsstart at 2000. - Line 8.
for ms in work:takes the item at position 2 ofwork:msis3400. - Line 9.
now = ticks_add(now, ms)setsnowto5400(it was2000). This reading started at 2000 and took 3,400 ms, so its work ends at 5400, long after the slot at 3000. - Line 10.
due = ticks_add(due, PERIOD)setsdueto3000(it was2000). - Line 11.
while ticks_diff(now, due) > 0:tests its condition: it is true (nowis5400anddueis3000), so the body runs. - Line 12.
due = ticks_add(due, PERIOD)setsdueto4000(it was3000). - Line 11.
while ticks_diff(now, due) > 0:tests its condition: it is true (nowis5400anddueis4000), so the body runs again (pass 2). - Line 12.
due = ticks_add(due, PERIOD)setsdueto5000(it was4000). - Line 11.
while ticks_diff(now, due) > 0:tests its condition: it is true (nowis5400anddueis5000), so the body runs again (pass 3). - Line 12.
due = ticks_add(due, PERIOD)setsdueto6000(it was5000). Slots 3000, 4000 and 5000 passed during the work, so the loop skips them: the next slot is 6000, still on the grid. - Line 11.
while ticks_diff(now, due) > 0:tests its condition: it is false (nowis5400anddueis6000), so the loop ends. - Line 13.
wait = ticks_diff(due, now)setswaitto600(it was965). - Line 14.
now = ticks_add(now, wait)setsnowto6000(it was5400). - Line 15.
print("start at", now)printsstart at 6000. Key point The reading after the slow one starts at 6000, not at 6400: the rhythm is kept, and the skipped slots are not run in a burst. - Line 8.
for ms in work:takes the item at position 3 ofwork:msis45. - Line 9.
now = ticks_add(now, ms)setsnowto6045(it was6000). - Line 10.
due = ticks_add(due, PERIOD)setsdueto7000(it was6000). - Line 11.
while ticks_diff(now, due) > 0:tests its condition: it is false from the start (nowis6045anddueis7000), so the body never runs. - Line 13.
wait = ticks_diff(due, now)setswaitto955(it was600). - Line 14.
now = ticks_add(now, wait)setsnowto7000(it was6045). - Line 15.
print("start at", now)printsstart at 7000. - Line 8.
for ms in work:finds no item at position 4 ofwork(it has 4 items), so the loop ends. That was the last line, so the program ends. Every start the program printed, 1000, 2000, 6000 and 7000, is on the 1,000 ms grid, although one reading took 3.4 seconds.
The slow reading ended at 5,400 ms, after the slots at 3,000, 4,000 and 5,000. The loop skipped all three, and the next reading started at 6,000, on the grid. A logger usually wants this. A job that must run once for every slot, such as one that totals pulses per second, catches up instead and records how late each run was.
Key idea
Add the period to the last deadline, sleep only for what is left, and decide in advance what a late pass does with the slots it missed.
Checkpoint
Check: Periodic jobs without drift
The RTC keeps the calendar, and a Pico’s starts at 2021
Intervals come from ticks; dates come from the RTC: Real-time clock: the part of a microcontroller that keeps the calendar date and time, read and set with machine.RTC().datetime(). A Pico has no battery for it, so it starts again from a fixed date at every power-up.. machine.RTC().datetime() returns eight
numbers in its own order, (year, month, day, weekday, hours, minutes, seconds, subseconds), with weekday 0 for Monday.
A Pico has no battery for its RTC: at every power-up and every hard reset the rp2 port starts it at
2021-01-01 00:00:00, and on a Pico the subseconds always read 0.
The time module turns the RTC into seconds and tuples. time.time() counts whole seconds from the port’s
epoch: The moment from which a clock counts seconds: the start of 1970 on the rp2 and unix ports and in CPython, the start of 2000 on esp32 and other embedded ports. time.gmtime(0)[0] gives its year., and time.gmtime() and time.localtime() return
(year, month, mday, hour, minute, second, weekday, yearday): almost the same fields, in another order. That
difference is the commonest way to set a clock wrong. The model below sets the RTC once from a time tuple and once in
the RTC’s own order:
rtc/rtc_tuples.py
"""A Pico's RTC after power-up, set once with the wrong tuple and once with the right one."""
import board_time as time # on a board: import time
from board_time import RTC # on a board: from machine import RTC
def show(label, value):
print("%-18s %s" % (label, value))
rtc = RTC()
show("after power-up:", rtc.datetime())
show("time.time():", time.time())
show("epoch year:", time.gmtime(0)[0])
tm = time.gmtime(1_800_000_930) # a UTC time tuple
show("time tuple:", tm)
rtc.datetime(tm) # the wrong order
show("set from tm:", rtc.datetime())
y, mo, d, h, mi, s, wd, yd = tm
rtc.datetime((y, mo, d, wd, h, mi, s, 0))
show("set in RTC order:", rtc.datetime())
show("time.time():", time.time())
show("localtime() == gmtime():", time.localtime() == time.gmtime()) Output
after power-up: (2021, 1, 1, 4, 0, 0, 0, 0) time.time(): 1609459200 epoch year: 1970 time tuple: (2027, 1, 15, 8, 15, 30, 4, 15) set from tm: (2027, 1, 15, 4, 15, 30, 4, 0) set in RTC order: (2027, 1, 15, 4, 8, 15, 30, 0) time.time(): 1800000930 localtime() == gmtime(): True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rtc_tuples.py
board_time.py (the model) · rtc/board_time.py
"""The calendar side of a Pico's clock: machine.RTC and the time module's date functions (MicroPython 1.29.0, rp2).
From the rp2 port's source: at every hard start main.c sets the RTC to 2021-01-01 00:00:00 (ports/rp2/main.c);
RTC.datetime() reads (year, month, day, weekday, hours, minutes, seconds, 0), and a tuple it is given must have 8
items, of which it uses the year, month and day (items 0 to 2) and the hours, minutes and seconds (items 4 to 6),
with no range check, so 25 hours become 1 o'clock the next day (ports/rp2/machine_rtc.c, shared/timeutils). The
time module counts from the start of 1970 on this port and has no time zones: gmtime and localtime are the same
function (extmod/modtime.c). Not MicroPython: a model for computers. power_up() stands for a hard reset.
"""
import calendar
import time as _host_time
_seconds = 0 # what the RTC holds: seconds since the start of 1970
def power_up():
global _seconds
_seconds = calendar.timegm((2021, 1, 1, 0, 0, 0))
def _tuple(secs):
return tuple(_host_time.gmtime(secs)[:8])
class RTC:
def datetime(self, dt=None):
global _seconds
if dt is None:
y, mo, d, h, mi, s, wd, _ = _tuple(_seconds)
return (y, mo, d, wd, h, mi, s, 0)
if len(dt) != 8:
raise ValueError("requested length 8 but object has length %d" % len(dt))
days = calendar.timegm((dt[0], dt[1], dt[2], 0, 0, 0)) // 86400
_seconds = days * 86400 + dt[4] * 3600 + dt[5] * 60 + dt[6]
def time():
return _seconds
def gmtime(secs=None):
return _tuple(_seconds if secs is None else secs)
localtime = gmtime # the same function on the rp2 port: no time zones
def mktime(tm):
return calendar.timegm(tuple(tm[:6]))
power_up() Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Given the time tuple, the RTC read the hour where it expects the weekday, which it ignores, took the minutes for hours and so on, and kept 15:30:04 for 08:15:30 UTC, without a word. The two orders, position by position:
| Position | RTC().datetime() |
time.gmtime() and time.localtime() |
|---|---|---|
| 0 | year | year |
| 1 | month | month |
| 2 | day of the month | day of the month |
| 3 | weekday, 0 = Monday, ignored when set | hour |
| 4 | hours | minute |
| 5 | minutes | second |
| 6 | seconds | weekday, 0 = Monday |
| 7 | subseconds, 0 on a Pico | day of the year |
Epochs differ as well. The rp2 port counts from the start of 1970, as Unix, CPython and the unix port do; the esp32
port, like most other embedded ports, counts from the start of 2000, so the same moment is 946,684,800 seconds smaller
on an ESP32. time.gmtime(0)[0] tells a program which epoch it runs on.
One range of dates on every port since 1.26
MicroPython 1.26 made time(), localtime() and mktime() work on the same range of dates on every port, from
1970 up to at least 2099, whichever epoch the port counts from.
Something has to set the RTC after every start. Follow a Pico W from power-up to timestamps it can trust:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- The clock starts at 2021. At power-up, and after every hard reset, the rp2 port starts the RTC at the first second of 2021. A Pico has no battery for its clock, so nothing earlier survives.
- The app starts. boot.py and main.py run. A timestamp taken now says 2021, because time.time() is the RTC's count of seconds since the start of 1970.
- The network first. The app joins Wi-Fi. Until it has, nothing can tell the board what time it is.
- Ask a time server. ntptime.settime() asks an NTP server, pool.ntp.org unless ntptime.host says otherwise, waits up to ntptime.timeout, 1 s, and writes the answer into the RTC in UTC. No answer raises OSError.
- Now trust it. From here time.time(), gmtime() and localtime() are right, and all three give UTC, because the board has no time zones.
- A soft reset. Ctrl-D or machine.soft_reset() leaves the RTC alone, and the boot files run again with the right time.
- A hard reset. machine.reset(), the reset button or a watchdog starts the clock at 2021 again, so the app must set it again before it trusts a timestamp.
A board’s time is right only after the last stage, and a hard reset sends it back to the first. So an app that stamps data keeps a flag that says whether the clock has been set since the start, and until it has, it marks its readings unsynchronised or holds them back.
Checkpoint
Check: The RTC, its tuple and the epoch
Store UTC on the device, and show local time only to people
A board has no time-zone database. On the rp2 and esp32 ports time.gmtime and time.localtime are one and the same
function, so local time is whatever was written into the RTC. On boards with a network, the Pico W and the ESP32,
the firmware carries ntptime from micropython-lib: ntptime.settime() asks an NTP: Network Time Protocol: a device asks a time server for the current time over UDP port 123. MicroPython's ntptime module uses it to set a board's RTC to UTC. server,
pool.ntp.org unless ntptime.host names another, waits up to ntptime.timeout, 1 second, and writes
UTC: Coordinated Universal Time, the reference time scale that servers and devices use to agree on a moment. Local times are fixed offsets from it, such as UTC+05:30 in India. into the RTC, because, as its source says, MicroPython has no time-zone support.
Keep it that way: store and send UTC, as seconds since the start of 1970 or as an RFC 3339 timestamp ending in Z,
and convert to a local time only where a person reads it. This program, which runs unchanged on a board, does it for
one reading, as a Pico and as an ESP32 count it:
"""One reading's time, as a Pico and an ESP32 count it, as it is stored and sent, and as a person in India reads it.
The same code runs on a board: the time module there counts from 1970 (rp2) or from 2000 (esp32), and
time.gmtime(0)[0] says which.
"""
import time
IST = 5 * 3600 + 30 * 60 # India: UTC+05:30, all year
EPOCH_2000 = 946_684_800 # seconds from the start of 1970 to the start of 2000
def unix_time(board_secs, epoch_year):
"""Seconds since the start of 1970, from a board's time.time()."""
return board_secs + (EPOCH_2000 if epoch_year == 2000 else 0)
def iso_utc(t):
return "%04d-%02d-%02dT%02d:%02d:%02dZ" % tuple(time.gmtime(t)[:6])
def iso_ist(t):
return "%04d-%02d-%02dT%02d:%02d:%02d+05:30" % tuple(time.gmtime(t + IST)[:6])
pico = 1_800_000_930 # time.time() on a Pico
esp32 = pico - EPOCH_2000 # the same moment on an ESP32
print("Pico time.time(): ", pico)
print("ESP32 time.time(): ", esp32)
print("both as Unix time: ", unix_time(pico, 1970), unix_time(esp32, 2000))
print("stored and sent: ", iso_utc(pico))
print("shown in India: ", iso_ist(pico))
# mpremote rtc --set copies the computer's local time into the RTC.
# On a laptop in India, the board's RTC then runs 5 h 30 min ahead of UTC.
wrong = pico + IST
print("after mpremote rtc --set in India:", iso_utc(wrong), "(%d s ahead)" % (wrong - pico)) Output
Pico time.time(): 1800000930 ESP32 time.time(): 853316130 both as Unix time: 1800000930 1800000930 stored and sent: 2027-01-15T08:15:30Z shown in India: 2027-01-15T13:45:30+05:30 after mpremote rtc --set in India: 2027-01-15T13:45:30Z (19800 s ahead)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 utc_ist.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Both counts are the same moment once the ESP32’s is moved from its epoch to 1970, and the stored form names its zone,
Z. The last line is a trap of a tool you use every day: mpremote rtc --set copies the computer’s local clock into
the board’s RTC, and the board, with no zones, takes it for UTC.
IST is UTC+05:30, all year
IST runs five and a half hours ahead of UTC throughout the year, and India has no daylight saving time, so
showing a UTC time in India always adds 19,800 seconds. A board set with mpremote rtc --set from a laptop in
India runs 19,800 seconds ahead of UTC, and a server that reads its timestamps as UTC places every reading five and
a half hours in the future.
Follow that trap from the laptop to the server:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Two clocks agree. The laptop's clock shows 13:45:30, India time. The same moment is 08:15:30 UTC.
- rtc --set. mpremote rtc --set reads the laptop's local time with Python's datetime.now() and writes 13:45:30 into the board's RTC, with no zone attached.
- The board believes it. The board has no time zones, so its RTC now says 13:45:30 UTC, and time.time() runs 19,800 s, five and a half hours, ahead.
- A reading goes out. The board stamps a reading with time.time() and sends it as 13:45:30Z, the Z meaning UTC.
- In the future. The server shows the reading at 19:15:30 India time, five and a half hours after it was taken.
The cure is to set the clock in UTC: ntptime.settime() on a board with a network, or, on a board without one, an
mpremote exec that writes a UTC tuple, in the RTC’s order, into machine.RTC().datetime().
Key idea
Keep UTC wherever a machine reads the time, and convert to IST, or any other zone, only on the screen a person looks at.
Checkpoint
Check: UTC on the device, local time on screen
Practise: a periodic job on a wrapping clock
Write the helper a deadline loop asks on every pass: is the job due now? It keeps the slots on their grid whatever the loop does, skips and counts the slots a stall missed, and stays right when the counter wraps. The two tick functions sit at the top of the starter, copied from MicroPython’s arithmetic, and the sample tests drive a fake clock:
Exercise · Medium · Python
A periodic job that never drifts and never bursts
Write the Every class in every.py. A main loop asks it, as often as it likes, whether a job that repeats every period_ms milliseconds is due, so the loop never has to sleep through the time between two runs.
Every(period_ms, clock) reads the clock once and starts the first period there: clock() returns tick values in milliseconds, as time.ticks_ms() does on a board. due() returns True once for each slot that has come, where the slots lie exactly period_ms apart from that start, and False otherwise. A call made late still keeps the next slot on that grid, so the job never drifts. When several slots passed without a call, due() returns True once, skips the others and adds how many it skipped to missed, so a long stall never turns into a burst of runs.
Tick values wrap at TICKS_PERIOD, 2**30, as on a Pico: compare them only with the ticks_diff() and ticks_add() at the top of the file, which work as MicroPython's do. A period_ms below 1 raises ValueError. The sample tests drive a fake clock, and one of them starts 5 ms before the wrap.
Starter code · every.py
TICKS_PERIOD = 1 << 30 # ticks wrap here on 32-bit ports such as the Pico's
def ticks_add(ticks, delta):
"""MicroPython's time.ticks_add(): the tick value delta ms after ticks."""
return (ticks + delta) & (TICKS_PERIOD - 1)
def ticks_diff(ticks1, ticks2):
"""MicroPython's time.ticks_diff(): ticks1 - ticks2, right across the wrap."""
half = TICKS_PERIOD // 2
return ((ticks1 - ticks2 + half) & (TICKS_PERIOD - 1)) - half
class Every:
"""Says when a job that repeats every period_ms is due, without drift and without bursts."""
def __init__(self, period_ms, clock):
self.period = period_ms
self.clock = clock
self.missed = 0
def due(self):
"""True once for each slot that has come; slots that passed unseen are skipped and counted in missed."""
return False The sample tests · test_every.py
from every import TICKS_PERIOD, Every
class FakeClock:
def __init__(self, t=0):
self.t = t
def __call__(self):
return self.t
def calls(every, clock, times):
seen = []
for t in times:
clock.t = t
seen.append(every.due())
return seen
def test_first_slot():
"""is due one period after it starts, and not a millisecond before"""
clock = FakeClock(0)
job = Every(1000, clock)
assert calls(job, clock, [1, 999, 1000]) == [False, False, True]
def test_once_per_slot():
"""says True once per slot when the loop asks every millisecond"""
clock = FakeClock(0)
job = Every(1000, clock)
times = [t for t in range(0, 3501) if calls(job, clock, [t])[0]]
assert times == [1000, 2000, 3000]
def test_no_drift():
"""keeps the slots on the grid when a call comes late"""
clock = FakeClock(0)
job = Every(1000, clock)
assert calls(job, clock, [1007, 1990, 2000, 2999, 3002]) == [True, False, True, False, True]
def test_skips_missed_slots():
"""runs once after a long stall, counts the skipped slots and keeps the grid"""
clock = FakeClock(0)
job = Every(1000, clock)
assert calls(job, clock, [5400, 5400, 5999, 6000]) == [True, False, False, True]
assert job.missed == 4
def test_across_the_wrap():
"""keeps time when the tick counter wraps to 0"""
start = TICKS_PERIOD - 5
clock = FakeClock(start)
job = Every(10, clock)
assert calls(job, clock, [TICKS_PERIOD - 1, 4, 5, 14, 15]) == [False, False, True, False, True]
def test_bad_period():
"""refuses a period below 1 ms"""
for bad in (0, -10):
try:
Every(bad, FakeClock(0))
except ValueError:
continue
raise AssertionError("Every(%r, clock) did not raise ValueError" % (bad,)) A hint
Keep the tick value of the next slot in the object. In due(), late = ticks_diff(clock(), self.next): a negative late means not yet. Otherwise late // period_ms slots after the next one have passed as well: count them in missed, and move self.next on by all of them plus one with ticks_add().
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Then a self-check on ticks, waits, drift, the RTC and UTC: it asks about your weakest idea first, and explains every answer as soon as you pick it:
Practice that adapts to you
Practise this lesson
12 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): What is the difference between time.ticks_ms() and time.time() on a microcontroller?
ticks_ms() is a millisecond counter with no fixed starting point: on a Pico it counts from the moment the board
started, and on 32-bit ports it wraps to 0 every 2³⁰ ms, about 12.4 days. It is for intervals and deadlines, compared
only with ticks_diff() and moved only with ticks_add(). time.time() is whole seconds since the port’s epoch, the
start of 1970 on rp2 and of 2000 on esp32, read from the RTC. It is for timestamps, and it is right only once something
has set the RTC, for example ntptime.settime(), because a Pico’s RTC starts at the beginning of 2021 after every
power-up. Use ticks inside one run of the program and time.time(), in UTC, for anything stored or sent.
Two longer interview questions on time start from this lesson:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
Sensor boards in a greenhouse send a reading every minute. After 30 days the newest readings from the Pico boards are 26 minutes behind the server's clock, and every reading from the ESP32 boards appears about 30 years in the past. Explain both faults and how you would fix them.
-
Mid to senior
A Pico W opens an irrigation valve at 06:00 India time every day. Design how the board knows the time, from power-up to the daily trigger, and say what it does when the network is down or the clock has just been corrected.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
For how long is ticks_diff() right?
While the two readings are less than half a period apart: 6.21 days for ticks_ms() and 8.95 minutes for ticks_us() on 32-bit ports.
-
Can a tick value saved before a reset be compared after it?
No: after a hard reset a Pico's ticks_ms() counts from 0 again. Use ticks within one run of the program, and store time.time() for anything that must outlive a reset.
-
How long does time.sleep(0.0004) wait on a Pico?
No time at all: sleep() keeps only whole milliseconds, so 0.4 ms becomes 0. sleep_us(400) does wait, as a busy-wait that runs no scheduled callbacks.
-
How does a periodic job avoid drift?
It makes each deadline from the last one, due = ticks_add(due, PERIOD), and sleeps only for what is left, ticks_diff(due, now). Sleeping a whole period after the work adds the work's time to every pass.
-
In what order does RTC().datetime() take and give its fields?
(year, month, day, weekday, hours, minutes, seconds, subseconds), with weekday 0 for Monday. time.gmtime() puts the hour where the RTC expects the weekday, so passing its tuple sets the clock wrong without a word.
-
What time does a Pico's RTC show after power-up?
The first second of 2021: with no battery, it starts there again at each power-up and each hard reset, so the board's timestamps are years out until something sets the clock.
-
Which epochs do the rp2 and esp32 ports count from?
rp2 from the start of 1970, as Unix and CPython do; esp32 from the start of 2000, so the same moment is 946,684,800 seconds smaller. time.gmtime(0)[0] tells a program which.
-
Why keep a board's clock in UTC, and what does mpremote rtc --set do?
Boards have no time zones, and ntptime.settime() writes UTC. mpremote rtc --set copies the computer's local clock, so a laptop in India sets the board 19,800 seconds ahead of UTC.
Key takeaways
- The tick counters time intervals: compare two readings with
ticks_diff()and make deadlines withticks_add(), and check a deadline well within half the period, 6.21 days forticks_ms()and 8.95 minutes forticks_us(). sleep_ms()rests and gives scheduled callbacks their turn but holds the loop;sleep_us()spins for waits up to 1 ms; a deadline loop withmachine.idle()waits and keeps listening.- A periodic job made from its last deadline never drifts, while sleeping for the period after the work drifted 343 seconds in a day, and a deadline loop decides in advance whether it skips missed slots or catches up.
RTC().datetime()puts the weekday in position 3, unliketime.gmtime(), a Pico’s RTC starts at 2021 after every hard reset, and an ESP32’s epoch is 2000, 946,684,800 seconds after the start of 1970.- Boards have no time zones: set the RTC to UTC with
ntptime.settime(), store and send UTC, and add 19,800 seconds only to show a time in India.
Terms in this lesson
- busy-wait
- A wait in which the processor runs a tight loop that checks the time until it is up, doing nothing else meanwhile. On a Pico, time.sleep_us() waits this way for delays of up to 1,000 microseconds.
- drift
- A timing error that grows with every repetition, as when a loop sleeps for its whole period after its work, so that each run starts a little later than the one before.
- epoch
- The moment from which a clock counts seconds: the start of 1970 on the rp2 and unix ports and in CPython, the start of 2000 on esp32 and other embedded ports. time.gmtime(0)[0] gives its year.
- NTP
- Network Time Protocol: a device asks a time server for the current time over UDP port 123. MicroPython's ntptime module uses it to set a board's RTC to UTC.
- RTC
- Real-time clock: the part of a microcontroller that keeps the calendar date and time, read and set with machine.RTC().datetime(). A Pico has no battery for it, so it starts again from a fixed date at every power-up.
- UTC
- Coordinated Universal Time, the reference time scale that servers and devices use to agree on a moment. Local times are fixed offsets from it, such as UTC+05:30 in India.
References
- time – time related functions (MicroPython project)
- class RTC – real time clock (MicroPython project)
- machine – functions related to the hardware (machine.idle) (MicroPython project)
- Quick reference for the RP2 (delay and timing, real time clock) (MicroPython project)
- Quick reference for the ESP32 (real time clock) (MicroPython project)
- MicroPython remote control: mpremote (rtc) (MicroPython project)
- Reset and Boot Sequence (what a soft reset keeps) (MicroPython project)
- MicroPython v1.26.0 release notes (one date range on every port) (MicroPython project)
- Tick functions and sleeps (extmod/modtime.c, v1.29.0) (MicroPython project)
- Tick period of 2**30 on 32-bit ports (py/mpconfig.h, v1.29.0) (MicroPython project)
- rp2 delays, sleep_ms and sleep_us (ports/rp2/mphalport.c, v1.29.0) (MicroPython project)
- rp2 tick sources, ticks_cpu (ports/rp2/mphalport.h, v1.29.0) (MicroPython project)
- rp2 start-up, the RTC's first value (ports/rp2/main.c, v1.29.0) (MicroPython project)
- rp2 RTC.datetime (ports/rp2/machine_rtc.c, v1.29.0) (MicroPython project)
- mpremote rtc --set (tools/mpremote/mpremote/commands.py, v1.29.0) (MicroPython project)
- ntptime (micropython-lib, the v1.29.0 submodule) (MicroPython project)
- bundle-networking, frozen into Pico W and ESP32 firmware (micropython-lib) (MicroPython project)
- RFC 5905: Network Time Protocol Version 4 (IETF)
- RFC 3339: Date and Time on the Internet: Timestamps (IETF)
- Time Zone Database, asia (Asia/Kolkata, IST) (IANA)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress