From 015cea494d4a674a85c13923c356628e415abd52 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 28 Jul 2016 11:32:14 -0400 Subject: [PATCH] Switched to a much-more straightforward PLL. I think I'm just fiddling now rather than moving forwards. Probably time to move on? --- .../Mac/Clock SignalTests/DPLLTests.swift | 4 +- Storage/Disk/DigitalPhaseLockedLoop.cpp | 101 +++++++----------- Storage/Disk/DigitalPhaseLockedLoop.hpp | 14 +-- 3 files changed, 48 insertions(+), 71 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/DPLLTests.swift b/OSBindings/Mac/Clock SignalTests/DPLLTests.swift index 214160a98..01d99c054 100644 --- a/OSBindings/Mac/Clock SignalTests/DPLLTests.swift +++ b/OSBindings/Mac/Clock SignalTests/DPLLTests.swift @@ -40,13 +40,13 @@ class DPLLTests: XCTestCase { testRegularNibblesOnPLL(pll, bitLength: 110) } - func testFivePercentSinePattern() { + func testTwentyPercentSinePattern() { let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3) var angle = 0.0 // clock in two 1s, a 0, and a 1, 200 times over for _ in 0 ..< 200 { - let bitLength: UInt = UInt(100 + 2 * sin(angle)) + let bitLength: UInt = UInt(100 + 20 * sin(angle)) pll.runForCycles(bitLength/2) pll.addPulse() diff --git a/Storage/Disk/DigitalPhaseLockedLoop.cpp b/Storage/Disk/DigitalPhaseLockedLoop.cpp index 3add6446d..484a7bcdd 100644 --- a/Storage/Disk/DigitalPhaseLockedLoop.cpp +++ b/Storage/Disk/DigitalPhaseLockedLoop.cpp @@ -12,88 +12,65 @@ using namespace Storage; -DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, int length_of_history) : +DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history) : _clocks_per_bit(clocks_per_bit), _tolerance(tolerance), - _length_of_history(length_of_history), - _pulse_history(new int[length_of_history]), - _current_window_length(clocks_per_bit), - _next_pulse_time(0), - _window_was_filled(false), - _window_offset(0), - _samples_collected(0) {} + + _phase(0), + _window_length(clocks_per_bit), + + _phase_error_pointer(0) +{ + _phase_error_history.reset(new std::vector(length_of_history, 0)); +} void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles) { // check whether this triggers any 0s - _window_offset += number_of_cycles; + _phase += number_of_cycles; if(_delegate) { - while(_window_offset > _current_window_length) + while(_phase > _window_length) { if(!_window_was_filled) _delegate->digital_phase_locked_loop_output_bit(0); _window_was_filled = false; - _window_offset -= _current_window_length; + _phase -= _window_length; } } else { - _window_offset %= _current_window_length; + _phase %= _window_length; } - - // update timing - _next_pulse_time += number_of_cycles; } void DigitalPhaseLockedLoop::add_pulse() { - int *const _pulse_history_array = (int *)_pulse_history.get(); - int outgoing_pulse_time = 0; - - if(_samples_collected <= _length_of_history) + if(!_window_was_filled) { - _samples_collected++; + if(_delegate) _delegate->digital_phase_locked_loop_output_bit(1); + _window_was_filled = true; + post_phase_error(_phase - (_window_length >> 1)); } - else - { - outgoing_pulse_time = _pulse_history_array[0]; - - // temporary: perform an exhaustive search for the ideal window length - int minimum_error = __INT_MAX__; - int ideal_length = 0; - for(int c = _clocks_per_bit - _tolerance; c < _clocks_per_bit + _tolerance; c++) - { - int total_error = 0; - const int half_window = c >> 1; - for(size_t pulse = 1; pulse < _length_of_history; pulse++) - { - int difference = _pulse_history_array[pulse] - _pulse_history_array[pulse-1]; - difference += half_window; - const int steps = difference / c; - const int offset = difference%c - half_window; - - total_error += abs(offset / steps); - } - if(total_error < minimum_error) - { - minimum_error = total_error; - ideal_length = c; - } - } - - // use a spring mechanism to effect a lowpass filter - _current_window_length = ((ideal_length + (_current_window_length*3)) + 2) >> 2; - } - - // therefore, there was a 1 in this window - _window_was_filled = true; - if(_delegate) _delegate->digital_phase_locked_loop_output_bit(1); - - // shift history one to the left, storing new value, potentially with a centring adjustment - for(size_t pulse = 1; pulse < _length_of_history; pulse++) - { - _pulse_history_array[pulse - 1] = _pulse_history_array[pulse] - outgoing_pulse_time; - } - _next_pulse_time -= outgoing_pulse_time; - _pulse_history_array[_length_of_history-1] = _next_pulse_time; +} + +void DigitalPhaseLockedLoop::post_phase_error(int error) +{ + // use a simple spring mechanism as a lowpass filter for phase + _phase -= (error + 1) >> 1; + + // use the average of the last few errors to affect frequency + std::vector *phase_error_history = _phase_error_history.get(); + size_t phase_error_history_size = phase_error_history->size(); + + (*phase_error_history)[_phase_error_pointer] = error; + _phase_error_pointer = (_phase_error_pointer + 1)%phase_error_history_size; + + int total_error = 0; + for(size_t c = 0; c < phase_error_history_size; c++) + { + total_error += (*phase_error_history)[c]; + } + int denominator = (int)(phase_error_history_size * 4); + _window_length += (total_error + (denominator >> 1)) / denominator; + _window_length = std::max(std::min(_window_length, _clocks_per_bit + _tolerance), _clocks_per_bit - _tolerance); } diff --git a/Storage/Disk/DigitalPhaseLockedLoop.hpp b/Storage/Disk/DigitalPhaseLockedLoop.hpp index 955c24f94..da257b445 100644 --- a/Storage/Disk/DigitalPhaseLockedLoop.hpp +++ b/Storage/Disk/DigitalPhaseLockedLoop.hpp @@ -10,6 +10,7 @@ #define DigitalPhaseLockedLoop_hpp #include +#include namespace Storage { @@ -22,7 +23,7 @@ class DigitalPhaseLockedLoop { @param tolerance The maximum tolerance for bit windows — extremes will be clocks_per_bit ± tolerance. @param length_of_history The number of historic pulses to consider in locking to phase. */ - DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, int length_of_history); + DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history); /*! Runs the loop, impliedly posting no pulses during that period. @@ -51,13 +52,12 @@ class DigitalPhaseLockedLoop { private: Delegate *_delegate; - std::unique_ptr _pulse_history; - int _current_window_length; - int _length_of_history; - int _samples_collected; + void post_phase_error(int error); + std::unique_ptr> _phase_error_history; + size_t _phase_error_pointer; - int _next_pulse_time; - int _window_offset; + int _phase; + int _window_length; bool _window_was_filled; int _clocks_per_bit;