Skip to content

Commit

Permalink
Support CoCo keyboard input!
Browse files Browse the repository at this point in the history
* Add CoCo keyboard matrix translations
* Add a work-a-round for differend keyboard polling
  • Loading branch information
jedie committed Aug 6, 2014
1 parent 7a3443e commit fd88b5c
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 106 deletions.
3 changes: 2 additions & 1 deletion CoCo_test.py
Expand Up @@ -65,11 +65,12 @@ def run(self):

setup_logging(log,
# level=1 # hardcore debug ;)
# level=10 # DEBUG
# level=10 # DEBUG
# level=20 # INFO
# level=30 # WARNING
# level=40 # ERROR
level=50 # CRITICAL/FATAL
# level=60
)
c = Dragon32()
c.run()
Expand Down
5 changes: 1 addition & 4 deletions README.creole
Expand Up @@ -14,11 +14,9 @@ Future goals are:
=== Current state
The Dragon 32 / 64 ROM works in Text mode.
The Dragon 32 / 64 and CoCo ROMs works in Text mode.
Also the "single board computer" ROMs sbc09, Simple6809 and Multicomp6809 works well.

CoCo Color BASIC v1.3 boots up and colored cursor is alive. Input doesn't work, yet.


=== ROMs

Expand Down Expand Up @@ -200,7 +198,6 @@ e.g. The Dragon 32 6809 machine with a 14.31818 MHz crystal runs with:

# Reimplement the CLI
# Fast save/load BASIC programm listings direct into RAM
# Support CoCo ROMs
# Use bottle for http control server part
# implement more Dragon 32 periphery
Expand Down
8 changes: 8 additions & 0 deletions dragonpy/CoCo/config.py
Expand Up @@ -15,9 +15,12 @@
from dragonpy.Dragon32.config import Dragon32Cfg
from dragonpy.CoCo.mem_info import get_coco_meminfo
from dragonpy.utils.logging_utils import log
from dragonpy.core.configs import COCO
from dragonpy.Dragon32.keyboard_map import get_coco_keymatrix_pia_result


class CoCoCfg(Dragon32Cfg):
CONFIG_NAME = COCO
MACHINE_NAME = "CoCo"

# How does the keyboard polling routine starts with?
Expand All @@ -43,7 +46,9 @@ class CoCoCfg(Dragon32Cfg):

DEFAULT_ROM = os.path.join(
os.path.abspath(os.path.dirname(__file__)),
# "Color Basic v1.2 (1982)(Tandy).rom"
"Color Basic v1.3 (1982)(Tandy).rom"
# "ExtendedColorBasic1.1.rom"
)

def __init__(self, cmd_args):
Expand Down Expand Up @@ -85,6 +90,9 @@ def get_initial_RAM(self):

return mem

def pia_keymatrix_result(self, char_or_code, pia0b):
return get_coco_keymatrix_pia_result(char_or_code, pia0b, auto_shift=True)


config = CoCoCfg

Expand Down
104 changes: 57 additions & 47 deletions dragonpy/Dragon32/MC6821_PIA.py
Expand Up @@ -19,12 +19,11 @@
"""

from dragonpy.utils.logging_utils import log
from dragonpy.Dragon32.keyboard_map import get_dragon_pia_result, \
get_dragon_col_row_values
from dragonpy.utils.humanize import byte2bit_string
from dragonpy.utils.bits import is_bit_set, invert_byte, clear_bit
import os
import Queue
from dragonpy.core.configs import COCO


class PIA_register(object):
Expand Down Expand Up @@ -78,11 +77,13 @@ def __init__(self, cfg, memory):

self.empty_key_toggle = True
self.input_queue = Queue.Queue()# maxsize=10)
# for char in 'AAABBBCCC111222':self.input_queue.put(char)
# for char in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':self.input_queue.put(char)
# for char in 'PRINT "HELLO WORLD!"\r':self.input_queue.put(char)
# for char in ("d", 50, "D", "\r", "A", "\r", "B", "\r", "C", "\r"):self.input_queue.put(char)
self.current_input_char = None



self.input_repead = 0

#
# TODO: Collect this information via a decorator similar to op codes in CPU!
#
Expand All @@ -106,7 +107,7 @@ def __init__(self, cfg, memory):
self.memory.add_write_byte_callback(self.write_PIA1_A_control, 0xff21) # PIA 1 A side Control reg.
self.memory.add_write_byte_callback(self.write_PIA1_B_data, 0xff22) # PIA 1 B side Data reg.
self.memory.add_write_byte_callback(self.write_PIA1_B_control, 0xff23) # PIA 1 B side Control reg.

# Only Dragon 64:
self.memory.add_read_byte_callback(self.read_serial_interface, 0xff04)
self.memory.add_write_word_callback(self.write_serial_interface, 0xff06)
Expand Down Expand Up @@ -181,7 +182,6 @@ def write_serial_interface(self, cpu_cycles, op_address, address, value):
#--------------------------------------------------------------------------
# Keyboard matrix on PIA0

COUNT = 0
def read_PIA0_A_data(self, cpu_cycles, op_address, address):
"""
read from 0xff00 -> PIA 0 A side Data reg.
Expand All @@ -197,61 +197,70 @@ def read_PIA0_A_data(self, cpu_cycles, op_address, address):
"""
pia0b = self.pia_0_B_register.get() # $ff02

if pia0b == self.cfg.PIA0B_KEYBOARD_START: # FIXME
if self.empty_key_toggle:
# Work-a-round for "poor" dragon keyboard scan routine:
# The scan routine in ROM ignores key pressed directly behind
# one another if they are in the same row!
# See "Inside the Dragon" book, page 203 ;)
#
# Here with the empty_key_toggle, we always send a "no key pressed"
# after every key press back and then we send the next key from
# the self.input_queue
#
# TODO: We can check the row of the previous key press and only
# force a 'no key pressed' if the row is the same
self.empty_key_toggle = False
self.current_input_char = None
log.debug("\tForce send 'no key pressed'")
else:
# FIXME: Find a way to handle CoCo and Dragon in the same way!
if self.cfg.CONFIG_NAME == COCO:
# log.critical("\t count: %i", self.input_repead)
if self.input_repead == 7:
try:
self.current_input_char = self.input_queue.get(block=False)
except Queue.Empty:
self.current_input_char = None
else:
col_row_values = get_dragon_col_row_values(self.current_input_char)
# log.critical(
log.debug(
"\tNew input char: %s col/row values: %s",
repr(self.current_input_char),
repr(col_row_values),
)
self.empty_key_toggle = True
log.critical("\tget new key from queue: %s", repr(self.current_input_char))
elif self.input_repead == 18:
# log.critical("\tForce send 'no key pressed'")
self.current_input_char = None
elif self.input_repead > 20:
self.input_repead = 0

self.input_repead += 1
else: # Dragon
if pia0b == self.cfg.PIA0B_KEYBOARD_START: # FIXME
if self.empty_key_toggle:
# Work-a-round for "poor" dragon keyboard scan routine:
# The scan routine in ROM ignores key pressed directly behind
# one another if they are in the same row!
# See "Inside the Dragon" book, page 203 ;)
#
# Here with the empty_key_toggle, we always send a "no key pressed"
# after every key press back and then we send the next key from
# the self.input_queue
#
# TODO: We can check the row of the previous key press and only
# force a 'no key pressed' if the row is the same
self.empty_key_toggle = False
self.current_input_char = None
# log.critical("\tForce send 'no key pressed'")
else:
try:
self.current_input_char = self.input_queue.get(block=False)
except Queue.Empty:
# log.critical("\tinput_queue is empty")
self.current_input_char = None
else:
# log.critical("\tget new key from queue: %s", repr(self.current_input_char))
self.empty_key_toggle = True

if self.current_input_char is None:
# log.debug("\tno key pressed")
# log.critical("\tno key pressed")
result = 0xff
self.empty_key_toggle = False
else:
result = get_dragon_pia_result(
char_or_code=self.current_input_char,
pia0b=pia0b,
auto_shift=True,
)
# log.critical("\tsend %s", repr(self.current_input_char))
result = self.cfg.pia_keymatrix_result(self.current_input_char, pia0b)

# if not is_bit_set(pia0b, bit=7):
# # bit 7 | PA7 | joystick comparison input
# result = clear_bit(result, bit=7)

if self.current_input_char is not None:
log.info(
# if self.current_input_char is not None:
# log.critical(
"%04x| read $%04x ($ff02 is $%02x %s) send $%02x %s back\t|%s",
op_address, address,
pia0b, '{0:08b}'.format(pia0b),
result, '{0:08b}'.format(result),
self.cfg.mem_info.get_shortest(op_address)
)
# "%04x| read $%04x ($ff02 is $%02x %s) send $%02x %s back\t|%s",
# op_address, address,
# pia0b, '{0:08b}'.format(pia0b),
# result, '{0:08b}'.format(result),
# self.cfg.mem_info.get_shortest(op_address)
# )
return result

def write_PIA0_A_data(self, cpu_cycles, op_address, address, value):
Expand Down Expand Up @@ -373,7 +382,8 @@ def test_run():
cmd_args = [
sys.executable,
os.path.join("..",
"Dragon32_test.py"
"CoCo_test.py"
# "Dragon32_test.py"
# "Dragon64_test.py"
),
]
Expand Down
16 changes: 10 additions & 6 deletions dragonpy/Dragon32/config.py
Expand Up @@ -12,10 +12,11 @@
import os
import logging

from dragonpy.core.configs import BaseConfig
from dragonpy.core.configs import BaseConfig, DRAGON32

from dragonpy.Dragon32.mem_info import get_dragon_meminfo
from dragonpy.utils.logging_utils import log
from dragonpy.Dragon32.keyboard_map import get_dragon_keymatrix_pia_result


class Dragon32Cfg(BaseConfig):
Expand All @@ -24,13 +25,14 @@ class Dragon32Cfg(BaseConfig):
* http://dragon32.info/info/memmap.html
* http://dragon32.info/info/romref.html
"""
CONFIG_NAME = DRAGON32
MACHINE_NAME = "Dragon 32"

# How does the keyboard polling routine starts with?
PIA0B_KEYBOARD_START = 0x00

RAM_START = 0x0000

# 1KB RAM is not runnable and raise a error
# 2-8 KB - BASIC Interpreter will be initialized. But every
# statement will end with a OM ERROR (Out of Memory)
Expand Down Expand Up @@ -61,11 +63,11 @@ def __init__(self, cmd_args):
self.mem_info = get_dragon_meminfo()

self.periphery_class = None# Dragon32Periphery

self.memory_byte_middlewares = {
# (start_addr, end_addr): (read_func, write_func)
# (0x0152, 0x0159): (None, self.keyboard_matrix_state),
(0x0115,0x0119): (self.rnd_seed_read, self.rnd_seed_write)
(0x0115, 0x0119): (self.rnd_seed_read, self.rnd_seed_write)
}

def keyboard_matrix_state(self, cpu, addr, value):
Expand All @@ -80,7 +82,7 @@ def keyboard_matrix_state(self, cpu, addr, value):
def rnd_seed_read(self, cycles, last_op_address, address, byte):
log.critical("%04x| read $%02x RND() seed from: $%04x", last_op_address, byte, address)
return byte

def rnd_seed_write(self, cycles, last_op_address, address, byte):
log.critical("%04x| write $%02x RND() seed to: $%04x", last_op_address, byte, address)
return byte
Expand All @@ -100,5 +102,7 @@ def get_initial_RAM(self):

return mem

def pia_keymatrix_result(self, char_or_code, pia0b):
return get_dragon_keymatrix_pia_result(char_or_code, pia0b, auto_shift=True)

config = Dragon32Cfg

0 comments on commit fd88b5c

Please sign in to comment.