231 lines
7.3 KiB
C
231 lines
7.3 KiB
C
// main.c
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "pico/stdlib.h"
|
|
#include "hardware/clocks.h"
|
|
#include "hardware/dma.h"
|
|
#include "hardware/irq.h"
|
|
#include "hardware/pio.h"
|
|
|
|
#include "video_dma.pio.h"
|
|
|
|
// ---------------------------
|
|
// User-adjustable pins
|
|
// ---------------------------
|
|
#define VIDEO_PIO pio0
|
|
#define DATA_SM 0
|
|
#define SYNC_SM 1
|
|
|
|
#define DATA_PIN 2 // pixel / luma bit pin
|
|
#define SYNC_PIN 3 // sync pin
|
|
|
|
// ---------------------------
|
|
// Video format
|
|
// ---------------------------
|
|
#define VIDEO_WIDTH 768
|
|
#define VIDEO_HEIGHT 576
|
|
#define WORDS_PER_LINE (VIDEO_WIDTH / 32)
|
|
#define LINES_PER_FIELD (VIDEO_HEIGHT / 2)
|
|
|
|
#define SYNC_INTERVAL_S 0.000002f // 2 us per sync-SM instruction
|
|
#define ACTIVE_VIDEO_S 0.000052f // 52 us active video per line
|
|
|
|
// Match the .pio program's published constant.
|
|
// pioasm will emit CLOCKS_PER_BIT in the generated header.
|
|
#ifndef CLOCKS_PER_BIT
|
|
#define CLOCKS_PER_BIT 6
|
|
#endif
|
|
|
|
// ---------------------------
|
|
// Framebuffer
|
|
// One bit per pixel, 32 pixels per word
|
|
// ---------------------------
|
|
static uint32_t framebuffer[VIDEO_HEIGHT][WORDS_PER_LINE];
|
|
|
|
// Field/line tracking for line-based DMA.
|
|
// This is intentionally kept in software to match the sync SM's 288 active
|
|
// lines per field.
|
|
static volatile bool even_field = true;
|
|
static volatile uint line_in_field = 0;
|
|
|
|
// DMA channel that feeds the PIO TX FIFO
|
|
static int video_dma_chan;
|
|
|
|
// ---------------------------
|
|
// Optional test pattern
|
|
// Replace this with your own drawing code.
|
|
// ---------------------------
|
|
static void fill_test_pattern(void) {
|
|
memset(framebuffer, 0x00, sizeof(framebuffer));
|
|
|
|
for (uint y = 0; y < VIDEO_HEIGHT; ++y) {
|
|
for (uint x = 0; x < VIDEO_WIDTH; ++x) {
|
|
bool on = false;
|
|
|
|
// Simple visible pattern:
|
|
// border + checker
|
|
if (x < 8 || x >= VIDEO_WIDTH - 8 || y < 8 || y >= VIDEO_HEIGHT - 8) {
|
|
on = true;
|
|
} else if (((x >> 4) ^ (y >> 4)) & 1) {
|
|
on = true;
|
|
}
|
|
|
|
if (on) {
|
|
uint word = x >> 5;
|
|
uint bit = 31 - (x & 31); // MSB-first
|
|
framebuffer[y][word] |= (1u << bit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------
|
|
// PIO init helpers
|
|
// ---------------------------
|
|
static void init_cvdata_program(PIO pio, uint sm, uint offset, float clkdiv, uint data_pin) {
|
|
pio_sm_config c = cvdata_program_get_default_config(offset);
|
|
|
|
sm_config_set_set_pins(&c, data_pin, 1);
|
|
sm_config_set_out_pins(&c, data_pin, 1);
|
|
sm_config_set_clkdiv(&c, clkdiv);
|
|
|
|
// Shift left, autopull every 32 bits.
|
|
// If the image is mirrored, change this to shift-right and/or change bit packing.
|
|
sm_config_set_out_shift(&c, false, true, 32);
|
|
|
|
pio_gpio_init(pio, data_pin);
|
|
pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true);
|
|
|
|
pio_sm_init(pio, sm, offset, &c);
|
|
|
|
// Preload X = VIDEO_WIDTH - 1
|
|
pio_sm_put_blocking(pio, sm, VIDEO_WIDTH - 1);
|
|
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
|
|
pio_sm_exec(pio, sm, pio_encode_mov(pio_x, pio_osr));
|
|
pio_sm_exec(pio, sm, pio_encode_out(pio_null, 32));
|
|
}
|
|
|
|
static void init_cvsync_program(PIO pio, uint sm, uint offset, float clkdiv, uint sync_pin) {
|
|
pio_sm_config c = cvsync_program_get_default_config(offset);
|
|
|
|
sm_config_set_sideset_pins(&c, sync_pin);
|
|
sm_config_set_clkdiv(&c, clkdiv);
|
|
|
|
pio_gpio_init(pio, sync_pin);
|
|
pio_sm_set_consecutive_pindirs(pio, sm, sync_pin, 1, true);
|
|
|
|
pio_sm_init(pio, sm, offset, &c);
|
|
|
|
// Preload OSR = lines_per_field - 1
|
|
pio_sm_put_blocking(pio, sm, LINES_PER_FIELD - 1);
|
|
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
|
|
|
|
// Start with Y = 0, meaning "even field" in this program.
|
|
pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0));
|
|
}
|
|
|
|
// ---------------------------
|
|
// DMA setup
|
|
// ---------------------------
|
|
static void init_video_dma(PIO pio, uint data_sm) {
|
|
video_dma_chan = dma_claim_unused_channel(true);
|
|
|
|
dma_channel_config cfg = dma_channel_get_default_config(video_dma_chan);
|
|
channel_config_set_transfer_data_size(&cfg, DMA_SIZE_32);
|
|
channel_config_set_read_increment(&cfg, true);
|
|
channel_config_set_write_increment(&cfg, false);
|
|
|
|
// Pace the DMA from the data SM TX FIFO DREQ
|
|
channel_config_set_dreq(&cfg, pio_get_dreq(pio, data_sm, true));
|
|
|
|
dma_channel_configure(
|
|
video_dma_chan,
|
|
&cfg,
|
|
&pio->txf[data_sm], // write address
|
|
NULL, // read address set per line
|
|
0, // transfer count set per line
|
|
false // don't start yet
|
|
);
|
|
}
|
|
|
|
// ---------------------------
|
|
// Start DMA for one scanline
|
|
// ---------------------------
|
|
static inline void start_dma_for_current_line(void) {
|
|
uint src_line = even_field ? (line_in_field * 2u) : (line_in_field * 2u + 1u);
|
|
|
|
dma_channel_set_read_addr(video_dma_chan, framebuffer[src_line], false);
|
|
dma_channel_set_trans_count(video_dma_chan, WORDS_PER_LINE, true);
|
|
|
|
line_in_field++;
|
|
if (line_in_field >= LINES_PER_FIELD) {
|
|
line_in_field = 0;
|
|
even_field = !even_field;
|
|
}
|
|
}
|
|
|
|
// ---------------------------
|
|
// PIO IRQ handler:
|
|
// fires once per active scanline when cvsync executes `irq set LINE_IRQ`.
|
|
// We clear the PIO IRQ and arm one DMA transfer for the next active line.
|
|
// ---------------------------
|
|
static void pio0_irq0_handler(void) {
|
|
if (pio_interrupt_get(VIDEO_PIO, 0)) {
|
|
pio_interrupt_clear(VIDEO_PIO, 0);
|
|
|
|
// Safety: a new line should only arrive after the previous line DMA is done.
|
|
// If it isn't, the clocks are wrong or something stalled badly.
|
|
if (!dma_channel_is_busy(video_dma_chan)) {
|
|
start_dma_for_current_line();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------
|
|
// Video start
|
|
// ---------------------------
|
|
static void video_init(void) {
|
|
if ((VIDEO_WIDTH % 32) != 0) {
|
|
panic("VIDEO_WIDTH must be a multiple of 32");
|
|
}
|
|
|
|
uint sync_offset = pio_add_program(VIDEO_PIO, &cvsync_program);
|
|
uint data_offset = pio_add_program(VIDEO_PIO, &cvdata_program);
|
|
|
|
// Same timing idea as the original repo:
|
|
// sync SM: 1 instruction every 2 us
|
|
// data SM: enough PIO cycles to emit VIDEO_WIDTH bits in 52 us
|
|
float sys_hz = (float)clock_get_hz(clk_sys);
|
|
float data_clkdiv = (sys_hz / ((float)VIDEO_WIDTH / ACTIVE_VIDEO_S)) / (float)CLOCKS_PER_BIT;
|
|
float sync_clkdiv = sys_hz * SYNC_INTERVAL_S;
|
|
|
|
init_cvdata_program(VIDEO_PIO, DATA_SM, data_offset, data_clkdiv, DATA_PIN);
|
|
init_cvsync_program(VIDEO_PIO, SYNC_SM, sync_offset, sync_clkdiv, SYNC_PIN);
|
|
init_video_dma(VIDEO_PIO, DATA_SM);
|
|
|
|
// Route PIO internal IRQ 0 to CPU IRQ 0
|
|
pio_set_irq0_source_enabled(VIDEO_PIO, pis_interrupt0, true);
|
|
irq_set_exclusive_handler(PIO0_IRQ_0, pio0_irq0_handler);
|
|
irq_set_enabled(PIO0_IRQ_0, true);
|
|
|
|
// Start with the first line of the even field
|
|
even_field = true;
|
|
line_in_field = 0;
|
|
|
|
pio_sm_set_enabled(VIDEO_PIO, DATA_SM, true);
|
|
pio_sm_set_enabled(VIDEO_PIO, SYNC_SM, true);
|
|
}
|
|
|
|
int main(void) {
|
|
stdio_init_all();
|
|
|
|
fill_test_pattern(); // Replace with your own framebuffer writer
|
|
video_init();
|
|
|
|
while (true) {
|
|
// Your application code goes here.
|
|
// Some other code can freely draw into framebuffer[].
|
|
tight_loop_contents();
|
|
}
|
|
} |