NTSC output
This commit is contained in:
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@@ -22,9 +22,6 @@
|
|||||||
"adapter speed 5000"
|
"adapter speed 5000"
|
||||||
],
|
],
|
||||||
"svdFile": "${env:PICO_SDK_PATH}/src/rp2350/hardware_regs/rp2350.svd",
|
"svdFile": "${env:PICO_SDK_PATH}/src/rp2350/hardware_regs/rp2350.svd",
|
||||||
"showDevDebugOutput": "raw",
|
|
||||||
"runToEntryPoint": "main",
|
|
||||||
// Give restart the same functionality as runToMain
|
|
||||||
"postRestartCommands": [
|
"postRestartCommands": [
|
||||||
"break main",
|
"break main",
|
||||||
"continue"
|
"continue"
|
||||||
|
|||||||
95
main.c
95
main.c
@@ -20,16 +20,20 @@
|
|||||||
#define DATA_PIN 2 // pixel / luma bit pin
|
#define DATA_PIN 2 // pixel / luma bit pin
|
||||||
#define SYNC_PIN 3 // sync pin
|
#define SYNC_PIN 3 // sync pin
|
||||||
|
|
||||||
|
// Set to 1 to bypass DMA/framebuffer and generate fixed black/white bars.
|
||||||
|
#define VIDEO_DIAGNOSTIC_BARS 0
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// Video format
|
// Video format
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
|
// NTSC-compatible 240p timing using the same 64us line structure as the
|
||||||
|
// original PAL code, but with 262 lines/frame at about 59.6Hz.
|
||||||
#define VIDEO_WIDTH 768
|
#define VIDEO_WIDTH 768
|
||||||
#define VIDEO_HEIGHT 576
|
#define VIDEO_HEIGHT 240
|
||||||
#define WORDS_PER_LINE (VIDEO_WIDTH / 32)
|
#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 SYNC_INTERVAL_S 0.000002f // 2 us per sync-SM instruction
|
||||||
#define ACTIVE_VIDEO_S 0.000052f // 52 us active video per line
|
#define ACTIVE_VIDEO_S 0.000050f // 52 us active video per line
|
||||||
|
|
||||||
// Match the .pio program's published constant.
|
// Match the .pio program's published constant.
|
||||||
// pioasm will emit CLOCKS_PER_BIT in the generated header.
|
// pioasm will emit CLOCKS_PER_BIT in the generated header.
|
||||||
@@ -43,11 +47,8 @@
|
|||||||
// ---------------------------
|
// ---------------------------
|
||||||
static uint32_t framebuffer[VIDEO_HEIGHT][WORDS_PER_LINE];
|
static uint32_t framebuffer[VIDEO_HEIGHT][WORDS_PER_LINE];
|
||||||
|
|
||||||
// Field/line tracking for line-based DMA.
|
// Current active scanline within the progressive frame.
|
||||||
// This is intentionally kept in software to match the sync SM's 288 active
|
static volatile uint line_in_frame = 0;
|
||||||
// lines per field.
|
|
||||||
static volatile bool even_field = true;
|
|
||||||
static volatile uint line_in_field = 0;
|
|
||||||
|
|
||||||
// DMA channel that feeds the PIO TX FIFO
|
// DMA channel that feeds the PIO TX FIFO
|
||||||
static int video_dma_chan;
|
static int video_dma_chan;
|
||||||
@@ -65,7 +66,7 @@ static void fill_test_pattern(void) {
|
|||||||
|
|
||||||
// Simple visible pattern:
|
// Simple visible pattern:
|
||||||
// border + checker
|
// border + checker
|
||||||
if (x < 8 || x >= VIDEO_WIDTH - 8 || y < 8 || y >= VIDEO_HEIGHT - 8) {
|
if (x < 20 || x >= VIDEO_WIDTH - 20 || y < 20 || y >= VIDEO_HEIGHT - 20) {
|
||||||
on = true;
|
on = true;
|
||||||
} else if (((x >> 4) ^ (y >> 4)) & 1) {
|
} else if (((x >> 4) ^ (y >> 4)) & 1) {
|
||||||
on = true;
|
on = true;
|
||||||
@@ -106,6 +107,18 @@ static void init_cvdata_program(PIO pio, uint sm, uint offset, float clkdiv, uin
|
|||||||
pio_sm_exec(pio, sm, pio_encode_out(pio_null, 32));
|
pio_sm_exec(pio, sm, pio_encode_out(pio_null, 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void init_cvdata_diag_program(PIO pio, uint sm, uint offset, float clkdiv, uint data_pin) {
|
||||||
|
pio_sm_config c = cvdata_diag_program_get_default_config(offset);
|
||||||
|
|
||||||
|
sm_config_set_set_pins(&c, data_pin, 1);
|
||||||
|
sm_config_set_clkdiv(&c, clkdiv);
|
||||||
|
|
||||||
|
pio_gpio_init(pio, data_pin);
|
||||||
|
pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true);
|
||||||
|
|
||||||
|
pio_sm_init(pio, sm, offset, &c);
|
||||||
|
}
|
||||||
|
|
||||||
static void init_cvsync_program(PIO pio, uint sm, uint offset, float clkdiv, uint sync_pin) {
|
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);
|
pio_sm_config c = cvsync_program_get_default_config(offset);
|
||||||
|
|
||||||
@@ -117,12 +130,9 @@ static void init_cvsync_program(PIO pio, uint sm, uint offset, float clkdiv, uin
|
|||||||
|
|
||||||
pio_sm_init(pio, sm, offset, &c);
|
pio_sm_init(pio, sm, offset, &c);
|
||||||
|
|
||||||
// Preload OSR = lines_per_field - 1
|
// Preload OSR = active_lines_per_frame - 1
|
||||||
pio_sm_put_blocking(pio, sm, LINES_PER_FIELD - 1);
|
pio_sm_put_blocking(pio, sm, VIDEO_HEIGHT - 1);
|
||||||
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
|
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@@ -153,15 +163,12 @@ static void init_video_dma(PIO pio, uint data_sm) {
|
|||||||
// Start DMA for one scanline
|
// Start DMA for one scanline
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
static inline void start_dma_for_current_line(void) {
|
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[line_in_frame], false);
|
||||||
|
|
||||||
dma_channel_set_read_addr(video_dma_chan, framebuffer[src_line], false);
|
|
||||||
dma_channel_set_trans_count(video_dma_chan, WORDS_PER_LINE, true);
|
dma_channel_set_trans_count(video_dma_chan, WORDS_PER_LINE, true);
|
||||||
|
|
||||||
line_in_field++;
|
line_in_frame++;
|
||||||
if (line_in_field >= LINES_PER_FIELD) {
|
if (line_in_frame >= VIDEO_HEIGHT) {
|
||||||
line_in_field = 0;
|
line_in_frame = 0;
|
||||||
even_field = !even_field;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,11 +178,11 @@ static inline void start_dma_for_current_line(void) {
|
|||||||
// We clear the PIO IRQ and arm one DMA transfer for the next active line.
|
// We clear the PIO IRQ and arm one DMA transfer for the next active line.
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
static void pio0_irq0_handler(void) {
|
static void pio0_irq0_handler(void) {
|
||||||
if (pio_interrupt_get(VIDEO_PIO, 0)) {
|
if (pio_interrupt_get(VIDEO_PIO, 1)) {
|
||||||
pio_interrupt_clear(VIDEO_PIO, 0);
|
pio_interrupt_clear(VIDEO_PIO, 1);
|
||||||
|
|
||||||
// Safety: a new line should only arrive after the previous line DMA is done.
|
// A new line request arrives during hsync/back porch so the DMA can
|
||||||
// If it isn't, the clocks are wrong or something stalled badly.
|
// prime the data SM before visible pixels begin.
|
||||||
if (!dma_channel_is_busy(video_dma_chan)) {
|
if (!dma_channel_is_busy(video_dma_chan)) {
|
||||||
start_dma_for_current_line();
|
start_dma_for_current_line();
|
||||||
}
|
}
|
||||||
@@ -191,27 +198,41 @@ static void video_init(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint sync_offset = pio_add_program(VIDEO_PIO, &cvsync_program);
|
uint sync_offset = pio_add_program(VIDEO_PIO, &cvsync_program);
|
||||||
uint data_offset = pio_add_program(VIDEO_PIO, &cvdata_program);
|
uint data_offset;
|
||||||
|
|
||||||
// Same timing idea as the original repo:
|
// sync SM: 1 instruction every 0.5 us
|
||||||
// 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 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;
|
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_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
|
#if VIDEO_DIAGNOSTIC_BARS
|
||||||
pio_set_irq0_source_enabled(VIDEO_PIO, pis_interrupt0, true);
|
// 0.5 us per instruction gives a visible bar pattern over the 52 us active period.
|
||||||
|
float diag_clkdiv = sys_hz * 0.0000005f;
|
||||||
|
data_offset = pio_add_program(VIDEO_PIO, &cvdata_diag_program);
|
||||||
|
init_cvdata_diag_program(VIDEO_PIO, DATA_SM, data_offset, diag_clkdiv, DATA_PIN);
|
||||||
|
#else
|
||||||
|
// data SM: enough PIO cycles to emit VIDEO_WIDTH bits in 52 us
|
||||||
|
float data_clkdiv = (sys_hz / ((float)VIDEO_WIDTH / ACTIVE_VIDEO_S)) / (float)CLOCKS_PER_BIT;
|
||||||
|
data_offset = pio_add_program(VIDEO_PIO, &cvdata_program);
|
||||||
|
init_cvdata_program(VIDEO_PIO, DATA_SM, data_offset, data_clkdiv, DATA_PIN);
|
||||||
|
init_video_dma(VIDEO_PIO, DATA_SM);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Route PIO internal IRQ 1 to CPU IRQ 0. IRQ 0 stays reserved for the
|
||||||
|
// data SM's wait instruction inside the PIO block.
|
||||||
|
pio_interrupt_clear(VIDEO_PIO, 0);
|
||||||
|
pio_interrupt_clear(VIDEO_PIO, 1);
|
||||||
|
pio_set_irq0_source_enabled(VIDEO_PIO, pis_interrupt1, true);
|
||||||
irq_set_exclusive_handler(PIO0_IRQ_0, pio0_irq0_handler);
|
irq_set_exclusive_handler(PIO0_IRQ_0, pio0_irq0_handler);
|
||||||
irq_set_enabled(PIO0_IRQ_0, true);
|
irq_set_enabled(PIO0_IRQ_0, true);
|
||||||
|
|
||||||
// Start with the first line of the even field
|
// Start with the first active line of the frame.
|
||||||
even_field = true;
|
line_in_frame = 0;
|
||||||
line_in_field = 0;
|
|
||||||
|
#if !VIDEO_DIAGNOSTIC_BARS
|
||||||
|
start_dma_for_current_line();
|
||||||
|
#endif
|
||||||
|
|
||||||
pio_sm_set_enabled(VIDEO_PIO, DATA_SM, true);
|
pio_sm_set_enabled(VIDEO_PIO, DATA_SM, true);
|
||||||
pio_sm_set_enabled(VIDEO_PIO, SYNC_SM, true);
|
pio_sm_set_enabled(VIDEO_PIO, SYNC_SM, true);
|
||||||
|
|||||||
111
video_dma.pio
111
video_dma.pio
@@ -4,78 +4,53 @@
|
|||||||
; - SM1: sync timing + line start IRQ
|
; - SM1: sync timing + line start IRQ
|
||||||
; - SM0: pixel output during active video
|
; - SM0: pixel output during active video
|
||||||
;
|
;
|
||||||
; Based on the same timing structure as alanpreed/pico-composite-video:
|
; NTSC-compatible progressive timing using the same coarse 2us timing grid as
|
||||||
; 4us hsync, 6us back porch, 52us active video, 2us front porch,
|
; the original PAL implementation:
|
||||||
; 288 active lines per field, interlaced fields.
|
; - 64us line period
|
||||||
|
; - 4us hsync
|
||||||
|
; - 6us back porch
|
||||||
|
; - 52us active video + front porch tail
|
||||||
|
; - 240 active lines, 262 total lines per frame (~59.64Hz)
|
||||||
|
|
||||||
.define DATA_DELAY 4
|
.define DATA_DELAY 4
|
||||||
.define LINE_IRQ 0
|
.define LINE_IRQ 0
|
||||||
|
.define DMA_IRQ 1
|
||||||
.define PUBLIC CLOCKS_PER_BIT DATA_DELAY + 2
|
.define PUBLIC CLOCKS_PER_BIT DATA_DELAY + 2
|
||||||
|
|
||||||
.program cvsync
|
.program cvsync
|
||||||
.side_set 1
|
.side_set 1
|
||||||
|
|
||||||
; OSR = lines_per_field - 1
|
; OSR = active_lines_per_frame - 1
|
||||||
; Y = field flag: 0 = even field, !0 = odd field
|
|
||||||
|
|
||||||
.wrap_target
|
.wrap_target
|
||||||
vsync_start:
|
frame_start:
|
||||||
; First set of short pulses:
|
; 3 lines of broad vertical sync.
|
||||||
; even field: 6
|
|
||||||
; odd field : 5
|
|
||||||
jmp !y set_even_counter side 0
|
|
||||||
set x, 3 side 1
|
|
||||||
jmp vsync_short_pulse side 1 [13]
|
|
||||||
|
|
||||||
set_even_counter:
|
|
||||||
set x, 4 side 1 [14]
|
|
||||||
|
|
||||||
vsync_short_pulse:
|
|
||||||
nop side 0
|
|
||||||
jmp x-- vsync_short_pulse [14] side 1
|
|
||||||
|
|
||||||
; Long sync pulses: always 5
|
|
||||||
set x, 4 side 0 [13]
|
|
||||||
|
|
||||||
vsync_long_start:
|
|
||||||
jmp !x vsync_long_end [1] side 1
|
|
||||||
jmp x-- vsync_long_start [13] side 0
|
|
||||||
vsync_long_end:
|
|
||||||
|
|
||||||
; Second set of short pulses:
|
|
||||||
; even field: 5
|
|
||||||
; odd field : 4
|
|
||||||
jmp !y set_even_counter_2 side 0
|
|
||||||
set x, 2 side 1
|
set x, 2 side 1
|
||||||
jmp flip_field_flag side 1 [12]
|
|
||||||
|
|
||||||
set_even_counter_2:
|
vsync_lines:
|
||||||
set x, 3 side 1 [13]
|
nop side 0 [14]
|
||||||
|
|
||||||
flip_field_flag:
|
|
||||||
mov y, !y side 1
|
|
||||||
|
|
||||||
vsync_short_pulse_2:
|
|
||||||
nop side 0
|
nop side 0
|
||||||
jmp x-- vsync_short_pulse_2 [14] side 1
|
|
||||||
|
|
||||||
; 17 blank lines
|
|
||||||
set x, 16 side 0 [1]
|
|
||||||
|
|
||||||
hsync_blank_start:
|
|
||||||
nop side 1 [14]
|
nop side 1 [14]
|
||||||
jmp !x hsync_blank_end side 1 [14]
|
jmp x-- vsync_lines side 1
|
||||||
jmp x-- hsync_blank_start side 0 [1]
|
|
||||||
hsync_blank_end:
|
|
||||||
|
|
||||||
; 288 active video lines
|
; 19 blank lines before active video.
|
||||||
mov x, osr side 0 [1]
|
set x, 18 side 1
|
||||||
|
|
||||||
hsync_video:
|
blank_lines:
|
||||||
|
irq set DMA_IRQ side 0 [1]
|
||||||
|
nop side 1 [14]
|
||||||
|
jmp x-- blank_lines side 1 [14]
|
||||||
|
|
||||||
|
; 240 active video lines.
|
||||||
|
mov x, osr side 1
|
||||||
|
|
||||||
|
active_lines:
|
||||||
|
irq set DMA_IRQ side 0 [1] ; 4us hsync, ask CPU to arm DMA
|
||||||
nop side 1 [2] ; 6us back porch
|
nop side 1 [2] ; 6us back porch
|
||||||
irq set LINE_IRQ side 1 [13] ; 52us active video
|
irq set LINE_IRQ side 1 [13] ; 28us visible video
|
||||||
jmp !x vsync_start side 1 [12] ; 2us front porch (+ tail of active)
|
jmp x-- active_lines side 1 [12]
|
||||||
jmp x-- hsync_video side 0 [1] ; 4us hsync pulse
|
|
||||||
|
jmp frame_start side 1 [12]
|
||||||
.wrap
|
.wrap
|
||||||
|
|
||||||
|
|
||||||
@@ -92,8 +67,34 @@ hsync_video:
|
|||||||
set pins, 0
|
set pins, 0
|
||||||
mov y, x
|
mov y, x
|
||||||
wait 1 irq LINE_IRQ
|
wait 1 irq LINE_IRQ
|
||||||
|
irq clear LINE_IRQ
|
||||||
|
|
||||||
data_out:
|
data_out:
|
||||||
out pins, 1 [DATA_DELAY]
|
out pins, 1 [DATA_DELAY]
|
||||||
jmp y-- data_out
|
jmp y-- data_out
|
||||||
.wrap
|
.wrap
|
||||||
|
|
||||||
|
|
||||||
|
.program cvdata_diag
|
||||||
|
|
||||||
|
; Diagnostic program:
|
||||||
|
; waits for each active line, then emits wide black/white bars without DMA.
|
||||||
|
; This isolates the analog output path from the framebuffer/DMA path.
|
||||||
|
|
||||||
|
.wrap_target
|
||||||
|
wait 1 irq LINE_IRQ
|
||||||
|
irq clear LINE_IRQ
|
||||||
|
|
||||||
|
set x, 3
|
||||||
|
|
||||||
|
diag_bars:
|
||||||
|
set pins, 1 [15]
|
||||||
|
nop [15]
|
||||||
|
nop [15]
|
||||||
|
set pins, 0 [15]
|
||||||
|
nop [15]
|
||||||
|
nop [15]
|
||||||
|
jmp x-- diag_bars [15]
|
||||||
|
|
||||||
|
set pins, 0
|
||||||
|
.wrap
|
||||||
|
|||||||
Reference in New Issue
Block a user