diff --git a/src/main.c b/src/main.c index 90e78d6..98ab869 100644 --- a/src/main.c +++ b/src/main.c @@ -17,21 +17,26 @@ #include #include #include +#include +#include #include "vga.pio.h" -#define FRAMES 2 +#define HPIXEL 640 // number of pixels displayed horizontally +#define VPIXEL 480 // number of pixels displayed vertically +#define BPP 1 // bits per pixel -#define HPIXEL 640 -#define VPIXEL 480 -#define BPP 1 +#define VSPULSE 3 // number of lines during the vertical sync pulse +#define VBBLANK 16 // number of lines of the initial back porch blanking period +#define VFBLANK 1 // number of lines of the final front porch blanking period -typedef struct Frame -{ - uint8_t data[HPIXEL * VPIXEL * BPP / 8]; -} Frame; - -static Frame frames[FRAMES] = {0}; +// Frame buffer. +// Due to limitations in program space of PIO interface, +// additional blank lines are needed and sent to the display, +// to fill the vertical blanking interval at top and bottom +// of the display +// (at 1BPP, this wastes about 1k) +uint8_t frame[HPIXEL * (VPIXEL + VBBLANK + VFBLANK) * BPP / 8] = {0}; static void vga_pixel_program_init(PIO pio, uint sm, uint offset) { @@ -125,18 +130,9 @@ typedef struct PIORun uint sm; //< executing SM } PIORun; -PIORun *vga_pixel_ptr = NULL; +static PIORun *vga_pixel_ptr = NULL; -void new_frame_handler(void) -{ - if (vga_pixel_ptr && dma_ready) - { - pio_sm_put_blocking(vga_pixel_ptr->pio, vga_pixel_ptr->sm, 0); - dma_channel_set_read_addr(dma_channel, &frames[0].data[4], true); - } - - pio_interrupt_clear(pio0_hw, 1); -} +static unsigned long int frame_counter = 0; void dma_handler(void) { @@ -144,14 +140,45 @@ void dma_handler(void) dma_hw->ints0 = 1 << dma_channel; } +bool valid_coord(uint16_t x, uint16_t y) +{ + if (x < 0 || x >= HPIXEL) + return false; + if (y < 0 || y >= VPIXEL) + return false; + + return true; +} + void set_pixel(uint16_t x, uint16_t y) { - frames[0].data[y * 80 + x / 8] |= (1 << x % 8); + // ignore out of frame pixels + if (!valid_coord(x, y)) + return; + + // Since there is not much space in PIO program memory, + // it is not possible to set the last pixel of a line, + // because there is not enough program space to unset it + // in the hardware, and, if left set, will mess with + // the sync signals. + if (x == 639) + return; + + // skip wasted blanking lines + // (see frame struct declaration) + y += VBBLANK + VFBLANK; + + // set bit + frame[y * 80 + x / 8] |= (1 << x % 8); } void clear_pixel(uint16_t x, uint16_t y) { - frames[0].data[y * 80 + x / 8] &= ~(1 << x % 8); + if (!valid_coord(x, y)) + return; + + y += VBBLANK + VFBLANK; + frame[y * 80 + x / 8] &= ~(1 << x % 8); } void draw_simple_line(uint16_t x, uint16_t y, uint16_t len, bool horizontal) @@ -165,6 +192,31 @@ void draw_simple_line(uint16_t x, uint16_t y, uint16_t len, bool horizontal) } } +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(); @@ -172,28 +224,29 @@ int main() sleep_ms(1000); // clear frame buffer - memset(&frames[0].data[0], 0, sizeof(frames[0].data)); + memset(&frame[0], 0, sizeof(frame)); // draw a cross // horizontal lines - for (uint16_t x = 0; x < 639; ++x) // last pixel high -> bad + for (uint16_t x = 0; x < 640; ++x) { if ((x / 32) % 2 == 0) - set_pixel(x, 20); + set_pixel(x, 0); if ((x / 32) % 2 == 1) - set_pixel(x, 23); + set_pixel(x, 2); set_pixel(x, 239); set_pixel(x, 479); } // vertical lines - for (uint16_t y = 21; y < 480; ++y) + for (uint16_t y = 0; y < 480; ++y) { - set_pixel(0 + ((y - 20) / 10) * 4, y); + set_pixel(0 + (y / 10) * 4, y); set_pixel(0, y); set_pixel(319, y); + // rightmost pixels can't be set, see description set_pixel(638, y); } // try to find strangeness using stairs - for (uint16_t x = 0; x < 639; ++x) + for (uint16_t x = 0; x < 640; ++x) { set_pixel(x, (x / 32) * 4 + 120); } @@ -203,8 +256,6 @@ int main() draw_simple_line(100, 104, 8, true); draw_simple_line(100, 108, 8, true); - clear_pixel(639, 479); - // Running programs on PIO PIORun vga_hsync, vga_vsync, vga_pixel; vga_hsync.pio = vga_vsync.pio = vga_pixel.pio = pio0; @@ -264,8 +315,11 @@ int main() irq_set_exclusive_handler(DMA_IRQ_0, dma_handler); irq_set_enabled(DMA_IRQ_0, true); - dma_channel_configure(dma_channel, &dma_config, &pio0_hw->txf[2], &frames[0].data[0], 9600 - 1, - 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 * (VPIXEL + VBBLANK + VFBLANK) / 32 - 1, true); dma_ready = true; printf("OK\n"); @@ -273,5 +327,15 @@ int main() printf("Now running.\n"); for (;;) - tight_loop_contents(); + { + // fancy drawings + const uint16_t fc = frame_counter; + const uint16_t x = fc % 640; + const uint16_t y = 239 + 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); + } }