From ac055b667378448405014e5f8307a0b6b4b09800 Mon Sep 17 00:00:00 2001 From: giomba Date: Sat, 3 Dec 2022 20:52:47 +0100 Subject: [PATCH] Dedicated module for VGA. --- CMakeLists.txt | 1 + src/main.c | 197 +++---------------------------------------------- src/vga.c | 186 ++++++++++++++++++++++++++++++++++++++++++++++ src/vga.h | 8 ++ 4 files changed, 207 insertions(+), 185 deletions(-) create mode 100644 src/vga.c create mode 100644 src/vga.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 226d790..3afbdd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ add_executable(ceda2vga src/main.c src/draw_demo.c src/framebuffer.c + src/vga.c ) pico_generate_pio_header(ceda2vga diff --git a/src/main.c b/src/main.c index 3b83e24..48aa224 100644 --- a/src/main.c +++ b/src/main.c @@ -8,10 +8,8 @@ */ #include "hardware/clocks.h" -#include "hardware/dma.h" -#include "hardware/pio.h" + #include "hardware/pll.h" -#include "hardware/structs/pio.h" #include "pico/stdlib.h" #include @@ -19,66 +17,9 @@ #include #include -#include "vga.pio.h" - #include "draw_demo.h" #include "framebuffer.h" - -static void vga_pixel_program_init(PIO pio, uint sm, uint offset) -{ - // config function 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); - - // destination pins for OUT instructions - sm_config_set_out_pins(&config, 22, 1); - sm_config_set_out_shift(&config, true, true, 0); - pio_gpio_init(pio, 22); - pio_sm_set_consecutive_pindirs(pio, sm, 22, 1, true); - sm_config_set_wrap(&config, offset + vga_pixel_wrap_target, offset + vga_pixel_wrap); - - pio_sm_init(pio, sm, offset, &config); - - pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0); - - pio_sm_set_enabled(pio, sm, true); -} - -static void vga_hsync_program_init(PIO pio, uint sm, uint offset) -{ - pio_sm_config config = vga_hsync_program_get_default_config(offset); - // destination pins for SET instructions - sm_config_set_set_pins(&config, 20, 1); - pio_gpio_init(pio, 20); - pio_sm_set_consecutive_pindirs(pio, sm, 20, 1, true); - sm_config_set_wrap(&config, offset + vga_hsync_wrap_target, offset + vga_hsync_wrap); - - pio_sm_init(pio, sm, offset, &config); - - pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0); - - pio->sm->shiftctrl = (1 << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB); - - pio_sm_set_enabled(pio, sm, true); -} - -static void vga_vsync_program_init(PIO pio, uint sm, uint offset) -{ - pio_sm_config config = vga_vsync_program_get_default_config(offset); - // destination pins for SET instructions - sm_config_set_set_pins(&config, 21, 1); - pio_gpio_init(pio, 21); - pio_sm_set_consecutive_pindirs(pio, sm, 21, 1, true); - sm_config_set_wrap(&config, offset + vga_vsync_wrap_target, offset + vga_vsync_wrap); - - pio_sm_init(pio, sm, offset, &config); - - pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0); - - pio->sm->shiftctrl = (1 << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB); - - pio_sm_set_enabled(pio, sm, true); -} +#include "vga.h" void setup_clocks(void) { @@ -104,132 +45,14 @@ void setup_clocks(void) clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 126 * MHZ, 126 * MHZ); } -static bool dma_ready = false; -static int dma_channel; - -/** - * @brief Represents a program running on the SM of a PIO. - * - */ -typedef struct PIORun -{ - PIO pio; //< executing PIO - uint offset; //< PIO memory offset for program - uint sm; //< executing SM -} PIORun; - -static PIORun *vga_pixel_ptr = NULL; - -static unsigned long int frame_counter = 0; - -void dma_handler(void) -{ - // acknoweledge DMA - dma_hw->ints0 = 1 << dma_channel; -} - -void new_frame_handler(void) -{ - if (vga_pixel_ptr && dma_ready) - { - // Explicitly put the first uint32_t word in the FIFO, - // because, when PIO pulls data for the first time, - // and its FIFO is empty, the DMA controller takes "a while" - // to put the first word and keep up with the transfer, - // making the whole frame a bit offset to the right. - // Since front porch is very short, even the smallest delay, - // may disrupt the timing. - // Also note: despite the "blocking put", when this interrupt - // is serviced, PIO FIFO is always empty, so in practice - // this will never block. - pio_sm_put_blocking(vga_pixel_ptr->pio, vga_pixel_ptr->sm, 0); - - // Start DMA (but skip the initial word, which has been put manually) - dma_channel_set_read_addr(dma_channel, &frame[4], true); - } - - frame_counter++; - - pio_interrupt_clear(pio0_hw, 1); -} - int main() { setup_clocks(); stdio_init_all(); - sleep_ms(1000); draw_demo(); - // Running programs on PIO - PIORun vga_hsync, vga_vsync, vga_pixel; - vga_hsync.pio = vga_vsync.pio = vga_pixel.pio = pio0; - - // Prepare frame interrupt - // Frame interrupt is asserted on PIO0 interrupt 1 by SM1 - pio_set_irq1_source_enabled(vga_vsync.pio, pis_interrupt1, - true); // allow SM1 of PIO to fire the interrupt - irq_set_exclusive_handler(PIO0_IRQ_1, new_frame_handler); // set handler for IRQ1 of PIO - irq_set_enabled(PIO0_IRQ_1, true); // enable interrupt - - // VGA HSYNC program - printf("Starting VGA hsync machine... "); - if (!pio_can_add_program(vga_hsync.pio, &vga_hsync_program)) - panic("cannot add program"); - 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 - 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"); - - // VGA VSYNC program - printf("Starting VGA vsync machine... "); - if (!pio_can_add_program(vga_vsync.pio, &vga_vsync_program)) - panic("cannot add program"); - 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 - 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"); - - // VGA pixel program - vga_pixel_ptr = &vga_pixel; - printf("Starting VGA pixel machine... "); - if (!pio_can_add_program(vga_pixel.pio, &vga_pixel_program)) - panic("cannot add program"); - 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); - pio_sm_put_blocking(vga_pixel.pio, vga_pixel.sm, 640 - 1); - printf("OK\n"); - - printf("Setting up DMA... "); - dma_channel = dma_claim_unused_channel(true); - dma_channel_config dma_config = dma_channel_get_default_config(dma_channel); - 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); - channel_config_set_dreq(&dma_config, DREQ_PIO0_TX2); - - 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 - dma_channel_configure(dma_channel, &dma_config, &pio0_hw->txf[2], &frame[0], - HPIXEL * (VSPULSE + VPIXEL + VBBLANK) / 32 - 1, true); - - dma_ready = true; - printf("OK\n"); - - printf("Now running.\n"); + vga_machines_init(); for (;;) { @@ -237,10 +60,14 @@ int main() const uint16_t fc = frame_counter; const uint16_t x = fc % 640; const uint16_t y = 380 + 100 * (abs(320 - x) / 640.0) * sin((fc % 640) / 12.738); - if ((fc / 640) % 2) - set_pixel(x, y); - else - clear_pixel(x, y); - sleep_ms(5); + + set_pixel(x, y); + + // 13 ms <=> 75 Hz + // Actually, there is a point in going in sync with vertical refresh, + // but let's just forget this for the demo. + sleep_ms(13); + + clear_pixel(x, y); } } diff --git a/src/vga.c b/src/vga.c new file mode 100644 index 0000000..09d56cf --- /dev/null +++ b/src/vga.c @@ -0,0 +1,186 @@ +#include "vga.h" + +#include + +#include "hardware/dma.h" +#include "hardware/pio.h" +#include "hardware/structs/pio.h" + +#include "framebuffer.h" +#include "vga.pio.h" + +/** + * @brief Represents a program running on the SM of a PIO. + * + */ +typedef struct PIORun +{ + PIO pio; //< executing PIO + uint offset; //< PIO memory offset for program + uint sm; //< executing SM +} PIORun; + +static PIORun vga_hsync, vga_vsync, vga_pixel; +static PIORun *vga_pixel_ptr = &vga_pixel; +static bool dma_ready = false; +static int dma_channel; + +unsigned long int frame_counter = 0; + +static void vga_pixel_program_init(PIO pio, uint sm, uint offset) +{ + // config function 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); + + // destination pins for OUT instructions + sm_config_set_out_pins(&config, 22, 1); + sm_config_set_out_shift(&config, true, true, 0); + pio_gpio_init(pio, 22); + pio_sm_set_consecutive_pindirs(pio, sm, 22, 1, true); + sm_config_set_wrap(&config, offset + vga_pixel_wrap_target, offset + vga_pixel_wrap); + + pio_sm_init(pio, sm, offset, &config); + + pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0); + + pio_sm_set_enabled(pio, sm, true); +} + +static void vga_hsync_program_init(PIO pio, uint sm, uint offset) +{ + pio_sm_config config = vga_hsync_program_get_default_config(offset); + // destination pins for SET instructions + sm_config_set_set_pins(&config, 20, 1); + pio_gpio_init(pio, 20); + pio_sm_set_consecutive_pindirs(pio, sm, 20, 1, true); + sm_config_set_wrap(&config, offset + vga_hsync_wrap_target, offset + vga_hsync_wrap); + + pio_sm_init(pio, sm, offset, &config); + + pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0); + + pio->sm->shiftctrl = (1 << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB); + + pio_sm_set_enabled(pio, sm, true); +} + +static void vga_vsync_program_init(PIO pio, uint sm, uint offset) +{ + pio_sm_config config = vga_vsync_program_get_default_config(offset); + // destination pins for SET instructions + sm_config_set_set_pins(&config, 21, 1); + pio_gpio_init(pio, 21); + pio_sm_set_consecutive_pindirs(pio, sm, 21, 1, true); + sm_config_set_wrap(&config, offset + vga_vsync_wrap_target, offset + vga_vsync_wrap); + + pio_sm_init(pio, sm, offset, &config); + + pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0); + + pio->sm->shiftctrl = (1 << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB); + + pio_sm_set_enabled(pio, sm, true); +} + +void dma_handler(void) +{ + // acknoweledge DMA + dma_hw->ints0 = 1 << dma_channel; +} + +void new_frame_handler(void) +{ + if (vga_pixel_ptr && dma_ready) + { + // Explicitly put the first uint32_t word in the FIFO, + // because, when PIO pulls data for the first time, + // and its FIFO is empty, the DMA controller takes "a while" + // to put the first word and keep up with the transfer, + // making the whole frame a bit offset to the right. + // Since front porch is very short, even the smallest delay, + // may disrupt the timing. + // Also note: despite the "blocking put", when this interrupt + // is serviced, PIO FIFO is always empty, so in practice + // this will never block. + pio_sm_put_blocking(vga_pixel_ptr->pio, vga_pixel_ptr->sm, 0); + + // Start DMA (but skip the initial word, which has been put manually) + dma_channel_set_read_addr(dma_channel, &frame[4], true); + } + + frame_counter++; + + pio_interrupt_clear(pio0_hw, 1); +} + +void vga_machines_init(void) +{ + // Running programs on PIO + vga_hsync.pio = vga_vsync.pio = vga_pixel.pio = pio0; + + // Prepare frame interrupt + // Frame interrupt is asserted on PIO0 interrupt 1 by SM1 + pio_set_irq1_source_enabled(vga_vsync.pio, pis_interrupt1, + true); // allow SM1 of PIO to fire the interrupt + irq_set_exclusive_handler(PIO0_IRQ_1, new_frame_handler); // set handler for IRQ1 of PIO + irq_set_enabled(PIO0_IRQ_1, true); // enable interrupt + + // VGA HSYNC program + printf("Starting VGA hsync machine... "); + if (!pio_can_add_program(vga_hsync.pio, &vga_hsync_program)) + panic("cannot add program"); + 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 + 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"); + + // VGA VSYNC program + printf("Starting VGA vsync machine... "); + if (!pio_can_add_program(vga_vsync.pio, &vga_vsync_program)) + panic("cannot add program"); + 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 + 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"); + + // VGA pixel program + printf("Starting VGA pixel machine... "); + if (!pio_can_add_program(vga_pixel.pio, &vga_pixel_program)) + panic("cannot add program"); + 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); + pio_sm_put_blocking(vga_pixel.pio, vga_pixel.sm, 640 - 1); + printf("OK\n"); + + printf("Setting up DMA... "); + dma_channel = dma_claim_unused_channel(true); + dma_channel_config dma_config = dma_channel_get_default_config(dma_channel); + 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); + channel_config_set_dreq(&dma_config, DREQ_PIO0_TX2); + + 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 + dma_channel_configure(dma_channel, &dma_config, &pio0_hw->txf[2], &frame[0], + HPIXEL * (VSPULSE + VPIXEL + VBBLANK) / 32 - 1, true); + + dma_ready = true; + printf("OK\n"); + + printf("Now running.\n"); +} diff --git a/src/vga.h b/src/vga.h new file mode 100644 index 0000000..46ccd46 --- /dev/null +++ b/src/vga.h @@ -0,0 +1,8 @@ +#ifndef VGA_H +#define VGA_H + +void vga_machines_init(void); + +extern unsigned long int frame_counter; + +#endif // VGA_H