First of all, SD cards use 3.3 volts power and logic levels, and most SD card holders are surface mount only. So I decided to use the Adafruit microSD card breakout board: this includes a voltage regulator, level shifter and microSD card holder all on a small PCB with standard .1" spacing suitable for breadboarding.
Now that the electrical hook-up was out of the way, I needed a way to make the Z80 and the microSD card talk to each other. Fortunately, the SD card standard allows for SPI communication, mostly for use with microcontrollers. The Z80 doesn't speak SPI, but it's not hard to bit-bang it.
I could have used a 82C55 parallel I/O interface chip (this was used in the original IBM PC for the parallel port, for example), but using a giant 40-pin DIP chip just for 4 data lines seems massive overkill. Instead I used a few 7400-series logic chips to create the interface:
The 74HC541 buffer (the same type I used in the video circuit) is used for data from the SD card to the Z80: when the Z80 issues an "IN" instruction it takes IORQ and RD both low, and the combination of both enables the '541 output drivers, placing the MISO signal from the SD card onto the Z80 data bus.
Note: both the keyboard controller a few posts back and this circuit have no address decoding logic, so they can't be used together, but more on that below.
For the other direction, I used a 74HC175 quad D-type flip-flop. It's essentially being used as a 4-bit register: when the Z80 issues an "OUT" instruction, IORQ and WR are both pulsed low and through the 74HC32 OR-gate generate a clock pulse to the flip-flops updating their state with the value being output by the Z80. Note that I used /Q4 to hook up the microSD enable signal, which is active-low, so that when the flip-flop is reset at startup the enable signal is high (inactive).
I've written some code to perform the SD card initialization sequence and here is what it looks like on the logic analyzer:
Note that since the protocol is SPI, there is a byte being transferred in both directions for each 8 clock transitions and it is up to the higher level protocol whether the MOSI or MISO direction is actually being used. After taking ENABLE low, the Z80 first reads a byte to verify the card is ready, then writes the sequence 0x40 0x00 0x00 0x00 0x00 0x95 which is the SD initialization command (CMD0). It then reads another byte to wait for the SD card's response being ready, which comes in the next byte: 0x01, which indicates successful initialization.
Unfortunately, this is all the software I've written so far for the SD card interface at this point. I do plan to expand it so I can load and save programs on the SD card, just like I used to do on tape with my ZX Spectrum. I'll miss the loading sounds though.
Now, back to the keyboard controller: given that the ATtiny85 used for the keyboard controller can do SPI as well, and there still are a few lines available on the 74HC541 and 74HC175, we can actually eliminate the 74HC595 shift register and hook up the ATtiny85 similar to the SD card.
The keyboard protocol is now changed as follows: the ATtiny's SPI hardware is initialized in slave mode; when a key is ready the ATtiny puts the scan code into the SPI output register and takes the PB0 line high. Once the Z80 sees this line has gone high, it toggles the PB2 line 8 times which causes the SPI hardware in the ATtiny to shift out the bits.
I've updated the code on GitHub for the ATtiny keyboard controller program as well as the Z80 machine monitor program to use the new protocol, which looks like this on the logic analyzer when the "a" key is pressed: