Sign in

NON-TOXICNo ads · No shady algorithms

iiinova is part of the Atmosphere — an open, public network where you own your identity.

diyelectromusic

@diyelectromusic.mastodon.social.ap.brid.gy
103 followers 2 following 482 posts

Simple DIY electronic music projects - #Arduino, #RaspberryPi #MIDI, #synthdiy, where music and #STEM overlap. Home of the #LoFiOrchestra and follower of […] 🌉 bridged from ⁂ mastodon.social/@diyelectromusic, follow @ap.brid.gy to interact

PostsRepliesMedia
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 23/10/2026
And so it starts. I've started getting marketing "we'd like to offer our widget/service/thing for you to try out" for things done with, by, or based on the use of, AI. It was only a matter of time I guess, but if they read my blog in the first place (presumably that is why they approached me) […]
mastodon.social
Original post on mastodon.social
000 2
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 07/10/2026
@pmc In all seriousness I am waiting for an updated set of CC licenses that indicate AI permissions...
000 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 07/10/2026
@pmc Not a HTTP header or status? X-SlopPercent= 209 Slop Content Or maybe there should be an AI record in DNS!!? :)
100 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 07/10/2026
Is there a standard "I made this with AI" content warning that we can encourage people to use, so I can filter them out and save the disappointment of finding out halfway through reading about a project? I guess if others choose that as a method, that's up to them, but personally I have very […]
mastodon.social
Original post on mastodon.social
413 4
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 04/10/2026
(I also now have a few "print x" or "wobble gpio pin" equivalents for debugging pio... but I'm sure there must be an easier way...)
000 0
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 04/10/2026
Hmm. Spent pretty much all day trying to work out where my PIO implementation is broken. Unfortunately its still broken, but I've managed to disprove quite a few things on the way. So, its still broken, but I am a lot more knowledgeable about the bits that aren't broken... I think that is […]
mastodon.social
Original post on mastodon.social
100 1
Reposted by diyelectromusic
Juan F. Ramírez @juanfr.mastodon.social.ap.brid.gy · 04/10/2026
Brand new Spectrum game: Amok Time, by Simon Allan. When a 1979 Disney film meets Spectrum classic Atic Atac 🙃 Physicall edition already available, digital edition in 16th october, both here: simonallan.itch.io/amok-time #zxspectrum #retrogaming
011 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 01/10/2026
I really don't know what I was expecting when I clicked on the site called "pink trombone". But I'm pretty sure it wasn't this... dood.al/pinktrombone (it's amazing, btw. Been playing with it for something like 10 minutes...) #WeirdWebOctober
dood.al
Pink Trombone
022 2
Reposted by diyelectromusic
Stefan Bohacek @fediverse.stefanbohacek.online · 01/10/2026
Hey fediverse, happy #WeirdWebOctober to everyone participating! weirdweboctober.website #WeirdWeb #SmallWeb #MakeAWebsite
weirdweboctober.website
Weird Web October
A challenge to try and make a website every day of October, based on the theme for each day
137 7
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 28/09/2026
Quick question @ExtKits .. the switches for the RP2350 RomWBW bus - should they be readable from basic using PRINT IN 0 If so, mine aren't being recognised for some reason so now wondering if I've done something daft... I've also managed to kill an LED, which is annoying, as I did test them […]
mastodon.social
Original post on mastodon.social
001 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 27/09/2026
It works! I now have my badge ready for the #RC2014Assembly v2. When is it on again? Oh wait... :) (btw to change to the second line from BASIC you need OUT 218,192 - ie OUT $DA,$C0 or writing b1100000 to the control register) #RC2014
Photo of two RC2014 modules joined together. On the right is a RC2014 micro which has a Z80 CPU, ROM, RAM and serial (all DIP) chips.  On the left is a backlit LCD1602 module showing the text "diyelectromusic" on the top line and "rc2014 asbly 2" on the bottom.
110 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 26/09/2026
Had a really great time at the #RC2014Assembly. Listened to some brilliant talks and talked to people about their projects. Also spoke to lots of people about my own messing around with the AY-3-8910 and ZX Spectrum compatible graphics. I hope Steve from SmallComputerCentral wasn't too annoyed […]
mastodon.social
Original post on mastodon.social
020 2
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 19/09/2026
Just managed to plug in one of my PGA2350's into a PCB rotated 90 degrees, so thought I'd killed it. Turns out it won't run my code if I don't load it first. I mean who knew? At least it doesn't seem dead! Phew. #MakerFail
020 2
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 18/09/2026
@lumecolca In that case you might be able to find a DIY 8088 project online and then see how they did things like initial boot, reset vectors, memory initialisation, etc. If you can find your path through the startup code, you might be able to infer things like ROM/RAM addresses, IO accesses and […]
mastodon.social
Original post on mastodon.social
000 0
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 18/09/2026
@lumecolca As a starting point you might be able to read the ROM with something like an Arduino. This is how I did that: diyelectromusic.com/2024/03/18/diag… For decompiling, what you really need is a schematic for the device it is […]
mastodon.social
Original post on mastodon.social
000 0
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 18/09/2026
This is great. It is using 6 XIAO synth modules. I've written about these myself on my own blog too - they really are quite fun little modules :) www.youtube.com/watch?v=2hYpgMcTs8o #Xiao #MIDI
020 2
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 16/09/2026
Ok, so now I need to figure out how to drive these beasties...
Photo of three Altera MAX EPM7128SLC84 CPLDs.  Each is a square 84-pin PLCC format device.
020 2
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 15/09/2026
@lproven Particularly topical for me, as I've been slowly working through how the AY driver Tim Follin used worked. diyelectromusic.com/2026/08/26/z80-…
diyelectromusic.com
100 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 15/09/2026
@lproven Ha yes! :) Great article :) I was reading that blog post on 1-bit music too. And it still amazes me, when I stop to think about it, that there are many of us still messing around with all this stuff in 2026!
100 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 15/09/2026
haha - how has the Mahler Project from 2015 passed me by? :) www.youtube.com/watch?v=LUvYcFBwGGo This is apparently in response to the challenge in the original ZX Spectrum manual under the BEEP command to program in the rest of Mahler's first symphony... I just love the expressions […]
mastodon.social
Original post on mastodon.social
100 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 15/09/2026
@jonathanhogg I think you're already doing it. Make something. Doesn't matter what. It is our collective superpower. And share that with the next generation. If you want a constant reminder, get one of these from @LewisWorkshop […]
mastodon.social
Original post on mastodon.social
000 0
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 15/09/2026
I've always known the ZX Spectrum 128K MIDI cable was weird. But it quite a bit weirder than I realised! As far as I can make out the circuit drives it as a current loop running at +/- 12V RS232 voltages... diyelectromusic.com/2026/09/14/zx-s… #ZXSpectrum #MIDI
diyelectromusic.com
ZX Spectrum Music Making
Having spent a fair bit of time over on my other blog looking at ZX Spectrums, I thought it about time I dragged some of that over here and looked at music making 1980s computer style. This shows everything I had back in the day for my ZX Spectrum. The hardware (from left to right): a 128K MIDI lead (5 pin DIN to BT RJ style plug); the RAM Music Machine; and a SpecDrum. The tapes (top left to bottom right): Melbourne House’s The Music Box (for 48K and 128K Spectrum); the RAM Music Machine software; the SpecDrum software. Then on the bottom row the three additional drum kits for the SpecDrum: Electro, Afro and Latin. There are a lots of resources online about all of these devices, but the one-line summary of each is as follows: * 128K MIDI Lead: The Spectrum 128 had a keypad/MIDI socket which hangs off the additional general purpose IO pins from the AY-3-8912 sound chip. * RAM Music Machine: From 1986 this provides MIDI IN, OUT and THRU as well as sample playing at 19.4kHz. * Cheetah SpecDrum: From 1985 this is essentially a DAC with drum machine software, additional kit samples and a drum kit editor to combine samples into a custom kit. Whilst supporting 8 samples, only 3 can be played at the same time. For this first post, I’m taking a bit of a dive into the default MIDI support that appeared with the original Spectrum 128K “Toastrack”. I started with a Spectrum+ but soon upgraded to an original 128K, so this is where I spent most of my own Spectrum days. I still have my original 128K, but I need to give it a bit of TLC – at least a new keyboard membrane – before I can get it out again. ### 128K Spectrum and MIDI There were three major advances (at least as far as I was concerned) with the new 128K Spectrum when it came out: more memory, of course; the AY-3-8912 sound chip (the slightly smaller version of the AY-3-8910); and the prospect of MIDI. It wasn’t full MIDI, that still required some additional peripherals, but with the correct lead, it was possible to use the ZX Spectrum to control a MIDI synthesizer via MIDI OUT. It turns out that lead is a curious beast though – the Spectrum’s implementation of MIDI isn’t following the MIDI spec as such. There are a few resources that discuss what is going on: * https://oldmachinery.blogspot.com/2012/11/spectrum-128-and-midi.html * https://www.benophetinternet.nl/hobby/vanmezelf/ZX_Spectrum_Midi_Out.pdf * https://s-huehn.de/spectrum/en/hardware2.htm#zx128midi * https://sinclair.wiki.zxnet.co.uk/wiki/AY-3-8912 But apart from the odd plug, it is a variant of BT style 6-pin “phone” plug that these days is particularly hard to come by, looking at the schematic for the 128K we can see a bit more what is going on: It isn’t very easy to make out, but the chip on the left is the AY-3-8912 and the two chips in the middle are RS232 driver/receiver chips. They are hanging off the AY-3-8912’s IO port and we can see in the 128K Service Manual what the 8 bits of this port are used for: R16 is the (octal) register 16 (i.e. 14 in decimal) of the AY-3-8912, which corresponds to IO PORT A. R17 would be PORT B, but there is no PORT B on the 8912, that only exists on the 8910. To see how this is used for MIDI, we can refer to the 128K Spectrum ROM Dissassembly: ; ------------------------ ; Send Byte to MIDI Device ; ------------------------ ; This routine sends a byte to the MIDI port. MIDI devices communicate at 31250 baud, ; although this routine actually generates a baud rate of 31388, which is within the 1% ; tolerance supported by MIDI devices. ; Entry: A=Byte to send. L11A3: LD L,A ; Store the byte to send. LD BC,$FFFD ; LD A,$0E ; OUT (C),A ; Select register 14 - I/O port. LD BC,$BFFD ; LD A,$FA ; Set RS232 'RXD' transmit line to 0. ; (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting) OUT (C),A ; Send out the START bit. LD E,$03 ; (7) Introduce delays such that the next bit is output 113 ; T-states from now. L11B4: DEC E ; (4) JR NZ,L11B4 ; (12/7) NOP ; (4) NOP ; (4) NOP ; (4) NOP ; (4) LD A,L ; (4) Retrieve the byte to send. LD D,$08 ; (7) There are 8 bits to send. L11BE: RRA ; (4) Rotate the next bit to send into the carry. LD L,A ; (4) Store the remaining bits. JP NC,L11C9 ; (10) Jump if it is a 0 bit. LD A,$FE ; (7) Set RS232 'RXD' transmit line to 1. ; (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting) OUT (C),A ; (11) JR L11CF ; (12) Jump forward to process the next bit. L11C9: LD A,$FA ; (7) Set RS232 'RXD' transmit line to 0. ; (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting) OUT (C),A ; (11) JR L11CF ; (12) Jump forward to process the next bit. There is more, but this is enough to see how MIDI is encoded as follows: PORT A Value: $FA -> 1111 1010 -> transmit line to 0 $FE -> 1111 1110 -> transmit line to 1 This is toggling the AY’s PORT A A2 bit according to the register usage, which maps over onto the RS232 CTS line. The above schematic extract has been redrawn by others which makes things a bit easier to see what happens next: A2 from the AY chip goes to IC33 which is a MC1488 quad line driver. It is powered from 12V and -12V and generates the RS232 voltages required. RS232 requires a positive and negative voltage with respect to a common ground. For the MC1488 it specifies that for a VCC of +/-13V it generates a high and low voltage output of +/- 9-10.5V. From the datasheet, we can see that the inputs get inverted: The AY A2 pin goes to pins 4 and 5, and thus pin 6 is the output which goes via a 1K resistor to pin 5 of the MIDI/RS232 socket. Unfortunately, the pin numbering for those sockets as listed above (and in the linked document) seem opposite to those used with BT phone sockets (at least I think – it is very confusing trying to figure out which pin is which on the plugs and sockets). The references for telephony I’ve found have pin 1 as the pin nearest the locking latch, but in the above link (and I believe in the schematic above) pin 6 is the one nearest to the locking latch. Looking inside my 128K MIDI cable, and checking for continuity, I can see the following: MIDI pin 4 (the VCC/source connection usually) seems to be connected to BT pin 5 which is RS232 CTS. MIDI pin 5 (the TX/sink connection usually) seems to be connected to BT pin 1 which is GND. Normally with MIDI, when VCC and TX are HIGH (transmitting a “1”), no current flows. But when TX is pulled LOW (for a “0”) it allows a 5mA current loop to flow through an optoisolator, reproducing the signal in an electrically isolated way in the receiving equipment. The best explanation of how MIDI works in my view is this video: https://www.youtube.com/watch?v=0L7WAMFWSgY With the Spectrum circuit, it would appear that the default position is now for what was VCC (now CTS) and TX (now GND) both to be LOW (for a “0”) so no current flows. Actually, as this is generated by the MC1488, we don’t get a “zero” condition, we get a negative voltage condition for LOW. This means that the protection diode in the receiving MIDI circuit will kick in as “VCC” (CTS) is more negative than “TX” (GND) and so it will conduct. The optoisolator will remain off though regardless. So when CTS goes HIGH (for a “1”) it allows current to flow, but how much current? Assuming the 12V through the RS232 line driver (in reality output is slightly less than VCC), and a typical MIDI receiving circuit (so maybe a ~1.5V Vf for the optoisolator and two 220R resistors as per the MIDI spec) we have a current of: * I = (12 – 1.5) / (1000 + 180 + 220 + 200) =~ 6mA. So I guess, although this seems to work somewhat in reverse to a normal MIDI circuit, it would still work fine for any MIDI receiving equipment that is following the MIDI Spec. If it was ever plugged into something that had a non-standard MIDI IN port though – for example, if they skimmed on the opto-isolator or the protection diode, then the receiver may find itself on the wrong end of the Spectrum’s 12V line or even worse, with -12V with respect to GND, which I imagine wouldn’t end well. For this reason, the aforementioned document recommends building a simple transistor circuit to “correct” the Spectrum’s MIDI OUT to make it more aligned to the official MIDI spec. So in summary, the full sequence from assembler to MIDI signals is as follows: A2=0 -> CTS = 1 -> +12V (ish) -> Current loop flows -> MIDI "0" received A2=1 -> CTS = 0 -> -12V (ish) -> Current loop stops -> MIDI "1" received Notice how that whilst A2 corresponds directly to a MIDI “0” or “1” being transmitted, anyone monitoring the hardware MIDI signal will see the CTS signal taking the opposite state due to the fact that the Spectrum’s MIDI link is GND based not VCC based. Goodness knows what anyone would think if they measured the negative state… ### Conclusion Having worked through all this, I’m now looking back with moderate concern at the free abandon of plugging my ZX Spectrum into my treasured Casio CZ3000 back in the day. I was blissfully ignorant of the above, and just had fun programming my synth via the Spectrum’s BASIC MIDI commands. But as far as I know we all survived the experience and so I hope to reunite them one day. But I am now seriously considering some electronics to do things properly. I have a few options; * The aforementioned transistor fix for the 128K as it stands, which I will probably try. It would be good to come back to this post with some oscilloscope traces once I get things up and running. * Getting my RAM Music Machine going. * Building a DIY MIDI interface. Two really interesting candidates are: * https://www.muzines.co.uk/articles/spectrum-midi/7920 * https://www.muzines.co.uk/articles/micromidi/6021 Kevin ### Share this: * Share on X (Opens in new window) X * Share on Facebook (Opens in new window) Facebook * Like Loading... ### _Related_
193 9
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 13/09/2026
Just seen the M-VAVE FM-1. Six op, 128 preset, 12 note polyphony, MIDI, FM synth, currently on Ali for <£50... I do not need another FM synth. I do not need another FM synth. I do not need another...
000 0
Reposted by diyelectromusic
inpc @inpc.go.mxtthxw.art.ap.brid.gy · 08/09/2026
Another little promo for Ramsgate Festival Of Sound, which I'm part of with my piece Process and Reality. Possibly too far a trek for most my Fedi friends but you never know.... x #RamsgateFestivalOfSound #Electronica #Dub #FunnyNoises
001 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 06/09/2026
Thanks for those comments. I think it is more the inconsistencies that annoy - like having four, basically the same, APIs for the four different types of pins is fine. But why do two of them allow a "count" parameter and two don''t? :)
110 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 05/09/2026
And in today's RP2350 API annoyance... sm_config_set_out_pins() compared to sm_config_set_in_pins() and sm_config_set_set_pins() and sm_config_set_sidestep_pins() I mean, why? #Sigh
100 1
Reposted by diyelectromusic
Massimo :bot: @rainmaker1973.zpravobot.news.ap.brid.gy · 04/09/2026
This guy's DIY audio visualizer
540140 140
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 03/09/2026
I'm fighting with the RP2350 SDK again. Anyone used PIO interrupts? Internal "soft" interrupts across PIO/SMs are called IRQs. But there are also two actual interrupts per PIO also called IRQs. So PIO IRQ0 or IRQ1 can be mapped onto any of IRQ0 to IRQ7. But then when attempting to work out the […]
mastodon.social
Original post on mastodon.social
000 0
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 02/09/2026
Does anyone know who V N Larman was? I think that was the name. I seem to have picked up a number of their old ZX Spectrum books and they are wonderful. They really weren't impressed with Personal Computer's "Sixty Programs for the Sinclair ZX Spectrum" I […] [Original post on mastodon.social]
Photo of the introduction to the mentioned book, with additional scribbled annotations in pencil commenting on the text.

Examples:
"real value for money" adding "and this lot isn't"
replacing "sixty fully-developed programs" with "half-developed"
adding "then you are easily pleased" to "exhaustively tested"
062 6
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 28/08/2026
When searching for a technical detail on something obscure and you find yourself on an article discussing in detail one of your own blog posts... I don't think that has ever happened to me before! :)
020 2
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 26/08/2026
And if you want to jump directly to the results... makertube.net/w/88CfTguC2tCaVyJGbK9…
makertube.net
S Ruddy's AY Sound Driver Playing Tim Follin on RC2014
This is an extract showing S Ruddy's AY sound driver for the ZX Spectrum running on an RC2014. It is actually processing the sound OpCodes defined (and composed) by Tim Follin for the soundtrack t...
030 3
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 26/08/2026
This has been a long time coming, but I've finally heard a Tim Follin tune played as coded via the original ZX Spectrum AY sound driver developed by S Ruddy... on an RC2014. diyelectromusic.com/2026/08/26/z80-… #RC2014 #ZXSpectrum #RetroComputers
diyelectromusic.com
273 7
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 24/08/2026
@rrmutt Oh - that article is perfect - thank you very much. I'll have a proper read now :) (I did wonder about reset too - I'm still pondering!)
000 0
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 24/08/2026
Well it might be a really bad idea, but at the moment this seems to be working... Of course I'm not driving anything and have no idea about current sourced by those outputs via the diodes when low... But from a 1.8432MHz oscillator this gives me a 5uS pulse […] [Original post on mastodon.social]
Schematic screengrab showing a 4-pin oscillator driving a CD4017 with Q8 going to a CD4040 and Q9 being the reset.  All outputs of the CD4040 are connected via diodes to the output which is by default pulled high.
000 0
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 24/08/2026
Say I wanted a short pulse at a fairly low frequency from a clock signal, could I use a CD4040 binary counter and use a diode AND gate arrangement on all outputs to only get a pulse when all 12 of the 4040 outputs are HIGH? So not just dividing the freq by 4096 but also the pulse width/4096 (I […]
mastodon.social
Original post on mastodon.social
200 2
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 22/08/2026
So, it turns out that when you do a composite video mod on a vintage computer, your TV can no longer tune into it. I mean who would have thought that changing the mode of video output would change the mode of video output!? I've just spent the last half-hour trying to work out why a video mod […]
mastodon.social
Original post on mastodon.social
100 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 21/08/2026
Why is changing versions of KiCad so painful? What am I doing wrong - I'm sure I mess something up every time and end up with borked libraries... Now I find I've lost the silkscreens of some custom library footprints and even when going back to v9 they've gone. Anyone got a good guide for […]
mastodon.social
Original post on mastodon.social
000 0
Reposted by diyelectromusic
Katharine Hayhoe @katharinehayhoe.com · 21/08/2026
For a customized list of the most effective actions you can take, depending on where you live and what your lifestyle looks like, check out this great resource from @kimberlynicholas.bsky.social and @projectdrawdown.bsky.social
jointheshift.earth
Home
Your guide to meaningful climate action is here! Take the quiz and discover your climate superpowers.
310347 103
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 20/08/2026
This is a fascinating project - a bespoke, dual 14MHz 65C02 gaming architecture with a 1-bit (mono) display... The documentation and design notes are quite extensive and well worth a browse :) (I do like seeing new "retro" projects - i.e. retro techniques or tech in new projects) […]
mastodon.social
Original post on mastodon.social
001 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 18/08/2026
The problem with reading Z80 assembly after many decades on the Internet is I keep reading PUSH AF as a really, really emphasized, "yes they really mean it", push operation. #RetroComputing #Z80
143 4
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 16/08/2026
RE: emalliab.wordpress.com/2026/08/16/z… I've got another ZX Spectrum up and running. This needed a keyboard replacement and comp mod, but then had the weird property that it would boot and pass all diagnostics when running from a diag card with external ROM, but […]
mastodon.social
Original post on mastodon.social
010 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 08/08/2026
Aww, just heard that William Orbit has passed away. I don't know a lot of his work, but his Pieces in a Modern Style are amazing. #WilliamOrbit
101 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 31/07/2026
Nice. My VGA breakout seems to work. Better yet, I've managed to build the RPi Pico scanvideo library using the unofficial Arduino IDE (which I find a lot easier to use than the SDK if I'm honest). #RaspberryPiPico #VGA #Arduino
Photo of a Raspberry Pi Pico connected using 18 jumper wires to a DIY PCB with pin headers, resistors and a VGA socket.  There is a small VGA monitor plugged in showing a "test card" style pattern showing colours (top to bottom): black, red, green, yellow, blue, magenta, cyan, white with the brightest versions on the right shading to dark (almost black) on the left.
131 3
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 29/07/2026
Somewhat amusingly, and potentially ironically, it could have been a 555...
110 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 29/07/2026
Now this is what I call a circuit patch. Can anyone guess what that 8-pin DIP chip might be? I just can't make out the writing... #RetroComputing
Photo of part of a circuit board.  The main board is a green manufactured PCB, but part of it has been cut away and replaced with a brown, homemade board with additional components.  The traces look like they have been drawn on by hand and link up to the traces on the main PCB in places.  There are two chips, one is a 74LS04 but the other has writing only partly visible with most of it worn away.
111 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 28/07/2026
Has anyone got any tips for doing TAP to WAV and getting it to load onto a Spectrum? I've used www.igormaznitsa.com/tap2wav/index.… and fiddled with a range of options but I can't get a level that works. I've used smartphone, dumbphone, MP3 player and adjusted volume levels. I''ve […]
mastodon.social
Original post on mastodon.social
000 0
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 27/07/2026
Whoops. I appear to have increased the number of Z80 based computers in the house by four thanks to the sale at the Retro Collective yesterday... #RetroComputing
001 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 25/07/2026
Right I've drawn a line under it regardless . In my defense, part of my reason for staying theoretical atm is I have a PCB on the way that will actually let me play with it all without lots of jumper wires :) emalliab.wordpress.com/2026/07/25/s…
emalliab.wordpress.com
Simplified Pico VGA
The Raspberry Pi Pico Hardware Design Manual has an example board that shows how an RP2040 can be used to create a VGA signal using a 3-way 5-bit resistor DAC. It is the basis of the Pimoroni Pico VGA Demo Base which makes messing around with it pretty easy. But the downside is that it requires 5 GPIO pins per colour plus H and V sync pins, so that is 17 GPIO pins in total just for the VGA connection. There is a connection diagram in the pico-playground here: https://github.com/raspberrypi/pico-playground/tree/master/scanvideo To use the code requires using the following: * pico_scanvideo library from pico_extras. * Examples from the pico_playground. As you might imagine it uses the Pico’s PIO and I believe DMA, to transfer pixel data over to the GPIO pins. But that is a lot of GPIO for video data, so are there any alternatives? This post looks in varying levels of detail at the following options: * Pico(Q)VGA and DispHSTX * MCEBlaster * pico-zxspectrum And finally then a proper dive into the Scanvideo library provided as part of the Pico Extras and Pico Playground repositories from Raspberry pi. ### Pico(Q)VGA and DispHSTX One option is the really comprehensive PicoVGA project here: https://www.breatharian.eu/hw/picovga/index_en.html (with a fork that is designed to use the standard SDK build process and also supports the RP2350 here and a more minimal PicoQVGA version here). This has a demo circuit that just uses 9 GPIO pins – 3 for red, 3 for green, 2 for blue plus one for Sync, which is a composite sync (CSYNC) for H and V sync combined. This is because it only implements 8-bit resolution VGA. From the website: _“The RP2040 processor contains 264 KB of RAM. This is not much for higher resolution image output and therefore RAM needs to be very sparing. In technical practice and for retro games (the processor does not have the power for more advanced games), 8-bit graphics output in R3G3B2 format (i.e. red 3 bits, green 3 bits and blue 2 bits) is fully sufficient. Output in 16 or 24 bits is not meaningful in normal practice, as the Raspberry Pico does not have enough memory or power to provide such large amounts of data in areas other than short demos. Using the dithering technique, interesting display results can be achieved even with 8-bit output.”_ I suspect the lowest level VGA output part of this library is probably relatively understandable, but the library itself is so comprehensive, it is probably a lot more than I am interested in right now. It boasts a large range of resolutions, various frame buffer modes, colour palettes, transparency, and so on (just browse over the “PicoVGA library features” section). The author has declared this now superseded by DispHSTX, which apparently supports HDMI (DVI) and VGA using the RP2350’s HSPX – the high speed transmit peripheral. There is also a RP2350 specific official HSTX example for driving DVI: https://github.com/raspberrypi/pico-examples/tree/master/hstx. Note: there are several versions of a “DVI sock” for the Pico that hooks up HDMI/DVI to the HSTX compatible GPIO pins (GPIO 12-19). ### MCE Blaster Another really interesting project is the MCE Blaster: _“A versatile standalone MCA/CGA/EGA TTL to VGA adapter based on a raspberry pi Pico.”_ It is a reimplementation of other similar projects (for example this mce adaptor) that tend to use PLDs or programmable logic. This uses a Pico to read video TTL data from MDA, CGA, EGA sources and output an equivalent VGA signal. But as it is just limited to the lower, legacy, resolutions and colour depth, it can do this using just two GPIO per colour, plus H and V, so that is 8 GPIO pins in total. That has the potential to be really useful for me, so digging a bit deeper into how it works, we can see the relevant parts for the VGA output in the schematic: The output works on the same principles as the Pico VGA demo, but instead of using 5:5:5 colour values, there are only 2 bits per colour, or 4 individual levels for each of R, G and B. Here is a video that describes how this works really well: https://www.youtube.com/watch?v=kgDOiGoxKvE. The video shows how the EGA and CGA signals differ: But the end result is a much smaller pallet of colours to be shown on a VGA display. The other thing to note of course is that the TTL signals are digital signals, with up to 2 bits per signal, whereas the VGA output is an analog signal with each of R, G ,B requiring between 0 and 0.7V each. A high resolution colour pallet is created by the Pico when using 5 bits per colour, as it splits the voltage range into 32 different levels. With the 2-bit input range there are only four levels (including 0) corresponding to the digital bits: 00, 01, 10, 11 where 11 will equal 0.7V. The video goes into detail how to use a two-input resistor circuit and which resistor values to choose. The calculations turn out to require R to be 419Ω. The circuit has ultimately chosen to use 390Ω and 780Ω for the two values. Note that horizontal (H) and vertical sync (V) are digital signals, so either HIGH (TTL 5V) or LOW). There is a lot of neat code in the MCE-Blaster to handle synchronisation between the digital and analog signals, especially when it comes to sample the incoming TTL pixel signal. It also has a comprehensive range of adjustments and looks like a really useful tool for anyone wishing to plug in an old graphics card into a VGA monitor. But I’m not going to need any of this if I’m just doing video output. #### MCA Blaster VGA Output Code All the firmware and PCB designs are available on GitHub: https://github.com/scrapcomputing/MCEBlaster There is a lot in the firmware folder, including some algorithmically generated PIO code to support the full range of input options, as well as the use of C++ templates. The key items handling the VGA output side of things are: * firmware/src/Pio/VGAOut4x1Pixels.pio * firmware/src/Pio/VGAOut8x1MDA.pio * firmware/src/VGAWriter.h * firmware/src/VGAWriter.cpp As far as I can make out, the main code is essentially split into two. There is the TTLReader, which runs on core 1, and the VGAWriter which runs on core 0. There is a display buffer (as described by the DisplayBuffer class) shared between them. I’m actually not interested in the MDA support, which supports monochrome, higher resolution, output so will just focus on the EGA/CGA side of things. The VGAOut4x1Pixels.pio code, as you might imagine, outputs four pixels of VGA data to the three 2-bit DACs, with options to set the H or V sync lines if required. The format of the data is four sets of 8-bit values of the form: VHRRGGBB. Note that this is different to that suggested by the comments in the PIO programme, which has V and H swapped over. There are a number of const definitions in the common.h file that define various bit patterns for the colours and H and V signals. Many of these have a _4 variant which creates a 4-pixel version, for example: static constexpr const uint32_t HMask = 0b01000000; static constexpr const uint32_t VMask = 0b10000000; static constexpr const uint32_t HMask_4 = HMask | HMask << 8 | HMask << 16 | HMask << 24; static constexpr const uint32_t VMask_4 = VMask | VMask << 8 | VMask << 16 | VMask << 24; Each of the colours has a definition too: static constexpr const uint32_t Black = 0b000000; static constexpr const uint32_t White = 0b111111; static constexpr const uint32_t Red = 0b110000; ...etc... There is a special set of definitions for use with black which has a special use when outputting the borders: static constexpr const uint32_t Black_4 = Black | Black << 8 | Black << 16 | Black << 24; static constexpr const uint32_t Black4_HV = Black_4 | HVMask_4; static constexpr const uint32_t Black4_V = Black_4 | VMask_4; Given all this pre-amble, I can start to piece together what the code is doing. #### VGAPIO The main PIO programme is managed from VGAWriter, using the following variables: * VGAPio – the PIO instance – pio1 is used for the output. * VGASM – the free statemachine allocated to the VGA output. * VGAOffset – the PIO code offset. There are three other PIO programmes running related to the input: NoInputSignalPio (pio1), VSyncPolarityPio (pio0), and HSyncPolarityPio (pio0). These are part of the input monitoring and so I’m largely ignoring those. There is a PIOProgramLoader helper class that is used to initialise all of the TTL reader PIO routines and the main VGA output PIO (it isn’t used for the three additional PIOs mentioned above). The VGA PIO initialisation is performed in tryChangePIOMode() which will check if a mode switch between EGA/CGA and MDA is required and reinitilise the PIO as required. #### VGA Writer The main functions in the code that I’m interested in are as follows: DrawBlackLineWithMask4x1() Output a black line with appropriate H and V signals DrawLineVSyncHigh4x1() Output a line from the DisplayBuffer with H and V tryChangePIOMode() Check input display mode Select between CGA/EGA and MDA if required Initialise VGA output PIO VGAWriter() Initialise DisplayBuffer Claim unused SM for VGASM Initiliase NoInputSignalPio, VSyncPolarityPio, HSyncPolarityPio (re)start TTLReader on core 1 tryChangePIOMode() drawFrame4x1() Draw back porch with DrawBlackLineWithMask4x1() Draw lines with DrawLineVSyncHigh4x1() Draw front porch with DrawBlackLineWithMask4x1() runForEver() drawFrame4x1() tryChangePIOMode() I would like to dig further into this code at some point, but actually I’m going to pause here for a moment, as I’ve found another simplification that is highly relevant for my eventual application of all of this. ### The pico_zxspectrum Approach One thing that was driving the investigation so far was seeing that the palette for CGA was very similar to that of the ZX Spectrum which is actually what I’d really like to be able to recreate. When I found the pico-zxspectrum project and more specifically the section on VGA and the Spectrum, I noted it has this GPIO configuration for VGA compatible output with ZX Spectrum characteristics (Image from the pico-zxspectrum repository here): This format is described as RGBY1111 and the key things to note are: * Only 5 GPIO pins are required. * It isn’t using VSYNC. * There is a single pin that effectively implements the “brightness” control, which reflects one of the limitations of the ZX Spectrum’s attribute arrangement – brightness affects all colour states within an attribute block. The pico-spectrum project also describes video options for RGB332 and RGB222: _“So far, there are three supported VGA configurations, which can be found in the various build targets. They have all been designed with a combination of plagiarism and guesswork, so please let me know if you have better versions and I will update this document.”_ There is no mention of the use of PIO in this video driver directly. There is a “zxspectrum_pio_all” definition that lists pio programs for audio i2s, ear, pwm, and st7789 video, but nothing that seems tailored to VGA output, but that is because it is using the scan_video code from the pico_extras repository. There are some references to “video_24mhz_composable” which appears in the extras repo. There is a full discussion of how to use the code here: https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_scanvideo There are many different configurations (including the aforementioned RGBY1111, RGB332, RGB222 modes) in src/vga/CMakeLists.txt which has many of the definitions for the scanvideo code, for example, for RGBY1111, I can see: set(ZxSpectrumI2SAudioVga1111Ps2_defines # VGA 1111 VGA_ENC_RGBY_1111 PICO_SCANVIDEO_COLOR_PIN_COUNT=4u PICO_SCANVIDEO_DPI_PIXEL_RCOUNT=2u PICO_SCANVIDEO_DPI_PIXEL_GCOUNT=1u PICO_SCANVIDEO_DPI_PIXEL_BCOUNT=1u PICO_SCANVIDEO_DPI_PIXEL_RSHIFT=2u PICO_SCANVIDEO_DPI_PIXEL_GSHIFT=1u PICO_SCANVIDEO_DPI_PIXEL_BSHIFT=0u PICO_SCANVIDEO_COLOR_PIN_BASE=12u I think what is happening is that there are single bit resolutions for blue and green, but red is configured for two bits. Mapping this up with the schematic we can see that the PIN_BASE of 12 with a PIN_COUNT of 4 means using GPIO 12, 13, 14, 15 which maps onto BGR and Y in the schematic. GPIO 16 is the HSYNC. These definitions will override the default values for the scanvideo driver which can be found in: * pico_extras:src/rp2_common/pico_scanvideo_dpi/include/pico/scanvideo.h * pico_extras:src/common/pico_scanvideo/include/pico/scanvideo/scanvideo_base.h The main code for preparing and handing off data to the scan library can be found in: * src/ZxRgb332RenderLoop.cpp * src/ZxScanlineVgaRenderLoop.cpp * src/ZxSpectrumPrepareRgbScanline.cpp * src/ZxSpectrumPrepareScanvideoScanline.cpp * src/ZxSpectrumVideoDriver.cpp The timings for the different modes for RGBY1111 are detailed in src/ZxScanlineVgaRenderLoop.cpp, for example for 640×480 at 60Hz: const scanvideo_timing_t vga_timing_640x480_60 = { .clock_freq = 25000000, .h_active = 640, .v_active = 480, .h_front_porch = 16, .h_pulse = 96, .h_total = 800, .h_sync_polarity = VGA_H_SYNC_POLARITY, // 1=HSYNC & VSYNC, 5=CSYNC .v_front_porch = 10, .v_pulse = 2, .v_total = 525, .v_sync_polarity = 1, .enable_clock = 0, .clock_polarity = 0, .enable_den = 0 }; This is also where the scanvideo library is initialised and where the key rendering pipeline happens. void __not_in_flash_func(ZxScanlineVgaRenderLoop)( ZxSpectrum &zxSpectrum, volatile uint &frames, bool &showMenu, volatile bool &toggleMenu, ZxSpectrumMenu& picoRootWin ) { if (!_scanvideo_mode) return; scanvideo_setup(_scanvideo_mode); scanvideo_timing_enable(true); unsigned char* screenPtr = zxSpectrum.screenPtr(); unsigned char* attrPtr = screenPtr + (32 * 24 * 8); while (true) { // // Display rendering infinite loop // } __builtin_unreachable(); } These functions are set up in the various structures in src/ZxSpectrumVideoDriver.cpp depending on the build settings. The default is determined by: #if defined(CVBS_12MHZ) || defined(CVBS_13_5MHZ) || defined(VGA_MODE) #include "ZxScanlineVgaRenderLoop.h" #ifndef DEFAULT_VIDEO_DRIVER #define DEFAULT_VIDEO_DRIVER zx_spectrum_video_driver_vga_index #endif #else #define ZxScanlineVgaRenderLoopInit NULL #define ZxScanlineVgaRenderLoop NULL #define ZxScanlineVgaRenderLoopAudioDefault NULL #endif The value of DEFAULT_VIDEO_DRIVER can be overwritten in the makefiles (as it is for the st7789 LCD display for example). The main rendering loop uses the following functions: * scanvideo_begin_scanline_generation() * zx_prepare_scanvideo_blankline() * pcw_prepare_scanvideo_scanline_80() * x_prepare_scanvideo_scanline() * scanvideo_end_scanline_generation() With a couple of callbacks called at appropriate times too. Key to processing the video are two pointers: screenPtr and attrPtr which are set to point to the appropriate locations in the Spectrum video memory and then passed into zx_prepare_scanvideo_scanline(). It also requires two other key variables for each rendering scan: frame_num and y: uint32_t frame_num = scanvideo_frame_number(scanline_buffer->scanline_id); uint32_t y = scanvideo_scanline_number(scanline_buffer->scanline_id); Moving over to src/ZxSpectrumPrepareScanvideoScanline.cpp I can see how the data is processed for each scan line: void __not_in_flash_func(zx_prepare_scanvideo_scanline)( struct scanvideo_scanline_buffer *scanline_buffer, uint32_t y, uint32_t frame, uint8_t* screenPtr, uint8_t* attrPtr, uint8_t borderColor ) { uint32_t* buf = scanline_buffer->data; // // fill in *buff from the data provided by screenPtr and attrPtr // including the top/bottom and side borders where necessary // } There is a lot of bit and number manipulation going on in this function to translate between the Spectrum’s screen data format and the data stream required by the scanvideo library. I’m not going to go into that here (its complicated) but I might come back to this at some point. In places this uses an internal function single_color_run() to fill in a block of the same colour. Note, with regards to some of the other video modes, I can see that src/ZxSpectrumPrepareRgbScanline.cpp is used for the 332 mode, called from src/ZxRgb332RenderLoop.cpp. But although this gets a mention in src/CMakeLists.txt, I can’t see where it would be enabled. set(zxspectrum_rgb332_src ${CMAKE_CURRENT_LIST_DIR}/ZxRgb332RenderLoop.cpp ) set(zxspectrum_rgb_src ${CMAKE_CURRENT_LIST_DIR}/ZxSpectrumPrepareRgbScanline.cpp ) set(zxspectrum_scanvideo_src ${CMAKE_CURRENT_LIST_DIR}/ZxSpectrumPrepareScanvideoScanline.cpp ${CMAKE_CURRENT_LIST_DIR}/ZxScanlineVgaRenderLoop.cpp ) In summary it appears that the main way the VGA video modes link together is something like: src/ZxSpectrumVideoDriver.cpp Setup _zx_spectrum_video_drivers[] Include VGA: { ZxScanlineVgaRenderLoopInit, ZxScanlineVgaRenderLoop, ZxScanlineVgaRenderLoopAudioDefault, "VGA" }, Include DVI Include ST7789 LCD Set DEFAULT_VIDEO_DRIVER unless overridden by CMakeLists.txt VGA is the default if VGA_MODE is set by CMakeLists.txt src/ZxScanlineVgaRenderLoop.cpp ZxScanlineVgaRenderLoop(): CALL zx_prepare_scanvideo_scanline() (using src/ZxSpectrumPrepareScanvideoScanline.cpp) src/ZxRgb332RenderLoop.cpp: ZxRgb332RenderLoop(): CALL zx_prepare_rgb_scanline() (using src/ZxSpectrumPrepareRgbScanline.cpp) At this point so far, I’ve not seen any way for the 332 VGA mode to be enabled or called other than that one mention in the main CMakeLists.txt file. But the main readme does list the following files as example configurations for each of the three video modes: * RGBY 1111: src/vga/CMakeLists.txt * RGB 222: src/picomputer/picomputer_vga_zx/CMakeLists.txt * RGB 332: src/picomputer/picomputer_vga/CMakeLists.txt These certainly do seem to have GPIO pin definitions for 222 and 332 modes, but as far as I can tell it is still using the vga/scanline line rendering code, rather than the 332/vga line rendering code, via all the mechanisms listed above. Whilst on the topic of video modes, these are defined in pico_extras:src/common/pico_scanvideo/include/pico/scanvideo/scanvideo_base.h. The following appear to be options used by the pico_zxspectrum: vga_mode_640x240_60 vga_mode_720x288_50 cvbs_mode_50 As already mentioned the various pin configurations are provided as part of the pico_zxspectrum CMakeLists.txt files and will override the defaults in the scanvideo driver header and source files using compiler directives. Even with all this though, I still can’t figure out how ZxRgb332RenderLoop is ever used 🙂 ### Using the Scanvideo Library In principle I could have started this post with this library and ignored most of the above. But when this is first encountered it isn’t obvious (at least to me) that something like the RGBY1111 mode would even be a possibility. It took seeing how pico_zxspectrum used it to get this far. But knowing that, I can now take a look at how this is possible and hopefully come up with a small test case to show it in action. The official provided examples for using scanvideo can be found here: https://github.com/raspberrypi/pico-playground/tree/master/scanvideo Although there is a “minimal” example, actually I think the “test_pattern” example is perhaps the easiest to understand. The pseudo code is something like the following: Set the vga_mode to vga_mode_320x240_60 main(): Launches video processing on core 1 while (TRUE): IF Space bar pressed THEN: invert colours draw_color_bar(): Use scanvideo_scanline_number() to get line number FOREACH bar of colour: Fill display buffer based on line and x position core1_func(): scanvideo_setup(&vga_mode); scanvideo_timing_enable(true); while (TRUE): scanvideo_scanline_buffer_t *scanline_buffer = scanvideo_begin_scanline_generation(true); draw_color_bar(scanline_buffer); scanvideo_end_scanline_generation(scanline_buffer); The buffer is defined by scanvideo_scanline_buffer as follows (details here): typedef struct scanvideo_scanline_buffer { uint32_t scanline_id; uint32_t *data; uint16_t data_used; uint16_t data_max; void *user_data; uint8_t status; } scanvideo_scanline_buffer_t; This example uses the following definitions as part of constructing the display pixel data: PICO_SCANVIDEO_PIXEL_FROM_RGB5 - Convert from RGB555 to pixel data. COMPOSABLE_COLOR_RUN - For 3 or more pixels of the same colour. COMPOSABLE_RAW_1P - For a single pixel with colour. COMPOSABLE_EOL_SKIP_ALIGN - End marker (no alignment reqd) The PICO_SCANVIDEO_PIXEL_FROM_RGB5 macro converts from three 5-bit R, G and B values into the required scanvideo pixel data format. There is also a RGB8 version too. This is defined in two places. Firstly in src/rp2_common/pico_scanvideo_dpi/include/pico/scanvideo.h: #define PICO_SCANVIDEO_PIXEL_RSHIFT 0 #define PICO_SCANVIDEO_PIXEL_GSHIFT 6 #define PICO_SCANVIDEO_PIXEL_BSHIFT 11 #define PICO_SCANVIDEO_PIXEL_FROM_RGB8(r, g, b) ( \ (((b)>>3)<<PICO_SCANVIDEO_PIXEL_BSHIFT)| \ (((g)>>3)<<PICO_SCANVIDEO_PIXEL_GSHIFT)| \ (((r)>>3)<<PICO_SCANVIDEO_PIXEL_RSHIFT)) #define PICO_SCANVIDEO_PIXEL_FROM_RGB5(r, g, b) ( \ ((b)<<PICO_SCANVIDEO_PIXEL_BSHIFT)| \ ((g)<<PICO_SCANVIDEO_PIXEL_GSHIFT)| \ ((r)<<PICO_SCANVIDEO_PIXEL_RSHIFT)) Or if no other definition exists, then the version from src/common/pico_scanvideo/include/pico/scanvideo/scanvideo_base.h is used instead which is slightly different but similar. Note that many of the scanvideo definitions map over onto scanvideo_dpi definitions, for example: #define PICO_SCANVIDEO_DPI_PIXEL_BCOUNT 5 #define PICO_SCANVIDEO_PIXEL_BCOUNT PICO_SCANVIDEO_DPI_PIXEL_BCOUNT Pulling all the above together, I think this ends up with a buffer format for pixels as follows: R = 5-bit value G = 5-bit value B = 5-bit value 11111 1 54321 09876 5 43210 Scanvideo Pixel = bbbbb ggggg 0 rrrrr So what is going on with that bit 5 of the pixel data? Looking at the shift definitions, I think this is the fourth pin value for the “ALPHA_PIN” as shown below: #define PICO_SCANVIDEO_DPI_ALPHA_PIN 5u #define PICO_SCANVIDEO_DPI_PIXEL_RSHIFT 0u #define PICO_SCANVIDEO_DPI_PIXEL_GSHIFT 6u #define PICO_SCANVIDEO_DPI_PIXEL_BSHIFT 11u There are more details on the basic mechanism in the main scanvideo readme here: https://github.com/raspberrypi/pico-extras/tree/master/src/common/pico_scanvideo #### The scanvideo PIO Program One thing I can’t quite figure out is how winding the mapping down to a single GPIO per colour works with the VGA pio programs. There are two pio programs: * src/common/pico_scanvideo/scanvideo.pio * src/rp2_common/pico_scanvideo_dpi/timing.pio And I can’t see anything that limits the number of bits of pixel data for them to stream out. The scanvideo readme states: _“The default scanline program deals with 16 bit pixels, generally assumed to be 5, 5, 5 of RGB and 1 optional transparency pin (for multiple video planes – see below). The pin numbers are configurable via`PICO_SCANVIDEO_PIXEL_RSHIFT` etc. and `PICO_SCANVIDEO_COLOR_PIN_BASE`“_ The function scanvideo_setup_with_timing() configures GPIO and PIO so I assume is handling the varying GPIO pin definitions. The “Hardware Design with RP2040” guide states the following when describing the example VGA circuit: _“To this end, we can free up a pin by limiting the green channel to five bits, which will make all the channels the same resolution, by removing the green LSB (least significant bit). It is still desirable for RP2040 to process RGB-565 format data, so PIO will still output six bits of green data to the GPIOs; but we can choose not to use the green LSB in the function select register of that particular GPIO, instead letting RP2040 use it for something else”_ Taking a closer look at the scanvideo.pio program, I can see there are two instances of the PIO code. I’m going to look at program “video_24mhz_composable_default”. Some observations right at the start: ; ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. ; ; SPDX-License-Identifier: BSD-3-Clause ; ; Default scanline program (|| means aligned word boundary, | means hword boundary) .program video_24mhz_composable_default .origin 0 ; must load at zero (offsets are hardcoded in instruction stream) .define extra0 0 ; set later by code based on xscale .define extra1 0 ; set later by code (1 more than extra0) ; note bpp must be a factor of 32 .define bpp 16 ;.define bpp 8 That origin statement is very important. From what I can see this code uses direct manipulation of the state machine’s PC register to jump around the code in response to the data stream it is processing. This means that positions of labels in the code are assumed to be relative to instruction position 0. The two parameters extra0 and extra1 appear as [wait] instructions to control the timing. Recall that VGA is an analogue signal, so to get a stream of pixels on the display simply means holding a particular colour signal for a certain length of time. Double the time it is held and the “line” drawn on the display will be twice as long. These parameters are set by the initialising code and I believe will serve to stretch each “pixel” to change the scaling in the horizontal direction. public end_of_scanline_skip_ALIGN: ; || jmp end_of_scanline_skip_ALIGN | ignored || ; was 16 but we just discard the reset of the OSR ; so as to also support 8 bit grayscale out null, 32; public end_of_scanline_ALIGN: ; | jmp end_of_scanline_ALIGN || This shows a principle that is used through the code. There are a number of instruction positions marked by public labels which can be referred to either from within the code as part of a JMP instruction, but more often than not get pulled out of the data stream and fed into an OUT instruction to change the PC register. This is detailed in the “how it works” section of the src/common/pico_scanvideo readme. Each “token” (for example “RAW1P” or “END_OF_SCANLINE_ALIGN”) maps onto a public label within the PIO code, so that when the token gets read from the bit stream, the OUT instruction uses that as an equivalent for the label to a JMP by setting the PC directly to the instruction position matching the label. public entry_point: wait irq, 4 ; todo perhaps change this to out exec, 16... so that we can do multiple things (including setting black pixel) public nop_raw: out pc, bpp So after waiting on IRQ 4 – presumably for timing synchronisation, we see out first “out pc, bpp”. bpp is 16 so this will take 16 bits from the Output Shift Register (OSR), itself filled from the TX FIFO, and use that to set the PC to the first token instruction. After this come a series of different instruction blocks corresponding to each of the token instructions, which are: * RAW1P * RAW2P * RAW_RUN * COLOR_RUN * END_OF_SCANLINE_ALIGN * END_OF_SCANLINE_SKIP_ALIGN * RAW1P_SKIP_ALIGN Each of these has its own PIO block with the instruction to jump to referenced by a public label. The last instruction in each PIO block is then another “out pc, bpp” instruction to grab the next token instruction. Here is the code for COLOR_RUN: public delay_h_0: public color_run: ; | jmp color_run | color | count-3 | out pins, bpp out x, bpp color_loop: public delay_a_1: jmp x-- color_loop [extra1] public nop_extra1: public delay_b_1: out pc, bpp [extra1] We can see 16-bits get written out from the OSR to the pins, then the next 16-bit value is loaded from the OSR in X. What follows is then a delay loop that will keep the value on the pins whilst X decreases down to 0. This has the effect, as previously mentioned, of extending the analogue output by the value read into X, thus creating a “run of coloured pixels” of the same colour. Once the loop is complete the “out pc,bpp” instruction will read the next token for processing. All other tokens are processed in a similar way. The exception is the RAW1P and RAW2P blocks which have a slightly more complex setup: .wrap_target public raw_1p: ; | jmp raw_1p | color | public delay_e_0: out pins, bpp [extra0] out pc, bpp public raw_2p: ; | jmp raw_2p | color | color | public delay_f_1: out pins, bpp [extra1] .wrap Jumping into RAW_1P is fairly easy enough – output 16-bits to pins then grab the next token with the “out pc, bpp”. But notice that if the code jumps into RAW_2P then after the outputting of 16-bits to the pins, the .wrap/.wrap_target will need the code loops back to the RAW_1P section to output a second 16-bit value and then do the “out pc, bpp”. Eventually the token reading will encounter one of the END_OF_SCANLINE_ tokens and execution will jump back to the top and wait on the next IRQ 4 to kick of the whole process once more. The code to initialise the PIO can be found in: src/rp2_common/pico_scanvideo_dpi/scanvideo.c in the function setup_sm(). This is used to set up both the timing and scanline PIO programs. It is called from scanvideo_setup_with_timing() which also initialises buffers, GPIO, timing frequencies, interrupts, DMA, and run-time adjusts the stored PIO code (e.g. sorting out the “extra” parameters – follow what is going on with “modified_program” for the details) prior to calling pio_add_program twice prior to setting up the PIO itself. The actual PIO configuration is performed in scanvideo_default_configure_pio() which sets the pin directions, outvpins, and shift and FIFO configuration. This relies on the following configuration definitions which, as previously mentioned, can be overriden by setting compiler directives in the makefiles: PICO_SCANVIDEO_COLOR_PIN_BASE PICO_SCANVIDEO_COLOR_PIN_COUNT PICO_SCANVIDEO_ALPHA_PIN On the topic of that run-time modification, I think this is all happening in video_24mhz_composable_adapt_for_mode(). This is referenced by a call to pio_program->adapt_for_mode() having previously set this up in the scanvideo_pio_program_t strucutre for the mode. This uses a series of writes to the PIO code referencing the various “public delay_f_0” type statements. These correspond to equivalent [extra0] statements in the PIO code so adjusting the instructions at that point will change the [extra0] instructions. Here is an example: ~~PIO code~~ public delay_b_1: out pc, bpp [extra1] ~~Corresponding adjustment lines~~ int delay0 = 2 * mode->xscale - 2; int delay1 = delay0 + 1; modifiable_instructions[video_24mhz_composable_program_extern(delay_b_1)] |= (unsigned) delay1 << 8u; Two other points to note about the PIO configuration: * Autopull is enabled. * It is configured for DMA. * The FIFOs are joined – presumably this is to give the DMA more buffer headroom. From the PIO end, then I have to conclude: * It only accepts TOKENised streams as mentioned in the scanvideo readme. * 16-bit TOKENs representing colours are passed directly out to the contiguous block of GPIO pins configured for the required video mode. * This implies that for a RGBY1111 mode, each 16-bit TOKEN would only have four significant bits mapped onto the GPIO. At this point I have to return to the pico-zxspectrum code and dig into that pixel scanline creation code to see what is going on. #### How pico-zxspectrum Prepares the Scanvideo Scanline So far I know there are DPI specific compiler definitions detailing 1 GPIO pin for G, B and 2 for R and the main rendering loop was found in ZxScanlineVgaRenderLoop(). This in turn calls zx_prepare_scanvideo_scanline() at various points to translate between ZX Spectrum screen memory and the format required by scanvideo. This can be found in src/ZxSpectrumPrepareScanvideoScanline.cpp. I don’t plan to go through this in detail, just the parts that give a clue as to how the scanvideo buffer is formatted for RGBY1111 use. This has to cope with a number of use cases depending on the X and Y coordinates: * Top and bottom borders are solid colours. * Side borders likewise are special with solid blocks. * It has to keep track of the foreground and background colours for each attribute block. * It has to map onto the Spectrum’s 256 x 192 pixels and 32 x 24 colour resolution. The main colour rendering section is below: for (int i = 0; i < 32; ++i) { uint8_t c = *a++; // Fetch the attribute for the character uint8_t p = *s++ ^ zx_invert_masks[(c >> 7) & m]; // fetch a byte of pixel data uint8_t bci = (c >> 3) & 0xf; // The background colour index uint8_t fci = (c & 7) | (bci & 0x8); // The foreground colour index uint32_t cw[2]; cw[0] = zxd_colour_words[bci]; // The background colour word cw[1] = zxd_colour_words[fci]; // The foreground colour word uint32_t cwb = cw[(p >> 7) & 1]; *buf++ = COMPOSABLE_RAW_RUN | (cwb << 16); *buf++ = (16 - 3) | (cwb << 16); *buf++ = cw[(p >> 6) & 1]; *buf++ = cw[(p >> 5) & 1]; *buf++ = cw[(p >> 4) & 1]; *buf++ = cw[(p >> 3) & 1]; *buf++ = cw[(p >> 2) & 1]; *buf++ = cw[(p >> 1) & 1]; *buf++ = cw[(p >> 0) & 1]; } This loops through the 32 colour attribute blocks on the line and does the following for each block: * Determine the background and foreground colours relevant to the block. * Map the colours over to RGBY1111 values using zxd_colour_words. * Grab a bytes worth of pixel data from the appropriate position in the screen memory. * Start building a COMPOSABLE_RAW_RUN scanvideo line. * For each pixel in p map it either onto the background (cw[0]) or foreground (cd[1]) colour. COMPOSABLE_RAW_RUN is used for three or more VGA screen pixels and has the following format: COMPOSABLE_RAW_RUN | COL1 | N-3 | COL2 | COL3 |...| COLN | Recall that these are 16-bit tokens. We also need to process pixels from right to left as this is the direction of the VGA scan, hence starting with pixel 7. The zxd_colour_words[] is a set of 16 values defined as follows: #define VGA_RGBY_1111(r,g,b,y) \ ((y##UL<<3)|(r##UL<<2)|(g##UL<<1)|b##UL) #define X2(a) ((a) | (a << 16)) static uint32_t zxd_colour_words[16] = { X2(VGA_RGBY_1111(0,0,0,0)), // Black X2(VGA_RGBY_1111(0,0,1,0)), // Blue X2(VGA_RGBY_1111(1,0,0,0)), // Red ... X2(VGA_RGBY_1111(1,1,0,1)), // Bright Yellow X2(VGA_RGBY_1111(1,1,1,1)) // Bright White }; This defines each of the 16 Spectrum colours (8 colours, but with normal and bright of all apart from black, so really 15 colours) in terms of 16-bit tokens, but each token will be in the range 0 to 15 (b0000 to b1111). The X2 macro doubles it up so that each 32-bit value contains the same repeated 16-bit token. Note that we can see how the extra R bit definition is used for the Y (or brightness) as the output order will be YRGB which will ultimately map onto the four GPIO pins. The one remaining quirk is the bit ordering of the GPIO pins though. We saw originally in the makefile definitions for VGA_END_RGBY_1111 that the bit-shifts are R:2, G:1, B:0 meaning that from the GPIO base of 12, when the PIO program outputs these to the mapped GPIO, then B=12, G=13, R=14, and thus Y=15. Here is a fully expanded example of the macros working for bright yellow: X2(VGA_RGBY_1111(1,1,0,1)), // Bright Yellow = X2((1##UL<<3)|(1##UL<<2)|(1##UL<<1)|0##UL) = X2(b0000 0000 0000 1110) = 0x000E000E On first glance of the code the ordering of the first two items in the buffer seems a little odd, but the Scanvideo readme states: “The state machines consumes these low half word first, followed by high half-word (little endian)” This explains the first few lines that fill up the buffer: // Set cwb to foregroud or background depending on state of p bit 7 uint32_t cwb = cw[(p >> 7) & 1]; // reduce cwb to just the HIGH 16-bits and combine *buf++ = COMPOSABLE_RAW_RUN | (cwb << 16); // reduce cwb to HIGH 16-bits again and combine *buf++ = (16 - 3) | (cwb << 16); This is filling the buffer with two scanvideo pixel TOKENs per ZX Spectrum pixel. The resultant buffer will look like the following: [0] = COL1 | RAW_RUN [1] = COL1 | 13 [2] = COL2 | COL2 [3] = COL3 | COL3 [4] = COL4 | COL4 [5] = COL5 | COL5 [6] = COL6 | COL6 [7] = COL7 | COL7 [8] = COL8 | COL8 Why the duplication? The ZX Spectrum resolution is 256×192 pixels wide. But this is just the main display area. There is also a border which takes the full horizontal resolution (in display terms, even if not usable for graphics) to closer to 384 for a TV output. The actual values used are defined in src/ZxSpectrumDisplay.h. Here are the relevant ones: #define ZX_SPECTRUM_SCREEN_WIDTH 256 #define DISPLAY_WIDTH_PIXELS 640 #define DISPLAY_BORDER_PIXELS (DISPLAY_WIDTH_PIXELS >> 1) #define DISPLAY_BORDER_PIXELS_LEFT \ ((DISPLAY_BORDER_PIXELS - ZX_SPECTRUM_SCREEN_WIDTH) >> 1) Which translates to: Left border pixels = (640 / 2 – 256) / 2 = 32. So the total number of “spectrum” pixels in a VGA horizontal screen line is 32 + 256 + 32 = 320 which is half the VGA resolution. Hence the doubling up of each pixel when creating the Scanvideo scan line. It is perhaps clear from this, that a ZX Spectrum video display on a VGA monitor will have slightly smaller borders compared to the original TV (320 vs 384) but the main display will be quite usable with pixel-doubling. I’m not entirely sure what is happening with the vertical resolution, but suspect something similar is going on. It seems to involve either DISPLAY_BLANK_LINES, which I think comes out as 0 as it appears to be using VGA 480 / 2 less 240 for the Spectrum resolution – but elsewhere in the code there are hardcoded references to 192 which is the number I’d expect to see for the Spectrum. So I’m not sure what is going on here, but don’t need to at this stage. ### In summary – Displaying a Spectrum Screen with Scanvideo I think they key points from all the above are: * VGA at the ZX Spectrum resolutions is provided using just four GPIO pins: RGB and Y for brightness. * Scanvideo PIO allows the configuration of a range of video modes. * pico-zxspectrum uses makefile defines to set up RGBY1111 as a video mode. * ZX Spectrum display is parsed to create a series of Scanvideo TOKEN scanlines each containing values for pixel/colour data between 0 and 15. * The Scanvideo TOKEN pixel lines are doubled up to emulate a 320 pixel resolution on a 640 resolution display. * When the PIO outputs the data from the tokens, the 0..15 values translate over to the four GPIO pins used in the order YRGB for GPIO 15 to 12. * I don’t know how the vertical resolution is handled, but suspect very similar things are going on. At this point, I’m going to conclude all the theory (not that anyone will read this far I’m sure) and in the next post I’ll actually attempt to do something with it all! Kevin ### Share this: * Share on Mastodon (Opens in new window) Mastodon * Share on Tumblr (Opens in new window) Tumblr * Share on Facebook (Opens in new window) Facebook * Share on Bluesky (Opens in new window) Bluesky * More * * Share on Reddit (Opens in new window) Reddit * Share on LinkedIn (Opens in new window) LinkedIn * Like Loading... ### _Related_
000 0
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 25/07/2026
When your "notes to self" about trying to figure out how something works gets to around 5,500 words, its probably time to move onto actually doing something with it... :)
100 1
diyelectromusic @diyelectromusic.mastodon.social.ap.brid.gy · 18/07/2026
I'm currently having one of those "I'm sure I've bought some of these but can't find them anywhere" moments that always keep us so thoroughly entertained...
110 1