1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-02 08:20:57 +11:00

Compare commits

..

No commits in common. "0939241db1ea2f3ee587e8e2bbc897159eb89fbb" and "7d28637740e7af358dd032d1edc05034268eee7c" have entirely different histories.

26 changed files with 926 additions and 153 deletions

View File

@ -6,7 +6,7 @@ MASTER INDEX
150 Extra words
200 Z80 assembler 260 Cross compilation
280 Z80 boot code 350 Core words
410 PS/2 keyboard subsystem 420 Bootstrap guide
410 PS/2 keyboard subsystem
490 TRS-80 Recipe 520 Fonts
550 TI-84+ Recipe 580 RC2014 Recipe
620 Sega Master System Recipe

View File

@ -13,4 +13,4 @@ Contents
4 Number literals 6 Compilation vs meta-comp.
8 Interpreter I/O 11 Signed-ness
14 Addressed devices 17 DOES>
18 Disk blocks 21 How blocks are organized
18 Disk blocks

13
blk/021
View File

@ -1,13 +0,0 @@
How blocks are organized
Organization of contiguous blocks is an ongoing challenge and
Collapse OS' blocks are never as tidy as they should, but we
try to strive towards a few goals:
1. Block 0 contains documentation discovery core keys to the
uninitiated.
2. First section (up to B100) is usage documentation.
3. B100-B200 are for runtime usage utilities
4. B200-B500 are for bootstrapping
5. The rest is for recipes.
6. I'm not sure yet how I'll organize multiple arches.

View File

@ -10,6 +10,6 @@
DUP 0x7f > IF DROP (key) EXIT THEN
DUP _shift? IF DROP 1 PS2_SHIFT C! (key) EXIT THEN
( ah, finally, we have a gentle run-of-the-mill KC )
PS2_CODES PS2_SHIFT C@ IF 0x80 + THEN + C@
PS2_CODES PS2_SHIFT @ IF 0x80 + THEN + C@
DUP NOT IF DROP (key) THEN ;

16
blk/420
View File

@ -1,16 +0,0 @@
Bootstrap guide
You want to deploy Collapse OS on a new system? Start here.
What is Collapse OS? It is a binary placed either in ROM on
in RAM by a bootloader. That binary, when executed, initializes
itself to a Forth interpreter. In most cases, that Forth
interpreter will have some access to a mass storage device,
which allows it to access Collapse OS' disk blocks and come
to this block to bootstrap itself some more.
This binary can be separated in 5 distinct layers:
1. Boot code (B280)
2. Boot words (B305)
3. Core words (low) (B350)
4. Drivers (cont.)

16
blk/421
View File

@ -1,16 +0,0 @@
5. Core words (high)
Boot code (B280)
This part contains core routines that underpins Forth fundamen-
tal structures: dict navigation and search, PSP/RSP bounds
checks, word types (atom, native, literals, "does type"), etc.
It also of course does core initialization: set RSP/PSP, HERE
CURRENT, then find BOOT and call it (see B89).
It also contains what we call the "stable ABI" in its first
0x100 bytes. The beginning og the dict is intertwined in this
layer because EXIT, (br), (?br) and (loop) are part of the
stable ABI.
(cont.)

16
blk/422
View File

@ -1,16 +0,0 @@
Boot words (B305)
Then come the implementation of core Forth words in native
assembly. Performance is not Collapse OS' primary design goal,
so we try to keep this section to a minimum: we much prefer
to implement our words in Forth.
However, some words are in this section for performance
reasons. Sometimes, the gain is too great to pass up.
Core words (low) (B350)
Then comes the part where we begin defining words in Forth.
Core words are designed to be cross-compiled (B260), from a
full Forth interpreter. This means that it has access to more
than boot words. This somes with tricky limitations. (cont.)

16
blk/423
View File

@ -1,16 +0,0 @@
See B260 for details.
Drivers
Up until now, we haven't implemented EMIT or KEY yet: those
words are defined in the "high" part of core words because we
generally need machine-specific drivers to implement (emit) and
(key).
Well, now is their time to shine. We split core in two
precisely to fit drivers in there. This way. they have access
to a pretty good vocabulary and they're also give the oppor-
tunity to provide (emit) and (key).
(cont.)

16
blk/424
View File

@ -1,16 +0,0 @@
Core words (high) (B350)
Then come EMIT, KEY and everything that depend on it, until
we have a full Forth interpreter. At the very end, we define
tricky IMMEDIATEs that, if defined earlier, would break cross
compilation.
We end that with a hook words which is also where CURRENT will
be on boot.
So that's the anatomy of a Collapse OS binary. How do you build
one? If your machine is already covered by a recipe, you're in
luck: follow instructions.
If you're deploying to a new machine, you'll have to write a
new xcomp (cross compilation) unit. Let's look at its (cont.)

16
blk/425
View File

@ -1,16 +0,0 @@
anatomy. First, we have constants. Some of them are device-
specific, but some of them are always there. RAMSTART is the
address at which the RAM starts on the system. System variables
will go there and HERE will go after it.
RS_ADDR is where RSP starts and PS_ADDR is where PSP starts.
RSP and PSP are designed to be contiguous. RSP goes up and PSP
goes down. If they meet, we know we have a stack overflow.
Then, we load the assembler and cross compilation unit, which
will be needed for the task ahead.
Then, it's a matter of adding layer after layer. For most
system, all those layers except the drivers will be added the
same way. Drivers are a bit tricker and machine specific. I
can't help you there, you'll have to use your wits. (cont.)

15
blk/426
View File

@ -1,15 +0,0 @@
After we've loaded the high part of the core words, we're at
the "wrapping up" part. We add what we call a "hook word" (an
empty word with a single letter name) which doesn't cost us
much and can be very useful if we need to augment the binary
with more words, and at that point we have our future boot
CURRENT, which PC yields. That is why we write it to the
LATEST field of the stable ABI: This value will be used at
boot.
After the last word of the dictionary comes the "source init"
part. The boot sequence is designed to interpret whatever comes
after LATEST as Forth source, and this, until it reads ASCII
EOT character (4). This is generally used for driver init.
Good luck!

View File

@ -0,0 +1,27 @@
PROGNAME = ps2ctl
AVRDUDEMCU ?= t45
AVRDUDEARGS ?= -c usbtiny -P usb
TARGETS = $(PROGNAME).hex os.bin
BASEDIR = ../../..
ZASM = $(BASEDIR)/emul/zasm/zasm
KERNEL = $(BASEDIR)/kernel
APPS = $(BASEDIR)/apps
# Rules
.PHONY: send all clean
all: $(TARGETS)
@echo Done!
send: $(PROGNAME).hex
avrdude $(AVRDUDEARGS) -p $(AVRDUDEMCU) -U flash:w:$(PROGNAME).hex
$(PROGNAME).hex: $(PROGNAME).asm
avra -o $@ $(PROGNAME).asm
os.bin: glue.asm
$(ZASM) $(KERNEL) $(APPS) < glue.asm > $@
clean:
rm -f $(TARGETS) *.eep.hex *.obj os.bin

View File

@ -61,23 +61,10 @@ probably have gone the flip-flop way. Seems more solid.
## Using the PS/2 interface
To use this interface, you have to build a new Collapse OS binary. We'll use
the xcomp unit from the base recipe and modify it.
After having built and flashed the `glue.asm` supplied with this recipe, you end
up with a shell driven by the PS/2 keyboard (but it still outputs to ACIA).
First, we need a `(ps2kc)` routine. In this case, it's easy, it's
`: (ps2kc) 8 PC@ ;`. Add this after ACIA loading. Then, we can load PS/2
subsystem. You add `411 414 LOADR`. Then, at initialization, you add `PS2$`
after `ACIA$`. You also need to define `PS2_MEM` at the top. You can probably
use `RAMSTART + 0x7a`.
Rebuild, reflash, should work. For debugging purposes, you might not want to
go straight to plugging PS/2 `(key)` into the system. What I did myself was
to load the PS/2 subsystem *before* ACIA (which overrides with its own `(key)`)
and added a dummy word in between to access PS/2's key.
Also (and this is a TODO: investigate), I had a problem where the break key I
got from `(ps2kc)` was 0x70 instead of 0xf0 which had the effect of duplicating
all my keystrokes. I added a 0x70 -> 0xf0 replacement in my version of
`(ps2kc)`. Does the trick (at the cost of a non-functional numpad 0).
There are still a few glitches, especially at initialization or at connect and
disconnect, but it otherwise works rather well!
[avra]: https://github.com/hsoft/avra

View File

@ -0,0 +1,59 @@
.equ RAMSTART 0x8000
.equ RAMEND 0xffff
.equ ACIA_CTL 0x80 ; Control and status. RS off.
.equ ACIA_IO 0x81 ; Transmit. RS on.
.equ KBD_PORT 0x08
jp init
.inc "err.h"
.inc "ascii.h"
.inc "core.asm"
.inc "str.asm"
.equ ACIA_RAMSTART RAMSTART
.inc "acia.asm"
.equ KBD_RAMSTART ACIA_RAMEND
.inc "kbd.asm"
.equ STDIO_RAMSTART KBD_RAMEND
.equ STDIO_GETC kbdGetC
.equ STDIO_PUTC aciaPutC
.inc "stdio.asm"
; *** BASIC ***
; RAM space used in different routines for short term processing.
.equ SCRATCHPAD_SIZE STDIO_BUFSIZE
.equ SCRATCHPAD STDIO_RAMEND
.inc "lib/util.asm"
.inc "lib/ari.asm"
.inc "lib/parse.asm"
.inc "lib/fmt.asm"
.equ EXPR_PARSE parseLiteralOrVar
.inc "lib/expr.asm"
.inc "basic/util.asm"
.inc "basic/parse.asm"
.inc "basic/tok.asm"
.equ VAR_RAMSTART SCRATCHPAD+SCRATCHPAD_SIZE
.inc "basic/var.asm"
.equ BUF_RAMSTART VAR_RAMEND
.inc "basic/buf.asm"
.equ BAS_RAMSTART BUF_RAMEND
.inc "basic/main.asm"
init:
di
ld sp, RAMEND
im 1
call aciaInit
call kbdInit
call basInit
ei
jp basStart
KBD_FETCHKC:
in a, (KBD_PORT)
ret

View File

@ -18,6 +18,7 @@ This recipe is for installing a minimal Collapse OS system on the SMS. There
are other recipes related to the SMS:
* [Interfacing a PS/2 keyboard](kbd/README.md)
* [zasm and ed from ROM](romasm/README.md)
## Gathering parts

69
recipes/sms/glue.asm Normal file
View File

@ -0,0 +1,69 @@
; 8K of onboard RAM
.equ RAMSTART 0xc000
; Memory register at the end of RAM. Must not overwrite
.equ RAMEND 0xfdd0
jp init
.fill 0x66-$
retn
.inc "err.h"
.inc "ascii.h"
.inc "core.asm"
.inc "str.asm"
.equ PAD_RAMSTART RAMSTART
.inc "sms/pad.asm"
.inc "sms/vdp.asm"
.equ GRID_RAMSTART PAD_RAMEND
.equ GRID_COLS VDP_COLS
.equ GRID_ROWS VDP_ROWS
.equ GRID_SETCELL vdpSetCell
.equ GRID_GETC padGetC
.inc "grid.asm"
.equ STDIO_RAMSTART GRID_RAMEND
.equ STDIO_GETC gridGetC
.equ STDIO_PUTC gridPutC
.inc "stdio.asm"
; *** BASIC ***
; RAM space used in different routines for short term processing.
.equ SCRATCHPAD_SIZE STDIO_BUFSIZE
.equ SCRATCHPAD STDIO_RAMEND
.inc "lib/util.asm"
.inc "lib/ari.asm"
.inc "lib/parse.asm"
.inc "lib/fmt.asm"
.equ EXPR_PARSE parseLiteralOrVar
.inc "lib/expr.asm"
.inc "basic/util.asm"
.inc "basic/parse.asm"
.inc "basic/tok.asm"
.equ VAR_RAMSTART SCRATCHPAD+SCRATCHPAD_SIZE
.inc "basic/var.asm"
.equ BUF_RAMSTART VAR_RAMEND
.inc "basic/buf.asm"
.equ BAS_RAMSTART BUF_RAMEND
.inc "basic/main.asm"
init:
di
im 1
ld sp, RAMEND
call gridInit
call padInit
call vdpInit
call basInit
jp basStart
FNT_DATA:
.bin "fnt/7x7.bin"
.fill 0x7ff0-$
.db "TMR SEGA", 0x00, 0x00, 0xfb, 0x68, 0x00, 0x00, 0x00, 0x4c

22
recipes/sms/kbd/Makefile Normal file
View File

@ -0,0 +1,22 @@
PROGNAME = ps2ctl
AVRDUDEMCU ?= t45
AVRDUDEARGS ?= -c usbtiny -P usb
TARGETS = $(PROGNAME).bin
BASEDIR = ../../..
EDIR = $(BASEDIR)/emul
# Rules
.PHONY: send all clean
all: $(TARGETS)
@echo Done!
send: $(PROGNAME).bin
avrdude $(AVRDUDEARGS) -p $(AVRDUDEMCU) -U flash:w:$(PROGNAME).bin
$(PROGNAME).bin: $(PROGNAME).fs
cd $(EDIR) && ./avra.sh < ../recipes/sms/kbd/$(PROGNAME).fs > ../recipes/sms/kbd/$@
clean:
rm -f $(TARGETS)

View File

@ -100,16 +100,16 @@ The code expects a SR-latch that works like a 4043, that is, S and R are
triggered high, S makes Q high, R makes Q low. R is hooked to PB4. S is hooked
to TH (and also the A/B on the '157). Q is hooked to PB0 and TL.
## Building the binary
## Usage
We start with the base recipe and add a few things:
The code in this recipe is set up to listen to the keyboard on port B, leaving
port A to drive, for example, an Everdrive with a D-pad. Unlike the generic
SMS recipe, this kernel has no character selection mechanism. It acts like a
regular shell, taking input from the keyboard.
1. at the top: `RAMSTART 0x72 + CONSTANT PS2_MEM`
2. After VDP load: `641 LOAD : (ps2kc) (ps2kcB) ;` (that binds us to port B)
3. Right after: `411 414 LOADR` (that gives us `(key)`)
4. After `VDP$`: `PS2$`.
`kernel/sms/kbd.asm` also has a FetchKC implementation for port A if you prefer.
Just hook it on. I've tried it, it works.
Rebuild, send to SMS, then run with your keyboard interface plugged to PortB.
It should mostly work. There are still a few glitches to iron out...
Did you get there? Feels pretty cool huh?
[rc2014-ps2]: ../../rc2014/ps2

82
recipes/sms/kbd/glue.asm Normal file
View File

@ -0,0 +1,82 @@
; 8K of onboard RAM
.equ RAMSTART 0xc000
; Memory register at the end of RAM. Must not overwrite
.equ RAMEND 0xddd0
jp init
.fill 0x66-$
retn
.inc "err.h"
.inc "ascii.h"
.inc "core.asm"
.inc "str.asm"
.inc "sms/kbd.asm"
.equ KBD_RAMSTART RAMSTART
.equ KBD_FETCHKC smskbdFetchKCB
.inc "kbd.asm"
.inc "sms/vdp.asm"
.equ GRID_RAMSTART KBD_RAMEND
.equ GRID_COLS VDP_COLS
.equ GRID_ROWS VDP_ROWS
.equ GRID_SETCELL vdpSetCell
.equ GRID_GETC kbdGetC
.inc "grid.asm"
.equ STDIO_RAMSTART GRID_RAMEND
.equ STDIO_GETC gridGetC
.equ STDIO_PUTC gridPutC
.inc "stdio.asm"
; *** BASIC ***
; RAM space used in different routines for short term processing.
.equ SCRATCHPAD_SIZE STDIO_BUFSIZE
.equ SCRATCHPAD STDIO_RAMEND
.inc "lib/util.asm"
.inc "lib/ari.asm"
.inc "lib/parse.asm"
.inc "lib/fmt.asm"
.equ EXPR_PARSE parseLiteralOrVar
.inc "lib/expr.asm"
.inc "basic/util.asm"
.inc "basic/parse.asm"
.inc "basic/tok.asm"
.equ VAR_RAMSTART SCRATCHPAD+SCRATCHPAD_SIZE
.inc "basic/var.asm"
.equ BUF_RAMSTART VAR_RAMEND
.inc "basic/buf.asm"
.equ BAS_RAMSTART BUF_RAMEND
.inc "basic/main.asm"
init:
di
im 1
ld sp, RAMEND
; Initialize the keyboard latch by "dummy reading" once. This ensures
; that the adapter knows it can fill its '164.
; Port B TH output, high
ld a, 0b11110111
out (0x3f), a
nop
; Port A/B reset
ld a, 0xff
out (0x3f), a
call kbdInit
call gridInit
call vdpInit
call basInit
jp basStart
FNT_DATA:
.bin "fnt/7x7.bin"
.fill 0x7ff0-$
.db "TMR SEGA", 0x00, 0x00, 0xfb, 0x68, 0x00, 0x00, 0x00, 0x4c

348
recipes/sms/kbd/ps2ctl.asm Normal file
View File

@ -0,0 +1,348 @@
; Receives keystrokes from PS/2 keyboard and send them to the '164. On the PS/2
; side, it works the same way as the controller in the rc2014/ps2 recipe.
; However, in this case, what we have on the other side isn't a z80 bus, it's
; the one of the two controller ports of the SMS through a DB9 connector.
; The PS/2 related code is copied from rc2014/ps2 without much change. The only
; differences are that it pushes its data to a '164 instead of a '595 and that
; it synchronizes with the SMS with a SR latch, so we don't need PCINT. We can
; also afford to run at 1MHz instead of 8.
; *** Register Usage ***
;
; GPIOR0 flags:
; 0 - when set, indicates that the DATA pin was high when we received a
; bit through INT0. When we receive a bit, we set flag T to indicate
; it.
;
; R16: tmp stuff
; R17: recv buffer. Whenever we receive a bit, we push it in there.
; R18: recv step:
; - 0: idle
; - 1: receiving data
; - 2: awaiting parity bit
; - 3: awaiting stop bit
; R19: Register used for parity computations and tmp value in some other places
; R20: data being sent to the '164
; Y: pointer to the memory location where the next scan code from ps/2 will be
; written.
; Z: pointer to the next scan code to push to the 595
;
.inc "avr.h"
.inc "tn254585.h"
.inc "tn45.h"
; *** Constants ***
.equ CLK 2 ; Port B
.equ DATA 1 ; Port B
.equ CP 3 ; Port B
; SR-Latch's Q pin
.equ LQ 0 ; Port B
; SR-Latch's R pin
.equ LR 4 ; Port B
; init value for TCNT0 so that overflow occurs in 100us
.equ TIMER_INITVAL 0x100-100
; *** Code ***
rjmp main
rjmp hdlINT0
; Read DATA and set GPIOR0/0 if high. Then, set flag T.
; no SREG fiddling because no SREG-modifying instruction
hdlINT0:
sbic PINB, DATA ; DATA clear? skip next
sbi GPIOR0, 0
set
reti
main:
ldi r16, RAMEND&0xff
out SPL, r16
ldi r16, RAMEND}8
out SPH, r16
; init variables
clr r18
out GPIOR0, r18
; Setup int0
; INT0, falling edge
ldi r16, 0x02 ; ISC01
out MCUCR, r16
; Enable INT0
ldi r16, 0x40 ; INT0
out GIMSK, r16
; Setup buffer
clr YH
ldi YL, SRAM_START&0xff
clr ZH
ldi ZL, SRAM_START&0xff
; Setup timer. We use the timer to clear up "processbit" registers after
; 100us without a clock. This allows us to start the next frame in a
; fresh state. at 1MHZ, no prescaling is necessary. Each TCNT0 tick is
; already 1us long.
ldi r16, 0x01 ; CS00 - no prescaler
out TCCR0B, r16
; init DDRB
sbi DDRB, CP
cbi PORTB, LR
sbi DDRB, LR
sei
loop:
brts processbit ; flag T set? we have a bit to process
cp YL, ZL ; if YL == ZL, buffer is empty
brne sendTo164 ; YL != ZL? our buffer has data
; nothing to do. Before looping, let's check if our communication timer
; overflowed.
in r16, TIFR
sbrc r16, 1 ; TOV0
rjmp processbitReset ; Timer0 overflow? reset processbit
; Nothing to do for real.
rjmp loop
; Process the data bit received in INT0 handler.
processbit:
in r19, GPIOR0 ; backup GPIOR0 before we reset T
andi r19, 0x1 ; only keep the first flag
cbi GPIOR0, 0
clt ; ready to receive another bit
; We've received a bit. reset timer
rcall resetTimer
; Which step are we at?
tst r18
breq processbits0
cpi r18, 1
breq processbits1
cpi r18, 2
breq processbits2
; step 3: stop bit
clr r18 ; happens in all cases
; DATA has to be set
tst r19 ; Was DATA set?
breq loop ; not set? error, don't push to buffer
; push r17 to the buffer
st Y+, r17
rcall checkBoundsY
rjmp loop
processbits0:
; step 0 - start bit
; DATA has to be cleared
tst r19 ; Was DATA set?
brne loop ; Set? error. no need to do anything. keep r18
; as-is.
; DATA is cleared. prepare r17 and r18 for step 1
inc r18
ldi r17, 0x80
rjmp loop
processbits1:
; step 1 - receive bit
; We're about to rotate the carry flag into r17. Let's set it first
; depending on whether DATA is set.
clc
sbrc r19, 0 ; skip if DATA cleared.
sec
; Carry flag is set
ror r17
; Good. now, are we finished rotating? If carry flag is set, it means
; that we've rotated in 8 bits.
brcc loop ; we haven't finished yet
; We're finished, go to step 2
inc r18
rjmp loop
processbits2:
; step 2 - parity bit
mov r1, r19
mov r19, r17
rcall checkParity ; --> r16
cp r1, r16
brne processbitError ; r1 != r16? wrong parity
inc r18
rjmp loop
processbitError:
clr r18
ldi r19, 0xfe
rcall sendToPS2
rjmp loop
processbitReset:
clr r18
rcall resetTimer
rjmp loop
; Send the value of r20 to the '164
sendTo164:
sbis PINB, LQ ; LQ is set? we can send the next byte
rjmp loop ; Even if we have something in the buffer, we
; can't: the SMS hasn't read our previous
; buffer yet.
; We disable any interrupt handling during this routine. Whatever it
; is, it has no meaning to us at this point in time and processing it
; might mess things up.
cli
sbi DDRB, DATA
ld r20, Z+
rcall checkBoundsZ
ldi r16, 8
sendTo164Loop:
cbi PORTB, DATA
sbrc r20, 7 ; if leftmost bit isn't cleared, set DATA high
sbi PORTB, DATA
; toggle CP
cbi PORTB, CP
lsl r20
sbi PORTB, CP
dec r16
brne sendTo164Loop ; not zero yet? loop
; release PS/2
cbi DDRB, DATA
sei
; Reset the latch to indicate that the next number is ready
sbi PORTB, LR
cbi PORTB, LR
rjmp loop
resetTimer:
ldi r16, TIMER_INITVAL
out TCNT0, r16
ldi r16, 0x02 ; TOV0
out TIFR, r16
ret
; Send the value of r19 to the PS/2 keyboard
sendToPS2:
cli
; First, indicate our request to send by holding both Clock low for
; 100us, then pull Data low
; lines low for 100us.
cbi PORTB, CLK
sbi DDRB, CLK
rcall resetTimer
; Wait until the timer overflows
in r16, TIFR
sbrs r16, 1 ; TOV0
rjmp $-4
; Good, 100us passed.
; Pull Data low, that's our start bit.
cbi PORTB, DATA
sbi DDRB, DATA
; Now, let's release the clock. At the next raising edge, we'll be
; expected to have set up our first bit (LSB). We set up when CLK is
; low.
cbi DDRB, CLK ; Should be starting high now.
; We will do the next loop 8 times
ldi r16, 8
; Let's remember initial r19 for parity
mov r1, r19
sendToPS2Loop:
; Wait for CLK to go low
sbic PINB, CLK
rjmp $-2
; set up DATA
cbi PORTB, DATA
sbrc r19, 0 ; skip if LSB is clear
sbi PORTB, DATA
lsr r19
; Wait for CLK to go high
sbis PINB, CLK
rjmp $-2
dec r16
brne sendToPS2Loop ; not zero? loop
; Data was sent, CLK is high. Let's send parity
mov r19, r1 ; recall saved value
rcall checkParity ; --> r16
; Wait for CLK to go low
sbic PINB, CLK
rjmp $-2
; set parity bit
cbi PORTB, DATA
sbrc r16, 0 ; parity bit in r16
sbi PORTB, DATA
; Wait for CLK to go high
sbis PINB, CLK
rjmp $-2
; Wait for CLK to go low
sbic PINB, CLK
rjmp $-2
; We can now release the DATA line
cbi DDRB, DATA
; Wait for DATA to go low. That's our ACK
sbic PINB, DATA
rjmp $-2
; Wait for CLK to go low
sbic PINB, CLK
rjmp $-2
; We're finished! Enable INT0, reset timer, everything back to normal!
rcall resetTimer
clt ; also, make sure T isn't mistakely set.
sei
ret
; Check that Y is within bounds, reset to SRAM_START if not.
checkBoundsY:
tst YL
breq $+4
ret ; not zero, nothing to do
; YL is zero. Reset Y
clr YH
ldi YL, SRAM_START&0xff
ret
; Check that Z is within bounds, reset to SRAM_START if not.
checkBoundsZ:
tst ZL
breq $+4
ret ; not zero, nothing to do
; ZL is zero. Reset Z
clr ZH
ldi ZL, SRAM_START&0xff
ret
; Counts the number of 1s in r19 and set r16 to 1 if there's an even number of
; 1s, 0 if they're odd.
checkParity:
ldi r16, 1
lsr r19
brcc $+4 ; Carry unset? skip next
inc r16 ; Carry set? We had a 1
tst r19 ; is r19 zero yet?
brne checkParity+2 ; no? loop and skip first LDI
andi r16, 0x1 ; Sets Z accordingly
ret

1
recipes/sms/romasm/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
user.h

View File

@ -0,0 +1,21 @@
BASEDIR = ../../..
ZASM = $(BASEDIR)/emul/zasm/zasm
KERNEL = $(BASEDIR)/kernel
APPS = $(BASEDIR)/apps
.PHONY: all clean
all: os.sms
# -o value synced with offset in glue.asm
ed.bin: $(APPS)/ed/glue.asm
$(ZASM) -o 1f $(KERNEL) $(APPS) user.h < $(APPS)/ed/glue.asm > $@
# -o value synced with offset in glue.asm
zasm.bin: $(APPS)/zasm/glue.asm
$(ZASM) -o 24 $(KERNEL) $(APPS) user.h < $(APPS)/zasm/glue.asm > $@
os.sms: glue.asm ed.bin zasm.bin
$(ZASM) $(KERNEL) $(APPS) ed.bin zasm.bin < glue.asm > $@
clean:
rm -f os.sms ed.bin zasm.bin

View File

@ -0,0 +1,61 @@
# zasm and ed from ROM
SMS' RAM is much tighter than in the RC2014, which makes the idea of loading
apps like zasm and ed in memory before using it a bit wasteful. In this recipe,
we'll include zasm and ed code directly in the kernel and expose them as shell
commands.
Moreover, we'll carve ourselves a little 1K memory map to put a filesystem in
there. This will give us a nice little system that can edit small source files
compile them and run them.
## Gathering parts
* A SMS that can run Collapse OS.
* A [PS/2 keyboard adapter](../kbd/README.md)
## Build
There's nothing special with building this recipe. Like the base recipe, run
`make` then copy `os.sms` to your destination medium.
If you look at the makefile, however, you'll see that we use a new trick here:
we embed "apps" binaries directly in our ROM so that we don't have to load them
in memory.
## Usage
Alright, here's what we'll do: we'll author a source file, assemble it and run
it, *all* on your SMS! Commands:
Collapse OS
> fnew 1 src
> ed src
: 1i
.org 0xc200
: 1a
ld hl, sFoo
: 2a
call 0x3f
: 3a
xor a
: 4a
ret
: 5a
sFoo: .db "foo", 0
: w
> fnew 1 dest
> fopn 0 src
> fopn 1 dest
> zasm 1 2
First pass
Second pass
> dest
foo>
Awesome right? Some precisions:
* Our glue code specifies a `USER_RAMSTART` of `0xc200`. This is where
`dest` is loaded by the `pgm` shell hook.
* `0x3f` is the offset of `printstr` in the jump table of our glue code.
* `xor a` is for the command to report as successful to the shell.

187
recipes/sms/romasm/glue.asm Normal file
View File

@ -0,0 +1,187 @@
; TODO: This recipe has not been tested since its conversion to the BASIC shell.
; My PS/2 adapter has been acting up and probably has a loose wire. I need to
; fix it beore I can test this recipe on real hardware.
; But theoretically, it works...
; 8K of onboard RAM
.equ RAMSTART 0xc000
.equ USER_CODE 0xd500
; Memory register at the end of RAM. Must not overwrite
.equ RAMEND 0xddd0
jp init
; *** JUMP TABLE ***
jp strncmp
jp upcase
jp findchar
jp parseHex
jp blkSel
jp blkSet
jp fsFindFN
jp fsOpen
jp fsGetB
jp fsPutB
jp fsSetSize
jp printstr
jp _blkGetB
jp _blkPutB
jp _blkSeek
jp _blkTell
jp printcrlf
jp stdioPutC
jp stdioReadLine
.fill 0x66-$
retn
.inc "err.h"
.inc "ascii.h"
.inc "blkdev.h"
.inc "fs.h"
.inc "core.asm"
.inc "str.asm"
.inc "sms/kbd.asm"
.equ KBD_RAMSTART RAMSTART
.equ KBD_FETCHKC smskbdFetchKCB
.inc "kbd.asm"
.inc "sms/vdp.asm"
.equ GRID_RAMSTART KBD_RAMEND
.equ GRID_COLS VDP_COLS
.equ GRID_ROWS VDP_ROWS
.equ GRID_SETCELL vdpSetCell
.equ GRID_GETC kbdGetC
.inc "grid.asm"
.equ STDIO_RAMSTART GRID_RAMEND
.equ STDIO_GETC gridGetC
.equ STDIO_PUTC gridPutC
.inc "stdio.asm"
.equ MMAP_START 0xd700
; 0x180 is to leave some space for the stack
.equ MMAP_LEN RAMEND-MMAP_START-0x180
.inc "mmap.asm"
.equ BLOCKDEV_RAMSTART STDIO_RAMEND
.equ BLOCKDEV_COUNT 3
.inc "blockdev.asm"
; List of devices
.dw mmapGetB, mmapPutB
.dw f0GetB, f0PutB
.dw f1GetB, f1PutB
.equ FS_RAMSTART BLOCKDEV_RAMEND
.equ FS_HANDLE_COUNT 2
.inc "fs.asm"
; *** BASIC ***
; RAM space used in different routines for short term processing.
.equ SCRATCHPAD_SIZE STDIO_BUFSIZE
.equ SCRATCHPAD FS_RAMEND
.inc "lib/util.asm"
.inc "lib/ari.asm"
.inc "lib/parse.asm"
.inc "lib/fmt.asm"
.equ EXPR_PARSE parseLiteralOrVar
.inc "lib/expr.asm"
.inc "basic/util.asm"
.inc "basic/parse.asm"
.inc "basic/tok.asm"
.equ VAR_RAMSTART SCRATCHPAD+SCRATCHPAD_SIZE
.inc "basic/var.asm"
.equ BUF_RAMSTART VAR_RAMEND
.inc "basic/buf.asm"
.equ BFS_RAMSTART BUF_RAMEND
.inc "basic/fs.asm"
.inc "basic/blk.asm"
.equ BAS_RAMSTART BFS_RAMEND
.inc "basic/main.asm"
; USER_CODE is set according to this output below.
.out BAS_RAMEND
init:
di
im 1
ld sp, RAMEND
; init a FS in mmap
ld hl, MMAP_START
ld a, 'C'
ld (hl), a
inc hl
ld a, 'F'
ld (hl), a
inc hl
ld a, 'S'
ld (hl), a
call fsInit
xor a
ld de, BLOCKDEV_SEL
call blkSel
call fsOn
call kbdInit
call gridInit
call vdpInit
call basInit
ld hl, basFindCmdExtra
ld (BAS_FINDHOOK), hl
jp basStart
basFindCmdExtra:
ld hl, basFSCmds
call basFindCmd
ret z
ld hl, basBLKCmds
call basFindCmd
ret z
ld hl, .mycmds
call basFindCmd
ret z
jp basPgmHook
.mycmds:
.db "ed", 0
.dw 0x1f00
.db "zasm", 0
.dw 0x2400
.db 0xff
f0GetB:
ld ix, FS_HANDLES
jp fsGetB
f0PutB:
ld ix, FS_HANDLES
jp fsPutB
f1GetB:
ld ix, FS_HANDLES+FS_HANDLE_SIZE
jp fsGetB
f1PutB:
ld ix, FS_HANDLES+FS_HANDLE_SIZE
jp fsPutB
; last time I checked, PC at this point was 0x128f. Let's give us a nice margin
; for the start of ed.
.fill 0x1f00-$
.bin "ed.bin"
; Last check: 0x23b0
.fill 0x2400-$
.bin "zasm.bin"
FNT_DATA:
.bin "fnt/7x7.bin"
.fill 0x7ff0-$
.db "TMR SEGA", 0x00, 0x00, 0xfb, 0x68, 0x00, 0x00, 0x00, 0x4c

32
recipes/sms/romasm/user.h Normal file
View File

@ -0,0 +1,32 @@
.equ USER_CODE 0xc200
; Make ed fit in SMS's memory
.equ ED_BUF_MAXLINES 0x100
.equ ED_BUF_PADMAXLEN 0x800
; Make zasm fit in SMS's memory
.equ ZASM_REG_MAXCNT 0x80
.equ ZASM_LREG_MAXCNT 0x10
.equ ZASM_REG_BUFSZ 0x800
.equ ZASM_LREG_BUFSZ 0x100
; *** JUMP TABLE ***
.equ strncmp 0x03
.equ upcase @+3
.equ findchar @+3
.equ parseHex @+3
.equ blkSel @+3
.equ blkSet @+3
.equ fsFindFN @+3
.equ fsOpen @+3
.equ fsGetB @+3
.equ fsPutB @+3
.equ fsSetSize @+3
.equ printstr @+3
.equ _blkGetB @+3
.equ _blkPutB @+3
.equ _blkSeek @+3
.equ _blkTell @+3
.equ printcrlf @+3
.equ stdioPutC @+3
.equ stdioReadLine @+3

View File

@ -44,7 +44,7 @@ int main(int argc, char **argv)
set_blocking(fd, 1);
char s[0x40];
sprintf(s,
": _ 0x%04x 0x%04x DO KEY DUP .x I C! LOOP ; _",
": _ 0x%04x 0x%04x DO KEY DUP .x I A! LOOP ; _",
memptr+bytecount, memptr);
sendcmd(fd, s);