Hi guys,
Here's something for you lot to play with. It's a software
implementation of the "Phase Jerked Loop" data separator, in C++
(although most of the logic is C), complete with notes...
This is extremely sub-optimal, but "It Works For Me"... :)
Tests against the old "Helix" decoder engine (the "smart" histogram
analyser that really is anything but) are still ongoing. As a bare
minimum, the timing figures need to be optimised -- most timesteps will
end up with no accompanying state change. If you can eliminate some of
these timesteps, you can speed up the code quite a bit. The catch is
that the timestep must be a multiple of NSECS_PER_PLLCK/2,
NSECS_PER_PLLCK and NSECS_PER_ACQ... The optimal value should be
lcm(NSECS_PER_PLLCK/2, NSECS_PER_ACQ) [lcm = lowest common multiple].
Optimising the timing factors for a given acq-clock is likely to be the
hardest part of the whole process...
I'm willing to bet it'll work with data grabbed from a Catweasel, but I
don't have any test data to play with... so that's left as an exercise
to the reader.
mfmbits is a vector<bool> which starts out empty and contains the output
MFM bits after the loop has run. A sync marker will appear in mfmbits as
0x4489.
buf is either an array or a vector containing integers of any sane size.
These represent the timing values in units of Tacq (Tacq = 1/Facq, the
frequency at which the acquisition counter is incremented). buflen is
the length of this buffer.
License is GPLv2, code is copyrighted to me. If you want to use this in
closed-source software, email me and ask for a less restrictive licence.
I'm getting pretty pissed off with people in the floppy disc
preservation/analysis field keeping their toys to themselves (*cough*
SPS), and have no intention of contributing further to such nonsense.
/**
* This is a software implementation of Jim Thompson's Phase-Jerked Loop
* design, available from
AnalogInnovations.com as the PDF file
* "FloppyDataExtractor.pdf".
*
* This consists of:
* A data synchroniser which forces RD_DATA to be active for 2 clock cycles.
* A counter which increments constantly while the PLL is running, and is
* reset to zero on an incoming data bit.
* Whenever the counter reaches half of its maximum value, a
* new data window is started.
*/
// Nanoseconds counters. Increment once per loop or "virtual" nanosecond
unsigned long nsecs1 = 0, nsecs2=0;
// Number of nanoseconds per acq tick -- (1/freq)*1e9. This is valid for 40MHz.
const unsigned long NSECS_PER_ACQ = 25;
// Number of nanoseconds per PLLCK tick -- (1/16e6)*1e9. 16MHz.
// This should be the reciprocal of 32 times the data rate in kbps, multiplied
// by 1e9 to get the time in nanoseconds.
const unsigned long NSECS_PER_PLLCK = 125/2;
// Number of clock increments per loop (timing granularity)
const unsigned long TIMER_INCREMENT = 1;
// Maximum value of the PJL counter. Determines the granularity of phase changes.
const unsigned char PJL_COUNTER_MAX = 16;
// Iterator for data buffer
size_t i = 0;
// True if RD_DATA was high in this clock cycle
bool rd_latch = false;
// Same but only active for 2Tcy (SHAPED_DATA)
int shaped_data = 0;
// Phase Jerked Loop counter
unsigned char pjl_shifter = 0;
// data window
unsigned char data_window = 0;
#ifdef VCD
FILE *vcd = fopen("values.vcd", "wt");
fprintf(vcd, "$version DiscFerret Analyser D2/DPLL 0.1 $end\n"
"$timescale 1 ns $end\n"
"$var reg 1 * clock $end\n"
"$var reg 1 ' pll_clock $end\n"
"$var reg 1 ! rd_data $end\n"
"$var reg 1 %% rd_data_latched $end\n"
"$var reg 1 ^ shaped_data $end\n"
"$var reg 8 & pjl_shifter $end\n"
"$var reg 1 ( data_window $end\n"
"$upscope $end\n"
"$enddefinitions $end\n"
"$dumpall\n"
"0*\n"
"0'\n"
"0!\n"
"0%%\n"
"0^\n"
"b00000000 &\n"
"0(\n"
"$end\n"
);
#endif
do {
// Increment counters
nsecs1 += TIMER_INCREMENT;
nsecs2 += TIMER_INCREMENT;
// Loop 1 -- floppy disc read channel
if (nsecs1 >= (buf[i] * NSECS_PER_ACQ)) {
// Flux transition. Set the read latches.
rd_latch = true;
shaped_data = 2;
// Update nanoseconds counter for read channel, retain error factor
nsecs1 -= (buf[i] * NSECS_PER_ACQ);
// Update buffer position
i++;
}
// Loop 2 -- DPLL channel
if (nsecs2 >= NSECS_PER_PLLCK) {
// Update nanoseconds counter for PLL, retain error factor
nsecs2 -= NSECS_PER_PLLCK;
// PJL loop
if (shaped_data > 0) {
pjl_shifter = 0;
} else {
// increment shifter
pjl_shifter = (pjl_shifter + 1) % PJL_COUNTER_MAX;
}
// DWIN detect
if (pjl_shifter == (PJL_COUNTER_MAX / 2)) {
// Data window toggle. Latch the current RD_LATCH blob into the output buffer.
mfmbits.push_back(rd_latch);
// Clear the data latch ready for the next data window.
rd_latch = false;
// Update DWIN
data_window ^= 0x01;
}
// Update shaped-data time counter
if (shaped_data > 0) shaped_data--;
}
} while (i < buflen);
printf("mfmbits count = %lu\n", mfmbits.size());
--
Phil.
classiccmp at philpem.me.uk
http://www.philpem.me.uk/