Skip to content

Commit 0c21aaf

Browse files
committed
Fix a timing bug in the video generation.
All the timing periods (front porch, sync pulse, back porch, visible video) were one clock cycle too short thanks to an off-by-one error when I was counting the overhead of the timing PIO function. This is what led to rolling video noise and slightly fuzzy video capture - monitors and capture devices were trying to grab 640 pixels out of a period that was only 639 clock cycles in length. Now the video looks much sharper.
1 parent 4bf443c commit 0c21aaf

File tree

1 file changed

+25
-9
lines changed

1 file changed

+25
-9
lines changed

src/vga/mod.rs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ impl ScanlineTimingBuffer {
800800
if vsync {
801801
value |= 1 << 1;
802802
}
803-
value |= (period - 6) << 2;
803+
value |= (period - FIXED_CLOCKS_PER_TIMING_PULSE) << 2;
804804
value | command << 16
805805
}
806806
}
@@ -1680,6 +1680,9 @@ static PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer::new_odd();
16801680
/// ```
16811681
static TEXT_COLOUR_LOOKUP: TextColourLookup = TextColourLookup::blank();
16821682

1683+
/// How many fixed clock cycles there are per timing pulse.
1684+
const FIXED_CLOCKS_PER_TIMING_PULSE: u32 = 5;
1685+
16831686
// -----------------------------------------------------------------------------
16841687
// Functions
16851688
// -----------------------------------------------------------------------------
@@ -1700,28 +1703,41 @@ pub fn init(
17001703
let (mut pio, sm0, sm1, _sm2, _sm3) = pio.split(resets);
17011704

17021705
// This program runs the timing loop. We post timing data (i.e. the length
1703-
// of each period, along with what the H-Sync and V-Sync pins should do)
1704-
// and it sets the GPIO pins and busy-waits the appropriate amount of
1705-
// time. It also takes an extra 'instruction' which we can use to trigger
1706-
// the appropriate interrupts.
1706+
// of each period, along with what the H-Sync and V-Sync pins should do) and
1707+
// it sets the GPIO pins and busy-waits the appropriate amount of time. It
1708+
// also takes an extra 'instruction' which we can use to trigger the
1709+
// appropriate interrupts.
1710+
//
1711+
// Note that the timing period value should be:
1712+
//
1713+
// timing_period = actual_timing_period - FIXED_CLOCKS_PER_TIMING_PULSE
1714+
//
1715+
// This is because there are unavoidable clock cycles within the algorithm.
1716+
// Currently FIXED_CLOCKS_PER_TIMING_PULSE should be set to 5.
17071717
//
17081718
// Post <value:32> where value: <clock_cycles:14> <hsync:1> <vsync:1>
17091719
// <instruction:16>
17101720
//
17111721
// The SM will execute the instruction (typically either a NOP or an IRQ),
1712-
// set the H-Sync and V-Sync pins as desired, then wait the given number
1713-
// of clock cycles.
1722+
// set the H-Sync and V-Sync pins as desired, then wait the given number of
1723+
// clock cycles.
17141724
//
17151725
// Note: autopull should be set to 32-bits, OSR is set to shift right.
17161726
let timing_program = pio_proc::pio_asm!(
17171727
".wrap_target"
1718-
// Step 1. Push next 2 bits of OSR into `pins`, to set H-Sync and V-Sync
1728+
// Step 1. Push next 2 bits of OSR into `pins`, to set H-Sync and V-Sync.
1729+
// Takes 1 clock cycle.
17191730
"out pins, 2"
17201731
// Step 2. Push last 14 bits of OSR into X for the timing loop.
1732+
// Takes 1 clock cycle.
17211733
"out x, 14"
1722-
// Step 3. Execute bottom 16-bits of OSR as an instruction. This take two cycles.
1734+
// Step 3. Execute bottom 16-bits of OSR as an instruction.
1735+
// This take two cycles, always.
17231736
"out exec, 16"
17241737
// Spin until X is zero
1738+
// Takes X + 1 clock cycles because the branch is conditioned on the initial value of the register.
1739+
// i.e. X = 0 => 1 clock cycle (the jmp when x = 0)
1740+
// i.e. X = 1 => 2 clock cycles (the jmp when x = 1 and again when x = 0)
17251741
"loop0:"
17261742
"jmp x-- loop0"
17271743
".wrap"

0 commit comments

Comments
 (0)