Initial commit: C64 project skeleton with oscar64 submodule

This commit is contained in:
ballz
2026-07-17 00:36:14 +02:00
commit cbe5cf6a47
31 changed files with 29332 additions and 0 deletions
@@ -0,0 +1,58 @@
# Interrupts (IRQ / NMI / BRK)
Source: https://www.c64-wiki.com/wiki/Interrupt
The C64 with its 6510 CPU supports two types of interrupt: **IRQ** (Interrupt Request, maskable) and **NMI** (Non-Maskable Interrupt). The CPU has the option of ignoring IRQ (via the I flag in the status register, set with `SEI`, cleared with `CLI`), but must respond to NMI.
The first thing the CPU does for either is push the program counter and status register onto the stack. Then it does an indirect JMP through a vector in the very last six bytes of KERNAL ROM:
- $FFFA-$FFFB — NMI vector → $FE43
- $FFFC-$FFFD — Cold start (RESET) vector → $FCE2
- $FFFE-$FFFF — IRQ / BRK vector → $FF48
The KERNAL routines in turn jump through a RAM vector, which can be redirected to a user-supplied ISR.
## IRQ flow
1. CPU pushes PC and P, jumps via ($FFFE) to $FF48.
2. $FF48 pushes A, X, Y onto the stack:
```asm
.C:ff48 48 PHA ; push A
.C:ff49 8A TXA
.C:ff4a 48 PHA ; push X
.C:ff4b 98 TYA
.C:ff4c 48 PHA ; push Y
; Stack now (top to bottom): PC_hi, PC_lo, P, A, X, Y
.C:ff4d BA TSX
.C:ff4e BD 04 01 LDA $0104,X ; load saved P
.C:ff51 29 10 AND #$10 ; test BREAK flag
.C:ff53 F0 03 BEQ $FF58
.C:ff55 6C 16 03 JMP ($0316) ; BRK vector
.C:ff58 6C 14 03 JMP ($0314) ; IRQ vector
```
3. Default IRQ vector at $0314-$0315 = $EA31 (KERNAL IRQ routine: maintains jiffy clock, scans keyboard for RUN/STOP, blinks cursor).
4. KERNAL exits via $EA81 (pops A/X/Y and RTI).
So a custom IRQ routine has the stack laid out as: PC_hi, PC_lo, P, A, X, Y (top to bottom). Your ISR ends with `PLA : TAY : PLA : TAX : PLA : RTI` (or just `JMP $EA81` if you want the KERNAL to handle the standard jobs first).
## NMI flow
1. CPU pushes PC and P, jumps via ($FFFA) to $FE43.
2. NMI routine sets the I flag (masking further IRQs), saves A/X/Y, then jumps via $0318-$0319 (NMI RAM vector).
3. Default NMI vector at $0318-$0319 = $FE47.
4. If a cartridge is present, NMI is handed to it via vector at $8002-$8003.
5. RUN/STOP+RESTORE triggers NMI which is treated as a soft reset (BASIC warm start).
## Interrupt sources on the C64
- **CIA 1** (IRQ): Timer A/B underflow, TOD=alarm, serial byte complete, FLAG pin (cassette)
- **CIA 2** (NMI): mostly RS-232 via FLAG pin, RESTORE key
- **VIC-II** (IRQ): raster match, sprite-sprite collision, sprite-data collision, light pen
## Tricks
- Disable interrupts in BASIC (kills keyboard): `POKE 56334, PEEK(56334) AND 254`
- Re-enable: `POKE 56334, PEEK(56334) OR 1`
- RUN/STOP+RESTORE: triggers NMI → soft reset
+57
View File
@@ -0,0 +1,57 @@
# Joysticks (Control Ports)
Source: https://www.c64-wiki.com/wiki/Joystick
A joystick is a gaming control device. The C64 uses the standard first seen on the Atari 2600 — eight directions and one fire button. The stick mechanically activates four switches (up/down/left/right); pushing diagonally activates two. Some joysticks have two fire buttons but they appear identical to software.
The switches and button connect to the CIA #1 ports A and B (in parallel with the keyboard matrix), which is why a joystick — especially on port 1 — can cause the machine to "type" characters when you operate it.
## Reading a joystick
- Port #1 (right port): read via $DC01 (CIA 1 PRB)
- Port #2 (left port): read via $DC00 (CIA 1 PRA)
In each byte, the bits are active-low (0 = pressed):
- Bit 0 (1) — Up
- Bit 1 (2) — Down
- Bit 2 (4) — Left
- Bit 3 (8) — Right
- Bit 4 (16) — Fire
## Typical values (rest position, no buttons)
| Position | Port 1 ($DC01) | Port 2 ($DC00) | +Fire (Port 1) | +Fire (Port 2) |
|----------|----------------|----------------|----------------|----------------|
| middle | 255 | 127 | 239 | 111 |
| up | 254 | 126 | 238 | 110 |
| down | 253 | 125 | 237 | 109 |
| left | 251 | 123 | 235 | 107 |
| right | 247 | 119 | 231 | 103 |
| up+left | 250 | 122 | 234 | 106 |
| up+right | 246 | 118 | 230 | 102 |
| down+left| 249 | 121 | 233 | 105 |
| down+right|245 | 117 | 229 | 101 |
## Keyboard collision
Keyboard scanning uses the same CIA port bits, so reading the joystick will "see" pressed keys. To disable the keyboard while polling: `POKE 56322, 224` (write %11100000 to CIA 1 DDRA so PRA pins are inputs, leaving only the rows set as output — wait, that is the opposite of "disable keyboard". The actual recipe to disable keyboard scanning is to disable CIA 1 interrupts or to set all keyboard columns to inputs).
The simpler approach is to mask out the keyboard bits and only test the low 5 bits of the joystick.
## Analog inputs
The control ports also provide +5V and two analog lines (designed for paddles) — the SID reads these as 8-bit values via $D419 (X) and $D41A (Y).
## Sample BASIC polling
```basic
10 J = NOT PEEK(56321)
20 PRINT CHR$(147);"JOYSTICKTEST"
30 IF (J AND 1) THEN PRINT "1-U ";
35 IF (J AND 2) THEN PRINT "1-D ";
40 IF (J AND 4) THEN PRINT "1-L ";
45 IF (J AND 8) THEN PRINT "1-R ";
50 IF (J AND 16) THEN PRINT "1-F ";
55 GOTO 10
```
+96
View File
@@ -0,0 +1,96 @@
# Raster Interrupts
Source: https://www.c64-wiki.com/wiki/Raster_interrupt
A raster interrupt is an interrupt trigger signal that the VIC-II can supply, if desired, to the CPU whenever the raster in the VIC's video signal reaches a specific line. With machine code programming, this mechanism can be exploited to perform many kinds of VIC "trickery" — having both text and high-res graphics on screen simultaneously, displaying more than eight hardware-supported sprites at once. This is heavily used on the C64/C128 for computer games and demos.
The Atari 800, MSX, and Amstrad CPC also support raster interrupts. The 80-column mode on the C128/VDC cannot.
## Setting up a raster interrupt
By default, the system is set up to receive timer-based signals from CIA-1's Timer A. To use the VIC raster interrupt:
```asm
Init SEI ; disable IRQ
LDA #%01111111
STA $DC0D ; switch off interrupt signals from CIA-1
AND $D011 ; clear MSB of VIC raster
STA $D011
STA $DC0D ; acknowledge pending CIA-1 IRQs
STA $DD0D ; acknowledge pending CIA-2 NMIs
LDA #210 ; set raster line where interrupt shall occur
STA $D012
LDA #<Irq
STA $0314 ; set IRQ vector
LDA #>Irq
STA $0315
LDA #%00000001
STA $D01A ; enable raster interrupt
CLI ; re-enable IRQ
RTS
```
Note: enabling raster interrupts from the VIC takes place *after* setting up everything else. The routine starts with `SEI` because if the interrupt is enabled before e.g. the vector is re-directed, an interrupt may occur while the vector is being altered, sending the CPU to a "random" address and crashing the system.
## Single ISR example (wiggle border)
```asm
Irq LDA #$07
STA $D020 ; border = yellow
LDX #$90 ; delay ~half a millisecond
Pause: DEX
BNE Pause
LDA #$00
STA $D020 ; border = black
ASL $D019 ; acknowledge raster IRQ
JMP $EA31 ; into KERNAL standard ISR (handles cursor blink, etc.)
```
The yellow stripe across the border is exactly 8 raster lines wide, set by the pause loop length.
## Multiple raster ISRs (split-screen: hires top, text bottom)
The "concatenated" pattern — two routines handling two different raster lines, each setting up the next:
```asm
Irq LDA $D011
AND #%11011111
STA $D011 ; switch to text mode
LDA #<Irq2
STA $0314
LDA #>Irq2
STA $0315
LDA #$0
STA $D012 ; next IRQ at line 0
ASL $D019 ; acknowledge
JMP $EA31 ; into KERNAL ISR
Irq2 LDA $D011
ORA #%00100000
STA $D011 ; switch to bitmap mode
LDA #<Irq
STA $0314
LDA #>Irq
STA $0315
LDA #210
STA $D012 ; next IRQ at line 210
ASL $D019
JMP $EA81 ; shorter ROM routine — just restore regs and RTI
```
Only one of the chained routines needs to jump to the full KERNAL ISR; the others can exit through $EA81 (the register-restore stub) for speed.