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.
Also, offline on the 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.

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.

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!

Currently, with a fresh calibration phase and without standing in direct sunlight (I can't model that unless I add an illumination sensor), it achieves a drift of ~2.5 seconds per three days, which results in around 10µs/s of drift! That is a factor of 100 better than uncalibrated, which I consider a project success :)

Concept of Measurement Phase

  • When it counts!

    PP6 days ago 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 3 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

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