joeylib2/docs/ApiSerial.adoc
2026-09-26 21:02:41 -05:00

212 lines
5.4 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 model is *non-blocking and buffered*. Received bytes accumulate in a
background ring -- a hardware interrupt on DOS, the OS on Amiga and ST, or
`jlSerialPoll` on the IIgs -- and reads never block. Call `jlSerialPoll` once
per frame: it is a no-op where the interrupt or OS already fills the ring, and
the hardware drain where it does not, so calling it every frame is always
correct.
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 A)
JL_SERIAL_PRINTER, // IIgs printer port (SCC channel B)
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 or Amiga unit 0.
`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.
*Parameters:* none. *Returns:* nothing.
=== jlSerialPoll
[source,c]
----
void jlSerialPoll(void);
----
Pumps the receive and transmit buffers. Call once per game frame.
*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.
=== 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.