Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
ed279e4776 | |||
bf6a5efd7e | |||
7fce58e481 | |||
d86ac8d52d | |||
8b2e7c2752 | |||
|
5ee9610616 | ||
|
c61de15048 | ||
eb2fb061a0 | |||
64492a5f6b | |||
9867e4a119 | |||
4e76be17ad | |||
5c02ff6585 | |||
9060dfcbbf | |||
aa55d5fb37 | |||
bd083150c5 | |||
e61871ed0f | |||
326b2f6247 | |||
e8696cc312 | |||
23e5e3cd35 | |||
b778b4dcd1 | |||
2a13ce0ca4 | |||
108e5af0b9 | |||
e7bd5096cd | |||
aebd813a55 | |||
808900602e | |||
|
e1f78b2927 | ||
|
7f909b4f75 | ||
b60ff7be12 | |||
2bfae04f50 | |||
ff4e284f26 | |||
5767133ae3 | |||
dce5a43804 | |||
b65a0249a5 | |||
de098dd9c6 | |||
498b9803f1 | |||
45dce42475 | |||
|
302e49a9f7 | ||
|
3d773390bf | ||
|
27a901c7c0 | ||
|
4d5edeecfe | ||
|
fc0dba6c9c | ||
|
25d0fcfb90 | ||
|
eb02613837 | ||
|
9e9978669a | ||
|
7b97ca096d | ||
|
2d06c9700b |
2861
.doxygen_config
Normal file
2861
.doxygen_config
Normal file
File diff suppressed because it is too large
Load Diff
72
.extra_docs.s65
Normal file
72
.extra_docs.s65
Normal 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
|
||||
;;
|
||||
;;********************************************************************************
|
4
Makefile
4
Makefile
@ -57,3 +57,7 @@ clean:
|
||||
rm -r $(OBJ_DIR)
|
||||
rm $(ROM)
|
||||
rm $(SPI)
|
||||
|
||||
docs:
|
||||
doxygen .doxygen_config
|
||||
|
||||
|
19
details.md
19
details.md
@ -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
219
doxy-asm65.py
Normal 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()
|
||||
|
@ -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
138
main.s65
@ -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='"
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
;;********************************************************************************
|
||||
;; @module SPI
|
||||
;; @type driver
|
||||
;; @file
|
||||
;; @brief DHT22 driver (WIP)
|
||||
;; @ingroup driver
|
||||
;; @details
|
||||
;; @depends IO-W65C22N
|
||||
;;
|
||||
;;********************************************************************************
|
||||
|
||||
;;TODO EVERYTHING
|
||||
|
@ -4,7 +4,7 @@
|
||||
.ifndef INCLUDE_PRINTER
|
||||
INCLUDE_PRINTER = 1
|
||||
|
||||
.global printer
|
||||
.global keypad_printer
|
||||
.import home:absolute
|
||||
|
||||
.endif ; guard
|
20
programs/keypad_printer.s65
Normal file
20
programs/keypad_printer.s65
Normal 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
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
61
programs/ps2_keyboard_printer.s65
Normal file
61
programs/ps2_keyboard_printer.s65
Normal 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
|
38
programs/ps2_keyboard_simple_handler.h65
Normal file
38
programs/ps2_keyboard_simple_handler.h65
Normal 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
|
77
programs/ps2_keyboard_simple_handler.s65
Normal file
77
programs/ps2_keyboard_simple_handler.s65
Normal 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
|
201
programs/ps2_keyboard_text_handler.h65
Normal file
201
programs/ps2_keyboard_text_handler.h65
Normal 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
|
310
programs/ps2_keyboard_text_handler.s65
Normal file
310
programs/ps2_keyboard_text_handler.s65
Normal 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"
|
159
programs/ps2_keyboard_util.s65
Normal file
159
programs/ps2_keyboard_util.s65
Normal 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 "
|
@ -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
|
||||
|
@ -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
54
programs/viu.s65
Normal 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
|
22
readme.md
22
readme.md
@ -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.
|
||||
|
236
spicode.s65
236
spicode.s65
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
137
system/lcd.h65
137
system/lcd.h65
@ -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
|
||||
|
141
system/lcd.s65
141
system/lcd.s65
@ -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
219
system/ps2_keyboard.h65
Normal 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
482
system/ps2_keyboard.s65
Normal 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 "
|
@ -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
|
||||
|
292
system/spi.s65
292
system/spi.s65
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
55
test.s65
55
test.s65
@ -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
123
util/bit_macros.h65
Normal 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
|
@ -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
41
util/parity.h65
Normal 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
22
util/parity.s65
Normal 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
42
util/reverse.s65
Normal 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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
129
util/string.s65
129
util/string.s65
@ -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
|
||||
|
151
utility.h65
151
utility.h65
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user