Mercurial > hg > beaglert
changeset 141:44d07fa9bd03 ClockSync
Ultra-basic feedback for clock sync works^CIssues: response time of the IIR filter is too slow, requires PID and better filtering algorithm.
author | Giulio Moro <giuliomoro@yahoo.it> |
---|---|
date | Mon, 14 Sep 2015 15:42:11 +0100 |
parents | 5edc6d0713ef |
children | dd24379336f1 |
files | core/ClockSync.cpp core/ClockSyncThread.cpp core/VirtualClock.cpp include/ClockSync.h include/VirtualClock.h projects/scope/render.cpp |
diffstat | 6 files changed, 167 insertions(+), 21 deletions(-) [+] |
line wrap: on
line diff
--- a/core/ClockSync.cpp Mon Sep 14 14:57:54 2015 +0100 +++ b/core/ClockSync.cpp Mon Sep 14 15:42:11 2015 +0100 @@ -10,14 +10,20 @@ // isSlave() ? client.setServer("127.0.0.1") : client.setServer("127.0.0.1"); isSlave() ? client.setServer("192.168.7.1") : client.setServer("192.168.7.2"); bufferLength=kSyncMessageLength; + resetTs(); + receiveLoopSleepUs=100; + receiveLoopTimeout=1e5; + movingAverage.setLength(31); + expectedClockSyncType=isSlave() ? kSync : kNone; +} +void ClockSync::resetTs(){ T1=-1; T1p=-1; T2=-1; T2p=-1; - receiveLoopSleepUs=100; - receiveLoopTimeout=1e5; - movingAverage.setLength(31); - expectedClockSyncType=isSlave() ? kSync : kNone; +} +bool ClockSync::areTsValid(){ + return T1>0 && T1p>0 && T2>0 && T2p>0; } ClockSync::ClockSync(bool thisIsSlave, int aPort, VirtualClock &aVirtualClock){ init(thisIsSlave, aPort, aVirtualClock); @@ -190,6 +196,7 @@ int ClockSync::slaveHandleMessage(){ switch(clockSyncType){ case kSync: //the clockSync timestamp is meaningless, the localTimestamp is when kSync was received + resetTs(); T1p=localTimestamp; expectedClockSyncType=kFollowUp; break; @@ -209,8 +216,42 @@ T2p=clockSyncTimestamp; //TODO: evaluate things double offset=(T1p-T1-T2p+T2)/2.0d; - printf("-----------OFFSET IS : %04.4f seconds, average: %04.4f seconds\n", - offset/44100.f, movingAverage.add(offset)/44100.f); + if(areTsValid()){ + processOffset(offset); + + /* + static int calls=0; + static double referenceOffset=0; + + if(calls<100){ // start by averaging everything + movingAverage.add(offset); + } else { //once we get an estimate, start discarding outliers + float maxOffsetDeviation=20; + float deviation=fabsf(movingAverage.getAverage()-offset); + if(deviation<maxOffsetDeviation){ + movingAverage.add(offset); + printf("w(end+1)=%4.1f;\n", movingAverage.getAverage()); + } else { +// printf("Out of range: %f \n", deviation); + } + } + printf("offset(end+1)=%f;\n", offset); + if (calls==100){ +// printf("Update reference\n"); + referenceOffset=movingAverage.getAverage(); + } else if (calls==200){ + calls=99; + } + calls++; +*/ + +// printf("%lld, %lld, %lld, %lld \n", T1, T1p, T2, T2p); +// if(T2-T1p<0){ +// printf("Negative: %lld, %lld, %lld, %lld \n", T1, T1p, T2, T2p); +// } + } else { + printf("InvalidTs:\n %lld, %lld, %lld, %lld \n", T1, T1p, T2, T2p); + } expectedClockSyncType=kSync; //end of the cycle, wait for next sync. break; } @@ -220,17 +261,61 @@ } return 1; } - +#include <I2c_Codec.h> +extern I2c_Codec* gAudioCodec; +void ClockSync::processOffset(double offset){ + static int calls=0; + // TODO: change the flow control below so that it can happen multiple times + //(base it upon the length of movingAverage rather than the number of calls) + if(calls<10) { //get an initial guess + movingAverage.add(offset); +// printf("-----------OFFSET IS : %04.4f samples, average: %04.4f samples\n", +// offset, movingAverage.getAverage()); + } else if (calls==10){ //then compensate for initial offset +// printf("compensating for offset: %f\n", offset); + virtualClock->addOffset(movingAverage.getAverage()); + movingAverage.reset(); + } else if (calls>=10){ //use IIR filter from now on + //filter coefficients obtained from Matlab : [B,A]=butter(2,0.005); +// static float B[3]={6.10061787580662e-05, 0.000122012357516132, 6.10061787580662e-05}; +// static float A[3]={1, -1.97778648377676, 0.978030508491796}; + static float B[3]={6.10061787580662e-05, 0.000122012357516132, 6.10061787580662e-05}; + static float A[3]={1, -1.97778648377676, 0.978030508491796}; + static float pastOut[3]={0,0,0}; + static float pastIn[3]={0,0,0}; + float in=offset; + float out= -pastOut[1]*A[1] -pastOut[2]*A[2] +in*B[0] +pastIn[1]*B[1] +pastIn[2]*B[2]; + pastOut[2]=pastOut[1]; + pastOut[1]=out; + pastIn[2]=pastIn[1]; + pastIn[1]=in; + offset=out; + static float maxOffset=0; + maxOffset=fabsf(offset) > fabsf(maxOffset) ? offset : maxOffset; + printf("%10.3f, %10.3f, %10.3f, %10.3f\n", in, offset, offset-pastOut[2], maxOffset); //unfiltered, filtered + if(fabsf(offset)>10 && calls>30){ + calls=11; + //TODO: correct for offset + float targetSamplingRate=offset>0 ? 44097 : 44103; + gAudioCodec->setAudioSamplingRate(targetSamplingRate); +// pastOut[1]=pastOut[2]=pastIn[1]=pastIn[2]=offset; + printf("------setAudioSmplingRate to %f\n", targetSamplingRate); + } + } + calls++; +} int ClockSync::masterHandleMessage(){ switch(clockSyncType){ case kDelayReq: - //TODO: do something with it //send kDelayResp setType(kDelayResp); setTimestamp(localTimestamp); send(); expectedClockSyncType=kNone; + return 1; break; + default: + return -1; } }
--- a/core/ClockSyncThread.cpp Mon Sep 14 14:57:54 2015 +0100 +++ b/core/ClockSyncThread.cpp Mon Sep 14 15:42:11 2015 +0100 @@ -39,9 +39,7 @@ #ifdef USE_JUCE #else void ClockSyncThread::startThread(){ - printf("starting\n"); BeagleRT_scheduleAuxiliaryTask(clockSyncTask); - printf("started\n"); } void ClockSyncThread::stopThread(){ threadIsExiting=true; @@ -56,11 +54,13 @@ }; void ClockSyncThread::run(){ + printf("var=["); while(!threadShouldExit()){ clockSync.sendReceiveLoop(); // double now=virtualClock->getNow(); // printf("th(end+1)=%f;\n", now); // printf("act(end+1)=%lld;\n", Clock::getTimeUs()); } - printf("Thread is not running \n"); + printf("];\n"); +// printf("Thread is not running \n"); }
--- a/core/VirtualClock.cpp Mon Sep 14 14:57:54 2015 +0100 +++ b/core/VirtualClock.cpp Mon Sep 14 15:42:11 2015 +0100 @@ -1,8 +1,12 @@ #include "VirtualClock.h" void VirtualClock::init(){ firstRun=true; - movingAverage.setLength(31); //TODO: a better filtering algorithm ( Did you say Kalman?) + movingAverage.setLength(101); //TODO: a better filtering algorithm ( Did you say Kalman?) period=-1; + elapsedPeriods=0; + startTime=0; + startTimeOffset=0; + elapsedPeriodsOffset=0; } VirtualClock::VirtualClock(){ @@ -11,13 +15,29 @@ void VirtualClock::sync(){ sync(1); } -void VirtualClock::sync(double count){ +void VirtualClock::sync(double numPeriods){ myClock_t currentTime=Clock::getTimeUs(); + static int calls=0; + elapsedPeriods+=numPeriods; + if(calls==50){ //TODO: this is dangerous as the clock might jump suddenly if currentTime is not precise + calls=0; + startTimeOffset=startTime; + startTime=currentTime; + elapsedPeriodsOffset=elapsedPeriods; + } + calls++; if(firstRun==true){ firstRun=false; startTime=currentTime; } else { - period=movingAverage.add((currentTime-lastSync)/count); //TODO: replace with Kalman filter + double newPeriod=(currentTime-lastSync)/numPeriods; + double expectedPeriod=22.67; + double maxPeriodDeviation=10; + if(fabs(newPeriod-expectedPeriod)<maxPeriodDeviation){ // filtering outliers + period=movingAverage.add(newPeriod); //TODO: replace with Kalman filter + } else { + printf("period out of range: %f\n", newPeriod); + } } lastSync=currentTime; // printf("lastSync: %lld\n",lastSync-startTime); @@ -26,16 +46,24 @@ double VirtualClock::getNow(){ myClock_t currentSystemTime=Clock::getTimeUs(); if(period<=0){ - return currentSystemTime; + return currentSystemTime; // TODO: this is not very meaningful. } // double beginningOfPeriod=lastSync; // TODO: if sync() does not get called every time (but e.g. only every so often), // then this line (and the class) needs editing myClock_t elapsed=(currentSystemTime-startTime); - double now=elapsed/(double)period; + double now=elapsedPeriodsOffset + elapsed/(double)period; // printf("elapsed=%lld; sincelastSync=%lld; period=%f; now=%f\n", elapsed, currentSystemTime-lastSync, period, now); return now; } +void VirtualClock::addOffset(double periodOffset){ + elapsedPeriods-=periodOffset; + if(elapsedPeriods<0){ + printf("ERROR: periodOffset adjustment of %f resulted in elapsedPeriods=%f\n", periodOffset, elapsedPeriods); + exit(1); + } + movingAverage.reset(); +} double VirtualClock::getPeriod(){ return period; }
--- a/include/ClockSync.h Mon Sep 14 14:57:54 2015 +0100 +++ b/include/ClockSync.h Mon Sep 14 15:42:11 2015 +0100 @@ -43,6 +43,9 @@ int receiveLoopTimeout; char buffer[kSyncMessageLength]; VirtualClock *virtualClock; + void resetTs(); + bool areTsValid(); + void processOffset(double offset); public: ClockSync(){}; ClockSync(bool thisIsSlave, int aPort, VirtualClock &aVirtualClock); @@ -78,4 +81,4 @@ } }; -#endif /* CLOCK_SYNC_H_INCLUDED */ \ No newline at end of file +#endif /* CLOCK_SYNC_H_INCLUDED */
--- a/include/VirtualClock.h Mon Sep 14 14:57:54 2015 +0100 +++ b/include/VirtualClock.h Mon Sep 14 15:42:11 2015 +0100 @@ -1,14 +1,22 @@ #ifndef VIRTUAL_CLOCK_H_INCLUDED #define VIRTUAL_CLOCK_H_INCLUDED +#include "math.h" #include "stats.hpp" #include "Clock.h" +#ifdef USE_JUCE +#else +#include <BeagleRT.h> +#endif /* USE_JUCE */ class VirtualClock{ private: myClock_t startTime; + myClock_t startTimeOffset; myClock_t lastSync; bool firstRun; + double elapsedPeriods; + double elapsedPeriodsOffset; double period; MovingAverage<double> movingAverage; public: @@ -21,7 +29,7 @@ /** Call this method asynchronously, passing a number of equally spaced events that have elapsed since the last call. */ - void sync(double count); + void sync(double numPeriods); /** Get the current time according to the VirtualClock. @@ -34,6 +42,12 @@ Get the length of the period (difference between calls to sync() after various filtering operations) */ double getPeriod(); +/** + * Add an offset to the number of elapsed periods. + * + * Add an offset to the number of elapsed periods. It also compensates for the corresponding time offset. + */ + void addOffset(double periodOffset); }; #endif /* VIRTUAL_CLOCK_H_INCLUDED */
--- a/projects/scope/render.cpp Mon Sep 14 14:57:54 2015 +0100 +++ b/projects/scope/render.cpp Mon Sep 14 15:42:11 2015 +0100 @@ -26,6 +26,19 @@ extern I2c_Codec* gAudioCodec; VirtualClock virtualClock; ClockSyncThread clockSyncThread; +AuxiliaryTask testTime; +void testTimeFunction(){ + rt_printf("time=["); + while(!gShouldStop){ + rt_task_sleep(50000*1e3); + rt_printf("%f, ", virtualClock.getNow()); + rt_printf("%f, ", virtualClock.getPeriod()); + rt_task_sleep(20000); + rt_printf("%f,", virtualClock.getNow()); + rt_printf("%f\n", virtualClock.getPeriod()); + } + rt_printf("];"); +} bool setup(BeagleRTContext *context, void *userData) { // receiveAudio0.init(10000, context->audioFrames, 0); @@ -35,7 +48,7 @@ // scope.setPort(0, 9999); // scope.setPort(1, 10000); // networkSend.setup(context->audioSampleRate, context->audioFrames, 0, 9999, "192.168.7.1"); - clockSynchronizer.setup(); +// clockSynchronizer.setup(); virtualClock.init(); clockSyncThread.init(true, 5000, virtualClock); //start as slave gInverseSampleRate = 1.0/context->audioSampleRate; @@ -46,6 +59,7 @@ gFrequency1 = 200.0; gFrequency2 = 201.0; +// testTime=BeagleRT_createAuxiliaryTask(testTimeFunction, 80, "testTimeTask"); return true; } @@ -58,8 +72,10 @@ { virtualClock.sync(context->audioFrames); static int count=0; - if(count==0) - clockSyncThread.startThread(); + if(count==0){ +// BeagleRT_scheduleAuxiliaryTask(testTime); + clockSyncThread.startThread(); //make sure you uncomment .init in setup() + } static float phase=0; float phaseInc=200.0/44100.0*2*M_PI; // rt_printf("phaseInc: %f, phase: %f\n",phaseInc,phase);