Close
0%
0%

A more precise Tuning Fork Clock

A clock that uses a tuning fork as a time reference, but calibrated!

ppPP
Similar projects worth following
I am building this clock (long) after being inspired by another project I saw on hackaday (https://hackaday.io/project/177317-tuning-fork-clock).
I really liked the concept of using something tangible and audible as a reference that we nowadays take for granted - time precision!

After the original author's youtube video comment, his/her design is about 60 to 120 seconds off per day, so I naturally wanted to throw computer science and statistics at it and see how that can be improved with a temperature measurement.
After all, TCXOs do the same, but much smaller (and more precise ;) )

As as secondary requirements, I wanted to 1. reduce the harsh audible tuning-fork tone, and 2. use a new (to me) display that I like to see (and 3. to keep the overall style to some kind of "art")

So here is my take at it :)

For the clock to know how much time has passed, it measures the number of tuning-fork oscillations.

But how much time has passed after a certain number of oscillations?
This is where an estimator comes into play. We need to estimate the frequency of the tuning fork with a given model and a given environment input.

Roughly speaking: If the model says: "Yup, at this temperature the fork oscillates at 440.1 Hz", then the passed time can be derived from the number of measured oscillations (let's say, 1000) by `1000 * 440.1 Hz = 2.272210861s`. The easiest model is that the tuning fork will always resonate at 440Hz, as the original project used.
They got drift of around 60-120 seconds per day, which is a drift of around 1ms/s.
(As a side note: phase noise is not considered in this project, but only drift.)

Nice, but how does the model know that? And what kind of model is that? And how precise can it get?

These are the main project questions that I wanted to find out.

For that, I separate the usage into two phases: Measurement and Application.

In the measurement phase, I use a precise reference* to log the actual frequency against the temperature.
Then, offline on my main computer, I try to find and apply functions that can model the seen behavior, and find/optimize the parameters to fit the measured frequency as good as possible.

(*precise reference: First the internal Pico's OSC, then I got my hands on a nice precise GPS Megahertz Timing Reference)

In the application phase, I then use the offline-trained (No AI! Only statistics!) now-constants for the given model in the clock to estimate the frequency and thus, the passed time since startup. Showing the actual current time is then nothing more than adding an time offset at setting-the-clock on top of the passed-time-since-setting-clock (see github repo).

So, just to wrap up: We shift what is "knowns" and "unknowns" in the phases. First, in the Fork Measurement Phase, we know the Fork frequency and temperature, but don't know the model parameters that fit the behavior.
Then, in the Application Phase, we don't know the true fork frequency, but know the model, its parameters, and the temperature to estimate that!

Concept of Measurement Phase

I had a lot of surprising influences on precision. Currently, with a fresh calibration, without standing in direct sunlight, and with a not-too-bright display, it achieves a drift of ~15 seconds per month, which results in around 5µs/s of drift! That is a factor of 250 better than uncalibrated, which I consider a project success :)

  • Tuning the Feedback Circuit

    PP08/30/2025 at 17:08 0 comments

    In the previous Post, I described that I have sporadic overtones in the Fork measurement which slightly shifts the base frequency.

    The main Feedback is this (taken from the Inspiration's Project):
    There, R4 and R5 together with C2 and C3 form a Bandpass filter. Mainly I suspected the large C3 (High-Pass) to be too big.

    So I used my "trusty" DS203 oscilloscope and attached Probes to ForkOut (Yellow) and Pin 3 of the Tuning Fork Inductor (Blue). It is labelled 47^3 because of the 47 nF Capacitor at C3.

    Notice the sharp edges  at the low and high spots of the Yellow trace, as soon as the blue trace (the driving coil) either Turns on or the first step-off. (I don't know why it does the first step down. Well, I am not an Electrical Engineer...) 

    Then I tried different Values for C3. From Top to bottom: 47^3, (10+22)^3, 10^3, and 68^2.

    You can notice the timing of the sharp edges being later with a smaller value, and also the drive of the Fork is more smooth with a lower value. Probably the Transistor was overloaded, but I don't know (Not an EE...)

    The Final Fork signal, after choosing a single 10nF Capacitor for C3 and moving the Fork until the trace looks optimal, I am much happier with the ForkOut signal, because it looks much more like an actual sine wave:

    Well, now off to a new test run for logging :)

  • Analysis of final Verification Run before One-Herz-Challenge

    PP08/17/2025 at 18:03 0 comments

    As the deadline for the one hertz challenge approaches, I need to conclude the current validation test run. The changes mentioned in the previous post - mainly the hot display - increased precision quite a lot. However, as one can see in the previous log run, the Oscillating Feedback Loop on the Fork seems to sometimes produce noise overtones which seems to slightly change the base frequency. I will address that by tuning the feedback loop capacitors, but sadly this is too late for the Challenge entry. And only the hard facts count!

    For the good side: The Drift, before the noise kicks in (Phase A), is around -0.5 seconds per day, which is a great project success for me. And in the last Phase C, when the frequency seems to settle, so does the drift (remember: Measured against a one-megahertz reference GPS "ground truth").

    And to allow everyone to look at it themselves, the raw data of that graph is published here.

    The other part of the Project was also the question what kind of Model is needed to estimate this System. This is fortunately answerable :)
    The final Process Model consists of 1. a rolling-average Temperature Dampening to estimate the temperature of the Fork based on the Sensor, 2. a two-degree polynomial for the Fork's frequency response, and 3. another two-degree polynomial for an estimation compensation based on the Temperature-Change-Rate to accommodate the warm-up of the fork for fast temperature changes. But, as you can see in the factors, the (3) Temperature-change-rate has a very small influence on the overall system, but increases the fit by a minor percentage.

    The Model Parameters that were used for this validation run are based on the previous run's data (see my last post) and was estimated to the following:


    Dampen factor b_d: 0.001893494029314581
    Factors for damped period estimation:
    b_0 = 9.885662144472119398e+05 (Base period per 440 oscillations at zero degrees)
    b_1 = 1.213906162853203297e+00 (linear temperature dependency)
    b_2 = 8.097113008716144879e-06 (exponential dependency: very small if any)
    Factors for period error estimation based on temp gradient:
    b_0 = -6.554192502536039244e-01 (constant error offset)
    b_1 = -1.555599069694961478e-01 (rate-change dependent error)
    b_2 =  2.188748997581093855e-02 (??)

    So, in conclusion: It is very possible to reach less-than-one-second per day of drift with a cheap tuning fork, a basic self-exciting circuit, and a lot of statistics and math :)
    I am sure that this can be done better, and I will also try to find out what in the circuit causes the noisy overtones / frequency nudge.

    But nevertheless, this project was very fun. I hope to also have sparked some interest, it sure did a lot for me. Bye!

  • A log without Temperature Influence of the Display

    PP08/12/2025 at 11:43 0 comments

    One of the things that go with a statistics project is, that you... well need a lot of samples.

    So after one week of sampling, I can conclude that the actions mentioned in the previous post (Mostly a less bright display setting) improved the correlation between temperature measurement and fork frequency by a lot:

    Correlation between measured temperature and fork frequency

    Also, the floating-average-factor (damp factor) is smaller now, which indicates a tighter temperature vs. frequency coupling (by time).

    The Estimation of drift also seems very good with a plateau after a very noisy initial start.
    Note That:
    1. The Absolute Drift (in red) stays roughly the same after the initial noise period
    2. The Per-Sample Estimation Differences are scaled so that they can be even seen
    3. I have too many lines in this plot. :D 

    Estimation of Performance

    Now, to deliver a final precision figure just in time for the end of the One-Hertz-Challenge, I used this week's data to estimate the final model parameters. The following week will then deliver real-world measured precision data based on the current settings.

  • Tricked by Myself and the Power-Hungry Display

    PP07/27/2025 at 21:11 0 comments

    While the test-run with the sub-microsecond estimation precision is running, I was reviewing the logs from the previous run:

    ... which was not too great considering some data loss and an unstable GPS readout.
    Small side-note: actual pulse counter was working, but only the register readout had a too-small timeout value. This is a topic for another post, but with the reference clock being switched back- and forth, you also see the drift of the internal clock compared to the GPS.

    But you also see periodic temperature drops, which made me wonder where they come from. Is some abrupt heating / cooling happening when I am not in the office?

    But after watching the Video of CuriousMarc about the Display and remembering "Taking a lot of power"... It hit me.

    Do you see it? I dim the display at nighttimes to reduce Eye-Strain and in that glass-dome the "dayBrightness" (5 of 7) actually heats up the thing! And to make matters worse, the heat rises up to the Temperature Sensor, but most of the Tuning Fork is actually below the display, making it worse to correlate measured temperature with actual tuning fork temperature.
    So my first change will be to set it to "medium brightness" all the time to not throw the measurement off, and on the long run probably will drill a vent-hole into the Glass-Dome's top to allow the air to circulate (and / or put the temperature sensor on the bottom half).

    Weird Problem to have; but when the PPMs count, this is actually measurable in the precision!

    Here in the last image you see that the "Difference per period" (which is a measure of precision) is actually worse (most off-zero) when the system is non-uniformly heated by the display.

  • Chasing the PPMs - Fixed / floating precision

    PP07/21/2025 at 21:57 0 comments

    I had a shower-thought: Most or all of my test runs tend to drift into the direction "too fast".
    And I was talking about a microsecond of resolution per period being at the limit of a second per day....


    The code is correctly estimating a lot of digits after the comma for the microseconds, but on every accumulation cycle (one second, i.e. around 440 periods) I round off the sub-microseconds... 

    So basically I am throwing accuracy out in the order of ~1 microseconds per second, which results in 86400 * 0.5us = 0.086s / day, which is roughly one second per week.

    My plan to tackle this is to keep the fixed-point microseconds for reading off the time, but also keeping the sub-microsecond digits after the comma in a float to correctly round up or down.

    Let's start another two week run with the modifications and let's see whether this improves the drift...

  • First validation is in - Success!

    PP07/07/2025 at 20:35 0 comments

    A am very happy to announce that the longest test run so far - three weeks in a hot office - shows a drift of around 45 seconds.

    This is far exceeding my goal of "under one minute per week" :)

    The calibration data is taken from a one-week GPS-backed trial, so I expect it to behave slightly better if I re-run the analyze script based on these three weeks to output newer model parameters. But, from now on, I consider these changes to be cosmetic only.


    I had to re-start the runs two times because of the I2C communication to the temperature sensor. The 1MHz from the external GPS-backed reference oscillator induced some serious noise, so I decreased the I2C bus resistors slightly.
    This was enough to make it mostly work, but for a time-piece that should run a long time, this was not enough. I had also to detect a stuck I2C bus and a consecutive I2C bus recovery (see also i2c-lock-up-prevention-and-recovery).
    And even that was not enough; sometimes the BME280 seemed to stop the measurement-cycle, which can be inferred by the registers having a certain default value. After detecting that state, and re-starting the continuous measurement mode, it finally ran through, even with people moving about in an office with carpet floor.

    To finalize the project, I want to crimp a custom cable to contain power, serial connection, and the (now optional!) external reference oscillator in one connection. Additionally, I'd like to make the model parameters run-time configurable (in the NOR flash) as a finishing touch, and perhaps I will improve the info of the watch-faces. Let's see.

    But first, after many requests, a video where you can actually hear the tuning fork when I take off the glass hood (and stop the fork by touching it):

    Oh, and by the way: Courious Marc just recently released a video about the HDSP-211X Display that I use. I agree both on the beautifulness as well as the fact that it is hard to film: www.youtube.com/watch?v=JXACON1XkPI

  • When it counts!

    PP05/11/2025 at 17:30 0 comments

    For the external Clock-Source I opted for a used GPS-Disipled Oscillator: The U-Blox EVK-6T Eval kit.

    With the cumbersome Windows configuration program "u-center" (which works on wine ;) ) it can be set to output up to one Megaherz from the "lock" timepulse. Which is coincidentally exactly the resolution that the internal counter of the RP2040 also has, which is nice.

    Of course, setting up an interrupt callback for 1MHz input would saturate the CPU considerably (in contrast to the Tuning-Fork measurement, which is a slow 0.000440MHz.

    As I wanted to learn about the PIO part of the RP2040 anyway, and did not find a pulsecounter in the examples, I implemented it in a PIO, which probably could count frequencies up to 50Mhz (depending on the implementation and base clock).

    To reduce load on the memory bus, I did not use a DMA transfer to read the current count from the PIO FIFO, but instead opted for a "read request", so that the PIO program only sends the current counter if the input FIFO contains a "request".
    For an "on average" correct pulse count, it checks for that request on both the high and low pulse (see https://github.com/Cirromulus/tuning-fork-clock/blob/main/lib/pulsecounter.pio).

    BTW, the rp2040 pio simulator did help me with debugging, especially with the limited jump conditions.


    Anyway, it counts :)

    I needed to add a filtering-capacitor to the fork input, as I noticed the Display heavily distorting the fork input signal if the USB-Hub is weak and has to power both the Tuning Fork Clock as well as the GPS receiver.

    Now, on to logging some time with the GPS-Precision of the external clock source :)

  • Calibration and the Model

    PP04/28/2025 at 09:05 0 comments

    Now that I have a steady oscillation, I can use the internal Quartz oscillator of the RP2040 to test my setup.
    Of course, that is not precise and also has a temperature coefficient, but for development I think that this is enough. Once the Process is settled, I can use an external and more precise reference with the same tools.

    A resolution problem comes up

    With a time reference of 1MHz (which I consider already high), the time between a single clock cycle at 440Hz is 2272.727µs. This poses a resolution problem, in where a single tick (µs) already can't resolve the exact (expected) frequency.
    So I instead measure the time that 440 oscillations take, which adds a factor of 440 on top of the resolution while still being around one measurement a second. I like to see seconds updating actually every second :D


    The process of finding some optimum was the usual data-science way: Fiddling around in python until something makes sense!

    The whole development is currently too long to write up for me, but basically I apply a simple polynom of the form `f(temp) = x1 + x2*temp + (x3*temp)^2`. This already finds a correlation and improves the estimation by a factor of 50:

    But, as you can slightly make out in Figure 2, the temp vs. period graph is not linear but has some "loops".
    This is caused by the tuning fork being of some considerable mass!

    It takes longer to get to temperature on temp changes than the faster sensor picks up. So I added a dampening model that adds a "low pass" to the temperature. This took a lot longer, because this dampening function is non-linear and can't be optimized by scipy. Well, it perhaps can, but I don't know how and the math is starting to get over my head :F

    ... So I track minima and maxima, and sample the mean time difference between temperature and frequency response, and linearize between some guessed sample points before optimizing the tempco polynom:

    More or less "final" estimation process

    So you know see the "damp factor". Please excuse the damp vs dampening misuse, I am not a native speaker :D
    Anyway, you can see the much more linear green dots in Figure 4 (correlation Data). The blue dots are the undampened samples. This actual measurement run went on for 5 Days, standing directly at a south-facing window, which I consider being the worst position, as sometimes the temperature goes from 20 Degrees up to 45 degrees (celsius)!  But this is probably good for calibration :)

    I suspect the sunlight being the last factor that I can't get out, so another run was recorded in a more calm environment, which surprisingly resulted in a much more linear behavior and thus less error at, tested in the application phase, around 10µs/s. Success!

    Now on to the display and final touches, and perhaps also an external reference to compensate the internal reference's drift that these runs could not consider.

  • First self-oscillating light

    PP04/28/2025 at 07:55 0 comments

    I started first in 2020, when the inspirational project still was fresh. I ordered only the base components for the self-oscillating fork circuit (that you can find in the inspiration project) along with a self-supporting holder.
    I did not directly design a PCB, because that was the first hurdle and I did not want to waste such a big PCB in testing phase.

    However, I was not very successful on the breadboard and the Fork did not hold itself reliably, so the project got on hold.

    Only much later I re-started the project and "just went for it" on a stripboard and, behold, it worked!
    So it turns out (if you want to replicate that!), that my breadboard connections were loose enough that the vibrations from the Clock somehow made it very fragile.

    Well, now it is stable, and I can start with my original project question: Can I make it more precise?

View all 9 project logs

Enjoy this project?

Share

Discussions

Isaac Wingfield wrote 07/22/2025 at 04:14 point

My mid-1960s Bulova Accutron tuning-fork watch was considerably better that a minute a month. I adjusted it to run very slightly slow so an occasional "thump" on the side would cause it to gain a tiny bit when necessary -- retarding it was considerably more difficult.

  Are you sure? yes | no

PP wrote 07/22/2025 at 08:28 point

I just looked it up, how cool is that :)
I can imagine that it is more precise. The Fork is precisely tuned, is a low-expansion alloy, and has most of its mass at the ends - My fork is a bad chinese Fork that has much more temperature dependence. At least online it says that it got a precision of two seconds per day or a minute per month (https://www.cliniquehorlogere.ch/en/archive/electric-watchmaking/bulova-accutron-tuning-fork.html), but perhaps you are referring to later models that use a quartz-backed oscillation. Which is also just a tuning fork, but even smaller :D
I really like the Idea, but unfortunately these watches are far too expensive for me.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates