From f1dfca488a285e7f8c3527c1f306f3772c489f8d Mon Sep 17 00:00:00 2001 From: Grazfather Date: Fri, 24 Jan 2025 11:30:55 -0500 Subject: [PATCH] Move time types (Absolute, Duration, Deadline) from rp2xxx HAL into mdf (#360) --- drivers/framework.zig | 124 ++++++++++++++++ .../rp2xxx/src/rp2040_only/i2c_bus_scan.zig | 3 +- .../rp2xxx/src/rp2040_only/uart_echo.zig | 2 +- port/raspberrypi/rp2xxx/src/hal/drivers.zig | 1 + port/raspberrypi/rp2xxx/src/hal/i2c.zig | 33 ++--- port/raspberrypi/rp2xxx/src/hal/time.zig | 133 +----------------- port/raspberrypi/rp2xxx/src/hal/uart.zig | 21 +-- 7 files changed, 161 insertions(+), 156 deletions(-) diff --git a/drivers/framework.zig b/drivers/framework.zig index 391546715..8c679ae9d 100644 --- a/drivers/framework.zig +++ b/drivers/framework.zig @@ -1,6 +1,7 @@ //! //! The driver framework provides device-independent drivers for peripherials supported by MicroZig. //! +const std = @import("std"); pub const display = struct { pub const ssd1306 = @import("display/ssd1306.zig"); @@ -42,6 +43,127 @@ pub const wireless = struct { // pub const sx1278 = @import("wireless/sx1278.zig"); }; +pub const time = struct { + /// + /// An absolute point in time since the startup of the device. + /// + /// NOTE: Using an enum to make it a distinct type, the underlying number is + /// time since boot in microseconds. + /// + pub const Absolute = enum(u64) { + _, + + pub fn from_us(us: u64) Absolute { + return @as(Absolute, @enumFromInt(us)); + } + + pub fn to_us(abs: Absolute) u64 { + return @intFromEnum(abs); + } + + pub fn is_reached_by(deadline: Absolute, point: Absolute) bool { + return deadline.to_us() <= point.to_us(); + } + + pub fn diff(future: Absolute, past: Absolute) Duration { + return Duration.from_us(future.to_us() - past.to_us()); + } + + pub fn add_duration(abs: Absolute, dur: Duration) Absolute { + return Absolute.from_us(abs.to_us() + dur.to_us()); + } + }; + + /// + /// A duration with microsecond precision. + /// + /// NOTE: Using an `enum` type here prevents type confusion with other + /// related or unrelated integer-like types. + /// + pub const Duration = enum(u64) { + _, + + pub fn from_us(us: u64) Duration { + return @as(Duration, @enumFromInt(us)); + } + + pub fn from_ms(ms: u64) Duration { + return from_us(1000 * ms); + } + + pub fn to_us(duration: Duration) u64 { + return @intFromEnum(duration); + } + + pub fn less_than(self: Duration, other: Duration) bool { + return self.to_us() < other.to_us(); + } + + pub fn minus(self: Duration, other: Duration) Duration { + return from_us(self.to_us() - other.to_us()); + } + + pub fn plus(self: Duration, other: Duration) Duration { + return from_us(self.to_us() + other.to_us()); + } + }; + + /// + /// The deadline construct is a construct to create optional timeouts. + /// + /// NOTE: Deadlines use maximum possible `Absolute` time for + /// marking the deadline as "unreachable", as this would mean the device + /// would ever reach an uptime of over 500.000 years. + /// + pub const Deadline = struct { + const disabled_sentinel = Absolute.from_us(std.math.maxInt(u64)); + + timeout: Absolute, + + /// Create a new deadline with an absolute end point. + /// + /// NOTE: `abs` must not point to the absolute maximum time stamp, as this is + /// used as a sentinel for "unset" deadlines. + pub fn init_absolute(abs: ?Absolute) Deadline { + if (abs) |a| + std.debug.assert(a != disabled_sentinel); + return .{ .timeout = abs orelse disabled_sentinel }; + } + + /// Create a new deadline with a certain duration from provided time. + pub fn init_relative(since: Absolute, dur: ?Duration) Deadline { + return init_absolute(if (dur) |d| + make_timeout(since, d) + else + null); + } + + /// Returns `true` if the deadline can be reached. + pub fn can_be_reached(deadline: Deadline) bool { + return (deadline.timeout != disabled_sentinel); + } + + /// Returns `true` if the deadline is reached. + pub fn is_reached_by(deadline: Deadline, now: Absolute) bool { + return deadline.can_be_reached() and deadline.timeout.is_reached_by(now); + } + + /// Checks if the deadline is reached and returns an error if so, + pub fn check(deadline: Deadline, now: Absolute) error{Timeout}!void { + if (deadline.is_reached_by(now)) + return error.Timeout; + } + }; + + pub fn make_timeout(since: Absolute, timeout: Duration) Absolute { + return @as(Absolute, @enumFromInt(since.to_us() + timeout.to_us())); + } + + pub fn make_timeout_us(since: Absolute, timeout_us: u64) Absolute { + return @as(Absolute, @enumFromInt(since.to_us() + timeout_us)); + } +}; + pub const base = struct { pub const Datagram_Device = @import("base/Datagram_Device.zig"); pub const Stream_Device = @import("base/Stream_Device.zig"); @@ -59,6 +181,8 @@ test { _ = IO_expander.pcf8574; + _ = time; + _ = base.Datagram_Device; _ = base.Stream_Device; _ = base.Digital_IO; diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/i2c_bus_scan.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/i2c_bus_scan.zig index ba9af3283..d95793a52 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/i2c_bus_scan.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/i2c_bus_scan.zig @@ -1,5 +1,6 @@ const std = @import("std"); const microzig = @import("microzig"); +const time = microzig.drivers.time; const rp2xxx = microzig.hal; const i2c = rp2xxx.i2c; @@ -54,7 +55,7 @@ pub fn main() !void { if (a.is_reserved()) continue; var rx_data: [1]u8 = undefined; - _ = i2c0.read_blocking(a, &rx_data, rp2xxx.time.Duration.from_ms(100)) catch continue; + _ = i2c0.read_blocking(a, &rx_data, time.Duration.from_ms(100)) catch continue; std.log.info("I2C device found at address {X}.", .{addr}); } diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/uart_echo.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/uart_echo.zig index 9513dabc4..b0a45b442 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/uart_echo.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/uart_echo.zig @@ -1,10 +1,10 @@ const std = @import("std"); const microzig = @import("microzig"); +const time = microzig.drivers.time; const rp2040 = microzig.hal; const uart = rp2040.uart; const gpio = rp2040.gpio; -const time = rp2040.time; const clocks = rp2040.clocks; const echo_uart = uart.instance.num(0); diff --git a/port/raspberrypi/rp2xxx/src/hal/drivers.zig b/port/raspberrypi/rp2xxx/src/hal/drivers.zig index d47a83eb6..0c0261600 100644 --- a/port/raspberrypi/rp2xxx/src/hal/drivers.zig +++ b/port/raspberrypi/rp2xxx/src/hal/drivers.zig @@ -7,6 +7,7 @@ const microzig = @import("microzig"); const hal = @import("../hal.zig"); const drivers = microzig.drivers.base; +const time = microzig.drivers.time; const Datagram_Device = drivers.Datagram_Device; const Stream_Device = drivers.Stream_Device; diff --git a/port/raspberrypi/rp2xxx/src/hal/i2c.zig b/port/raspberrypi/rp2xxx/src/hal/i2c.zig index e67d22401..b2a67b805 100644 --- a/port/raspberrypi/rp2xxx/src/hal/i2c.zig +++ b/port/raspberrypi/rp2xxx/src/hal/i2c.zig @@ -6,6 +6,7 @@ //! const std = @import("std"); const microzig = @import("microzig"); +const mdf = microzig.drivers; const peripherals = microzig.chip.peripherals; const I2C0 = peripherals.I2C0; const I2C1 = peripherals.I2C1; @@ -345,7 +346,7 @@ pub const I2C = enum(u1) { /// returning. The one exception is if timeout is hit, then return, /// potentially still leaving the I2C block in an "active" state. /// However, this avoids an infinite loop. - fn ensure_stop_condition(i2c: I2C, deadline: time.Deadline) void { + fn ensure_stop_condition(i2c: I2C, deadline: mdf.time.Deadline) void { const regs = i2c.get_regs(); // From pico-sdk: @@ -355,7 +356,7 @@ pub const I2C = enum(u1) { // As far as I can tell from the datasheet, no, this is not possible. while (regs.IC_RAW_INTR_STAT.read().STOP_DET == .INACTIVE) { hw.tight_loop_contents(); - if (deadline.is_reached()) + if (deadline.is_reached_by(time.get_time_since_boot())) break; } _ = regs.IC_CLR_STOP_DET.read(); @@ -366,7 +367,7 @@ pub const I2C = enum(u1) { /// - An error occurs and the transaction is aborted /// - The transaction times out (a null for timeout blocks indefinitely) /// - pub fn write_blocking(i2c: I2C, addr: Address, data: []const u8, timeout: ?time.Duration) TransactionError!void { + pub fn write_blocking(i2c: I2C, addr: Address, data: []const u8, timeout: ?mdf.time.Duration) TransactionError!void { return i2c.writev_blocking(addr, &.{data}, timeout); } @@ -380,7 +381,7 @@ pub const I2C = enum(u1) { /// suffixes won't need to be concatenated/inserted to the original buffer, but can be managed /// in a separate memory. /// - pub fn writev_blocking(i2c: I2C, addr: Address, chunks: []const []const u8, timeout: ?time.Duration) TransactionError!void { + pub fn writev_blocking(i2c: I2C, addr: Address, chunks: []const []const u8, timeout: ?mdf.time.Duration) TransactionError!void { if (addr.is_reserved()) return TransactionError.TargetAddressReserved; @@ -388,7 +389,7 @@ pub const I2C = enum(u1) { if (write_vec.size() == 0) return TransactionError.NoData; - var deadline = time.Deadline.init_relative(timeout); + var deadline = mdf.time.Deadline.init_relative(time.get_time_since_boot(), timeout); i2c.set_address(addr); const regs = i2c.get_regs(); @@ -414,7 +415,7 @@ pub const I2C = enum(u1) { // Note that this WILL loop infinitely if called when I2C is uninitialized and no // timeout is supplied! while (i2c.tx_fifo_available_spaces() == 0) { - if (deadline.is_reached()) { + if (deadline.is_reached_by(time.get_time_since_boot())) { timed_out = true; break; } @@ -428,7 +429,7 @@ pub const I2C = enum(u1) { // Waits until everything in the TX FIFO is either successfully transmitted, or flushed // due to an abort. This functions because of TX_EMPTY_CTRL being enabled in apply(). while (regs.IC_RAW_INTR_STAT.read().TX_EMPTY == .INACTIVE) { - if (deadline.is_reached()) { + if (deadline.is_reached_by(time.get_time_since_boot())) { timed_out = true; break; } @@ -445,7 +446,7 @@ pub const I2C = enum(u1) { /// - An error occurs and the transaction is aborted /// - The transaction times out (a null for timeout blocks indefinitely) /// - pub fn read_blocking(i2c: I2C, addr: Address, dst: []u8, timeout: ?time.Duration) TransactionError!void { + pub fn read_blocking(i2c: I2C, addr: Address, dst: []u8, timeout: ?mdf.time.Duration) TransactionError!void { return try i2c.readv_blocking(addr, &.{dst}, timeout); } @@ -459,7 +460,7 @@ pub const I2C = enum(u1) { /// suffixes won't need to be concatenated/inserted to the original buffer, but can be managed /// in a separate memory. /// - pub fn readv_blocking(i2c: I2C, addr: Address, chunks: []const []u8, timeout: ?time.Duration) TransactionError!void { + pub fn readv_blocking(i2c: I2C, addr: Address, chunks: []const []u8, timeout: ?mdf.time.Duration) TransactionError!void { if (addr.is_reserved()) return TransactionError.TargetAddressReserved; @@ -467,7 +468,7 @@ pub const I2C = enum(u1) { if (read_vec.size() == 0) return TransactionError.NoData; - const deadline = time.Deadline.init_relative(timeout); + const deadline = mdf.time.Deadline.init_relative(time.get_time_since_boot(), timeout); i2c.set_address(addr); const regs = i2c.get_regs(); @@ -490,7 +491,7 @@ pub const I2C = enum(u1) { while (true) { try i2c.check_and_clear_abort(); - if (deadline.is_reached()) { + if (deadline.is_reached_by(time.get_time_since_boot())) { timed_out = true; break; } @@ -511,7 +512,7 @@ pub const I2C = enum(u1) { /// - The transaction times out (a null for timeout blocks indefinitely) /// /// This is useful for the common scenario of writing an address to a target device, and then immediately reading bytes from that address - pub fn write_then_read_blocking(i2c: I2C, addr: Address, src: []const u8, dst: []u8, timeout: ?time.Duration) TransactionError!void { + pub fn write_then_read_blocking(i2c: I2C, addr: Address, src: []const u8, dst: []u8, timeout: ?mdf.time.Duration) TransactionError!void { return i2c.writev_then_readv_blocking(addr, &.{src}, &.{dst}, timeout); } @@ -528,7 +529,7 @@ pub const I2C = enum(u1) { /// suffixes won't need to be concatenated/inserted to the original buffer, but can be managed /// in a separate memory. /// - pub fn writev_then_readv_blocking(i2c: I2C, addr: Address, write_chunks: []const []const u8, read_chunks: []const []u8, timeout: ?time.Duration) TransactionError!void { + pub fn writev_then_readv_blocking(i2c: I2C, addr: Address, write_chunks: []const []const u8, read_chunks: []const []u8, timeout: ?mdf.time.Duration) TransactionError!void { if (addr.is_reserved()) return TransactionError.TargetAddressReserved; @@ -538,7 +539,7 @@ pub const I2C = enum(u1) { if (write_vec.size() == 0) return TransactionError.NoData; - const deadline = time.Deadline.init_relative(timeout); + const deadline = mdf.time.Deadline.init_relative(timeout); i2c.set_address(addr); const regs = i2c.get_regs(); @@ -566,7 +567,7 @@ pub const I2C = enum(u1) { // timeout is supplied! while (i2c.tx_fifo_available_spaces() == 0) { hw.tight_loop_contents(); - if (deadline.is_reached()) { + if (deadline.is_reached_by(time.get_time_since_boot())) { timed_out = true; break; } @@ -594,7 +595,7 @@ pub const I2C = enum(u1) { while (true) { try i2c.check_and_clear_abort(); - if (deadline.is_reached()) { + if (deadline.is_reached_by(time.get_time_since_boot())) { timed_out = true; break :recv_loop; } diff --git a/port/raspberrypi/rp2xxx/src/hal/time.zig b/port/raspberrypi/rp2xxx/src/hal/time.zig index 3703ca74b..128a30367 100644 --- a/port/raspberrypi/rp2xxx/src/hal/time.zig +++ b/port/raspberrypi/rp2xxx/src/hal/time.zig @@ -1,5 +1,6 @@ const std = @import("std"); const microzig = @import("microzig"); +const time = microzig.drivers.time; const chip = @import("compatibility.zig").chip; const TIMER = @field( @@ -10,148 +11,24 @@ const TIMER = @field( }, ); -/// -/// An absolute point in time since the startup of the device. -/// -/// NOTE: Using an enum to make it a distinct type, the underlying number is -/// time since boot in microseconds. -/// -pub const Absolute = enum(u64) { - _, - - pub fn from_us(us: u64) Absolute { - return @as(Absolute, @enumFromInt(us)); - } - - pub fn to_us(time: Absolute) u64 { - return @intFromEnum(time); - } - - pub fn is_reached_by(deadline: Absolute, point: Absolute) bool { - return deadline.to_us() <= point.to_us(); - } - - pub fn is_reached(time: Absolute) bool { - const now = get_time_since_boot(); - return time.is_reached_by(now); - } - - pub fn diff(future: Absolute, past: Absolute) Duration { - return Duration.from_us(future.to_us() - past.to_us()); - } - - pub fn add_duration(time: Absolute, dur: Duration) Absolute { - return Absolute.from_us(time.to_us() + dur.to_us()); - } -}; - -/// -/// A duration with microsecond precision. -/// -/// NOTE: Using an `enum` type here prevents type confusion with other -/// related or unrelated integer-like types. -/// -pub const Duration = enum(u64) { - _, - - pub fn from_us(us: u64) Duration { - return @as(Duration, @enumFromInt(us)); - } - - pub fn from_ms(ms: u64) Duration { - return from_us(1000 * ms); - } - - pub fn to_us(duration: Duration) u64 { - return @intFromEnum(duration); - } - - pub fn less_than(self: Duration, other: Duration) bool { - return self.to_us() < other.to_us(); - } - - pub fn minus(self: Duration, other: Duration) Duration { - return from_us(self.to_us() - other.to_us()); - } - - pub fn plus(self: Duration, other: Duration) Duration { - return from_us(self.to_us() + other.to_us()); - } -}; - -/// -/// The deadline construct is a construct to create optional timeouts. -/// -/// NOTE: Deadlines use maximum possible `Absolute` time for -/// marking the deadline as "unreachable", as this would mean the device -/// would ever reach an uptime of over 500.000 years. -/// -pub const Deadline = struct { - const disabled_sentinel = Absolute.from_us(std.math.maxInt(u64)); - - timeout: Absolute, - - /// Create a new deadline with an absolute end point. - /// - /// NOTE: `abs` must not point to the absolute maximum time stamp, as this is - /// used as a sentinel for "unset" deadlines. - pub fn init_absolute(abs: ?Absolute) Deadline { - if (abs) |a| - std.debug.assert(a != disabled_sentinel); - return .{ .timeout = abs orelse disabled_sentinel }; - } - - /// Create a new deadline with a certain duration from now on. - pub fn init_relative(dur: ?Duration) Deadline { - return init_absolute(if (dur) |d| - make_timeout(d) - else - null); - } - - /// Returns `true` if the deadline can be reached. - pub fn can_be_reached(deadline: Deadline) bool { - return (deadline.timeout != disabled_sentinel); - } - - /// Returns `true` if the deadline is reached. - pub fn is_reached(deadline: Deadline) bool { - return deadline.can_be_reached() and deadline.timeout.is_reached(); - } - - /// Checks if the deadline is reached and returns an error if so, - pub fn check(deadline: Deadline) error{Timeout}!void { - if (deadline.is_reached()) - return error.Timeout; - } -}; - -pub fn get_time_since_boot() Absolute { +pub fn get_time_since_boot() time.Absolute { var high_word = TIMER.TIMERAWH.read().TIMERAWH; return while (true) { const low_word = TIMER.TIMERAWL.read().TIMERAWL; const next_high_word = TIMER.TIMERAWH.read().TIMERAWH; if (next_high_word == high_word) - break @as(Absolute, @enumFromInt(@as(u64, @intCast(high_word)) << 32 | low_word)); + break @as(time.Absolute, @enumFromInt(@as(u64, @intCast(high_word)) << 32 | low_word)); high_word = next_high_word; } else unreachable; } -pub fn make_timeout(timeout: Duration) Absolute { - return @as(Absolute, @enumFromInt(get_time_since_boot().to_us() + timeout.to_us())); -} - -pub fn make_timeout_us(timeout_us: u64) Absolute { - return @as(Absolute, @enumFromInt(get_time_since_boot().to_us() + timeout_us)); -} - pub fn sleep_ms(time_ms: u32) void { sleep_us(time_ms * 1000); } pub fn sleep_us(time_us: u64) void { - const end_time = make_timeout_us(time_us); - while (!end_time.is_reached()) {} + const end_time = time.make_timeout_us(get_time_since_boot(), time_us); + while (!end_time.is_reached_by(get_time_since_boot())) {} } diff --git a/port/raspberrypi/rp2xxx/src/hal/uart.zig b/port/raspberrypi/rp2xxx/src/hal/uart.zig index 3edf8ff98..2bf05f01c 100644 --- a/port/raspberrypi/rp2xxx/src/hal/uart.zig +++ b/port/raspberrypi/rp2xxx/src/hal/uart.zig @@ -1,5 +1,6 @@ const std = @import("std"); const microzig = @import("microzig"); +const mdf = microzig.drivers; const peripherals = microzig.chip.peripherals; const UART0_reg = peripherals.UART0; const UART1_reg = peripherals.UART1; @@ -230,7 +231,7 @@ pub const UART = enum(u1) { /// /// Note that this does NOT disable reception while this is happening, /// so if this takes too long the RX FIFO can potentially overflow. - pub fn write_blocking(uart: UART, payload: []const u8, timeout: ?time.Duration) TransmitError!void { + pub fn write_blocking(uart: UART, payload: []const u8, timeout: ?mdf.time.Duration) TransmitError!void { return try uart.writev_blocking(&.{payload}, timeout); } @@ -243,16 +244,16 @@ pub const UART = enum(u1) { /// /// Note that this does NOT disable reception while this is happening, /// so if this takes too long the RX FIFO can potentially overflow. - pub fn writev_blocking(uart: UART, payloads: []const []const u8, timeout: ?time.Duration) TransmitError!void { + pub fn writev_blocking(uart: UART, payloads: []const []const u8, timeout: ?mdf.time.Duration) TransmitError!void { const uart_regs = uart.get_regs(); - const deadline = time.Deadline.init_relative(timeout); + const deadline = mdf.time.Deadline.init_relative(time.get_time_since_boot(), timeout); var iter = microzig.utilities.Slice_Vector([]const u8).init(payloads).iterator(); while (iter.next_chunk(null)) |payload| { var offset: usize = uart.prime_tx_fifo(payload); while (offset < payload.len) { while (!uart.is_writeable()) { - try deadline.check(); + try deadline.check(time.get_time_since_boot()); } uart_regs.UARTDR.write_raw(payload[offset]); offset += 1; @@ -260,7 +261,7 @@ pub const UART = enum(u1) { } while (uart.is_busy()) { - try deadline.check(); + try deadline.check(time.get_time_since_boot()); } } @@ -324,7 +325,7 @@ pub const UART = enum(u1) { /// Returns a transaction error immediately if it occurs and doesn't /// complete the transaction. Errors are preserved for further inspection, /// so must be cleared with clear_errors() before another transaction is attempted. - pub fn read_blocking(uart: UART, buffer: []u8, timeout: ?time.Duration) ReceiveError!void { + pub fn read_blocking(uart: UART, buffer: []u8, timeout: ?mdf.time.Duration) ReceiveError!void { return uart.readv_blocking(&.{buffer}, timeout); } @@ -338,14 +339,14 @@ pub const UART = enum(u1) { /// Returns a transaction error immediately if it occurs and doesn't /// complete the transaction. Errors are preserved for further inspection, /// so must be cleared with clear_errors() before another transaction is attempted. - pub fn readv_blocking(uart: UART, buffers: []const []u8, timeout: ?time.Duration) ReceiveError!void { - const deadline = time.Deadline.init_relative(timeout); + pub fn readv_blocking(uart: UART, buffers: []const []u8, timeout: ?mdf.time.Duration) ReceiveError!void { + const deadline = mdf.time.Deadline.init_relative(time.get_time_since_boot(), timeout); var iter = microzig.utilities.Slice_Vector([]u8).init(buffers).iterator(); while (iter.next_chunk(null)) |buffer| { for (buffer) |*byte| { while (!uart.is_readable()) { - try deadline.check(); + try deadline.check(time.get_time_since_boot()); } byte.* = try uart.read_rx_fifo_with_error_check(); } @@ -353,7 +354,7 @@ pub const UART = enum(u1) { } /// Convenience function for waiting for a single byte to come across the RX line. - pub fn read_word(uart: UART, timeout: ?time.Duration) ReceiveError!u8 { + pub fn read_word(uart: UART, timeout: ?mdf.time.Duration) ReceiveError!u8 { var byte: [1]u8 = undefined; try uart.read_blocking(&byte, timeout); return byte[0];