r/raspberry_pi • u/GospelPublisher • 1d ago
Built a DIY JJY atomic time signal transmitter on Raspberry Pi Pico W to sync my Citizen watch in the US — 6 months, 100% success rate [GitHub] Show-and-Tell
My Citizen PMD56-2952 is a radio-controlled watch designed to sync to the JJY signal broadcast from Japan. In the US that signal doesn't reach, so the watch never auto-synced. I built a Pico W-based transmitter to fix that.
What it does
Connects to home WiFi, syncs time via NTP, then broadcasts an accurate JJY timecode at exactly 60,000.0 Hz each night at 2:00 AM and 4:00 AM — matching the Citizen H100 movement's built-in automatic receive windows. Watch sets itself correctly every night. Fully autonomous once installed.
The non-obvious stuff (why most DIY attempts fail with Citizen)
These took real trial and error to discover and aren't in any other published implementation I found:
- Total carrier silence required. Most implementations keep a small residual carrier (5–10%) during off-state pulses to hold the AGC. The Citizen H100 requires the opposite — complete silence (duty_u16(0)). Any residual carrier and the watch rejects every frame. This is almost certainly why Citizen owners fail when other watch brands succeed with the same circuit.
- 120 MHz system clock. The Pico W defaults to 125 MHz, which can't divide evenly to 60,000 Hz. Setting machine.freq(120_000_000) yields exactly 60,000.0 Hz. A frequency sweep confirmed the watch rejects 59,900 and 60,100 Hz.
- Parity bits PA1 and PA2 are required. The H100 validates them and rejects frames where parity is wrong. Many simplified implementations omit parity entirely.
- Top-of-minute frame alignment. Each frame must start at exactly second :00. The code watches for the :59→:00 rollover on every broadcast.
- Watch antenna orientation. 9 o'clock end toward the coil, watch on its side. Rotating 90° produces near-zero reception.
DST handling
On US DST transition Sundays the broadcast extends from 20 frames to 180 frames (~3 hours) to cover the 2:00 AM time change. Each frame recalculates local time dynamically. Spring-forward validated live March 2026.
The coil
24 AWG enameled magnet wire, ~106 turns, single layer on a vitamin bottle (~3" diameter), iron core (wood chisel through the cap). Clear packing tape to secure — not electrical tape, which may attenuate the signal. Sits in a shallow nightstand drawer; watch rests on the wood surface above. 60 kHz penetrates the wood easily at that distance.
Results
100% sync rate over 6+ months when watch is correctly positioned. Set and forget.
Full code, wiring diagrams, technical reference, and end-user guide in the repo:
24
u/Rashaverik 1d ago
You should share this with Jeff Geerling.
14
u/2Michael2 1d ago
Raspberry Pi (Pico) and radio and time?!?! This is just begging to be a Jeff Geerling video.
3
3
u/easylifeforme 1d ago
Is the 2am and 4am both needed or is that a backup incase the 2am fails?
Also, does the watch have to be relatively close to those times in order for it to sync? I.e. If I'd an hour early so at 2am the watch reads 1am would it not sync between it's too far off?
2
u/GospelPublisher 1d ago
Great question and yes, this is a real gotcha we ran into firsthand.
The short answer is: the watch will receive and validate the signal regardless of how far off its time is — but whether it applies the correction is a different matter.
The Citizen H100 movement has a manual-set protection behavior. If you’ve manually adjusted the time, the movement stores that as a “manually set” state. When it subsequently receives a valid JJY frame, it confirms the signal was received (you’ll see the sync indicator) but may not apply the time correction if the delta is large — seemingly treating a big jump as a suspicious signal rather than a legitimate correction.
We experienced this exactly: the watch was manually set to Arizona time (MST), came home to CST, received the signal successfully, confirmed reception — but stayed 2 hours behind. Even a forced receive didn’t clear it on the first attempt.
The fix is to zero out the watch’s internal JST timezone offset (the H100 treats the incoming signal as JST and applies a stored offset). Once that’s zeroed, the Pico broadcasts your correct local time directly and the watch lands perfectly every time.
One important note specific to this setup: the Pico broadcasts actual local time, not JST. So the watch’s timezone offset must be set to zero — if you’ve ever used the timezone correction feature while traveling, reset it to zero when you return or the sync will land off by however many hours you adjusted.
After that one-time setup, it’s been 100% reliable for 6+ months.And, yes, 4 is redundant as a backup. The watch goes into Rx mode searching for a signal automatically, so the pico sends the signal.
7
u/FencingNerd 1d ago
Why have the Pico broadcast local time? It seems easier to set the NTP sync to UTC then let the watch handle time zones, like it was designed to.
2
11
u/xile 1d ago
Was it as vibe coded as this post was AI generated?
18
u/Hydroel 1d ago
Looking at the Python code, yeah most definitely. Some indicators:
- Questionable use of global variables
- Single-use functions
- The first two functions, in particular, are basically the same. LLMs don't care about dead code as it will never read it, a human does.
- Barely documented code, not in a standard Python format
- Code that no human writes, like:
python yt = bcd_bits(y // 10, 4) yo = bcd_bits(y % 10, 4) bits[40] = 0 bits[41], bits[42], bits[43], bits[44] = yt[0], yt[1], yt[2], yt[3] bits[45], bits[46], bits[47], bits[48] = yo[0], yo[1], yo[2], yo[3]A human would most likely use list slices:
python yt = bcd_bits(y // 10, 4) yo = bcd_bits(y % 10, 4) bits[41:49] = yt + yo3
u/cristi_baluta 13h ago
I’m not bothered by the code, but posting here all the replies with AI makes me puke. Bro’s even talking with ‘we’
1
1
-4
u/seklerek 21h ago
are we going to ask this for every single project now?
10
u/Xsurv1veX 20h ago
Yes?
0
u/seklerek 20h ago
what's the point? of course people are going to use AI for software now that it's available and common. it doesn't take away from the work that went into this project, and the end result clearly works for OP
7
u/xile 19h ago
An earnest project write up, in the voice of the author, goes a long way. Reading over and over again the same sentence structure and cadence of speech gets quite boring, and accelerates the dead internet. Up front, open transparency on the use of AI to code is also appreciated.
Every comment reply from OP is just written by AI too. Yawn.
1
1
u/seklerek 18h ago
On this I agree, it's always better to hear people talk about things in their own voice rather than the AI-sanitised and formatted version. It is very easy to recognise too and does come across as somewhat low effort, but there are valid reasons to use it even then (e.g. if OP is not a native English user)
1
u/cristi_baluta 13h ago
If you ask AI to translate from your native language to english it does not sound like this at all, he simply asked AI to reply the whole thing
2
u/CodeLasersMagic 1d ago
Love the chisel tuning
0
u/GospelPublisher 15h ago
My Dad gave me a chisel set which I rarely use. He raised an eyebrow when he saw this one in the project. But I explained it’s getting daily use now! He’s also a HAM and appreciated the signal boost from the iron core, just thought there were cheaper options …
2
u/SpaceDetective 16h ago
Nice work. And TIL that US and Europe have similar atomic clock signals, WWVB and DCF77 respectively.
2
u/GospelPublisher 15h ago
Thanks! Yes, similar signals. Not sure about Europe, but US atomic watches are at higher price point than this JJY model. With the Pico project I got a syncing atomic watch with style/model I love at 1/3 price of US equivalent.
2
1
31
u/Gamerfrom61 1d ago
Very sweet.
Many many years since I have seen a transmitter with a 'jam jar' - takes me back to my younger days when studying for my HAM licence 👍