From ac3116a339530f2ddfc2cb1ca333bb2e5efd77c5 Mon Sep 17 00:00:00 2001 From: giomba Date: Sat, 3 Dec 2022 22:20:34 +0100 Subject: [PATCH] Add documentation. --- src/vga.c | 79 +++++++++++++++++++++++++++++++++++++++++++-------- src/vga.pio | 82 ++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 136 insertions(+), 25 deletions(-) diff --git a/src/vga.c b/src/vga.c index fa2a0b8..97e71d6 100644 --- a/src/vga.c +++ b/src/vga.c @@ -22,12 +22,26 @@ typedef struct PIORun uint sm; //< executing SM } PIORun; +/* + There are 3 SM running: horizontal and vertical sync, and pixels. + See vga.pio for details. +*/ static PIORun vga_hsync, vga_vsync, vga_pixel; -static bool dma_ready = false; -static int dma_channel; -static unsigned long int frame_counter = 0; +static bool dma_ready = false; //< current DMA acceleration status +static int dma_channel; //< DMA channel number, used to refill the vga_pixel FIFO +static unsigned long int frame_counter = 0; //< current frame + +/** + * @brief Setup system clocks. + * + * Main system clock must run at 126MHz, + * which is 4 times pixel clock (31.5 MHz). + * This allows SM to output one pixel every 4 clock cycles, + * and to do some stuff in the mean time (eg. sending interrupts to sync with the other SM) + * + */ static void setup_clocks(void) { // disable resuscitation clock @@ -52,9 +66,16 @@ static void setup_clocks(void) clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 126 * MHZ, 126 * MHZ); } +/** + * @brief Initializes the VGA pixel shift machine. + * + * @param pio PIO peripheral to use. + * @param sm SM to use. + * @param offset PIO memory offset for program. + */ static void vga_pixel_program_init(PIO pio, uint sm, uint offset) { - // config function automatically declared by SDK scripts + // config function is automatically declared by SDK scripts // based on the names given to programs in .pio pio_sm_config config = vga_pixel_program_get_default_config(offset); @@ -72,6 +93,13 @@ static void vga_pixel_program_init(PIO pio, uint sm, uint offset) pio_sm_set_enabled(pio, sm, true); } +/** + * @brief Initializes the VGA horizontal sync machine. + * + * @param pio PIO peripheral to use. + * @param sm SM to use. + * @param offset PIO memory offset for program. + */ static void vga_hsync_program_init(PIO pio, uint sm, uint offset) { pio_sm_config config = vga_hsync_program_get_default_config(offset); @@ -90,6 +118,13 @@ static void vga_hsync_program_init(PIO pio, uint sm, uint offset) pio_sm_set_enabled(pio, sm, true); } +/** + * @brief Initializes the VGA vertical sync machine. + * + * @param pio PIO peripheral to use. + * @param sm SM to use. + * @param offset PIO memory offset for program. + */ static void vga_vsync_program_init(PIO pio, uint sm, uint offset) { pio_sm_config config = vga_vsync_program_get_default_config(offset); @@ -108,13 +143,20 @@ static void vga_vsync_program_init(PIO pio, uint sm, uint offset) pio_sm_set_enabled(pio, sm, true); } -void dma_handler(void) +/** + * @brief DMA interrupt handler. + */ +static void dma_handler(void) { // acknoweledge DMA dma_hw->ints0 = 1 << dma_channel; } -void new_frame_handler(void) +/** + * @brief Vertical sync interrupt handler. + * + */ +static void new_frame_handler(void) { if (dma_ready) { @@ -142,7 +184,6 @@ void new_frame_handler(void) void vga_machines_init(void) { // setup a proper system clock - // (126MHz = 4 x 31.5 MHz pixel clock) setup_clocks(); // Running programs on PIO @@ -162,7 +203,10 @@ void vga_machines_init(void) vga_hsync.offset = pio_add_program(vga_hsync.pio, &vga_hsync_program); vga_hsync.sm = pio_claim_unused_sm(vga_hsync.pio, true); vga_hsync_program_init(vga_hsync.pio, vga_hsync.sm, vga_hsync.offset); - // low pulse is X pixels long, and SM runs at 4x of pixel clock + + // push configuration words to horizontal sync machine FIFO + // - hsync pulse duration (in pixel), assert time (low) + // - hsync pulse duration (in pixel), idle time (high), minus back porch pio_sm_put_blocking(vga_hsync.pio, vga_hsync.sm, (64 - 2)); pio_sm_put_blocking(vga_hsync.pio, vga_hsync.sm, (640 + 16 - 1)); // 16 is front porch printf("OK\n"); @@ -174,7 +218,9 @@ void vga_machines_init(void) vga_vsync.offset = pio_add_program(vga_vsync.pio, &vga_vsync_program); vga_vsync.sm = pio_claim_unused_sm(vga_vsync.pio, true); vga_vsync_program_init(vga_vsync.pio, vga_vsync.sm, vga_vsync.offset); - // low pulse assert lasts for 3 horizontal lines, then adjust by one + // push configuration words to vertical sync machine FIFO + // - vsync pulse duration (in lines), assert time (low) + // - vsync pulse duration (in lines), idle time (high) pio_sm_put_blocking(vga_vsync.pio, vga_vsync.sm, 3 - 1); pio_sm_put_blocking(vga_vsync.pio, vga_vsync.sm, 500 - 3 - 1); printf("OK\n"); @@ -186,6 +232,8 @@ void vga_machines_init(void) vga_pixel.offset = pio_add_program(vga_pixel.pio, &vga_pixel_program); vga_pixel.sm = pio_claim_unused_sm(vga_pixel.pio, false); vga_pixel_program_init(vga_pixel.pio, vga_pixel.sm, vga_pixel.offset); + // push configuration word to pixel machine FIFO + // - number of visible pixels in a line pio_sm_put_blocking(vga_pixel.pio, vga_pixel.sm, 640 - 1); printf("OK\n"); @@ -195,16 +243,18 @@ void vga_machines_init(void) channel_config_set_transfer_data_size(&dma_config, DMA_SIZE_32); channel_config_set_read_increment(&dma_config, true); channel_config_set_write_increment(&dma_config, false); + // link Data Request signal of PIO SM2 TX FIFO to DMA channel_config_set_dreq(&dma_config, DREQ_PIO0_TX2); + // setup transfer acknowledge interrupt dma_channel_set_irq0_enabled(dma_channel, true); irq_set_exclusive_handler(DMA_IRQ_0, dma_handler); irq_set_enabled(DMA_IRQ_0, true); - // -1 because first word is manually injected - // before the first DMA request, in order to - // avoid any possible delay + // pull one word less, because first word is manually injected + // before the first DMA request, in order to avoid any possible delay + // (see new frame interrupt handler) dma_channel_configure(dma_channel, &dma_config, &pio0_hw->txf[2], &frame[0], HPIXEL * (VSPULSE + VPIXEL + VBBLANK) / 32 - 1, true); @@ -214,6 +264,11 @@ void vga_machines_init(void) printf("Now running.\n"); } +/** + * @brief Get current frame counter. + * + * @return Current frame counter. + */ unsigned long int vga_get_frame_counter(void) { return frame_counter; diff --git a/src/vga.pio b/src/vga.pio index 5884897..e4b32e5 100644 --- a/src/vga.pio +++ b/src/vga.pio @@ -1,60 +1,116 @@ +/* + - horizontal sync pulse generation + this machine commands the timing for the other machines too + - vertical sync pulse generation + counts the horizontal sync pulses and generated a vertical sync pulse accordingly + - pixel machine + horizontal sync machine also sends an interrupt when the back porch is finished, + thus triggering the pixel machine to start shifting out the pixels +*/ + +/* + Pixel Machine + Wait for the "start-of-visible-line" interrupt signal, + and output 640 pixels +*/ .program vga_pixel entrypoint_vga_pixel: + // pull configuration into Y register + // Y = number of visible pixels in the line pull mov y, osr .wrap_target mov x, y wait irq 7 + + // loop lasts 4 SM clocks, which corresponds to 1 VGA pixel loop: out pins, 1 [2] jmp x-- loop .wrap +/* + Horizontal Sync Machine + A line is composed by 840 pixels: + - 64 sync pulse + - 120 back porch + - 640 visible area + - 16 front porch +*/ .program vga_hsync - entrypoint_vga_hsync: + // pull configuration into ISR and OSR registers + // ISR = sync pulse duration - 2 (64 pixel) + // OSR = visible line + front porch - 1 (640 + 16 pixel) + // Note: configuration contains desired value -1/-2, + // because of how end-of-loop check is performed + // and how time is spent in overhead instructions pull mov isr, osr pull + // once configured, no more data is pulled from the TX FIFO, + // so program wraps around here .wrap_target - set pins, 0 [1] - irq set 0 - mov x, isr + set pins, 0 [1] // start pulse low + irq set 0 // trigger "start-of-line" interrupt signal + mov x, isr // load register X with sync pulse duration + // +4 SM clocks elapsed (first VGA pixel) + + // loop for the duration of the low pulse (remaining 63 pixels) hsync_pulse: jmp x-- hsync_pulse [3] - set pins, 1 [1] - set y, 27 + set pins, 1 [1] // start pulse high (.5 pixel) + // hardcoded backporch duration: 120 pixel => 120 x 4 = 480 SM clocks + // but one pixel is already in the surronding logic, then only 476 clocks are cycled + // where 476 = (27 + 1) * (16 + 1) + set y, 27 // another .25 pixel hsync_back_porch: jmp y-- hsync_back_porch [16] - irq set 7 + irq set 7 // trigger "start-of-visible-line" interrupt signal + // (.25 more pixel) - mov x, osr [3] + mov x, osr [3] // load register X with remaining line duration (visible line + front porch) + // also, just make this last 4 SM clocks (1 pixel) hsync_idle: jmp x-- hsync_idle [3] .wrap +/* + Vertical Sync Machine + A frame is composed by 500 lines: + - 3 vertical sync pulse + - 16 back porch + - 480 visible lines + - 1 front porch +*/ .program vga_vsync - entrypoint_vga_vsync: + // Pull configuration into ISR and OSR registers + // ISR = sync pulse assert duration - 1 (3 lines) + // OSR = sync pulse idle duration - 1 (497 lines) pull mov isr, osr pull + // once configured, no more data is pulled from the TX FIFO, + // so program wraps around here .wrap_target - mov x, isr + mov x, isr // load register X with low pulse duration vsync_pulse: - wait irq 0 - set pins, 0 + wait irq 0 // wait for "start-of-line" interrupt from hsync machine + set pins, 0 // start pulse low jmp x-- vsync_pulse + // trigger "start-of-frame" interrupt + // (actually, 3 lines of the back porch are already passed) irq set 1 - mov x, osr + mov x, osr // load register X with high pulse duration vsync_idle: wait irq 0 set pins, 1