== Serial (Optional Add-On) Declared in ``. [IMPORTANT] ==== This header is *not* pulled in by ``. Include it explicitly: [source,c] ---- #include #include ---- 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.