Serial ports for Zig: rate, framing, flow control and modem lines on termios2 for Linux and the DCB for Windows.
  • Zig 95.9%
  • Nix 4.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jeffrey C. Ollie 13a840e05d
All checks were successful
test / test (push) Successful in 8m20s
test / docs (push) Successful in 6m9s
Say where the repository lives
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KaBaVYY8vv3qA6sgbQBuZT
2026-10-10 13:32:00 -05:00
.forgejo/workflows Serial ports for Zig 2026-10-10 12:08:54 -05:00
LICENSES Serial ports for Zig 2026-10-10 12:08:54 -05:00
src Serial ports for Zig 2026-10-10 12:08:54 -05:00
tools Serial ports for Zig 2026-10-10 12:08:54 -05:00
.gitignore Serial ports for Zig 2026-10-10 12:08:54 -05:00
build.zig Serial ports for Zig 2026-10-10 12:08:54 -05:00
build.zig.zon Serial ports for Zig 2026-10-10 12:08:54 -05:00
flake.lock Serial ports for Zig 2026-10-10 12:08:54 -05:00
flake.nix Serial ports for Zig 2026-10-10 12:08:54 -05:00
package.nix Serial ports for Zig 2026-10-10 12:08:54 -05:00
README.md Say where the repository lives 2026-10-10 13:32:00 -05:00
REUSE.toml Serial ports for Zig 2026-10-10 12:08:54 -05:00

zig-serial

Serial ports for Zig 0.17: open one, set its rate and framing, read and write it, and drive its modem lines.

const serial = @import("serial");

var port = try serial.Port.open("/dev/ttyUSB0", .{ .baud = 9600 }, .{});
defer port.close();

_ = try port.write("AT\r");
var buf: [256]u8 = undefined;
const n = try port.read(&buf);

The API documentation is generated from the doc comments and published at https://jeff.jcollie.page/zig-serial/.

What a Port does

  • Raw mode. Opening a port puts it into raw mode, so that what is written is what goes on the wire and what arrives is what is read: no echo, no line editing, no newline translation, no signals. Closing it puts back the settings the device had before.
  • Settings. Settings holds the rate, data bits, parity (including mark and space), stop bits, flow control (RTS/CTS or XON/XOFF), hang-up-on-close and CLOCAL. configure applies all of them in one call, so the line never runs at a mix of the old configuration and the new.
  • What the driver actually chose. current reads the settings back from the driver, which may differ from what was asked: a driver can round a rate it cannot divide down to.
  • Modem lines and line control. modem reads the modem lines and setLine raises or lowers DTR and RTS. sendBreak, drain and flush do what their names say.
  • Exclusive by default. On Linux an advisory flock and TIOCEXCL keep a second program off the port; on Windows a port is always exclusive.
  • Non-blocking by default. read and write return error.WouldBlock rather than waiting, which is what an event loop polling Port.handle wants. Pass .nonblocking = false for a thread that does nothing but read.

Platforms

  • Linux. Built on raw system calls with no libc. It uses termios2 (TCGETS2/TCSETS2) with BOTHER, so every rate is asked for by number and a rate like 31250 needs no special case. The asm-generic termios2 layout is the only one written out: MIPS, PowerPC, SPARC and Alpha lay the structure out differently and are refused at compile time.
  • Windows. Uses the DCB and COMMTIMEOUTS through kernel32, declared in the source rather than taken from a bindings package. Every rate, every parity, and one and a half stop bits are ordinary values. DTR and RTS cannot be read back on Windows, so modem reports them as this port last set them. This backend is compiled by zig build check but has not been run against hardware.
  • The BSDs and macOS have no backend yet. They want one on their libc termios, with IOSSIOSPEED on macOS for arbitrary rates.

Testing

$ nix develop -c zig build test --summary all
$ nix develop -c zig build check   # Windows and aarch64-linux, compiled only

The tests use pseudo-terminals in place of a cable. A pty cannot test everything, because it always reports eight data bits and no parity whatever it is told. So the conversion between Settings and termios2 is tested on its own, in memory, and the pty tests cover what a pty does keep: the rate, the stop bits, flow control, raw data in both directions, exclusivity, and the restoring of the original settings on close.

Where this came from

zettacom has a serial layer of its own, but most of it is ported from picocom and is GPL. zig-serial is a fresh MIT implementation, written from the interfaces in the references below, so that MIT programs — the harrier terminal among them — can use it.

Repository

The canonical repository is on my Forgejo instance, and Tangled carries a mirror of it:

git clone https://git.jcollie.dev/jeff/zig-serial.git

CI and the published documentation come from the Forgejo repository.

License

MIT; see LICENSES/MIT.txt. The project follows the REUSE specification, and reuse lint passes.

References cited