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
|
||||
} 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 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)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
// config function automatically declared by SDK scripts
|
||||
// config function is automatically declared by SDK scripts
|
||||
// based on the names given to programs in .pio
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void dma_handler(void)
|
||||
/**
|
||||
* @brief DMA interrupt handler.
|
||||
*/
|
||||
static void dma_handler(void)
|
||||
{
|
||||
// acknoweledge DMA
|
||||
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)
|
||||
{
|
||||
@ -142,7 +184,6 @@ void new_frame_handler(void)
|
||||
void vga_machines_init(void)
|
||||
{
|
||||
// setup a proper system clock
|
||||
// (126MHz = 4 x 31.5 MHz pixel clock)
|
||||
setup_clocks();
|
||||
|
||||
// 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.sm = pio_claim_unused_sm(vga_hsync.pio, true);
|
||||
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, (640 + 16 - 1)); // 16 is front porch
|
||||
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.sm = pio_claim_unused_sm(vga_vsync.pio, true);
|
||||
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, 500 - 3 - 1);
|
||||
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.sm = pio_claim_unused_sm(vga_pixel.pio, false);
|
||||
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);
|
||||
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_read_increment(&dma_config, true);
|
||||
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);
|
||||
|
||||
// setup transfer acknowledge interrupt
|
||||
dma_channel_set_irq0_enabled(dma_channel, true);
|
||||
|
||||
irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
|
||||
irq_set_enabled(DMA_IRQ_0, true);
|
||||
|
||||
// -1 because first word is manually injected
|
||||
// before the first DMA request, in order to
|
||||
// avoid any possible delay
|
||||
// pull one word less, because first word is manually injected
|
||||
// before the first DMA request, in order to avoid any possible delay
|
||||
// (see new frame interrupt handler)
|
||||
dma_channel_configure(dma_channel, &dma_config, &pio0_hw->txf[2], &frame[0],
|
||||
HPIXEL * (VSPULSE + VPIXEL + VBBLANK) / 32 - 1, true);
|
||||
|
||||
@ -214,6 +264,11 @@ void vga_machines_init(void)
|
||||
printf("Now running.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get current frame counter.
|
||||
*
|
||||
* @return Current frame counter.
|
||||
*/
|
||||
unsigned long int vga_get_frame_counter(void)
|
||||
{
|
||||
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
|
||||
|
||||
entrypoint_vga_pixel:
|
||||
// pull configuration into Y register
|
||||
// Y = number of visible pixels in the line
|
||||
pull
|
||||
mov y, osr
|
||||
|
||||
.wrap_target
|
||||
mov x, y
|
||||
wait irq 7
|
||||
|
||||
// loop lasts 4 SM clocks, which corresponds to 1 VGA pixel
|
||||
loop:
|
||||
out pins, 1 [2]
|
||||
jmp x-- loop
|
||||
.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
|
||||
|
||||
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
|
||||
mov isr, osr
|
||||
pull
|
||||
|
||||
// once configured, no more data is pulled from the TX FIFO,
|
||||
// so program wraps around here
|
||||
.wrap_target
|
||||
set pins, 0 [1]
|
||||
irq set 0
|
||||
mov x, isr
|
||||
set pins, 0 [1] // start pulse low
|
||||
irq set 0 // trigger "start-of-line" interrupt signal
|
||||
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:
|
||||
jmp x-- hsync_pulse [3]
|
||||
|
||||
set pins, 1 [1]
|
||||
set y, 27
|
||||
set pins, 1 [1] // start pulse high (.5 pixel)
|
||||
// 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:
|
||||
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:
|
||||
jmp x-- hsync_idle [3]
|
||||
.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
|
||||
|
||||
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
|
||||
mov isr, osr
|
||||
pull
|
||||
|
||||
// once configured, no more data is pulled from the TX FIFO,
|
||||
// so program wraps around here
|
||||
.wrap_target
|
||||
mov x, isr
|
||||
mov x, isr // load register X with low pulse duration
|
||||
vsync_pulse:
|
||||
wait irq 0
|
||||
set pins, 0
|
||||
wait irq 0 // wait for "start-of-line" interrupt from hsync machine
|
||||
set pins, 0 // start pulse low
|
||||
jmp x-- vsync_pulse
|
||||
|
||||
// trigger "start-of-frame" interrupt
|
||||
// (actually, 3 lines of the back porch are already passed)
|
||||
irq set 1
|
||||
|
||||
mov x, osr
|
||||
mov x, osr // load register X with high pulse duration
|
||||
vsync_idle:
|
||||
wait irq 0
|
||||
set pins, 1
|
||||
|
Loading…
Reference in New Issue
Block a user