|
| 1 | +const std = @import("std"); |
| 2 | +const microzig = @import("microzig"); |
| 3 | +const mdf = microzig.drivers; |
| 4 | +const peripherals = microzig.chip.peripherals; |
| 5 | + |
| 6 | +const I2C0 = peripherals.I2C; |
| 7 | +const RESET = microzig.chip.peripherals.RESET; |
| 8 | + |
| 9 | +pub const Config = struct { |
| 10 | + baud: u8, |
| 11 | +}; |
| 12 | + |
| 13 | +/// |
| 14 | +/// 7-bit I²C address, without the read/write bit. |
| 15 | +/// |
| 16 | +pub const Address = enum(u7) { |
| 17 | + /// The general call addresses all devices on the bus using the I²C address 0. |
| 18 | + pub const general_call: Address = @enumFromInt(0x00); |
| 19 | + |
| 20 | + _, |
| 21 | + |
| 22 | + pub fn new(addr: u7) Address { |
| 23 | + const a = @as(Address, @enumFromInt(addr)); |
| 24 | + // std.debug.assert(!a.is_reserved()); |
| 25 | + return a; |
| 26 | + } |
| 27 | + |
| 28 | + /// |
| 29 | + /// Returns `true` if the Address is a reserved I²C address. |
| 30 | + /// |
| 31 | + /// Reserved addresses are ones that match `0b0000XXX` or `0b1111XXX`. |
| 32 | + /// |
| 33 | + /// See more here: https://www.i2c-bus.org/addressing/ |
| 34 | + pub fn is_reserved(addr: Address) bool { |
| 35 | + const value: u7 = @intFromEnum(addr); |
| 36 | + return ((value & 0x78) == 0) or ((value & 0x78) == 0x78); |
| 37 | + } |
| 38 | + |
| 39 | + pub fn format(addr: Address, fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { |
| 40 | + _ = fmt; |
| 41 | + _ = options; |
| 42 | + try writer.print("I2C(0x{X:0>2})", .{@intFromEnum(addr)}); |
| 43 | + } |
| 44 | +}; |
| 45 | + |
| 46 | +const i2c = @This(); |
| 47 | + |
| 48 | +pub const instance = struct { |
| 49 | + pub const I2C: i2c.I2C = @enumFromInt(0); |
| 50 | + pub fn num(instance_number: u1) i2c.I2C { |
| 51 | + std.debug.assert(instance_number == 0); |
| 52 | + return @enumFromInt(instance_number); |
| 53 | + } |
| 54 | +}; |
| 55 | + |
| 56 | +pub const TransactionError = error{ |
| 57 | + Timeout, |
| 58 | + TargetAddressReserved, |
| 59 | + NoData, |
| 60 | +}; |
| 61 | + |
| 62 | +pub const ConfigError = error{ |
| 63 | + UnsupportedBaudRate, |
| 64 | +}; |
| 65 | + |
| 66 | +pub const I2C = enum(u1) { |
| 67 | + _, |
| 68 | + |
| 69 | + pub fn apply(self: I2C, comptime config: Config) ConfigError!void { |
| 70 | + self.reset(); |
| 71 | + I2C0.TM.write(.{ .TM = config.baud }); |
| 72 | + I2C0.ADDR.write(.{ |
| 73 | + .I2CADR = 0, |
| 74 | + .GC = 0, |
| 75 | + }); |
| 76 | + } |
| 77 | + |
| 78 | + pub inline fn reset(_: I2C) void { |
| 79 | + RESET.PREI_RESET.modify(.{ |
| 80 | + .I2C = 0, |
| 81 | + }); |
| 82 | + RESET.PREI_RESET.modify(.{ |
| 83 | + .I2C = 1, |
| 84 | + }); |
| 85 | + } |
| 86 | + |
| 87 | + pub inline fn enable(_: I2C) void { |
| 88 | + I2C0.TMRUN.write(.{ |
| 89 | + // enable baudrate generation |
| 90 | + .TME = 1, |
| 91 | + }); |
| 92 | + |
| 93 | + I2C0.CR.modify(.{ |
| 94 | + // enable i2c module |
| 95 | + .ENS = 1, |
| 96 | + // enable high speed mode |
| 97 | + .H1M = 1, |
| 98 | + }); |
| 99 | + } |
| 100 | + |
| 101 | + pub inline fn disable(_: I2C) void { |
| 102 | + I2C0.CR.modify(.{ |
| 103 | + .TMRUN = 0, |
| 104 | + .ENS = 0, |
| 105 | + .H1M = 0, |
| 106 | + }); |
| 107 | + } |
| 108 | + |
| 109 | + pub fn writev_blocking(self: I2C, addr: Address, buffers: []const []const u8, timeout: ?mdf.time.Duration) TransactionError!void { |
| 110 | + // TODO: timeouts |
| 111 | + _ = timeout; |
| 112 | + |
| 113 | + const write_vec = microzig.utilities.Slice_Vector([]const u8).init(buffers); |
| 114 | + if (write_vec.size() == 0) |
| 115 | + return TransactionError.NoData; |
| 116 | + |
| 117 | + self.set_start(); |
| 118 | + self.wait_for_irq(); |
| 119 | + |
| 120 | + // send address |
| 121 | + self.wait_for_any_state(&.{ 0x08, 0x10 }); |
| 122 | + self.clear_start(); |
| 123 | + self.write_data(@intFromEnum(addr)); |
| 124 | + self.clear_irq(); |
| 125 | + |
| 126 | + self.wait_for_irq(); |
| 127 | + // @breakpoint(); |
| 128 | + self.wait_for_any_state(&.{ 0x18, 0x28 }); |
| 129 | + |
| 130 | + var iter = write_vec.iterator(); |
| 131 | + while (iter.next_element()) |element| { |
| 132 | + self.write_data(element.value); |
| 133 | + self.clear_irq(); |
| 134 | + |
| 135 | + self.wait_for_irq(); |
| 136 | + self.wait_for_any_state(&.{ 0x18, 0x28 }); |
| 137 | + } |
| 138 | + |
| 139 | + self.set_stop(); |
| 140 | + self.clear_irq(); |
| 141 | + } |
| 142 | + |
| 143 | + pub fn write_blocking(self: I2C, addr: Address, data: []const u8, timeout: ?mdf.time.Duration) TransactionError!void { |
| 144 | + return self.writev_blocking(addr, &.{data}, timeout); |
| 145 | + } |
| 146 | + |
| 147 | + pub fn readv_blocking(self: I2C, addr: Address, buffers: []const []u8, timeout: ?mdf.time.Duration) TransactionError!void { |
| 148 | + // TODO: timeouts |
| 149 | + _ = timeout; |
| 150 | + _ = addr; |
| 151 | + _ = buffers; |
| 152 | + _ = self; |
| 153 | + } |
| 154 | + |
| 155 | + pub fn read_blocking(self: I2C, addr: Address, dst: []u8, timeout: ?mdf.time.Duration) TransactionError!void { |
| 156 | + return try self.readv_blocking(addr, &.{dst}, timeout); |
| 157 | + } |
| 158 | + |
| 159 | + inline fn set_start(_: I2C) void { |
| 160 | + I2C0.CR.modify(.{ .STA = 1 }); |
| 161 | + } |
| 162 | + |
| 163 | + inline fn clear_start(_: I2C) void { |
| 164 | + I2C0.CR.modify(.{ .STA = 0 }); |
| 165 | + } |
| 166 | + |
| 167 | + inline fn set_stop(_: I2C) void { |
| 168 | + I2C0.CR.modify(.{ .STO = 1 }); |
| 169 | + } |
| 170 | + |
| 171 | + inline fn clear_stop(_: I2C) void { |
| 172 | + I2C0.CR.modify(.{ .STO = 0 }); |
| 173 | + } |
| 174 | + |
| 175 | + inline fn set_ack(_: I2C) void { |
| 176 | + I2C0.CR.modify(.{ .AA = 1 }); |
| 177 | + } |
| 178 | + |
| 179 | + inline fn clear_ack(_: I2C) void { |
| 180 | + I2C0.CR.modify(.{ .AA = 0 }); |
| 181 | + } |
| 182 | + |
| 183 | + inline fn write_data(_: I2C, data: u8) void { |
| 184 | + I2C0.DATA.write(.{ .I2CDAT = data }); |
| 185 | + } |
| 186 | + |
| 187 | + fn wait_for_any_state(_: I2C, comptime states: []const u8) void { |
| 188 | + while (true) { |
| 189 | + const state = I2C0.STAT.read().I2CSTA; |
| 190 | + inline for (states) |s| { |
| 191 | + if (s == state) |
| 192 | + return; |
| 193 | + } |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + fn wait_for_irq(self: I2C) void { |
| 198 | + while (!self.is_irq_set()) {} |
| 199 | + } |
| 200 | + |
| 201 | + inline fn is_irq_set(_: I2C) bool { |
| 202 | + return I2C0.CR.read().SI == 1; |
| 203 | + } |
| 204 | + |
| 205 | + inline fn clear_irq(_: I2C) void { |
| 206 | + I2C0.CR.modify(.{ .SI = 0 }); |
| 207 | + } |
| 208 | +}; |
0 commit comments