#include "vga.h" #include #include "hardware/dma.h" #include "hardware/pio.h" #include "hardware/structs/pio.h" #include "hardware/clocks.h" #include "hardware/pll.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; /* 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; //< 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 clocks_hw->resus.ctrl = 0; // before changing PLL, switch sys and ref cleanly away from their aux sources hw_clear_bits(&clocks_hw->clk[clk_sys].ctrl, CLOCKS_CLK_SYS_CTRL_SRC_BITS); while (clocks_hw->clk[clk_sys].selected != 0x1) tight_loop_contents(); hw_clear_bits(&clocks_hw->clk[clk_ref].ctrl, CLOCKS_CLK_REF_CTRL_SRC_BITS); while (clocks_hw->clk[clk_ref].selected != 0x1) tight_loop_contents(); // set PLL at 126 MHz, and wait for it to stabilize pll_init(pll_sys, 1, 1512 * MHZ, 6, 2); // re-configure sys_clk to use PLL clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX, CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS, 126 * MHZ, 126 * MHZ); // re-configure CLK PERI = clk_sys 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 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); // 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); } /** * @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); // 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); } /** * @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); // 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); } /** * @brief DMA interrupt handler. */ static void dma_handler(void) { // acknoweledge DMA dma_hw->ints0 = 1 << dma_channel; } /** * @brief Vertical sync interrupt handler. * */ static void new_frame_handler(void) { if (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.pio, vga_pixel.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) { // setup a proper system clock setup_clocks(); // 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); // 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"); // 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); // 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"); // 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); // 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"); printf("Setting up VGA 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); // 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); // 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); dma_ready = true; printf("OK\n"); } /** * @brief Get current frame counter. * * @return Current frame counter. */ unsigned long int vga_get_frame_counter(void) { return frame_counter; }