#scanvideo — Public Fediverse posts
Live and recent posts from across the Fediverse tagged #scanvideo, aggregated by home.social.
-
Simplified Pico VGA – Part 2
Having spent far too long chewing over how the Raspberry Pi Pico Scanvideo library works over in Simplified Pico VGA, this post actually starts to do something with it.
One constraint I have for myself, is that I want to keep using the Arduino IDE for the Pico, so the first step is to see if I can get the Scanvideo library over to an Arduino sketch. This means grabbing the files from the following locations:
- https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_scanvideo
- https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_scanvideo_dpi
- https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_util_buffer
- https://github.com/raspberrypi/pico-playground/tree/master/scanvideo
The pico_util_buffer code is used for buffer management, so that is required too. The last one houses some examples, I’m just using the https://github.com/raspberrypi/pico-playground/tree/master/scanvideo/test_pattern as a basis for my own example.
As with my other PIO experiments, I’m using the unofficial Arduino RP2040 core.
Starting the Arduino Sketch
In order to get things started, I’m just taking all the files from the above projects and dropping them directly into the Arduino Sketch folder, with no further hierarchy. My file list looks as follows:
25/07/2026 19:26 392 buffer.c
25/07/2026 19:25 2,265 buffer.h
25/07/2026 19:20 1,425 composable_scanline.h
25/07/2026 19:27 1,675 PicoScanVideo.ino
25/07/2026 19:49 6,293 scanvideo-pio.h
25/07/2026 19:26 83,925 scanvideo.c
25/07/2026 19:24 1,949 scanvideo.h
25/07/2026 19:16 13,613 scanvideo_base.h
25/07/2026 19:49 2,488 timing-pio.h
25/07/2026 19:20 17,057 vga_modes.cEach one of these files must be edited to change the include paths to drop down to a single directory.
There are three files here that are not in the original libraries. The .ino I’ll come to shortly, but the two *-pio.h files are the assembled files from the two original .pio files. These are created using https://wokwi.com/tools/pioasm and the output is pasted into these two new h files.
Initial Test Code
I’ve created an example based on https://github.com/raspberrypi/pico-playground/tree/master/scanvideo/test_pattern. But I’ve removed the multicore and keyboard input. This will (hopefully) just display the “test card” and nothing else.
#include "scanvideo.h"
#include "composable_scanline.h"
#define vga_mode vga_mode_320x240_60
static bool invert = false;
void draw_color_bar(scanvideo_scanline_buffer_t *buffer) {
// figure out 1/32 of the color value
uint line_num = scanvideo_scanline_number(buffer->scanline_id);
uint32_t primary_color = 1u + (line_num * 7 / vga_mode.height);
uint32_t color_mask = PICO_SCANVIDEO_PIXEL_FROM_RGB5(
0x1f * (primary_color & 1u),
0x1f * ((primary_color >> 1u) & 1u),
0x1f * ((primary_color >> 2u) & 1u));
uint bar_width = vga_mode.width / 32;
uint16_t *p = (uint16_t *) buffer->data;
uint32_t invert_bits = invert ? PICO_SCANVIDEO_PIXEL_FROM_RGB5(0x1f,0x1f,0x1f) : 0;
for (uint bar = 0; bar < 32; bar++) {
*p++ = COMPOSABLE_COLOR_RUN;
uint32_t color = PICO_SCANVIDEO_PIXEL_FROM_RGB5(bar, bar, bar);
*p++ = (color & color_mask) ^ invert_bits;
*p++ = bar_width - 3;
}
// 32 * 3, so we should be word aligned
assert(!(3u & (uintptr_t) p));
// black pixel to end line
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
// end of line with alignment padding
*p++ = COMPOSABLE_EOL_SKIP_ALIGN;
*p++ = 0;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}
void setup() {
// initialize video and interrupts on core 1
scanvideo_setup(&vga_mode);
scanvideo_timing_enable(true);
}
void loop() {
scanvideo_scanline_buffer_t *scanline_buffer =
scanvideo_begin_scanline_generation(true);
draw_color_bar(scanline_buffer);
scanvideo_end_scanline_generation(scanline_buffer);
}This it the same as the provided example apart from the fact that the core1 function has been split into the initialisation code, placed in setup(), and the infinite loop code, now placed in loop().
This requires the circuit from the pico_playground here: https://github.com/raspberrypi/pico-playground/tree/master/scanvideo, to provide a 5-bit (RGB555) VGA interface.
VGA Breakout
As can be seen in the above photo I’m using a breakout board between the Pico and the VGA display. I designed this to let me experiment with VGA and the Pico.
Bill of Materials:
- VGA socket (see photos and PCB for footprint).
- Pin headers.
- Range of resistors and diodes depending on the DAC design.
GitHub link: https://github.com/diyelectromusic/sdemp_pcbs/tree/main/PicoVGABreakout
The photo below shows two configurations. On the left is a RGB555 as detailed in the RP2040 Hardware Design Guide and the pico_playground repository. This uses a 5-resistor DAC comprising: 8K, 4K, 2K, 1K, 500R resistors. Although I only had 4K7 resistors, so as you can see have used two 2K resistors in series. It uses two 47R resistors for HSYNC and VSYNC.
On the right is the 330R/100R and diode configuration mentioned in Part 1 for a RGBY1111 configuration. This uses two 100R resistors for HSYNC and VSYNC (I don’t know why they are different to the RGB555 case).
There are suggested resistor values for different options printed on the board, although there is one mistake. The RGBY1111 suggests 300R+100R in the text, whereas 330R+100R against the components. 330R is the recommended value.
To wire up the board for the default RGB555 configuration with a Pico uses the resistor and colour to GPIO mapping from the Raspberry Pi library example (schematic here):
GP08KRed MSBitGP14KRedGP22KRedGP31KRedGP4500RRed LSBitGP5N/CN/CGP68KGreen MSBitGP74KGreenGP82KGreenGP91KGreenGP10500RGreen LSBitGP118KBlue MSBitGP124KBlueGP132KBlueGP141KBlueGP15500RBlue LSBitGP1647RHSYNCGP1747RVSYNCNote that the order here is the opposite to many of the other modes used in the pico_zxspectrum. The RPi scanvideo example has the sequence:
- GPIO 0 -> GPIO 15 = R5 G5 B5
Many of the other modes use BGR ordering (i.e. B MSbit is the lowest GPIO used) as can be seen in the following definitions (from here):
#define VGA_RGB_555(r,g,b) ((r##UL<<0)|(g##UL<<6)|(b##UL << 11))
#define VGA_BGYR_1111(r,g,b,y) ((y##UL<<2)|(r##UL<<3)|(g##UL<<1)|b##UL)
#define VGA_RGBY_1111(r,g,b,y) ((y##UL<<3)|(r##UL<<2)|(g##UL<<1)|b##UL)
#define VGA_RGB_332(r,g,b) ((r##UL<<5)|(g##UL<<2)|b##UL)
#define VGA_RGB_222(r,g,b) ((r##UL<<4)|(g##UL<<2)|b##UL)For completeness, RGB222 would be implemented using the breakout as follows:
GP01KBlue MSBitGP1470RBlue LSBitGP21KGreen MSBitGP3470RGreen LSBitGP41KRed MSBitGP5470RRed LSBitGP6100RHSYNCGP7100RVSYNCVGA to CGA
The reason for doing all this is to get to that simpler CGA-like mode that mirrors the ZX Spectrum video capability. To do this I need to get the scanvideo library implementing RGBY1111 as mentioned in Part 1.
The scanvideo library assumes the use of compiler directives to set the video modes, but these can’t just be set prior to including the appropriate header files in my .ino file as they need to actually be compiled into the library C files too.
To do this, I created a vgamode.h file which can be included once in scanvideo.h which is included everywhere within the library.
vgamode.h:
#define VGA_RGBY111
#define PICO_SCANVIDEO_COLOR_PIN_COUNT 4u
#define PICO_SCANVIDEO_DPI_PIXEL_RCOUNT 2u
#define PICO_SCANVIDEO_DPI_PIXEL_GCOUNT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_BCOUNT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_RSHIFT 2u
#define PICO_SCANVIDEO_DPI_PIXEL_GSHIFT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_BSHIFT 0u
#define PICO_SCANVIDEO_COLOR_PIN_BASE 12uAs described in Part 1 this sets the hardware interface up for 4 GPIO pins for RGB and Y (as a second R) and sets the GPIO base pin to 12. The SYNC pins are automatically set elsewhere to be:
#define PICO_SCANVIDEO_SYNC_PIN_BASE (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_COLOR_PIN_COUNT)
This creates the following GPIO mapping and resistor usage:
GP12330R+100RBlueGP13330R+100RGreenGP14330R+100RRedGP15Diodes + 330R x 3Second Red (Y)GP16100RHSYNCGP17100RVSYNCThis also now requires an alternative scanline generator, so I’ve implemented the following:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num =
scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 16ul) / vga_mode.height;
uint16_t num_pxls = vga_mode.width;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
*p++ = COMPOSABLE_COLOR_RUN ;
*p++ = zxd_colour_words[col];
*p++ = num_pxls - 3;
*p++ = COMPOSABLE_EOL_ALIGN;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}This creates a “COLOR_RUN” line of a single colour using the mapping in zxd_colour_words[] which has been taken from the pico_zxspectrum code, but rather than providing 32-bit (duplicated) values (as described last time) I’m just using direct, single, 16-bit values:
#define VGA_RGBY_1111(r,g,b,y) ((y<<3)|(r<<2)|(g<<1)|b)
static uint16_t zxd_colour_words[16] = {
VGA_RGBY_1111(0,0,0,0), // Black
VGA_RGBY_1111(0,0,1,0), // Blue
VGA_RGBY_1111(1,0,0,0), // Red
VGA_RGBY_1111(1,0,1,0), // Magenta
VGA_RGBY_1111(0,1,0,0), // Green
VGA_RGBY_1111(0,1,1,0), // Cyan
VGA_RGBY_1111(1,1,0,0), // Yellow
VGA_RGBY_1111(1,1,1,0), // White
VGA_RGBY_1111(0,0,0,0), // Bright Black
VGA_RGBY_1111(0,0,1,1), // Bright Blue
VGA_RGBY_1111(1,0,0,1), // Bright Red
VGA_RGBY_1111(1,0,1,1), // Bright Magenta
VGA_RGBY_1111(0,1,0,1), // Bright Green
VGA_RGBY_1111(0,1,1,1), // Bright Cyan
VGA_RGBY_1111(1,1,0,1), // Bright Yellow
VGA_RGBY_1111(1,1,1,1) // Bright White
};This seems to work pretty well, but all the colours are a bit too dark. The bottom bar should be “bright white” for example, and it is a pretty dirty grey really.
I’m also not sure what is going on at the top – it looks like there is a bit of bleed-through of the last colour at the start. I don’t know if this is a sync/coordination issue with scanline number and the processing code or if this is something else.
Looking more closely at what is produced between the two modes, the only difference I could see was that I was creating a single scanline entry for the whole line of pixels, whereas the testcard pattern was splitting each line up in to 32 “bars”.
Rewriting the code to do the same, albeit with the same colour in each “bar” gives me:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num =
scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 8ul) / vga_mode.height;
uint bar_width = vga_mode.width / 32;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
for (uint bar = 0; bar < 32; bar++) {
*p++ = COMPOSABLE_COLOR_RUN ;
if (bar < 16) {
*p++ = zxd_colour_words[col];
} else {
*p++ = zxd_colour_words[col+8];
}
*p++ = bar_width - 3;
}
// 32 * 3, so we should be word aligned
assert(!(3u & (uintptr_t) p));
// black pixel to end line
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
// end of line with alignment padding
*p++ = COMPOSABLE_EOL_SKIP_ALIGN;
*p++ = 0;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}This is so much better! I’m getting proper colours now.
In the first version (as shown above) it isn’t obvious if the brightness is working or not (it is – I used cut-and-paste on the photo to compare the top and the bottom!), so I created a version (given above) to show the bright and non-bright colours side by side. Now we can see the full 15 colours properly.
I have no idea why this makes a difference, but for some reason it does. My initial working theory is that having too long a COLOR_RUN causes too much time to be taken up in the PIO program, as COLOR_RUN is basically implemented as “set the colour values; then wait around for the number of pixels time before getting the next video instruction”, so a large number of pixels means the PIO is stuck in a loop without consuming data from DMA.
I don’t know what effect this will have, but with each scan line split into 32 chunks, there is more data to DMA to the PIO, but the PIO also consumes it quicker.
I don’t know why this would affect the video signal, but maybe it means there are gaps in the GPIO output video signal meaning the voltage gets averaged to a lower value somehow (a bit like how PWM would work)? If that is the case I should be able to see that on a scope…
In the following traces, yellow is the B GPIO pin and blue is the HSYNC. On the left is the correctly functioning 32-block version and on the right is the “draw a whole line in one go” version.
Zooming out a little…
Ok, so now I’ve even less idea what is going on. For the failing “write a whole line in one go” case the B GPIO never seems to let up – it is constantly HIGH. For the working case, it drops back to zero synchronised with each scan line.
Then I realised what is causing that – there is an additional black pixel added for the working case – the 32 blocks is probably irrelevant! So third try:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num = scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 16ul) / vga_mode.height;
uint16_t numpxls = vga_mode.width;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
*p++ = COMPOSABLE_COLOR_RUN ;
*p++ = zxd_colour_words[col];
*p++ = numpxls - 3;
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
*p++ = COMPOSABLE_EOL_ALIGN;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}And this works!! This gives me the nice full-coloured patterns with brightness.
A bit of searching turns up this note on the scanvideo readme that I’d missed:
“Important; You MUST end the scanline with one or more black pixels of your own (otherwise your color will bleed into the blanking!!!).”
Well that explains the problem, but I still don’t really understand what “bleed into the blanking” means and why that results in a much lower intensity colour on the screen. Searching some more has not enlightened me, so if you know why this is the case, do let me know.
Update: Someone sent me a link to this, which explains it: https://www.righto.com/2018/04/#fn:blanking
“When I forgot to blank the pixels outside the valid screen area, the monitor still managed to display an image, but it was very dim because the monitor got confused about what voltage represented “dark”. Just a tip in case you find your display mysteriously darkened.”
So it is something to do with ensuring the monitor can detect what “off” looks like in the signal, which would explain why everything went screwy when it what it thought was “off” was still set to a voltage.
Anyway. It all now works. I have a working, ZX Spectrum compatible video mode implemented on a Raspberry Pi Pico using just 6 GPIO in the end (as I still have both SYNCs).
Kevin
#cga #pio #raspberryPiPico #scanvideo #vga #zxSpectrum -
Simplified Pico VGA – Part 2
Having spent far too long chewing over how the Raspberry Pi Pico Scanvideo library works over in Simplified Pico VGA, this post actually starts to do something with it.
One constraint I have for myself, is that I want to keep using the Arduino IDE for the Pico, so the first step is to see if I can get the Scanvideo library over to an Arduino sketch. This means grabbing the files from the following locations:
- https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_scanvideo
- https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_scanvideo_dpi
- https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_util_buffer
- https://github.com/raspberrypi/pico-playground/tree/master/scanvideo
The pico_util_buffer code is used for buffer management, so that is required too. The last one houses some examples, I’m just using the https://github.com/raspberrypi/pico-playground/tree/master/scanvideo/test_pattern as a basis for my own example.
As with my other PIO experiments, I’m using the unofficial Arduino RP2040 core.
Starting the Arduino Sketch
In order to get things started, I’m just taking all the files from the above projects and dropping them directly into the Arduino Sketch folder, with no further hierarchy. My file list looks as follows:
25/07/2026 19:26 392 buffer.c
25/07/2026 19:25 2,265 buffer.h
25/07/2026 19:20 1,425 composable_scanline.h
25/07/2026 19:27 1,675 PicoScanVideo.ino
25/07/2026 19:49 6,293 scanvideo-pio.h
25/07/2026 19:26 83,925 scanvideo.c
25/07/2026 19:24 1,949 scanvideo.h
25/07/2026 19:16 13,613 scanvideo_base.h
25/07/2026 19:49 2,488 timing-pio.h
25/07/2026 19:20 17,057 vga_modes.cEach one of these files must be edited to change the include paths to drop down to a single directory.
There are three files here that are not in the original libraries. The .ino I’ll come to shortly, but the two *-pio.h files are the assembled files from the two original .pio files. These are created using https://wokwi.com/tools/pioasm and the output is pasted into these two new h files.
Initial Test Code
I’ve created an example based on https://github.com/raspberrypi/pico-playground/tree/master/scanvideo/test_pattern. But I’ve removed the multicore and keyboard input. This will (hopefully) just display the “test card” and nothing else.
#include "scanvideo.h"
#include "composable_scanline.h"
#define vga_mode vga_mode_320x240_60
static bool invert = false;
void draw_color_bar(scanvideo_scanline_buffer_t *buffer) {
// figure out 1/32 of the color value
uint line_num = scanvideo_scanline_number(buffer->scanline_id);
uint32_t primary_color = 1u + (line_num * 7 / vga_mode.height);
uint32_t color_mask = PICO_SCANVIDEO_PIXEL_FROM_RGB5(
0x1f * (primary_color & 1u),
0x1f * ((primary_color >> 1u) & 1u),
0x1f * ((primary_color >> 2u) & 1u));
uint bar_width = vga_mode.width / 32;
uint16_t *p = (uint16_t *) buffer->data;
uint32_t invert_bits = invert ? PICO_SCANVIDEO_PIXEL_FROM_RGB5(0x1f,0x1f,0x1f) : 0;
for (uint bar = 0; bar < 32; bar++) {
*p++ = COMPOSABLE_COLOR_RUN;
uint32_t color = PICO_SCANVIDEO_PIXEL_FROM_RGB5(bar, bar, bar);
*p++ = (color & color_mask) ^ invert_bits;
*p++ = bar_width - 3;
}
// 32 * 3, so we should be word aligned
assert(!(3u & (uintptr_t) p));
// black pixel to end line
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
// end of line with alignment padding
*p++ = COMPOSABLE_EOL_SKIP_ALIGN;
*p++ = 0;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}
void setup() {
// initialize video and interrupts on core 1
scanvideo_setup(&vga_mode);
scanvideo_timing_enable(true);
}
void loop() {
scanvideo_scanline_buffer_t *scanline_buffer =
scanvideo_begin_scanline_generation(true);
draw_color_bar(scanline_buffer);
scanvideo_end_scanline_generation(scanline_buffer);
}This it the same as the provided example apart from the fact that the core1 function has been split into the initialisation code, placed in setup(), and the infinite loop code, now placed in loop().
This requires the circuit from the pico_playground here: https://github.com/raspberrypi/pico-playground/tree/master/scanvideo, to provide a 5-bit (RGB555) VGA interface.
VGA Breakout
As can be seen in the above photo I’m using a breakout board between the Pico and the VGA display. I designed this to let me experiment with VGA and the Pico.
Bill of Materials:
- VGA socket (see photos and PCB for footprint).
- Pin headers.
- Range of resistors and diodes depending on the DAC design.
The photo below shows two configurations. On the left is a RGB555 as detailed in the RP2040 Hardware Design Guide and the pico_playground repository. This uses a 5-resistor DAC comprising: 8K, 4K, 2K, 1K, 500R resistors. Although I only had 4K7 resistors, so as you can see have used two 2K resistors in series. It uses two 47R resistors for HSYNC and VSYNC.
On the right is the 330R/100R and diode configuration mentioned in Part 1 for a RGBY1111 configuration. This uses two 100R resistors for HSYNC and VSYNC (I don’t know why they are different to the RGB555 case).
There are suggested resistor values for different options printed on the board, although there is one mistake. The RGBY1111 suggests 300R+100R in the text, whereas 330R+100R against the components. 330R is the recommended value.
To wire up the board for the default RGB555 configuration with a Pico uses the following resistor to GPIO mapping:
GP08KRed MSBitGP14KRedGP22KRedGP31KRedGP4500RRed LSBitGP5N/CN/CGP68KGreen MSBitGP74KGreenGP82KGreenGP91KGreenGP10500RGreen LSBitGP118KBlue MSBitGP124KBlueGP132KBlueGP141KBlueGP15500RBlue LSBitGP1647RHSYNCGP1747RVSYNCVGA to CGA
The reason for doing all this is to get to that simpler CGA-like mode that mirrors the ZX Spectrum video capability. To do this I need to get the scanvideo library implementing RGBY1111 as mentioned in Part 1.
The scanvideo library assumes the use of compiler directives to set the video modes, but these can’t just be set prior to including the appropriate header files in my .ino file as they need to actually be compiled into the library C files too.
To do this, I created a vgamode.h file which can be included once in scanvideo.h which is included everywhere within the library.
vgamode.h:
#define VGA_RGBY111
#define PICO_SCANVIDEO_COLOR_PIN_COUNT 4u
#define PICO_SCANVIDEO_DPI_PIXEL_RCOUNT 2u
#define PICO_SCANVIDEO_DPI_PIXEL_GCOUNT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_BCOUNT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_RSHIFT 2u
#define PICO_SCANVIDEO_DPI_PIXEL_GSHIFT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_BSHIFT 0u
#define PICO_SCANVIDEO_COLOR_PIN_BASE 12uAs described in Part 1 this sets the hardware interface up for 4 GPIO pins for RGB and Y (as a second R) and sets the GPIO base pin to 12. The SYNC pins are automatically set elsewhere to be:
#define PICO_SCANVIDEO_SYNC_PIN_BASE (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_COLOR_PIN_COUNT)
This creates the following GPIO mapping and resistor usage:
GP12330R+100RBlueGP13330R+100RGreenGP14330R+100RRedGP15Diodes + 330R x 3Second Red (Y)GP16100RHSYNCGP17100RVSYNCThis also now requires an alternative scanline generator, so I’ve implemented the following:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num =
scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 16ul) / vga_mode.height;
uint16_t num_pxls = vga_mode.width;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
*p++ = COMPOSABLE_COLOR_RUN ;
*p++ = zxd_colour_words[col];
*p++ = num_pxls - 3;
*p++ = COMPOSABLE_EOL_ALIGN;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}This creates a “COLOR_RUN” line of a single colour using the mapping in zxd_colour_words[] which has been taken from the pico_zxspectrum code, but rather than providing 32-bit (duplicated) values (as described last time) I’m just using direct, single, 16-bit values:
#define VGA_RGBY_1111(r,g,b,y) ((y<<3)|(r<<2)|(g<<1)|b)
static uint16_t zxd_colour_words[16] = {
VGA_RGBY_1111(0,0,0,0), // Black
VGA_RGBY_1111(0,0,1,0), // Blue
VGA_RGBY_1111(1,0,0,0), // Red
VGA_RGBY_1111(1,0,1,0), // Magenta
VGA_RGBY_1111(0,1,0,0), // Green
VGA_RGBY_1111(0,1,1,0), // Cyan
VGA_RGBY_1111(1,1,0,0), // Yellow
VGA_RGBY_1111(1,1,1,0), // White
VGA_RGBY_1111(0,0,0,0), // Bright Black
VGA_RGBY_1111(0,0,1,1), // Bright Blue
VGA_RGBY_1111(1,0,0,1), // Bright Red
VGA_RGBY_1111(1,0,1,1), // Bright Magenta
VGA_RGBY_1111(0,1,0,1), // Bright Green
VGA_RGBY_1111(0,1,1,1), // Bright Cyan
VGA_RGBY_1111(1,1,0,1), // Bright Yellow
VGA_RGBY_1111(1,1,1,1) // Bright White
};This seems to work pretty well, but all the colours are a bit too dark. The bottom bar should be “bright white” for example, and it is a pretty dirty grey really.
I’m also not sure what is going on at the top – it looks like there is a bit of bleed-through of the last colour at the start. I don’t know if this is a sync/coordination issue with scanline number and the processing code or if this is something else.
Looking more closely at what is produced between the two modes, the only difference I could see was that I was creating a single scanline entry for the whole line of pixels, whereas the testcard pattern was splitting each line up in to 32 “bars”.
Rewriting the code to do the same, albeit with the same colour in each “bar” gives me:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num =
scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 8ul) / vga_mode.height;
uint bar_width = vga_mode.width / 32;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
for (uint bar = 0; bar < 32; bar++) {
*p++ = COMPOSABLE_COLOR_RUN ;
if (bar < 16) {
*p++ = zxd_colour_words[col];
} else {
*p++ = zxd_colour_words[col+8];
}
*p++ = bar_width - 3;
}
// 32 * 3, so we should be word aligned
assert(!(3u & (uintptr_t) p));
// black pixel to end line
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
// end of line with alignment padding
*p++ = COMPOSABLE_EOL_SKIP_ALIGN;
*p++ = 0;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}This is so much better! I’m getting proper colours now.
In the first version (as shown above) it isn’t obvious if the brightness is working or not (it is – I used cut-and-paste on the photo to compare the top and the bottom!), so I created a version (given above) to show the bright and non-bright colours side by side. Now we can see the full 15 colours properly.
I have no idea why this makes a difference, but for some reason it does. My initial working theory is that having too long a COLOR_RUN causes too much time to be taken up in the PIO program, as COLOR_RUN is basically implemented as “set the colour values; then wait around for the number of pixels time before getting the next video instruction”, so a large number of pixels means the PIO is stuck in a loop without consuming data from DMA.
I don’t know what effect this will have, but with each scan line split into 32 chunks, there is more data to DMA to the PIO, but the PIO also consumes it quicker.
I don’t know why this would affect the video signal, but maybe it means there are gaps in the GPIO output video signal meaning the voltage gets averaged to a lower value somehow (a bit like how PWM would work)? If that is the case I should be able to see that on a scope…
In the following traces, yellow is the B GPIO pin and blue is the HSYNC. On the left is the correctly functioning 32-block version and on the right is the “draw a whole line in one go” version.
Zooming out a little…
Ok, so now I’ve even less idea what is going on. For the failing “write a whole line in one go” case the B GPIO never seems to let up – it is constantly HIGH. For the working case, it drops back to zero synchronised with each scan line.
Then I realised what is causing that – there is an additional black pixel added for the working case – the 32 blocks is probably irrelevant! So third try:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num = scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 16ul) / vga_mode.height;
uint16_t numpxls = vga_mode.width;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
*p++ = COMPOSABLE_COLOR_RUN ;
*p++ = zxd_colour_words[col];
*p++ = numpxls - 3;
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
*p++ = COMPOSABLE_EOL_ALIGN;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}And this works!! This gives me the nice full-coloured patterns with brightness.
A bit of searching turns up this note on the scanvideo readme that I’d missed:
“Important; You MUST end the scanline with one or more black pixels of your own (otherwise your color will bleed into the blanking!!!).”
Well that explains the problem, but I still don’t really understand what “bleed into the blanking” means and why that results in a much lower intensity colour on the screen. Searching some more has not enlightened me, so if you know why this is the case, do let me know.
Update: Someone sent me a link to this, which explains it: https://www.righto.com/2018/04/#fn:blanking
“When I forgot to blank the pixels outside the valid screen area, the monitor still managed to display an image, but it was very dim because the monitor got confused about what voltage represented “dark”. Just a tip in case you find your display mysteriously darkened.”
So it is something to do with ensuring the monitor can detect what “off” looks like in the signal, which would explain why everything went screwy when it what it thought was “off” was still set to a voltage.
Anyway. It all now works. I have a working, ZX Spectrum compatible video mode implemented on a Raspberry Pi Pico using just 6 GPIO in the end (as I still have both SYNCs).
Kevin
#cga #pio #raspberryPiPico #scanvideo #vga #zxSpectrum -
Simplified Pico VGA – Part 2
Having spent far too long chewing over how the Raspberry Pi Pico Scanvideo library works over in Simplified Pico VGA, this post actually starts to do something with it.
One constraint I have for myself, is that I want to keep using the Arduino IDE for the Pico, so the first step is to see if I can get the Scanvideo library over to an Arduino sketch. This means grabbing the files from the following locations:
- https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_scanvideo
- https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_scanvideo_dpi
- https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_util_buffer
- https://github.com/raspberrypi/pico-playground/tree/master/scanvideo
The pico_util_buffer code is used for buffer management, so that is required too. The last one houses some examples, I’m just using the https://github.com/raspberrypi/pico-playground/tree/master/scanvideo/test_pattern as a basis for my own example.
As with my other PIO experiments, I’m using the unofficial Arduino RP2040 core.
Starting the Arduino Sketch
In order to get things started, I’m just taking all the files from the above projects and dropping them directly into the Arduino Sketch folder, with no further hierarchy. My file list looks as follows:
25/07/2026 19:26 392 buffer.c
25/07/2026 19:25 2,265 buffer.h
25/07/2026 19:20 1,425 composable_scanline.h
25/07/2026 19:27 1,675 PicoScanVideo.ino
25/07/2026 19:49 6,293 scanvideo-pio.h
25/07/2026 19:26 83,925 scanvideo.c
25/07/2026 19:24 1,949 scanvideo.h
25/07/2026 19:16 13,613 scanvideo_base.h
25/07/2026 19:49 2,488 timing-pio.h
25/07/2026 19:20 17,057 vga_modes.cEach one of these files must be edited to change the include paths to drop down to a single directory.
There are three files here that are not in the original libraries. The .ino I’ll come to shortly, but the two *-pio.h files are the assembled files from the two original .pio files. These are created using https://wokwi.com/tools/pioasm and the output is pasted into these two new h files.
Initial Test Code
I’ve created an example based on https://github.com/raspberrypi/pico-playground/tree/master/scanvideo/test_pattern. But I’ve removed the multicore and keyboard input. This will (hopefully) just display the “test card” and nothing else.
#include "scanvideo.h"
#include "composable_scanline.h"
#define vga_mode vga_mode_320x240_60
static bool invert = false;
void draw_color_bar(scanvideo_scanline_buffer_t *buffer) {
// figure out 1/32 of the color value
uint line_num = scanvideo_scanline_number(buffer->scanline_id);
uint32_t primary_color = 1u + (line_num * 7 / vga_mode.height);
uint32_t color_mask = PICO_SCANVIDEO_PIXEL_FROM_RGB5(
0x1f * (primary_color & 1u),
0x1f * ((primary_color >> 1u) & 1u),
0x1f * ((primary_color >> 2u) & 1u));
uint bar_width = vga_mode.width / 32;
uint16_t *p = (uint16_t *) buffer->data;
uint32_t invert_bits = invert ? PICO_SCANVIDEO_PIXEL_FROM_RGB5(0x1f,0x1f,0x1f) : 0;
for (uint bar = 0; bar < 32; bar++) {
*p++ = COMPOSABLE_COLOR_RUN;
uint32_t color = PICO_SCANVIDEO_PIXEL_FROM_RGB5(bar, bar, bar);
*p++ = (color & color_mask) ^ invert_bits;
*p++ = bar_width - 3;
}
// 32 * 3, so we should be word aligned
assert(!(3u & (uintptr_t) p));
// black pixel to end line
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
// end of line with alignment padding
*p++ = COMPOSABLE_EOL_SKIP_ALIGN;
*p++ = 0;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}
void setup() {
// initialize video and interrupts on core 1
scanvideo_setup(&vga_mode);
scanvideo_timing_enable(true);
}
void loop() {
scanvideo_scanline_buffer_t *scanline_buffer =
scanvideo_begin_scanline_generation(true);
draw_color_bar(scanline_buffer);
scanvideo_end_scanline_generation(scanline_buffer);
}This it the same as the provided example apart from the fact that the core1 function has been split into the initialisation code, placed in setup(), and the infinite loop code, now placed in loop().
This requires the circuit from the pico_playground here: https://github.com/raspberrypi/pico-playground/tree/master/scanvideo, to provide a 5-bit (RGB555) VGA interface.
VGA Breakout
As can be seen in the above photo I’m using a breakout board between the Pico and the VGA display. I designed this to let me experiment with VGA and the Pico.
Bill of Materials:
- VGA socket (see photos and PCB for footprint).
- Pin headers.
- Range of resistors and diodes depending on the DAC design.
The photo below shows two configurations. On the left is a RGB555 as detailed in the RP2040 Hardware Design Guide and the pico_playground repository. This uses a 5-resistor DAC comprising: 8K, 4K, 2K, 1K, 500R resistors. Although I only had 4K7 resistors, so as you can see have used two 2K resistors in series. It uses two 47R resistors for HSYNC and VSYNC.
On the right is the 330R/100R and diode configuration mentioned in Part 1 for a RGBY1111 configuration. This uses two 100R resistors for HSYNC and VSYNC (I don’t know why they are different to the RGB555 case).
There are suggested resistor values for different options printed on the board, although there is one mistake. The RGBY1111 suggests 300R+100R in the text, whereas 330R+100R against the components. 330R is the recommended value.
To wire up the board for the default RGB555 configuration with a Pico uses the following resistor to GPIO mapping:
GP08KRed MSBitGP14KRedGP22KRedGP31KRedGP4500RRed LSBitGP5N/CN/CGP68KGreen MSBitGP74KGreenGP82KGreenGP91KGreenGP10500RGreen LSBitGP118KBlue MSBitGP124KBlueGP132KBlueGP141KBlueGP15500RBlue LSBitGP1647RHSYNCGP1747RVSYNCVGA to CGA
The reason for doing all this is to get to that simpler CGA-like mode that mirrors the ZX Spectrum video capability. To do this I need to get the scanvideo library implementing RGBY1111 as mentioned in Part 1.
The scanvideo library assumes the use of compiler directives to set the video modes, but these can’t just be set prior to including the appropriate header files in my .ino file as they need to actually be compiled into the library C files too.
To do this, I created a vgamode.h file which can be included once in scanvideo.h which is included everywhere within the library.
vgamode.h:
#define VGA_RGBY111
#define PICO_SCANVIDEO_COLOR_PIN_COUNT 4u
#define PICO_SCANVIDEO_DPI_PIXEL_RCOUNT 2u
#define PICO_SCANVIDEO_DPI_PIXEL_GCOUNT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_BCOUNT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_RSHIFT 2u
#define PICO_SCANVIDEO_DPI_PIXEL_GSHIFT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_BSHIFT 0u
#define PICO_SCANVIDEO_COLOR_PIN_BASE 12uAs described in Part 1 this sets the hardware interface up for 4 GPIO pins for RGB and Y (as a second R) and sets the GPIO base pin to 12. The SYNC pins are automatically set elsewhere to be:
#define PICO_SCANVIDEO_SYNC_PIN_BASE (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_COLOR_PIN_COUNT)
This creates the following GPIO mapping and resistor usage:
GP12330R+100RBlueGP13330R+100RGreenGP14330R+100RRedGP15Diodes + 330R x 3Second Red (Y)GP16100RHSYNCGP17100RVSYNCThis also now requires an alternative scanline generator, so I’ve implemented the following:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num =
scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 16ul) / vga_mode.height;
uint16_t num_pxls = vga_mode.width;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
*p++ = COMPOSABLE_COLOR_RUN ;
*p++ = zxd_colour_words[col];
*p++ = num_pxls - 3;
*p++ = COMPOSABLE_EOL_ALIGN;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}This creates a “COLOR_RUN” line of a single colour using the mapping in zxd_colour_words[] which has been taken from the pico_zxspectrum code, but rather than providing 32-bit (duplicated) values (as described last time) I’m just using direct, single, 16-bit values:
#define VGA_RGBY_1111(r,g,b,y) ((y<<3)|(r<<2)|(g<<1)|b)
static uint16_t zxd_colour_words[16] = {
VGA_RGBY_1111(0,0,0,0), // Black
VGA_RGBY_1111(0,0,1,0), // Blue
VGA_RGBY_1111(1,0,0,0), // Red
VGA_RGBY_1111(1,0,1,0), // Magenta
VGA_RGBY_1111(0,1,0,0), // Green
VGA_RGBY_1111(0,1,1,0), // Cyan
VGA_RGBY_1111(1,1,0,0), // Yellow
VGA_RGBY_1111(1,1,1,0), // White
VGA_RGBY_1111(0,0,0,0), // Bright Black
VGA_RGBY_1111(0,0,1,1), // Bright Blue
VGA_RGBY_1111(1,0,0,1), // Bright Red
VGA_RGBY_1111(1,0,1,1), // Bright Magenta
VGA_RGBY_1111(0,1,0,1), // Bright Green
VGA_RGBY_1111(0,1,1,1), // Bright Cyan
VGA_RGBY_1111(1,1,0,1), // Bright Yellow
VGA_RGBY_1111(1,1,1,1) // Bright White
};This seems to work pretty well, but all the colours are a bit too dark. The bottom bar should be “bright white” for example, and it is a pretty dirty grey really.
I’m also not sure what is going on at the top – it looks like there is a bit of bleed-through of the last colour at the start. I don’t know if this is a sync/coordination issue with scanline number and the processing code or if this is something else.
Looking more closely at what is produced between the two modes, the only difference I could see was that I was creating a single scanline entry for the whole line of pixels, whereas the testcard pattern was splitting each line up in to 32 “bars”.
Rewriting the code to do the same, albeit with the same colour in each “bar” gives me:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num =
scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 8ul) / vga_mode.height;
uint bar_width = vga_mode.width / 32;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
for (uint bar = 0; bar < 32; bar++) {
*p++ = COMPOSABLE_COLOR_RUN ;
if (bar < 16) {
*p++ = zxd_colour_words[col];
} else {
*p++ = zxd_colour_words[col+8];
}
*p++ = bar_width - 3;
}
// 32 * 3, so we should be word aligned
assert(!(3u & (uintptr_t) p));
// black pixel to end line
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
// end of line with alignment padding
*p++ = COMPOSABLE_EOL_SKIP_ALIGN;
*p++ = 0;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}This is so much better! I’m getting proper colours now.
In the first version (as shown above) it isn’t obvious if the brightness is working or not (it is – I used cut-and-paste on the photo to compare the top and the bottom!), so I created a version (given above) to show the bright and non-bright colours side by side. Now we can see the full 15 colours properly.
I have no idea why this makes a difference, but for some reason it does. My initial working theory is that having too long a COLOR_RUN causes too much time to be taken up in the PIO program, as COLOR_RUN is basically implemented as “set the colour values; then wait around for the number of pixels time before getting the next video instruction”, so a large number of pixels means the PIO is stuck in a loop without consuming data from DMA.
I don’t know what effect this will have, but with each scan line split into 32 chunks, there is more data to DMA to the PIO, but the PIO also consumes it quicker.
I don’t know why this would affect the video signal, but maybe it means there are gaps in the GPIO output video signal meaning the voltage gets averaged to a lower value somehow (a bit like how PWM would work)? If that is the case I should be able to see that on a scope…
In the following traces, yellow is the B GPIO pin and blue is the HSYNC. On the left is the correctly functioning 32-block version and on the right is the “draw a whole line in one go” version.
Zooming out a little…
Ok, so now I’ve even less idea what is going on. For the failing “write a whole line in one go” case the B GPIO never seems to let up – it is constantly HIGH. For the working case, it drops back to zero synchronised with each scan line.
Then I realised what is causing that – there is an additional black pixel added for the working case – the 32 blocks is probably irrelevant! So third try:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num = scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 16ul) / vga_mode.height;
uint16_t numpxls = vga_mode.width;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
*p++ = COMPOSABLE_COLOR_RUN ;
*p++ = zxd_colour_words[col];
*p++ = numpxls - 3;
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
*p++ = COMPOSABLE_EOL_ALIGN;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}And this works!! This gives me the nice full-coloured patterns with brightness.
A bit of searching turns up this note on the scanvideo readme that I’d missed:
“Important; You MUST end the scanline with one or more black pixels of your own (otherwise your color will bleed into the blanking!!!).”
Well that explains the problem, but I still don’t really understand what “bleed into the blanking” means and why that results in a much lower intensity colour on the screen. Searching some more has not enlightened me, so if you know why this is the case, do let me know.
Update: Someone sent me a link to this, which explains it: https://www.righto.com/2018/04/#fn:blanking
“When I forgot to blank the pixels outside the valid screen area, the monitor still managed to display an image, but it was very dim because the monitor got confused about what voltage represented “dark”. Just a tip in case you find your display mysteriously darkened.”
So it is something to do with ensuring the monitor can detect what “off” looks like in the signal, which would explain why everything went screwy when it what it thought was “off” was still set to a voltage.
Anyway. It all now works. I have a working, ZX Spectrum compatible video mode implemented on a Raspberry Pi Pico using just 6 GPIO in the end (as I still have both SYNCs).
Kevin
#cga #pio #raspberryPiPico #scanvideo #vga #zxSpectrum -
Simplified Pico VGA – Part 2
Having spent far too long chewing over how the Raspberry Pi Pico Scanvideo library works over in Simplified Pico VGA, this post actually starts to do something with it.
One constraint I have for myself, is that I want to keep using the Arduino IDE for the Pico, so the first step is to see if I can get the Scanvideo library over to an Arduino sketch. This means grabbing the files from the following locations:
- https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_scanvideo
- https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_scanvideo_dpi
- https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_util_buffer
- https://github.com/raspberrypi/pico-playground/tree/master/scanvideo
The pico_util_buffer code is used for buffer management, so that is required too. The last one houses some examples, I’m just using the https://github.com/raspberrypi/pico-playground/tree/master/scanvideo/test_pattern as a basis for my own example.
As with my other PIO experiments, I’m using the unofficial Arduino RP2040 core.
Starting the Arduino Sketch
In order to get things started, I’m just taking all the files from the above projects and dropping them directly into the Arduino Sketch folder, with no further hierarchy. My file list looks as follows:
25/07/2026 19:26 392 buffer.c
25/07/2026 19:25 2,265 buffer.h
25/07/2026 19:20 1,425 composable_scanline.h
25/07/2026 19:27 1,675 PicoScanVideo.ino
25/07/2026 19:49 6,293 scanvideo-pio.h
25/07/2026 19:26 83,925 scanvideo.c
25/07/2026 19:24 1,949 scanvideo.h
25/07/2026 19:16 13,613 scanvideo_base.h
25/07/2026 19:49 2,488 timing-pio.h
25/07/2026 19:20 17,057 vga_modes.cEach one of these files must be edited to change the include paths to drop down to a single directory.
There are three files here that are not in the original libraries. The .ino I’ll come to shortly, but the two *-pio.h files are the assembled files from the two original .pio files. These are created using https://wokwi.com/tools/pioasm and the output is pasted into these two new h files.
Initial Test Code
I’ve created an example based on https://github.com/raspberrypi/pico-playground/tree/master/scanvideo/test_pattern. But I’ve removed the multicore and keyboard input. This will (hopefully) just display the “test card” and nothing else.
#include "scanvideo.h"
#include "composable_scanline.h"
#define vga_mode vga_mode_320x240_60
static bool invert = false;
void draw_color_bar(scanvideo_scanline_buffer_t *buffer) {
// figure out 1/32 of the color value
uint line_num = scanvideo_scanline_number(buffer->scanline_id);
uint32_t primary_color = 1u + (line_num * 7 / vga_mode.height);
uint32_t color_mask = PICO_SCANVIDEO_PIXEL_FROM_RGB5(
0x1f * (primary_color & 1u),
0x1f * ((primary_color >> 1u) & 1u),
0x1f * ((primary_color >> 2u) & 1u));
uint bar_width = vga_mode.width / 32;
uint16_t *p = (uint16_t *) buffer->data;
uint32_t invert_bits = invert ? PICO_SCANVIDEO_PIXEL_FROM_RGB5(0x1f,0x1f,0x1f) : 0;
for (uint bar = 0; bar < 32; bar++) {
*p++ = COMPOSABLE_COLOR_RUN;
uint32_t color = PICO_SCANVIDEO_PIXEL_FROM_RGB5(bar, bar, bar);
*p++ = (color & color_mask) ^ invert_bits;
*p++ = bar_width - 3;
}
// 32 * 3, so we should be word aligned
assert(!(3u & (uintptr_t) p));
// black pixel to end line
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
// end of line with alignment padding
*p++ = COMPOSABLE_EOL_SKIP_ALIGN;
*p++ = 0;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}
void setup() {
// initialize video and interrupts on core 1
scanvideo_setup(&vga_mode);
scanvideo_timing_enable(true);
}
void loop() {
scanvideo_scanline_buffer_t *scanline_buffer =
scanvideo_begin_scanline_generation(true);
draw_color_bar(scanline_buffer);
scanvideo_end_scanline_generation(scanline_buffer);
}This it the same as the provided example apart from the fact that the core1 function has been split into the initialisation code, placed in setup(), and the infinite loop code, now placed in loop().
This requires the circuit from the pico_playground here: https://github.com/raspberrypi/pico-playground/tree/master/scanvideo, to provide a 5-bit (RGB555) VGA interface.
VGA Breakout
As can be seen in the above photo I’m using a breakout board between the Pico and the VGA display. I designed this to let me experiment with VGA and the Pico.
Bill of Materials:
- VGA socket (see photos and PCB for footprint).
- Pin headers.
- Range of resistors and diodes depending on the DAC design.
The photo below shows two configurations. On the left is a RGB555 as detailed in the RP2040 Hardware Design Guide and the pico_playground repository. This uses a 5-resistor DAC comprising: 8K, 4K, 2K, 1K, 500R resistors. Although I only had 4K7 resistors, so as you can see have used two 2K resistors in series. It uses two 47R resistors for HSYNC and VSYNC.
On the right is the 330R/100R and diode configuration mentioned in Part 1 for a RGBY1111 configuration. This uses two 100R resistors for HSYNC and VSYNC (I don’t know why they are different to the RGB555 case).
There are suggested resistor values for different options printed on the board, although there is one mistake. The RGBY1111 suggests 300R+100R in the text, whereas 330R+100R against the components. 330R is the recommended value.
To wire up the board for the default RGB555 configuration with a Pico uses the following resistor to GPIO mapping:
GP08KRed MSBitGP14KRedGP22KRedGP31KRedGP4500RRed LSBitGP5N/CN/CGP68KGreen MSBitGP74KGreenGP82KGreenGP91KGreenGP10500RGreen LSBitGP118KBlue MSBitGP124KBlueGP132KBlueGP141KBlueGP15500RBlue LSBitGP1647RHSYNCGP1747RVSYNCVGA to CGA
The reason for doing all this is to get to that simpler CGA-like mode that mirrors the ZX Spectrum video capability. To do this I need to get the scanvideo library implementing RGBY1111 as mentioned in Part 1.
The scanvideo library assumes the use of compiler directives to set the video modes, but these can’t just be set prior to including the appropriate header files in my .ino file as they need to actually be compiled into the library C files too.
To do this, I created a vgamode.h file which can be included once in scanvideo.h which is included everywhere within the library.
vgamode.h:
#define VGA_RGBY111
#define PICO_SCANVIDEO_COLOR_PIN_COUNT 4u
#define PICO_SCANVIDEO_DPI_PIXEL_RCOUNT 2u
#define PICO_SCANVIDEO_DPI_PIXEL_GCOUNT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_BCOUNT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_RSHIFT 2u
#define PICO_SCANVIDEO_DPI_PIXEL_GSHIFT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_BSHIFT 0u
#define PICO_SCANVIDEO_COLOR_PIN_BASE 12uAs described in Part 1 this sets the hardware interface up for 4 GPIO pins for RGB and Y (as a second R) and sets the GPIO base pin to 12. The SYNC pins are automatically set elsewhere to be:
#define PICO_SCANVIDEO_SYNC_PIN_BASE (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_COLOR_PIN_COUNT)
This creates the following GPIO mapping and resistor usage:
GP12330R+100RBlueGP13330R+100RGreenGP14330R+100RRedGP15Diodes + 330R x 3Second Red (Y)GP16100RHSYNCGP17100RVSYNCThis also now requires an alternative scanline generator, so I’ve implemented the following:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num =
scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 16ul) / vga_mode.height;
uint16_t num_pxls = vga_mode.width;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
*p++ = COMPOSABLE_COLOR_RUN ;
*p++ = zxd_colour_words[col];
*p++ = num_pxls - 3;
*p++ = COMPOSABLE_EOL_ALIGN;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}This creates a “COLOR_RUN” line of a single colour using the mapping in zxd_colour_words[] which has been taken from the pico_zxspectrum code, but rather than providing 32-bit (duplicated) values (as described last time) I’m just using direct, single, 16-bit values:
#define VGA_RGBY_1111(r,g,b,y) ((y<<3)|(r<<2)|(g<<1)|b)
static uint16_t zxd_colour_words[16] = {
VGA_RGBY_1111(0,0,0,0), // Black
VGA_RGBY_1111(0,0,1,0), // Blue
VGA_RGBY_1111(1,0,0,0), // Red
VGA_RGBY_1111(1,0,1,0), // Magenta
VGA_RGBY_1111(0,1,0,0), // Green
VGA_RGBY_1111(0,1,1,0), // Cyan
VGA_RGBY_1111(1,1,0,0), // Yellow
VGA_RGBY_1111(1,1,1,0), // White
VGA_RGBY_1111(0,0,0,0), // Bright Black
VGA_RGBY_1111(0,0,1,1), // Bright Blue
VGA_RGBY_1111(1,0,0,1), // Bright Red
VGA_RGBY_1111(1,0,1,1), // Bright Magenta
VGA_RGBY_1111(0,1,0,1), // Bright Green
VGA_RGBY_1111(0,1,1,1), // Bright Cyan
VGA_RGBY_1111(1,1,0,1), // Bright Yellow
VGA_RGBY_1111(1,1,1,1) // Bright White
};This seems to work pretty well, but all the colours are a bit too dark. The bottom bar should be “bright white” for example, and it is a pretty dirty grey really.
I’m also not sure what is going on at the top – it looks like there is a bit of bleed-through of the last colour at the start. I don’t know if this is a sync/coordination issue with scanline number and the processing code or if this is something else.
Looking more closely at what is produced between the two modes, the only difference I could see was that I was creating a single scanline entry for the whole line of pixels, whereas the testcard pattern was splitting each line up in to 32 “bars”.
Rewriting the code to do the same, albeit with the same colour in each “bar” gives me:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num =
scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 8ul) / vga_mode.height;
uint bar_width = vga_mode.width / 32;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
for (uint bar = 0; bar < 32; bar++) {
*p++ = COMPOSABLE_COLOR_RUN ;
if (bar < 16) {
*p++ = zxd_colour_words[col];
} else {
*p++ = zxd_colour_words[col+8];
}
*p++ = bar_width - 3;
}
// 32 * 3, so we should be word aligned
assert(!(3u & (uintptr_t) p));
// black pixel to end line
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
// end of line with alignment padding
*p++ = COMPOSABLE_EOL_SKIP_ALIGN;
*p++ = 0;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}This is so much better! I’m getting proper colours now.
In the first version (as shown above) it isn’t obvious if the brightness is working or not (it is – I used cut-and-paste on the photo to compare the top and the bottom!), so I created a version (given above) to show the bright and non-bright colours side by side. Now we can see the full 15 colours properly.
I have no idea why this makes a difference, but for some reason it does. My initial working theory is that having too long a COLOR_RUN causes too much time to be taken up in the PIO program, as COLOR_RUN is basically implemented as “set the colour values; then wait around for the number of pixels time before getting the next video instruction”, so a large number of pixels means the PIO is stuck in a loop without consuming data from DMA.
I don’t know what effect this will have, but with each scan line split into 32 chunks, there is more data to DMA to the PIO, but the PIO also consumes it quicker.
I don’t know why this would affect the video signal, but maybe it means there are gaps in the GPIO output video signal meaning the voltage gets averaged to a lower value somehow (a bit like how PWM would work)? If that is the case I should be able to see that on a scope…
In the following traces, yellow is the B GPIO pin and blue is the HSYNC. On the left is the correctly functioning 32-block version and on the right is the “draw a whole line in one go” version.
Zooming out a little…
Ok, so now I’ve even less idea what is going on. For the failing “write a whole line in one go” case the B GPIO never seems to let up – it is constantly HIGH. For the working case, it drops back to zero synchronised with each scan line.
Then I realised what is causing that – there is an additional black pixel added for the working case – the 32 blocks is probably irrelevant! So third try:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num = scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 16ul) / vga_mode.height;
uint16_t numpxls = vga_mode.width;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
*p++ = COMPOSABLE_COLOR_RUN ;
*p++ = zxd_colour_words[col];
*p++ = numpxls - 3;
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
*p++ = COMPOSABLE_EOL_ALIGN;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}And this works!! This gives me the nice full-coloured patterns with brightness.
A bit of searching turns up this note on the scanvideo readme that I’d missed:
“Important; You MUST end the scanline with one or more black pixels of your own (otherwise your color will bleed into the blanking!!!).”
Well that explains the problem, but I still don’t really understand what “bleed into the blanking” means and why that results in a much lower intensity colour on the screen. Searching some more has not enlightened me, so if you know why this is the case, do let me know.
Update: Someone sent me a link to this, which explains it: https://www.righto.com/2018/04/#fn:blanking
“When I forgot to blank the pixels outside the valid screen area, the monitor still managed to display an image, but it was very dim because the monitor got confused about what voltage represented “dark”. Just a tip in case you find your display mysteriously darkened.”
So it is something to do with ensuring the monitor can detect what “off” looks like in the signal, which would explain why everything went screwy when it what it thought was “off” was still set to a voltage.
Anyway. It all now works. I have a working, ZX Spectrum compatible video mode implemented on a Raspberry Pi Pico using just 6 GPIO in the end (as I still have both SYNCs).
Kevin
#cga #pio #raspberryPiPico #scanvideo #vga #zxSpectrum -
Simplified Pico VGA – Part 2
Having spent far too long chewing over how the Raspberry Pi Pico Scanvideo library works over in Simplified Pico VGA, this post actually starts to do something with it.
One constraint I have for myself, is that I want to keep using the Arduino IDE for the Pico, so the first step is to see if I can get the Scanvideo library over to an Arduino sketch. This means grabbing the files from the following locations:
- https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_scanvideo
- https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_scanvideo_dpi
- https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_util_buffer
- https://github.com/raspberrypi/pico-playground/tree/master/scanvideo
The pico_util_buffer code is used for buffer management, so that is required too. The last one houses some examples, I’m just using the https://github.com/raspberrypi/pico-playground/tree/master/scanvideo/test_pattern as a basis for my own example.
As with my other PIO experiments, I’m using the unofficial Arduino RP2040 core.
Starting the Arduino Sketch
In order to get things started, I’m just taking all the files from the above projects and dropping them directly into the Arduino Sketch folder, with no further hierarchy. My file list looks as follows:
25/07/2026 19:26 392 buffer.c
25/07/2026 19:25 2,265 buffer.h
25/07/2026 19:20 1,425 composable_scanline.h
25/07/2026 19:27 1,675 PicoScanVideo.ino
25/07/2026 19:49 6,293 scanvideo-pio.h
25/07/2026 19:26 83,925 scanvideo.c
25/07/2026 19:24 1,949 scanvideo.h
25/07/2026 19:16 13,613 scanvideo_base.h
25/07/2026 19:49 2,488 timing-pio.h
25/07/2026 19:20 17,057 vga_modes.cEach one of these files must be edited to change the include paths to drop down to a single directory.
There are three files here that are not in the original libraries. The .ino I’ll come to shortly, but the two *-pio.h files are the assembled files from the two original .pio files. These are created using https://wokwi.com/tools/pioasm and the output is pasted into these two new h files.
Initial Test Code
I’ve created an example based on https://github.com/raspberrypi/pico-playground/tree/master/scanvideo/test_pattern. But I’ve removed the multicore and keyboard input. This will (hopefully) just display the “test card” and nothing else.
#include "scanvideo.h"
#include "composable_scanline.h"
#define vga_mode vga_mode_320x240_60
static bool invert = false;
void draw_color_bar(scanvideo_scanline_buffer_t *buffer) {
// figure out 1/32 of the color value
uint line_num = scanvideo_scanline_number(buffer->scanline_id);
uint32_t primary_color = 1u + (line_num * 7 / vga_mode.height);
uint32_t color_mask = PICO_SCANVIDEO_PIXEL_FROM_RGB5(
0x1f * (primary_color & 1u),
0x1f * ((primary_color >> 1u) & 1u),
0x1f * ((primary_color >> 2u) & 1u));
uint bar_width = vga_mode.width / 32;
uint16_t *p = (uint16_t *) buffer->data;
uint32_t invert_bits = invert ? PICO_SCANVIDEO_PIXEL_FROM_RGB5(0x1f,0x1f,0x1f) : 0;
for (uint bar = 0; bar < 32; bar++) {
*p++ = COMPOSABLE_COLOR_RUN;
uint32_t color = PICO_SCANVIDEO_PIXEL_FROM_RGB5(bar, bar, bar);
*p++ = (color & color_mask) ^ invert_bits;
*p++ = bar_width - 3;
}
// 32 * 3, so we should be word aligned
assert(!(3u & (uintptr_t) p));
// black pixel to end line
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
// end of line with alignment padding
*p++ = COMPOSABLE_EOL_SKIP_ALIGN;
*p++ = 0;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}
void setup() {
// initialize video and interrupts on core 1
scanvideo_setup(&vga_mode);
scanvideo_timing_enable(true);
}
void loop() {
scanvideo_scanline_buffer_t *scanline_buffer =
scanvideo_begin_scanline_generation(true);
draw_color_bar(scanline_buffer);
scanvideo_end_scanline_generation(scanline_buffer);
}This it the same as the provided example apart from the fact that the core1 function has been split into the initialisation code, placed in setup(), and the infinite loop code, now placed in loop().
This requires the circuit from the pico_playground here: https://github.com/raspberrypi/pico-playground/tree/master/scanvideo, to provide a 5-bit (RGB555) VGA interface.
VGA Breakout
As can be seen in the above photo I’m using a breakout board between the Pico and the VGA display. I designed this to let me experiment with VGA and the Pico.
Bill of Materials:
- VGA socket (see photos and PCB for footprint).
- Pin headers.
- Range of resistors and diodes depending on the DAC design.
GitHub link: https://github.com/diyelectromusic/sdemp_pcbs/tree/main/PicoVGABreakout
The photo below shows two configurations. On the left is a RGB555 as detailed in the RP2040 Hardware Design Guide and the pico_playground repository. This uses a 5-resistor DAC comprising: 8K, 4K, 2K, 1K, 500R resistors. Although I only had 4K7 resistors, so as you can see have used two 2K resistors in series. It uses two 47R resistors for HSYNC and VSYNC.
On the right is the 330R/100R and diode configuration mentioned in Part 1 for a RGBY1111 configuration. This uses two 100R resistors for HSYNC and VSYNC (I don’t know why they are different to the RGB555 case).
There are suggested resistor values for different options printed on the board, although there is one mistake. The RGBY1111 suggests 300R+100R in the text, whereas 330R+100R against the components. 330R is the recommended value.
To wire up the board for the default RGB555 configuration with a Pico uses the resistor and colour to GPIO mapping from the Raspberry Pi library example (schematic here):
GP08KRed MSBitGP14KRedGP22KRedGP31KRedGP4500RRed LSBitGP5N/CN/CGP68KGreen MSBitGP74KGreenGP82KGreenGP91KGreenGP10500RGreen LSBitGP118KBlue MSBitGP124KBlueGP132KBlueGP141KBlueGP15500RBlue LSBitGP1647RHSYNCGP1747RVSYNCNote that the order here is the opposite to many of the other modes used in the pico_zxspectrum. The RPi scanvideo example has the sequence:
- GPIO 0 -> GPIO 15 = R5 G5 B5
Many of the other modes use BGR ordering (i.e. B MSbit is the lowest GPIO used) as can be seen in the following definitions (from here):
#define VGA_RGB_555(r,g,b) ((r##UL<<0)|(g##UL<<6)|(b##UL << 11))
#define VGA_BGYR_1111(r,g,b,y) ((y##UL<<2)|(r##UL<<3)|(g##UL<<1)|b##UL)
#define VGA_RGBY_1111(r,g,b,y) ((y##UL<<3)|(r##UL<<2)|(g##UL<<1)|b##UL)
#define VGA_RGB_332(r,g,b) ((r##UL<<5)|(g##UL<<2)|b##UL)
#define VGA_RGB_222(r,g,b) ((r##UL<<4)|(g##UL<<2)|b##UL)For completeness, RGB222 would be implemented using the breakout as follows:
GP01KBlue MSBitGP1470RBlue LSBitGP21KGreen MSBitGP3470RGreen LSBitGP41KRed MSBitGP5470RRed LSBitGP6100RHSYNCGP7100RVSYNCVGA to CGA
The reason for doing all this is to get to that simpler CGA-like mode that mirrors the ZX Spectrum video capability. To do this I need to get the scanvideo library implementing RGBY1111 as mentioned in Part 1.
The scanvideo library assumes the use of compiler directives to set the video modes, but these can’t just be set prior to including the appropriate header files in my .ino file as they need to actually be compiled into the library C files too.
To do this, I created a vgamode.h file which can be included once in scanvideo.h which is included everywhere within the library.
vgamode.h:
#define VGA_RGBY111
#define PICO_SCANVIDEO_COLOR_PIN_COUNT 4u
#define PICO_SCANVIDEO_DPI_PIXEL_RCOUNT 2u
#define PICO_SCANVIDEO_DPI_PIXEL_GCOUNT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_BCOUNT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_RSHIFT 2u
#define PICO_SCANVIDEO_DPI_PIXEL_GSHIFT 1u
#define PICO_SCANVIDEO_DPI_PIXEL_BSHIFT 0u
#define PICO_SCANVIDEO_COLOR_PIN_BASE 12uAs described in Part 1 this sets the hardware interface up for 4 GPIO pins for RGB and Y (as a second R) and sets the GPIO base pin to 12. The SYNC pins are automatically set elsewhere to be:
#define PICO_SCANVIDEO_SYNC_PIN_BASE (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_COLOR_PIN_COUNT)
This creates the following GPIO mapping and resistor usage:
GP12330R+100RBlueGP13330R+100RGreenGP14330R+100RRedGP15Diodes + 330R x 3Second Red (Y)GP16100RHSYNCGP17100RVSYNCThis also now requires an alternative scanline generator, so I’ve implemented the following:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num =
scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 16ul) / vga_mode.height;
uint16_t num_pxls = vga_mode.width;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
*p++ = COMPOSABLE_COLOR_RUN ;
*p++ = zxd_colour_words[col];
*p++ = num_pxls - 3;
*p++ = COMPOSABLE_EOL_ALIGN;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}This creates a “COLOR_RUN” line of a single colour using the mapping in zxd_colour_words[] which has been taken from the pico_zxspectrum code, but rather than providing 32-bit (duplicated) values (as described last time) I’m just using direct, single, 16-bit values:
#define VGA_RGBY_1111(r,g,b,y) ((y<<3)|(r<<2)|(g<<1)|b)
static uint16_t zxd_colour_words[16] = {
VGA_RGBY_1111(0,0,0,0), // Black
VGA_RGBY_1111(0,0,1,0), // Blue
VGA_RGBY_1111(1,0,0,0), // Red
VGA_RGBY_1111(1,0,1,0), // Magenta
VGA_RGBY_1111(0,1,0,0), // Green
VGA_RGBY_1111(0,1,1,0), // Cyan
VGA_RGBY_1111(1,1,0,0), // Yellow
VGA_RGBY_1111(1,1,1,0), // White
VGA_RGBY_1111(0,0,0,0), // Bright Black
VGA_RGBY_1111(0,0,1,1), // Bright Blue
VGA_RGBY_1111(1,0,0,1), // Bright Red
VGA_RGBY_1111(1,0,1,1), // Bright Magenta
VGA_RGBY_1111(0,1,0,1), // Bright Green
VGA_RGBY_1111(0,1,1,1), // Bright Cyan
VGA_RGBY_1111(1,1,0,1), // Bright Yellow
VGA_RGBY_1111(1,1,1,1) // Bright White
};This seems to work pretty well, but all the colours are a bit too dark. The bottom bar should be “bright white” for example, and it is a pretty dirty grey really.
I’m also not sure what is going on at the top – it looks like there is a bit of bleed-through of the last colour at the start. I don’t know if this is a sync/coordination issue with scanline number and the processing code or if this is something else.
Looking more closely at what is produced between the two modes, the only difference I could see was that I was creating a single scanline entry for the whole line of pixels, whereas the testcard pattern was splitting each line up in to 32 “bars”.
Rewriting the code to do the same, albeit with the same colour in each “bar” gives me:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num =
scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 8ul) / vga_mode.height;
uint bar_width = vga_mode.width / 32;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
for (uint bar = 0; bar < 32; bar++) {
*p++ = COMPOSABLE_COLOR_RUN ;
if (bar < 16) {
*p++ = zxd_colour_words[col];
} else {
*p++ = zxd_colour_words[col+8];
}
*p++ = bar_width - 3;
}
// 32 * 3, so we should be word aligned
assert(!(3u & (uintptr_t) p));
// black pixel to end line
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
// end of line with alignment padding
*p++ = COMPOSABLE_EOL_SKIP_ALIGN;
*p++ = 0;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}This is so much better! I’m getting proper colours now.
In the first version (as shown above) it isn’t obvious if the brightness is working or not (it is – I used cut-and-paste on the photo to compare the top and the bottom!), so I created a version (given above) to show the bright and non-bright colours side by side. Now we can see the full 15 colours properly.
I have no idea why this makes a difference, but for some reason it does. My initial working theory is that having too long a COLOR_RUN causes too much time to be taken up in the PIO program, as COLOR_RUN is basically implemented as “set the colour values; then wait around for the number of pixels time before getting the next video instruction”, so a large number of pixels means the PIO is stuck in a loop without consuming data from DMA.
I don’t know what effect this will have, but with each scan line split into 32 chunks, there is more data to DMA to the PIO, but the PIO also consumes it quicker.
I don’t know why this would affect the video signal, but maybe it means there are gaps in the GPIO output video signal meaning the voltage gets averaged to a lower value somehow (a bit like how PWM would work)? If that is the case I should be able to see that on a scope…
In the following traces, yellow is the B GPIO pin and blue is the HSYNC. On the left is the correctly functioning 32-block version and on the right is the “draw a whole line in one go” version.
Zooming out a little…
Ok, so now I’ve even less idea what is going on. For the failing “write a whole line in one go” case the B GPIO never seems to let up – it is constantly HIGH. For the working case, it drops back to zero synchronised with each scan line.
Then I realised what is causing that – there is an additional black pixel added for the working case – the 32 blocks is probably irrelevant! So third try:
void draw_zxcolor_bar(scanvideo_scanline_buffer_t *buffer) {
uint32_t line_num = scanvideo_scanline_number(buffer->scanline_id);
uint32_t col = (line_num * 16ul) / vga_mode.height;
uint16_t numpxls = vga_mode.width;
uint16_t *p = (uint16_t *) buffer->data;
// | jmp color_run | color | count-3 |
*p++ = COMPOSABLE_COLOR_RUN ;
*p++ = zxd_colour_words[col];
*p++ = numpxls - 3;
*p++ = COMPOSABLE_RAW_1P;
*p++ = 0;
*p++ = COMPOSABLE_EOL_ALIGN;
buffer->data_used = ((uint32_t *) p) - buffer->data;
assert(buffer->data_used < buffer->data_max);
buffer->status = SCANLINE_OK;
}And this works!! This gives me the nice full-coloured patterns with brightness.
A bit of searching turns up this note on the scanvideo readme that I’d missed:
“Important; You MUST end the scanline with one or more black pixels of your own (otherwise your color will bleed into the blanking!!!).”
Well that explains the problem, but I still don’t really understand what “bleed into the blanking” means and why that results in a much lower intensity colour on the screen. Searching some more has not enlightened me, so if you know why this is the case, do let me know.
Update: Someone sent me a link to this, which explains it: https://www.righto.com/2018/04/#fn:blanking
“When I forgot to blank the pixels outside the valid screen area, the monitor still managed to display an image, but it was very dim because the monitor got confused about what voltage represented “dark”. Just a tip in case you find your display mysteriously darkened.”
So it is something to do with ensuring the monitor can detect what “off” looks like in the signal, which would explain why everything went screwy when it what it thought was “off” was still set to a voltage.
Anyway. It all now works. I have a working, ZX Spectrum compatible video mode implemented on a Raspberry Pi Pico using just 6 GPIO in the end (as I still have both SYNCs).
Kevin
#cga #pio #raspberryPiPico #scanvideo #vga #zxSpectrum -
Meta lance une nouvelle app et un selfie vidéo anti-arnaques
https://mac4ever.com/197299
#Mac4Ever #Meta #ScanVideo #Seller -
Meta lance une nouvelle app et un selfie vidéo anti-arnaques
https://mac4ever.com/197299
#Mac4Ever #Meta #ScanVideo #Seller -
Meta lance une nouvelle app et un selfie vidéo anti-arnaques
https://mac4ever.com/197299
#Mac4Ever #Meta #ScanVideo #Seller -
Meta lance une nouvelle app et un selfie vidéo anti-arnaques
https://mac4ever.com/197299
#Mac4Ever #Meta #ScanVideo #Seller -
Meta lance une nouvelle app et un selfie vidéo anti-arnaques
https://mac4ever.com/197299
#Mac4Ever #Meta #ScanVideo #Seller