This commit is contained in:
2023-06-15 12:00:00 +01:00
parent 38010a5fa6
commit ff2e22ccfc
5 changed files with 172 additions and 178 deletions

View File

@@ -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)

View File

@@ -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 <stdio.h>
#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();
}
}

138
src/vga.c
View File

@@ -1,7 +1,4 @@
// Copyright Benjamin Kyd 2021 All Rights Reserved
#include <stdio.h>
#include <string.h>
#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;
vga_pixel_t vga_framebuffer[VGA_FB_HEIGHT][VGA_FB_WIDTH];
// setup 2 buffers with DMA
// setup swap
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;
// use pin mask (5 pins for 1 bit colour, 12 for 4)
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);
}

View File

@@ -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 <stdint.h>
#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

View File

@@ -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