Compare commits
9 Commits
2baddfc76e
...
ac3116a339
Author | SHA1 | Date |
---|---|---|
giomba | ac3116a339 | |
giomba | c23bcc7f43 | |
giomba | 5f11fa209f | |
giomba | 36cc3e1c0a | |
giomba | ac055b6673 | |
giomba | 74e5da5405 | |
giomba | fa0734fec0 | |
giomba | 8af09d4a6e | |
giomba | 0297598438 |
|
@ -18,6 +18,9 @@ add_compile_options(-Wall
|
|||
|
||||
add_executable(ceda2vga
|
||||
src/main.c
|
||||
src/draw_demo.c
|
||||
src/framebuffer.c
|
||||
src/vga.c
|
||||
)
|
||||
|
||||
pico_generate_pio_header(ceda2vga
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
#include "draw_demo.h"
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "pico/platform.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static bool valid_coord(uint16_t x, uint16_t y)
|
||||
{
|
||||
if (x < 0 || x >= HPIXEL)
|
||||
return false;
|
||||
if (y < 0 || y >= VPIXEL)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// just because i did not need all the letters
|
||||
// and didn't want to fill a proper array :-)
|
||||
const Letter letters[] = {
|
||||
{'A', 6, (Point[]){{0, 7}, {0, 0}, {7, 0}, {7, 7}, {7, 3}, {0, 3}}},
|
||||
{'G', 6, (Point[]){{7, 0}, {0, 0}, {0, 7}, {7, 7}, {7, 3}, {3, 3}}},
|
||||
{'L', 3, (Point[]){{0, 0}, {0, 7}, {7, 7}}},
|
||||
{'M', 5, (Point[]){{0, 7}, {0, 0}, {3, 3}, {7, 0}, {7, 7}}},
|
||||
{'O', 5, (Point[]){{0, 0}, {0, 7}, {7, 7}, {7, 0}, {0, 0}}},
|
||||
{'P', 5, (Point[]){{0, 7}, {0, 0}, {7, 0}, {7, 3}, {0, 3}}},
|
||||
{'R', 6, (Point[]){{0, 7}, {0, 0}, {7, 0}, {7, 3}, {0, 3}, {7, 7}}},
|
||||
{'S', 6, (Point[]){{0, 7}, {7, 7}, {7, 3}, {0, 3}, {0, 0}, {7, 0}}},
|
||||
};
|
||||
|
||||
void draw_path(uint16_t x, uint16_t y, Point points[], size_t len)
|
||||
{
|
||||
Point *old_point = &points[0];
|
||||
|
||||
for (int i = 1; i < len; ++i)
|
||||
{
|
||||
Point *current_point = &points[i];
|
||||
draw_line(x + old_point->x, y + old_point->y, x + current_point->x, y + current_point->y,
|
||||
true);
|
||||
old_point = current_point;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_letter(uint16_t x, uint16_t y, char c)
|
||||
{
|
||||
for (int i = 0; i < count_of(letters); ++i)
|
||||
{
|
||||
if (letters[i].letter == c)
|
||||
{
|
||||
draw_path(x, y, letters[i].points, letters[i].len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw_string(uint16_t x, uint16_t y, const char *s)
|
||||
{
|
||||
while (*s != 0)
|
||||
{
|
||||
draw_letter(x, y, *s);
|
||||
s++;
|
||||
x += 9;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_demo(void)
|
||||
{
|
||||
// draw a little demo
|
||||
|
||||
// clear frame buffer
|
||||
memset(&frame[0], 0, sizeof(frame));
|
||||
|
||||
// horizontal lines
|
||||
draw_line(0, 0, 640 - 1, 0, true);
|
||||
draw_line(0, 240 - 1, 640 - 1, 240 - 1, true);
|
||||
draw_line(0, 480 - 1, 640 - 1, 480 - 1, true);
|
||||
|
||||
// vertical lines
|
||||
draw_line(0, 0, 0, 480 - 1, true);
|
||||
draw_line(320 - 1, 0, 320 - 1, 480 - 1, true);
|
||||
draw_line(638, 0, 638, 480 - 1, true); // last pixel of a line can't be set, see set_pixel()
|
||||
|
||||
// cat eye
|
||||
for (uint16_t x = 0; x < 640; x += 20)
|
||||
{
|
||||
draw_line(x, 0, 0, (480 - x * 480 / 640), true);
|
||||
draw_line(640, x * 480 / 640, 640 - x, 480, true);
|
||||
}
|
||||
|
||||
// string
|
||||
draw_string(200, 200, "GLG PROGRAMS");
|
||||
}
|
||||
|
||||
void draw_simple_line(uint16_t x, uint16_t y, uint16_t len, bool horizontal)
|
||||
{
|
||||
for (uint16_t i = 0; i < len; ++i)
|
||||
{
|
||||
if (horizontal)
|
||||
set_pixel(x + i, y);
|
||||
else
|
||||
set_pixel(x, y + i);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_line(uint16_t _x0, uint16_t _y0, uint16_t _x1, uint16_t _y1, bool fill)
|
||||
{
|
||||
const float x0 = _x0;
|
||||
const float y0 = _y0;
|
||||
const float x1 = _x1;
|
||||
const float y1 = _y1;
|
||||
|
||||
if (x0 != x1)
|
||||
{
|
||||
const uint16_t x_begin = MIN(x0, x1);
|
||||
const uint16_t x_end = MAX(x0, x1);
|
||||
|
||||
for (uint16_t x = x_begin; x < x_end; ++x)
|
||||
{
|
||||
const uint16_t y = (y1 - y0) * (x - x0) / (x1 - x0) + y0;
|
||||
if (fill)
|
||||
set_pixel(x, y);
|
||||
else
|
||||
clear_pixel(x, y);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint16_t y_begin = MIN(y0, y1);
|
||||
const uint16_t y_end = MAX(y0, y1);
|
||||
|
||||
for (uint16_t y = y_begin; y < y_end; ++y)
|
||||
{
|
||||
if (fill)
|
||||
set_pixel(x0, y);
|
||||
else
|
||||
clear_pixel(x0, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_pixel(uint16_t x, uint16_t y)
|
||||
{
|
||||
// ignore out of frame pixels
|
||||
if (!valid_coord(x, y))
|
||||
return;
|
||||
|
||||
// Since there is not much space in PIO program memory,
|
||||
// it is not possible to set the last pixel of a line,
|
||||
// because there is not enough program space to unset it
|
||||
// in the hardware, and, if left set, will mess with
|
||||
// the sync signals.
|
||||
if (x == 639)
|
||||
return;
|
||||
|
||||
// skip wasted blanking lines
|
||||
// (see frame struct declaration)
|
||||
y += VBBLANK + VSPULSE - 1;
|
||||
|
||||
// set bit
|
||||
frame[y * 80 + x / 8] |= (1 << x % 8);
|
||||
}
|
||||
|
||||
void clear_pixel(uint16_t x, uint16_t y)
|
||||
{
|
||||
if (!valid_coord(x, y))
|
||||
return;
|
||||
|
||||
y += VBBLANK + VSPULSE - 1;
|
||||
frame[y * 80 + x / 8] &= ~(1 << x % 8);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef DRAW_DEMO_H
|
||||
#define DRAW_DEMO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct Point
|
||||
{
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
} Point;
|
||||
|
||||
typedef struct Letter
|
||||
{
|
||||
char letter;
|
||||
size_t len;
|
||||
Point *points;
|
||||
} Letter;
|
||||
|
||||
void draw_path(uint16_t x, uint16_t y, Point points[], size_t len);
|
||||
void draw_letter(uint16_t x, uint16_t y, char c);
|
||||
void draw_string(uint16_t x, uint16_t y, const char *s);
|
||||
void draw_demo(void);
|
||||
void draw_simple_line(uint16_t x, uint16_t y, uint16_t len, bool horizontal);
|
||||
void draw_line(uint16_t _x0, uint16_t _y0, uint16_t _x1, uint16_t _y1, bool fill);
|
||||
void set_pixel(uint16_t x, uint16_t y);
|
||||
void clear_pixel(uint16_t x, uint16_t y);
|
||||
|
||||
#endif // DRAW_DEMO_H
|
|
@ -0,0 +1,8 @@
|
|||
#include "framebuffer.h"
|
||||
|
||||
// Frame buffer.
|
||||
// Due to limitations in program space of PIO interface,
|
||||
// additional blank lines are needed and sent to the display,
|
||||
// to fill the vertical blanking interval at the top of the display.
|
||||
// (at 1BPP, this wastes about 1k)
|
||||
uint8_t frame[HPIXEL * (VSPULSE + VBBLANK + VPIXEL) * BPP / 8] = {0};
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef FRAMEBUFFER_H
|
||||
#define FRAMEBUFFER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define HPIXEL 640 // number of pixels displayed horizontally
|
||||
#define VPIXEL 480 // number of pixels displayed vertically
|
||||
#define BPP 1 // bits per pixel
|
||||
|
||||
#define VSPULSE 3 // number of lines during the vertical sync pulse
|
||||
#define VBBLANK 16 // number of lines of the initial back porch blanking period
|
||||
#define VFBLANK 1 // number of lines of the final front porch blanking period
|
||||
|
||||
extern uint8_t frame[HPIXEL * (VSPULSE + VBBLANK + VPIXEL) * BPP / 8];
|
||||
|
||||
#endif
|
330
src/main.c
330
src/main.c
|
@ -7,335 +7,39 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "hardware/pll.h"
|
||||
#include "hardware/structs/pio.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "vga.pio.h"
|
||||
|
||||
#define HPIXEL 640 // number of pixels displayed horizontally
|
||||
#define VPIXEL 480 // number of pixels displayed vertically
|
||||
#define BPP 1 // bits per pixel
|
||||
|
||||
#define VSPULSE 3 // number of lines during the vertical sync pulse
|
||||
#define VBBLANK 16 // number of lines of the initial back porch blanking period
|
||||
#define VFBLANK 1 // number of lines of the final front porch blanking period
|
||||
|
||||
// Frame buffer.
|
||||
// Due to limitations in program space of PIO interface,
|
||||
// additional blank lines are needed and sent to the display,
|
||||
// to fill the vertical blanking interval at top and bottom
|
||||
// of the display
|
||||
// (at 1BPP, this wastes about 1k)
|
||||
uint8_t frame[HPIXEL * (VSPULSE + VBBLANK + VPIXEL) * BPP / 8] = {0};
|
||||
|
||||
static void vga_pixel_program_init(PIO pio, uint sm, uint offset)
|
||||
{
|
||||
// magic definition of the called function?
|
||||
pio_sm_config config = vga_free_run_program_get_default_config(offset);
|
||||
// destination pins for OUT instructions
|
||||
sm_config_set_out_pins(&config, 22, 1);
|
||||
sm_config_set_out_shift(&config, true, true, 0);
|
||||
pio_gpio_init(pio, 22);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, 22, 1, true);
|
||||
sm_config_set_wrap(&config, offset + vga_free_run_wrap_target, offset + vga_free_run_wrap);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &config);
|
||||
|
||||
pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0);
|
||||
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
|
||||
static void vga_hsync_program_init(PIO pio, uint sm, uint offset)
|
||||
{
|
||||
pio_sm_config config = vga_hsync_program_get_default_config(offset);
|
||||
// destination pins for SET instructions
|
||||
sm_config_set_set_pins(&config, 20, 1);
|
||||
pio_gpio_init(pio, 20);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, 20, 1, true);
|
||||
sm_config_set_wrap(&config, offset + vga_hsync_wrap_target, offset + vga_hsync_wrap);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &config);
|
||||
|
||||
pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0);
|
||||
|
||||
pio->sm->shiftctrl = (1 << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB);
|
||||
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
|
||||
static void vga_vsync_program_init(PIO pio, uint sm, uint offset)
|
||||
{
|
||||
pio_sm_config config = vga_vsync_program_get_default_config(offset);
|
||||
// destination pins for SET instructions
|
||||
sm_config_set_set_pins(&config, 21, 1);
|
||||
pio_gpio_init(pio, 21);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, 21, 1, true);
|
||||
sm_config_set_wrap(&config, offset + vga_vsync_wrap_target, offset + vga_vsync_wrap);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &config);
|
||||
|
||||
pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0);
|
||||
|
||||
pio->sm->shiftctrl = (1 << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB);
|
||||
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
|
||||
void setup_clocks(void)
|
||||
{
|
||||
// disable resuscitation clock
|
||||
clocks_hw->resus.ctrl = 0;
|
||||
|
||||
// before changing PLL, switch sys and ref cleanly away from their aux sources
|
||||
hw_clear_bits(&clocks_hw->clk[clk_sys].ctrl, CLOCKS_CLK_SYS_CTRL_SRC_BITS);
|
||||
while (clocks_hw->clk[clk_sys].selected != 0x1)
|
||||
tight_loop_contents();
|
||||
hw_clear_bits(&clocks_hw->clk[clk_ref].ctrl, CLOCKS_CLK_REF_CTRL_SRC_BITS);
|
||||
while (clocks_hw->clk[clk_ref].selected != 0x1)
|
||||
tight_loop_contents();
|
||||
|
||||
// set PLL at 126 MHz, and wait for it to stabilize
|
||||
pll_init(pll_sys, 1, 1512 * MHZ, 6, 2);
|
||||
|
||||
// re-configure sys_clk to use PLL
|
||||
clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
|
||||
CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS, 126 * MHZ, 126 * MHZ);
|
||||
|
||||
// re-configure CLK PERI = clk_sys
|
||||
clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 126 * MHZ, 126 * MHZ);
|
||||
}
|
||||
|
||||
static bool dma_ready = false;
|
||||
static int dma_channel;
|
||||
|
||||
/**
|
||||
* @brief Represents a program running on the SM of a PIO.
|
||||
*
|
||||
*/
|
||||
typedef struct PIORun
|
||||
{
|
||||
PIO pio; //< executing PIO
|
||||
uint offset; //< PIO memory offset for program
|
||||
uint sm; //< executing SM
|
||||
} PIORun;
|
||||
|
||||
static PIORun *vga_pixel_ptr = NULL;
|
||||
|
||||
static unsigned long int frame_counter = 0;
|
||||
|
||||
void dma_handler(void)
|
||||
{
|
||||
// acknoweledge DMA
|
||||
dma_hw->ints0 = 1 << dma_channel;
|
||||
}
|
||||
|
||||
bool valid_coord(uint16_t x, uint16_t y)
|
||||
{
|
||||
if (x < 0 || x >= HPIXEL)
|
||||
return false;
|
||||
if (y < 0 || y >= VPIXEL)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_pixel(uint16_t x, uint16_t y)
|
||||
{
|
||||
// ignore out of frame pixels
|
||||
if (!valid_coord(x, y))
|
||||
return;
|
||||
|
||||
// Since there is not much space in PIO program memory,
|
||||
// it is not possible to set the last pixel of a line,
|
||||
// because there is not enough program space to unset it
|
||||
// in the hardware, and, if left set, will mess with
|
||||
// the sync signals.
|
||||
if (x == 639)
|
||||
return;
|
||||
|
||||
// skip wasted blanking lines
|
||||
// (see frame struct declaration)
|
||||
y += VBBLANK + VSPULSE - 1;
|
||||
|
||||
// set bit
|
||||
frame[y * 80 + x / 8] |= (1 << x % 8);
|
||||
}
|
||||
|
||||
void clear_pixel(uint16_t x, uint16_t y)
|
||||
{
|
||||
if (!valid_coord(x, y))
|
||||
return;
|
||||
|
||||
y += VBBLANK + VSPULSE - 1;
|
||||
frame[y * 80 + x / 8] &= ~(1 << x % 8);
|
||||
}
|
||||
|
||||
void draw_simple_line(uint16_t x, uint16_t y, uint16_t len, bool horizontal)
|
||||
{
|
||||
for (uint16_t i = 0; i < len; ++i)
|
||||
{
|
||||
if (horizontal)
|
||||
set_pixel(x + i, y);
|
||||
else
|
||||
set_pixel(x, y + i);
|
||||
}
|
||||
}
|
||||
|
||||
void new_frame_handler(void)
|
||||
{
|
||||
if (vga_pixel_ptr && dma_ready)
|
||||
{
|
||||
// Explicitly put the first uint32_t word in the FIFO,
|
||||
// because, when PIO pulls data for the first time,
|
||||
// and its FIFO is empty, the DMA controller takes "a while"
|
||||
// to put the first word and keep up with the transfer,
|
||||
// making the whole frame a bit offset to the right.
|
||||
// Since front porch is very short, even the smallest delay,
|
||||
// may disrupt the timing.
|
||||
// Also note: despite the "blocking put", when this interrupt
|
||||
// is serviced, PIO FIFO is always empty, so in practice
|
||||
// this will never block.
|
||||
pio_sm_put_blocking(vga_pixel_ptr->pio, vga_pixel_ptr->sm, 0);
|
||||
|
||||
// Start DMA (but skip the initial word, which has been put manually)
|
||||
dma_channel_set_read_addr(dma_channel, &frame[4], true);
|
||||
}
|
||||
|
||||
frame_counter++;
|
||||
|
||||
pio_interrupt_clear(pio0_hw, 1);
|
||||
}
|
||||
#include "draw_demo.h"
|
||||
#include "framebuffer.h"
|
||||
#include "vga.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
setup_clocks();
|
||||
stdio_init_all();
|
||||
sleep_ms(1000);
|
||||
|
||||
// clear frame buffer
|
||||
memset(&frame[0], 0, sizeof(frame));
|
||||
// draw a cross
|
||||
// horizontal lines
|
||||
for (uint16_t x = 0; x < 640; ++x)
|
||||
{
|
||||
if ((x / 32) % 2 == 0)
|
||||
set_pixel(x, 0);
|
||||
if ((x / 32) % 2 == 1)
|
||||
set_pixel(x, 2);
|
||||
set_pixel(x, 239);
|
||||
set_pixel(x, 479);
|
||||
}
|
||||
// vertical lines
|
||||
for (uint16_t y = 0; y < 480; ++y)
|
||||
{
|
||||
set_pixel(0 + (y / 10) * 4, y);
|
||||
set_pixel(0, y);
|
||||
set_pixel(319, y);
|
||||
// rightmost pixels can't be set, see description
|
||||
set_pixel(638, y);
|
||||
}
|
||||
// try to find strangeness using stairs
|
||||
for (uint16_t x = 0; x < 640; ++x)
|
||||
{
|
||||
set_pixel(x, (x / 32) * 4 + 120);
|
||||
}
|
||||
// draw "E" - nice because it's not symmetrical along X
|
||||
draw_simple_line(100, 100, 8, true);
|
||||
draw_simple_line(100, 100, 8, false);
|
||||
draw_simple_line(100, 104, 8, true);
|
||||
draw_simple_line(100, 108, 8, true);
|
||||
draw_demo();
|
||||
|
||||
// Running programs on PIO
|
||||
PIORun vga_hsync, vga_vsync, vga_pixel;
|
||||
vga_hsync.pio = vga_vsync.pio = vga_pixel.pio = pio0;
|
||||
|
||||
// Prepare frame interrupt
|
||||
// Frame interrupt is asserted on PIO0 interrupt 1 by SM1
|
||||
pio_set_irq1_source_enabled(vga_vsync.pio, pis_interrupt1,
|
||||
true); // allow SM1 of PIO to fire the interrupt
|
||||
irq_set_exclusive_handler(PIO0_IRQ_1, new_frame_handler); // set handler for IRQ1 of PIO
|
||||
irq_set_enabled(PIO0_IRQ_1, true); // enable interrupt
|
||||
|
||||
// VGA HSYNC program
|
||||
printf("Starting VGA hsync machine... ");
|
||||
if (!pio_can_add_program(vga_hsync.pio, &vga_hsync_program))
|
||||
panic("cannot add 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_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
|
||||
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");
|
||||
|
||||
// VGA VSYNC program
|
||||
printf("Starting VGA vsync machine... ");
|
||||
if (!pio_can_add_program(vga_vsync.pio, &vga_vsync_program))
|
||||
panic("cannot add 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_program_init(vga_vsync.pio, vga_vsync.sm, vga_vsync.offset);
|
||||
// low pulse assert lasts for 3 horizontal lines, then adjust by one
|
||||
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");
|
||||
|
||||
// VGA pixel program
|
||||
vga_pixel_ptr = &vga_pixel;
|
||||
printf("Starting VGA pixel machine... ");
|
||||
if (!pio_can_add_program(vga_pixel.pio, &vga_free_run_program))
|
||||
panic("cannot add program");
|
||||
vga_pixel.offset = pio_add_program(vga_pixel.pio, &vga_free_run_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);
|
||||
pio_sm_put_blocking(vga_pixel.pio, vga_pixel.sm, 640 - 1);
|
||||
printf("OK\n");
|
||||
|
||||
printf("Setting up DMA... ");
|
||||
dma_channel = dma_claim_unused_channel(true);
|
||||
dma_channel_config dma_config = dma_channel_get_default_config(dma_channel);
|
||||
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);
|
||||
channel_config_set_dreq(&dma_config, DREQ_PIO0_TX2);
|
||||
|
||||
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
|
||||
dma_channel_configure(dma_channel, &dma_config, &pio0_hw->txf[2], &frame[0],
|
||||
HPIXEL * (VSPULSE + VPIXEL + VBBLANK) / 32 - 1, true);
|
||||
|
||||
dma_ready = true;
|
||||
printf("OK\n");
|
||||
|
||||
printf("Now running.\n");
|
||||
vga_machines_init();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// fancy drawings
|
||||
const uint16_t fc = frame_counter;
|
||||
// moving sine
|
||||
const uint16_t fc = vga_get_frame_counter();
|
||||
const uint16_t x = fc % 640;
|
||||
const uint16_t y = 239 + 100 * (abs(320 - x) / 640.0) * sin((fc % 640) / 12.738);
|
||||
if ((fc / 640) % 2)
|
||||
set_pixel(x, y);
|
||||
else
|
||||
clear_pixel(x, y);
|
||||
sleep_ms(5);
|
||||
const uint16_t y = 380 + 100 * (abs(320 - x) / 640.0) * sin((fc % 640) / 12.738);
|
||||
|
||||
set_pixel(x, y);
|
||||
|
||||
// 13 ms <=> 75 Hz
|
||||
// Actually, there is a point in going in sync with vertical refresh,
|
||||
// but let's just forget this for the demo.
|
||||
sleep_ms(13);
|
||||
|
||||
clear_pixel(x, y);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
#include "vga.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "hardware/structs/pio.h"
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/pll.h"
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "vga.pio.h"
|
||||
|
||||
/**
|
||||
* @brief Represents a program running on the SM of a PIO.
|
||||
*
|
||||
*/
|
||||
typedef struct PIORun
|
||||
{
|
||||
PIO pio; //< executing PIO
|
||||
uint offset; //< PIO memory offset for program
|
||||
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; //< 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
|
||||
clocks_hw->resus.ctrl = 0;
|
||||
|
||||
// before changing PLL, switch sys and ref cleanly away from their aux sources
|
||||
hw_clear_bits(&clocks_hw->clk[clk_sys].ctrl, CLOCKS_CLK_SYS_CTRL_SRC_BITS);
|
||||
while (clocks_hw->clk[clk_sys].selected != 0x1)
|
||||
tight_loop_contents();
|
||||
hw_clear_bits(&clocks_hw->clk[clk_ref].ctrl, CLOCKS_CLK_REF_CTRL_SRC_BITS);
|
||||
while (clocks_hw->clk[clk_ref].selected != 0x1)
|
||||
tight_loop_contents();
|
||||
|
||||
// set PLL at 126 MHz, and wait for it to stabilize
|
||||
pll_init(pll_sys, 1, 1512 * MHZ, 6, 2);
|
||||
|
||||
// re-configure sys_clk to use PLL
|
||||
clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
|
||||
CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS, 126 * MHZ, 126 * MHZ);
|
||||
|
||||
// re-configure CLK PERI = clk_sys
|
||||
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 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);
|
||||
|
||||
// destination pins for OUT instructions
|
||||
sm_config_set_out_pins(&config, 22, 1);
|
||||
sm_config_set_out_shift(&config, true, true, 0);
|
||||
pio_gpio_init(pio, 22);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, 22, 1, true);
|
||||
sm_config_set_wrap(&config, offset + vga_pixel_wrap_target, offset + vga_pixel_wrap);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &config);
|
||||
|
||||
pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0);
|
||||
|
||||
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);
|
||||
// destination pins for SET instructions
|
||||
sm_config_set_set_pins(&config, 20, 1);
|
||||
pio_gpio_init(pio, 20);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, 20, 1, true);
|
||||
sm_config_set_wrap(&config, offset + vga_hsync_wrap_target, offset + vga_hsync_wrap);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &config);
|
||||
|
||||
pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0);
|
||||
|
||||
pio->sm->shiftctrl = (1 << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB);
|
||||
|
||||
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);
|
||||
// destination pins for SET instructions
|
||||
sm_config_set_set_pins(&config, 21, 1);
|
||||
pio_gpio_init(pio, 21);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, 21, 1, true);
|
||||
sm_config_set_wrap(&config, offset + vga_vsync_wrap_target, offset + vga_vsync_wrap);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &config);
|
||||
|
||||
pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0);
|
||||
|
||||
pio->sm->shiftctrl = (1 << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB);
|
||||
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief DMA interrupt handler.
|
||||
*/
|
||||
static void dma_handler(void)
|
||||
{
|
||||
// acknoweledge DMA
|
||||
dma_hw->ints0 = 1 << dma_channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vertical sync interrupt handler.
|
||||
*
|
||||
*/
|
||||
static void new_frame_handler(void)
|
||||
{
|
||||
if (dma_ready)
|
||||
{
|
||||
// Explicitly put the first uint32_t word in the FIFO,
|
||||
// because, when PIO pulls data for the first time,
|
||||
// and its FIFO is empty, the DMA controller takes "a while"
|
||||
// to put the first word and keep up with the transfer,
|
||||
// making the whole frame a bit offset to the right.
|
||||
// Since front porch is very short, even the smallest delay,
|
||||
// may disrupt the timing.
|
||||
// Also note: despite the "blocking put", when this interrupt
|
||||
// is serviced, PIO FIFO is always empty, so in practice
|
||||
// this will never block.
|
||||
pio_sm_put_blocking(vga_pixel.pio, vga_pixel.sm, 0);
|
||||
|
||||
// Start DMA (but skip the initial word, which has been put manually)
|
||||
dma_channel_set_read_addr(dma_channel, &frame[4], true);
|
||||
}
|
||||
|
||||
frame_counter++;
|
||||
|
||||
pio_interrupt_clear(pio0_hw, 1);
|
||||
}
|
||||
|
||||
void vga_machines_init(void)
|
||||
{
|
||||
// setup a proper system clock
|
||||
setup_clocks();
|
||||
|
||||
// Running programs on PIO
|
||||
vga_hsync.pio = vga_vsync.pio = vga_pixel.pio = pio0;
|
||||
|
||||
// Prepare frame interrupt
|
||||
// Frame interrupt is asserted on PIO0 interrupt 1 by SM1
|
||||
pio_set_irq1_source_enabled(vga_vsync.pio, pis_interrupt1,
|
||||
true); // allow SM1 of PIO to fire the interrupt
|
||||
irq_set_exclusive_handler(PIO0_IRQ_1, new_frame_handler); // set handler for IRQ1 of PIO
|
||||
irq_set_enabled(PIO0_IRQ_1, true); // enable interrupt
|
||||
|
||||
// VGA HSYNC program
|
||||
printf("Starting VGA hsync machine... ");
|
||||
if (!pio_can_add_program(vga_hsync.pio, &vga_hsync_program))
|
||||
panic("cannot add 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_program_init(vga_hsync.pio, vga_hsync.sm, vga_hsync.offset);
|
||||
|
||||
// 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");
|
||||
|
||||
// VGA VSYNC program
|
||||
printf("Starting VGA vsync machine... ");
|
||||
if (!pio_can_add_program(vga_vsync.pio, &vga_vsync_program))
|
||||
panic("cannot add 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_program_init(vga_vsync.pio, vga_vsync.sm, vga_vsync.offset);
|
||||
// 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");
|
||||
|
||||
// VGA pixel program
|
||||
printf("Starting VGA pixel machine... ");
|
||||
if (!pio_can_add_program(vga_pixel.pio, &vga_pixel_program))
|
||||
panic("cannot add 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_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");
|
||||
|
||||
printf("Setting up DMA... ");
|
||||
dma_channel = dma_claim_unused_channel(true);
|
||||
dma_channel_config dma_config = dma_channel_get_default_config(dma_channel);
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
dma_ready = true;
|
||||
printf("OK\n");
|
||||
|
||||
printf("Now running.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get current frame counter.
|
||||
*
|
||||
* @return Current frame counter.
|
||||
*/
|
||||
unsigned long int vga_get_frame_counter(void)
|
||||
{
|
||||
return frame_counter;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef VGA_H
|
||||
#define VGA_H
|
||||
|
||||
void vga_machines_init(void);
|
||||
unsigned long int vga_get_frame_counter(void);
|
||||
|
||||
#endif // VGA_H
|
86
src/vga.pio
86
src/vga.pio
|
@ -1,60 +1,116 @@
|
|||
.program vga_free_run
|
||||
/*
|
||||
- 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
|
||||
*/
|
||||
|
||||
entrypoint_vga_free_run:
|
||||
/*
|
||||
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