2010.05.03-apple-ii-sid-cia-hardware

Apple II - Interfacing a SID and CIA

That Apple II sure has lousy sound. And no interrupts. Let's interface those two awesome aspects of the Commodore series with our Apples! Thanks Sark for the great idea and the hardware knowhow!

Testing the SID

Quick Applesoft Test of the SID chip

Let's see if we can talk to the sid first. Whipping up a quick BASIC program should pose no particular challenge..

5   S = 50176
10  FOR  N = S TO S + 24: POKE N,0: NEXT N
20  POKE S+24,15
30  FOR T = 1 TO 1000: NEXT
35  F = 0
40  POKE S + 1,F
50  POKE S + 5,12
60  POKE S + 6,4
70  POKE S + 4, 33
70  F = F + 1
80  FOR X = 1 TO 1400: NEXT
90  POKE S + 4,0
100 GOTO 40

That program will play a whole range of tones, incrementing the frequency high byte for oscillator 1 each iteration

Testing the CIA

Testing the CIA means doing tricky things with interrupts. So before we get into that, let us consult http://support.apple.com/kb/TA28361?viewlocale=en_US and give ourselves a quick primer on Apple II interrupts (in BASIC using ROM routines). They have a quick and simple program to demonstrate how to combine a machine language routine and the ONERR statement to modify the execution of an Applesoft program when the interrupt occurs.

Quick Applesoft Interrupt Demo

10   POKE 800,162: POKE 801,100: POKE 802,104: POKE 803,104
20   POKE 804,104: POKE 805,76 : POKE 806,233: POKE 807,242
30   POKE 1022,0 : POKE 1023,8
40   ONERR GOTO 1000
50   PRINT "NO INTERRUPT"
60   GOTO 50
1000 IF PEEK (222) <> 100 THEN END
1010 PRINT "INTERRUPT!!!"
1020 RESUME

Inside the Applesoft Interrupt Demo

The POKES at lines 10 and 20 deserves some additional explanation. http://en.wikipedia.org/wiki/Interrupts_in_65xx_processors tells us all we need to know about the 6502's behavior during an interrupt. The stack gets these 3 bytes pushed onto it in order: high PC register, low PC register, status register. (Unknown whether or not the interrupt bit is set when it is pushed. Does it matter?) (Why is it sending to 0800

 800 / $0320:    LDX #$64               ;; load 100 decimal into x
 802 / $0322:    PLA                    ;; pop saved status register from stack
 803 / $0323:    PLA                    ;; pop low byte of pc register
 804 / $0324:    PLA                    ;; pop hi byte
 805 / $0325:    JMP $F2E9              ;; continue to applesoft error handling routine?

1022 / $03FE:    $00                    ;; low byte of interrupt routine
1023 / $03FF:    $08                    ;; high byte of interrupt routine

Interrupt Demo using the CIA

Let's turn on some interrupts and test it out.

1 T = 0
5 GOSUB 5000
8 POKE 798,88: POKE 799,96
9 CALL 798
10 POKE 800,162: POKE 801,100: POKE 802,104: POKE 803,104
20 POKE 804,104: POKE 805,76 : POKE 806,233: POKE 807,242
30 POKE 1022,0 : POKE 1023,8
40 ONERR GOTO 1000
50 PRINT "NO INTERRUPT"
60 GOTO 50
1000 IF PEEK (222) <> 100 THEN GOTO 2000
1010 PRINT "INTERRUPT!!!"
1012 T = T + 1:IF T > 10 GOTO 6000
1020 RESUME
2000 PRINT "ONERR GOTO REACHED.. PEEK(222) == ";
2010 PRINT PEEK (222)
2020 END
5000 REM RESET CIA, TURN ON INTERRUPTS
5010 S = 49344
5020 POKE S + 5,0 : rem poke 0 into timer high register
5030 POKE S + 4,255: rem poke 255 into timer low register
5040 POKE S + 15, 33: rem change control register A
5050 RETURN
6000 REM GOT TEN INTERRUPTS, TURN THEM OFF
6010 POKE S + 15, 32: rem turn off timer
6020 GOTO 50

Interrupt Test #2

This time we'll try using the SSC in slot #1 to generate transmit interrupts. UPDATE Holy Shit, This Works!!!! =D

300:A2 10 A9 11 9D 8B C0 A9 05 9D 8A C0 58 9D 88 C0
310:A9 AE 20 F0 FD AD 00 C0 10 F6 A2 10 A9 00 9D 8A
320:C0 8D 10 C0 78 60 A9 23 20 F0 FD AD 99 C0 8D 98
330:C0 A5 45 40
3FE: 26 03

(code below)

;; turn on interrupts
300- A2 10     LDX #10       ; ssc in slot 1
302- A9 11     LDA #11       ; set baud rate to 50 baud
304- 9D 8B C0  STA C08B,X
307- A9 05     LDA #5        ; dtr on, xmit interrupt enabled  00000101
309- 9D 8A C0  STA C08A,X
30C- 58        CLI           ; clear interrupt mask, allow 6502 to receive intr
30D- 9D 88 C0  sta c088,X    ; prime transmitter by inserting a byte??

;; print dots..
310- A9 AE     LDA #AE       ; load a period
312- 20 F0 FD  JSR FDF0      ; print it
315- AD 00 C0  LDA C000      ; check keyboard
318- 10 F6     BPL 0310      ; continue if not pressed

;; received keypress, disable interrupts CLEAR KBD STROBE and exit
31A- A2 10     ldx #10       ; again, ssc in slot 1
31C- A9 00     LDA #0        ; reset SSC
31E- 9D 8A C0  STA C08A,X
321- 8D 10 C0  sta c010      ; clear kbd strobe
324- 78        sei           ; disable interrupts
325- 60        RTS           ; return/end

; interrupt handler (print not a dot)
326- A9 23     LDA #23       ; inverse #
328- 20 F0 FD  JSR $FDF0     ; print it
32B- AD 99 C0  LDA C099      ; load status register, to clear irq??
32E- 8D 98 C0  STA c098      ; 'got xmit irq so fill transmitter?'
331- A5 45     LDA $45       ; restore accumulator
333- 40        RTI

3FE: 26 03                   ; set $03FE / $03FF to our interrupt handler

changing slots:
  make these n0 : 301 318
  hardcoded: 329 (n9) 32c (n8)

Interrupt Test 3 - Testing the CIA (for real)

Now that we know that this code works, let's replace the interrupt toggling code with CIA-specifics.

300:A9 00 8D C5 C0 A9 FF 8D C4 C0 A9 01 8D CE C0 EA
310:EA EA EA EA EA EA EA EA EA 58 A9 AE 20 F0 FD AD
320:00 C0 10 F6 A9 00 8D CE C0 8D 10 C0 78 60 A9 23
330:20 F0 FD AD CD C0 EA EA EA EA EA EA EA A5 45 40

3FE: 2E 03                   ; set $03FE / $03FF to our interrupt handler


;; turn on CIA timer A interrupt
300- A9 00     LDA #0
302- 8D C5 C0  STA C0C5     ; timer A high
305- A9 FF     LDA #FF
307- 8D C4 C0  STA C0C4     ; timer A low
30A- A9 01     LDA #01
30C- 8D CE C0  STA C0CE     ; control register A
30F- A9 81     LDA #81
311- 8D CD C0  STA C0CD     ; set MASK
314- EA        NOP
315- EA        NOP
316- EA        NOP
317- EA        NOP
318- EA        NOP
319- 58        CLI           ; clear interrupt mask, allow 6502 to receive intr

;; print dots..
31A- A9 AE     LDA #AE       ; load a period
31C- 20 F0 FD  JSR FDF0      ; print it
31F- AD 00 C0  LDA C000      ; check keyboard
322- 10 F6     BPL 0E1A      ; continue if not pressed

;; received keypress, disable interrupts CLEAR KBD STROBE and exit
324- A9 00     LDA #0        ; reset CIA?
326- 8D CE C0  STA C0CE      ; control register A
329- 8D 10 C0  sta c010      ; clear kbd strobe
32C- 78        sei           ; disable interrupts
32D- 60        RTS           ; return/end

; interrupt handler (print not a dot), read/reset interrupt
32E- A9 23     LDA #23       ; inverse #
330- 20 F0 FD  JSR $FDF0     ; print it
333- AD CD C0  LDA C0CD      ; read from INTR CTRL REG - toss result but required
336- EA        NOP           ;    to reset interrupt!!!!
337- EA        NOP
338- EA        NOP
339- EA        NOP
33A- EA        NOP
33B- EA        NOP
33C- EA        NOP
33D- A5 45     LDA $45       ; restore accumulator
33F- 40        RTI

3FE: 2E 03                   ; set $03FE / $03FF to our interrupt handler

Conclusion

Success! The CIA generates interrupts. Not exactly clear on the correlation between timer HIGH/LOW byte and actual millisecond timing - apparently it counts phi-0 clock pulses from the Apple II bus. Let's figure out the math!

Stay tuned for part TWO of this series. These URLs look promising: