// // DigitalPhaseLockedLoop.cpp // Clock Signal // // Created by Thomas Harte on 11/07/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // #include "DigitalPhaseLockedLoop.hpp" #include #include using namespace Storage; DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, size_t length_of_history) : clocks_per_bit_(clocks_per_bit), phase_(0), window_length_(clocks_per_bit), offset_history_pointer_(0), offset_history_(length_of_history, 0), offset_(0), delegate_(nullptr) {} void DigitalPhaseLockedLoop::run_for(const Cycles cycles) { offset_ += cycles.as_int(); phase_ += cycles.as_int(); if(phase_ >= window_length_) { int windows_crossed = phase_ / window_length_; // check whether this triggers any 0s, if anybody cares if(delegate_) { if(window_was_filled_) windows_crossed--; for(int c = 0; c < windows_crossed; c++) delegate_->digital_phase_locked_loop_output_bit(0); } window_was_filled_ = false; phase_ %= window_length_; } } void DigitalPhaseLockedLoop::add_pulse() { if(!window_was_filled_) { if(delegate_) delegate_->digital_phase_locked_loop_output_bit(1); window_was_filled_ = true; post_phase_offset(phase_, offset_); offset_ = 0; } } void DigitalPhaseLockedLoop::post_phase_offset(int new_phase, int new_offset) { offset_history_[offset_history_pointer_] = new_offset; offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size(); // use an unweighted average of the stored offsets to compute current window size, // bucketing them by rounding to the nearest multiple of the base clocks per bit int total_spacing = 0; int total_divisor = 0; for(int offset : offset_history_) { int multiple = (offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_; if(!multiple) continue; total_divisor += multiple; total_spacing += offset; } if(total_divisor) { window_length_ = total_spacing / total_divisor; } int error = new_phase - (window_length_ >> 1); // use a simple spring mechanism as a lowpass filter for phase phase_ -= (error + 1) >> 1; }