diff --git a/CMakeLists.txt b/CMakeLists.txt index 797326b..051ed73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,35 +1,35 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_C_STANDARD 11) - set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -# Pull in Raspberry Pi Pico SDK (must be before project) set(PICO_SDK_FETCH_FROM_GIT on) include(pico_sdk_import.cmake) project(pico_vga C CXX ASM) -include_directories(${CMAKE_SOURCE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/src) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wall -O2") pico_sdk_init() add_executable(pico_vga src/main.c - ) + src/vga.c +) -# Add the standard library to the build target_link_libraries(pico_vga pico_stdlib hardware_pio hardware_dma hardware_irq - ) + hardware_clocks +) pico_generate_pio_header(pico_vga ${CMAKE_CURRENT_LIST_DIR}/src/vga.pio) +pico_enable_stdio_usb(pico_vga 1) +pico_enable_stdio_uart(pico_vga 0) + pico_add_extra_outputs(pico_vga) diff --git a/src/main.c b/src/main.c index 6b6b8de..0d8886d 100644 --- a/src/main.c +++ b/src/main.c @@ -1,94 +1,32 @@ -// Copyright Benjamin Kyd 2021 All Rights Reserved +// Copyright Benjamin Kyd 2023 All Rights Reserved +// +// GPIO 0-3: RED DAC, 4-7: GREEN DAC, 8-11: BLUE DAC +// GPIO 12: HSYNC, GPIO 13: VSYNC -// PIN LAYOUT -// PIN 0-3 RED DAC -// PIN 4-7 GREEN DAC -// PIN 8-11 BLUE DAC -// PIN 12 HSYNC -// PIN 13 VSYNC - -#include #include "pico/stdlib.h" -#include "hardware/clocks.h" -#include "hardware/pio.h" -#include "hardware/dma.h" -#include "hardware/irq.h" +#include "vga.h" -#include "vga.pio.h" +static void draw_test_pattern(void) { + for (int y = 0; y < VGA_FB_HEIGHT; y++) { + for (int x = 0; x < VGA_FB_WIDTH; x++) { + uint8_t r = (uint8_t)((x * 15) / VGA_FB_WIDTH); + uint8_t g = (uint8_t)((y * 15) / VGA_FB_HEIGHT); + uint8_t b = 8; -typedef struct video_timing { - uint32_t clock_freq; + if (x % 10 == 0 || y % 10 == 0) { + r = g = b = 15; + } - uint16_t h_active; - uint16_t v_active; - - uint16_t h_front_porch; - uint16_t h_pulse; - uint16_t h_total; - uint8_t h_sync_polarity; - - uint16_t v_front_porch; - uint16_t v_pulse; - uint16_t v_total; - uint8_t v_sync_polarity; - - uint8_t enable_clock; - uint8_t clock_polarity; - - uint8_t enable_den; -} video_timing_t; - -const video_timing_t vga_timing_800x600_60 = -{ - .clock_freq = 38400000, - - .h_active = 800, - .v_active = 600, - - .h_front_porch = 4 * 8, - .h_pulse = 10 * 8, - .h_total = 128 * 8, - .h_sync_polarity = 0, - - .v_front_porch = 1, - .v_pulse = 3, - .v_total = 625, - .v_sync_polarity = 0, - - .enable_clock = 0, - .clock_polarity = 0, - - .enable_den = 0 -}; - -static inline void vga_program_init(PIO pio, uint sm, uint offset, uint pin) { - - pio_gpio_init(pio, pin); - pio_gpio_init(pio, pin+1); - pio_sm_set_consecutive_pindirs(pio, sm, pin, 2, true); - - pio_sm_config c = vga_program_get_default_config(offset); - - sm_config_set_out_pins(&c, pin, 2); - - sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); - sm_config_set_out_shift(&c, false, true, 2); - - pio_sm_init(pio, sm, offset, &c); - pio_sm_set_enabled(pio, sm, true); -} - -int main() { - PIO pio = pio0; - uint offset = pio_add_program(pio, &vga_program); - uint sm = pio_claim_unused_sm(pio, true); - - vga_program_init(pio, sm, offset, 12); - - while (true) { - pio_sm_put_blocking(pio, sm, 0b01); - sleep_ms(20); - pio_sm_put_blocking(pio, sm, 0b10); - sleep_ms(20); + vga_framebuffer[y][x] = vga_rgb(r, g, b); + } + } +} + +int main(void) { + vga_init(); + draw_test_pattern(); + + while (true) { + tight_loop_contents(); } } diff --git a/src/vga.c b/src/vga.c index e6c63a8..f44f9ae 100644 --- a/src/vga.c +++ b/src/vga.c @@ -1,7 +1,4 @@ -// Copyright Benjamin Kyd 2021 All Rights Reserved - -#include - +#include #include "pico/stdlib.h" #include "hardware/clocks.h" #include "hardware/pio.h" @@ -9,52 +6,113 @@ #include "hardware/irq.h" #include "vga.h" -#include "video.pio.h" +#include "vga.pio.h" -// const video_timing_t vga_timing_800x600_60 = -// { -// .clock_freq = 38400000, -// -// .h_active = 800, -// .v_active = 600, -// -// .h_front_porch = 4 * 8, -// .h_pulse = 10 * 8, -// .h_total = 128 * 8, -// .h_sync_polarity = 0, -// -// .v_front_porch = 1, -// .v_pulse = 3, -// .v_total = 625, -// .v_sync_polarity = 0, -// -// .enable_clock = 0, -// .clock_polarity = 0, -// -// .enable_den = 0 -// }; +#define H_ACTIVE 800 +#define H_FRONT_PORCH 40 +#define H_SYNC_PULSE 128 +#define H_BACK_PORCH 88 +#define H_TOTAL 1056 -#define PIN_START 8 -#define PIN_COUNT 2 +#define V_ACTIVE 600 +#define V_FRONT_PORCH 1 +#define V_SYNC_PULSE 4 +#define V_BACK_PORCH 23 +#define V_TOTAL 628 -void vga_init(const video_timing_t* timing) -{ - // Calculate the PIO clock divider - uint sys_clock = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS); - float clock_div = ((float)sys_clock * 1000.f) / (float)timing->clock_freq; +#define HSYNC_BIT (1u << 12) +#define VSYNC_BIT (1u << 13) - printf("System clock frequency %i\n", sys_clock); - printf("Pixel clock frequency %i\n", timing->clock_freq); - printf("Clock divider %f\n", clock_div); - +#define VGA_PIO pio0 +#define VGA_SM 0 +#define VGA_DMA_CHAN 0 +static uint32_t scanline_buf[2][H_TOTAL]; +static volatile int active_buf = 0; +static volatile uint32_t current_line = 0; - // setup 2 buffers with DMA - // setup swap - - // use pin mask (5 pins for 1 bit colour, 12 for 4) +vga_pixel_t vga_framebuffer[VGA_FB_HEIGHT][VGA_FB_WIDTH]; +static void build_scanline(uint32_t *buf, uint32_t line) { + const bool vsync = (line >= (uint32_t)(V_ACTIVE + V_FRONT_PORCH)) && + (line < (uint32_t)(V_ACTIVE + V_FRONT_PORCH + V_SYNC_PULSE)); + const uint32_t vsync_bit = vsync ? VSYNC_BIT : 0u; + uint32_t *ptr = buf; + if (line < V_ACTIVE) { + const uint32_t fy = line >> 2; + const vga_pixel_t *row = vga_framebuffer[fy < VGA_FB_HEIGHT ? fy : VGA_FB_HEIGHT - 1u]; + for (int x = 0; x < H_ACTIVE; x++) { + *ptr++ = (uint32_t)row[x >> 2] | vsync_bit; + } + } else { + for (int x = 0; x < H_ACTIVE; x++) { + *ptr++ = vsync_bit; + } + } + + for (int x = 0; x < H_FRONT_PORCH; x++) *ptr++ = vsync_bit; + + const uint32_t hsync_word = HSYNC_BIT | vsync_bit; + for (int x = 0; x < H_SYNC_PULSE; x++) *ptr++ = hsync_word; + + for (int x = 0; x < H_BACK_PORCH; x++) *ptr++ = vsync_bit; } +static void dma_irq_handler(void) { + dma_hw->ints0 = 1u << VGA_DMA_CHAN; + + current_line++; + if (current_line >= V_TOTAL) current_line = 0; + + active_buf ^= 1; + dma_channel_set_read_addr(VGA_DMA_CHAN, scanline_buf[active_buf], false); + dma_channel_set_trans_count(VGA_DMA_CHAN, H_TOTAL, true); + + const uint32_t next_line = (current_line + 1u) % V_TOTAL; + build_scanline(scanline_buf[active_buf ^ 1], next_line); +} + +void vga_init(void) { + set_sys_clock_khz(120000, true); + + const uint offset = pio_add_program(VGA_PIO, &vga_out_program); + + pio_sm_config c = vga_out_program_get_default_config(offset); + sm_config_set_out_pins(&c, 0, 14); + sm_config_set_out_shift(&c, true, true, 14); + sm_config_set_clkdiv(&c, 3.0f); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + + for (int i = 0; i < 14; i++) pio_gpio_init(VGA_PIO, i); + pio_sm_set_consecutive_pindirs(VGA_PIO, VGA_SM, 0, 14, true); + pio_sm_init(VGA_PIO, VGA_SM, offset, &c); + + dma_channel_config dc = dma_channel_get_default_config(VGA_DMA_CHAN); + channel_config_set_transfer_data_size(&dc, DMA_SIZE_32); + channel_config_set_read_increment(&dc, true); + channel_config_set_write_increment(&dc, false); + channel_config_set_dreq(&dc, pio_get_dreq(VGA_PIO, VGA_SM, true)); + + dma_channel_configure( + VGA_DMA_CHAN, &dc, + &VGA_PIO->txf[VGA_SM], + scanline_buf[0], + H_TOTAL, + false + ); + + dma_channel_set_irq0_enabled(VGA_DMA_CHAN, true); + irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler); + irq_set_enabled(DMA_IRQ_0, true); + + build_scanline(scanline_buf[0], 0); + build_scanline(scanline_buf[1], 1); + active_buf = 0; + current_line = 0; + + pio_sm_set_enabled(VGA_PIO, VGA_SM, true); + dma_channel_set_read_addr(VGA_DMA_CHAN, scanline_buf[0], false); + dma_channel_set_trans_count(VGA_DMA_CHAN, H_TOTAL, true); +} diff --git a/src/vga.h b/src/vga.h index 41e7c4e..83aa9c6 100644 --- a/src/vga.h +++ b/src/vga.h @@ -1,40 +1,23 @@ #ifndef PICOVGA_VGA_H_ #define PICOVGA_VGA_H_ -#include "pico/types.h" -// -// typedef struct video_timing { -// uint32_t clock_freq; -// -// uint16_t h_active; -// uint16_t v_active; -// -// uint16_t h_front_porch; -// uint16_t h_pulse; -// uint16_t h_total; -// uint8_t h_sync_polarity; -// -// uint16_t v_front_porch; -// uint16_t v_pulse; -// uint16_t v_total; -// uint8_t v_sync_polarity; -// -// uint8_t enable_clock; -// uint8_t clock_polarity; -// -// uint8_t enable_den; -// } video_timing_t; -// -// const video_timing_t vga_timing_800x600_60; -// -// // typedef struct video { -// // uint16_t* -// // } video_t; -// -// void vga_init(const video_timing_t* timing); -// -// void vga_start(); -// -// void vga_swap_buffers(); -// +#include + +#define VGA_FB_WIDTH 200 +#define VGA_FB_HEIGHT 150 + +typedef uint16_t vga_pixel_t; + +static inline vga_pixel_t vga_rgb(uint8_t r, uint8_t g, uint8_t b) { + return (uint16_t)((r & 0xF) | ((g & 0xF) << 4) | ((b & 0xF) << 8)); +} + +static inline vga_pixel_t vga_rgb8(uint8_t r, uint8_t g, uint8_t b) { + return vga_rgb(r >> 4, g >> 4, b >> 4); +} + +extern vga_pixel_t vga_framebuffer[VGA_FB_HEIGHT][VGA_FB_WIDTH]; + +void vga_init(void); + #endif diff --git a/src/vga.pio b/src/vga.pio index 9bcb69a..9ab7f07 100644 --- a/src/vga.pio +++ b/src/vga.pio @@ -1,4 +1,19 @@ -.program vga -loop: - out pins, 2 - jmp loop +; +; VGA pixel output PIO program +; +; Outputs 14 bits per pixel clock cycle to GPIO 0-13: +; bits [3:0] = RED (GPIO 0-3) +; bits [7:4] = GREEN (GPIO 4-7) +; bits [11:8] = BLUE (GPIO 8-11) +; bit [12] = HSYNC (GPIO 12) - positive polarity for 800x600@60Hz +; bit [13] = VSYNC (GPIO 13) - positive polarity for 800x600@60Hz +; +; Each 32-bit FIFO word = one pixel (14 bits used, 18 bits discarded via autopull). +; PIO clock divider must be set so instruction rate = pixel clock (40 MHz). +; + +.program vga_out + +.wrap_target + out pins, 14 ; output 14 bits to GPIO 0-13 +.wrap