diff --git a/src/backend/linux.zig b/src/backend/linux.zig index 0a62055..239419a 100644 --- a/src/backend/linux.zig +++ b/src/backend/linux.zig @@ -3,6 +3,17 @@ const std = @import("std"); const serialport = @import("../serialport.zig"); const linux = std.os.linux; +pub const BaudRate = b: { + const ti = @typeInfo(std.os.linux.speed_t).@"enum"; + const baud_rate_ti: std.builtin.Type.Enum = .{ + .tag_type = ti.tag_type, + .fields = ti.fields, + .decls = &.{}, + .is_exhaustive = false, + }; + break :b @Type(std.builtin.Type{ .@"enum" = baud_rate_ti }); +}; + pub const PortImpl = std.fs.File; pub const ReadError = std.fs.File.ReadError; @@ -21,29 +32,99 @@ pub fn close(port: PortImpl) void { port.close(); } +fn configureParity( + termios: *std.os.linux.termios, + parity: serialport.Config.Parity, +) void { + termios.cflag.PARENB = parity != .none; + termios.cflag.PARODD = parity == .odd or parity == .mark; + termios.cflag.CMSPAR = parity == .mark or parity == .space; + + termios.iflag.INPCK = parity != .none; + termios.iflag.IGNPAR = parity == .none; +} + +fn configureFlowControl( + termios: *std.os.linux.termios, + flow_control: serialport.Config.FlowControl, +) void { + termios.cflag.CLOCAL = flow_control == .none; + termios.cflag.CRTSCTS = flow_control == .hardware; + + termios.iflag.IXANY = flow_control == .software; + termios.iflag.IXON = flow_control == .software; + termios.iflag.IXOFF = flow_control == .software; +} + pub fn configure(port: PortImpl, config: serialport.Config) !void { + const CBAUD: u32 = switch (comptime builtin.target.cpu.arch) { + .powerpc, .powerpcle, .powerpc64, .powerpc64le => 0x000000FF, + else => 0x0000100F, + }; + const CIBAUD: u32 = switch (comptime builtin.target.cpu.arch) { + .powerpc, .powerpcle, .powerpc64, .powerpc64le => 0x00FF0000, + else => 0x100F0000, + }; + const BOTHER = switch (comptime builtin.target.cpu.arch) { + .powerpc, .powerpcle, .powerpc64, .powerpc64le => 0x0000001F, + else => 0x00001000, + }; + const IBSHIFT = 16; + + const custom_output_baud: bool = std.enums.tagName( + serialport.Config.BaudRate, + config.baud_rate, + ) == null; + const custom_input_baud: bool = if (config.input_baud_rate) |ibr| + std.enums.tagName(serialport.Config.BaudRate, ibr) == null + else + custom_output_baud; + var settings = try std.posix.tcgetattr(port.handle); - settings.iflag = .{}; - settings.iflag.INPCK = config.parity != .none; - settings.iflag.IXON = config.handshake == .software; - settings.iflag.IXOFF = config.handshake == .software; + // `cfmakeraw` + settings.iflag.IGNBRK = false; + settings.iflag.BRKINT = false; + settings.iflag.PARMRK = false; + settings.iflag.ISTRIP = false; + settings.iflag.INLCR = false; + settings.iflag.IGNCR = false; + settings.iflag.ICRNL = false; + settings.iflag.IXON = false; + + settings.oflag.OPOST = false; + + settings.lflag.ECHO = false; + settings.lflag.ECHONL = false; + settings.lflag.ICANON = false; + settings.lflag.ISIG = false; + settings.lflag.IEXTEN = false; + + var cflag: u32 = @bitCast(settings.cflag); + + // Set CBAUD and CIBAUD in cflag. + const baud_bits: u32 = @bitCast(@intFromEnum(config.baud_rate)); + cflag &= ~CBAUD; + cflag &= ~CIBAUD; + cflag |= if (custom_output_baud) BOTHER else baud_bits; + if (config.input_baud_rate) |ibr| { + const input_baud_bits: u32 = @bitCast(@intFromEnum(ibr)); + cflag |= + (if (custom_input_baud) input_baud_bits else BOTHER) << IBSHIFT; + } - settings.cflag = @bitCast(@intFromEnum(config.baud_rate)); + settings.cflag = @bitCast(cflag); settings.cflag.CREAD = true; - settings.cflag.CLOCAL = config.handshake == .none; settings.cflag.CSTOPB = config.stop_bits == .two; - settings.cflag.CSIZE = @enumFromInt(@intFromEnum(config.word_size)); - settings.cflag.CRTSCTS = config.handshake == .hardware; + settings.cflag.CSIZE = @enumFromInt(@intFromEnum(config.data_bits)); - settings.cflag.PARENB = config.parity != .none; - settings.cflag.PARODD = config.parity == .odd or config.parity == .mark; - settings.cflag.CMSPAR = config.parity == .mark or config.parity == .space; + configureParity(&settings, config.parity); + configureFlowControl(&settings, config.flow_control); - settings.oflag = .{}; - settings.lflag = .{}; - settings.ispeed = config.baud_rate; - settings.ospeed = config.baud_rate; + const ispeed: *serialport.Config.BaudRate = @ptrCast(&settings.ispeed); + ispeed.* = if (config.input_baud_rate) |ibr| ibr else config.baud_rate; + const ospeed: *serialport.Config.BaudRate = @ptrCast(&settings.ospeed); + ospeed.* = config.baud_rate; // Minimum arrived bytes before read returns. settings.cc[@intFromEnum(linux.V.MIN)] = 0; @@ -192,7 +273,7 @@ test { const config: serialport.Config = .{ .baud_rate = .B230400, - .handshake = .software, + .flow_control = .software, }; try configure(master, config); diff --git a/src/backend/macos.zig b/src/backend/macos.zig index e92f73f..cb23d5c 100644 --- a/src/backend/macos.zig +++ b/src/backend/macos.zig @@ -24,16 +24,18 @@ pub fn close(port: PortImpl) void { pub fn configure(port: PortImpl, config: serialport.Config) !void { var settings = try std.posix.tcgetattr(port.handle); + if (config.input_baud_rate != null) return error.InputBaudRateUnsupported; + settings.iflag = .{}; settings.iflag.INPCK = config.parity != .none; - settings.iflag.IXON = config.handshake == .software; - settings.iflag.IXOFF = config.handshake == .software; + settings.iflag.IXON = config.flow_control == .software; + settings.iflag.IXOFF = config.flow_control == .software; settings.cflag = @bitCast(@intFromEnum(config.baud_rate)); settings.cflag.CREAD = true; settings.cflag.CSTOPB = config.stop_bits == .two; - settings.cflag.CSIZE = @enumFromInt(@intFromEnum(config.word_size)); - if (config.handshake == .hardware) { + settings.cflag.CSIZE = @enumFromInt(@intFromEnum(config.data_bits)); + if (config.flow_control == .hardware) { settings.cflag.CCTS_OFLOW = true; settings.cflag.CRTS_IFLOW = true; } else { diff --git a/src/backend/windows.zig b/src/backend/windows.zig index 23c3260..6973ca4 100644 --- a/src/backend/windows.zig +++ b/src/backend/windows.zig @@ -84,18 +84,20 @@ pub fn configure(port: *const PortImpl, config: serialport.Config) !void { var dcb: DCB = std.mem.zeroes(DCB); dcb.DCBlength = @sizeOf(DCB); + if (config.input_baud_rate != null) return error.InputBaudRateUnsupported; + if (GetCommState(port.file.handle, &dcb) == 0) return windows.unexpectedError(windows.GetLastError()); dcb.BaudRate = config.baud_rate; dcb.flags = .{ .Parity = config.parity != .none, - .OutxCtsFlow = config.handshake == .hardware, - .OutX = config.handshake == .software, - .InX = config.handshake == .software, - .RtsControl = config.handshake == .hardware, + .OutxCtsFlow = config.flow_control == .hardware, + .OutX = config.flow_control == .software, + .InX = config.flow_control == .software, + .RtsControl = config.flow_control == .hardware, }; - dcb.ByteSize = 5 + @as(windows.BYTE, @intFromEnum(config.word_size)); + dcb.ByteSize = 5 + @as(windows.BYTE, @intFromEnum(config.data_bits)); dcb.Parity = @intFromEnum(config.parity); dcb.StopBits = if (config.stop_bits == .two) 2 else 0; dcb.XonChar = 0x11; diff --git a/src/serialport.zig b/src/serialport.zig index a1117f3..7b691ca 100644 --- a/src/serialport.zig +++ b/src/serialport.zig @@ -113,11 +113,21 @@ pub const Port = struct { }; pub const Config = struct { + /// Baud rate. Used as both output and input baud rate, unless an input + /// baud is separately provided. baud_rate: BaudRate, + /// Input-specific baud rate. Use only when a custom input baud rate + /// different than the output baud rate must be specified. + input_baud_rate: ?BaudRate = null, + /// Per-character parity bit use. Data bits must be less than eight to use + /// parity bit (eighth bit is used as parity bit). parity: Parity = .none, + /// Number of bits used to signal end of character. Appended after all data + /// and parity bits. stop_bits: StopBits = .one, - word_size: WordSize = .eight, - handshake: Handshake = .none, + /// Number of data bits to use per character. + data_bits: DataBits = .eight, + flow_control: FlowControl = .none, pub const BaudRate = if (@hasDecl(backend, "BaudRate")) backend.BaudRate @@ -127,42 +137,43 @@ pub const Config = struct { @compileError("unsupported backend/OS"); pub const Parity = enum(u3) { - /// No parity bit is used. + /// Do not create or check for parity bit per character. none, - /// Parity bit is `0` when an odd number of bits is set in the data. + /// Parity bit set to `0` when data has odd number of `1` bits. odd, - /// Parity bit is `0` when an even number of bits is set in the data. + /// Parity bit set to `0` when data has even number of `1` bits. even, - /// Parity bit is always `1`. + /// Parity bit always set to `1`. mark, - /// Parity bit is always `0`. + /// Parity bit always set to `0`. A.k.a. bit filling. space, }; pub const StopBits = enum(u1) { - /// Length of stop bits is one bit. + /// One bit to signal end of character. one, - /// Length of stop bits is two bits. + /// Two bits to signal end of character. two, }; - pub const WordSize = enum(u2) { - /// There are five data bits per word. + pub const DataBits = enum(u2) { + /// Five data bits per character. five, - /// There are six data bits per word. + /// Six data bits per character. six, - /// There are seven data bits per word. + /// Seven data bits per character. seven, - /// There are eight data bits per word. + /// Eight data bits per character. eight, }; - pub const Handshake = enum(u2) { - /// No handshake is used. + pub const FlowControl = enum(u2) { + /// No flow control is used. none, - /// XON-XOFF software handshake is used. + /// XON-XOFF software flow control is used. software, - /// Hardware handshake with RTS (RFR) / CTS is used. + /// Hardware flow control with RTS (RFR) / CTS is used. A.k.a. hardware + /// handshaking, pacing. hardware, }; };