1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-10-01 13:58:20 +00:00

Switched to a much-more straightforward PLL. I think I'm just fiddling now rather than moving forwards. Probably time to move on?

This commit is contained in:
Thomas Harte 2016-07-28 11:32:14 -04:00
parent e061e849d4
commit 015cea494d
3 changed files with 48 additions and 71 deletions

View File

@ -40,13 +40,13 @@ class DPLLTests: XCTestCase {
testRegularNibblesOnPLL(pll, bitLength: 110) testRegularNibblesOnPLL(pll, bitLength: 110)
} }
func testFivePercentSinePattern() { func testTwentyPercentSinePattern() {
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3) let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3)
var angle = 0.0 var angle = 0.0
// clock in two 1s, a 0, and a 1, 200 times over // clock in two 1s, a 0, and a 1, 200 times over
for _ in 0 ..< 200 { 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.runForCycles(bitLength/2)
pll.addPulse() pll.addPulse()

View File

@ -12,88 +12,65 @@
using namespace Storage; 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), _clocks_per_bit(clocks_per_bit),
_tolerance(tolerance), _tolerance(tolerance),
_length_of_history(length_of_history),
_pulse_history(new int[length_of_history]), _phase(0),
_current_window_length(clocks_per_bit), _window_length(clocks_per_bit),
_next_pulse_time(0),
_window_was_filled(false), _phase_error_pointer(0)
_window_offset(0), {
_samples_collected(0) {} _phase_error_history.reset(new std::vector<int>(length_of_history, 0));
}
void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles) void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles)
{ {
// check whether this triggers any 0s // check whether this triggers any 0s
_window_offset += number_of_cycles; _phase += number_of_cycles;
if(_delegate) if(_delegate)
{ {
while(_window_offset > _current_window_length) while(_phase > _window_length)
{ {
if(!_window_was_filled) _delegate->digital_phase_locked_loop_output_bit(0); if(!_window_was_filled) _delegate->digital_phase_locked_loop_output_bit(0);
_window_was_filled = false; _window_was_filled = false;
_window_offset -= _current_window_length; _phase -= _window_length;
} }
} }
else else
{ {
_window_offset %= _current_window_length; _phase %= _window_length;
} }
// update timing
_next_pulse_time += number_of_cycles;
} }
void DigitalPhaseLockedLoop::add_pulse() void DigitalPhaseLockedLoop::add_pulse()
{ {
int *const _pulse_history_array = (int *)_pulse_history.get(); if(!_window_was_filled)
int outgoing_pulse_time = 0;
if(_samples_collected <= _length_of_history)
{ {
_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]; void DigitalPhaseLockedLoop::post_phase_error(int error)
{
// temporary: perform an exhaustive search for the ideal window length // use a simple spring mechanism as a lowpass filter for phase
int minimum_error = __INT_MAX__; _phase -= (error + 1) >> 1;
int ideal_length = 0;
for(int c = _clocks_per_bit - _tolerance; c < _clocks_per_bit + _tolerance; c++) // use the average of the last few errors to affect frequency
{ std::vector<int> *phase_error_history = _phase_error_history.get();
int total_error = 0; size_t phase_error_history_size = phase_error_history->size();
const int half_window = c >> 1;
for(size_t pulse = 1; pulse < _length_of_history; pulse++) (*phase_error_history)[_phase_error_pointer] = error;
{ _phase_error_pointer = (_phase_error_pointer + 1)%phase_error_history_size;
int difference = _pulse_history_array[pulse] - _pulse_history_array[pulse-1];
difference += half_window; int total_error = 0;
const int steps = difference / c; for(size_t c = 0; c < phase_error_history_size; c++)
const int offset = difference%c - half_window; {
total_error += (*phase_error_history)[c];
total_error += abs(offset / steps); }
} int denominator = (int)(phase_error_history_size * 4);
if(total_error < minimum_error) _window_length += (total_error + (denominator >> 1)) / denominator;
{ _window_length = std::max(std::min(_window_length, _clocks_per_bit + _tolerance), _clocks_per_bit - _tolerance);
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;
} }

View File

@ -10,6 +10,7 @@
#define DigitalPhaseLockedLoop_hpp #define DigitalPhaseLockedLoop_hpp
#include <memory> #include <memory>
#include <vector>
namespace Storage { namespace Storage {
@ -22,7 +23,7 @@ class DigitalPhaseLockedLoop {
@param tolerance The maximum tolerance for bit windows extremes will be clocks_per_bit ± tolerance. @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. @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. Runs the loop, impliedly posting no pulses during that period.
@ -51,13 +52,12 @@ class DigitalPhaseLockedLoop {
private: private:
Delegate *_delegate; Delegate *_delegate;
std::unique_ptr<int> _pulse_history; void post_phase_error(int error);
int _current_window_length; std::unique_ptr<std::vector<int>> _phase_error_history;
int _length_of_history; size_t _phase_error_pointer;
int _samples_collected;
int _next_pulse_time; int _phase;
int _window_offset; int _window_length;
bool _window_was_filled; bool _window_was_filled;
int _clocks_per_bit; int _clocks_per_bit;