Skip to content

Commit

Permalink
Move time types (Absolute, Duration, Deadline) from rp2xxx HAL into m…
Browse files Browse the repository at this point in the history
…df (#360)
  • Loading branch information
Grazfather authored Jan 24, 2025
1 parent 8ae6b47 commit f1dfca4
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 156 deletions.
124 changes: 124 additions & 0 deletions drivers/framework.zig
Original file line number Diff line number Diff line change
@@ -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");
Expand Down Expand Up @@ -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");
Expand All @@ -59,6 +181,8 @@ test {

_ = IO_expander.pcf8574;

_ = time;

_ = base.Datagram_Device;
_ = base.Stream_Device;
_ = base.Digital_IO;
Expand Down
3 changes: 2 additions & 1 deletion examples/raspberrypi/rp2xxx/src/rp2040_only/i2c_bus_scan.zig
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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});
}
Expand Down
2 changes: 1 addition & 1 deletion examples/raspberrypi/rp2xxx/src/rp2040_only/uart_echo.zig
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
1 change: 1 addition & 0 deletions port/raspberrypi/rp2xxx/src/hal/drivers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
33 changes: 17 additions & 16 deletions port/raspberrypi/rp2xxx/src/hal/i2c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand All @@ -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();
Expand All @@ -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);
}

Expand All @@ -380,15 +381,15 @@ 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;

const write_vec = microzig.utilities.Slice_Vector([]const u8).init(chunks);
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();
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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);
}

Expand All @@ -459,15 +460,15 @@ 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;

const read_vec = microzig.utilities.Slice_Vector([]u8).init(chunks);
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();
Expand All @@ -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;
}
Expand All @@ -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);
}

Expand All @@ -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;

Expand All @@ -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();
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
Loading

0 comments on commit f1dfca4

Please sign in to comment.