So here I am, two full days into my RetroChallenge project – and not a lot to show for it yet. But half an hour ago I was at “nothing to show for it”, so that’s an improvement.
I decided to start on my “disk controller” by figuring out the communication between the 6502 computer and an Arduino (which will hold the SD card, and which I’ll replace with a bare ATmega later on). I’ve done this before with an SPI protocol, but this time I wanted to upgrade and use a parallel port for communications. I figured I’d be able to read or write an entire byte at a time, and that would make my life easier further down the road.
Yeah, what’s that old Donald Knuth saying? “Premature optimization is the root of all evil?” Yeah. He ain’t kidding.
The 6522 VIA that I use for the P:65 computer’s IO actually has two parallel ports. I’m using one of them for my bit-banged SPI circuit, but the other one, Port A, was still available. The 6522 also supports handshaking using two additional lines, CA1 and CA2. The computer signals the peripheral (the Arduino) with CA2, and the peripheral signals back using CA1. It seemed like it should be possible to manage two-way communication using these signals.
The VIA’s datasheet gives a reasonably good description of how this works, but there are a lot of settings, and what I really could’ve used was an example or two. Unfortunately, Googling mostly just gave me links back to my own blog. (Hey, Google: personalized search results are nice, but that’s not helping!)
I went through quite a bit of trial and error getting this to work. I experimented with the 6522’s output latches before deciding to just leave them off, and tried several different modes for the handshake signals.
When the VIA wants to write to the Arduino, it puts a data byte on the parallel port and then pulses CA2 (the VIA is set to do this automatically whenever the port is read from or written to). Then, the P:65 waits for a pulse on CA1 which indicates that the Arduino has read the byte.
When the Arduino writes to the VIA, the Arduino puts data on the port and pulses CA1. Then it waits for the P:65 computer to pulse CA2. Of course, both sides need to be on the same page about who’s writing and who’s reading, or the whole thing breaks down.
I’m not doing any significant interrupt processing on the P:65 yet, so when it’s the P:65’s turn to wait I’m just busy-waiting on the 6522 Interrupt Flag Register. That’s actually where my biggest problem came from: When I do a write followed by a read, the P:65 is waiting on the IFR twice in a row without doing anything else. In this particular case, I need to manually clear the IFR between the two waits. If I’d read or written to the data port, that would’ve automatically cleared the interrupt flag. That sounds simple, but it took me a couple hours with the logic analyzer to puzzle out what was actually happening.
I’ll mention one debugging trick I discovered while working on this. On the Arduino side of this circuit, there are three different places where the code sends a pulse on CA1, and I was having a hard time telling them apart when I was using the logic analyzer. I added some delay commands so that each pulse had a different duration, and suddenly I was able to see exactly which one was which.
Here’s a bit of code to read a character from the Arduino. We actually send one character (a read command) and then read back two more (a status byte and the actual data). I want to emphasize that this code has worked for me exactly once; I’m sure there’ll be changes once I start really using it.
; Get character. Returns result in A, and Carry is true if ; a character was read. SD_GETC: lda #%00001010 sta VIA_PCR ; set CA2 to pulse mode, CA1 negative edge trigger lda #$ff ; data port to write mode sta VIA_DDRA lda #$19 ; tell the Arduino I want to read a byte sta VIA_DATAA ; write to port A ; wait for arduino to read @wait: lda VIA_IFR ; busy wait for IFR bit 1 to go high and #$2 beq @wait lda #$FF sta VIA_IFR ; clear the interrupt register stz VIA_DDRA ; data port to read mode ; wait for arduino to write status byte @wait3: lda VIA_IFR ; busy wait for IFR bit 1 to go high and #$2 beq @wait3 lda VIA_DATAA ; read status byte (also clears IFR) ; status 0 = good, 1 = not ready, 2 = eof bne @nochar ; wait for arduino to write data byte @wait2: lda VIA_IFR ; busy wait for next char - real data and #$2 beq @wait2 lda VIA_DATAA ; retrieve actual character read sec ; read a byte, so set carry rts @nochar: clc rts ; no char read, so clear carry & return