added joystick to C64 emulation (via numpad keys)

This commit is contained in:
Irmen de Jong 2020-03-01 18:38:00 +01:00
parent 99942748f9
commit 7522d3cb3b
13 changed files with 192 additions and 80 deletions

BIN
c64testprgs/joytest.prg Normal file

Binary file not shown.

BIN
free-c64-roms/basic Normal file

Binary file not shown.

BIN
free-c64-roms/chargen Normal file

Binary file not shown.

BIN
free-c64-roms/kernal Normal file

Binary file not shown.

24
free-c64-roms/readme.txt Normal file
View File

@ -0,0 +1,24 @@
Free/open source roms for the C64
See https://github.com/MEGA65/open-roms
The following copyright notices apply to the entirety of this package,
including each source file, unless otherwise noted in each file or directory.
Copyright Paul Gardner-Stephen, 2019.
Copyright Roman Standzikowski (FeralChil64), 2019-2020.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -9,12 +9,20 @@ import java.awt.event.KeyEvent
/** /**
* Minimal simulation of the MOS 6526 CIA chip. * Minimal simulation of the MOS 6526 CIA chip.
* Depending on what CIA it is (1 or 2), some registers do different things on the C64. * Depending on what CIA it is (1 or 2), some registers do different things on the C64.
* This implementation provides a working keyboard matrix, TOD clock, and the essentials of the timer A and B. * This implementation provides a working keyboard matrix, joystick in port#2 (cia 1),
* time of day clock, and the essentials of the timer A and B.
*/ */
class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) { class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) {
private var ramBuffer = Array<UByte>(endAddress-startAddress+1) { 0 } private var ramBuffer = Array<UByte>(endAddress-startAddress+1) { 0 }
private var regPRA = 0xff private var regPRA = 0xff
// joystick in port 2 configuration (only works on cia#1)
private var joy2up = false
private var joy2down = false
private var joy2left = false
private var joy2right = false
private var joy2fire = false
class TimeOfDay { class TimeOfDay {
private var updatedAt = 0L private var updatedAt = 0L
private var startedAt = 0L private var startedAt = 0L
@ -125,6 +133,11 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu:
timerAset = 0 timerAset = 0
timerBactual = 0 timerBactual = 0
timerBset = 0 timerBset = 0
joy2up = false
joy2down = false
joy2left = false
joy2right = false
joy2fire = false
} }
override operator fun get(offset: Int): UByte { override operator fun get(offset: Int): UByte {
@ -139,71 +152,85 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu:
} }
val register = offset and 15 val register = offset and 15
if (number == 1 && register == 0x01) {
// register 1 on CIA#1 is the keyboard data port if(number==1) {
// if bit is cleared in PRA, contains keys pressed in that column of the matrix // first CIA has keyboard matrix
return when (regPRA) { if(register==0x00) {
0b00000000 -> { // reading $dc00 is joystick in port #2
// check if any keys are pressed at all (by checking all columns at once) return (0b01100000
if (hostKeyPresses.isEmpty()) 0xff.toShort() else 0x00.toShort() or (if(joy2up) 0 else 0b00000001)
} or (if(joy2down) 0 else 0b00000010)
0b11111110 -> { or (if(joy2left) 0 else 0b00000100)
// read column 0 or (if(joy2right) 0 else 0b00001000)
scanColumn(HostKeyPress(KeyEvent.VK_DOWN), HostKeyPress(KeyEvent.VK_F5), HostKeyPress(KeyEvent.VK_F3), or (if(joy2fire) 0 else 0b00010000)).toShort()
HostKeyPress(KeyEvent.VK_F1), HostKeyPress(KeyEvent.VK_F7), HostKeyPress(KeyEvent.VK_RIGHT), } else if(register==0x01) {
HostKeyPress(KeyEvent.VK_ENTER), HostKeyPress(KeyEvent.VK_BACK_SPACE)) // register 1 on CIA#1 is the keyboard data port (and joystick #1...)
} // if bit is cleared in PRA, contains keys pressed in that column of the matrix
0b11111101 -> { // NOTE: we do not emulate a joystick in port #1 as this conflicts with the keyboard.
// read column 1 // just use the joystick in port #2 for now...
scanColumn(HostKeyPress(KeyEvent.VK_SHIFT), // left shift return when (regPRA) {
HostKeyPress(KeyEvent.VK_E), HostKeyPress(KeyEvent.VK_S), HostKeyPress(KeyEvent.VK_Z), 0b00000000 -> {
HostKeyPress(KeyEvent.VK_4), HostKeyPress(KeyEvent.VK_A), HostKeyPress(KeyEvent.VK_W), // check if any keys are pressed at all (by checking all columns at once)
HostKeyPress(KeyEvent.VK_3)) if (hostKeyPresses.isEmpty()) 0xff.toShort() else 0x00.toShort()
} }
0b11111011 -> { 0b11111110 -> {
// read column 2 // read column 0
scanColumn(HostKeyPress(KeyEvent.VK_X), HostKeyPress(KeyEvent.VK_T), HostKeyPress(KeyEvent.VK_F), scanColumn(HostKeyPress(KeyEvent.VK_DOWN), HostKeyPress(KeyEvent.VK_F5), HostKeyPress(KeyEvent.VK_F3),
HostKeyPress(KeyEvent.VK_C), HostKeyPress(KeyEvent.VK_6), HostKeyPress(KeyEvent.VK_D), HostKeyPress(KeyEvent.VK_F1), HostKeyPress(KeyEvent.VK_F7), HostKeyPress(KeyEvent.VK_RIGHT),
HostKeyPress(KeyEvent.VK_R), HostKeyPress(KeyEvent.VK_5)) HostKeyPress(KeyEvent.VK_ENTER), HostKeyPress(KeyEvent.VK_BACK_SPACE))
} }
0b11110111 -> { 0b11111101 -> {
// read column 3 // read column 1
scanColumn(HostKeyPress(KeyEvent.VK_V), HostKeyPress(KeyEvent.VK_U), HostKeyPress(KeyEvent.VK_H), scanColumn(HostKeyPress(KeyEvent.VK_SHIFT), // left shift
HostKeyPress(KeyEvent.VK_B), HostKeyPress(KeyEvent.VK_8), HostKeyPress(KeyEvent.VK_G), HostKeyPress(KeyEvent.VK_E), HostKeyPress(KeyEvent.VK_S), HostKeyPress(KeyEvent.VK_Z),
HostKeyPress(KeyEvent.VK_Y), HostKeyPress(KeyEvent.VK_7)) HostKeyPress(KeyEvent.VK_4), HostKeyPress(KeyEvent.VK_A), HostKeyPress(KeyEvent.VK_W),
} HostKeyPress(KeyEvent.VK_3))
0b11101111 -> { }
// read column 4 0b11111011 -> {
scanColumn(HostKeyPress(KeyEvent.VK_N), HostKeyPress(KeyEvent.VK_O), HostKeyPress(KeyEvent.VK_K), // read column 2
HostKeyPress(KeyEvent.VK_M), HostKeyPress(KeyEvent.VK_0), HostKeyPress(KeyEvent.VK_J), scanColumn(HostKeyPress(KeyEvent.VK_X), HostKeyPress(KeyEvent.VK_T), HostKeyPress(KeyEvent.VK_F),
HostKeyPress(KeyEvent.VK_I), HostKeyPress(KeyEvent.VK_9)) HostKeyPress(KeyEvent.VK_C), HostKeyPress(KeyEvent.VK_6), HostKeyPress(KeyEvent.VK_D),
} HostKeyPress(KeyEvent.VK_R), HostKeyPress(KeyEvent.VK_5))
0b11011111 -> { }
// read column 5 0b11110111 -> {
scanColumn(HostKeyPress(KeyEvent.VK_COMMA), HostKeyPress(KeyEvent.VK_AT), HostKeyPress(KeyEvent.VK_COLON), // read column 3
HostKeyPress(KeyEvent.VK_PERIOD), HostKeyPress(KeyEvent.VK_MINUS), HostKeyPress(KeyEvent.VK_L), scanColumn(HostKeyPress(KeyEvent.VK_V), HostKeyPress(KeyEvent.VK_U), HostKeyPress(KeyEvent.VK_H),
HostKeyPress(KeyEvent.VK_P), HostKeyPress(KeyEvent.VK_PLUS)) HostKeyPress(KeyEvent.VK_B), HostKeyPress(KeyEvent.VK_8), HostKeyPress(KeyEvent.VK_G),
} HostKeyPress(KeyEvent.VK_Y), HostKeyPress(KeyEvent.VK_7))
0b10111111 -> { }
// read column 6 0b11101111 -> {
scanColumn(HostKeyPress(KeyEvent.VK_SLASH), HostKeyPress(KeyEvent.VK_CIRCUMFLEX), HostKeyPress(KeyEvent.VK_EQUALS), // read column 4
HostKeyPress(KeyEvent.VK_SHIFT, rightSide = true), // right shift scanColumn(HostKeyPress(KeyEvent.VK_N), HostKeyPress(KeyEvent.VK_O), HostKeyPress(KeyEvent.VK_K),
HostKeyPress(KeyEvent.VK_HOME), HostKeyPress(KeyEvent.VK_SEMICOLON), HostKeyPress(KeyEvent.VK_ASTERISK), HostKeyPress(KeyEvent.VK_M), HostKeyPress(KeyEvent.VK_0), HostKeyPress(KeyEvent.VK_J),
HostKeyPress(KeyEvent.VK_DEAD_TILDE) // pound sign HostKeyPress(KeyEvent.VK_I), HostKeyPress(KeyEvent.VK_9))
) }
} 0b11011111 -> {
0b01111111 -> { // read column 5
// read column 7 scanColumn(HostKeyPress(KeyEvent.VK_COMMA), HostKeyPress(KeyEvent.VK_AT), HostKeyPress(KeyEvent.VK_COLON),
scanColumn(HostKeyPress(KeyEvent.VK_ESCAPE), HostKeyPress(KeyEvent.VK_Q), HostKeyPress(KeyEvent.VK_ALT), HostKeyPress(KeyEvent.VK_PERIOD), HostKeyPress(KeyEvent.VK_MINUS), HostKeyPress(KeyEvent.VK_L),
HostKeyPress(KeyEvent.VK_SPACE), HostKeyPress(KeyEvent.VK_2), HostKeyPress(KeyEvent.VK_CONTROL), HostKeyPress(KeyEvent.VK_P), HostKeyPress(KeyEvent.VK_PLUS))
HostKeyPress(KeyEvent.VK_BACK_QUOTE), HostKeyPress(KeyEvent.VK_1)) }
} 0b10111111 -> {
else -> { // read column 6
// invalid column selection scanColumn(HostKeyPress(KeyEvent.VK_SLASH), HostKeyPress(KeyEvent.VK_CIRCUMFLEX), HostKeyPress(KeyEvent.VK_EQUALS),
0xff HostKeyPress(KeyEvent.VK_SHIFT, rightSide = true), // right shift
HostKeyPress(KeyEvent.VK_HOME), HostKeyPress(KeyEvent.VK_SEMICOLON), HostKeyPress(KeyEvent.VK_ASTERISK),
HostKeyPress(KeyEvent.VK_DEAD_TILDE) // pound sign
)
}
0b01111111 -> {
// read column 7
scanColumn(HostKeyPress(KeyEvent.VK_ESCAPE), HostKeyPress(KeyEvent.VK_Q), HostKeyPress(KeyEvent.VK_ALT),
HostKeyPress(KeyEvent.VK_SPACE), HostKeyPress(KeyEvent.VK_2), HostKeyPress(KeyEvent.VK_CONTROL),
HostKeyPress(KeyEvent.VK_BACK_QUOTE), HostKeyPress(KeyEvent.VK_1))
}
else -> {
// invalid column selection
0xff
}
} }
} }
} else ramBuffer[register] }
return when (register) { return when (register) {
0x04 -> (timerAactual and 0xff).toShort() 0x04 -> (timerAactual and 0xff).toShort()
@ -224,19 +251,6 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu:
} }
} }
private fun toBCD(data: Int): UByte {
val tens = data/10
val ones = data-tens*10
return ((tens shl 4) or ones).toShort()
}
private fun fromBCD(bcd: UByte): Int {
val ibcd = bcd.toInt()
val tens = ibcd ushr 4
val ones = ibcd and 0x0f
return tens*10+ones
}
override operator fun set(offset: Int, data: UByte) { override operator fun set(offset: Int, data: UByte) {
val register = offset and 15 val register = offset and 15
if (number == 1 && register == 0x00) { if (number == 1 && register == 0x00) {
@ -296,6 +310,19 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu:
} }
} }
private fun toBCD(data: Int): UByte {
val tens = data/10
val ones = data-tens*10
return ((tens shl 4) or ones).toShort()
}
private fun fromBCD(bcd: UByte): Int {
val ibcd = bcd.toInt()
val tens = ibcd ushr 4
val ones = ibcd and 0x0f
return tens*10+ones
}
fun hostKeyPressed(event: KeyEvent) { fun hostKeyPressed(event: KeyEvent) {
val rightSide = event.keyLocation == KeyEvent.KEY_LOCATION_RIGHT val rightSide = event.keyLocation == KeyEvent.KEY_LOCATION_RIGHT
val numpad = event.keyLocation == KeyEvent.KEY_LOCATION_NUMPAD val numpad = event.keyLocation == KeyEvent.KEY_LOCATION_NUMPAD
@ -374,4 +401,15 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu:
} }
} }
} }
fun setJoystick2(up: Boolean, down: Boolean, left: Boolean, right: Boolean, fire: Boolean) {
if(number!=1)
throw NotImplementedError("joystick port 2 is connected to cia#1")
joy2up = up
joy2down = down
joy2left = left
joy2right = right
joy2fire = fire
}
} }

View File

@ -82,16 +82,50 @@ class MainC64Window(title: String, chargen: Rom, val ram: MemoryComponent, val c
// keyboard events: // keyboard events:
override fun keyTyped(event: KeyEvent) {} override fun keyTyped(event: KeyEvent) {}
private var joy2up = false
private var joy2down = false
private var joy2left = false
private var joy2right = false
private var joy2fire = false
override fun keyPressed(event: KeyEvent) { override fun keyPressed(event: KeyEvent) {
// '\' is mapped as RESTORE, this causes a NMI on the cpu // '\' is mapped as RESTORE, this causes a NMI on the cpu
if (event.keyChar == '\\') { if (event.keyChar == '\\') {
cpu.nmiAsserted = true cpu.nmiAsserted = true
} else { } else {
keypressCia.hostKeyPressed(event) if(event.keyLocation==KeyEvent.KEY_LOCATION_NUMPAD) {
// numpad is joystick #2
if (event.keyChar in "789") joy2up = true
if (event.keyChar in "123") joy2down = true
if (event.keyChar in "741") joy2left = true
if (event.keyChar in "963") joy2right = true
if (event.keyChar in "05\n") joy2fire = true
keypressCia.setJoystick2(joy2up, joy2down, joy2left, joy2right, joy2fire)
} else {
keypressCia.hostKeyPressed(event)
}
} }
} }
override fun keyReleased(event: KeyEvent) { override fun keyReleased(event: KeyEvent) {
keypressCia.hostKeyPressed(event) if(event.keyLocation==KeyEvent.KEY_LOCATION_NUMPAD) {
// numpad is joystick #2
if (event.keyChar in "789") joy2up = false
if (event.keyChar in "123") joy2down = false
if (event.keyChar in "741") joy2left = false
if (event.keyChar in "963") joy2right = false
if (event.keyChar in "05\n") joy2fire = false
keypressCia.setJoystick2(joy2up, joy2down, joy2left, joy2right, joy2fire)
} else {
keypressCia.hostKeyPressed(event)
}
}
fun reset() {
joy2up = false
joy2down = false
joy2left = false
joy2right = false
joy2fire = false
} }
} }

View File

@ -216,6 +216,11 @@ class C64Machine(title: String) : IVirtualMachine {
while (cpu.instrCycles > 0) bus.clock() while (cpu.instrCycles > 0) bus.clock()
} }
override fun reset() {
bus.reset()
hostDisplay.reset()
}
override fun executeMonitorCommand(command: String) = monitor.command(command) override fun executeMonitorCommand(command: String) = monitor.command(command)
fun start() { fun start() {

View File

@ -182,6 +182,7 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v
} }
"reset" -> { "reset" -> {
vm.reset()
vm.bus.reset() vm.bus.reset()
updateCpu(vm.cpu, vm.bus) updateCpu(vm.cpu, vm.bus)
} }

View File

@ -242,4 +242,8 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
else keyboardBuffer.pop() else keyboardBuffer.pop()
} }
fun reset() {
// when the reset button is pressed
}
} }

View File

@ -65,6 +65,11 @@ class VirtualMachine(title: String) : IVirtualMachine {
while (cpu.instrCycles > 0) bus.clock() while (cpu.instrCycles > 0) bus.clock()
} }
override fun reset() {
bus.reset()
hostDisplay.reset()
}
override fun executeMonitorCommand(command: String) = monitor.command(command) override fun executeMonitorCommand(command: String) = monitor.command(command)
fun start() { fun start() {

View File

@ -7,6 +7,7 @@ import java.io.File
interface IVirtualMachine { interface IVirtualMachine {
fun step() fun step()
fun pause(paused: Boolean) fun pause(paused: Boolean)
fun reset()
fun getZeroAndStackPages(): Array<UByte> fun getZeroAndStackPages(): Array<UByte>
fun loadFileInRam(file: File, loadAddress: Address?) fun loadFileInRam(file: File, loadAddress: Address?)

View File

@ -1 +1 @@
version=1.8 version=1.9-dev