home.social

#cga — Public Fediverse posts

Live and recent posts from across the Fediverse tagged #cga, aggregated by home.social.

fetched live
  1. Even after all this time, I'm still impressed how much the decision to replace dark yellow with an orangey brown in the standard EGA palette improved the overall quality of said palette. Most 16 colour palettes at that time had just the same eight colours twice, once at half and once at full brightness, resulting from the fact that early digital colour monitors had just one bit per colour channel and an additional one for brightness. EGA allowed for two bits per colour channel, resulting in a total of 64 colours, but since it still only used four bits per pixel, you always had a limited palette of 16 simultaneously active colours out of those 64. The standard EGA palette was almost identical to that earlier 16 colours from the CGA era (although CGA could only display all of the colours simultaneously in text mode or in a special undocumented very low resolution 160x100 pixel mode), but that simple switch from dark yellow to brown made the palette much more useful for pixel art.

    #ega #cga #retrocomputing #pixelart

  2. A brand new #cga game for #msdos has arrived! Not from me, although it does use CGALIB. Penult is a lovely RPG that previously appeared on the Atari 2600. karl-g.itch.io/dospenult

  3. Frog & Cat is a new #msdos #cga game for 2026, by Wesley Games. Lovely looking title screen, I'm looking forward to giving it a go later on, especially on the proper spec hardware! wesley-games.itch.io/frog-and-

  4. I've been playing around with #povray again. This is a very unfinished render; I've only just started on this tree. I'm trying to make things from scratch rather than bringing in too many tools, or prebuilt textures and the like. I'm hoping to bring back that little fighter ship I posted earlier in the year, and stick a cityscape behind it with a bunch of these trees. The amount of cyan and magenta might give some people a clue where this is headed #cga

  5. ZX Spectrum Compatible Video for RC2014

    As part of my tinkering with the RC2014 I wanted an easy way to transfer ZX Spectrum compatible assembly over to an RC2014. Naturally the language and code itself are pretty straight forward, but when it comes to what the assembly code is attempting to achieve this will be quite different.

    There are a number of architectural features of the ZX Spectrum that don’t exist on an RC2014 based system. The big ones being:

    So really, although the original aim of the RC2014 project was spurred on by the idea of recreating a ZX Spectrum, ZX80 or ZX81 or similar computer, in reality the idea of reproducing all of the above design constraints on an RC2014 compared to finding an old ZX spectrum to recondition or repair, running one of the many modern recreations (The Spectrum, Spectrum Next, Harlequin, and so on) or using software emulation removes much of the reason for doing so.

    Still, some people have already come up with various ways to attempt to do it and deal with the issues. Some interesting ones related to the RC2014 are:

    But far most the most admirable effort is the ZX128: https://github.com/ZXQuirkafleeg/ZX128. This provides everything apart from the Z80 itself in a RC2014 format to provide a pseudo ZX Spectrum with an RC2014 backplane. This uses a CPLD to implement the ULA, which is an approach used on the ZX MAX boards too (e.g. the MAX 128).

    There is detailed discussion of the idea here: https://groups.google.com/g/rc2014-z80/c/SUhZvL308ug. In particular, at the end of that thread is a post from 2023 from “Samster” who spent a fair bit of effort finding replacements for all of the above that could be used with an RC2014 system, and managed the amazing feat of a ZX Spectrum in RC2014 format. It is awesome work.

    At this point I should also acknowledge some of the projects that are recreating the functionality of the ZX Spectrum using modern microcontrollers. The key ones I know of are:

    As I dug deeper into the idea of a ZX Spectrum compatible display card for the RC2014, I eventually stumbled across the BusRaider. This is another strong contender for “ZX Spectrum from an RC2014”. It provides memory mapped graphics that are ZX Spectrum compatible, among other things, for an RC2014 system:

    This takes an interesting approach – it grabs the Z80 bus and then uses logic chips to quickly read the memory and then uses the read data to generate a display. It thus has to “play nice” with the Z80 CPU in terms of sharing the bus, i.e. it has to use the various bus control signals properly. In this system the Z80 is writing to the memory and the BusRaider is reading it. As I understand things, this isn’t dissimilar to how the ULA worked to build up the display from the memory and bus shared with the Z80 CPU.

    So why do I mention all this? Well, I’m not interested in a full clone, but I was definitely wondering what it would take to create a ZX Spectrum-compatible “video card” for the RC2014. Because, well, why not?

    It looks like BusRaider does exactly that using a Raspberry Pi Zero. But I wondered if rather than reading out of the memory directly, could something like a Raspberry Pi Pico monitor the bus “live” and watch the writes as they happen?

    It turns out that this excellent use of the Specduino does pretty much exactly that, to watch the memory of the ZX Spectrum and use that to generate a “live” map of the game Atic Atac: https://www.youtube.com/watch?v=aWh8rTzOfH4.

    So that is the concept I’m starting from.

    ZX Spectrum Video

    The ZX Spectrum supports a 256 x 192 pixel display using a palette of 15 fixed colours (more here). Pixel and colour data are stored independently. One of the best resources I’ve found for describing how the ZX Spectrum display works is: http://www.breakintoprogram.co.uk/hardware/computers/zx-spectrum/screen-memory-layout.

    There is a whole chunk of memory from $4000 to $5AFF that maps onto display data as described in the above link. There are 192 blocks of 32 bytes (6144 in total) for pixel data and 768 bytes for attribute data. In binary, these addresses look like the following;

    b0100 0000 0000 0000 = $4000 - Start of Pixel data
    b0100 1111 1111 1111 = $4FFF
    b0101 0000 0000 0000 = $5000
    b0101 0111 1111 1111 = $57FF - End of Pixel data
    b0101 1000 0000 0000 = $5800 - Start of Attribute data
    b0101 1010 1111 1111 = $5AFF - End of Attribute data

    The pixel data is relatively straight forward, if a little odd, which I’ll come on to in a moment. A bit set in the pixel memory means the pixel will be set to the foreground colour (INK). A bit cleared maps to the background colour (PAPER). As already mentioned there are 192 “lines” of pixel data of 32 bytes each, so 32 x 8 pixels (256).

    The most famous quirk of the ZX Spectrum graphics surely has to be the infamous “attribute clash”. This is due to the fact that, to save memory each 8×8 pixel block (character cell) can only have one INK and one PAPER colour at a time. This allows for the colour information for the entire display to fit into a 32 x 24 character grid, totaling just 768 bytes.

    The address decoding is slightly odd too. As per the above link, the mapping of X,Y coordinates onto an address in memory is as follows:

    For:
    X = X0..X4 (5 bits = 0 to 31) = X coordinate (in bytes) 0 to 31
    Y = Y0..Y7 (8 bits = 0 to 255) = Y coordinate (in lines) 0 to 191

    Pixel Memory Address (in binary):
    0-1-0-Y7 Y6-Y2-Y1-Y0 Y5-Y4-Y3-X4 X3-X2-X1-X0

    Attribute Memory Address (in binary):
    0-1-0-1 -1--0-Y7-Y6 Y5-Y4-Y3-X4 X3-X2-X1-X0

    One reason for the odd ordering of the Y values is that when working with characters (e.g. in BASIC) then moving down one line, assuming the address of the first line is in register HL, is simply a matter of INC H, which is a really quick operation. This is also the reason for the curious ordering of lines when loading a screen from tape. As the data or each address loads in sequentially, it loads one byte at a time (X4-X0), by taking each third of the display in turn (Y7,Y6), and working through all bytes in the same row (Y2,Y1,Y0) for all of the 8 rows of characters in that third of the display (Y5,Y4,Y3). This loads in monochrome until the attributes then load over the top.

    As the attributes work on 8×8 characters, the Y coordinate required is actually Y / 8 or Y >> 3, hence discarding Y0,Y1,Y2 above and only using Y3-Y7 to decode the address. Note that in both the pixel and attribute maps Y6,Y7 will encode which third of the display is being targeted. For the 192 lines, this will only be using the binary values b00,b01,b10 (b1011111 = 191).

    One quirk of the addressing is that when calculating positions of 8×8 character blocks, the bottom 8 bits of the address are the same for the pixel data and attribute data. For example:

    Coordinate (x=4,y=25) = (b00100,b00-011-001):
    Pixel address = b010[0 0[001] 011]0 0100 = $4162 = 16740
    \--\ | | |
    Attr address = b0101 10[00 011]0 0100 = $5864 = 22628

    Just double checking that attribute value then: for coordinate (4,25) we need attribute cell (4, 3) as 25/8 rounds down to 3. So taking $5800 (22528) + (3 * 32) + 4 = 22528 + 96 + 4 = 22628.

    But what is stored at each attribute address? Again, from various sources, we know that an attribute byte has the following format:

    Attr = F B P2-P1-P0 I2-I1-I0
    F = Flash
    B = Bright
    P = Paper
    I = Ink

    Ignoring flash, this means there are four bits associated with colours. But there is no concept of “bright black” so both bright and normal black are just black. This gives us the 15 colour palette we all know and love, although we have to note that BRIGHT will apply to both PAPER and INK.

    Wikipedia has all the colours mapped onto HTML colour codes and notes that this is a variation of 4-bit RGBI used by CGA – the Color Graphics Adaptor standard which was introduced in 1981 by IBM, although CGA had slightly higher resolution in terms of pixels.

    RC2014 Video Options

    To get video onto an RC2014, there are some existing options:

    • Pi Zero Serial Terminal – a now retired product that used a Raspberry Pi Zero to support a serial terminal on an HDMI display.
    • Pico VGA Terminal – this uses a Raspberry Pi Pico to drive a VGA output using GPIO and resister DACs from a UART serial connection to the RC2014.
    • RP2040 VGA Terminal – this is a rebuild of the above using a RP2040 microcontroller directly, but it is essentially a similar device.
    • Marco’s VGA Serial Terminal Console – another board (no longer produced) based on the same idea but based on a Parallax Propeller microcontroller.
    • TMS9918A Video Card for RC2014 – composite graphics board using the popular (at the time) TMS9918A graphics processor.
    • TMSEMU3 graphics card designed for RC2014 – replacement for the above using a RP2040 based DVI TMS9918A emulator device to provide DVI (via a HDMI connector) output.
    • Stegosaur V9958 RGB – based on the more recent V9958 video display processor (VDP) to give a VGA output.
    • MSX V99x8 Video Module – similar to the above, but specifically aimed at MSX emulation.

    The serial terminal options are just that – serial console output only. The TMS9918 based solutions are interesting – these are genuine graphics devices and the register interface to the TMS99x8 is pretty well understood. But as I understand things, it can sometimes be slow to get throughput of screen updates, one reason MSX games were not as highly performing as other systems (apparently).

    Using any of these TMX99x8 solutions would also mean significantly re-writing anything that already uses a memory-mapped video interface such as that expected by code running on the ZX Spectrum.

    As already mentioned, what I’d like to do is somehow snoop the RC2014 bus and pull out any writes to the graphics area of the memory map and deal with them appropriately. There has been a bit of discussion of this in some places (see here for example).

    But really this has a lot more in common with some of the projects that use a microcontroller to replace RAM or ROM directly or projects that act as a bus logic analyser than any of the existing RC2014 video options that I’ve found so far.

    Raspberry PI Pico and Vintage Computers

    Following up on that last thought, in addition to the aforementioned serial terminal to VGA options, there are a number of other interesting Raspberry Pi/Pico projects that actually do something very similar to what I’m after.

    • TK-Pie – Uses a Raspberry Pi Zero on a ZX Spectrum edge connector add-on to snoop the ZX Spectrum (Z80) bus directly. This does exactly what I want to do – it watches for address access to the video graphics area of memory and processes it accordingly. To solve performance and voltage level issues it uses a CPLD as the interface between the Z80 bus and the RPi Zero.
    • OneROM – Uses a RP2350 or STM32 to act in real-time as a replacement for a wide range of E(E)PROMS and RAM used in vintage computers. A very comprehensive open source solution with highly optimised PIO and assembler code running on the RP2350. Can replace a range of 24, 28, 32, 40 pin 8-bit and 16-bit devices and has comprehensive tooling support to make it easy to use.
    • Picotari – Using a Raspberry Pi Pico as an Atari 2600 ROM replacement.
    • Picocomputer – a 6502 based single board computer using two Raspberry Pi Picos to support the peripheral devices required by the 6502, including providing a VGA graphics interface.
    • RP2350 Pico2 400MHz 24 Channel Logic Analyzer – Using a Raspberry Pi Pico 2 (RP2350) as a multi-channel logic analyser.
    • MCEBlaster – using a RPi Pico as a converter (and, to a degree, an upscaler) between MCA/CGA/EGA graphics standards and VGA output for a more modern monitor.
    • Scott Baker’s Bus Supervisor – using two MCP23017 and a PCF8574 under Raspberry Pi I2C control to grab control of the Z80 bus and allow writing to or reading from RAM.
    • picoZ80 – a RP2350 based full emulation of the Z80 microprocessor all within the same 40-pin DIP footprint.
    • Happy Little Diodes’ “Specduino” – I can’t find designs published online (yet), but it is described in a lot of detail in this video with more here. This uses an ATMega32U4 and some latches to grab chunks of the ZX Spectrum’s memory by processing the signals directly from the Spectrum’s edge connector.
    • Apple II VGA Card – This does exactly what I’m thinking – it has a Pico snoop the 6502 bus and creates an in-memory map of the video which is sent to a VGA compatible output.
    • RP2350 ZX Spectrum compatible – all the 1980s components for a ZX Spectrum but with a RP2350 replacing the ULA, including providing video output.

    All of the Raspberry Pi Pico or RP2350/RP2040 based solutions will typically be using the PIO subsystem for optimum transfer, so that is where I’m going to be heading.

    Raspberry Pi Pico Video

    The Raspberry Pi Pico has been used to produce VGA signals using GPIO to output to a resistor DAC for each of the analog signals, alongside the H and V sync signals.

    It has also been used to provide HDMI, or at least a compatible DVI HDMI variant. There are breakout boards available for both options for experimentation. The RPi Pico VGA output is one of the official RPi sample “playground” projects and in the RPi “Hardware Design with RP2040” manual.

    The MCEBlaster is also really interesting as it provides a physical VGA output for a range of smaller resolution TTL video inputs, including CGA, which is effectively the Spectrums video pallet.

    But as mentioned here (Simplified Pico VGA – Part 2) the closest thing I’ve found is the pico-zxspectrum, which has a range of options for ZX Spectrum video for VGA. In particularly, the RGBY1111 mode taken from the pico-zxspectrum repository: https://github.com/fruit-bat/pico-zxspectrum/blob/main/docs/ZxSpectrum4PinAudioVga1111Ps2.md.

    RPi Pico ZX Spectrum Video

    Given everything discussed so far, the key requirements I’m after in this system are as follows:

    • 16-bit address lines, 8-bit data lines, some (tbd) control lines.
    • Address decoding to be done within the Pico, possibly with the PIO.
    • Snoop the Z80 address and data lines, so does not need to be write to the actual Z80 bus.
    • Must be able to handle bus control logic to understand valid writes and distinguish from IO and refresh cycles.
    • Fast enough to support typical frame rates (e.g. 50Hz refresh rate for a PAL display).
    • Video output that is scaled down to the ZX Spectrums video capabilities.

    One issue with aiming for a VGA output is the large number of GPIO required (17 for three colours and sync). HDMI requires less GPIO so would be another option.

    Looking at the MCEBlaster again is very interesting as it uses a much lower resolution resistor DAC for a CGA compatible output on a VGA screen than is required for full VGA. When limited to the colours of CGA, this manages the required output using just 8 GPIO: 2 each for R, G, B; H sync; V sync.

    But as explored in Simplified Pico VGA – Part 2 when I’m limiting things to ZX Spectrum video compatibility, with a single brightness control, the RGBY1111 mode can be implemented with just 6 GPIO.

    So the final GPIO requirements:

    • Address bus decoding: 16 GPIO
    • Data bus decoding: 8 GPIO
    • Control bus decoding: up to 8 GPIO
    • CGA/VGA signal output: 6 GPIO

    This is a total of up between 30 and 38 GPIO which is more than available on a standard Pico which has 23 digital IO pins plus 3 digital/ADC pins broken out. They will also need to be 5V tolerant.

    Z80 Bus Memory Writes

    As described in Watching a Z80 from an RP2350, there are a number of different bus control signals, creating different bus cycles. This was followed in Watching a Z80 Bus from an RP2350 – Part 2 by showing how a CPU memory write can be monitored and captured in the RP2350’s own memory.

    The conclusion is that I need access to the following bus signals: /MREQ, /RD, /WR, /IORQ, /M1.

    Bringing it all Together

    At this point then, I believe I have every required to build a ZX Spectrum compatible video card for the RC2014, by combining:

    Key design points:

    • I’ll be running on an overclocked (probably at least 200MHz) RP2350B with 48 GPIO using the Pimoroni PGA2350 at least in the first instance.
    • A shared ram[64*1024] structure, although only the area within the Spectrum memory map will really be used.
    • Core 0 – running the VGA output using GPIO 40-45 reading from the raw ram[] area.
    • Core 1 – running the memory monitor using GPIO 0-31 writing to the raw ram[] area.

    These will be explored in the following sections.

    Adjusting the Scanvideo Example for RP2350

    In principle the code I used in Simplified Pico VGA – Part 2 should run as is on the RP2350, but there is a slight complication when attempting to use it with my RC2014 PGA2350. I want the video output to run on GPIOs 40 onwards.

    From the hardware_pio API documentation:

    • On RP2040, pin numbers may always be specified from 0-31.
    • On RP2350A, pin numbers may always be specified from 0-31.
    • On RP2350B, there are 48 pins but each PIO instance can only address 32 pins (the PIO instance either addresses pins 0-31 or 16-47 based on pio_set_gpio_base). The pio_sm_ methods that directly affect the hardware always take real pin numbers in the full range.

    pio_set_gpio_base takes either 0 or 16 as the base. This can be set per PIO instance, i.e. pio0, pio1 or pio2 (on RP2350). There is also the definition PICO_PIO_USE_GPIO_BASE which will be set within the SDK to 0 for the RP2040, with no adjustable PIO base, or 1 for the RP2350 where the base can be chosen.

    But although there are routines to allow PIO to function with any GPIO pin, in the scanvideo library the scanvideo_setup_with_timing() function uses a pin_mask value that assumes GPIO pins will be less than 32:

    uint pin_mask = 3u << PICO_SCANVIDEO_SYNC_PIN_BASE;

    Actually, this is assuming the number of GPIO will be less than the size of the default integer, which on the RP2350 should be 32-bits, but this is not exactly portable code. This really should be uint32_t.

    This is used in the function to figure out which GPIO need initialising and then doing so:

    for(uint8_t i = 0; pin_mask; i++, pin_mask>>=1u) {
    if (pin_mask & 1) gpio_set_function(i, GPIO_FUNC_PIO0);
    }

    I did wonder about just making pin_mask a 64-bit value, but this didn’t make the code happy. In the end I set some global macros in the vgamode.h file to adjust the mask processing as required:


    // If using all GPIO > 32 then this will adjust scanvideo.c
    #define PSVMASKOFFSET 32
    // If using all GPIO < 32 then this will leave scanvideo.c as is
    //#define PSVMASKOFFSET 0

    #define PSVMASK(x) ((x)-PSVMASKOFFSET)

    And then in scanvideo.c whenever it is setting a bit in pin_mask it uses the PSVMASK() macro to adjust the shift value. Then when setting up the GPIO pins it adds in PSVMASKOFFSET. Some examples below:

    uint32_t pin_mask = 3u << PSVMASK(PICO_SCANVIDEO_SYNC_PIN_BASE);
    ...
    pin_mask |= 4u << PSVMASK(PICO_SCANVIDEO_SYNC_PIN_BASE);
    ...
    for(uint8_t i = 0; pin_mask; i++, pin_mask>>=1u) {
    if (pin_mask & 1) {
    gpio_set_function(i+PSVMASKOFFSET, GPIO_FUNC_PIO0);
    }
    }

    #if PSVMASKOFFSET==32
    // Using the higher GPIO for PIO so change the base
    pio_set_gpio_base(video_pio, 16);
    #endif

    We also need to adjust the base itself so that has been added just after the gpio_set_function() calls prior to adding the PIO program.

    That seems to have done the trick. Here is my demo code from Simplified Pico VGA – Part 2 now running on GPIO 40-45 on my RC2014 PGA2350 board.

    The complete diff for scanvideo.c can be found, alongside the other main files and instructions for how to reproduce and build the sketch, in GitHub here: https://github.com/diyelectromusic/sdemp/tree/main/src/Misc/PicoScanVideo

    Formatting for the Spectrum Display Memory

    The demo uses a relatively simple “draw_color_bar” function to drop a block of colour onto the display. I’m going to need to process the Spectrum video memory to create each scanline.

    My starting point for this is the excellent pico-zxspectrum project as that already does exactly this. This requires the use of the following key files and functions:

    • src/ZxSpectrumDisplay.h
    • src/ZxScanlineVgaRenderLoop.cpp::ZxScanlineVgaRenderLoop().
    • src/ZxSpectrumPrepareScanvideoScanline.cpp::zx_prepare_scanvideo_scanline().

    Implementing a ZX Spectrum compatible display is largely a case of porting the above over to my sketch. But the first version had an issue – I just couldn’t get the right alignment going on for the pixels, so in the end I re-implemented it myself using the above as a guide.

    zxs_scanline

    My own implementation has the following simplifications:

    • Uses VGA 320×240 mode directly as per my previous tests. The original uses 640×480 with pixel doubling to drop back to the ZX Spectrum resolution.
    • Pre-define the sizes of the ZX Spectrum display in a file zxdisplay.h. The original has a range of configurable options to cope with many different circumstances.
    • Uses the same 16-bit versions of the tokens I used in my demo. The original used 32-bit “double pixel” values to support the pixel doubling.
    • The border is currently fixed. I’ll need to research how tto change it (I see it was either a ROM call or an OUT IO instruction – filed as a “todo” for later).

    Now the main calling code is as follows:

    void setup() {
    scanvideo_setup(&vga_mode);
    scanvideo_timing_enable(true);
    zxs_scanline_init(&vga_mode);
    }

    void loop() {
    scanvideo_scanline_buffer_t *buffer =
    scanvideo_begin_scanline_generation(true);
    uint32_t frame_num =
    scanvideo_frame_number(buffer->scanline_id);
    uint32_t y =
    scanvideo_scanline_number(buffer->scanline_id);

    zxs_scanline(buffer, y, frame_num,
    &ram[ZXS_DISP_ADDR], &ram[ZXS_ATTR_ADDR],
    borderColour);

    scanvideo_end_scanline_generation(buffer);
    }

    I initially dropped the frame number from the call, but then I realised that the original used the least significant bit of the frame number to control the flash attribute – i.e. every other frame the colours were inverted – so I put it back in.

    I’ve used the Spectrum screen definitions as described in “The ZX Spectrum ULA” by Chris Smith:

    // ZX Spectrum pixels definitions
    #define ZXS_DISP_WIDTH 256
    #define ZXS_DISP_HEIGHT 192
    #define ZXS_BORDER_TOP 24
    #define ZXS_BORDER_BOTTOM 24
    #define ZXS_BORDER_LEFT 32
    #define ZXS_BORDER_RIGHT 32

    // Attribute cells are 8x8 pixels
    #define ZXS_ATTR_WIDTH 32
    #define ZXS_ATTR_HEIGHT 24

    The original border would have been stretched to support the 312 scanlines of a typical PAL TV, but here they are cut short to fit a 640×480 VGA display. As the Spectrum resolution is 256×192 we use pixel doubling in both directions. This means 1 Spectrum pixel is a 2×2 VGA pixel at 640×480. Of course, when using VGA at 320×240 it is a single (larger) pixel.

    So, for example, the space for the top and bottom border would be 480 – (192*2) = 48, meaning 24 pixels each rather than the perhaps more expected 312 – (192) = 120, meaning 60 pixels each (although for some reason I see this often stated as 56 rather than 60).

    The original code from pico_zxspectrum supports many more configurations including some that require a blanking period before the top or left borders are drawn. I’m not bothering with any of that here.

    The algorithm for outputting a line of the display is as follows:

    zx_scanline (y):
    IF y is in TOP or BOTTOM border:
    Draw solid scanline for whole width in border colour

    ELSE:
    Draw left border using COLOR_RUN in border colour
    Find position in display and attribute memory for start of line y

    FOREACH of the 32 8-bit attribute blocks:
    Set brightness, paper and ink colours from attribute memory
    Output RAW_RUN for 8 bits of pixel data in display memory

    Draw right border using COLOR_RUN in border colour
    Output single BLACK pixel to complete line

    Much of the code that turns line numbers into actual Spectrum display parameters is left as it was from the pico_zxspectrum ZxSpectrumPrepareScanvideoScanline.cpp.

    The resultant code can be found on GitHub here.

    Here is an example that is just filling the display RAM sequentially, which of course means we get to see the characteristic Spectrum “loading screen” pattern fill.

    By the way, the bottom block of colour is because the last two attribute rows are inverted to test the attribute handling (well actually they are flashing, but this has caught them inverted). The middle third of the display also has bright set, but you can’t really see that at this point.

    Putting it all Together

    Plugging the Pico board into an RC2014 does actually seem to work!

    Here is a BASIC program to fill the attribute memory and then the video memory:


    10 FOR N=0 TO 767
    20 POKE 22528+N,58
    30 NEXT N
    40 FOR N=0 TO 6143
    50 POKE 16384+N,255
    60 NEXT N

    This fills the attribute memory with 0x3A (58) = no flash, no bright, white paper, red ink and the fills all video memory with 0xFF (255) revealing the now expected display loading pattern.

    It is a bit slow running in BASIC mind. I did try using the fill command in the SC monitor, which is a lot faster, but I seemed to crash things pretty regularly and I’m not convinced the Pico could keep up with the burst of bus activity. It may also be that the SCM was using part of the memory that corresponds to the video memory and I was overwriting it, so I’ll have to look into that at some point.

    It also has to be remembered that this is also running on an 8MHz Z80 not the 3.5MHz of the original ZX Spectrum so the RP2350 may well struggle to keep up.

    Here is the BASIC code in action:

    https://makertube.net/w/cmHGYyQ9fMfewLhb7QBUQT

    Conclusion and Next Steps

    It has taken quite a long time to get this far and there have been lots of side quests along the way into Raspberry Pi Pico PIO, using VGA on the RP2040 and RP2350, monitoring a Z80 from the RP2350, building some helper PCBs and so on.

    But I’m really pleased with how this has come together so far.

    The next steps involve some of the following:

    • Looking at the performance. The bus monitor might need to switch to PIO, or I might be able to overclock a bit more. Of course, I need to try it with a more authentic 3.5MHz Z80 too.
    • Figure out how to code something directly for the RC2014 so I’m not relying on BASIC or the SCM, but also look into why the SCM seemed to crash when writing to the display area.
    • Write some kind of loading screen selection box thing. It would be fun to be able to watch some classic game loading screens build up.
    • Ponder supporting more than one video mode so I could potentially support higher resolution (or at least proper CGA without attribute clash) video output. I wanted it for the Spectrum compatible display, but there is no reason I couldn’t go a bit further. I have enough GPIO for RGB222 at least.
    • Create a new PCB with the RP2350 and video circuitry combined.

    But this is a really good place to leave this post for the moment.

    Kevin

    #cga #rc2014 #rp2350 #vga #zxSpectrum
  6. 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:

    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.c

    Each 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 LSBitGP1647RHSYNCGP1747RVSYNC

    VGA 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 12u

    As 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)GP16100RHSYNCGP17100RVSYNC

    This 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
  7. Подробная история развития 2D-графики в PC

    Сейчас в сети можно найти много статей и видеороликов о становлении 3D-графики в обычных пользовательских ПК. Даже я в своем блоге веду соответствующую рубрику, где мы с вами не только вспоминаем историю знаковых видеокарт из 90-х, но и во всех подробностях изучаем их архитектуру, возможности и графические API. Однако всё это время мы с вами обходили тему 2D-ускорителей на ПК, которые тоже очень сильно эволюционировали и изменились со времен самой первой видеокарты в IBM PC. Если вам интересно узнать о том, зачем видеокарте нужен был свой собственный BIOS, как работали цветные режимы в первых видеокартах и почему объём VRAM в те годы решал больше, чем сейчас — жду вас под катом!

    habr.com/ru/companies/timeweb/

    #monobogdan_ништячки #пк #ibm_pc #железо #гаджеты #видеокарты #mda #cga #vga #timeweb_статьи

  8. I just found pristine KryoFlux streams for the 5.25inch 360K Blockout diskette and thus upgraded my personal library, as well as this entry on the Internet Archive. Blockout is a true icon of 80s retro gaming!

    Link: archive.org/details/blockout_5

    #Blockout #EGA #CGA #IBMPC #Diskette #FloppyDisk #KryoFlux #Diskette #Tandy #InternetArchive #Preservation #80s #Retro #Gaming #RetroGaming #PCGaming #MSDOS #DOS #DOSGaming

  9. Mistodon: this menacing #CGA-palette psychic furry #ANSIart mob scene (say that three times fast!) was drawn over a six hour peiod by @TheGreenHerring in a Twitch stream (after which, maybe their hand was glowing magenta also!), and was included in the recent science fiction-themed MIST0526 artpack collection.

  10. Via Palazzotto, la magistratura sequestra il cantiere

    Il sequestro penale dell’autorità giudiziaria ha fermato il cantiere di via Palazzotto, subito sotto il Tondo Gioeni. Un sequestro preventivo d’urgenza di cui ancora non abbiamo il provvedimento ma che ha riaperto le porte alla speranza di tutti i cittadini che si sono mossi in questi anni per difendere gli spazi comuni, consegnati via via ad interessi privati.

    Su via Palazzotto Argo […]

    Leggi il resto: https://www.argocatania.it/2026/07/29/via-palazzotto-la-magistratura-sequestra-il-cantiere/

    #CGA #ComuneDiCatania #concessioniEdilizie #GiuntaTrantino #PianoRegolatoreGenerale #PRGDiCatania #RegioneSiciliana #TARDiCatania

  11. Exactly one year ago today I released my most popular game to date, The #ChambersBeneath. It's a graphical roguelike for the #ibmpc and compatibles running #msdos and needs only #cga graphics. If this sounds like your kind of thing, you can download it from cyningstan.itch.io/the-chamber

  12. My latest bit of #TravellerRPG material has arrived: the updated Central Supply Catalogue for Mongoose 2nd edition. I wish I'd had this for inspiration back when I was developing #StarCadre Combat Class for #msdos back in 2024. #cga #hp100lx

  13. @goosey That palette utility I posted about a while back might help aesthetically, but if it's using colour to represent tile layers then the 4 colour limit will still be a problem. I'm sure I've seen a #cga version of this that used the more usual oblique projection. My vague memory of it suggests it was on the 640x200 mono screen.

  14. RE: floss.social/@danslerush/11675

    I love them both, especially the 8086 #cga variant. I don't think you can develop applications to install into them, although you can modify and rebuild the OS itself and add/replace applications that way.

  15. Glycemic control in the context of #frailty: a #mortality risk assessment in older diabetic patients

    "...These findings challenge the application of uniform #treatment targets and suggest the potential value of personalized, frailty-stratified approaches to #diabetes management in older adults..."

    #Geriatrics #Care #Health #CGA #MedMastodon

    link.springer.com/article/10.1

  16. RE: oldbytes.space/@gloriouscow/11

    This is a fascinating thread if you have any interest in retro pc graphics and the magic those in the demo scene can work. #retro #retropc #cga

  17. Conveyors level 4 brings a helpful guard like those on level 2. The guard picks up one card and places it on a conveyor belt each turn. You have a single robot to take all four cards to their card readers. While there's no panic like in level 2, you still want to get a move on to help your score. You can get #TeamDroid from cyningstan.itch.io/team-droid #ibmpc #msdos #cga

  18. Assessment of #sleep in older people within comprehensive geriatric assessments: a scoping review

    "...Sleep can be assessed by any #healthcare practitioner in the multidisciplinary team..."

    #Geriatrics #Frailty #CGA #Health #Care
    #MedMastodon

    academic.oup.com/ageing/articl

  19. went and added most of the scenes/dialog to the CGA/DOS version of Peasant's Quest. Definitely not the whole game, but can be fun to walk around doing things.

    Can't always decide which palette would be best. Cool Ranch? Taco-Seasoning? The horribly monstrosity that King's Quest 1 used?

    You can try it yourself on the internet archive here: archive.org/details/peasant_20

    use control-A/B/Y to mess with the palette yourself

    #dos #cga #homestarrunner

  20. In The Anarchic Kingdom, the buildings and characters on a barony screen are a visual representation of that barony's stats. The better a barony is doing, the more impressive they look. cyningstan.itch.io/the-anarchi #anarchickingdom #ibmpc #msdos #cga

  21. Ever since I saw this Q&A about "LCD mode" I've wanted to implement it in my games. My #hp100lx has the feature built in, but some #msdos #cga portables like the Amstrad PPC and the Psion MC don't. But I realised it's more complex than just inverting all the graphics as you load them. You have to make sure, for instance, that you don't invert software sprite masks, since they're working on logic rather than just aesthetics. gaming.stackexchange.com/quest

  22. Level 3 of Conveyors goes back to the dual chamber theme, with a one-way conveyor belt journey between them. But this time there's a robot in each chamber, so the robot in one chamber can start sending the data cards down to the robot in the other. It's possible that the first robot's work is complete before the second one makes a start. You can get #TeamDroid from cyningstan.itch.io/team-droid #ibmpc #msdos #cga

  23. @jfaulken @lyda Lots of people are doing development in DOS. I know of 72 #msdos games released last year. My own projects target early DOS machines, 8088 and #cga using OpenWatcom C. There are occasional jams on Itch, which often link into relevant Discords.

  24. Your empire might be bounded by the sea. The sea is completely impassable to your armies, but any port city will bring in 3 gold from sea trade for each adjacent sea square, in addition to that raised by taxes. Save the empire from the #Barbarians by downloading the game from cyningstan.itch.io/barbarians- #ibmpc #msdos #cga

  25. Land in The Anarchic Kingdom is the measure of your success. It also influences how many peasants your barony can support: when you gain land, people move in. Play at cyningstan.itch.io/the-anarchi #anarchickingdom #ibmpc #msdos #cga