m-lundberg / simple-pid

A simple and easy to use PID controller in Python
MIT License
767 stars 209 forks source link

Using PID to monitor/correct Xtal frequency wrt other/reference system. #75

Open mungewell opened 1 year ago

mungewell commented 1 year ago

I have a project for an Open-Source LTC box, similar to Lock-it or Tenticle-Sync. Multiple boxes communicate 'time' with an audio signal and then are expected to remain in sync. The issue I have is the the Pico's clock is not precision, and I want to add some synchronization method.... https://github.com/mungewell/pico-timecode

The basics are that each box is effectively a cascade counter, implemented in the PIO blocks, which count according to the PIO clock divider. I have already implemented a way to adjust the divider, and to monitor the timing difference between 'local' LTC and that received from the reference. This kind of looks like this....

clock_adjust

When monitoring the moment each frame (local vs reference) starts I get a 'delta time' (in us). I tried feeding this into a PID instance, expecting the output to produce a 'correction' value which would stabilize over time to a non-zero value...

Q. Is this correct thinking? - because I couldn't get it to work like that.... it never seems to settle.

It's worth noting that the measured time value is from the local clock, so it would appear that the reference is wrong... but is in-fact the correct one.

When starting a session the units are 'Jam Synced' together; which gets the clocks and phase the same. After some time they will drift apart. I believe that if the clock rate(s) are the same, then we will not need to monitor/correct the phase as it will just be correct.

mungewell commented 1 year ago

I continued to mess with the various gains, and after introducing some 'D' gain I seemed to get something a little more stable.

        pid = PID(-0.001, -0.00001, -0.001, setpoint=0)
        pid.auto_mode = False
        pid.sample_time = 60
        pid.output_limits = (-10.0, 10.0)
        sync_after_jam = False

and

                        # Update PID every 60s or so
                        if (g & 0xFFFF0000) != l:
                            if l:
                                if sync_after_jam == True:
                                    if pid.auto_mode == False:
                                        pid.set_auto_mode(True, last_output=eng.duty)

                                    eng.duty = pid(dave/len(dcache))

I get following, where blue is the delta measurement in 'us' (dave/len(dcache) is averaging a noisy signal) and red (scaled * 100) is the resultant duty factor used for correcting the clocks. Seems to settle around 0.66.

The fractional part of the output is used to toggle between adjacent fractional dividers (ie between 128/256 and 127/256), integer part (0 in this case) adds constant 1/256 and wraps whole divisor where appropriate.

20230530_A-B_30NDF

I'll keep experimenting with the gains...

mungewell commented 1 year ago

Code is here, if anyone is interested. PID is used in main.py. https://github.com/mungewell/pico-timecode/tree/adjust_clock_with_PID