mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-10 03:08:06 +11:00
323 lines
12 KiB
Markdown
323 lines
12 KiB
Markdown
# TRS-80 Model 4p
|
|
|
|
The TRS-80 (models 1, 3 and 4) are among the most popular z80 machines. They're
|
|
very nicely designed and I got my hands on a 4p with two floppy disk drives and
|
|
a RS-232 port. In this recipe, we're going to get Collapse OS running on it.
|
|
|
|
![Collapse OS on a TRS-80 Model 4P](collapseos-on-trs80.jpg)
|
|
|
|
## Not entirely standalone
|
|
|
|
Collapse OS uses the TRS-80 drivers rather than its own. On most TRS-80 models,
|
|
those drivers are on ROM, but in the case of the 4P model, those drivers are on
|
|
the TRSDOS disk (well, from what I understand, not all of it, but still, a big
|
|
part of it).
|
|
|
|
It would be preferable to develop drivers from scratch, but it represents a
|
|
significant effort for a modest payout (because it's only actually useful when
|
|
you want to use a 4P model that has no TRSDOS disk).
|
|
|
|
Maybe those drivers will be developed later, but it's not a priority for now.
|
|
|
|
## Floppy or RS-232?
|
|
|
|
There are many ways to get Collapse OS to run on it. One would involve writing
|
|
it to a floppy. I bought myself old floppy drives for that purpose, but I happen
|
|
to not have any functional computer with a floppy port on it. I still have the
|
|
motherboard of my old pentium, but I don't seem to have a video card for it any
|
|
more.
|
|
|
|
Because my 4p has a RS-232 port and because I have equipment to do serial
|
|
communication from modern machines (I didn't have a DB-9 to DB-25 adapter
|
|
though, I had to buy one), I chose that route.
|
|
|
|
## Gathering parts
|
|
|
|
* A TRS-80 model 4p with a RS-232 port
|
|
* A TRSDOS 6.x disk
|
|
* A means to do serial communication. In my case, that meant:
|
|
* A USB-to-serial device
|
|
* A null modem cable
|
|
* A DB-9 gender changer
|
|
* A DB-9 to DB-25 adapter
|
|
|
|
## Overview
|
|
|
|
We need to send sizeable binary programs through the RS-232 port and then run
|
|
it. The big challenge here is ensuring data integrity. Sure, serial
|
|
communication has parity check, but it has no helpful way of dealing with
|
|
parity errors. When parity check is enabled and that a parity error occurs, the
|
|
byte is simply dropped on the receiving side. Also, a double bit error could be
|
|
missed by those checks.
|
|
|
|
What we'll do here is to ping back every received byte back and have the sender
|
|
do the comparison and report mismatched data.
|
|
|
|
Another problem is ASCII control characters. When those are sent across serial
|
|
communication channels, all hell breaks lose. When sending binary data, those
|
|
characters have to be avoided. We use `tools/ttysafe` for that.
|
|
|
|
Does TRSDOS have a way to receive this binary inside these constraints? Not to
|
|
my knowledge. As far as I know, the COMM program doesn't allow this.
|
|
|
|
What are we going to do? We're going to punch in a binary program to handle that
|
|
kind of reception! You're gonna feel real badass about it too...
|
|
|
|
## Keyboard tips
|
|
|
|
* `_` is `CLEAR+ENTER`.
|
|
|
|
## Building the binary
|
|
|
|
You can build the binary to send to the TRS-80 with `make`, which will yield
|
|
`os.bin`. You'll need it later.
|
|
|
|
## Testing serial communication
|
|
|
|
The first step here is ensuring that you have bi-directional serial
|
|
communication. To do this, first prepare your TRS-80:
|
|
|
|
set *cl to com
|
|
setcomm (word=8,parity=no)
|
|
|
|
The first line loads the communication driver from the `COM/DRV` file on the
|
|
TRSDOS disk and binds it to `*cl`, the name generally used for serial
|
|
communication devices. The second line sets communication parameters in line
|
|
with what is generally the default on modern machine. Note that I left the
|
|
default of 300 bauds as-is.
|
|
|
|
Then, you can run `COMM *cl` to start a serial communication console.
|
|
|
|
Then, on the modern side, use your favorite serial communication program and set
|
|
the tty to 300 baud with option "raw". Make sure you have `-parenb`.
|
|
|
|
If your line is good, then what you type on either side should echo on the
|
|
other side. If it does not, something's wrong. Debug.
|
|
|
|
## Punching in the goodie
|
|
|
|
As stated in the overview, we need a program on the TRS-80 that:
|
|
|
|
1. Listens to `*cl`
|
|
2. Echoes each character back to `*cl`
|
|
3. Adjusts `ttysafe` escapes
|
|
4. Stores received bytes in memory
|
|
|
|
You're in luck: that program has already been written. It's in B502 and B503.
|
|
You can compile it with:
|
|
|
|
212 LOAD ( z80 assembler )
|
|
0x0238 CONSTANT COM_DRV_ADDR
|
|
0x3000 CONSTANT DEST_ADDR
|
|
502 LOAD
|
|
503 LOAD
|
|
|
|
Then, you can use `DUMP` to visualize the data you'll need to punch in:
|
|
|
|
H@ ORG @ - ORG @ DUMP
|
|
|
|
It can run from any offset (all jumps in it are relative), but writes to
|
|
`DEST_ADDR`. Make sure you don't place it in a way to be overwritten by its
|
|
received data.
|
|
|
|
Wondering what is that `COM_DRV_ADDR` constant? That's the DCB handle of your
|
|
`*cl` device. You will need to get that address before you continue. Go read
|
|
the following section and come back here.
|
|
|
|
How will you punch that in? The `debug` program! This very useful piece of
|
|
software is supplied in TRSDOS. To invoke it, first run `debug (on)` and then
|
|
press the `BREAK` key. You'll get the debug interface which allows you to punch
|
|
in any data in any memory address. Let's use `0x5000` which is the offset it's
|
|
designed for.
|
|
|
|
For reference: to go back to the TRSDOS prompt, it's `o<return>`.
|
|
|
|
First, display the `0x5000-0x503f` range with the `d5000<space>` command (I
|
|
always press Enter by mistake, but it's space you need to press). Then, you can
|
|
begin punching in with `h5000<space>`. This will bring up a visual indicator of
|
|
the address being edited. Punch in the stuff with a space in between each byte
|
|
and end the edit session with `x`.
|
|
|
|
## Getting your DCB address
|
|
|
|
In the previous step, you need to set `COM_DRV_ADDR` to your "DCB" address for
|
|
`*cl`. That address is your driver "handle". To get it, first get the address
|
|
where the driver is loaded in memory. You can get this by running `device
|
|
(b=y)`. That address you see next to `*cl`? that's it. But that's not our DCB.
|
|
|
|
To get your DBC, go explore that memory area. Right after the part where there's
|
|
the `*cl` string, there's the DCB address (little endian). On my setup, the
|
|
driver was loaded in `0x0ff4` and the DCB address was 8 bytes after that, with
|
|
a value of `0x0238`. Don't forget that z80 is little endian. `38` will come
|
|
before `02`.
|
|
|
|
## Saving that program for later
|
|
|
|
If you want to save yourself typing for later sessions, why not save the
|
|
program you've painfully typed to disk? TRSDOS enables that easily. Let's say
|
|
that you typed your program at `0x5000` and that you want to save it to
|
|
`RECV/CMD` on your second floppy drive, you'd do:
|
|
|
|
dump recv/cmd:1 (start=x'5000',end=x'5030',tra='5000')
|
|
|
|
A memory range dumped this way will be re-loaded at the same offset through
|
|
`load recv/cmd:1`. Even better, `TRA` indicates when to jump after load when
|
|
using the `RUN` command. Therefore, you can avoid all this work above in later
|
|
sessions by simply typing `recv` in the DOS prompt.
|
|
|
|
Note that you might want to turn `debug` off for these commands to run. I'm not
|
|
sure why, but when the debugger is on, launching the command triggers the
|
|
debugger.
|
|
|
|
## Sending binary through the RS-232 port
|
|
|
|
Once you're finished punching your program in memory, you can run it with
|
|
`g5000<enter>` (not space). If you've saved it to disk, run `recv` instead.
|
|
Because it's an infinite loop, your screen will freeze. You can start sending
|
|
your data.
|
|
|
|
To that end, there's the `tools/pingpong` program. It takes a device and a
|
|
filename to send. Before you send the binary, make it go through
|
|
`tools/ttysafe` first (which just takes input from stdin and spits tty-safe
|
|
content to stdout):
|
|
|
|
./ttysafe < stage1.bin > stage1.ttysafe
|
|
|
|
On OpenBSD, the invocation can look like:
|
|
|
|
doas ./pingpong /dev/ttyU0 os.ttysafe
|
|
|
|
You will be prompted for a key before the contents is sent. This is because on
|
|
OpenBSD, TTY configuration is lost as soon as the TTY is closed, which means
|
|
that you can't just run `stty` before running `pingpong`. So, what you'll do is,
|
|
before you press your key, run `doas stty -f /dev/ttyU0 300 raw` and then press
|
|
any key on the `pingpong` invocation.
|
|
|
|
If everything goes well, the program will send your contents, verifying every
|
|
byte echoed back, and then send a null char to indicate to the receiving end
|
|
that it's finished sending. This will end the infinite loop on the TRS-80 side
|
|
and return. That should bring you back to a refreshed debug display and you
|
|
should see your sent content in memory, at the specified address (`0x3000` if
|
|
you didn't change it).
|
|
|
|
If there was no error during `pingpong`, the content should be exact.
|
|
Nevertheless, I recommend that you manually validate a few bytes using TRSDOS
|
|
debugger before carrying on.
|
|
|
|
*debugging tip*: Sometimes, the communication channel can be a bit stubborn and
|
|
always fail, as if some leftover data was consistently blocking the channel. It
|
|
would cause a data mismatch at the very beginning of the process, all the time.
|
|
What I do in these cases is start a `COMM *cl` session on one side and a screen
|
|
session on the other, type a few characters, and try `pingpong` again.
|
|
|
|
## Saving to disk
|
|
|
|
If everything went well, you could run Collapse OS with `g3000<return>`. You
|
|
would get a usable Collapse OS prompt. But don't do that just yet. That
|
|
executable bootstraps itself from code and it takes a while to do that every
|
|
time you launch it. You don't want that right? Let's save a compiled version of
|
|
it to disk.
|
|
|
|
Turn off the debugger (which can mess up some things) and save your sent
|
|
content as-is by following the instructions you had for the `RECV` program.
|
|
This way, if you mess up a step below, you can quickly start over. Now you can
|
|
launch Collapse OS. Then, we need to:
|
|
|
|
* Reclaim wasted memory
|
|
* Create hook entry
|
|
* Update LATEST
|
|
* Write down initialization code
|
|
* Write memory to floppy
|
|
|
|
### Reclaim wasted memory
|
|
|
|
During initialization, `RDLN$` allocated memory in `HERE` for its input buffer.
|
|
If you don't reclaim that space, that will be dead space in your binary.
|
|
|
|
You can reclaim that space with `FORGET _` which will rewind to before we
|
|
defined our initialization routine (see xcomp.fs).
|
|
|
|
However, that is not enough. If you only do that, there will be a conflict
|
|
between the rdln input buffer and your `HERE` space! So we need to go put that
|
|
input buffer somewhere else first. Therefore, your commands will be:
|
|
|
|
500 ALLOT RDLN$ FORGET _
|
|
|
|
### Create hook entry
|
|
|
|
That one is easy:
|
|
|
|
(entry) _
|
|
|
|
### Update LATEST
|
|
|
|
At this point, both `HERE` and `CURRENT` point to your future `LATEST`.
|
|
|
|
H@ 0x08 BIN+ !
|
|
|
|
Done.
|
|
|
|
### Write down initialization code
|
|
|
|
You'll do something similar to what we do in xcomp, except you'll have to add
|
|
"HERE rewinding" code because by default, `HERE` starts at RAMSTART+0x80. So:
|
|
|
|
," CURRENT @ HERE ! (ok) RDLN$ "
|
|
|
|
As soon as `RDLN$` is called, the `C<` pointer changes and gives control to
|
|
keyboard, giving us our full forth interpreter.
|
|
|
|
### Write memory to floppy
|
|
|
|
What you currently have in memory is gold. You want that on floppy. First, run
|
|
`H@ .X` to know your upper bound (and `0 BIN+ .X` to know your lower one, but
|
|
you're already supposed to know that one). Then, run `BYE` to return to TRSDOS
|
|
(the TRSDOS driver overrides `BYE` so that it calls the proper SVC instead of
|
|
just halting). Then, you can dump memory to floppy as you already did for
|
|
`RECV`.
|
|
|
|
## Sending blkfs to floppy
|
|
|
|
As it is, your system fully supports reading and writing to floppy drive 1. It
|
|
also had `*CL<` to read a char from `*cl` and `*CL>` to emit a char to `*cl`.
|
|
|
|
That's all you need to have a full Collapse OS with access to disk blocks.
|
|
|
|
First, make sure your floppies are formatted. Collapse OS is currently
|
|
hardcoded to single side and single density, which means there's a limit of 100
|
|
blocks per disk.
|
|
|
|
You'll need to send those blocks through RS-232. Begin by taking over the
|
|
prompt:
|
|
|
|
' *CL> 0x53 RAM+ !
|
|
' *CL< 0x55 RAM+ !
|
|
|
|
See B80 for details about those RAM offsets. Your serial link now has the
|
|
prompt. Now, you can use `/tools/blkup` to send a disk's contents. First,
|
|
extract the first 100 blocks from blkfs:
|
|
|
|
dd if=emul/blkfs bs=1024 count=100 > d1
|
|
|
|
Now, insert your formatted disk in drive 1 and push your blocks:
|
|
|
|
tools/blkup /dev/ttyUSB0 0 d1
|
|
|
|
It takes a while, but you will end up having your first 100 blocks on floppy!
|
|
Go ahead, `LIST` around. Then, repeat for other disks.
|
|
|
|
## Floppy organisation
|
|
|
|
Making blkfs span multiple disk is a bit problematic with regards to absolute
|
|
block references in the code. You'll need to work a bit to design your very
|
|
own Collapse OS floppy set. See Usage guide (B3) for details.
|
|
|
|
## Self-hosting
|
|
|
|
As it is, your installment of Collapse OS is self-hosting using instructions
|
|
very similar to `recipes/rc2014/selhost`. The difference is that instead of
|
|
writing the binary you have in memory to EEPROM, you'll quit to TRSDOS with
|
|
`BYE` and use TRSDOS' `DUMP` utility to save to disk like you already did
|
|
before.
|
|
|