Capturing Mac SE's video from PDS
This is a project to directly capture a Mac SE's video memory writes using an stm32f4discovery board. The board is simply wired straight to the Mac SE's Processor Direct Slot (PDS).
SetupIn this setup, I have the Mac SE's logic board removed from the case and sitting on the desk, using a 20pin ATX power supply extension cable. The extension cable lines up nicely with the connector on the logic board, with the extra pins merely overhanging, allowing the logic board to run outside the case quite nicely.
The stm32f4discovery board is connected to the PDS slot with regular old jumper wire, with a pin on one end and a female jumperblock on the other. The pins are pushed into the SE's PDS connector. The stm32f4discovery is acting as a USB VCP (Virtual COM Port, aka serial) to a Mac OSX host computer. The discovery board sniffs writes to the video memory, and maintains a shadow copy of the framebuffer internally. The OSX host then polls the discovery board over USB and pulls down copies of the framebuffer, and displays it locally.
FirmwareThe discovery board is configured to interrupt when the R/W pin goes low, indicating a write is occuring. It then waits for /AS to go low indicating the address is on the bus, reads that, and makes sure it is in the range of the framebuffer. The Mac SE has 2 video buffers that get toggled between. The "main" video buffer starts at the top of memory minus 0x5900. The "alternate" video buffer starts at 0xD900. Mac SE memory extends from 0x0 to 0x3FFFFF, with the system ROM starting at 0x400000. The current implementation assumes the machine has the maximum 4MB memory installed, and only looks at the "main" video buffer. If a write occurs between 0x3FA700 and 0x3FFFFF inclusive, it gets written to a local buffer on the discovery board, maintaining a copy of the video memory. When a request comes in over the USB connection, the discovery board will send a copy of the video memory over USB. When a request for a frame comes in over USB, the controller first waits for /IPL0 to go low. /IPL0 is the interrupt pin shared by both the VIA that controls the vertical blanking interrupt, and the SCSI controller.
The firmware initially started from the example USB VCP program here, which is largely a wrapper around sample code from ST. Little of the original sample remains, as several bugs needed to be fixed in the included ST USB code. First, the VCP transmit routine buffers the data to be transmitted, and wraps back to index 0 if the buffer overflows, as if it were a circular buffer. However, 1) there's no checking of the buffer to ensure the producer doesn't overtake the consumer, and 2) the consumer treats the buffer as a linear buffer, not a circular buffer. So, transmit is kinda broken out of the box. The second problem with the sample implementation is in USB, the host assumes the current immediate transfer is over if it receives a less than full sized frame (typically 64 bytes in FullSpeed mode). If the last frame sent is the full size, it needs to send a second zero sized packet to indicate it is done. The ST implementation doesn't do this. It looks like this issue was reported with a fix in 2012, but it's still not fixed in ST's sample code AFAICT. The report with fix I found here.
Host SoftwareOn the OSX host side, there is a simple application that fetches frames over the USB serial connection, and uses CGImage on to display the SE's framebuffer in a 512x342 window. The only real trick here was the SE's black and white display was inverted from CoreGraphics' representation, so a custom color space was needed. Fortunately, a 1bit indexed color map wasn't very tedious to make!
ConnectionsThe connections are as follows:
|Mac Side||stm32f4discovery side|