Flashpoint
This was a piece of yak shaving for Wren, which is a whole story in and of itself, so I'll give you a potted summary. Wren is a 6502 computer I've been working on on-and-off for a year or two now. Most of the 6502 material on the modern web suggests using a 28C256 EEPROM as the computer's ROM, but when I was ordering parts for Wren they were WAY more expensive than flash chips, so in the interest of saving money I went with flash. Read operations are the same so it's not like the computer cares. I realise this preamble is slightly redundant because I still would have had to make or buy a programmer if I went with EEPROM, but flash is different so I thought I'd mention the rationale.
When you have programmable ROM of any kind in your computer, you want a way to program it. With EEPROM this is easy - you just poke the /WE pin (sometimes with a high voltage) and it programs. With flash, however, you need to erase and write an entire block at once, and modern flash chips have what's called "software data protection", where you need to poke /WE and the address/data lines in a specific pattern to allow erases and writes to go through. This is done to prevent wierd conditions during startup from accidentally erasing the ROM.
ANYWAY. I didn't want to spend money on a pre-built flash programmer, so I decided to build my own. An ATMega88 (which is what I had lying around) does not have enough GPIO pins to drive all the data, address and control lines on the flash chip by itself, so my first draft used an MCP23017 IO expander that I also happened to have lying around. The MCP23017 works over I2C, and Adafruit have a library for it, which made programming simple (although probably not as simple as it should have been). I wired everything up on a breadboard, wrote a bunch of code, stared at the datasheet for a while, wrote a bunch more code, and eventually got it working. And then when it came to move it over to stripboard, I basically started over.
The thing about I2C is it's not very fast. And the thing about Adafruit's code is it's not very fast. So when I rebuilt the entire programmer around shift registers controlled using the AVR's onboard SPI, it was a lot faster. I'd like to take a moment here to point out that Adafruit's MCP23017 library and its dependencies used about 4kB of flash and several hundred bytes of RAM, on a device which only has 8kB of flash and 2kB of RAM. That's more than the entire finished program uses!
The other main components of Flashpoint are the control protocol and the host program. A flash programmer is useless if you have to way to give it data to program, right? The first version of the protocol was very slow, exchanging single bytes in hexadecimal and sending a full address each time. This gave it an overhead of several hundred percent - definitely not the best use of a low-baud serial connection. Later I went back and designed a protocol that essentially streamed 16 bytes at a time and used an address counter on the AVR. This was (unsurprisingly) a lot faster. The host program is written in Rust, and is pretty simple. All it has to do is tell the programmer which sectors to erase, then set the address pointer and throw bytes at it until it runs out. It takes something like 12 seconds to write a 4kB sector, which I could probably improve if I really tried, but for my purposes it's more than fast enough.
You can check out the code and Kicad schematics here.