Add documentation.
This commit is contained in:
parent
c23bcc7f43
commit
ac3116a339
79
src/vga.c
79
src/vga.c
@ -22,12 +22,26 @@ typedef struct PIORun
|
|||||||
uint sm; //< executing SM
|
uint sm; //< executing SM
|
||||||
} PIORun;
|
} PIORun;
|
||||||
|
|
||||||
|
/*
|
||||||
|
There are 3 SM running: horizontal and vertical sync, and pixels.
|
||||||
|
See vga.pio for details.
|
||||||
|
*/
|
||||||
static PIORun vga_hsync, vga_vsync, vga_pixel;
|
static PIORun vga_hsync, vga_vsync, vga_pixel;
|
||||||
static bool dma_ready = false;
|
|
||||||
static int dma_channel;
|
|
||||||
|
|
||||||
static unsigned long int frame_counter = 0;
|
static bool dma_ready = false; //< current DMA acceleration status
|
||||||
|
static int dma_channel; //< DMA channel number, used to refill the vga_pixel FIFO
|
||||||
|
|
||||||
|
static unsigned long int frame_counter = 0; //< current frame
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Setup system clocks.
|
||||||
|
*
|
||||||
|
* Main system clock must run at 126MHz,
|
||||||
|
* which is 4 times pixel clock (31.5 MHz).
|
||||||
|
* This allows SM to output one pixel every 4 clock cycles,
|
||||||
|
* and to do some stuff in the mean time (eg. sending interrupts to sync with the other SM)
|
||||||
|
*
|
||||||
|
*/
|
||||||
static void setup_clocks(void)
|
static void setup_clocks(void)
|
||||||
{
|
{
|
||||||
// disable resuscitation clock
|
// disable resuscitation clock
|
||||||
@ -52,9 +66,16 @@ static void setup_clocks(void)
|
|||||||
clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 126 * MHZ, 126 * MHZ);
|
clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 126 * MHZ, 126 * MHZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the VGA pixel shift machine.
|
||||||
|
*
|
||||||
|
* @param pio PIO peripheral to use.
|
||||||
|
* @param sm SM to use.
|
||||||
|
* @param offset PIO memory offset for program.
|
||||||
|
*/
|
||||||
static void vga_pixel_program_init(PIO pio, uint sm, uint offset)
|
static void vga_pixel_program_init(PIO pio, uint sm, uint offset)
|
||||||
{
|
{
|
||||||
// config function automatically declared by SDK scripts
|
// config function is automatically declared by SDK scripts
|
||||||
// based on the names given to programs in .pio
|
// based on the names given to programs in .pio
|
||||||
pio_sm_config config = vga_pixel_program_get_default_config(offset);
|
pio_sm_config config = vga_pixel_program_get_default_config(offset);
|
||||||
|
|
||||||
@ -72,6 +93,13 @@ static void vga_pixel_program_init(PIO pio, uint sm, uint offset)
|
|||||||
pio_sm_set_enabled(pio, sm, true);
|
pio_sm_set_enabled(pio, sm, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the VGA horizontal sync machine.
|
||||||
|
*
|
||||||
|
* @param pio PIO peripheral to use.
|
||||||
|
* @param sm SM to use.
|
||||||
|
* @param offset PIO memory offset for program.
|
||||||
|
*/
|
||||||
static void vga_hsync_program_init(PIO pio, uint sm, uint offset)
|
static void vga_hsync_program_init(PIO pio, uint sm, uint offset)
|
||||||
{
|
{
|
||||||
pio_sm_config config = vga_hsync_program_get_default_config(offset);
|
pio_sm_config config = vga_hsync_program_get_default_config(offset);
|
||||||
@ -90,6 +118,13 @@ static void vga_hsync_program_init(PIO pio, uint sm, uint offset)
|
|||||||
pio_sm_set_enabled(pio, sm, true);
|
pio_sm_set_enabled(pio, sm, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the VGA vertical sync machine.
|
||||||
|
*
|
||||||
|
* @param pio PIO peripheral to use.
|
||||||
|
* @param sm SM to use.
|
||||||
|
* @param offset PIO memory offset for program.
|
||||||
|
*/
|
||||||
static void vga_vsync_program_init(PIO pio, uint sm, uint offset)
|
static void vga_vsync_program_init(PIO pio, uint sm, uint offset)
|
||||||
{
|
{
|
||||||
pio_sm_config config = vga_vsync_program_get_default_config(offset);
|
pio_sm_config config = vga_vsync_program_get_default_config(offset);
|
||||||
@ -108,13 +143,20 @@ static void vga_vsync_program_init(PIO pio, uint sm, uint offset)
|
|||||||
pio_sm_set_enabled(pio, sm, true);
|
pio_sm_set_enabled(pio, sm, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dma_handler(void)
|
/**
|
||||||
|
* @brief DMA interrupt handler.
|
||||||
|
*/
|
||||||
|
static void dma_handler(void)
|
||||||
{
|
{
|
||||||
// acknoweledge DMA
|
// acknoweledge DMA
|
||||||
dma_hw->ints0 = 1 << dma_channel;
|
dma_hw->ints0 = 1 << dma_channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void new_frame_handler(void)
|
/**
|
||||||
|
* @brief Vertical sync interrupt handler.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static void new_frame_handler(void)
|
||||||
{
|
{
|
||||||
if (dma_ready)
|
if (dma_ready)
|
||||||
{
|
{
|
||||||
@ -142,7 +184,6 @@ void new_frame_handler(void)
|
|||||||
void vga_machines_init(void)
|
void vga_machines_init(void)
|
||||||
{
|
{
|
||||||
// setup a proper system clock
|
// setup a proper system clock
|
||||||
// (126MHz = 4 x 31.5 MHz pixel clock)
|
|
||||||
setup_clocks();
|
setup_clocks();
|
||||||
|
|
||||||
// Running programs on PIO
|
// Running programs on PIO
|
||||||
@ -162,7 +203,10 @@ void vga_machines_init(void)
|
|||||||
vga_hsync.offset = pio_add_program(vga_hsync.pio, &vga_hsync_program);
|
vga_hsync.offset = pio_add_program(vga_hsync.pio, &vga_hsync_program);
|
||||||
vga_hsync.sm = pio_claim_unused_sm(vga_hsync.pio, true);
|
vga_hsync.sm = pio_claim_unused_sm(vga_hsync.pio, true);
|
||||||
vga_hsync_program_init(vga_hsync.pio, vga_hsync.sm, vga_hsync.offset);
|
vga_hsync_program_init(vga_hsync.pio, vga_hsync.sm, vga_hsync.offset);
|
||||||
// low pulse is X pixels long, and SM runs at 4x of pixel clock
|
|
||||||
|
// push configuration words to horizontal sync machine FIFO
|
||||||
|
// - hsync pulse duration (in pixel), assert time (low)
|
||||||
|
// - hsync pulse duration (in pixel), idle time (high), minus back porch
|
||||||
pio_sm_put_blocking(vga_hsync.pio, vga_hsync.sm, (64 - 2));
|
pio_sm_put_blocking(vga_hsync.pio, vga_hsync.sm, (64 - 2));
|
||||||
pio_sm_put_blocking(vga_hsync.pio, vga_hsync.sm, (640 + 16 - 1)); // 16 is front porch
|
pio_sm_put_blocking(vga_hsync.pio, vga_hsync.sm, (640 + 16 - 1)); // 16 is front porch
|
||||||
printf("OK\n");
|
printf("OK\n");
|
||||||
@ -174,7 +218,9 @@ void vga_machines_init(void)
|
|||||||
vga_vsync.offset = pio_add_program(vga_vsync.pio, &vga_vsync_program);
|
vga_vsync.offset = pio_add_program(vga_vsync.pio, &vga_vsync_program);
|
||||||
vga_vsync.sm = pio_claim_unused_sm(vga_vsync.pio, true);
|
vga_vsync.sm = pio_claim_unused_sm(vga_vsync.pio, true);
|
||||||
vga_vsync_program_init(vga_vsync.pio, vga_vsync.sm, vga_vsync.offset);
|
vga_vsync_program_init(vga_vsync.pio, vga_vsync.sm, vga_vsync.offset);
|
||||||
// low pulse assert lasts for 3 horizontal lines, then adjust by one
|
// push configuration words to vertical sync machine FIFO
|
||||||
|
// - vsync pulse duration (in lines), assert time (low)
|
||||||
|
// - vsync pulse duration (in lines), idle time (high)
|
||||||
pio_sm_put_blocking(vga_vsync.pio, vga_vsync.sm, 3 - 1);
|
pio_sm_put_blocking(vga_vsync.pio, vga_vsync.sm, 3 - 1);
|
||||||
pio_sm_put_blocking(vga_vsync.pio, vga_vsync.sm, 500 - 3 - 1);
|
pio_sm_put_blocking(vga_vsync.pio, vga_vsync.sm, 500 - 3 - 1);
|
||||||
printf("OK\n");
|
printf("OK\n");
|
||||||
@ -186,6 +232,8 @@ void vga_machines_init(void)
|
|||||||
vga_pixel.offset = pio_add_program(vga_pixel.pio, &vga_pixel_program);
|
vga_pixel.offset = pio_add_program(vga_pixel.pio, &vga_pixel_program);
|
||||||
vga_pixel.sm = pio_claim_unused_sm(vga_pixel.pio, false);
|
vga_pixel.sm = pio_claim_unused_sm(vga_pixel.pio, false);
|
||||||
vga_pixel_program_init(vga_pixel.pio, vga_pixel.sm, vga_pixel.offset);
|
vga_pixel_program_init(vga_pixel.pio, vga_pixel.sm, vga_pixel.offset);
|
||||||
|
// push configuration word to pixel machine FIFO
|
||||||
|
// - number of visible pixels in a line
|
||||||
pio_sm_put_blocking(vga_pixel.pio, vga_pixel.sm, 640 - 1);
|
pio_sm_put_blocking(vga_pixel.pio, vga_pixel.sm, 640 - 1);
|
||||||
printf("OK\n");
|
printf("OK\n");
|
||||||
|
|
||||||
@ -195,16 +243,18 @@ void vga_machines_init(void)
|
|||||||
channel_config_set_transfer_data_size(&dma_config, DMA_SIZE_32);
|
channel_config_set_transfer_data_size(&dma_config, DMA_SIZE_32);
|
||||||
channel_config_set_read_increment(&dma_config, true);
|
channel_config_set_read_increment(&dma_config, true);
|
||||||
channel_config_set_write_increment(&dma_config, false);
|
channel_config_set_write_increment(&dma_config, false);
|
||||||
|
// link Data Request signal of PIO SM2 TX FIFO to DMA
|
||||||
channel_config_set_dreq(&dma_config, DREQ_PIO0_TX2);
|
channel_config_set_dreq(&dma_config, DREQ_PIO0_TX2);
|
||||||
|
|
||||||
|
// setup transfer acknowledge interrupt
|
||||||
dma_channel_set_irq0_enabled(dma_channel, true);
|
dma_channel_set_irq0_enabled(dma_channel, true);
|
||||||
|
|
||||||
irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
|
irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
|
||||||
irq_set_enabled(DMA_IRQ_0, true);
|
irq_set_enabled(DMA_IRQ_0, true);
|
||||||
|
|
||||||
// -1 because first word is manually injected
|
// pull one word less, because first word is manually injected
|
||||||
// before the first DMA request, in order to
|
// before the first DMA request, in order to avoid any possible delay
|
||||||
// avoid any possible delay
|
// (see new frame interrupt handler)
|
||||||
dma_channel_configure(dma_channel, &dma_config, &pio0_hw->txf[2], &frame[0],
|
dma_channel_configure(dma_channel, &dma_config, &pio0_hw->txf[2], &frame[0],
|
||||||
HPIXEL * (VSPULSE + VPIXEL + VBBLANK) / 32 - 1, true);
|
HPIXEL * (VSPULSE + VPIXEL + VBBLANK) / 32 - 1, true);
|
||||||
|
|
||||||
@ -214,6 +264,11 @@ void vga_machines_init(void)
|
|||||||
printf("Now running.\n");
|
printf("Now running.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current frame counter.
|
||||||
|
*
|
||||||
|
* @return Current frame counter.
|
||||||
|
*/
|
||||||
unsigned long int vga_get_frame_counter(void)
|
unsigned long int vga_get_frame_counter(void)
|
||||||
{
|
{
|
||||||
return frame_counter;
|
return frame_counter;
|
||||||
|
82
src/vga.pio
82
src/vga.pio
@ -1,60 +1,116 @@
|
|||||||
|
/*
|
||||||
|
- horizontal sync pulse generation
|
||||||
|
this machine commands the timing for the other machines too
|
||||||
|
- vertical sync pulse generation
|
||||||
|
counts the horizontal sync pulses and generated a vertical sync pulse accordingly
|
||||||
|
- pixel machine
|
||||||
|
horizontal sync machine also sends an interrupt when the back porch is finished,
|
||||||
|
thus triggering the pixel machine to start shifting out the pixels
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Pixel Machine
|
||||||
|
Wait for the "start-of-visible-line" interrupt signal,
|
||||||
|
and output 640 pixels
|
||||||
|
*/
|
||||||
.program vga_pixel
|
.program vga_pixel
|
||||||
|
|
||||||
entrypoint_vga_pixel:
|
entrypoint_vga_pixel:
|
||||||
|
// pull configuration into Y register
|
||||||
|
// Y = number of visible pixels in the line
|
||||||
pull
|
pull
|
||||||
mov y, osr
|
mov y, osr
|
||||||
|
|
||||||
.wrap_target
|
.wrap_target
|
||||||
mov x, y
|
mov x, y
|
||||||
wait irq 7
|
wait irq 7
|
||||||
|
|
||||||
|
// loop lasts 4 SM clocks, which corresponds to 1 VGA pixel
|
||||||
loop:
|
loop:
|
||||||
out pins, 1 [2]
|
out pins, 1 [2]
|
||||||
jmp x-- loop
|
jmp x-- loop
|
||||||
.wrap
|
.wrap
|
||||||
|
|
||||||
|
/*
|
||||||
|
Horizontal Sync Machine
|
||||||
|
A line is composed by 840 pixels:
|
||||||
|
- 64 sync pulse
|
||||||
|
- 120 back porch
|
||||||
|
- 640 visible area
|
||||||
|
- 16 front porch
|
||||||
|
*/
|
||||||
.program vga_hsync
|
.program vga_hsync
|
||||||
|
|
||||||
entrypoint_vga_hsync:
|
entrypoint_vga_hsync:
|
||||||
|
// pull configuration into ISR and OSR registers
|
||||||
|
// ISR = sync pulse duration - 2 (64 pixel)
|
||||||
|
// OSR = visible line + front porch - 1 (640 + 16 pixel)
|
||||||
|
// Note: configuration contains desired value -1/-2,
|
||||||
|
// because of how end-of-loop check is performed
|
||||||
|
// and how time is spent in overhead instructions
|
||||||
pull
|
pull
|
||||||
mov isr, osr
|
mov isr, osr
|
||||||
pull
|
pull
|
||||||
|
|
||||||
|
// once configured, no more data is pulled from the TX FIFO,
|
||||||
|
// so program wraps around here
|
||||||
.wrap_target
|
.wrap_target
|
||||||
set pins, 0 [1]
|
set pins, 0 [1] // start pulse low
|
||||||
irq set 0
|
irq set 0 // trigger "start-of-line" interrupt signal
|
||||||
mov x, isr
|
mov x, isr // load register X with sync pulse duration
|
||||||
|
// +4 SM clocks elapsed (first VGA pixel)
|
||||||
|
|
||||||
|
// loop for the duration of the low pulse (remaining 63 pixels)
|
||||||
hsync_pulse:
|
hsync_pulse:
|
||||||
jmp x-- hsync_pulse [3]
|
jmp x-- hsync_pulse [3]
|
||||||
|
|
||||||
set pins, 1 [1]
|
set pins, 1 [1] // start pulse high (.5 pixel)
|
||||||
set y, 27
|
// hardcoded backporch duration: 120 pixel => 120 x 4 = 480 SM clocks
|
||||||
|
// but one pixel is already in the surronding logic, then only 476 clocks are cycled
|
||||||
|
// where 476 = (27 + 1) * (16 + 1)
|
||||||
|
set y, 27 // another .25 pixel
|
||||||
hsync_back_porch:
|
hsync_back_porch:
|
||||||
jmp y-- hsync_back_porch [16]
|
jmp y-- hsync_back_porch [16]
|
||||||
irq set 7
|
irq set 7 // trigger "start-of-visible-line" interrupt signal
|
||||||
|
// (.25 more pixel)
|
||||||
|
|
||||||
mov x, osr [3]
|
mov x, osr [3] // load register X with remaining line duration (visible line + front porch)
|
||||||
|
// also, just make this last 4 SM clocks (1 pixel)
|
||||||
hsync_idle:
|
hsync_idle:
|
||||||
jmp x-- hsync_idle [3]
|
jmp x-- hsync_idle [3]
|
||||||
.wrap
|
.wrap
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Vertical Sync Machine
|
||||||
|
A frame is composed by 500 lines:
|
||||||
|
- 3 vertical sync pulse
|
||||||
|
- 16 back porch
|
||||||
|
- 480 visible lines
|
||||||
|
- 1 front porch
|
||||||
|
*/
|
||||||
.program vga_vsync
|
.program vga_vsync
|
||||||
|
|
||||||
entrypoint_vga_vsync:
|
entrypoint_vga_vsync:
|
||||||
|
// Pull configuration into ISR and OSR registers
|
||||||
|
// ISR = sync pulse assert duration - 1 (3 lines)
|
||||||
|
// OSR = sync pulse idle duration - 1 (497 lines)
|
||||||
pull
|
pull
|
||||||
mov isr, osr
|
mov isr, osr
|
||||||
pull
|
pull
|
||||||
|
|
||||||
|
// once configured, no more data is pulled from the TX FIFO,
|
||||||
|
// so program wraps around here
|
||||||
.wrap_target
|
.wrap_target
|
||||||
mov x, isr
|
mov x, isr // load register X with low pulse duration
|
||||||
vsync_pulse:
|
vsync_pulse:
|
||||||
wait irq 0
|
wait irq 0 // wait for "start-of-line" interrupt from hsync machine
|
||||||
set pins, 0
|
set pins, 0 // start pulse low
|
||||||
jmp x-- vsync_pulse
|
jmp x-- vsync_pulse
|
||||||
|
|
||||||
|
// trigger "start-of-frame" interrupt
|
||||||
|
// (actually, 3 lines of the back porch are already passed)
|
||||||
irq set 1
|
irq set 1
|
||||||
|
|
||||||
mov x, osr
|
mov x, osr // load register X with high pulse duration
|
||||||
vsync_idle:
|
vsync_idle:
|
||||||
wait irq 0
|
wait irq 0
|
||||||
set pins, 1
|
set pins, 1
|
||||||
|
Loading…
Reference in New Issue
Block a user