326 lines
11 KiB
Text
326 lines
11 KiB
Text
== Serial (Optional Add-On)
|
|
|
|
Declared in `<joey/serial.h>`.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
This header is *not* pulled in by `<joey/joey.h>`. Include it explicitly:
|
|
|
|
[source,c]
|
|
----
|
|
#include <joey/joey.h>
|
|
#include <joey/serial.h>
|
|
----
|
|
|
|
A program that never references `jlSerial*` pays nothing -- the linker drops
|
|
the module entirely.
|
|
====
|
|
|
|
One byte-stream API spans every port while the HAL hides very different
|
|
hardware: the IIgs drives a Zilog 8530 SCC directly or a 6551 card in a slot;
|
|
DOS uses an interrupt-driven 8250/16550 UART; the Amiga uses `serial.device`;
|
|
the ST goes through the BIOS Aux channel; the X68000 transmits through IOCS
|
|
and receives on its own SCC interrupt.
|
|
|
|
The model is *non-blocking and buffered*. Received bytes accumulate in a
|
|
background ring -- a hardware interrupt on DOS, the X68000 and the IIgs, the
|
|
OS on Amiga and ST -- and reads never block. Call `jlSerialPoll` once per
|
|
frame: it is next to a no-op where the interrupt or OS already fills the ring,
|
|
and the hardware drain where nothing does, so calling it every frame is always
|
|
correct.
|
|
|
|
On the IIgs a Super Serial Card only interrupts with its interrupt switch
|
|
(SW2-6) on; with it off the card still works, drained by `jlSerialPoll`, but
|
|
loses what arrives while the program is busy. The card's 6551 holds a single
|
|
received byte, about 1 ms at 9600 baud, which is less than the IIgs's own
|
|
worst-case interrupt latency during a present: it runs loss-free at 2400 baud,
|
|
and above that relies on the protocol to recover a dropped byte. The built-in
|
|
modem and printer ports buffer three bytes and stay loss-free at 9600 through
|
|
full-screen presents, joystick reads and sound effects. To get there, JoeyLib
|
|
cuts its own long interrupts-off stretches into pieces of at most ~1.7 ms while
|
|
a port is open: the present and the full-surface clear, the joystick read
|
|
(~2.8 ms otherwise, with the stick at full deflection), and the sound-effect
|
|
calls into the NinjaTrackerPlus replayer (~2.4 ms to start an effect, and a
|
|
refill interrupt that tops up every streaming effect that is due at once). A
|
|
full-screen present gets about 5% slower; a joystick read costs about what it
|
|
does with no port open, up to ~0.8 ms more with the stick in a corner, and its
|
|
reading can differ from the same stick's reading with no port open by a count.
|
|
With no port open all of these run as before. Two stretches are not
|
|
JoeyLib's to cut and can still cost bytes: GS/OS disk access (a 3.5-inch
|
|
floppy holds interrupts off for 65 to 220 ms at a time), and starting a MOD
|
|
song with `jlAudioPlayMod`, which loads its instruments into the sound chip
|
|
with interrupts off, roughly 12 ms per KB of samples.
|
|
|
|
On the Amiga the port is `serial.device`, which AmigaOS loads from `DEVS:`:
|
|
every Workbench installation has it, but Kickstart 3.1 on its own does not, so
|
|
a program booted without Workbench cannot open the port. `joey-run` puts the
|
|
installer's copy (component `amigaSerial`) on the boot drive; a game shipped
|
|
to boot on its own must do the same.
|
|
|
|
A failed `jlSerialOpen` is non-fatal: it returns `false` and every other call
|
|
becomes a safe no-op, exactly like a failed audio init.
|
|
|
|
=== Types
|
|
|
|
[source,c]
|
|
----
|
|
typedef enum {
|
|
JL_SERIAL_DEFAULT = 0, // the platform's natural RS-232 port
|
|
JL_SERIAL_MODEM, // IIgs modem port (SCC channel B)
|
|
JL_SERIAL_PRINTER, // IIgs printer port (SCC channel A)
|
|
JL_SERIAL_SLOT // card/port selected by config->unit
|
|
} jlSerialDeviceE;
|
|
|
|
typedef enum {
|
|
JL_SERIAL_PARITY_NONE = 0, JL_SERIAL_PARITY_ODD, JL_SERIAL_PARITY_EVEN
|
|
} jlSerialParityE;
|
|
|
|
typedef enum {
|
|
JL_SERIAL_FLOW_NONE = 0, // no handshaking
|
|
JL_SERIAL_FLOW_RTSCTS, // hardware RTS/CTS
|
|
JL_SERIAL_FLOW_XONXOFF // software XON/XOFF
|
|
} jlSerialFlowE;
|
|
|
|
typedef struct {
|
|
uint32_t baud; // 300 .. 115200, clamped per port
|
|
uint8_t dataBits; // 5 .. 8
|
|
uint8_t stopBits; // 1 or 2
|
|
jlSerialParityE parity;
|
|
jlSerialFlowE flow;
|
|
uint8_t unit; // JL_SERIAL_SLOT only
|
|
} jlSerialConfigT;
|
|
----
|
|
|
|
The IIgs is the machine with three device choices; elsewhere `JL_SERIAL_MODEM`
|
|
and `JL_SERIAL_PRINTER` both mean the default port. `JL_SERIAL_DEFAULT` selects
|
|
the IIgs modem port, DOS COM1, the ST Modem port, Amiga unit 0 or the X68000's
|
|
RS-232C port.
|
|
|
|
On the X68000 the line is set by the RS-232C routines in charge. With
|
|
Human68k's RS-232C driver, RSDRV.SYS, loaded (the stock Human68k 3.02 system
|
|
disk's CONFIG.SYS loads it, and so do JoeyLib's own X68000 boot disks), the
|
|
port runs at 75 to 38400 bps and `JL_SERIAL_FLOW_RTSCTS` makes transmit wait
|
|
for CTS; RTS stays up, since JoeyLib's receive interrupt, not the driver's,
|
|
fills the receive ring. 38400 is really 39062 bps, 1.7% fast, within the
|
|
usual tolerance of a UART at the far end. Without RSDRV.SYS the ROM's
|
|
routines allow 75 to 9600 bps: a faster request clamps to 9600, and
|
|
`JL_SERIAL_FLOW_RTSCTS` falls back to no handshaking. JoeyLib checks for the
|
|
driver at every `jlSerialOpen`.
|
|
|
|
`unit` disambiguates `JL_SERIAL_SLOT` and is ignored otherwise: an IIgs Super
|
|
Serial Card slot 1..7, a DOS COM number 1..4, or an Amiga `serial.device` unit.
|
|
|
|
=== jlSerialOpen
|
|
|
|
[source,c]
|
|
----
|
|
bool jlSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config);
|
|
----
|
|
|
|
Opens and configures the port. Only one port may be open at a time; a second
|
|
open without an intervening close fails.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`device` |`jlSerialDeviceE` |Which interface.
|
|
|`config` |`const jlSerialConfigT *` |Line configuration.
|
|
|===
|
|
|
|
*Returns:* `true` on success. `false` if the port cannot be opened or the
|
|
platform has no serial support -- and then every other call no-ops.
|
|
|
|
=== jlSerialClose
|
|
|
|
[source,c]
|
|
----
|
|
void jlSerialClose(void);
|
|
----
|
|
|
|
Closes the port and releases its resources. Safe when not open. A port still
|
|
open when the program exits is closed then: the first successful
|
|
`jlSerialOpen` registers an `atexit` handler, because on DOS, the X68000 and
|
|
the IIgs the port's receive interrupt vector points into the program until it
|
|
is closed.
|
|
|
|
Closing does *not* hang up: `jlSerialClose` leaves DTR exactly as it is, so
|
|
a modem call or a BBS caller survives the program handing the line back: a
|
|
door returning to its BBS, or a terminal program exiting while the call stays
|
|
up. RTS is not part of that promise; on the Amiga, `serial.device` puts back
|
|
whatever RTS was before the open.
|
|
|
|
To hang up, drop DTR first: call `jlSerialSetDtr(false)`, wait longer than
|
|
the modem needs to notice (a Hayes modem's S25 setting, 50 ms by default),
|
|
then close. The line stays down.
|
|
|
|
[NOTE]
|
|
====
|
|
Two ports cannot keep the line perfectly steady, though neither gap is long
|
|
enough to hang up a modem (a Hayes modem ignores DTR drops shorter than its
|
|
S25 setting, 50 ms by default):
|
|
|
|
* On the Amiga, when another program had the port open first, the last close
|
|
can drop DTR (`serial.device` puts back the lines as that program found
|
|
them), and JoeyLib raises it again straight away: the line is off for under
|
|
a millisecond. Otherwise JoeyLib raises DTR before its own open, so the
|
|
close keeps it up with no gap.
|
|
* On the IIgs, opening a port resets its channel, which drops DTR briefly
|
|
even if it was already up: about a quarter of a millisecond on the modem
|
|
and printer ports, about a millisecond on a Super Serial Card.
|
|
====
|
|
|
|
*Parameters:* none. *Returns:* nothing.
|
|
|
|
=== jlSerialPoll
|
|
|
|
[source,c]
|
|
----
|
|
void jlSerialPoll(void);
|
|
----
|
|
|
|
Pumps the receive and transmit buffers. Call once per game frame. Where an
|
|
interrupt fills the ring this does next to nothing; it is the drain for a port
|
|
that cannot interrupt.
|
|
|
|
*Parameters:* none. *Returns:* nothing.
|
|
|
|
=== jlSerialAvailable
|
|
|
|
[source,c]
|
|
----
|
|
uint16_t jlSerialAvailable(void);
|
|
----
|
|
|
|
*Parameters:* none.
|
|
|
|
*Returns:* the number of bytes waiting in the receive buffer, readable now
|
|
without blocking.
|
|
|
|
=== jlSerialRead
|
|
|
|
[source,c]
|
|
----
|
|
uint16_t jlSerialRead(uint8_t *buf, uint16_t max);
|
|
----
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`buf` |`uint8_t *` |Destination buffer.
|
|
|`max` |`uint16_t` |Buffer capacity.
|
|
|===
|
|
|
|
*Returns:* the number of bytes copied, 0 to `max`. Never blocks.
|
|
|
|
=== jlSerialWrite
|
|
|
|
[source,c]
|
|
----
|
|
uint16_t jlSerialWrite(const uint8_t *buf, uint16_t len);
|
|
----
|
|
|
|
Sends bytes. Transmit is a bounded poll.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`buf` |`const uint8_t *` |Data to send.
|
|
|`len` |`uint16_t` |Byte count.
|
|
|===
|
|
|
|
*Returns:* the number of bytes accepted, which may be fewer than `len` if a
|
|
flow-controlled peer or a full transmit path stalls.
|
|
|
|
=== jlSerialWriteByte / jlSerialReadByte
|
|
|
|
[source,c]
|
|
----
|
|
bool jlSerialWriteByte(uint8_t b);
|
|
int16_t jlSerialReadByte(void);
|
|
----
|
|
|
|
Single-byte convenience calls.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`b` |`uint8_t` |Byte to send.
|
|
|===
|
|
|
|
*Returns:* `jlSerialWriteByte` returns `true` if the byte was accepted.
|
|
`jlSerialReadByte` returns the byte, or `-1` when none is ready.
|
|
|
|
=== jlSerialFlush
|
|
|
|
[source,c]
|
|
----
|
|
void jlSerialFlush(void);
|
|
----
|
|
|
|
Discards any buffered received bytes.
|
|
|
|
*Parameters:* none. *Returns:* nothing.
|
|
|
|
=== jlSerialSetDtr
|
|
|
|
[source,c]
|
|
----
|
|
void jlSerialSetDtr(bool on);
|
|
----
|
|
|
|
Raises or drops DTR. `jlSerialOpen` raises DTR and `jlSerialClose` leaves it
|
|
as it is, so this is how a program hangs up:
|
|
|
|
[source,c]
|
|
----
|
|
jlSerialSetDtr(false); // hang up
|
|
// ...wait longer than the modem's DTR-drop detection time (Hayes S25,
|
|
// 50 ms by default)...
|
|
jlSerialClose(); // DTR stays down
|
|
----
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`on` |`bool` |`true` raises DTR, `false` drops it.
|
|
|===
|
|
|
|
*Returns:* nothing. A closed port no-ops, so this can be driven
|
|
unconditionally.
|
|
|
|
Two ports tie DTR to more than the line:
|
|
|
|
* On an IIgs Super Serial Card the 6551 has no DTR that is only a line:
|
|
dropping DTR also stops the card's receiver until DTR is raised again.
|
|
* On the X68000 the line setup in the ROM and in RSDRV.SYS raises DTR
|
|
whenever it runs, so the next `jlSerialOpen` -- or another program setting
|
|
the line -- raises it again.
|
|
|
|
Closing after a hang-up keeps DTR down, but on two ports it rises briefly on
|
|
the way: on the Amiga for under a millisecond (`serial.device` puts back the
|
|
lines as they were at its open) and on the X68000 for about 0.2 ms (close puts
|
|
back the line settings, which raises DTR), before JoeyLib drops it again. A
|
|
modem that has hung up ignores that.
|
|
|
|
=== jlSerialSetRts
|
|
|
|
[source,c]
|
|
----
|
|
void jlSerialSetRts(bool on);
|
|
----
|
|
|
|
Asserts or drops RTS, for a caller doing its own receive-side flow control.
|
|
|
|
`JL_SERIAL_FLOW_RTSCTS` asks the *port* to handshake; this is the other half --
|
|
the application saying "my buffer is nearly full, stop sending", and later "go
|
|
ahead". That is what a client with a small ring and a peer that can be told to
|
|
pause actually needs.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`on` |`bool` |`true` asserts RTS, meaning ready to receive.
|
|
|===
|
|
|
|
*Returns:* nothing. A port with no controllable RTS line no-ops, and a closed
|
|
port no-ops; neither is an error, so this can be driven unconditionally.
|