Embedded Mastery Series · Volume 1 · Companion
Bench Tools
Mac- and Linux-side helper scripts that pair with the Volume 1 labs. These don't run on the NUCLEO — they run on your development host, providing the network endpoints, fixtures, and scaffolding the embedded labs need.
fake_ntp_server.py — Local SNTPv4 server for §S12.2
The Stage 12.2 lab points the board's SNTPv4 client at
192.168.100.230:123. Rather than rely on the public NTP
pool (which would require routing the bench from the P2P
192.168.100.0/24 subnet onto the wider internet), the lab runs a
minimal SNTPv4 server on your Mac that responds with the host's
current time. About 50 lines of stdlib Python; no third-party
dependencies.
Setup
- Plug the Ethernet adapter into the Mac and wire it directly to the NUCLEO's CN14 RJ45.
- Configure the Mac's adapter to a static IP on the bench subnet:
192.168.100.230/255.255.255.0, no gateway, no DNS. (System Settings → Network → that adapter → Details → TCP/IP → Configure IPv4 = Manually). - Save
fake_ntp_server.pybelow to your Mac. - Run with
sudo(UDP port 123 is privileged):
sudo python3 fake_ntp_server.py
Expected output once the board boots and starts polling:
[fake-ntp] listening on 0.0.0.0:123
[fake-ntp] replied to 192.168.100.10:54321 with ntp_seconds=3964258245
[fake-ntp] replied to 192.168.100.10:54321 with ntp_seconds=3964258250
On the board's VCP you should see human-readable UTC timestamps updating every 5 s:
NTP sync: 2026-05-03 16:30:45 UTC (unix_ts=1777854645)
NTP sync: 2026-05-03 16:30:50 UTC (unix_ts=1777854650)
Source — fake_ntp_server.py
--- BEGIN EMBED:fake_ntp_server_py ---
#!/usr/bin/env python3
"""Minimal SNTPv4 server for V1 S12.2 NTP client lab.
Listens on UDP :123, responds with current Unix time converted to NTP
epoch (seconds since 1900-01-01). Bind to 192.168.100.230 to match the
board's hard-coded NTP_SERVER_IP. Run with sudo (port < 1024).
"""
import socket
import struct
import time
NTP_TO_UNIX_OFFSET = 2208988800 # seconds between 1900-01-01 and 1970-01-01
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("0.0.0.0", 123))
print("[fake-ntp] listening on 0.0.0.0:123")
while True:
data, addr = sock.recvfrom(2048)
if len(data) < 48:
continue
ntp_seconds = int(time.time()) + NTP_TO_UNIX_OFFSET
ntp_fraction = 0
# LI=0, VN=4, Mode=4 (server) → 0x24
# Stratum=1, Poll=4, Precision=-20 (~1us)
resp = bytearray(48)
resp[0] = 0x24
resp[1] = 1 # stratum 1
resp[2] = 4 # poll
resp[3] = 0xEC # precision -20 (signed int8)
# Reference identifier 'LOCL'
resp[12:16] = b"LOCL"
# Reference timestamp
resp[16:20] = struct.pack(">I", ntp_seconds)
resp[20:24] = struct.pack(">I", 0)
# Originate timestamp = client's transmit timestamp (bytes 40..47 of req)
resp[24:32] = data[40:48]
# Receive timestamp
resp[32:36] = struct.pack(">I", ntp_seconds)
resp[36:40] = struct.pack(">I", 0)
# Transmit timestamp
resp[40:44] = struct.pack(">I", ntp_seconds)
resp[44:48] = struct.pack(">I", ntp_fraction)
sock.sendto(bytes(resp), addr)
print(f"[fake-ntp] replied to {addr[0]}:{addr[1]} with ntp_seconds={ntp_seconds}")
if __name__ == "__main__":
main()
--- END EMBED:fake_ntp_server_py ---
Other bench helpers
The Volume 1 bench loop ships a small set of build/flash/capture
shell scripts under bench/ in the GitHub repo
(iterate.sh, flash.sh, capture.sh,
vcp.sh) — these automate the headless STM32CubeIDE
build, ST-Programmer-CLI flash, and tio-based VCP
capture. They're convenience for the maintainers' day-to-day work,
not required for the customer's normal lab flow (the labs are
written to be run interactively in STM32CubeIDE).
If you want the same automation on your bench: clone
github.com/pbwarmu017/Mastering-Series-Books and
run bench/README.md's setup steps.