Skip to content

Commit ee6882e

Browse files
committed
floppy: apply NCO-based 2nd-order PLL to WDATA line
Centurion Finch Floppy Controller (FFC, https://github.com/Nakazoto/CenturionComputer/wiki/FFC-Board) is an 8" floppy controller that applies up to 350ns write precompensation. FlashFloppy's existing approach of re-aligning the bit clock to the last pulse edge causes enough additional error for bitcells to be missed or injected in this case. This commit applies a 2nd-order (proportional and integral) PLL to the incoming WDATA to mitigate both frequency offset and extreme write precompensation. The implementation is based on a virtual numerically-controlled oscillator nominally running at the same frequency at the WDATA timer but with a 16.16 fixed-point representation allowing for considerable precision in frequency adjustment. Proportional and integral constants were calcuated to nominally provide a 1440kHz natural loop frequency and a damping factor of 0.25 to allow for 2% frequency offset and strong jitter rejection against write precompenstaion.
1 parent c6310b8 commit ee6882e

File tree

1 file changed

+119
-16
lines changed

1 file changed

+119
-16
lines changed

src/floppy_generic.c

Lines changed: 119 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
* See the file COPYING for more details, or visit <http://unlicense.org>.
1111
*/
1212

13+
/*
14+
* PLL nominally runs at the same frequency as the timer's clock but with a
15+
* 16.16 fixed-point representation. This allows for lots of precision in phase
16+
* error calcuations. NOMINAL_PLL_PHASE_STEP is the PLL phase increment which
17+
* causes the PLL to exactly equal the timer's frequency.
18+
*/
19+
#define NOMINAL_PLL_PHASE_STEP (1 << 16)
20+
1321
/* A DMA buffer for running a timer associated with a floppy-data I/O pin. */
1422
struct dma_ring {
1523
/* Current state of DMA (RDATA):
@@ -33,7 +41,12 @@ struct dma_ring {
3341
uint16_t cons;
3442
union {
3543
uint16_t prod; /* dma_rd: our producer index for flux samples */
36-
uint16_t prev_sample; /* dma_wr: previous CCRx sample value */
44+
struct {
45+
uint32_t phase_step; /* PLL period */
46+
int32_t phase_integral; /* PLL's running sum of phase errors */
47+
uint32_t prev_bc_left; /* PLL timestamp of left edge of previous bitcell */
48+
uint32_t curr_bc_left; /* PLL timestamp of left edge of current bitcell */
49+
} wr;
3750
};
3851
/* DMA ring buffer of timer values (ARR or CCRx). */
3952
uint16_t buf[1024];
@@ -401,6 +414,10 @@ static void wdata_start(void)
401414
}
402415

403416
dma_wr->state = DMA_starting;
417+
dma_wr->wr.phase_step = NOMINAL_PLL_PHASE_STEP;
418+
dma_wr->wr.phase_integral = 0;
419+
dma_wr->wr.prev_bc_left = 0;
420+
dma_wr->wr.curr_bc_left = 0;
404421

405422
/* Start timer. */
406423
tim_wdata->egr = TIM_EGR_UG;
@@ -643,13 +660,15 @@ static void IRQ_rdata_dma(void)
643660
static void IRQ_wdata_dma(void)
644661
{
645662
const uint16_t buf_mask = ARRAY_SIZE(dma_rd->buf) - 1;
646-
uint16_t cons, prod, prev, next;
663+
uint16_t cons, prod;
647664
uint32_t bc_dat = 0, bc_prod;
648665
uint32_t *bc_buf = image->bufs.write_bc.p;
649666
unsigned int sync = image->sync;
650667
unsigned int bc_bufmask = (image->bufs.write_bc.len / 4) - 1;
651-
int curr, cell = image->write_bc_ticks;
652668
struct write *write = NULL;
669+
uint32_t next_edge, distance_from_prev_bc_left, distance_from_curr_bc_left;
670+
uint32_t bc_step;
671+
int32_t phase_error;
653672

654673
/* Clear DMA peripheral interrupts. */
655674
dma1->ifcr = DMA_IFCR_CGIF(dma_wdata_ch);
@@ -669,27 +688,56 @@ static void IRQ_wdata_dma(void)
669688
}
670689

671690
/* Process the flux timings into the raw bitcell buffer. */
672-
prev = dma_wr->prev_sample;
673691
bc_prod = image->bufs.write_bc.prod;
674692
bc_dat = image->write_bc_window;
675693
for (cons = dma_wr->cons; cons != prod; cons = (cons+1) & buf_mask) {
676-
next = dma_wr->buf[cons];
677-
curr = (int16_t)(next - prev) - (cell >> 1);
678-
if (unlikely(curr < 0)) {
679-
/* Runt flux, much shorter than bitcell clock. Merge it forward. */
694+
/* Calculate duration of a bitcell using the PLL's current period */
695+
bc_step = dma_wr->wr.phase_step * (uint32_t)image->write_bc_ticks;
696+
697+
/*
698+
* Incoming sample ticks are shifted up 16-bits to match PLL's internal
699+
* precision
700+
*/
701+
next_edge = (uint32_t)dma_wr->buf[cons] << 16;
702+
703+
/*
704+
* If this is the first pulse after WGATE was asserted, treat it as
705+
* perfectly in phase.
706+
*/
707+
if (dma_wr->wr.prev_bc_left == 0 && dma_wr->wr.curr_bc_left == 0) {
708+
dma_wr->wr.curr_bc_left = next_edge - (bc_step / 2);
709+
dma_wr->wr.prev_bc_left = dma_wr->wr.curr_bc_left - bc_step;
710+
}
711+
712+
/* By computing distance, wraparound is accounted for naturally. */
713+
distance_from_prev_bc_left = next_edge - dma_wr->wr.prev_bc_left;
714+
715+
/* If the next edge would fall within the previous bitcell, ignore it. */
716+
if (distance_from_prev_bc_left < (dma_wr->wr.curr_bc_left - dma_wr->wr.prev_bc_left))
717+
{
680718
continue;
681719
}
682-
prev = next;
683-
while ((curr -= cell) > 0) {
720+
721+
/* Advance to the current bitcell */
722+
distance_from_curr_bc_left = next_edge - dma_wr->wr.curr_bc_left;
723+
724+
/* Record zeros for each bitcell that passed before this pulse */
725+
while (distance_from_curr_bc_left > bc_step)
726+
{
684727
bc_dat <<= 1;
685728
bc_prod++;
686-
if (!(bc_prod&31))
687-
bc_buf[((bc_prod-1) / 32) & bc_bufmask] = htobe32(bc_dat);
729+
730+
if (!(bc_prod & 31))
731+
bc_buf[((bc_prod - 1) / 32) & bc_bufmask] = htobe32(bc_dat);
732+
733+
distance_from_curr_bc_left -= bc_step;
734+
dma_wr->wr.curr_bc_left += bc_step;
688735
}
689-
curr += cell >> 1; /* remove the 1/2-cell bias */
690-
prev -= curr >> 2; /* de-jitter/precomp: carry 1/4 of phase error */
736+
737+
/* Record a one for this bitcell */
691738
bc_dat = (bc_dat << 1) | 1;
692739
bc_prod++;
740+
693741
switch (sync) {
694742
case SYNC_fm:
695743
/* FM clock sync clock byte is 0xc7. Check for:
@@ -704,6 +752,63 @@ static void IRQ_wdata_dma(void)
704752
}
705753
if (!(bc_prod&31))
706754
bc_buf[((bc_prod-1) / 32) & bc_bufmask] = htobe32(bc_dat);
755+
756+
/*
757+
* Calculate per-tick phase error.
758+
*
759+
* Start with total phase error accumulated since the last flux pulse.
760+
* Assume that error was introduced evenly by each clock tick that
761+
* occurred during that time. Divide the total phase error by the
762+
* number of ticks to find average per-tick phase error.
763+
*/
764+
phase_error = ((int32_t)distance_from_curr_bc_left - ((int32_t)bc_step / 2)) / (int32_t)image->write_bc_ticks;
765+
766+
/* Adjust bitcell history for next iteration */
767+
dma_wr->wr.prev_bc_left = dma_wr->wr.curr_bc_left;
768+
dma_wr->wr.curr_bc_left += bc_step;
769+
770+
/* Accumulate error into integral, saturating as necessary */
771+
if (dma_wr->wr.phase_integral > 0 && phase_error > INT32_MAX - dma_wr->wr.phase_integral) {
772+
dma_wr->wr.phase_integral = INT32_MAX;
773+
} else if (dma_wr->wr.phase_integral < 0 && phase_error < INT32_MIN - dma_wr->wr.phase_integral) {
774+
dma_wr->wr.phase_integral = INT32_MIN;
775+
} else {
776+
dma_wr->wr.phase_integral += phase_error;
777+
}
778+
779+
/*
780+
* Calculate next phase_step by applying a PI loop using the constants
781+
* from the following analysis (based on
782+
* https://www.dsprelated.com/showarticle/967.php):
783+
*
784+
* f_s = NCO base frequency
785+
* k_p = phase detector gain
786+
* k_nco = NCO feedback scaler
787+
* zeta = loop dampening coefficient
788+
* f_n = loop natural frequency
789+
* k_l = proportional scaler
790+
* k_i = integral scaler
791+
*
792+
* w_n = f_n * 2 * pi
793+
* Ts = 1 / f_s
794+
* k_l = 2 * zeta * w_n * Ts / (k_p * k_nco)
795+
* k_i = w_n^2 * Ts^2 / (k_p * k_nco)
796+
*
797+
* Assumptions:
798+
* f_s = 72,000,000 Hz
799+
* k_p = 1.0
800+
* k_nco = 1.0
801+
* f_n = 1,440,000 Hz (2% of f_s to allow for moderate freq offset)
802+
* zeta = 0.25 (strong jitter rejection for write precomp)
803+
*
804+
* Results:
805+
* k_l = 0.06283185307 ~= 1/16
806+
* k_i = 0.01579136704 ~= 1/64
807+
*/
808+
dma_wr->wr.phase_step = (uint32_t)(
809+
(int32_t)NOMINAL_PLL_PHASE_STEP
810+
+ phase_error / 16
811+
+ dma_wr->wr.phase_integral / 64);
707812
}
708813

709814
if (bc_prod & 31)
@@ -718,14 +823,12 @@ static void IRQ_wdata_dma(void)
718823
/* Initialise decoder state for the start of the next write. */
719824
bc_prod = (bc_prod + 31) & ~31;
720825
bc_dat = ~0;
721-
prev = 0;
722826
}
723827

724828
/* Save our progress for next time. */
725829
image->write_bc_window = bc_dat;
726830
image->bufs.write_bc.prod = bc_prod;
727831
dma_wr->cons = cons;
728-
dma_wr->prev_sample = prev;
729832
}
730833

731834
/*

0 commit comments

Comments
 (0)