Compare commits

...

46 Commits
main ... dev

Author SHA1 Message Date
ed279e4776 doc improvements 2024-08-08 21:11:25 +02:00
bf6a5efd7e fix doc format 2024-08-08 20:39:25 +02:00
7fce58e481 improve documentation 2024-08-08 20:15:50 +02:00
d86ac8d52d move kb util to main 2024-08-08 20:15:28 +02:00
8b2e7c2752 add docs with doxygen using custom filter 2024-08-08 00:11:15 +02:00
Matthias@Dell
5ee9610616 add set dd addr from charcount 2024-01-15 09:19:05 +01:00
Matthias@Dell
c61de15048 wip viu 2024-01-15 09:18:49 +01:00
eb2fb061a0 rm debug code 2024-01-14 04:26:58 +01:00
64492a5f6b fix cpx instead of cmp 2024-01-11 20:51:45 +01:00
9867e4a119 MaskedWrite: allow A 2024-01-11 20:51:28 +01:00
4e76be17ad 1.8 MHz 2024-01-11 20:51:07 +01:00
5c02ff6585 fix link 2024-01-09 02:30:12 +01:00
9060dfcbbf use kb debug utils 2024-01-09 02:30:01 +01:00
aa55d5fb37 add debug utils 2024-01-09 02:29:45 +01:00
bd083150c5 rename 2024-01-09 02:29:18 +01:00
e61871ed0f use other shift mode instead of generating clk pulse 2024-01-09 02:28:54 +01:00
326b2f6247 include bit macros 2024-01-07 00:42:26 +01:00
e8696cc312 send commands working 2024-01-07 00:42:05 +01:00
23e5e3cd35 add useful macros 2024-01-07 00:41:12 +01:00
b778b4dcd1 add bit branch/set/reset macros 2024-01-07 00:40:23 +01:00
2a13ce0ca4 rename to keypad_printer 2024-01-07 00:39:51 +01:00
108e5af0b9 fix do all 8 bits 2024-01-03 13:08:04 +01:00
e7bd5096cd wip: keyboard send 2024-01-02 23:37:07 +01:00
aebd813a55 add irq en/disable macros 2024-01-02 23:36:58 +01:00
808900602e fix JsrIndirect 2024-01-02 23:36:41 +01:00
Matthias@Dell
e1f78b2927 split into driver and handler 2024-01-01 14:56:11 +01:00
Matthias@Dell
7f909b4f75 add parity 2023-12-31 14:56:24 +01:00
b60ff7be12 wip keyboard 2023-12-31 01:59:06 +01:00
2bfae04f50 use additional buffer size var 2023-12-31 01:58:47 +01:00
ff4e284f26 add byte reverse 2023-12-31 01:57:52 +01:00
5767133ae3 add byte reverse 2023-12-31 01:57:46 +01:00
dce5a43804 add printf 2023-12-31 01:57:05 +01:00
b65a0249a5 use masked writes for RA 2023-12-31 01:56:42 +01:00
de098dd9c6 define spi mem 2023-12-31 01:56:02 +01:00
498b9803f1 Reapply "add keyboard"
This reverts commit 45dce42475.
2023-12-27 21:40:14 +01:00
45dce42475 Revert "add keyboard"
This reverts commit 302e49a9f7.
2023-12-27 21:38:10 +01:00
Matthias@Dell
302e49a9f7 add keyboard 2023-12-27 16:56:22 +01:00
Matthias@Dell
3d773390bf wip 2023-12-27 16:56:14 +01:00
Matthias@Dell
27a901c7c0 add test 2023-12-27 16:56:10 +01:00
Matthias@Dell
4d5edeecfe use res for ARGX 2023-12-27 16:55:56 +01:00
Matthias@Dell
fc0dba6c9c add memcopy16 2023-12-27 16:55:40 +01:00
Matthias@Dell
25d0fcfb90 use indirect spi handler 2023-12-23 14:19:15 +01:00
Matthias@Dell
eb02613837 fmt 2023-12-23 14:18:48 +01:00
Matthias@Dell
9e9978669a fmt 2023-12-23 14:18:32 +01:00
Matthias@Dell
7b97ca096d fmt 2023-12-23 14:18:15 +01:00
Matthias@Dell
2d06c9700b use size, add write 2023-12-23 14:17:49 +01:00
46 changed files with 6352 additions and 778 deletions

2861
.doxygen_config Normal file

File diff suppressed because it is too large Load Diff

72
.extra_docs.s65 Normal file
View File

@ -0,0 +1,72 @@
;;********************************************************************************
;; @mainpage 8-bit Breadboard Computer with W65C02S Processor
;; This repo contains the assembly code for my [6502-project](https://quintern.xyz/de/posts/2021_6502).
;;
;; The assembler used for this project is the excellent [ca65](https://github.com/cc65/cc65).
;; After assembling it, the binary is loaded onto the EEPROM using [my *eeprom.py* script](https://git.quintern.xyz/MatthiasQuintern/AT28C256-rpi-util) on a Raspberry Pi 4B.
;;
;; @section main_os Operating System
;; ... is probably a far stretch, since it is just the programs I wrote pieced together. My "OS" consists of these functionalities:
;; - Supported Hardware:
;; - 4x4 Matrix Keypad
;; - 4x16 Characters LCD Display
;; - PS/2 Keyboard
;; - SPI Connection to an Arduino
;; - Software
;; - @ref ps2_keyboard_printer "Keyboard" and @ref keypad_printer "keypad printer": Prints the characters you press on to the lcd
;; <!-- - Temperature: Shows the temperature using a dht sensor. *Work in progress, this does not work yet* -->
;; - @ref print_slow "(Stylishly)" print @ref "str::strf" "formated strings" to the LCD
;; - @ref sleep "Sleep"
;; - Send @ref ps2_keyboard_util "various commands" to the keyboard
;; - @ref ringbuffer "Ringbuffer"
;;
;; > It's not much, but it's honest work.
;;
;; @section main_hardware Hardware details
;; @subsection main_hw_addr Address Space
;; |Name|From|To|r/w|
;; |---|---:|---:|:---:|
;; |ZEROPAGE |$00 |$ff |`rw`|
;; |STACK |$100 |$1ff |`rw`|
;; |RAM |$200 |$4fff |`rw`|
;; |SPI |$5000 |$5fff |`rw`|
;; |VIA1 |$6000 |$600f |`rw`|
;; |VIA2 |$7000 |$700f |`rw`|
;; |ROM |$8000 |$ffff |`r `|
;;
;;
;; @section main_software Software details
;; @subsection main_sw_naming Naming conventions
;; leading underscors `_` indicate a "private" label/variable, that is meant for internal use within the module only.
;;
;; **Labels**:
;; - **scopes**: snake case
;; - **subroutines** and **variables**: snake case (`scope::(_)fname_snake_case` or `scope::(_)varname_2`)
;; - **macros**: camel case (`(_)GoodMacroname` or `scope_GoodMacroname`)
;; - **constants** (eg. in ROM): upper case (`scope::(_)NICE_SYMBOLNAME`)
;; - **enums**: upper case (`scope::(_)ENUM_NAME::ENUM_MEMBER`)
;;********************************************************************************
;;********************************************************************************
;; @defgroup applications
;; @brief Applications that do ... *something*
;;
;;********************************************************************************
;;********************************************************************************
;; @defgroup drivers
;; @brief Code that handles a physical device
;;
;;********************************************************************************
;;********************************************************************************
;; @defgroup libs Libraries
;; @brief Code that does something useful and is intended to be used in other code
;;
;;********************************************************************************
;;********************************************************************************
;; @defgroup system
;; @brief Core code
;;
;;********************************************************************************

View File

@ -57,3 +57,7 @@ clean:
rm -r $(OBJ_DIR)
rm $(ROM)
rm $(SPI)
docs:
doxygen .doxygen_config

View File

@ -1,19 +0,0 @@
# Project details
## Address Space
|Name|From|To|r/w|
|---|---:|---:|:---:|
|ZEROPAGE |$00 |$ff |`rw`|
|STACK |$100 |$1ff |`rw`|
|RAM |$200 |$4fff |`rw`|
|SPI |$5000 |$5fff |`rw`|
|VIA1 |$6000 |$600f |`rw`|
|VIA2 |$7000 |$700f |`rw`|
|ROM |$8000 |$ffff |`r `|
## Naming conventions
leading underscors `_` indicate a "private" label/variable, that is meant for internal use within the module only.
### Labels
- **scopes**: snake case
- **subroutines** and **variables**: snake case (`scope::(_)fname_snake_case` or `scope::(_)varname_2`)
- **macros**: camel case (`(_)GoodMacroname` or `scope_GoodMacroname`)
- **constants** (eg. in ROM): upper case (`scope::(_)NICE_SYMBOLNAME`)

219
doxy-asm65.py Normal file
View File

@ -0,0 +1,219 @@
import sys
import re
from typing import Callable
##
# @defgroup documentation Documentation
# @brief
#
##
# @file
# @brief Doxygen filter for ca65 assembler files
# @details
# This filter converts ca65 to C++ statements that doxygen can parse.
#
# Doxygen comments are double semicolons `;;`
# - turns procedures `.proc` into function statements with `proc` as return type
# Parameters documented with the @param command are put into the paranthesis (function arguments) with `Param` as type (@ref handle_procedure)
# - turns macros `.macro` into function statements with `macro` as return type with the parameter macros
# as function arguments with `Param` type (@ref handle_procedure)
# - enums become ... enums, documentation of enum members after their name is also handled when using ;;< (@ref handle_procedure)
# - labeled storage allocations with `.byte`, `.res`, `.ascii` etc. are turned into variable declarations with the label as variable name (@ref handle_procedure)
# - if allocations are strings, they are concatenated together to `char * LABEL_NAME = "<string(s)>";`
# - if there are multiple non-string allocations: `bytes LABEL_NAME[] = {alloc1, alloc2, ...};`
# - if there is one non-string allocation: `alloc_type * LABEL_NAME = alloc;`
# - if the allocation is not initilized: `alloc_type * LABEL_NAME;`
# - if the allocation type is `res SIZE`: `bytes LABEL_NAME[SIZE];`
# - the sizes of the arrays may be wrong!
# - include statements are kept
# - all other preprocessor macros are removed
#
# @todo Handle structs
# @todo for storage allocators, check in which segment they are in and apply `const` where necessary
# @ingroup documentation
filename = "unknown"
def pdebug(*args, **k):
print(f"DEBUG ({filename}):", *args, file=sys.stderr, **k)
def parse_custom_language(file_content: str):
# procedure_name: scope_name
exported_names = {}
def handle_export(m):
export_type = m.groups()[0]
scope = m.groups()[1]
functions = m.groups()[2].replace(" ", "").strip(",")
for f in functions.split(","):
# pdebug(f"Add Exported function: '{f}' in '{scope}'")
exported_names[f] = scope
return ""
def handle_procedure(m):
# print("handle procedure:", m.groups())
p_docs = m.groups()[0].strip("\n")
p_type = m.groups()[1]
p_name = m.groups()[2]
p_args = m.groups()[3].strip(" ")
p_code = m.groups()[4]
s = ""
in_namespace = False
if p_name in exported_names:
# print(f"{p_name} is exported")
in_namespace = True
# wrap function in namespace {}, which consumes the first line of the docstring, which must be ;;***...
namespace = exported_names[p_name]
# assert p_docs.startswith(";;*"), f"Documentation of an exported procedure must start with ';;***...' ({p_name})"
# assert p_docs[p_docs.rfind('\n'):].startswith("\n;;*"), f"Documentation of an exported procedure must end with ';;***...' ({p_name})"
s += f"namespace {namespace}" + " {" + p_docs
# s += p_docs[p_docs.find('\n'):p_docs.rfind('\n')]
s += "\n"
# s += f"@ingroup {namespace}\n"
else:
s += p_docs + "\n" #re.sub(r";;\*+", ";;", p_docs, 0, re.MULTILINE) + "\n"
if p_type == "proc":
s += f"proc {p_name}("
for match in re.finditer(r"[\@\\]param +(.+?) *:", p_docs):
s += f"Param {match.groups()[0]},"
if s[-1] == ",": s = s[:-1]
s += ");\n"
elif p_type == "macro":
# pdebug(f"Processing macro '{p_name}' with args '{'TXT'.join(p_args.replace(' ', '').split(','))}'")
s += f"macro {p_name}("
p_args = "".join("Param " + param + "," for param in p_args.replace(" ", "").split(',')).strip(",")
s += p_args
s += ");\n"
elif p_type == "enum":
p_code = re.sub(r"(.*=.*?)( *(?:;;.*)?\n)", r"\1,\2", p_code)
s += f"enum {p_name}" + "{\n" + p_code + "};"
else:
raise NotImplementedError(f"handle_procedure not implemented for procedure type {p_type}")
s += re.sub(".*", "", p_code)
if in_namespace:
s += "} // namespace"
else:
s += "\n"
return s
def handle_storage_label(m):
l_docs = m.groups()[0]
l_name = m.groups()[1]
l_allocs = m.groups()[2]
l_docs2 = m.groups()[3] # if doc was in the same line as the label
storage_alloc = r"\.(byte|res|dbyte|word|addr|faraddr|dword|asciiz?)(([, ]+(?:0x[a-fA-F0-9]+|0b[01]+|\d+|\w+|\"[^\n]*?[^\\\n]\")[ \n]*)*)"
storage_alloc_arg = r"(0x[a-fA-F0-9]+|0b[01]+|\d+|\w+|\"[^\n]*[^\\\n]\")"
args = []
allocs = []
for alloc_match in re.finditer(storage_alloc, l_allocs):
allocs.append(alloc_match)
alloc_args = alloc_match.groups()[1]
if alloc_args:
args += re.findall(storage_alloc_arg, alloc_args)
# pdebug(f"Storage label {l_name} with allocs '{[ma.group() for ma in allocs]}' and args '{args}'\n\t{m.groups()}")
s = ""
in_namespace = False
# if the label is exported, put it in a namespace
if l_name in exported_names:
in_namespace = True
namespace = exported_names[l_name]
s += f"namespace {namespace}" + " {"
# docs after the namespace, otherwise they document the namespace
if l_docs:
s += l_docs
# put the single line comment into a /** */ comment in front of the declaration
if l_docs2:
s += "/** "
if not "brief" in l_docs2: s += "@brief "
s += f"{l_docs2.strip(';')} */ "
# completely ignoring the type of the storage allocation here
if len(args) > 1:
if all(arg.startswith("\"") for arg in args):
s += f'char* {l_name} = "' + "".join(map(lambda x: x.strip('"'), args)) + '"'
else:
s += f"bytes {l_name}[{len(args)}] = " + "{"
for arg in args:
s += arg + ","
s = s.strip(",") + "}"
else:
l_type = allocs[0].groups()[0]
if len(args) == 0:
l_arg = None
else:
l_arg = args[0]
# if res: use bytes[length] as type
if l_type == "res":
l_type = f"bytes[{l_arg}]"
l_arg = None
# else use type* as type
else: l_type += "*"
s += f"{l_type} {l_name}"
if l_arg:
s += f" = {l_arg}"
s += ";"
if in_namespace: s += "} // namespace"
s += m.group().count('\n') * '\n' # make sure the #lines is the same
# pdebug(args, "\n---\n", s)
return s
patterns: dict[str, str|Callable[[re.Match], str]] = {
r"\@(?:macro|function)": "@brief",
r"^\.scope ([a-zA-Z0-9_]+)": r"namespace \1 {",
# r"^\.macro ([a-zA-Z0-9_]+)(.*)?": r"macro \1(\2 \2); ",
# r"^\.end(?:macro)": "",
r"^\.end(?:scope)": "}",
r"^\.(include)": r"#\1",
r"^(Export(?:Zp)?) (\w+)((?: *, *\w+)+)": handle_export,
r"^(Import(?:Zp)?) (\w+)((?: *, *\w+)+)": "",
r"(?<!^;)\$([A-Fa-f0-9_]+)": r"0x\1", # $HEX -> 0xHEX except in comments
r"(?<!^;)%([01_]+)": r"0b\1", # %BIN -> 0bBIN except in comments
r"^((?:;;.*\n)*) *\.(proc|enum|macro) (\w+)(.*?)\n((?:.|\n)*?)\.end(proc|enum|macro).*": handle_procedure,
r"^((?:;;.*\n)*) *(\w+):((?:\s*\.(?:byte|res|dbyte|word|addr|faraddr|dword|asciiz?)(?:[, ]+(?:0x[a-fA-F0-9]+|0b[01]+|\d+|\w+|\"[^\n]*[^\\\n]\")[ \n]*)*)+)(;;.*)?": handle_storage_label,
r"^INCLUDE_[A-Z0-9_]+ *= *1$": r"", # Include guard variables
r";;": "//!", # C++ comments
# TODO this is currently case sensitive
r"(?<!^;)( *\w+ *= *[^;,\n]+?) *(//.*)?$": r"\1;\2", # semicolons after assignments, except in comments and when they already end with a comma or semicolon. Also preserve comments after the assignment
r"^([^\n;]*)(?<!\w)\.(\w+)": r"\1// #\2", # all .preprocessor commands
}
compiled_patterns = []
for k,v in patterns.items():
compiled_patterns.append((re.compile(k), v))
resub_patterns: dict[str, str|Callable[[re.Match], str]] = {
r"(?<!^;;)[ \t\r\f\v]+": " ", # turn all spaces into single whitespace except if in doxygen comment
r"^((?:[^\"\n;]||[^\"\n;]*\"(?:[^\"\n]|\\\")+\")+);(?!;).*": r"\1", # remove normal comments, detect strings
r"^;;\*+": ";;", # remove ;;*** comments
r"[ \t\r\f\v]+$": "", # remove trailing spaces print(file_content)
}
for pat, subst in resub_patterns.items():
file_content = re.sub(pat, subst, file_content, 0, re.MULTILINE)
for pat,subst in patterns.items():
(file_content, n_subst) = re.subn(pat, subst, file_content, 0, re.MULTILINE)
return file_content
def main():
global filename
if len(sys.argv) != 2:
print("Usage: python doxy-asm65.py <input_file>")
sys.exit(1)
filename = sys.argv[1]
with open(filename, 'r') as file:
file_content = file.read()
transformed_content = parse_custom_language(file_content)
print(transformed_content)
if __name__ == "__main__":
main()

View File

@ -3,10 +3,11 @@ MEMORY {
# RAM: start = $0100, size = $5eff, type = rw, file = "", fill = yes;
STACK: start = $0100, size = $0100, type = rw, file = "", fill = yes;
RAM: start = $0200, size = $4e00, type = rw, file = "", fill = yes;
SPI: start = $5000, size = $1000, type = rw, file = "../spi.bin", fill = no;
SPI: start = $5000, size = $1000, type = rw, file = "../spi.bin", fill = no, define = yes;
VIA1: start = $6000, size = $000f, type = rw, file = "", fill = yes;
VIA2: start = $7000, size = $000f, type = rw, file = "", fill = yes;
ROM: start = $8000, size = $8000, type = ro, file = %O, fill = yes;
TEST: start = $9000, size = $1000, type = rw, file = "../test.bin";
}
SEGMENTS {
VIA1: load = VIA1, type = bss;
@ -14,7 +15,8 @@ SEGMENTS {
ZEROPAGE: load = RAM_ZP, type = bss, start = $0;
BSS: load = RAM, type = bss;
CODE: load = ROM, type = ro;
RODATA: load = ROM, type = ro;
RODATA: load = ROM, type = ro, align = $800;
RESET_VECTOR: load = ROM, type = ro, start = $FFFA;
SPI: load = SPI, type = rw;
TEST: load = TEST,type = rw;
}

138
main.s65
View File

@ -1,34 +1,13 @@
.include "system/system.h65"
.include "string.h65"
.export home,homeloop
.import printer:absolute
.import keypad_printer:absolute
.import spi_menu:absolute
.import ps2_keyboard_printer:absolute
.import ps2_keyboard_util:absolute
.code
.macro DEBUG_LED_OFF nr
pha
lda IO1 + IO::RA
.if nr = 0
and #%11111110
.else
and #%11111101
.endif
sta IO1 + IO::RA
pla
.endmacro
.macro DEBUG_LED_ON nr
pha
lda IO1 + IO::RA
.if nr = 0
ora #%00000001
.else
ora #%00000010
.endif
sta IO1 + IO::RA
pla
.endmacro
;********************************************************************************
; Modules
;********************************************************************************
@ -62,12 +41,17 @@ nmi:
rti
irq:
; read IRFs, while bit 7 ist set handle interrupts
; DEBUG_LED_ON 2
@irq_io1:
lda IO1 + IO::IFR
and IO1 + IO::IER ; sometimes CB1/2 IFR set even though not enabled in IER
sta irq_via_ifr
bbr7 irq_via_ifr,@irq_io2 ; skip
bbs2 irq_via_ifr,@irq_kb1 ; shit reg -> first 8 bits
bbs5 irq_via_ifr,@irq_kb2 ; timer -> last 3 bits
bbr7 irq_via_ifr,@irq_io2 ; skip
bbs2 irq_via_ifr,@irq_kb_sr
bbs3 irq_via_ifr,@irq_kb_cb2
bbs4 irq_via_ifr,@irq_kb_cb1
bbs5 irq_via_ifr,@irq_kb_t2
bra @irq_unknown
@irq_io2:
lda IO2 + IO::IFR
sta irq_via_ifr
@ -75,29 +59,31 @@ irq:
bbs2 irq_via_ifr,@irq_spi_p ; check SR
bbs1 irq_via_ifr,@irq_keypad ; check CA1
@irq_unknown:
; this SHOULD never be reached
jsr lcd::clear
Print "Unknown IRQ"
; force reset interrupt flags
lda #$ff
sta IO1 + IO::IFR
sta IO2 + IO::IFR
rti
; bra @irq_return
bra @irq_return
@irq_keypad:
jsr kp::read_irq
bra @irq_return
@irq_spi_p:
jsr spi_p::read
bra @irq_return
@irq_kb1:
; PrintNC "<30>"
jsr $3000
bra @irq_return
@irq_kb2:
; PrintNC "<31>"
jsr $3100
jsr spi_p::irq_read_byte
bra @irq_return
; jmp (spi_p::irq_handler)
; JsrIndirect (spi_p::irq_handler), @irq_return
@irq_kb_sr:
; Print "$3000"
JsrIndirect ($3000), @irq_return
@irq_kb_cb2:
JsrIndirect ($3020), @irq_return
@irq_kb_cb1:
JsrIndirect ($3040), @irq_return
@irq_kb_t2:
JsrIndirect ($3060), @irq_return
; @irq_dht:
; lda IO1 + IO::T1CL ;T1L2 ; clear interrupt flag
; bra @irq_return
@ -116,25 +102,20 @@ reset:
sta IO1 + IO::DDRA
DEBUG_LED_OFF 0
DEBUG_LED_OFF 1
DEBUG_LED_OFF 1
jsr kp::init
SetCustomChar chars::CAT,0
SetCustomChar chars::SMILEY,1
SetCustomChar chars::SMILEY_XD,2
DEBUG_LED_ON 0
jsr spi_p::init
DEBUG_LED_ON 1
; ; INIT DHT
; lda #%11000010 ; enable interrupt for Timer 1 and CA1 on IO2
; sta IER2
; lda #%00111111 ; set Timer 1 to interrupt when loaded
; and ACR2
; sta ACR2
; lda #%00000001 ; set PCR2 bit 0 CA1 pos edge interrupt
; ora PCR2
; sta PCR2
; stz DHT_STATUS
; SetCustomChar chars::CAT,0
; SetCustomChar chars::SMILEY,1
; SetCustomChar chars::SMILEY_XD,2
; DEBUG_LED_ON 2
; enable interrupts
cli
@ -156,7 +137,7 @@ reset:
lda 0
; beq home
cmp #'A'
jeq printer
jeq keypad_printer
cmp #'B'
jeq spi_menu
cmp #'C'
@ -164,19 +145,30 @@ reset:
cmp #'D'
jeq print_2
cmp #'1'
beq @debug0_on
; beq @debug0_on
beq @ps2_util
cmp #'2'
beq @debug0_off
beq @ps2_printer
; beq @debug0_off
cmp #'4'
beq @debug1_on
cmp #'5'
beq @debug1_off
cmp #'7'
beq @debug2_on
cmp #'8'
beq @debug2_off
cmp #'9'
beq @print_rb
cmp #'*' ; print home menu again if not visible (message 1 and 2 jmp to home)
beq home
wai
jmp @loop
@ps2_util:
jmp ps2_keyboard_util
@ps2_printer:
jmp ps2_keyboard_printer
@debug0_off:
DEBUG_LED_OFF 0
jmp @loop
@ -189,6 +181,12 @@ reset:
@debug1_on:
DEBUG_LED_ON 1
jmp @loop
@debug2_off:
DEBUG_LED_OFF 2
jmp @loop
@debug2_on:
DEBUG_LED_ON 2
jmp @loop
@print_rb:
jsr lcd::clear
Print str_io2
@ -217,20 +215,20 @@ message_1:
.byte " **** "
.asciiz "www.quintern.xyz"
message_2:
.byte " Hello "
.byte " there "
.byte " <3 "
.asciiz "================"
.byte "0123456789=!?#+*"
.byte "ABCDEFGHIJKLMNOP"
.byte "QRSTUVWXYZöäüß-_"
.asciiz "{[(<>)]}$%&/,;.:"
message_menu:
.byte "<A> Printer "
; .byte "<B> Temperatur "
.byte "<B> SPI-Menu "
.byte "<C> Text 1 "
.asciiz "<D> Text 2 "
str_spi_begin:
.asciiz "---BEGIN SPI---"
str_spi_start:
.asciiz "---START SPI---"
.byte "A/2: KP/KB Print"
; .byte "<B> Temperatur"
.byte "1: KB Util "
.byte "B: SPI-Menu "
.asciiz "C/D: Text 1/2 "
; str_spi_begin:
; .asciiz "---BEGIN SPI---"
; str_spi_start:
; .asciiz "---START SPI---"
str_io2:
.asciiz "IO2::RB='"

View File

@ -1,8 +1,10 @@
;;********************************************************************************
;; @module SPI
;; @type driver
;; @file
;; @brief DHT22 driver (WIP)
;; @ingroup driver
;; @details
;; @depends IO-W65C22N
;;
;;********************************************************************************
;;TODO EVERYTHING

View File

@ -4,7 +4,7 @@
.ifndef INCLUDE_PRINTER
INCLUDE_PRINTER = 1
.global printer
.global keypad_printer
.import home:absolute
.endif ; guard

View File

@ -0,0 +1,20 @@
.include "keypad_printer.h65"
.include "lcd.h65"
.include "keypad.h65"
.code
;;********************************************************************************
;; @brief Print characters to the display when a button was pressed
;; @ingroup applications
;;********************************************************************************
.proc keypad_printer
jsr lcd::clear
@printer_loop:
jsr kp::read
beq @printer_loop
cmp #'*'
jeq home
jsr lcd::print_char
bra @printer_loop
.endproc

View File

@ -3,15 +3,16 @@ INCLUDE_MEMCOPY = 1
.include "system.h65"
.export memcopy
.export memcopy,memcopy16
.code
;********************************************************************************
; @function Copy a block of memory to a different address
; @param ARG0-1: Source address
; @param ARG2-3: Target address
; @param y: Number of bytes to copy
;********************************************************************************
;;********************************************************************************
;; @function Copy a block of memory to a different address
;; @param ARG0-1: Source address
;; @param ARG2-3: Target address
;; @param Y: Number of bytes to copy
;; @modifies A,Y
;;********************************************************************************
.proc memcopy
cpy #0
beq @rts
@ -24,5 +25,41 @@ INCLUDE_MEMCOPY = 1
@rts:
rts
.endproc
;;********************************************************************************
;; @function Copy a block of memory to a different address
;; @param ARG0-1: Source address
;; @param ARG2-3: Target address
;; @param ARG5-6: Number of bytes to copy (LE)
;; @modifies A,Y
;;********************************************************************************
.proc memcopy16
lda ARG6
beq @last_page ; no full page
ldy #$ff
bra @copy_byte
@next_page: ; y is 0
lda ARG6
beq @rts ; done
inc ARG1
inc ARG3
dec ARG6
beq @last_page
ldy #$ff
@copy_byte:
lda (ARG0),y
sta (ARG2),y
@dec_y:
dey
beq @next_page
bra @copy_byte
@last_page:
ldy ARG5
dey
bra @copy_byte
@rts:
rts
.endproc
.endif ; guard

View File

@ -5,11 +5,13 @@
.export print_slow
.code
;********************************************************************************
; @function Print a null-terminated string
; @param ARG0-1: Address of the string to print
; @param x: time to sleep in centiseconds
;********************************************************************************
;;********************************************************************************
;; @function Print a null-terminated string with a delay between each character
;; @param ARG0-1: Address of the string to print
;; @param x: time to sleep in centiseconds
;; @ingroup applications
;; @clock_dependent
;;********************************************************************************
.proc print_slow
ldy #$00
@print_loop:

View File

@ -1,15 +0,0 @@
.include "printer.h65"
.include "lcd.h65"
.include "keypad.h65"
.code
.proc printer
jsr lcd::clear
@printer_loop:
jsr kp::read
beq @printer_loop
cmp #'*'
jeq home
jsr lcd::print_char
bra @printer_loop
.endproc

View File

@ -0,0 +1,61 @@
.include "lcd.h65"
.include "string.h65"
.include "ps2_keyboard_text_handler.h65"
.export ps2_keyboard_printer
.import home:absolute
.code
;;********************************************************************************
;; @brief Print characters to the display when a key was pressed
;; @details
;; Print the character that corresponds to a key.
;; This uses the @ref kb "text keyboard handler" to correctly handle modifier keys
;;
;; Controls:
;; - `ESC`: Jump home
;; - `Print`: Clear the display
;; - `F12`: Restart the program
;; @ingroup applications keyboard
;;********************************************************************************
.proc ps2_keyboard_printer
jsr lcd::clear
jsr kb::init
jeq loop
ps2kb_PrintCmdFailed
; jmp homeloop
ps2kb_CmdEnable
jsr ps2kb::begin_receive
loop:
wai
; ; put shift, ralt and lalt in debug leds
; lda kb::modifier
; Reverse A
; and #%00000111
; ora IO1 + IO::RANH
; sta IO1 + IO::RANH
lda kb::char
beq @no_char
stz kb::char
jsr lcd::print_char
bra loop
@no_char:
lda kb::keycode
beq loop
stz kb::keycode
cmp #kb::K::ESCAPE
beq @esacpe
cmp #kb::K::PRINT
beq @clear_display
cmp #kb::K::F12
jeq ps2_keyboard_printer
bra loop
@esacpe:
jmp home
@clear_display:
jsr lcd::clear
bra loop
.endproc

View File

@ -0,0 +1,38 @@
;;********************************************************************************
;; @file
;; @brief Simple PS/2 keyboard handler
;; @ingroup libs
;; @details:
;; This module processes keyboard scancodes from the ps2_keyboard driver.
;; It requires a PS/2 keyboard that supports scancode set 3.
;;
;; @section what What it does
;; - swallow break (release) scancodes
;;
;; This handler is designed for debug purposes.
;;
;; @section usage How to use it
;; Call `kb::init` and check if it was successful (`Z = 1`).
;; In your program loop, check if kb::scancode is 0. If it is not, a key has been pressed.
;;
;; @subsection change_behaviour Changing the keyboard behaviour
;; You can change the typematic rate and delay (see PS/2 keyboard command `0xF3`)
;; and the typematic behavior (make, make/release, typematic) for all keys.
;;
;; You may also send the echo (`0xEE`) and identify (`0xF2`) commands to the keyboard.
;;********************************************************************************
.ifndef INCLUDE_KEYBOARD_SIMPLE
INCLUDE_KEYBOARD_SIMPLE = 1
.include "ps2_keyboard.h65"
;;********************************************************************************
;; @brief Simple Keyboard Handler
;;********************************************************************************
.scope skb
Import skb, init, scancode
K_BREAK = $F0
.endscope
.endif ; guard

View File

@ -0,0 +1,77 @@
.include "ps2_keyboard_simple_handler.h65"
Export skb, init, scancode
.bss
previous_scancode: .res 1 ; all 1 if previous_scancode is break
scancode: .res 1
.code
;;********************************************************************************
;; @function Initialize the keyboard handler
;; @returns Z: Z = 1 => success, Z = 0 => failure
;; @details
;; Initializes the PS/2 keyboard driver and the handler.
;; If any PS/2 command fails, init returns with Z = 0. The failed command (and parameter) can be loaded from `ps2kb::send_cmd` and `ps2kb::send_data`
;; After init, the keyboard will be able to send scancodes.
;;********************************************************************************
.proc init
stz scancode
stz previous_scancode
jsr ps2kb::init
; init & reset
ps2kb_CmdSelfTest
ps2kb_WaitFinishCmd
lda ps2kb::cmd_response+1
cmp #$aa
bne fail
; set set 3
ps2kb_CmdSetScancodeSet 3
ps2kb_WaitFinishCmd
lda ps2kb::cmd_response+1
cmp #ps2kb::ACK
bne fail
ps2kb_CmdEnable
ps2kb_WaitFinishCmd
lda ps2kb::cmd_response
cmp #ps2kb::ACK
bne fail
StoreDByte process_scancode, ps2kb::scancode_handler
jsr ps2kb::begin_receive
lda #0 ; set Z = success
fail:
rts
.endproc
;;********************************************************************************
;; @function Process a scancode
;; @details
;; If it is a 'make' scancode, store it in skb::scancode
;; If the key is not a mod key, the keycode is stored in kb::keycode
;; If the key is an ascii character, the corresponding ascii character is stored in kb::char
;; @param X: The scancode
;; @modifies A, X, Y
;;********************************************************************************
.proc process_scancode
; check how this scancode needs to be interpreted
bit previous_scancode
jmi break_key ; bit 7 set = BREAK
lda ps2kb::scancode
cmp #skb::K_BREAK
beq @break_scancode
sta scancode
rts
@break_scancode:
lda #$ff
sta previous_scancode
rts
break_key:
stz previous_scancode
rts
.endproc

View File

@ -0,0 +1,201 @@
; normal
;;********************************************************************************
;; @file
;; @brief PS/2 Keyboard handler for text input
;; @ingroup libs
;; @details
;; This module processes keyboard scancodes from the ps2_keyboard driver.
;; It requires a PS/2 keyboard that supports scancode set 3.
;;
;; @section what What it does
;; - keeps track of modifier keys: shift, left/right alt, left right meta (super),
;; control, capslock, numlock, scrollock
;; - convert valid keycodes to chars, dependening on the state of shift and right alt
;; - update the keyboard LEDs (numlock, capslock,, scrolllock)
;;
;; This handler is designed for efficient text input.
;; It does not track the status of the keys (except modifiers) and is therefore not suitable for games.
;;
;; @section working_principle How it works
;; In the init function, the module disables the *typematic* behaviour for the modifier keys by
;; sending commands to the keyboard. The 3 lock keys will be set to *make only* mode and
;; the others to *make release*.
;; This makes tracking the modifier keys much easier and is the reason why scancode set 3
;; is required, as these commands are only available with this set.
;;
;; Using scancode set 3 also adds the benefit that all scancodes are only 1 byte large,
;; making the recognition of scancodes trivial.
;;
;; Scancodes sent from the keyboard are processed immediately, as the handling functions is called
;; from the keyboard's interrupt handler.
;;
;; @subsection mapping Mapping keycodes to chars
;; The mapping of keycodes to chars is done using an array of chars for each state:
;; default, shift and right alt. The tables contains the ascii characters, `0` where the keycode does not
;; generate a character and `1` where the keycode is a modifier key.
;; This approach consumes a relatively large amount of ROM (3 * 144 bytes), but is super fast,
;; as loading a character is merely one indexed load instruction.
;; Checking for mod keys is now also fast, beceause we only have to do 1 comparison to see IF it is a modifier key.
;;
;; @section usage How to use it
;; Call `kb::init` and check if it was successful (`Z = 1`).
;; In your program loop, check if `kb::keycode` is 0. If it is not, a key has been pressed.
;;
;; If the keypress generated a character `kb::char` will be non-zero and contain the character.
;;
;; Note that if you will probably want to write a 0 to `kb::keycode` and `kb::char`
;; after processing the keycode. This will not be done by the handler.
;;
;; Conditional branches based on the status of a modifier (`SHIFT`, `RALT`, `LALT`, `CTRL`, `META`, `NUMLOCK`, `SCROLLLOCK`) can be done
;; using the `bbs` and `bbr` instructions, as each modifier is represented by a bit of the `kb::modifier` variable
;; in the zeropage.
;;
;; @subsection change_behaviour Changing the keyboard behaviour
;; You can change the typematic rate and delay (see PS/2 keyboard command `0xF3`)
;; and the typematic behavior (make, make/release, typematic) for all keys except the modifier keys.
;;
;; You may also send the echo (`0xEE`) and identify (`0xF2`) commands to the keyboard.
;;********************************************************************************
.ifndef INCLUDE_KEYBOARD_TEXT
INCLUDE_KEYBOARD_TEXT = 1
.include "ps2_keyboard.h65"
;;********************************************************************************
;; @brief Keyboard Handler
;;********************************************************************************
.scope kb
Import kb, init, char, keycode
Import kb, CHARS_NOMOD, CHARS_MODRALT, CHARS_MODSHIFT, CHARS_NUMLOCK_OFF, CHARS_NUMLOCK_ON
ImportZp kb, modifier
;;********************************************************************************
;; @brief Bitmask for checking modifier keys
;;********************************************************************************
.enum MOD
SHIFT = %10000000 ; set if either capslock is on or shift is pressed
RALT = %01000000 ; set while rightalt is pressed
LALT = %00100000 ; set while leftalt is pressed
META = %00010000 ; set while left or right meta is pressed
CTRL = %00001000 ; set while left or right ctrl is pressed
CAPSLOCK = %00000100 ; ps/2 LED
NUMLOCK = %00000010 ; ps/2 LED
SCROLLLOCK = %00000001 ; ps/2 LED
.endenum
;; @brief Scancode that is sent by the keyboard when a key is released
K_BREAK = $F0
;;********************************************************************************
;; @brief S3 German Keycodes
;;********************************************************************************
.enum K
F1 = $07
ESCAPE = $08
TAB = $0D
GRAVE = $0E
F2 = $0F
LEFTCTRL = $11
LEFTSHIFT = $12
CAPSLOCK = $14
K_Q = $15
K_1 = $16
F3 = $17
LEFTALT = $19
K_Y = $1A
K_S = $1B
K_A = $1C
K_W = $1D
K_2 = $1E
F4 = $1F
K_C = $21
K_X = $22
K_D = $23
K_E = $24
K_4 = $25
K_3 = $26
F5 = $27
SPACE = $29
K_V = $2A
K_F = $2B
K_T = $2C
K_R = $2D
K_5 = $2E
F6 = $2F
K_N = $31
K_B = $32
K_H = $33
K_G = $34
K_Z = $35
K_6 = $36
F7 = $37
RIGHTALT = $39
K_M = $3A
K_J = $3B
K_U = $3C
K_7 = $3D
K_8 = $3E
F8 = $3F
COMMA = $41
K_K = $42
K_I = $43
K_O = $44
K_0 = $45
K_9 = $46
F9 = $47
DOT = $49
DASH = $4A
KPDIVIDE = $4A
K_L = $4B
OUML = $4C
K_P = $4D
SSHARP = $4E
KPMINUS = $4E
F10 = $4F
AUML = $52
UUML = $54
BACKTICK = $55
F11 = $56
PRINT = $57
RIGHTCTRL = $58
RIGHTSHIFT = $59
ENTER = $5A
PLUS = $5B
LESSTHAN = $5C
HASH = $5D
F12 = $5E
SCROLLLOCK = $5F
RIGHT = $60
LEFT = $61
PAUSE = $62
UP = $63
DELETE = $64
END = $65
BACKSPACE = $66
INSERT = $67
KP1 = $69
DOWN = $6A
KP4 = $6B
KP7 = $6C
PAGEDOWN = $6D
POS1 = $6E
PAGEUP = $6F
KP0 = $70
KPDOT = $71
KP2 = $72
KP5 = $73
KP6 = $74
KP8 = $75
NUMLOCK = $76
KPENTER = $79
KP3 = $7A
KPPLUS = $7C
KP9 = $7D
KPASTERISK = $7E
LEFTMETA = $8B
RIGHTMETA = $8C
MENU = $8D
.endenum
.endscope
.endif ; guard

View File

@ -0,0 +1,310 @@
.include "ps2_keyboard_text_handler.h65"
Export kb, init, char, keycode
Export kb, CHARS_NOMOD, CHARS_MODRALT, CHARS_MODSHIFT, CHARS_NUMLOCK_OFF, CHARS_NUMLOCK_ON
ExportZp kb, modifier
.zeropage
;; @ref kb::MOD "status of the modifier keys"
modifier: .res 1
.bss
;; previous scancode is used to determine if a key was released (BREAK sent previously)
;; will be $ff if the previous scancode was K_BREAK
previous_scancode: .res 1
;; 0 when no key or non-character key was pressed, otherwise character of the key (with modifiers applied)
char: .res 1
;; @ref kb::K "keycode" of the last key that was pressed or 0 if none was pressed
keycode: .res 1
.code
;;********************************************************************************
;; @function Initialize the keyboard handler
;; @returns Z: Z = 1 => success, Z = 0 => failure
;; @details
;; Initializes the PS/2 keyboard driver and the handler.
;; If any PS/2 command fails, init returns with Z = 0. The failed command (and parameter) can be loaded from `ps2kb::send_cmd` and `ps2kb::send_data`
;; After init, the keyboard will be able to send scancodes.
;; @modifies A, X, Y
;;********************************************************************************
.proc init
stz modifier
stz char
stz keycode
stz previous_scancode
jsr ps2kb::init
; init & reset
ps2kb_CmdSelfTest
ps2kb_WaitFinishCmd
lda ps2kb::cmd_response+1
cmp #$aa
bne fail
; set set 3
ps2kb_CmdSetScancodeSet 3
ps2kb_WaitFinishCmd
lda ps2kb::cmd_response+1
cmp #ps2kb::ACK
bne fail
; toggle-behaving keys
ldx #kb::K::CAPSLOCK
jsr set_make
ldx #kb::K::NUMLOCK
jsr set_make
ldx #kb::K::SCROLLLOCK
jsr set_make
; modifiers
ldx #kb::K::LEFTSHIFT
jsr set_make_release
ldx #kb::K::RIGHTSHIFT
jsr set_make_release
ldx #kb::K::LEFTCTRL
jsr set_make_release
ldx #kb::K::RIGHTCTRL
jsr set_make_release
ldx #kb::K::LEFTALT
jsr set_make_release
ldx #kb::K::RIGHTALT
jsr set_make_release
ldx #kb::K::LEFTMETA
jsr set_make_release
ldx #kb::K::RIGHTMETA
jsr set_make_release
ps2kb_CmdEnable
ps2kb_WaitFinishCmd
lda ps2kb::cmd_response
cmp #ps2kb::ACK
bne fail
StoreDByte process_scancode, ps2kb::scancode_handler
jsr ps2kb::begin_receive
lda #0 ; set Z = success
fail:
rts
set_make:
; x must be set to scancode
lda #$fd
bra set
set_make_release:
; x must be set to scancode
lda #$fc
set:
ldy #0
jsr ps2kb::send_command
ps2kb_WaitFinishCmd
lda ps2kb::cmd_response+1
cmp #ps2kb::ACK
bne fail
rts
.endproc
;;********************************************************************************
;; @function Process a scancode
;; @details
;; Update modifier bits if a mod key is pressed.
;; If the key is not a mod key, the keycode is stored in @ref kb::keycode
;; If the key is an ascii character, the corresponding ascii character is stored in @ref kb::char
;; @modifies A, X
;;********************************************************************************
.proc process_scancode
; check how this scancode needs to be interpreted
ldx ps2kb::scancode
bit previous_scancode
jmi break_key ; bit 7 set = BREAK
cpx #$60 ; codes >= 60 in separate table to reduce memory consumption
bcs x_ge_60
bbs kb::MOD::SHIFT, modifier, modshift ; if shift active
bbs kb::MOD::RALT, modifier, modralt ; if ralt active
; load from the correct table, if 0 => non-char, if 1 => non-char but mod, else char
nomod:
lda CHARS_NOMOD,x
bra check_char
modralt:
lda CHARS_MODRALT,x
bra check_char
modshift:
lda CHARS_MODSHIFT,x
check_char:
beq not_mod ; 0 => not mod and not char
cmp #1 ; 1 => mod
beq is_mod
@char: ; store char
sta char
not_mod: ; store keycode if not mod
stx keycode
_rts:
rts
x_ge_60: ; x >= $60
cpx #$90 ; only BREAK is higher than 8F, (also code table only has 8F bytes)
bcs check_release
; CHARS_NUMLOCK_OFF - CHARS_NOMOD = 60 -> no need to subtract $60 first
; CHARS_NUMLOCK_ON - CHARS_MODSHIFT = 60 -> no need to subtract $60 first
bbs kb::MOD::NUMLOCK, modifier, modshift ; if numlock active
lda CHARS_NOMOD,x
bra check_char
check_release: ; x >= $90
cpx #kb::K_BREAK
bne _rts
lda #$ff
sta previous_scancode
rts
is_mod:
; check which mod
cpx #kb::K::LEFTSHIFT
beq set_shift
cpx #kb::K::RIGHTSHIFT
beq set_shift
cpx #kb::K::RIGHTALT
beq set_rightalt
cpx #kb::K::LEFTALT
beq set_leftalt
cpx #kb::K::RIGHTMETA
beq set_meta
cpx #kb::K::LEFTMETA
beq set_meta
cpx #kb::K::RIGHTCTRL
beq set_ctrl
cpx #kb::K::LEFTCTRL
beq set_ctrl
cpx #kb::K::CAPSLOCK
beq toggle_capslock
cpx #kb::K::NUMLOCK
beq toggle_numlock
cpx #kb::K::SCROLLLOCK
beq toggle_scrolllock
; a real keycode and not a released keycode, twobyte or release scancode
set_ctrl:
smb kb::MOD::CTRL, modifier
rts
set_meta:
smb kb::MOD::META, modifier
rts
set_leftalt:
smb kb::MOD::LALT, modifier
rts
set_rightalt:
smb kb::MOD::RALT, modifier
rts
set_shift: ; process shift
bbs kb::MOD::CAPSLOCK, modifier, @unset_shift ; CAPSLOCK unset shift when capslock is on
@set_shift:
smb kb::MOD::SHIFT, modifier
rts
@unset_shift:
rmb kb::MOD::SHIFT, modifier
rts
toggle_numlock: ; toggle numlock
lda modifier
eor #kb::MOD::NUMLOCK
sta modifier
bra update_leds
toggle_scrolllock: ; toggle numlock
lda modifier
eor #kb::MOD::SCROLLLOCK
sta modifier
bra update_leds
toggle_capslock: ; toggle capslock & shift
lda modifier
eor #(kb::MOD::CAPSLOCK | kb::MOD::SHIFT)
sta modifier
; fallthrough
load_update_leds:
lda modifier
update_leds:
and #%00000111 ; only bottom 3 bits allowed - otherwise command fails
tax
ps2kb_CmdSetLeds
rts
.endproc
;;********************************************************************************
;; @details
;; Unset modifier bits if a mod key is released.
;; @param X: The scancode
;; @modifies
;;********************************************************************************
.proc break_key
; rmb kb::PREVIOUS::BREAK, kb::previous_scancode
stz previous_scancode
; check mod keys
cpx #kb::K::LEFTSHIFT
beq unset_shift
cpx #kb::K::RIGHTSHIFT
beq unset_shift
cpx #kb::K::RIGHTALT
beq unset_rightalt
cpx #kb::K::LEFTALT
beq unset_leftalt
cpx #kb::K::RIGHTMETA
beq unset_meta
cpx #kb::K::LEFTMETA
beq unset_meta
cpx #kb::K::RIGHTCTRL
beq unset_ctrl
cpx #kb::K::LEFTCTRL
rts
unset_shift: ; process shift
bbs kb::MOD::CAPSLOCK, modifier, @set_shift ; set shift when capslock is on
rmb kb::MOD::SHIFT, modifier ; unset if capslock is off
rts
@set_shift:
smb kb::MOD::SHIFT, modifier
rts
unset_ctrl:
rmb kb::MOD::CTRL, modifier
rts
unset_meta:
rmb kb::MOD::META, modifier
rts
unset_leftalt:
rmb kb::MOD::LALT, modifier
rts
unset_rightalt:
rmb kb::MOD::RALT, modifier
rts
.endproc
.rodata
.align 256
CHARS_NOMOD:
.byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00^\x00"
.byte "\x00\x01\x01<\x01q1\x00\x00\x01ysaw2\x00"
.byte "\x00cxde43\x00\x00 vftr5\x00"
.byte "\x00nbhgz6\x00\x00\x01mju78\x00"
.byte "\x00,kio09\x00\x00.-l\xEFp\xE2\x00"
.byte "\x00\x00\xE1#\xF5`\x00\x00\x01\x01\x00\x00\x00\x00\x00\x01"
;; numlock range characters where numlock is off
CHARS_NUMLOCK_OFF:
.byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
.byte "\x00\x00\x00\x00\x00\x00\x01/\x00\x00\x00\x00+\x00*\x00"
.byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00-\x00"
CHARS_MODSHIFT:
.byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xDF\x00"
.byte "\x00\x01\x01>\x01Q!\x00\x00\x01YSAW\"\x00"
.byte "\x00CXDE$\xED\x00\x00 VFTR%\x00"
.byte "\x00NBHGZ&\x00\x00\x01MJU/(\x00"
.byte "\x00;KIO=)\x00\x00:_L\xEFP\xE2\x00"
.byte "\x00\x00\xE1'\xF5`\x00\x00\x01\x01\x00\x00\x00\x00\x00\x01"
CHARS_NUMLOCK_ON:
.byte "\x00\x00\x00\x00\x00\x00\x00\x00\x001\x0047\x00\x00\x00"
.byte "0.2568\x01\x00\x00\x003\x00\x009\x00\x00"
.byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00"
CHARS_MODRALT:
.byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\x00"
.byte "\x00\x01\x01|\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
.byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
.byte "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00{[\x00"
.byte "\x00\x00\x00\x00\x00}]\x00\x00\xA5\xB0\x00\x00\x00\x00\x00"
.byte "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x01"

View File

@ -0,0 +1,159 @@
.include "system.h65"
.include "string.h65"
.include "keypad.h65"
.include "lcd.h65"
.include "ps2_keyboard_simple_handler.h65"
.include "ps2_keyboard_text_handler.h65"
.export ps2_keyboard_util
.import home:absolute
.code
;;********************************************************************************
;; @brief Send various commands to the keyboard and print the response bytes
;; @details
;; - `0`: `0xf0` query scancode set
;; - `1`: `0xee` echo
;; - `2`: `0xf2` identify
;; - `3`: `0xf3` set typematic min speed and delay (`0b0111111`)
;; - `4`: `0xf4` enable
;; - `5`: `0xf5` disable
;; - `6`: `0xf3` set typematic max speed and min delay (`0b00000000`)
;; - `7`: `0xf7` set typematic all
;; - `8`: `0xf8` set make/release all
;; - `9`: `0xed` set CAPSLOCK led
;; - `#`: `0xed` set SCROLLLOCK led
;; - `C`: `0xed` set NUMLOCK led
;; - `D`: `0xed` turn all leds off
;; - `A`: reprint menu
;; - `B`: reprint menu
;; - `*`: jump home
;; For more details, see https://wiki.osdev.org/PS/2_Keyboard
;; @ingroup applications
;;********************************************************************************
.proc ps2_keyboard_util
stz kp::_DEBUG_VAL
jsr skb::init
beq print_menu
ps2kb_PrintCmdFailed
bra loop
print_menu:
Print MENU
loop:
wai
lda skb::scancode
beq @read_keypad
Printf FMT_CODE,skb::scancode
stz skb::scancode
@read_keypad:
lda kp::_DEBUG_VAL
jeq loop
stz kp::_DEBUG_VAL
cmp #'*'
jeq home
pha
jsr lcd::clear
pla
ldy #0 ; arg for commands
ldx #ps2kb::NO_DATA
cmp #'0'
beq @l0
cmp #'1'
beq @l1
cmp #'2'
beq @l2
cmp #'3'
beq @l3
cmp #'4'
beq @l4
cmp #'5'
beq @l5
cmp #'6'
beq @l6
cmp #'7'
beq @l7
cmp #'8'
beq @l8
cmp #'9'
beq @l9
cmp #'#'
beq @lhash
cmp #'A'
beq @lA
cmp #'B'
beq @lB
cmp #'C'
beq @lC
cmp #'D'
beq @lD
jsr lcd::print_char
jmp loop
@l0: ; get scancode set
lda #$f0
ldx #0
ldy #1
bra @send_cmd
@l1: ; echo
lda #$ee
bra @send_cmd
@l2: ; identify
lda #$f2
ldy #2
bra @send_cmd
@l3: ; set typematic min speed and delay
lda #$f3
ldx #%01111111
bra @send_cmd
@l4: ; enable
lda #$f4
bra @send_cmd
@l5: ; disable
lda #$f5
bra @send_cmd
@l6: ; set typematic max speed and min delay
lda #$f3
ldx #0
bra @send_cmd
@l7: ; set typematic all
lda #$f7
bra @send_cmd
@l8: ; set make/release all
lda #$f8
bra @send_cmd
@l9: ; set CAPSLOCK led
lda #$ed
ldx #kb::MOD::CAPSLOCK
bra @send_cmd
@lhash: ; set SCROLLLOCK led
lda #$ed
ldx #kb::MOD::SCROLLLOCK
bra @send_cmd
@lC: ; set NUMLOCK led
lda #$ed
ldx #kb::MOD::NUMLOCK
bra @send_cmd
@lD: ; turn all leds off
lda #$ed
ldx #0
bra @send_cmd
@lA:
; TODO
@lB: ; reprint menu
jmp print_menu
@send_cmd:
jsr ps2kb::send_command
ps2kb_WaitFinishCmd
Printf FMT_CMD_DATA, ps2kb::send_cmd, ps2kb::send_data, ps2kb::cmd_response, ps2kb::cmd_response+1, ps2kb::cmd_response+2
jmp loop
.endproc
.rodata
FMT_CODE: .asciiz "K %x "
FMT_CMD_DATA: .asciiz "C %x-%x > %x%x%x"
MENU: .byte " EE F2 F3 "
.byte " F4 F5 F3' MEN"
.byte " T1 T0 LC LN "
.asciiz " RET F0 LS L0 "

View File

@ -3,14 +3,16 @@
.export sleep
.code
;********************************************************************************
; @function sleep
; @param x: Time to sleep in centiseconds (10^-2s = 10ms)
; @details
; Interrupts might change the actual time to finish
; To be exact, time_cs is in units of 0.010244s
; @modifies: ARG15
;********************************************************************************
;;********************************************************************************
;; @function sleep
;; @param x: Time to sleep in centiseconds (10^-2s = 10ms) @ 1MHz
;; @details
;; Interrupts might change the actual time to finish
;; To be exact, time_cs is in units of 0.010244s
;; @modifies ARG15
;; @ingroup applications
;; @clock_dependent
;;********************************************************************************
.proc sleep
_VAR_1 = ARG15
stz _VAR_1

View File

@ -4,44 +4,76 @@
.include "lcd.h65"
.include "chars.h65"
.import home:absolute
.import SPI_IO
.import CODE_START: absolute
.import __SPI_SIZE__
.export spi_menu
.bss
trans_bytes: .res 1
trans_pages: .res 1
status: .res 1
status_str: .res 17 ; 16 + null
trans_bytes: .res 2 ;; used to check if screen needs to be updated
status_char: .res 1
status_str: .res 17 ;; space for 16 + null
.code
;;********************************************************************************
;; @brief Receive code via SPI and execute it
;; @ingroup applications
;;********************************************************************************
.proc spi_menu
stz trans_bytes
stz trans_pages
lda #'X'
sta status
stz trans_bytes+1
@print_menu:
jsr lcd::clear
Print MENU
@update_status:
lda spi_p::status
cmp #spi_p::STATUS::ERROR
beq @status_ERROR
cmp #spi_p::STATUS::DONE
beq @status_DONE
cmp #spi_p::STATUS::XFER
beq @status_XFER
cmp #spi_p::STATUS::XFER_SIZEL
beq @status_XFER_SIZEL
cmp #spi_p::STATUS::XFER_SIZEH
beq @status_XFER_SIZEH
@status_UNKNOWN:
lda #'?'
bra @update_status_end
@status_ERROR:
lda #'E'
bra @update_status_end
@status_DONE:
lda #'O'
bra @update_status_end
@status_XFER_SIZEL:
lda #'1'
bra @update_status_end
@status_XFER_SIZEH:
lda #'2'
bra @update_status_end
@status_XFER:
lda #'X'
@update_status_end:
sta status_char
@print_status:
lda #lcd::LINE4
jsr lcd::set_position
Strf FMT_STATUS,status_str,trans_pages,trans_bytes,status
Strf "%x%x/%x%x|%x%x|%c",status_str,trans_bytes+1,trans_bytes,spi_p::recv_size+1,spi_p::recv_size,spi_p::buffer_size+1,spi_p::buffer_size,status_char
PrintNC status_str
@loop:
wai
; check if a byte has been transferred
@check_byte:
lda spi_p::bytes_written
@check_received:
lda spi_p::recv_bytes
cmp trans_bytes
beq @read_keypad
@byte_written:
@byte_received:
sta trans_bytes
@check_page:
lda spi_p::pages_written
cmp trans_pages
beq @read_keypad
@page_written:
sta trans_pages
bra @print_status
lda spi_p::recv_bytes+1
sta trans_bytes+1
jmp @update_status
@read_keypad:
lda kp::_DEBUG_VAL
beq @loop
@ -57,24 +89,35 @@ status_str: .res 17 ; 16 + null
beq @spi_end
cmp #'C'
beq @spi_jump
bra @loop
cmp #'D'
beq @spi_jump2
jmp @update_status ; any other key
@spi_begin:
lda #'@'
sta status
jsr spi_p::begin
lda #<CODE_START
sta ARG0
lda #>CODE_START
sta ARG1
lda #<__SPI_SIZE__
sta ARG2
lda #>__SPI_SIZE__
sta ARG3
jsr spi_p::begin_read
jmp @print_menu
@spi_end:
lda #'X'
sta status
jsr spi_p::end
jsr spi_p::end_read
jmp @print_menu
@spi_jump:
jsr spi_p::end
jsr spi_p::end_read
jsr lcd::clear
Print START
jmp spi_p::CODE_START
Print "---START SPI---"
jmp CODE_START
@spi_jump2:
jsr spi_p::end_read
jsr lcd::clear
Printf " >>> %x%x >>> ", spi_p::buffer_ptr+1, spi_p::buffer_ptr
jmp (spi_p::buffer_ptr)
@return_home:
jsr spi_p::end
jsr spi_p::end_read
jmp home
.endproc
@ -84,6 +127,5 @@ MENU:
.byte "B> Stop Transfer"
.asciiz "C> Jump Home <*"
; .asciiz "0b0p Status: X"
FMT_STATUS: .asciiz "%x%xb Status:%x"
BEGIN: .asciiz "---BEGIN SPI---"
START: .asciiz "---START SPI---"

54
programs/viu.s65 Normal file
View File

@ -0,0 +1,54 @@
;;********************************************************************************
;; @file
;; @brief VIU - VI Unimproved
;;********************************************************************************
.include "lcd.h65"
.include "ps2_keyboard_text_handler.h65"
.import home:absolute
.zeropage
ptr: .res 2
.bss
buffer: .res 256
.code
.proc init
stz ptr
stz ptr+1
stz buffer
.endproc
.proc insert_mode
jsr lcd::clear
jsr kb::init
loop:
lda kb::char
ldx ptr
beq @no_char
stz kb::char
jsr lcd::print_char
bra loop
@no_char:
lda kb::keycode
beq loop
cmp #kb::K::ESCAPE
beq k_esacpe
cmp #kb::K::PRINT
beq clear_display
cmp #kb::K::BACKSPACE
beq k_backspace
bra loop
k_esacpe:
jmp home
clear_display:
jsr lcd::clear
bra loop
k_backspace:
lcd_DecrementCursor
lda #' '
jsr lcd::print_char
lcd_DecrementCursor
bra loop
.endproc

View File

@ -1,16 +1,22 @@
# 8-bit Breadboard Computer with W65C02S Processor
This repo contains the assembly code for my [6502-project](https://quintern.xyz/de/6502.html).
This repo contains the assembly code for my [6502-project](https://quintern.xyz/de/posts/2021_6502).
The assembler used for this project is the excellent [ca65](https://github.com/cc65/cc65).
After assembling it, the binary is loaded onto the EEPROM using [my *eeprom.py* script](https://git.quintern.xyz/MatthiasQuintern/AT28C256-rpi-util) on a Raspberry Pi 4B.
## Operating System
... is probably a far stretch, since it is just the programs I wrote pieced together. My "os" consists of these functionalities:
- Main Menu:
- Printer: Prints the characters you press on the keypad to the lcd.
- Temperature: Shows the temperature using a dht sensor. *Work in progress, this does not work yet*
- Text 1: Show a 4x16 character text (defined at compile time)
- Text 2: Show a 4x16 character text (defined at compile time)
- Ringbuffer for pressed keys.
... is probably a far stretch, since it is just the programs I wrote pieced together. My "OS" consists of these functionalities:
- Supported Hardware:
- 4x4 Matrix Keypad
- 4x16 Characters LCD Display
- PS/2 Keyboard
- SPI Connection to an Arduino
- Software
- Keyboard and keypad printer: Prints the characters you press on to the lcd
<!-- - Temperature: Shows the temperature using a dht sensor. *Work in progress, this does not work yet* -->
- (Stylishly) print formated strings to the LCD
- Sleep
- Send various commands to the keyboard
- Ringbuffer
> It's not much, but it's honest work.

View File

@ -1,227 +1,109 @@
;;********************************************************************************
;; @file
;; @details
;; This code is loaded to the computer via a SPI interface
;; Currently, it must be loaded to $5000, where the main loop will begin
;;********************************************************************************
.include "system.h65"
.include "string.h65"
.include "lcd.h65"
.include "math.h65"
.include "keypad.h65"
.include "ps2_keyboard_text_handler.h65"
.include "chars.h65"
.include "parity.h65"
.include "sleep.h65"
.import homeloop:absolute
.import home:absolute
.import ps2_keyboard_util:absolute
.import ps2_keyboard_printer:absolute
.segment "SPI"
.export CODE_START
.import memcopy
.scope kb
KB_IO = IO1
.endscope
CODE_START:
.assert * = $5000, error, "SPI Code not at $5000"
jsr lcd::clear
DEBUG_LED_OFF 0
DEBUG_LED_OFF 1
DEBUG_LED_OFF 2
stz kp::_DEBUG_VAL
lda #'$'
jsr lcd::print_char
; stz kp::_DEBUG_VAL
; @loop:
; lda kp::_DEBUG_VAL
; beq @loop
; stz kp::_DEBUG_VAL
; cmp #'*'
; jeq homeloop
; jsr lcd::print_char
; bra @loop
lda #<kb_irq1
sta ARG0
lda #>kb_irq1
sta ARG1
lda #<$3000
sta ARG2
lda #>$3000
sta ARG3
ldy #20
jsr memcopy
lda #<kb_irq2
sta ARG0
lda #>kb_irq2
sta ARG1
lda #<$3100
sta ARG2
lda #>$3100
sta ARG3
ldy #20
jsr memcopy
lda #'?'
jsr lcd::print_char
; PrintNC $3000
jsr kbinit
lda #'%'
jsr lcd::print_char
stz kp::_DEBUG_VAL
ldy #0
@loop:
wai
@read_keypad:
lda kp::_DEBUG_VAL
beq @loop
stz kp::_DEBUG_VAL
jeq @loop
cmp #'*'
jeq homeloop
cmp #'0'
beq @l0
cmp #'1'
beq @l1
cmp #'2'
beq @l2
cmp #'3'
beq @l3
cmp #'4'
beq @l4
cmp #'5'
beq @l5
cmp #'6'
beq @l6
cmp #'7'
beq @l7
cmp #'8'
beq @l8
cmp #'9'
beq @l9
cmp #'#'
beq @lhash
cmp #'A'
beq @lA
cmp #'B'
beq @lB
cmp #'C'
beq @lC
cmp #'D'
beq @lD
jsr lcd::print_char
bra @loop
jmp @loop
@l0:
jmp ps2_keyboard_util
@l1:
; jsr irq_on_shift_reg
jsr $3000
lda #'*'
jsr lcd::print_char
bra @loop
jmp ps2_keyboard_printer
@l2:
; jsr irq_on_timer
jsr $3100
lda #'#'
lcd_SetCursorPos $26
lda #'6'
jsr lcd::print_char
lcd_IncrementCursor
lda #'8'
jsr lcd::print_char
bra @loop
@l3:
lda $3000,y
jsr lcd::print_char
iny
bra @loop
@l4:
@l5:
@l6:
@l7:
@l8:
@l9:
@lhash:
@lA:
lda kb::KB_IO + IO::SR
jsr lcd::print_char
bra @loop
@lB:
Strf fmt_str, out_str, keycode
Print out_str
bra @loop
@lC:
jsr lcd::clear
bra @loop
@lD:
jmp @loop
kbinit:
lda #'['
jsr lcd::print_char
; - use the shift register interrupts to read the first 8 bits
; set shift register to shift in under external clock on CB1
; - configure timer for timing the read of the last 3 bits
; timer 2 one shot mode is sufficient, leaves T1 available
lda #(IO::ACR::SR_SIN_PHIE | IO::ACR::T2_IRQ_LOAD)
tsb kb::KB_IO + IO::ACR
; the 3 last bits take about 230us, at @1MHz => wait 230 cycles and then the shift register
lda #230
sta kb::KB_IO + IO::T2CL
stz key_read
stz key_read+1
; enable SR interrupts
lda #(IO::IRQ::IRQ | IO::IRQ::SR)
sta kb::KB_IO + IO::IER
; load SR to reset
lda kb::KB_IO + IO::SR
lda #']'
jsr lcd::print_char
rts
;; @details
;; IO::SR has to be read before the next bit is shifted in, which happens ~75us after the irq
;; at 1MHz, handlings this interrupt takes about 50us (without any additional debug code), so it should work
irq_on_shift_reg:
; lda #'{'
; jsr lcd::print_char
lda kb::KB_IO + IO::SR
sta key_read
stz kb::KB_IO + IO::SR
; disable SR interrupts
lda #IO::IRQ::SR
sta kb::KB_IO + IO::IER
; enable timer interrupts
lda #(IO::IRQ::IRQ | IO::IRQ::T2)
sta kb::KB_IO + IO::IER
; start timer
lda #1
sta kb::KB_IO + IO::T2CH
; lda #'}'
; jsr lcd::print_char
rts
irq_on_timer:
; lda #'<'
; jsr lcd::print_char
lda kb::KB_IO + IO::SR
sta key_read + 1
lda kb::KB_IO + IO::T2CL ; clear interrupt flag
; disable timer interrupts
lda #(IO::IRQ::T2)
sta kb::KB_IO + IO::IER
; enable shift register interrupts
lda #(IO::IRQ::IRQ | IO::IRQ::SR)
sta kb::KB_IO + IO::IER
; reset SR
stz kb::KB_IO + IO::SR
; lda #'|'
; rotate bit 2 (last bit of keycode) into the carry
ror
ror
ror
lda key_read ; not affecting carry
rol ; rotate carry into byte, rotate startbit into carry
; TODO byte is inverted, maybe consider wasting 256 bytes for a bit reverse lookup table?
sta keycode
Strf fmt_str2, out_str, keycode, key_read, key_read+1
PrintNC out_str
stz key_read
stz key_read+1
rts
key_read: .res 2
keycode: .res 1
kb_irq1:
; lda #'!'
; jsr lcd::print_char
jsr irq_on_shift_reg
; lda #':'
; jsr lcd::print_char
rts
.byte '='
kb_irq2:
; lda #'?'
; jsr lcd::print_char
jsr irq_on_timer
; lda #';'
; jsr lcd::print_char
rts
.byte '@'
out_str: .res 40
fmt_str: .asciiz "kc%x;"
fmt_str2: .asciiz "kc%x-%x-%x; "
out_str: .res 40
FMT_INIT_FAIL: .asciiz "KB init failed: %x%x > %x%x%x"

View File

@ -1,16 +1,21 @@
;********************************************************************************
; @module ringbuffer
; @type utility
; @details
; Size of the ringbuffer is RBUF_MEM_END - RBUF_MEM_START - 2, since two bytes
; are used by the read and write pointer
; The RBUF_NAME variable must be defined, the functions will then be exported
; as rb_<RBUF_NAME>_<function> where <function> = init, read or write
; @requires
; RBUF_MEM_START: First address of ringbuffer memory space
; RBUF_MEM_END: Last address of ringbuffer memory space
; RBUF_NAME: Name of the ringbuffer
;********************************************************************************
;;********************************************************************************
;; @file
;; @brief Ringbuffer
;; @details
;; Size of the ringbuffer is RBUF_MEM_END - RBUF_MEM_START - 2, since two bytes
;; are used by the read and write pointer
;; The `RBUF_NAME` variable must be defined, the functions will then be exported
;; as `rb_<RBUF_NAME>_<function>` where `<function>` = `init`, `read` or `write`
;;
;; @note
;; Doxygen can not generate proper documentation for this module, because
;; all names are defined using macros
;; @requires
;; RBUF_MEM_START: First address of ringbuffer memory space
;; RBUF_MEM_END: Last address of ringbuffer memory space
;; RBUF_NAME: Name of the ringbuffer
;; @ingroup libs
;;********************************************************************************
.code
.ifndef RBUF_MEM_START
@ -36,9 +41,9 @@ RB_LENGTH = RBUF_MEM_END - RBUF_MEM_START - 2
.endif
;********************************************************************************
; @function Initialize the buffer
;********************************************************************************
;;********************************************************************************
;; @function Initialize the buffer
;;********************************************************************************
.ident(.concat(_RBUF_NAME, "_init")):
.scope
stz RB_WRITE
@ -46,13 +51,13 @@ RB_LENGTH = RBUF_MEM_END - RBUF_MEM_START - 2
rts
.endscope
;********************************************************************************
; @function Read a value from the buffer
; @details
; If there is no value to be read, the Pz will be set
; @returns A: value
; @modifies: A, X
;********************************************************************************
;;********************************************************************************
;; @function Read a value from the buffer
;; @details
;; If there is no value to be read, the Pz will be set
;; @returns A: value
;; @modifies A, X
;;********************************************************************************
.ident(.concat(_RBUF_NAME, "_read")):
.scope
ldx RB_READ
@ -72,11 +77,11 @@ RB_LENGTH = RBUF_MEM_END - RBUF_MEM_START - 2
rts
.endscope
;********************************************************************************
; @function Write a value to the buffer
; @param A: value to store
; @modifies: X
;********************************************************************************
;;********************************************************************************
;; @function Write a value to the buffer
;; @param A: value to store
;; @modifies X
;;********************************************************************************
.ident(.concat(_RBUF_NAME, "_write")):
.scope
; lda kp_VALUES, x ; load the char in a

View File

@ -1,35 +1,51 @@
;********************************************************************************
; @module IO-W65C22
; @type utility
; @device Western Design - W65C22N Versatile Interface Adapter
; @details
;********************************************************************************
;;********************************************************************************
;; @file
;; @brief VIA W65C22 header
;; @ingroup system
;; @details
;; @device Western Design - W65C22N Versatile Interface Adapter
;;********************************************************************************
.ifndef INCLUDE_IOW65C22
INCLUDE_IOW65C22 = 1
;;********************************************************************************
;; @brief Versatile Interface Adapter (VIA) W65C22
;; @ingroup system
;; @device Western Design - W65C22N Versatile Interface Adapter
;; @todo rename to VIA
;; @warning @anchor via_hardware_bug
;; The 6522 and 65C22 have a hardware bug, where a bit is not read in when the
;; external shift register clock transitions close to @f$ \phi_2 @f$.
;; To resolve this, you should use a @f$ \phi_2 @f$ controlled flip flop to ensure
;; the external clock transitions after the system clock.
;;********************************************************************************
.scope IO
; not using a struct for this since the syntax for access would be the same,
; ie label+IO::RA
;;********************************************************************************
;; @brief VIA register offsets from the base address
;; @details
;; Use like this: `VIA_ADDRESS + IO::RB`
;;********************************************************************************
.enum
; IO-CHIPS OFFSETS FOR PINS FROM BASE ADDRESS
RB = $0 ; Register B (ORB/IRB)
RA = $1 ; Register A (ORA/IRA)
DDRB = $2 ; Data Direction Register B
DDRA = $3 ; Data Direction Register A
T1CL = $4 ; Timer 1 Counter Low/High
RB = $0 ;;< Register B (ORB/IRB)
RA = $1 ;;< Register A (ORA/IRA)
DDRB = $2 ;;< Data Direction Register B
DDRA = $3 ;;< Data Direction Register A
T1CL = $4 ;;< Timer 1 Counter Low/High
T1CH = $5
T1LL = $6 ; Timer 1 Latch Low/High
T1LL = $6 ;;< Timer 1 Latch Low/High
T1LH = $7
T2CL = $8 ; Timer 2 Counter Low/High
T2CL = $8 ;;< Timer 2 Counter Low/High
T2CH = $9
SR = $a ; Shift Register
ACR = $b ; Auxiliary Control Register
PCR = $c ; Peripheral Control Register
IFR = $d ; Interrupt Flag Register
IER = $e ; Interrupt Enable Register
RANH = $f ; RA without handshake
SR = $a ;;< Shift Register
ACR = $b ;;< Auxiliary Control Register
PCR = $c ;;< Peripheral Control Register
IFR = $d ;;< Interrupt Flag Register
IER = $e ;;< Interrupt Enable Register
RANH = $f ;;< RA without handshake
.endenum
.enum ACR_MASK ; ACR Masks
@ -40,27 +56,32 @@ INCLUDE_IOW65C22 = 1
T1 = %11000000
.endenum
.enum ACR ; ACR Settings
;;********************************************************************************
;; @brief Settings for Auxiliary Control Register
;; @details
;; `OR` the values with the appropriate IO::ACR_MASK to not target only specific settings
;;********************************************************************************
.enum ACR
; SR Modes
SR_DISABLE = %00000000 ; Disabled
SR_SIN_T2 = %00000100 ; Shift in under control of T2
SR_SIN_PHI2 = %00001000 ; Shift in under control of PHI2
SR_SIN_PHIE = %00001100 ; Shift in under control of external clock
SR_SOUT_FREE_T2 = %00010000 ; Shift out free running at T2 rate
SR_SOUT_T2 = %00010100 ; Shift out under control of T2
SR_SOUT_PHI2 = %00011000 ; Shift out under control of PHI2
SR_SOUT_PHIE = %00011100 ; Shift out under control of external clock
SR_DISABLE = %00000000 ;;< Disabled
SR_SIN_T2 = %00000100 ;;< Shift in under control of T2
SR_SIN_PHI2 = %00001000 ;;< Shift in under control of PHI2
SR_SIN_PHIE = %00001100 ;;< Shift in under control of external clock
SR_SOUT_FREE_T2 = %00010000 ;;< Shift out free running at T2 rate
SR_SOUT_T2 = %00010100 ;;< Shift out under control of T2
SR_SOUT_PHI2 = %00011000 ;;< Shift out under control of PHI2
SR_SOUT_PHIE = %00011100 ;;< Shift out under control of external clock
; T1 Modes
T1_IRQ_LOAD = %00000000 ; Timed interrupt each time T1 is loaded
T1_IRQ_CONT = %01000000 ; Continuous interrupts
T1_IRQ_LOAD_PB7 = %10000000 ; Timed interrupt each time T1 is loaded - PB7 One Shot output
T1_IRQ_CONT_PB7 = %11000000 ; Continuous interrupts - PB7 Square wave output
T1_IRQ_LOAD = %00000000 ;;< Timed interrupt each time T1 is loaded
T1_IRQ_CONT = %01000000 ;;< Continuous interrupts
T1_IRQ_LOAD_PB7 = %10000000 ;;< Timed interrupt each time T1 is loaded - PB7 One Shot output
T1_IRQ_CONT_PB7 = %11000000 ;;< Continuous interrupts - PB7 Square wave output
; T2 Modes
T2_IRQ_LOAD = %00000000 ; Timed interrupt each time T2 is loaded
T2_COUNT_PB6 = %00100000 ; Count down with pulsen on PB6
T2_IRQ_LOAD = %00000000 ;;< Timed interrupt each time T2 is loaded
T2_COUNT_PB6 = %00100000 ;;< Count down with pulsen on PB6
; Latch
LATCH_DISABLE = %00000000
LATCH_ENBLE = %00000000
LATCH_DISABLE = %00000000 ;;< `OR` this with IO::ACR_MASK::PA or IO::ACR_MASK::PB
LATCH_ENBLE = %00000011 ;;< `OR` this with IO::ACR_MASK::PA or IO::ACR_MASK::PB
.endenum
.enum PCR_MASK ; PCR Masks
@ -70,34 +91,42 @@ INCLUDE_IOW65C22 = 1
CB2 = %11100000 ;
.endenum
;;********************************************************************************
;; @brief Settings for Peripheral Control Register
;; @details
;; `OR` the values with the appropriate IO::PCR_MASK to not target only specific settings
;;********************************************************************************
.enum PCR
; CA1 Modes
CA1_IN_AE = %00000000 ; Input-negative active edge
CA1_IP_AE = %00000001 ; Input-positive active edge
CA1_IN_AE = %00000000 ;;< Input-negative active edge
CA1_IP_AE = %00000001 ;;< Input-positive active edge
; CA2 Modes
CA2_IN_AE = %00000000 ; Input-negative active edge
CA2_IN_AE_IRQ_IND= %00000010 ; Independent interrupt input-negative edge
CA2_IP_AE = %00000100 ; Input-positive active edge
CA2_IP_AE_IRQ_IND= %00000110 ; Independent interrupt input-positive edge
CA2_IN_HANDSHAKE = %00001000 ; Handshake output
CA2_IN_PULSE_OUT = %00001010 ; Pulse output
CA2_IN_LOW_OUT = %00001100 ; Low output
CA2_IN_HIGH_OUT = %00001110 ; High output
CA2_IN_AE = %00000000 ;;< Input-negative active edge
CA2_IN_AE_IRQ_IND= %00000010 ;;< Independent interrupt input-negative edge
CA2_IP_AE = %00000100 ;;< Input-positive active edge
CA2_IP_AE_IRQ_IND= %00000110 ;;< Independent interrupt input-positive edge
CA2_OUT_HANDSHAKE= %00001000 ;;< Handshake output
CA2_OUT_PULSE = %00001010 ;;< Pulse output
CA2_OUT_LOW = %00001100 ;;< Low output
CA2_OUT_HIGH = %00001110 ;;< High output
; CB1 Modes
CB1_IN_AE = %00000000 ; Input-negative active edge
CB1_IP_AE = %00010000 ; Input-positive active edge
CB1_IN_AE = %00000000 ;;< Input-negative active edge
CB1_IP_AE = %00010000 ;;< Input-positive active edge
; CB2 Modes
CB2_IN_AE = %00000000 ; Input-negative active edge
CB2_IN_AE_IRQ_IND= %00100000 ; Independent interrupt input-negative edge
CB2_IP_AE = %01000000 ; Input-positive active edge
CB2_IP_AE_IRQ_IND= %01100000 ; Independent interrupt input-positive edge
CB2_IN_HANDSHAKE = %10000000 ; Handshake output
CB2_IN_PULSE_OUT = %10100000 ; Pulse output
CB2_IN_LOW_OUT = %11000000 ; Low output
CB2_IN_HIGH_OUT = %11100000 ; High output
CB2_IN_AE = %00000000 ;;< Input-negative active edge
CB2_IN_AE_IRQ_IND= %00100000 ;;< Independent interrupt input-negative edge
CB2_IP_AE = %01000000 ;;< Input-positive active edge
CB2_IP_AE_IRQ_IND= %01100000 ;;< Independent interrupt input-positive edge
CB2_OUT_HANDSHAKE= %10000000 ;;< Handshake output
CB2_OUT_PULSE = %10100000 ;;< Pulse output
CB2_OUT_LOW = %11000000 ;;< Low output
CB2_OUT_HIGH = %11100000 ;;< High output
.endenum
; IFR/IER bits
;;********************************************************************************
;; @brief Interrupt Flag/Enable Register bits
;;********************************************************************************
.enum IRQ
CA2 = %00000001
CA1 = %00000010
@ -109,5 +138,24 @@ INCLUDE_IOW65C22 = 1
IRQ = %10000000
.endenum
;;********************************************************************************
;; @macro Enable an interrupt source
;; @modifies A
;; @param flag: A flag of the interrupt flag register (IO::IRQ)
;;********************************************************************************
.macro IO_EnableIRQ ioaddr, flag
lda #(IO::IRQ::IRQ | flag)
sta ioaddr + IO::IER
.endmacro
;;********************************************************************************
;; @macro Disable an interrupt source
;; @modifies A
;; @param flag: A flag of the interrupt flag register (IO::IRQ)
;;********************************************************************************
.macro IO_DisableIRQ ioaddr, flag
lda #flag
sta ioaddr + IO::IER
.endmacro
.endscope ; IO
.endif ; guard

View File

@ -1,7 +1,8 @@
;********************************************************************************
; @module irq_handler
; @type system
;********************************************************************************
;;********************************************************************************
;; @file
;; @brief WIP irq handler with jump tables and stuff
;; @ingroup system
;;********************************************************************************
.ifndef INCLUDE_IRQ_HANDLER
INCLUDE_IRQ_HANDLER = 1

View File

@ -1,17 +1,20 @@
;********************************************************************************
; @module keypad4x4
; @type driver
; @device 4x4 Matrix Keypad
; @details
; The LCD must be connected to a W65C22N Interface Chip:
; - IO.RA0-7 ->
; @requires KP_IO: Base Address of IO Chip
; @depends IO-W65C22N
;********************************************************************************
;;********************************************************************************
;; @file
;; @brief 4x4 Matrix Keypad driver
;; @details
;; The LCD must be connected to a W65C22N Interface Chip:
;; - IO.RA0-7 ->
;; @requires KP_IO: Base Address of IO Chip
;; @depends IO-W65C22N
;; @ingroup drivers
;;********************************************************************************
.ifndef INCLUDE_KEYPAD
INCLUDE_KEYPAD = 1
.include "system.h65"
;;********************************************************************************
;; @brief 4x4 Matrix Keypad
;;********************************************************************************
.scope kp
KP_IO = IO2
; .import KP_IO

View File

@ -1,26 +1,63 @@
;********************************************************************************
; @module LCD-W164B
; @type driver
; @device ELECTRONIC ASSEMBLY - W164B-NLW
; @details
; The LCD must be connected to a W65C22N Interface Chip:
; - IO.RB0-7 -> LCD.D0-7 data lines
; - IO.RA5 -> LCD.RS register select
; - IO.RA6 -> LCD.R/W read/write
; - IO.RA7 -> LCD.E enable
;
; @requires IO: Base Address of IO Chip
; @depends IO-W65C22N
;********************************************************************************
;;********************************************************************************
;; @file
;; @brief LCD-W164B driver
;; @ingroup drivers
;; @device ELECTRONIC ASSEMBLY - W164B-NLW
;; @details
;; The LCD must be connected to a W65C22N Interface Chip:
;; - IO.RB0-7 -> LCD.D0-7 data lines
;; - IO.RA5 -> LCD.RS register select
;; - IO.RA6 -> LCD.R/W read/write
;; - IO.RA7 -> LCD.E enable
;; @requires IO: Base Address of IO Chip
;; @depends IO-W65C22N
;;********************************************************************************
.ifndef INCLUDE_LCD
INCLUDE_LCD = 1
.include "system/system.h65"
;;********************************************************************************
;; @brief LCD character display
;; @ingroup drivers
;;********************************************************************************
.scope lcd
LCD_IO = IO1
Import lcd,init,clear,print,print_char,set_position,set_custom_char
Import lcd,_cmd,_wait_nbusy,_write_ram,_read_ram
Import lcd,_cmd,_wait_nbusy,_write_ram,_read_ram,_set_dd_ram_addr_from_charcount
Import lcd,_charcount
; PIN MASKS
E = %10000000
RW = %01000000
RS = %00100000
RA_MASK = %11100000
;;********************************************************************************
;; LCD Instructions
;; @details See LCD-W164B datasheet for a complete list
;;********************************************************************************
.enum CMD
CLEAR = %00000001 ;; clear display
ENTRY_MODE = %00000110 ;; auto-shift cursor
DISPLAY_ON = %00001111 ;; everything on, with blinking cursor
SHIFT_CUR_LEFT = %00101000 ;; shift the cursor to the left
SHIFT_DIS_LEFT = %00111000 ;; shift the display to the left
SHIFT_DIS_RIGHT = %00110000 ;; shift the display to the right
SHIFT_CUR_RIGHT = %00100000 ;; shift the cursor to the right
FUNCTION_SET = %00111000 ;; 8-bit, 4 lines, 5x7 font
SET_CG_ADDRESS = %01000000 ;; or this with the address
SET_ADDRESS = %10000000 ;; or this with the address
.endenum
; LCD Constants
LINE1 = $00
LINE2 = $40
LINE3 = $10
LINE4 = $50
.endscope
;;********************************************************************************
;; @macro Clear the screen and print a null-terminated string
@ -73,29 +110,55 @@ Import lcd,_cmd,_wait_nbusy,_write_ram,_read_ram
jsr lcd::print
.endmacro
; PIN MASKS
E = %10000000
RW = %01000000
RS = %00100000
RA_MASK = %11100000
;;********************************************************************************
;; @macro Set the cursor to the n-th position
;; @param n: Position [0,$40) or a register
;;********************************************************************************
.macro lcd_SetCursorPos n
.if .match (n, a)
.elseif .match (n, x)
txa
.elseif .match (n, y)
tya
.else
lda #n
.endif
sta lcd::_charcount
jsr lcd::_set_dd_ram_addr_from_charcount
.endmacro
; LCD Instructions (see datasheet for more)
CMD_CLEAR = %00000001 ; clear display
CMD_ENTRY_MODE = %00000110 ; auto-shift cursor
CMD_DISPLAY_ON = %00001111 ; everything on, with blinking cursor
CMD_SHIFT_CUR_LEFT = %00101000 ; shift the display to the left
CMD_SHIFT_DIS_LEFT = %00111000 ; shift the display to the left
CMD_SHIFT_DIS_RIGHT = %00110000 ; shift the display to the right
CMD_SHIFT_CUR_RIGHT = %00100000 ; shift the display to the right
CMD_FUNCTION_SET = %00111000 ; 8-bit, 4 lines, 5x7 font
CMD_SET_CG_ADDRESS = %01000000 ; or with the address
CMD_SET_ADDRESS = %10000000 ; or with the address
; LCD Constants
LINE1 = $00
LINE2 = $40
LINE3 = $10
LINE4 = $50
;;********************************************************************************
;; @macro Move the cursor to the right
;; @param n: Number of characters to move n. Defaults to 1 if not provided
;;********************************************************************************
.macro lcd_IncrementCursor n
.ifblank n
inc lcd::_charcount
.elseif n = 1
inc lcd::_charcount
.else
lda lcd::_charcount
add #n
sta lcd::_charcount
.endif
jsr lcd::_set_dd_ram_addr_from_charcount
.endmacro
.endscope
;;********************************************************************************
;; @macro Move the cursor to the left
;; @param n: Number of characters to move n. Defaults to 1 if not provided
;;********************************************************************************
.macro lcd_DecrementCursor n
.ifblank n
dec lcd::_charcount
.elseif n = 1
dec lcd::_charcount
.else
lda lcd::_charcount
sub #n
sta lcd::_charcount
.endif
jsr lcd::_set_dd_ram_addr_from_charcount
.endmacro
.endif ; guard

View File

@ -5,12 +5,13 @@
.endif
Export lcd,init,clear,print,print_char,set_position,set_custom_char
Export lcd,_cmd,_wait_nbusy,_write_ram,_read_ram
Export lcd,_cmd,_wait_nbusy,_write_ram,_read_ram,_set_dd_ram_addr_from_charcount
Export lcd,_charcount
; RAM VARIABLES
.bss
charcount: .res 1
_charcount: .res 1
; TODO when clockspeeds are more than 1MHz, _cmd, _write_ram and _read_ram might need to be adjusted
; @TODO when clockspeeds are more than 1MHz, _cmd, _write_ram and _read_ram might need to be adjusted
.code
;;********************************************************************************
;; @function Initialize the lcd module
@ -22,21 +23,21 @@ charcount: .res 1
lda #$ff ; RB 0-7 output
sta lcd::LCD_IO+IO::DDRB
MaskedWrite lcd::LCD_IO+IO::DDRA, (lcd::RS | lcd::RW | lcd::E), lcd::RA_MASK
; lda #(lcd::RS | lcd::RW | lcd::E) ; RA 5-7 output
; sta lcd::LCD_IO+IO::DDRA
lda lcd::LCD_IO+IO::DDRA
ora #(lcd::RS | lcd::RW | lcd::E)
sta lcd::LCD_IO+IO::DDRA
; init lcd
lda #lcd::CMD_FUNCTION_SET
lda #lcd::CMD::FUNCTION_SET
jsr _cmd
lda #lcd::CMD_DISPLAY_ON
lda #lcd::CMD::DISPLAY_ON
jsr _cmd
lda #lcd::CMD_CLEAR
lda #lcd::CMD::CLEAR
jsr _cmd
lda #lcd::CMD_ENTRY_MODE
lda #lcd::CMD::ENTRY_MODE
jsr _cmd
stz charcount
stz _charcount
rts
.endproc
@ -44,7 +45,7 @@ charcount: .res 1
;;********************************************************************************
;; @function Set the cursor to a position
;; @param A: cursor position: `(lcd::LINEX + offset)`, where offset € [$0, $f]
;; @details:
;; @details
;; If the position is too large, it will be set to char 4 in line 2
;; @returns A: the cursor position
;;********************************************************************************
@ -61,37 +62,37 @@ charcount: .res 1
cmp #$10
bge @line3
bra @set
; @line1: ; starts at $00, charcount at $00
@line2: ; starts at $40, charcount at $10
; @line1: ; starts at $00, _charcount at $00
@line2: ; starts at $40, _charcount at $10
sbc #$30 ; carry is already set
bra @set
@line3: ; starts at $10, charcount at $20
@line3: ; starts at $10, _charcount at $20
add #$10
bra @set
@line4: ; starts at $50, charcount at $30
@line4: ; starts at $50, _charcount at $30
sbc #$20
@set:
sta charcount
sta _charcount
pla
pha
ora #lcd::CMD_SET_ADDRESS
ora #lcd::CMD::SET_ADDRESS
jsr _cmd
; and #(<~lcd::CMD_SET_ADDRESS) ; return original argument
; and #(<~lcd::CMD::SET_ADDRESS) ; return original argument
pla
rts
@invalid:
pla ; otherwise stack corruption
lda #$13
sta charcount
lda #(lcd::CMD_SET_ADDRESS | (lcd::LINE2 + 3))
sta _charcount
lda #(lcd::CMD::SET_ADDRESS | (lcd::LINE2 + 3))
jsr _cmd
lda #(lcd::LINE2 + 3)
rts
.endproc
;;********************************************************************************
;; PRINTING TO LCD
;;********************************************************************************
;********************************************************************************
; PRINTING TO LCD
;********************************************************************************
;;********************************************************************************
;; @function Clear the display
@ -99,8 +100,8 @@ charcount: .res 1
;; @modifies A
;;********************************************************************************
.proc clear
stz charcount
lda #lcd::CMD_CLEAR
stz _charcount
lda #lcd::CMD::CLEAR
jsr _cmd
rts
.endproc
@ -108,7 +109,7 @@ charcount: .res 1
;;********************************************************************************
;; @function Print a null-terminated string
;; @param ARG0-1 Address of the string to print
;; @modifies: A,Y
;; @modifies A,Y
;; @returns Y: Length of the string
;;********************************************************************************
.proc print
@ -131,7 +132,7 @@ charcount: .res 1
.proc print_char
pha
jsr _write_ram
inc charcount
inc _charcount
jsr _break_line
pla ; put char back in a
.endproc
@ -142,7 +143,7 @@ charcount: .res 1
;; @param A: The ASCII code: 0-7
;; @param ARG0-1: Start address of the 8 bytes describing the character
;; @returns: C: 0 => success, 1 => invalid argument
;; @modifies: A,Y
;; @modifies A,Y
;;********************************************************************************
.proc set_custom_char
cmp #8
@ -150,7 +151,7 @@ charcount: .res 1
rol ; address is bytes 3-5
rol
rol
ora #lcd::CMD_SET_CG_ADDRESS
ora #lcd::CMD::SET_CG_ADDRESS
jsr lcd::_cmd
ldy #0
@loop:
@ -176,29 +177,34 @@ charcount: .res 1
.proc _wait_nbusy
pha
stz lcd::LCD_IO + IO::DDRB ; set IO1-IO + IO::RB to input
@lcd_wait_nbusy_loop: ; read the busy flag
; TODO use update_with_mask
lda #lcd::RW
lda lcd::LCD_IO + IO::RA
and #<~lcd::RA_MASK
ora #lcd::RW
sta lcd::LCD_IO + IO::RA
lda #(lcd::RW | lcd::E)
@lcd_wait_nbusy_loop: ; read the busy flag
; set E low, then high
lda lcd::LCD_IO + IO::RA
and #<~lcd::E
sta lcd::LCD_IO + IO::RA
ora #lcd::E
sta lcd::LCD_IO + IO::RA
lda lcd::LCD_IO + IO::RB
bmi @lcd_wait_nbusy_loop ; msb set
; and #%10000000 ; and updates zero flag, if not set retry
; bne @lcd_wait_nbusy_loop
lda #%00000000 ; TODO dont overwrite 0-4
lda lcd::LCD_IO + IO::RA
and #<~lcd::RA_MASK
sta lcd::LCD_IO + IO::RA
lda #%11111111 ; set IO1-IO + IO::RB to output
sta lcd::LCD_IO + IO::DDRB
pla
rts
.endproc
;;********************************************************************************
;; @function Send a command to the lcd
;; @function Send a @ref lcd::CMD "command" to the lcd
;; @param A: command
;; @modifies A
;;********************************************************************************
@ -281,11 +287,54 @@ charcount: .res 1
;;********************************************************************************
;; @function Set the LCD DD-RAM Address so that text linebreaks after 16 chars
;; @function Set the LCD DD-RAM Address from the character count
;; @details
;; Sets the DD-RAM Address so that a line break occurs after 16 characters
;; If _charcount is more than $40, the position will be set to _charcount % $40
;;********************************************************************************
.proc _set_dd_ram_addr_from_charcount
cmp #$40 ; set to line1 when full
bcs @wrap_to_line1 ; a € [$40, $ff]
cmp #$10
bcc @line1 ; a € [$00, $0f]
cmp #$20
bcc @line2 ; a € [$10, $1f]
cmp #$30
bcc @line3 ; a € [$20, $2f]
; bra @line4; a € [$30, $3f]
@line4:
clc
adc #$20 ; _charcount $30-$3f -> $50 - $5f
bra @set_address
@wrap_to_line1:
and #%00111111
; now every _charcount is mapped between 0 and $3f
bra @set_address
@line2:
adc #$30 ; _charcount $10-$1f -> $40 - $4f
bra @set_address
@line3:
sec
sbc #$10 ; _charcount $20-$2f -> $10 - $1f
bra @set_address
@line1: ; _charcount $00-$1f -> $00 - $0f - nothing to do
@set_address:
ora #lcd::CMD::SET_ADDRESS
jsr _cmd
rts
.endproc
;;********************************************************************************
;; @function Set the LCD DD-RAM Address to te next line if the end of the line was reached
;; @details
;; If the cursor is past the end of a line, the DD-RAM Address is set to the next line.
;; If _charcount is more than $40, the position will be set to line 1.
;; This function is intended to be used with to set the correct address when
;; auto-shift is enabled
;;********************************************************************************
.proc _break_line
; check if checks are necessary
lda charcount
lda _charcount
beq @line1
bit #%10001111 ; ($10 | $20 | $30 | $40) = %01110000
beq @check
@ -302,20 +351,20 @@ charcount: .res 1
bge @line1
rts
@line1:
stz charcount
lda #(lcd::CMD_SET_ADDRESS | lcd::LINE1)
stz _charcount
lda #(lcd::CMD::SET_ADDRESS | lcd::LINE1)
jsr _cmd
rts
@line2:
lda #(lcd::CMD_SET_ADDRESS | lcd::LINE2)
lda #(lcd::CMD::SET_ADDRESS | lcd::LINE2)
jsr _cmd
rts
@line3:
lda #(lcd::CMD_SET_ADDRESS | lcd::LINE3)
lda #(lcd::CMD::SET_ADDRESS | lcd::LINE3)
jsr _cmd
rts
@line4:
lda #(lcd::CMD_SET_ADDRESS | lcd::LINE4)
lda #(lcd::CMD::SET_ADDRESS | lcd::LINE4)
jsr _cmd
rts
.endproc

219
system/ps2_keyboard.h65 Normal file
View File

@ -0,0 +1,219 @@
;;********************************************************************************
;; @file
;; @brief PS/2 Keyboard driver
;; @ingroup drivers
;; @details
;; Support for a PS2 Keyboard using the shift register of a 6522 VIA
;; @section reading Reading a scancode/command answer
;; Pressing a key causes 11 bits to be sent: 1 start - 8 scancode - 1 parity - 1 stop
;; The VIA is set up to interrupt after 8 bits have been shifted into the shift register
;; from the external clock pulses of the keyboard (additional hardware required to
;; address the hardware bug of the VIA, where bit get lost when the external clock
;; transition happens during falling edge of PHI2). After reading the shift register,
;; the VIAs T2 timer is set to interrupt after the last 3 bits have been shifted in,
;; which takes about ~230ms.
;; T2 is used because it leaves to more versatile T1 available for something else
;;
;; @section processing Processing a scancode
;; The scancode may be processed by storing the address of a handler subroutine in
;; `ps2kb::scancode_handler`. This handler can load the scancode byte from `ps2kb::scancode`.
;;
;; To use a different layout, change the enum K and the CHARS_NOMOD and CHARS_MODSHIFT
;; @warning
;; The value with which the timer is loaded must depends on the clock frequency
;; of the keyboard and the computer
;;********************************************************************************
.ifndef INCLUDE_PS2_KEYBOARD
INCLUDE_PS2_KEYBOARD = 1
.include "system.h65"
;;********************************************************************************
;; @brief PS/2 Keyboard
;; This requires the data line to be hooked up to `CB2` and the clock to `CB1`.
;; The VIA will be set to shift in the data into the shift register under the external clock.
;; @see @ref via_hardware_bug "VIA external clock bug"
;; @include keyboard
;; @ingroup drivers
;;********************************************************************************
.scope ps2kb
Import ps2kb, init, begin_receive, scancode, status, scancode_handler
Import ps2kb, _receive_irq_shift_reg_handler, _receive_irq_timer_handler, _send_byte, _send_irq_shift_reg_handler, _send_irq_timer_handler,
Import ps2kb, send_command, send_cmd, send_data, cmd_response, response_length, FMT_CMD_FAIL
;; Base address of the VIA the keyboard is conencted to
VIA = IO1
;; @brief #clock cycles to to wait for the last 3 bits after the first 8 have been shifted in
;; @details
;; The last 3 bits take about 230 ms.
;; Calculate the appropriate value using: @f$ N_\text{cycles} = 230 \times f_\text{in MHZ} @f$
;; - 230 \@ 1MHz
;; - 400 \@ 1.84 MHz
;; @clock_dependent
TIMER_RECV = 400
;; @brief #clock cycles to wait after loading the SR a second time while sending a command to the keyboard
;; @details
;; Enough time must pass for one bit must be shifted out (parity),
;; but at most two (parity+stop).
;; After that, the interrupt must have happened because the keyboard will pull data low.
;; At that point, the shift register needs to be set to input again
;;
;; Values that seem to work:
;; - 230 \@ 1MHz
;; - 400 \@ 1.84 MHz
;; @clock_dependent
TIMER_SEND = 400
PULL_REG = IO::RANH
PULL_DDR = IO::DDRA
; use RA4 to pull the clock low
PULL_MASK_CLK = %00010000
; using ff because 0 is a possible data byte (set led)
NO_DATA = $ff ;; indicates that no data byte should be send
NO_RESPONSE = $ff ;; indicates a command did not receive a response (yet)
ACK = $fa ;; successful transmission
RESEND = $fe ;; unsuccessful transmission
.enum STATUS
RECEIVE_KEYS = %10000000 ;;< keyboard sends scancodes
RECEIVE_ANSWER = %01000000 ;;< keyboard replies to a command
SEND_CMD = %00100000 ;;< host sends/sent the command byte
SEND_DATA = %00010000 ;;< host sends/sent the data byte
SEND_RECV = %00001000 ;;< keyboard sends additional data byte
SEND = SEND_CMD | SEND_DATA | SEND_RECV ;;< host is sending something, only used for checking status
NONE = %00000000
.endenum
.enum TYPEMATIC
REPEAT_MASK = %00011111 ;;< 00000 = 30Hz, ..., 11111 = 2Hz
DELAY250 = %00000000
DELAY500 = %00100000
DELAY750 = %01000000
DELAY1000 = %01100000
.endenum
.endscope
; see https://wiki.osdev.org/PS/2_Keyboard
.macro ps2kb_CmdEcho
lda #$ee
ldx #ps2kb::NO_DATA
ldy #0
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdSetLeds mask
lda #$ed
.ifnblank mask
ldx #mask
.endif
ldy #0
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdGetScancodeSet
lda #$f0
ldx #0
ldy #1
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdSetScancodeSet set
.assert set = 1 .or set = 2 .or set = 3, warning, "ps2kb_CmdSetScancodeSet: invalid scancode set"
lda #$f0
ldx #set
ldy #0
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdIdentify
lda #$f2
ldx #ps2kb::NO_DATA
ldy #2
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdTypematicSettings settings
lda #$f3
ldx #settings
ldy #0
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdEnable
lda #$f4
ldx #ps2kb::NO_DATA
ldy #0
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdDisable
lda #$f5
ldx #ps2kb::NO_DATA
ldy #0
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdSetTypematic scancode
ldy #0
.ifblank scancode
lda #$f7
ldx #ps2kb::NO_DATA
.else
lda #$fb
ldx #scancode
.endif
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdSetMakeRelease scancode
ldy #0
.ifblank scancode
lda #$f8
ldx #ps2kb::NO_DATA
.else
lda #$fc
ldx #scancode
.endif
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdSetMake scancode
ldy #0
.ifblank scancode
lda #$f9
ldx #ps2kb::NO_DATA
.else
lda #$fd
ldx #scancode
.endif
jsr ps2kb::send_command
.endmacro
.macro ps2kb_CmdSelfTest
lda #$ff
ldx #ps2kb::NO_DATA
ldy #1
jsr ps2kb::send_command
.endmacro
.macro ps2kb_WaitFinishCmd
:
wai
lda ps2kb::status
and #ps2kb::STATUS::SEND
bne :-
.endmacro
;;********************************************************************************
;; @note You need to manually include `string.h65` when using this macro!
;;********************************************************************************
.macro ps2kb_PrintCmdFailed
Printf ps2kb::FMT_CMD_FAIL,ps2kb::send_cmd,ps2kb::send_data,ps2kb::cmd_response,ps2kb::cmd_response+1,ps2kb::cmd_response+2
.endmacro
.endif ; guard

482
system/ps2_keyboard.s65 Normal file
View File

@ -0,0 +1,482 @@
.include "ps2_keyboard.h65"
.include "string.h65"
.include "lcd.h65"
.include "parity.h65"
Export ps2kb, init, begin_receive, scancode, status, scancode_handler
Export ps2kb, _receive_irq_shift_reg_handler, _receive_irq_timer_handler, _send_byte, _send_irq_shift_reg_handler, _send_irq_timer_handler,
Export ps2kb, send_command, send_cmd, send_data, cmd_response, response_length, FMT_CMD_FAIL
.bss
status: .res 1 ;; current status
prev_status: .res 1 ;; status before sending command
send_last_bits: .res 1 ;; last bits to load after 8 bits were shifted out
send_cmd: .res 1 ;; command to send/last sent
send_data: .res 1 ;; data byte to send/last sent or NO_DATA
expect_data_length: .res 1 ;; number of data bytes to expect from keyboard
response_length: .res 1 ;; number of response bytes from keyboard
cmd_response: .res 3 ;; responses from keyboard
key_read: .res 2 ;; first 8 bits, last 3 bits from scancode
scancode: .res 1 ;; last received scancode
scancode_handler: .res 2 ;; pointer to a function that handles new scancodes
.code
; spi-transferred code will be placed here
; these are macros and not subroutines to save time during the interrupt handler (no jsr, rts)
;;********************************************************************************
;; @macro Enable the clock signal from the keyboard
;; @modifies A
;; @details
;; Stop pulling the keyboards clock low
;;********************************************************************************
.macro _EnableClock
; set pin to input
lda ps2kb::VIA + ps2kb::PULL_DDR
and #<~ps2kb::PULL_MASK_CLK
sta ps2kb::VIA + ps2kb::PULL_DDR
.endmacro
;;********************************************************************************
;; @macro Disable the clock signal from the keyboard
;; @modifies A
;; @details
;; Pulls the keyboards clock low
;;********************************************************************************
.macro _DisableClock
; set pin to output
lda ps2kb::VIA + ps2kb::PULL_DDR
ora #ps2kb::PULL_MASK_CLK
sta ps2kb::VIA + ps2kb::PULL_DDR
; set pin low
lda ps2kb::VIA + ps2kb::PULL_REG
and #<~ps2kb::PULL_MASK_CLK
sta ps2kb::VIA + ps2kb::PULL_REG
.endmacro
;;********************************************************************************
;; @macro Stop pulling the keyboard data pin low
;; @modifies A
;;********************************************************************************
.macro _StopPullDataLow
; set pin to input
lda ps2kb::VIA + ps2kb::PULL_DDR
and #<~ps2kb::PULL_MASK_DAT
sta ps2kb::VIA + ps2kb::PULL_DDR
.endmacro
;;********************************************************************************
;; @macro Pull the keyboard data pin low
;; @modifies A
;;********************************************************************************
.macro _PullDataLow
; set pin to output
lda ps2kb::VIA + ps2kb::PULL_DDR
ora #ps2kb::PULL_MASK_DAT
sta ps2kb::VIA + ps2kb::PULL_DDR
; set pin low
lda ps2kb::VIA + ps2kb::PULL_REG
and #<~ps2kb::PULL_MASK_DAT
sta ps2kb::VIA + ps2kb::PULL_REG
.endmacro
;;********************************************************************************
;; @function Initialize the keyboard
;; @details
;; - @ref _DisableClock "Disable the clock"
;; - Initialize variables to 0
;; - Clear the VIAs shift register and set T2 to oneshote mode
;; - Set scancode_handler to immediately return (dont handle scancodes)
;; @modifies A
;;********************************************************************************
.proc init
_DisableClock
stz key_read
stz key_read+1
stz scancode
stz status
; T2 oneshot mode, clear shift reg
lda ps2kb::VIA + IO::ACR
and #<~IO::ACR_MASK::SR
ora #IO::ACR::T2_IRQ_LOAD
sta ps2kb::VIA + IO::ACR
; load this rts as scancode_handler
StoreDByte @rts, scancode_handler
@rts:
rts
.endproc
;;********************************************************************************
;; @function Start receiving data from the keyboard
;; @details
;; - use the shift register interrupts to read the first 8 bits
;; - configure timer for timing the read of the last 3 bits
;; @modifies A
;;********************************************************************************
.proc begin_receive
; disable timer interrupts (this might be called while waiting on the last 3 bits)
IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2
; (re)set shift register to shift in under external clock on CB1
lda ps2kb::VIA + IO::ACR
and #<~IO::ACR_MASK::SR
sta ps2kb::VIA + IO::ACR ; important, disabling SR resets the count
ora #IO::ACR::SR_SIN_PHIE
sta ps2kb::VIA + IO::ACR
stz key_read
stz key_read+1
stz scancode
; the 3 last bits take about 230us, at @1MHz => wait 230 cycles and then the shift register
; (this could be shorter since the it takes a few cycles after the interrupt)
lda #<ps2kb::TIMER_RECV
sta ps2kb::VIA + IO::T2CL
; set to RECEIVE_KEYS only if RECEIVE_ANSWER is not set
bit status
bvs @status_set
lda #ps2kb::STATUS::RECEIVE_KEYS
sta status
@status_set:
; todo setup irq handlers
StoreDByte _receive_irq_shift_reg_handler, $3000
StoreDByte _receive_irq_timer_handler, $3060
_EnableClock
IO_EnableIRQ ps2kb::VIA, IO::IRQ::SR
; reset and start SR
stz ps2kb::VIA + IO::SR
rts
.endproc
;;********************************************************************************
;; @function Read the first 8 bits an
;; @modifies A
;; @details
;; - read shift register
;; - disable shift register interrupts
;; - reset shift register
;; - enable timer 2 interrupts
;; - start timer 2
;; IO::SR has to be read before the next bit is shifted in, which happens ~75us after the irq
;; at 1MHz, handling this interrupt takes about 50us (without any additional debug code),
;; so it should work
;;********************************************************************************
.proc _receive_irq_shift_reg_handler
lda ps2kb::VIA + IO::SR
sta key_read
stz ps2kb::VIA + IO::SR
IO_DisableIRQ ps2kb::VIA, IO::IRQ::SR
IO_EnableIRQ ps2kb::VIA, IO::IRQ::T2
; start timer, low order count already in latch after begin_receive
lda #>ps2kb::TIMER_RECV
sta ps2kb::VIA + IO::T2CH
rts
.endproc
;;********************************************************************************
;; @function Read the last 3 bits after after timer 2 is up
;; @modifies A
;; @details
;; - read shift register
;; - disable timer 2 interrupts
;; - enable shift register interrupts
;; - reset shift register
;; - jump to the subroutine pointed to by scancode_handler
;;********************************************************************************
.proc _receive_irq_timer_handler
lda ps2kb::VIA + IO::SR
sta key_read+1
lda ps2kb::VIA + IO::T2CL ; clear interrupt flag
IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2
; reset SR
; disabling shifting in acr seems necessary to reset - otherwise
; it continues counting and interrupts after the first 5 bits of the next keypress
lda #(IO::ACR::SR_SIN_PHIE | IO::ACR::T2_IRQ_LOAD)
trb ps2kb::VIA + IO::ACR
tsb ps2kb::VIA + IO::ACR
stz ps2kb::VIA + IO::SR
IO_EnableIRQ ps2kb::VIA, IO::IRQ::SR
lda key_read+1
ror ; stop bit -> C
sta key_read+1 ; parity in bit 0, D7 in bit 1
ror ; parity -> C
ror ; D7 -> C
lda key_read ; not affecting carry
rol ; C -> bit 0, startbit -> C
Reverse A
sta scancode
; check parity
CalculateOddParity
eor key_read+1 ; if bit 0 is 1 - parity error
and #1 ; bit 1 is still D7
bne @parity_error
@handle_scancode:
; check what to do with the scancode
bit status
bpl @status_not_receive_keys ; RECEIVE_KEYS
jmp (scancode_handler)
@status_not_receive_keys:
bvc @status_dont_handle ; RECEIVE_ANSWER
jmp _process_cmd_answer
@status_dont_handle:
rts
@parity_error: ; TODO handle somehow
lda #$fe ; fe means error/resend for commands
sta scancode
DEBUG_LED_ON 2
bra @handle_scancode
.endproc
;;********************************************************************************
;; @function Send a command to the keyboard
;; @details
;; No checks are done on the validity of the command and data byte.
;; You can send anything you want to the keyboard.
;; `send_command` will return immediately, as sending the command is asynchronous.
;; The command is done if `ps2kb::status & ps2kb::STATUS::SEND == 0`.
;; @section Response
;; The answers will be in the byte array ps2kb::cmd_response.
;; If the command did not have a data byte, cmd_response is:
;; - 0: command response
;; - 1: NO_RESPONSE or additional response byte (identify keyboard command only)
;; - 2: NO_RESPONSE or additional response byte (identify keyboard command only)
;;
;; If the command did have a data byte, cmd_response is:
;; - 0: 0xFA (ACK) or 0xFE (Resend/Error)
;; - 1: 0xFA (ACK) or 0xFE (Resend/Error)
;; - 2: NO_RESPONSE or additional response byte
;;
;; If ANY of the bytes in cmd_response is 0xFE, the command failed.
;;
;; @modifies A,X,Y
;; @param A: The command byte
;; @param X: The data byte or NO_DATA if only the command byte should be sent
;; @param Y: The number of data bytes expected to receive. Must be one of {0, 1, 2}
;;********************************************************************************
.proc send_command
sty expect_data_length
stx send_data
stz response_length
ldy #ps2kb::NO_RESPONSE
sty cmd_response
sty cmd_response+1
sty cmd_response+2
ldy status
sty prev_status
ldy #ps2kb::STATUS::SEND_CMD
sty status
sta send_cmd ; store if it needs to be resent
jmp _send_byte
.endproc
;;********************************************************************************
;; @function Send a byte to the keyboard
;; @modifies A,X,Y
;; @param A: The byte to send
;; @details
;; - pull clock low to stop keyboard transmissions
;; - setup SR to shift out
;; - pull data low (by shifting out a zero)
;; - reset SR
;; - put (reversed) byte in SR
;; - put parity + stopbit(s) in send_last_bits variable
;; - setup interrupt handlers
;; - enable clock
;;********************************************************************************
.proc _send_byte
pha
IO_DisableIRQ ps2kb::VIA, (IO::IRQ::T2 | IO::IRQ::SR)
_DisableClock
; set shift register to output
; shift out the startbit = data low
; set SR to shift out with PHI2 and shift out zeros
lda ps2kb::VIA + IO::ACR
and #<~IO::ACR_MASK::SR
ora #IO::ACR::SR_SOUT_PHI2
sta ps2kb::VIA + IO::ACR
stz ps2kb::VIA + IO::SR
; reset shift register to start the count at 0 again
and #<~IO::ACR_MASK::SR
sta ps2kb::VIA + IO::ACR
; set SR to shift out with external clock
ora #IO::ACR::SR_SOUT_PHIE
sta ps2kb::VIA + IO::ACR
; setup the low timer byte
lda #<ps2kb::TIMER_SEND
sta ps2kb::VIA + IO::T2CL
; setup interrupt handlers
StoreDByte _send_irq_shift_reg_handler, $3000
StoreDByte _send_irq_timer_handler, $3060
pla
Reverse A
pha
CalculateOddParity
ror ; Parity -> C
lda #$ff
ror ; Parity -> bit 7, rest = 1
sta send_last_bits ; loaded into SR by irq handler
pla
sta ps2kb::VIA + IO::SR
IO_EnableIRQ ps2kb::VIA, IO::IRQ::SR
_EnableClock
rts
.endproc
;;********************************************************************************
;; @function Send the lasts 3 bits
;; @modifies A
;; @details
;; - load the remaining 3 bits of the transmission into the shift register
;; - disable shift register interrupts
;; - enable timer 2 interrupts
;; - start timer 2
;;********************************************************************************
.proc _send_irq_shift_reg_handler
lda send_last_bits
sta ps2kb::VIA + IO::SR
IO_DisableIRQ ps2kb::VIA, IO::IRQ::SR
IO_EnableIRQ ps2kb::VIA, IO::IRQ::T2
; start timer, low order count already in latch after init
lda #>ps2kb::TIMER_SEND
sta ps2kb::VIA + IO::T2CH
rts
.endproc
;;********************************************************************************
;; @function Setup VIA to receive the keyboard's answer
;; @modifies A
;; @details
;; - disable timer 2 interrupts
;; - pull clock low
;; - or status with RECEIVE_ANSWER
;; - begin receive
;;********************************************************************************
.proc _send_irq_timer_handler
lda ps2kb::VIA + IO::T2CL ; clear interrupt flag
IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2
; disable shift register
lda ps2kb::VIA + IO::ACR
and #<~IO::ACR_MASK::SR
sta ps2kb::VIA + IO::ACR
_DisableClock ; disable keyboard while setting up receive
lda status
ora #ps2kb::STATUS::RECEIVE_ANSWER
sta status
jmp ps2kb::begin_receive
.endproc
;;********************************************************************************
;; @function Process the response of a command
;; @details
;; Stores the answer in the `ps2kb::cmd_response` array.
;; If the response is $FE or command related transmissions are done (see below),
;; no further data will be sent or received and the `ps2kb::status` takes the previous value.
;;
;; - store response
;; - if response == 0xFE -> stop
;; - if status == SEND_CMD -> send databyte
;; - if statusk
;; You can send anything you want to the keyboard.
;; `send_command` will return immediately, as sending the command is asynchronous.
;; The command is done if `ps2kb::status & ps2kb::STATUS::SEND == 0`.
;; @section Response
;; The answers will be in the byte array ps2kb::cmd_response.
;; If the command did not have a data byte, cmd_response is:
;; - 0: command response
;; - 1: NO_RESPONSE or additional response byte (identify keyboard command only)
;; - 2: NO_RESPONSE or additional response byte (identify keyboard command only)
;; If the command did have a data byte, cmd_response is:
;; - 0: 0xFA (ACK) or 0xFE (Resend/Error)
;; - 1: 0xFA (ACK) or 0xFE (Resend/Error)
;; - 2: NO_RESPONSE or additional response byte
;;
;; If ANY of the bytes in cmd_response is 0xFE, the command failed.
;;
;; @modifies A,X,Y
;; @param A: The command byte
;; @param X: The data byte or NO_DATA if only the command byte should be sent
;; @param Y: The number of data bytes expected to receive. Must be one of {0, 1, 2}
;;********************************************************************************
.proc _process_cmd_answer
@store_response:
ldx response_length
lda scancode
sta cmd_response,x
inx
stx response_length
stz scancode
; check for resend
cmp #$fe
beq @cmd_fail
; received something useful, check state
lda status
bit #ps2kb::STATUS::SEND_CMD
bne @cmd_sent
bit #ps2kb::STATUS::SEND_DATA
bne @everything_sent
; status must be SEND_RECV
@receive_data_response:
dec expect_data_length
bmi @cmd_done ; no more expected data
rts ; wait for another data byte
@cmd_sent:
; check if a data byte needs to be sent
lda send_data
beq @send_data ; if 0 (would fall through the cmp check TODO would it??)
cmp #ps2kb::NO_DATA
beq @everything_sent ; if not NO_DATA
@send_data:
lda #ps2kb::STATUS::SEND_DATA
sta status
lda send_data
jmp ps2kb::_send_byte ; send the data
@everything_sent: ; check if additonal bytes are expected
dec expect_data_length
bmi @cmd_done ; expect_data_length was 0
lda #(ps2kb::STATUS::SEND_RECV | ps2kb::STATUS::RECEIVE_ANSWER)
sta status
rts
@cmd_fail: ; keyboard wont expect data byte/send an additional response byte
@cmd_done:
; restore previous status
lda prev_status
sta status
; disable the clock if the previous was not RECEIVE_KEYS
cmp #ps2kb::STATUS::RECEIVE_KEYS
beq @rts
_DisableClock
@rts:
rts
.endproc
.rodata
;; Format string that can be used by other programs for keyboard command errors
FMT_CMD_FAIL: .asciiz "PS/2 Cmd fail: %x-%x > %x%x%x "

View File

@ -1,18 +1,57 @@
;********************************************************************************
; @module SPI
; @type driver
; @details
; @depends IO-W65C22N
;********************************************************************************
;;********************************************************************************
;; @file
;; @brief SPI Perpiheral
;; @details
;; Support being a peripheral SPI device.
;; @depends IO-W65C22N
;;********************************************************************************
.ifndef INCLUDE_SPI
INCLUDE_SPI = 1
.include "system/system.h65"
;;********************************************************************************
;; @brief Support being a peripheral SPI device
;; @details
;; This requires the data line to be hooked up to `CB2` and the clock to `CB1`.
;; The VIA will be set to shift in the data into the shift register under the external clock.
;; @ingroup drivers
;; @see via_hardware_bug "VIA external clock bug"
;;********************************************************************************
.scope spi_p
;; Address of the VIA that is used for the spi connection
SPI_IO = IO2
.import CODE_START:absolute
Import spi_p, begin, end, read, pages_written, bytes_written
Import spi_p, init, irq_handler, status, buffer_size, recv_size
ImportZp spi_p, buffer_ptr
Import spi_p, begin_read, end_read, irq_read_byte, recv_bytes
Import spi_p, begin_write, end_write, irq_write_byte, sent_bytes
;;********************************************************************************
;; @brief Status of a SPI transfer
;;********************************************************************************
.enum STATUS
XFER_SIZEL = %10000000
XFER_SIZEH = %01000000
XFER = %00000001
DONE = %00000000
ERROR = %00100000
.endenum
.endscope
.endif ; guard
; .struct SPI_P_Pins
; ; VIA addresses
; DDR_a .word ; address of the data direction register
; R_a .word ; address of the register
; ; pin mask
; SCLK_p .byte ; Serial Clock
; POCI_p .byte ; Peripheral Out / Controller In
; PICO_p .byte ; Peripheral In / Controller Out
; CSB_p .byte ; Chip Select
; ; settings
; CPOL .byte ; Clock Polarity
; CPHA .byte ; Clock Phase
; .endstruct

View File

@ -1,114 +1,256 @@
.include "spi.h65"
.include "system.h65"
Export spi_p, begin, end, read, pages_written, bytes_written
Export spi_p, init, irq_handler, status, buffer_size, recv_size
ExportZp spi_p, buffer_ptr
Export spi_p, begin_read, end_read, irq_read_byte, recv_bytes
Export spi_p, begin_write, end_write, irq_write_byte, sent_bytes
.zeropage
code_page: .res 2 ; CODE_START + pages_written * 256
buffer_ptr: .res 2
.bss
bytes_written: .res 1
pages_written: .res 1
recv_bytes:
sent_bytes: .res 2
status: .res 1
buffer_size: .res 2
recv_size:
send_size: .res 2
irq_handler: .res 2
SPI_IO := spi_p::SPI_IO
CODE_START := spi_p::CODE_START
; spi-transferred code will be placed here
; SPI_P = as peripheral
.code
.struct SPI_P_Pins
; VIA addresses
DDR_a .word ; address of the data direction register
R_a .word ; address of the register
; pin mask
SCLK_p .byte ; Serial Clock
POCI_p .byte ; Peripheral Out / Controller In
PICO_p .byte ; Peripheral In / Controller Out
CSB_p .byte ; Chip Select
; settings
CPOL .byte ; Clock Polarity
CPHA .byte ; Clock Phase
.endstruct
;********************************************************************************
; @function Begin listening for SPI transfers
; @details
; - initialize variables
; - configure shift register to shift in under external clock
; - enable shift register interrupts
;********************************************************************************
.proc begin
stz pages_written
stz bytes_written
;;********************************************************************************
;; @function Begin listening for SPI transfers
;;********************************************************************************
.proc init
lda #spi_p::STATUS::DONE
sta status
stz recv_bytes
stz recv_bytes+1
stz buffer_size
stz buffer_size+1
stz recv_size
stz recv_size+1
rts
.endproc
;;********************************************************************************
;; @function Begin listening for SPI transfers
;; @details
;; The transfer must be: <SIZE, >SIZE, data (SIZE bytes)
;;
;; - initialize variables
;; - configure shift register to shift in under external clock
;; - enable shift register interrupts
;; @param ARG0-1: Start address of the buffer
;; @param ARG2-3: Size of the buffer
;; @todo: Use irq register handler
;; @note: The buffer must be large enough for the received data.
;;********************************************************************************
.proc begin_read
stz recv_bytes
stz recv_bytes+1
stz recv_size
stz recv_size+1
lda #spi_p::STATUS::XFER_SIZEL
sta status
; store address in zp
lda #<CODE_START
sta code_page
lda #>CODE_START
sta code_page + 1
; todo USE MASKS
; set Shift register to shift in under external clock on CB1
lda #IO::ACR::SR_SIN_PHIE
sta SPI_IO + IO::ACR
lda ARG0
sta buffer_ptr
lda ARG1
sta buffer_ptr+1
; load irq handler
lda #<irq_read_byte
sta irq_handler
lda #>irq_read_byte
sta irq_handler+1
; temporarily store the low byte in buffer, will be checked later
lda ARG2
sta buffer_size
lda ARG3
sta buffer_size+1
; set Shift register to shift in under external clock on CB1
MaskedWrite SPI_IO+IO::ACR, #IO::ACR::SR_SIN_PHIE, #IO::ACR_MASK::SR
; enable SR interrupts
lda #(IO::IRQ::IRQ | IO::IRQ::SR)
sta SPI_IO + IO::IER
; load SR to reset
lda SPI_IO + IO::SR
rts
.endproc
;********************************************************************************
; @function Stop listening for SPI transfers
; @details
; Disables shift register interrupts
; @modifies A
;********************************************************************************
.proc end
;;********************************************************************************
;; @function Stop reading/writing from/to SPI
;; @details
;; Disables shift register interrupts
;; @modifies A
;;********************************************************************************
end_write:
.proc end_read
; disable SR interrupts
lda #IO::IRQ::SR
sta SPI_IO + IO::IER
lda #spi_p::STATUS::DONE
sta status
rts
.endproc
;;********************************************************************************
;; @function Read a byte from SPI
;; @details
;; Reads a byte from the shift register and stores it in the SPI code buffer
;; The first two bytes must be the size of the incumong data (LE).
;; The spi_p:status is updated according to the current stage of the transfer.
;; If the buffer size given in begin_read is too small too fit the incoming data,
;; the status is set to ERROR and spi (shift register) interrupts are disabled before
;; the first real byte is read.
;; @modifies A,Y
;;********************************************************************************
.proc irq_read_byte
lda SPI_IO + IO::SR
; Printf "%x",
bit status
bmi @read_size1 ; bit7 set => XFER_SIZEL
bvs @read_size2 ; bit6 set => XFER_SIZEH
ldy recv_bytes
sta (buffer_ptr),y
inc recv_bytes
beq @new_page
rts
@new_page: ; increment high bytes
inc recv_bytes+1
inc buffer_ptr+1
rts
@read_size1:
sta recv_size
lda #spi_p::STATUS::XFER_SIZEH
sta status
rts
@read_size2:
sta recv_size+1
; check if the buffer is large enough
cmp buffer_size+1
beq @hieq
bcc @enough ; recv_size+1 < buffer_size+1
bra @not_enough
@hieq: ; high bytes are equal, check lo
lda buffer_size
cmp recv_size
bcs @enough
@not_enough:
lda #spi_p::STATUS::ERROR
sta status
; disable SR interrupts
lda #IO::IRQ::SR
sta SPI_IO + IO::IER
rts
@enough:
lda #spi_p::STATUS::XFER
sta status
rts
.endproc
;;********************************************************************************
;; @function Begin writing to spi
;; @details
;; - initialize variables
;; - configure shift register to shift in under external clock
;; - enable shift register interrupts
;; @param ARG0-1: Start address of the buffer
;; @param ARG2-3: Size of the buffer
;; @todo: Use irq register handler
;;********************************************************************************
.proc begin_write
stz recv_bytes
stz recv_bytes+1
; store address in zp
lda #<ARG0
sta buffer_ptr
lda #>ARG1
sta buffer_ptr+1
; store size
lda #<ARG2
sta buffer_size
lda #>ARG3
sta buffer_size+1
; load irq handler
lda #<irq_read_byte
sta irq_handler
lda #>irq_read_byte
sta irq_handler+1
; set Shift register to shift out under external clock on CB1
MaskedWrite SPI_IO+IO::ACR, #IO::ACR::SR_SOUT_PHIE, #IO::ACR_MASK::SR
; enable SR interrupts
lda #(IO::IRQ::IRQ | IO::IRQ::SR)
sta SPI_IO + IO::IER
; write the first byte
@write_size1:
lda buffer_size
sta SPI_IO+IO::SR
lda #spi_p::STATUS::XFER_SIZEH
sta status
rts
.endproc
;********************************************************************************
; @function Read a byte from SPI
; @function Write a byte
; @details
; Reads a byte from the shift register and stores it in the SPI code buffer
; Write a byte to the spi shift reister
; @modifies A,Y
;********************************************************************************
.proc read
ldy bytes_written
lda SPI_IO + IO::SR
sta (code_page),y
inc bytes_written
;.proc irq_write_byte
; ldy sent_bytes
; lda (buffer_ptr),y
; sta SPI_IO + IO::SR
; inc sent_bytes
; beq @new_page
; rts
;@new_page:
; inc sent_bytes+1
; inc buffer_ptr+1
; rts
;.endproc
;;********************************************************************************
;; @function Write a byte to SPI
;; @details
;; Write a byte from the buffer to the (spi) shift register.
;; The first two bytes are the size of the data (LE).
;; The spi_p:status is updated according to the current stage of the transfer.
;; @modifies A,Y
;;********************************************************************************
.proc irq_write_byte
bit status
bvs @write_size2 ; bit 6 set -> XFER_SIZEH
ldy sent_bytes
lda (buffer_ptr),y
sta SPI_IO + IO::SR
inc sent_bytes
beq @new_page
rts
@new_page:
inc pages_written
inc code_page + 1
inc sent_bytes+1
inc buffer_ptr+1
rts
@write_size2:
lda buffer_size+1
sta SPI_IO+IO::SR
lda #spi_p::STATUS::XFER
sta status
rts
.endproc
;********************************************************************************
; @function Initialize the IO Adapter for SPI
; @param ARG0-1 Address of the SPI_Pins struct
;********************************************************************************
;********************************************************************************
; @function Read bytes
; @param X Number of bytes to send
; @param ARG0-1 Address of the SPI_Pins struct
; @param ARG2-3 Address of the first byte
;********************************************************************************
.proc recv_data
.endproc
;********************************************************************************
; @function Send bytes
; @param X Number of bytes to send
; @param ARG0-1 Address of the SPI_Pins struct
; @param ARG2-3 Address of the first byte
;********************************************************************************
.proc send_data
.endproc

View File

@ -1,50 +1,69 @@
;********************************************************************************
; @module system
; @type header
; @details
; Variable definitions for the current hardware setup
;********************************************************************************
;;********************************************************************************
;; @file
;; @brief System header
;; @ingroup system
;; @details
;; Variables for the current hardware setup
;;********************************************************************************
.ifndef INCLUDE_SYSTEM
INCLUDE_SYSTEM = 1
; reserved RAM addresses
; 00-0f - free
; 10-1f - arguments / return values
; 20-ff - free
; 0100 - 01FF Stack
; 0200,0201 keybuffer write/read pointer
; 0202-02ff keybuffer
; 0300 lcd character counter
; 0400, 0401, 0402 dht status, dht bit, dht_bit_rot
; 0403 value offset
; 0405-04a0 rh high/low, temp high/low, checksum
;;********************************************************************************
;; @page system_stuff More System stuff
;; @subsubsection ram_reserved Reserved RAM addresses
;; - 00-0f - free use (Z0-Z15)
;; - 10-1f - arguments / return values (ARG0-ARG15)
;; - 20-ff - free
;; - 0100 - 01FF Stack
;; - 0200,0201 keypad keybuffer write/read pointer
;; - 0202-02ff keypad keybuffer
;; - 0300 lcd character counter
;; @ingroup system
;;********************************************************************************
.include "io_W65C22.h65"
.include "utility.h65"
.importzp Z0,Z1,Z2,Z3,Z4,Z5,Z6,Z7,Z8,Z9,Z10,Z11,Z12,Z13,Z14,Z15
.importzp ARG0,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8,ARG9,ARG10,ARG11,ARG12,ARG13,ARG14,ARG15
; ARGUMENTS
; a,x,y can also be used
ARG0 = $10
ARG1 = $11
ARG2 = $12
ARG3 = $13
ARG4 = $14
ARG5 = $15
ARG6 = $16
ARG7 = $17
ARG8 = $18
ARG9 = $19
ARG10 = $1a
ARG11 = $1b
ARG12 = $1c
ARG13 = $1d
ARG14 = $1e
ARG15 = $1f
; Z0 = $0
; Z1 = $1
; Z2 = $2
; Z3 = $3
; Z4 = $4
; Z5 = $5
; Z6 = $6
; Z7 = $7
; Z8 = $8
; Z9 = $9
; Z10 = $a
; Z11 = $b
; Z12 = $c
; Z13 = $d
; Z14 = $e
; Z15 = $f
; ; ARGUMENTS
; ; a,x,y can also be used
; ARG0 = $10
; ARG1 = $11
; ARG2 = $12
; ARG3 = $13
; ARG4 = $14
; ARG5 = $15
; ARG6 = $16
; ARG7 = $17
; ARG8 = $18
; ARG9 = $19
; ARG10 = $1a
; ARG11 = $1b
; ARG12 = $1c
; ARG13 = $1d
; ARG14 = $1e
; ARG15 = $1f
; RETURN VALUE
; in a
.segment "VIA1"
; IO1: .res 16
IO1 = $6000
@ -53,4 +72,13 @@ IO1 = $6000
; IO2: .res 16
IO2 = $7000
; charmappings for the lcd
; .charmap 'ü', $F5
; .charmap 'Ü', $F5
; .charmap 'ö', $EF
; .charmap 'Ö', $EF
; .charmap 'ä', $E1
; .charmap 'Ä', $E1
; .charmap 'ß', $E2
.endif ; include guard

View File

@ -1,3 +1,42 @@
; .include "system.h65"
.exportzp Z0,Z1,Z2,Z3,Z4,Z5,Z6,Z7,Z8,Z9,Z10,Z11,Z12,Z13,Z14,Z15
.exportzp ARG0,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8,ARG9,ARG10,ARG11,ARG12,ARG13,ARG14,ARG15
.zeropage
.org $10
.res 16
.org $0
.assert * = $0, error, "Code not at 0"
Z0: .res 1
Z1: .res 1
Z2: .res 1
Z3: .res 1
Z4: .res 1
Z5: .res 1
Z6: .res 1
Z7: .res 1
Z8: .res 1
Z9: .res 1
Z10: .res 1
Z11: .res 1
Z12: .res 1
Z13: .res 1
Z14: .res 1
Z15: .res 1
; ARGUMENTS
; a,x,y can also be used
.assert * = $10, error, "Code not at 10"
ARG0: .res 1
ARG1: .res 1
ARG2: .res 1
ARG3: .res 1
ARG4: .res 1
ARG5: .res 1
ARG6: .res 1
ARG7: .res 1
ARG8: .res 1
ARG9: .res 1
ARG10: .res 1
ARG11: .res 1
ARG12: .res 1
ARG13: .res 1
ARG14: .res 1
ARG15: .res 1

View File

@ -1,55 +0,0 @@
;.include "system/system.h65"
;.segment "CODE"
;;********************************************************************************
;; Interrupts
;;********************************************************************************
;nmi:
; rti
;irq:
; .repeat 20
; .endrepeat
; rti
;;********************************************************************************
;; Reset sequence
;;********************************************************************************
;reset:
; sei
; ; setup io2 bank a 1-3
; lda #%11111111
; sta IO1 + IO_DDRA
; sta IO1 + IO_DDRB
;@loop:
; lda #%00000000
; sta IO1 + IO_RA
; .repeat 3
; nop
; .endrepeat
; lda #%11111111
; sta IO1 + IO_RA
; .repeat 15
; nop
; .endrepeat
; lda #%00000000
; sta IO1 + IO_RB
; .repeat 5
; nop
; .endrepeat
; lda #%11111111
; sta IO1 + IO_RB
; .repeat 10
; nop
; .endrepeat
; bra @loop
;;********************************************************************************
;; reset vector
;;********************************************************************************
;.segment "RESET_VECTOR"
; .word nmi
; .word reset
; .word irq

123
util/bit_macros.h65 Normal file
View File

@ -0,0 +1,123 @@
;;********************************************************************************
;; @file
;; @brief Macros for bitwise operations
;; @ingroup libs
;;********************************************************************************
;;********************************************************************************
;; @macro Use the bbs instruction by providing the bit as a mask
;; @details
;; This is useful when having a enum with bitmasks, which might change later.
;; @param mask: A byte where a single bit is set
;; @param addr: The zero page address to test
;; @param label: The label to jump to when the bit is set
;;********************************************************************************
.macro bbs mask,addr,label
.if mask = %00000001
bbs0 addr,label
.elseif mask = %00000010
bbs1 addr,label
.elseif mask = %00000100
bbs2 addr,label
.elseif mask = %00001000
bbs3 addr,label
.elseif mask = %00010000
bbs4 addr,label
.elseif mask = %00100000
bbs5 addr,label
.elseif mask = %01000000
bbs6 addr,label
.elseif mask = %10000000
bbs7 addr,label
.else
.fatal .sprintf("bbs macro got invalid mask: %x", mask)
.endif
.endmacro
;;********************************************************************************
;; @macro Use the bbr instruction by providing the bit as a mask
;; @details
;; This is useful when having a enum with bitmasks, which might change later.
;; @param mask: A byte where a single bit is set
;; @param addr: The zero page address to test
;; @param label: The label to jump to when the bit is clear
;;********************************************************************************
.macro bbr mask,addr,label
.if mask = %00000001
bbr0 addr,label
.elseif mask = %00000010
bbr1 addr,label
.elseif mask = %00000100
bbr2 addr,label
.elseif mask = %00001000
bbr3 addr,label
.elseif mask = %00010000
bbr4 addr,label
.elseif mask = %00100000
bbr5 addr,label
.elseif mask = %01000000
bbr6 addr,label
.elseif mask = %10000000
bbr7 addr,label
.else
.fatal .sprintf("bbr macro got invalid mask: 0x%x", mask)
.endif
.endmacro
;;********************************************************************************
;; @macro Use the smb instruction by providing the bit as a mask
;; @details
;; This is useful when having a enum with bitmasks, which might change later.
;; @param mask: A byte where a single bit is set
;; @param addr: The zero page address to update
;;********************************************************************************
.macro smb mask,addr
.if mask = %00000001
smb0 addr
.elseif mask = %00000010
smb1 addr
.elseif mask = %00000100
smb2 addr
.elseif mask = %00001000
smb3 addr
.elseif mask = %00010000
smb4 addr
.elseif mask = %00100000
smb5 addr
.elseif mask = %01000000
smb6 addr
.elseif mask = %10000000
smb7 addr
.else
.fatal .sprintf("smb macro got invalid mask: 0x%x", mask)
.endif
.endmacro
;;********************************************************************************
;; @macro Use the rmb instruction by providing the bit as a mask
;; @details
;; This is useful when having a enum with bitmasks, which might change later.
;; @param mask: A byte where a single bit is set
;; @param addr: The zero page address to update
;;********************************************************************************
.macro rmb mask,addr
.if mask = %00000001
rmb0 addr
.elseif mask = %00000010
rmb1 addr
.elseif mask = %00000100
rmb2 addr
.elseif mask = %00001000
rmb3 addr
.elseif mask = %00010000
rmb4 addr
.elseif mask = %00100000
rmb5 addr
.elseif mask = %01000000
rmb6 addr
.elseif mask = %10000000
rmb7 addr
.else
.fatal .sprintf("rmb macro got invalid mask: 0x%x", mask)
.endif
.endmacro

View File

@ -1,19 +1,21 @@
;********************************************************************************
; @module math
; @type utility
; @details
; Math library
;********************************************************************************
;;********************************************************************************
;; @file
;; @brief Math library
;;********************************************************************************
.ifndef INCLUDE_MATH
INCLUDE_MATH = 1
;;********************************************************************************
;; @brief Math library
;; @ingroup libs
;;********************************************************************************
.scope math
;********************************************************************************
; @macro Divide a num (or A) by a factor
; @param num: Memory or A
; @param divider: must be power of 2
;********************************************************************************
;;********************************************************************************
;; @macro Divide a num (or A) by a factor
;; @param num: Memory or A
;; @param divider: must be power of 2
;;********************************************************************************
.macro div num,divider
.if divider = 2
lsr num

41
util/parity.h65 Normal file
View File

@ -0,0 +1,41 @@
.ifndef INCLUDE_PARITY
INCLUDE_PARITY = 1
;;********************************************************************************
;; @file
;; @brief Macros for calculating parity
;; @ingroup libs
;;********************************************************************************
.import count_set_bits
;;********************************************************************************
;; @macro Calculate even parity
;; @modifies A, X, Y
;; @returns A: 0 - even number of set bits, 1 - uneven number of set bits
;; @returns Y: The number of set bits in the byte
;;********************************************************************************
.macro CalculateEvenParity
jsr count_set_bits
tya
and #1
.endmacro
;;********************************************************************************
;; @macro Calculate odd parity
;; @details
;; Count the number of set bits in Y.
;;
;; @modifies A, X, Y
;; @returns A: 1 - even number of set bits, 0 - uneven number of set bits
;; @returns Y: The number of set bits in the byte
;;********************************************************************************
.macro CalculateOddParity
jsr count_set_bits
tya
and #1
eor #1
.endmacro
.endif ; guard

22
util/parity.s65 Normal file
View File

@ -0,0 +1,22 @@
.export count_set_bits
;;********************************************************************************
;; @function Count the number of set bits in the `A` register
;; @modifies A, X, Y
;; @param A: Byte to count the set bits in
;; @returns Y: The number of set bits in the byte
;;********************************************************************************
.proc count_set_bits
ldx #9
ldy #0 ; number of set bits
@loop:
dex
beq @done
ror
bcc @loop
@set:
iny
bra @loop
@done:
rts
.endproc

42
util/reverse.s65 Normal file
View File

@ -0,0 +1,42 @@
.export REVERSE_TABLE
.rodata
.align 256
;;********************************************************************************
;; @brief A 256 byte table where the *i*-th byte is the value *i* with bits reversed
;; @see Reverse
;;********************************************************************************
REVERSE_TABLE:
.byte $00, $80, $40, $c0, $20, $a0, $60, $e0
.byte $10, $90, $50, $d0, $30, $b0, $70, $f0
.byte $08, $88, $48, $c8, $28, $a8, $68, $e8
.byte $18, $98, $58, $d8, $38, $b8, $78, $f8
.byte $04, $84, $44, $c4, $24, $a4, $64, $e4
.byte $14, $94, $54, $d4, $34, $b4, $74, $f4
.byte $0c, $8c, $4c, $cc, $2c, $ac, $6c, $ec
.byte $1c, $9c, $5c, $dc, $3c, $bc, $7c, $fc
.byte $02, $82, $42, $c2, $22, $a2, $62, $e2
.byte $12, $92, $52, $d2, $32, $b2, $72, $f2
.byte $0a, $8a, $4a, $ca, $2a, $aa, $6a, $ea
.byte $1a, $9a, $5a, $da, $3a, $ba, $7a, $fa
.byte $06, $86, $46, $c6, $26, $a6, $66, $e6
.byte $16, $96, $56, $d6, $36, $b6, $76, $f6
.byte $0e, $8e, $4e, $ce, $2e, $ae, $6e, $ee
.byte $1e, $9e, $5e, $de, $3e, $be, $7e, $fe
.byte $01, $81, $41, $c1, $21, $a1, $61, $e1
.byte $11, $91, $51, $d1, $31, $b1, $71, $f1
.byte $09, $89, $49, $c9, $29, $a9, $69, $e9
.byte $19, $99, $59, $d9, $39, $b9, $79, $f9
.byte $05, $85, $45, $c5, $25, $a5, $65, $e5
.byte $15, $95, $55, $d5, $35, $b5, $75, $f5
.byte $0d, $8d, $4d, $cd, $2d, $ad, $6d, $ed
.byte $1d, $9d, $5d, $dd, $3d, $bd, $7d, $fd
.byte $03, $83, $43, $c3, $23, $a3, $63, $e3
.byte $13, $93, $53, $d3, $33, $b3, $73, $f3
.byte $0b, $8b, $4b, $cb, $2b, $ab, $6b, $eb
.byte $1b, $9b, $5b, $db, $3b, $bb, $7b, $fb
.byte $07, $87, $47, $c7, $27, $a7, $67, $e7
.byte $17, $97, $57, $d7, $37, $b7, $77, $f7
.byte $0f, $8f, $4f, $cf, $2f, $af, $6f, $ef
.byte $1f, $9f, $5f, $df, $3f, $bf, $7f, $ff

View File

@ -1,6 +1,6 @@
.include "string.h65"
.include "math.h65"
Export str,hex_char_to_uint8, hex_str_to_uint, uint8_to_hex_str
Export str,hex_char_to_uint8, hex_str_to_uint, uint8_to_hex_str, uint_to_hex_str
.code
;
@ -130,6 +130,55 @@ Export str,hex_char_to_uint8, hex_str_to_uint, uint8_to_hex_str
.endproc
;;********************************************************************************
;; @function Convert a 1 byte number into two hex characters
;; @param A: Size of the number in bytes
;; @param X: Offset onto ARG4, so that the number starts at `ARG4+x` (BE)
;; @param ARG2-3: Pointer to output string
;; @param Y: Offset onto the string in ARG2-3
;; @returns A: 0
;; @returns X: Offset onto ARG4, past the end of the number
;; @returns Y: Offset onto the the string in ARG2-3, past the end of the number
;; @returns N: Clear if success, else set
;; @modifies A,X,Y
;;********************************************************************************
.proc uint_to_hex_str
@loop:
cmp #0
beq @rts
pha
lda ARG4,x
phx
pha
and #%00001111
tax
lda HEX_CHARS_UPPER,x
sta (ARG2),y
pla
iny
beq @overflow2
div A,16
and #%00001111
tax
lda HEX_CHARS_UPPER,x
sta (ARG2),y
iny
beq @overflow2
plx
pla
inx
beq @overflow
bra @loop
@overflow2:
plx
pla
@overflow:
lda #$ff ; set n
@rts:
rts
.endproc
.rodata
HEX_CHARS_UPPER: .byte "0123456789ABCDEF"
HEX_CHARS_LOWER: .byte "0123456789abcdef"

View File

@ -1,20 +1,31 @@
;********************************************************************************
; @module string
; @type utility
; @details
; String utility
;********************************************************************************
;;********************************************************************************
;; @file
;; @brief String utility library
;; @details
;; @see str
;;********************************************************************************
.ifndef INCLUDE_STRING
INCLUDE_STRING = 1
.include "system/system.h65"
;;********************************************************************************
;; @brief String utility
;; @ingroup libs
;;********************************************************************************
.scope str
Import str, strf
Import str, hex_char_to_uint8, hex_str_to_uint, uint8_to_hex_str
Import str, strf, printf_buffer
Import str, hex_char_to_uint8, hex_str_to_uint
Import str, uint8_to_hex_str, uint_to_hex_str
;;********************************************************************************
;; @macro Helper for str::Strf macro
;; @details
;; Store arg in ARG4-ARG... and increment counter variable `\@N_ARGS` by one.
;; Arg can be x, y, immediate or an address
;;********************************************************************************
.macro _StrfStoreArg arg
.if (.not .blank(arg))
.if .match(arg, x)
@ -29,15 +40,15 @@ Import str, hex_char_to_uint8, hex_str_to_uint, uint8_to_hex_str
.endif
.endmacro
;********************************************************************************
; @function Macro for passing arguments to strf
; @param fmt: Format string address or string literal
; @param out: Output string address
; @param x0-x9: Additional parameters
; @warning Addresses as additional paramters must be passed like this `#<addr,#>addr`
; @modifies: A, X, Y
; @see strf
;********************************************************************************
;;********************************************************************************
;; @macro Macro for passing arguments to strf
;; @param fmt: Format string address or string literal
;; @param out: Output string address
;; @param x0-x9: Additional parameters
;; @warning Addresses as additional paramters must be passed like this `#<addr,#>addr`
;; @modifies A, X, Y, ARG4, ARG5
;; @see str::strf
;;********************************************************************************
.macro Strf fmt,out,x0,x1,x2,x3,x4,x5,x6,x7,x8,x9
@N_ARGS .set 0 ; @ so that it doesnt break cheap labels
.if .match(fmt, "") ; fmt is a string literal -> store in rodata
@ -73,8 +84,16 @@ Import str, hex_char_to_uint8, hex_str_to_uint, uint8_to_hex_str
; .out .sprintf("info: Strf: called with %d arguments", @N_ARGS)
.endmacro
; TODO allocate zp memory
fmt_idx = $30
out_idx = $31
;;********************************************************************************
;; @function Macro for formated printing
;; @details
;; Pass all arguments to str::Strf macro, then call lcd::PrintNC macro
;; @see str::strf
;;********************************************************************************
.macro Printf fmt,x0,x1,x2,x3,x4,x5,x6,x7,x8,x9
Strf fmt,str::printf_buffer,x0,x1,x2,x3,x4,x5,x6,x7,x8,x9
PrintNC str::printf_buffer
.endmacro
.endscope
.endif ; guard

View File

@ -1,24 +1,38 @@
.include "string.h65"
Export str, strf
Export str, strf, printf_buffer
.bss
;;********************************************************************************
;; @brief Used to store output string of Printf macro
;; @todo Use dynamically allocated buffer when a memory allocator is implemented
;;********************************************************************************
printf_buffer: .res $41
.zeropage
fmt_idx: .res 1
out_idx: .res 1
pad_zero:.res 1
digits: .res 1
; addr: .res 2
.code
;********************************************************************************
; @function Format a string
; @details
; If there is no value to be read, the Pz will be set
; Formats:
; - x: unsigned hex integer (1 byte) -> 2 chars
; - X: unsigned hex integer (2 byte) -> 4 chars TODO
; - u: unsigned decimal integer (1 byte) TODO
; - U: unsigned decimal integer (2 bytes) TODO
; @param ARG0-1: Format string address
; @param ARG2-3: Output string address
; @param ARG4+: Additional parameters
; @returns
; @modifies: A, X, Y
;********************************************************************************
out_idx := str::out_idx
fmt_idx := str::fmt_idx
;;********************************************************************************
;; @function Format a string
;; @details
;; If there is no value to be read, the `Pz` will be set
;; Formats:
;; - `x`: unsigned hex integer (1 byte) -> 2 chars
;; - `X`: unsigned hex integer (2 byte) -> 4 chars TODO
;; - `u`: unsigned decimal integer (1 byte) TODO
;; - `U`: unsigned decimal integer (2 bytes) TODO
;; - `s`: null-terminated string (2 bytes ptr)
;; - `c`: char
;; @todo Implement decimal, test X
;; @param ARG0-1: Format string address
;; @param ARG2-3: Output string address
;; @param ARG4+: Additional parameters
;; @returns
;; @modifies A, X, Y, ARG4, ARG5
;;********************************************************************************
.proc strf
stz out_idx
stz fmt_idx
@ -27,7 +41,7 @@ fmt_idx := str::fmt_idx
@loop:
ldy fmt_idx
lda (ARG0),y
beq @null
jeq @null
cmp #'%'
beq @percent
@normal_char: ; store A in output string
@ -42,29 +56,88 @@ fmt_idx := str::fmt_idx
sty fmt_idx
lda (ARG0),y
beq @null
; padding
cmp #'0'
beq @percent_zero
stz pad_zero
; formats
cmp #'x'
beq @format_hex1
cmp #'X'
beq @format_hex2
cmp #'c'
beq @format_char
bra @normal_char
@percent_zero:
sta pad_zero
bra @percent
; @percent_num:
; sta pad_zero
; bra @percent
@format_hex1: ; 1 byte hex -> 2 chars
lda ARG4,x
phx
jsr str::uint8_to_hex_str
ldy out_idx
sta (ARG2),y ; most sig digit
jsr str::uint8_to_hex_str
ldy out_idx
sta (ARG2),y ; most sig digit
txa
plx
iny
beq @out_overflow
txa
sta (ARG2),y ; least sig digit
iny
beq @out_overflow
sty out_idx
plx
inx ; 1 byte of args handeled
; bra @format_return
@format_return: ; increment fmt_idx to swallow the formating char
inc fmt_idx
bra @loop
bra @format_return
@format_hex2: ; 2 byte hex -> 4 chars
lda #2
ldy out_idx
jsr str::uint_to_hex_str
bmi @out_overflow ; might also be x overflow
sty out_idx
bra @format_return
@format_char: ; 1 char
lda ARG4,x
ldy out_idx
sta (ARG2),y
inc out_idx
beq @out_overflow
inx
bra @format_return
@format_string: ; string
ldy #0
cpx #0
beq @format_string_loop
; store the pointer to the string at arg4-5
@format_string_move_ptr:
lda ARG4,x
sta ARG4
lda ARG5,x
sta ARG5
@format_string_loop:
lda (ARG4),y
beq @format_string_end ; if null
phy
ldy out_idx
sta (ARG2),y
inc out_idx
beq @out_overflow
ply
iny
@format_string_end:
inx
inx
bra @format_return
@format_return: ; increment fmt_idx to account for the formating char
inc fmt_idx ; at this point, out_idx will always overflow first
jmp @loop
@out_overflow: ; store 0 in last position
ldy #$ff
sty out_idx

View File

@ -1,32 +1,133 @@
.ifndef INCLUDE_UTILITY
INCLUDE_UTILITY = 1
;;********************************************************************************
;; @file
;; @brief Various useful macros
;; @ingroup libs
;;********************************************************************************
.macpack longbranch ; jeq, jge...
.macpack generic ; bge, add, sub
.feature string_escapes
.feature underline_in_numbers
.include "bit_macros.h65"
;********************************************************************************
; @macro Update a byte in memory using a mask
; @param addr Address of the byte to update
; @param value New value
; @param mask Mask of the bits to affect by the new value
; @details
; xor #value with addr -> only bits that need to flip are 1
; and result with #mask -> only selected bits that need to flip stay 1
; xor result with addr -> flips selected bits
;********************************************************************************
.macro DEBUG_LED_OFF nr
pha
lda IO1 + IO::RA
.if nr = 0
and #%11111110
.elseif nr = 1
and #%11111101
.else
and #%11111011
.endif
sta IO1 + IO::RA
pla
.endmacro
.macro DEBUG_LED_ON nr
pha
lda IO1 + IO::RA
.if nr = 0
ora #%00000001
.elseif nr = 1
ora #%00000010
.else
ora #%00000100
.endif
sta IO1 + IO::RA
pla
.endmacro
;;********************************************************************************
;; @macro Store a double byte (immediate) at an address
;; @param addr Address, double byte will be loaded in LE to addr, addr+1
;; @param dbyte Location or value
;; @param reg Register, defaults to A
;; @modifies
;;********************************************************************************
.macro StoreDByte dbyte,addr,reg
; .out .sprintf("%x, %x", dbyte, addr)
.if .blank(reg) .or .match(reg, A)
lda #<dbyte
sta addr
lda #>dbyte
sta addr+1
.elseif .match(reg, X)
ldx #<dbyte
stx addr
ldx #>dbyte
stx addr+1
.elseif .match(reg, Y)
ldy #<dbyte
sty addr
ldy #>dbyte
sty addr+1
.else
.fatal "Invalid reg given to StoreDByte"
.endif
.endmacro
;;********************************************************************************
;; @macro Update a byte in memory using a mask
;; @param addr Address of the byte to update
;; @param value New value
;; @param mask Mask of the bits to affect by the new value
;; @details
;; xor #value with addr -> only bits that need to flip are 1
;; and result with #mask -> only selected bits that need to flip stay 1
;; xor result with addr -> flips selected bits
;; @TODO optimize when immediate is used
;;********************************************************************************
.macro MaskedWrite addr,value,mask
lda #value
.if .not .match(value, A)
lda value
.endif
eor addr
and #mask
and mask
eor addr
sta addr
.endmacro
;;********************************************************************************
;; @macro Jump to the subroutine and let the routine return at another location
;; @details
;; By using a indirect address `(addr)` and no ret_val, this macro behaves
;; like an indirect version of jsr.
;; @param addr Target of the jump (can be: `addr`, `(addr)` or `{(addr,x)}`
;; @param ret_addr Return address (optional)
;; @details
;;********************************************************************************
.macro JsrIndirect addr,ret_addr
; -1 because rts increments it
.if .blank(ret_addr)
lda #>(:+ - 1)
pha
lda #<(:+ - 1)
pha
.else
lda #>(ret_addr -1)
pha
lda #<(ret_addr -1)
pha
.endif
jmp addr
.if .blank(ret_addr)
:
.endif
.endmacro
_n_genlabel .set 0
;;********************************************************************************
;; @macro Generate a unique label
@ -91,4 +192,28 @@ _n_genlabel .set 0
.ident(.sprintf("%s", .string(l1))) = .ident(.sprintf("%s_%s", .string(prefix), .string(l1)))
ImportZp prefix,l2,l3,l4,l5,l6,l7,l8
.endmacro
.endif
.import REVERSE_TABLE
;;********************************************************************************
;; @macro Reverse a byte using a @ref REVERSE_TABLE "table"
;; @param reg: The byte to reverse. May be in X, Y or A
;; @returns A: The reversed byte
;; @modifies A, X (only when reg = A)
;;********************************************************************************
.macro Reverse reg
.if .match(reg, A)
tax
lda REVERSE_TABLE,x
.elseif .match(reg, x)
lda REVERSE_TABLE,x
.elseif .match(reg, y)
lda REVERSE_TABLE,y
.else
.fatal ("Reverse macro got invalid argument. This syntax error is intentional to show the line number")
.endif
.endmacro
.endif ; guard