Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ec2bf02
Initial commit
ikskuh Jul 16, 2023
87d68b2
Initial commit: Keyboard and SSD1306 driver and some basic project st…
Jul 16, 2023
8a2f832
Introduces StreamDevice and DatagramDevice for the framework as a ref…
Jul 16, 2023
e2f3d28
Introduces DigitalIO as a base driver and ports KeyboardMatrix to it.
Jul 16, 2023
46265ac
Starts implementing the ST77xx driver
Jul 17, 2023
f8139a0
Adjusts to ZEG coding guidelines
Jul 17, 2023
1f606a9
Updates README.md
Jul 17, 2023
50f97ff
Adds some minor fixes.
Jul 19, 2023
fb73c5e
Update to 0.13.0
Sep 25, 2024
6786a26
Updates DigitalIO to make .read() may error (think: port expanders!),…
Sep 29, 2024
9dd9164
Adds a framebuffer type for the SSD1306
Oct 1, 2024
55308cd
Fixes some bugs in the SSD1306 driver, enables quicker image transfer.
Oct 1, 2024
4b935ce
Removes pre-merge cruft
Oct 7, 2024
2d6ef3f
Merges the MicroZig Driver Framework into MicroZig
Oct 7, 2024
c578eff
Adds missing build.zig
Oct 7, 2024
31c7fe8
Applies style guides more thoroughly
Oct 8, 2024
089a4be
Deletes empty driver files
Oct 8, 2024
6b65e8d
Refactors Keyboard_Matrix
Oct 8, 2024
47a33ae
Refactors Debounced_Button
Oct 8, 2024
13cbfd9
Refactors Rotary_Encoder
Oct 8, 2024
7c5bc9c
Renames ./driver to ./drivers, adds the drivers package to '@import(m…
Oct 8, 2024
7a1decf
Adds README to ./drivers
Oct 8, 2024
2bc0079
Adds missing build.zig.zon to driver framework.
Oct 8, 2024
a40b90f
Renames project to work around bug in boxzer
Oct 8, 2024
2222536
Merge branch 'main' into work/driver_framework
Oct 12, 2024
a4ea592
Ports SSD1306 and Datagram_Device to use/provide writev/readv functions
Oct 12, 2024
601d760
Prepares SSD1306 to be used with 4-wire SPI
Oct 12, 2024
d7284a4
Updates Stream_Device to provide writev/readv
Oct 12, 2024
ec68953
Adds a unit test for the Datagram_Device.Test_Device
Oct 12, 2024
031235a
Implements Stream_Device.Test_Device with a unit test to ensure prope…
Oct 12, 2024
2b686e3
Enables SSD1306 with 4-wire SPI mode.
Oct 12, 2024
621d8a6
Starts to implement device drivers for RP2 HAL
Oct 12, 2024
27cece2
Adds rp2.hal.GPIO_Device as a Digital_IO driver
Oct 12, 2024
6e7b86a
Introduces the SSD1306 dynamic mode support, which can serve all othe…
Oct 12, 2024
f12a0b2
Drops accidential file clone
Oct 12, 2024
520171f
Merge remote-tracking branch 'origin/main' into work/driver_framework
Nov 14, 2024
bc2e430
Attempts to fix boxzer CI failure
Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
zig-out/
zig-cache/
.zig-cache/
microzig-deploy/
__pycache__/
.direnv/
.DS_Store
.gdbinit
.lldbinit
.direnv/
__pycache__/
.venv
.zig-cache/
boxzer-out
microzig-deploy/
zig-cache/
zig-out/
180 changes: 180 additions & 0 deletions driver/base/DatagramDevice.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//!
//! An abstract datagram orientied device with runtime dispatch.
//!
//! Datagram devices behave similar to an SPI or Ethernet device where
//! packets with an ahead-of-time known length can be transferred in a
//! single transaction.
//!

const std = @import("std");

const DatagramDevice = @This();

const BaseError = error{ IoError, Timeout };

pub const ConnectError = BaseError || error{DeviceBusy};

/// Establishes a connection to the device (like activating a chip-select lane or similar).
/// NOTE: Call `.disconnect()` when the usage of the device is done to release it.
pub fn connect(dd: DatagramDevice) ConnectError!void {
if (dd.vtable.connectFn) |connectFn| {
return connectFn(dd.object);
}
}

/// Releases a device from the connection.
pub fn disconnect(dd: DatagramDevice) void {
if (dd.vtable.disconnectFn) |disconnectFn| {
return disconnectFn(dd.object);
}
}

pub const WriteError = BaseError || error{ Unsupported, NotConnected };

/// Writes a single `datagram` to the device.
pub fn write(dd: DatagramDevice, datagram: []const u8) WriteError!void {
if (dd.vtable.writeFn) |writeFn| {
return writeFn(dd.object, datagram);
} else {
return error.Unsupported;
}
}

pub const ReadError = BaseError || error{ Unsupported, NotConnected };

/// Reads a single `datagram` from the device.
pub fn read(dd: DatagramDevice, datagram: []u8) ReadError!void {
if (dd.vtable.readFn) |readFn| {
return readFn(dd.object, datagram);
} else {
return error.Unsupported;
}
}

object: ?*anyopaque,
Comment thread
ikskuh marked this conversation as resolved.
Outdated
vtable: *const VTable,

pub const VTable = struct {
connectFn: ?*const fn (?*anyopaque) ConnectError!void,
Comment thread
ikskuh marked this conversation as resolved.
Outdated
disconnectFn: ?*const fn (?*anyopaque) void,
writeFn: ?*const fn (?*anyopaque, datagram: []const u8) WriteError!void,
readFn: ?*const fn (?*anyopaque, datagram: []u8) ReadError!void,
};

/// A device implementation that can be used to write unit tests.
pub const TestDevice = struct {
arena: std.heap.ArenaAllocator,
packets: std.ArrayList([]u8),

input_sequence: ?[]const []const u8,
input_sequence_pos: usize,

write_enabled: bool,

connected: bool,

pub fn init_receiver_only() TestDevice {
return init(null, true);
}

pub fn init_sender_only(input: []const []const u8) TestDevice {
return init(input, false);
}

pub fn init(input: ?[]const []const u8, write_enabled: bool) TestDevice {
return TestDevice{
.arena = std.heap.ArenaAllocator.init(std.testing.allocator),
.packets = std.ArrayList([]u8).init(std.testing.allocator),

.input_sequence = input,
.input_sequence_pos = 0,

.write_enabled = write_enabled,

.connected = false,
};
}

pub fn deinit(td: *TestDevice) void {
td.arena.deinit();
td.packets.deinit();
td.* = undefined;
}

pub fn expect_sent(td: TestDevice, expected_datagrams: []const []const u8) !void {
const actual_datagrams = td.packets.items;

try std.testing.expectEqual(expected_datagrams.len, actual_datagrams.len);
for (expected_datagrams, actual_datagrams) |expected, actual| {
try std.testing.expectEqualSlices(u8, expected, actual);
}
}

pub fn datagram_device(td: *TestDevice) DatagramDevice {
return DatagramDevice{
.object = td,
.vtable = &vtable,
};
}

fn connectFn(ctx: ?*anyopaque) ConnectError!void {
Comment thread
ikskuh marked this conversation as resolved.
Outdated
const td: *TestDevice = @ptrCast(@alignCast(ctx.?));
if (td.connected)
return error.DeviceBusy;
td.connected = true;
}

fn disconnectFn(ctx: ?*anyopaque) void {
Comment thread
ikskuh marked this conversation as resolved.
Outdated
const td: *TestDevice = @ptrCast(@alignCast(ctx.?));
if (!td.connected) {
std.log.err("disconnect when test device was not connected!", .{});
}
td.connected = false;
}

fn writeFn(ctx: ?*anyopaque, datagram: []const u8) WriteError!void {
Comment thread
ikskuh marked this conversation as resolved.
Outdated
const td: *TestDevice = @ptrCast(@alignCast(ctx.?));

if (!td.connected) {
return error.NotConnected;
}

if (!td.write_enabled) {
return error.Unsupported;
}

const dg = td.arena.allocator().dupe(u8, datagram) catch return error.IoError;
errdefer td.arena.allocator().free(dg);

td.packets.append(dg) catch return error.IoError;
}

fn readFn(ctx: ?*anyopaque, datagram: []u8) ReadError!void {
Comment thread
ikskuh marked this conversation as resolved.
Outdated
const td: *TestDevice = @ptrCast(@alignCast(ctx.?));

if (!td.connected) {
return error.NotConnected;
}

const inputs = td.input_sequence orelse return error.Unsupported;

if (td.input_sequence_pos >= inputs.len) {
return error.IoError;
}

const packet = inputs[td.input_sequence_pos];
td.input_sequence_pos += 1;

if (packet.len != datagram.len)
return error.IoError;

@memcpy(datagram, packet);
}

const vtable = VTable{
.connectFn = connectFn,
.disconnectFn = disconnectFn,
.writeFn = writeFn,
.readFn = readFn,
};
};
109 changes: 109 additions & 0 deletions driver/base/DigitalIO.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//!
//! An abstract digital input/output pin.
//!
//! Digital I/Os can be used to drive single-wire data
//!

const std = @import("std");

const DigitalIO = @This();
Comment thread
ikskuh marked this conversation as resolved.
Outdated
const BaseError = error{ IoError, Timeout };

object: ?*anyopaque,
vtable: *const VTable,

pub const SetDirError = error{Unsupported};
pub const SetBiasError = error{Unsupported};
pub const WriteError = error{Unsupported};
pub const ReadError = error{Unsupported};

pub const State = enum(u1) {
low = 0,
high = 1,

pub inline fn invert(state: State) State {
return @as(State, @enumFromInt(~@intFromEnum(state)));
}

pub inline fn value(state: State) u1 {
return @intFromEnum(state);
}
};
pub const Direction = enum { input, output };

/// Sets the direction of the pin.
pub fn set_direction(dio: DigitalIO, dir: Direction) SetDirError!void {
return dio.vtable.set_direction_fn(dio.object, dir);
}

/// Sets if the pin has a bias towards either `low` or `high` or no bias at all.
/// Bias is usually implemented with pull-ups and pull-downs.
pub fn set_bias(dio: DigitalIO, bias: ?State) SetBiasError!void {
return dio.vtable.set_bias_fn(dio.object, bias);
}

/// Changes the state of the pin.
pub fn write(dio: DigitalIO, state: State) WriteError!void {
return dio.vtable.write_fn(dio.object, state);
}

/// Reads the state state of the pin.
pub fn read(dio: DigitalIO) ReadError!State {
return dio.vtable.read_fn(dio.object);
}

pub const VTable = struct {
set_direction_fn: *const fn (?*anyopaque, dir: Direction) SetDirError!void,
set_bias_fn: *const fn (?*anyopaque, bias: ?State) SetBiasError!void,
write_fn: *const fn (?*anyopaque, state: State) WriteError!void,
read_fn: *const fn (?*anyopaque) State,
};

pub const TestDevice = struct {
state: State,
dir: Direction,

pub fn init(initial_dir: Direction, initial_state: State) TestDevice {
return TestDevice{
.dir = initial_dir,
.state = initial_state,
};
}

pub fn digital_io(dev: *TestDevice) DigitalIO {
return DigitalIO{
.object = dev,
.vtable = &vtable,
};
}

fn set_direction_fn(ctx: ?*anyopaque, dir: Direction) SetDirError!void {
const dev: *TestDevice = @ptrCast(@alignCast(ctx.?));
dev.dir = dir;
}

fn set_bias_fn(ctx: ?*anyopaque, bias: ?State) SetBiasError!void {
const dev: *TestDevice = @ptrCast(@alignCast(ctx.?));
_ = dev;
_ = bias;
}

fn write_fn(ctx: ?*anyopaque, state: State) WriteError!void {
const dev: *TestDevice = @ptrCast(@alignCast(ctx.?));
if (dev.dir != .output)
return error.Unsupported;
dev.state = state;
}

fn read_fn(ctx: ?*anyopaque) State {
const dev: *TestDevice = @ptrCast(@alignCast(ctx.?));
return dev.state;
}

const vtable = VTable{
.set_direction_fn = set_direction_fn,
.set_bias_fn = set_bias_fn,
.write_fn = write_fn,
.read_fn = read_fn,
};
};
77 changes: 77 additions & 0 deletions driver/base/StreamDevice.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//!
//! An abstract stream orientied device with runtime dispatch.
//!
//! Stream devices behave similar to an UART and can send/receive data
//! in variying lengths without clear boundaries between transmissions.
//!

const std = @import("std");

const StreamDevice = @This();

object: ?*anyopaque,
vtable: *const VTable,

const BaseError = error{ IoError, Timeout };

pub const ConnectError = BaseError || error{DeviceBusy};
pub const WriteError = BaseError || error{ Unsupported, NotConnected };
pub const ReadError = BaseError || error{ Unsupported, NotConnected };

/// Establishes a connection to the device (like activating a chip-select lane or similar).
/// NOTE: Call `.disconnect()` when the usage of the device is done to release it.
pub fn connect(sd: StreamDevice) ConnectError!void {
if (sd.vtable.connectFn) |connectFn| {
return connectFn(sd.object);
}
}

/// Releases a device from the connection.
pub fn disconnect(sd: StreamDevice) void {
if (sd.vtable.disconnectFn) |disconnectFn| {
return disconnectFn(sd.object);
}
}

/// Writes some `bytes` to the device and returns the number of bytes written.
pub fn write(sd: StreamDevice, bytes: []const u8) WriteError!usize {
if (sd.vtable.writeFn) |writeFn| {
return writeFn(sd.object, bytes);
} else {
return error.Unsupported;
}
}

/// Reads some `bytes` to the device and returns the number of bytes read.
pub fn read(sd: StreamDevice, bytes: []u8) ReadError!usize {
if (sd.vtable.readFn) |readFn| {
return readFn(sd.object, bytes);
} else {
return error.Unsupported;
}
}

pub const Reader = std.io.Reader(StreamDevice, ReadError, reader_read);
pub fn reader(sd: StreamDevice) Reader {
return .{ .context = sd };
}

fn reader_read(sd: StreamDevice, buf: []u8) ReadError!usize {
return sd.read(buf);
}

pub const Writer = std.io.Reader(StreamDevice, WriteError, writer_write);
pub fn writer(sd: StreamDevice) Writer {
return .{ .context = sd };
}

fn writer_write(sd: StreamDevice, buf: []const u8) WriteError!usize {
return sd.write(buf);
}

pub const VTable = struct {
connect_fn: ?*const fn (?*anyopaque) ConnectError!void,
disconnect_fn: ?*const fn (?*anyopaque) void,
write_fn: ?*const fn (?*anyopaque, datagram: []const u8) WriteError!usize,
read_fn: ?*const fn (?*anyopaque, datagram: []u8) ReadError!usize,
};
Loading