Uses the RP2040's Programmable IO to create a PAL colour video signal. It consists of the PIO program, some "driver" and tools code, a test program and a program for creating colour Look-up Tables (LUTs).
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
rppico-pio-pal/paltest.c

167 lines
4.6 KiB

// A little example program to show how the PAL video generator is used.
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/sem.h"
#include "hardware/pll.h"
#include "hardware/clocks.h"
#include "analogVideoDriver.h"
#define VBLANK_PIN 5
const uint grayShades[] = {0x44444444, 0x55555555, 0x66666666, 0x77777777,
0x88888888, 0x99999999, 0xaaaaaaaa, 0xbbbbbbbb,
0xcccccccc};
// we only have nine shades of gray as black is 0x4 and white is 0xe
const uint nGrayShades = 9;
uint colourIdx = 0;
uint pixelsPerShade, rowsPerShade;
xy resolution;
semaphore_t renderLineSemaphore;
// can be used to control the speed at which the LED blinks (25 -> 1Hz)
const uint vBlankLedSlowdown = 25;
uint currLine = 0;
uint *renderBuffer = NULL;
// NOTE: This is the preferred way to implement pixel rendering.
// This function is called from the interrupt handler and simply stores the
// line number and pointer to video buffer. It then increments a semaphore which
// a loop in main waits for to start rendering. This allows the interrupt handler
// to finish its work quickly.
void hBlankTask(uint line, uint* buffer) {
currLine = line;
renderBuffer = buffer;
sem_release(&renderLineSemaphore);
}
void vBlankTask(void) {
static uint nextLedVal = 0, slowdown = vBlankLedSlowdown;
#ifdef PICO_DEFAULT_LED_PIN
gpio_put(PICO_DEFAULT_LED_PIN, nextLedVal);
// printDebugInfo();
if(--slowdown == 0) {
nextLedVal = (++nextLedVal) & 1;
slowdown = vBlankLedSlowdown;
if(++colourIdx >= getColourCount())
colourIdx = 0;
}
#endif
}
void renderLine(uint line, uint* buffer) {
uint gray = 0, x = 0, pixelsRendered = 0;
uint rowMSBits = line / rowsPerShade;
for(x = 0; x < resolution.x; ++x) {
if(pixelsRendered++ == pixelsPerShade) {
++gray;
pixelsRendered = 0;
}
// here are some example codes to render different things.
// render two horizontal colour bars
/* if(line > (resolution.y >> 1))
putPixel(&buffer[x], 48);
else
putPixel(&buffer[x], 12);
*/
// render some gray shades
// buffer[x] = grayShades[gray];
// render an 8x8 array of coloured boxes (should produce all 64 colours)
putPixel(&buffer[x], (rowMSBits << 3) | gray);
// render 8 coloured lines
// putPixel(&buffer[x], gray << 3);
// render one specific colour
// putPixel(&buffer[x], colourIdx);
// make the whole screen red
// putPixel(&buffer[x], 48);
// render single line high coloured lines
// putPixel(&buffer[x], (line + (x >> 1)) & 63);
}
}
uint print = 0, calls = 0, line = 625, branch;
// not used any longer
void debugTask(uint l, uint b) {
line = l;
branch = b;
print = 1;
++calls;
// printf("line: %u branch: %u\n (call %u) FIFO level: %u\n", line, branch, calls, pio_sm_get_tx_fifo_level(pio0, 0));
printf("line: %u branch: %u avStatus %x\n", line, branch, analogVideoStatus());
}
int main() {
#ifndef PICO_DEFAULT_LED_PIN
#warning paltest requires a board with a regular LED
#else
// first set the proper clock
set_sys_clock_khz(141600, true);
stdio_init_all();
// you can use this if you want to see printf() output, it gives you some time
// to start the terminal application (necessary on Linux as the TTY device
// is removed once you unplug the Pico)
// sleep_ms(2000);
printf("started\n");
// initialize LED pin, we'll use the LED to visualize vertical blank,
// it should blink at 1 Hz
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
gpio_put(PICO_DEFAULT_LED_PIN, 1);
analogVideoInit(pio0, 0, 5);
analogVideoGetResolution(&resolution);
pixelsPerShade = resolution.x / 8;
pixelsPerShade += ((resolution.x & 7) > 4) ? 1 : 0;
// gray scale
// pixelsPerShade += ((resolution.x % nGrayShades) > (nGrayShades >> 1)) ? 1 : 0;// this is just a bit more general than the above
rowsPerShade = resolution.y / 8;
rowsPerShade += ((resolution.y & 7) > 4) ? 1 : 0;
// printf("Screen resolution: %u x %u\nPixels per gray shade: %u (%u shades)\n", resolution.x, resolution.y, pixelsPerShade, nGrayShades);
printf("Screen resolution: %u x %u\ncolour box size: %u x %u (64 colours)\n", resolution.x, resolution.y, pixelsPerShade, rowsPerShade);
// setDebugIsrCallback(debugTask);
printf("video init done\n");
enterTestMode(0);
// initialize the semaphore which is used to wait for the next line to be rendered
sem_init(&renderLineSemaphore, 0, 1);
analogVideoStart(hBlankTask, vBlankTask);
printf("video started\n");
while (true) {
// wait until the semaphore is > 0 (so the H-Blank function has been called)...
sem_acquire_blocking(&renderLineSemaphore);
// ... then call the rendering function.
renderLine(currLine, renderBuffer);
}
#endif
}