From c2ba2efd7623796620749ae1d6dca0f0101371b8 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Mon, 10 Apr 2023 17:38:54 -0700 Subject: [PATCH] add i2c native implementation Fixes #1 --- src/display.zig | 49 +++++++ src/i2cnative.zig | 74 +++++++++++ src/linux.zig | 317 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 56 +------- 4 files changed, 447 insertions(+), 49 deletions(-) create mode 100644 src/i2cnative.zig create mode 100644 src/linux.zig diff --git a/src/display.zig b/src/display.zig index d75a436..05e6bb2 100644 --- a/src/display.zig +++ b/src/display.zig @@ -12,3 +12,52 @@ pub const LINES = 8; // Device specifications pub const PAGES = 8; + +// Device ID on this device is hardwired to 0x3c +pub const DEVICE_ID = 0x3c; + +pub fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void { + // Each u8 in pixels is a single bit. We need to pack these bits + for (packed_pixels, 0..) |*b, i| { + const column = i % WIDTH; + const page = i / WIDTH; + + // if (column == 0) std.debug.print("{d}: ", .{page}); + // pixel array will be 8x as "high" as the data array we are sending to + // the device. So the device column above is only our starter + // Display has 8 pages, which is a set of 8 pixels with LSB at top of page + // + // To convert from the pixel array above, we need to: + // 1. convert from device page to a base "row" in the pixel array + const row = page * PAGES; + // 2. We will have 8 rows for each base row + // 3. Multiple each row by the width to get the index of the start of + // the row + // 4. Add our current column index for the final pixel location in + // the pixel array. + // + // Now that we have the proper index in the pixel array, we need to + // convert that into our destination byte. Each index will be a u8, either + // 0xff for on or 0x00 for off. So... + // + // 1. We will take the value and bitwise and with 0x01 so we get one bit + // per source byte + // 2. Shift that bit into the proper position in our destination byte + + b.* = (pixels[(0 + row) * WIDTH + column] & 0x01) << 0 | + (pixels[(1 + row) * WIDTH + column] & 0x01) << 1 | + (pixels[(2 + row) * WIDTH + column] & 0x01) << 2 | + (pixels[(3 + row) * WIDTH + column] & 0x01) << 3 | + (pixels[(4 + row) * WIDTH + column] & 0x01) << 4 | + (pixels[(5 + row) * WIDTH + column] & 0x01) << 5 | + (pixels[(6 + row) * WIDTH + column] & 0x01) << 6 | + (pixels[(7 + row) * WIDTH + column] & 0x01) << 7; + + // std.debug.print("{s}", .{std.fmt.fmtSliceHexLower(&[_]u8{b.*})}); + // if (column == 127) std.debug.print("\n", .{}); + + // Last 2 pages are yellow...16 pixels vertical + // if (page == 6 or page == 7) b.* = 0xff; + // b.* = 0xf0; + } +} diff --git a/src/i2cnative.zig b/src/i2cnative.zig new file mode 100644 index 0000000..b770718 --- /dev/null +++ b/src/i2cnative.zig @@ -0,0 +1,74 @@ +const std = @import("std"); +const display = @import("display.zig"); +const linux = @import("linux.zig"); +const log = std.log.scoped(.i2cnative); +const c = @cImport({ + @cInclude("linux/i2c-dev.h"); +}); + +var pixels_write_command: [display.WIDTH * display.PAGES + 1]u8 = undefined; + +pub fn sendPixels(pixels: []const u8, file: [:0]const u8, device_id: u8) !void { + log.debug( + "sendPixels start. File {s}, Device_id 0x{s}\n", + .{ file, std.fmt.bytesToHex(&[_]u8{device_id}, .lower) }, + ); + + const device_file = try openFile(file, device_id); + defer device_file.close(); + + try initializeDevice(device_file); + + // Do the write + const write_cmds = &[_]u8{ + 0x20, 0x00, // Set memory addressing mode to horizontal + 0x21, 0x00, 0x7F, // Start column 0, end column 127 + 0x22, 0x00, 0x07, // Start page 0, end page 7 + }; + try device_file.writeAll(write_cmds); + display.packPixelsToDeviceFormat(pixels, pixels_write_command[1..]); + log.debug("pixel array length: {d}. After packing: {d}", .{ pixels.len, pixels_write_command.len - 1 }); + + pixels_write_command[0] = 0x40; + try device_file.writeAll(&pixels_write_command); + log.debug("sendPixels end", .{}); +} + +fn openFile(file: [:0]const u8, device_id: u8) !std.fs.File { + const device_file = try std.fs.openFileAbsoluteZ(file, .{ + .mode = .read_write, + }); + errdefer device_file.close(); + try linux.ioctl(device_file.handle, c.I2C_SLAVE, device_id); + return device_file; +} + +fn initializeDevice(file: std.fs.File) !void { + // Send initialization commands to the display + // zig fmt: off + const init_cmds = &[_]u8{ + // 0xAE, // Display off + // oscillator frequency will manage flicker. + // Recommended at 0x80, I found we sometimes need higher + // 0xD5, 0xF0, // Set display clock divide ratio/oscillator frequency + 0xD5, 0x80, // Set display clock divide ratio/oscillator frequency + 0xA8, 0x3F, // Set multiplex ratio. Correct for 128x64 devices + // 0xA8, 0x80, // Set multiplex ratio + 0xD3, 0x00, // Set display offset. Should be 0 normally + 0x40, // Set start line + 0xA0, // Set segment remap + // 0xC8, // Set COM output scan direction (reversed) + 0xC0, // Set COM output scan direction (normal) + 0xDA, 0x12, // Set COM pins hardware configuration. 0x12 for 128x64 + 0x81, 0x7F, // Set contrast + // These next 4 should not be needed + // 0xD9, 0xF1, // Set precharge period + // 0xDB, 0x40, // Set VCOMH deselect level + // 0xA4, // Set entire display on/off + // 0xA6, // Set normal display + 0x8D, 0x14, // Charge pump + 0xAF, // Display on + }; + // zig fmt: on + try file.writeAll(init_cmds); +} diff --git a/src/linux.zig b/src/linux.zig new file mode 100644 index 0000000..9df2486 --- /dev/null +++ b/src/linux.zig @@ -0,0 +1,317 @@ +const std = @import("std"); + +pub fn ioctl(handle: std.os.fd_t, request: u32, arg: usize) !void { + const rc = std.os.linux.ioctl(handle, request, arg); + try throwReturnCodeError(rc); + return; +} + +pub fn throwReturnCodeError(return_code: usize) LinuxGenericError!void { + // zig fmt: off + switch (std.os.errno(return_code)) { + .SUCCESS => return, + .PERM => return LinuxGenericError.OperationNotPermittedError, + .NOENT => return LinuxGenericError.NoSuchFileOrDirectoryError, + .SRCH => return LinuxGenericError.NoSuchProcessError, + .INTR => return LinuxGenericError.InterruptedSystemCallError, + .IO => return LinuxGenericError.IOError, + .NXIO => return LinuxGenericError.NoSuchDeviceOrAddressError, + .@"2BIG" => return LinuxGenericError.ArgumentListTooLongError, + .NOEXEC => return LinuxGenericError.ExecFormatError, + .BADF => return LinuxGenericError.BadFileNumberError, + .CHILD => return LinuxGenericError.NoChildProcessesError, + .AGAIN => return LinuxGenericError.TryAgainError, + .NOMEM => return LinuxGenericError.OutOfMemoryError, + .ACCES => return LinuxGenericError.PermissionDeniedError, + .FAULT => return LinuxGenericError.BadAddressError, + .NOTBLK => return LinuxGenericError.BlockDeviceRequiredError, + .BUSY => return LinuxGenericError.DeviceOrResourceBusyError, + .EXIST => return LinuxGenericError.FileExistsError, + .XDEV => return LinuxGenericError.CrossDeviceLinkError, + .NODEV => return LinuxGenericError.NoSuchDeviceError, + .NOTDIR => return LinuxGenericError.NotADirectoryError, + .ISDIR => return LinuxGenericError.IsADirectoryError, + .INVAL => return LinuxGenericError.InvalidArgumentError, + .NFILE => return LinuxGenericError.FileTableOverflowError, + .MFILE => return LinuxGenericError.TooManyOpenFilesError, + .NOTTY => return LinuxGenericError.NotATypewriterError, + .TXTBSY => return LinuxGenericError.TextFileBusyError, + .FBIG => return LinuxGenericError.FileTooLargeError, + .NOSPC => return LinuxGenericError.NoSpaceLeftOnDeviceError, + .SPIPE => return LinuxGenericError.IllegalSeekError, + .ROFS => return LinuxGenericError.ReadOnlyFilesystemError, + .MLINK => return LinuxGenericError.TooManyLinksError, + .PIPE => return LinuxGenericError.BrokenPipeError, + .DOM => return LinuxGenericError.MathArgumentOutOfDomainOfFunctionError, + .RANGE => return LinuxGenericError.MathResultNotRepresentableError, + .DEADLK => return LinuxGenericError.ResourceDeadLockWouldOccurError, + .NAMETOOLONG => return LinuxGenericError.FileNameTooLongError, + .NOLCK => return LinuxGenericError.NoRecordLocksAvailableError, + .NOSYS => return LinuxGenericError.FunctionNotImplementedError, + .NOTEMPTY => return LinuxGenericError.DirectoryNotEmptyError, + .LOOP => return LinuxGenericError.TooManySymbolicLinksEncounteredError, + .NOMSG => return LinuxGenericError.NoMessageOfDesiredTypeError, + .IDRM => return LinuxGenericError.IdentifierRemovedError, + .CHRNG => return LinuxGenericError.ChannelNumberOutOfRangeError, + .L2NSYNC => return LinuxGenericError.Level2NotSynchronizedError, + .L3HLT => return LinuxGenericError.Level3HaltedError, + .L3RST => return LinuxGenericError.Level3ResetError, + .LNRNG => return LinuxGenericError.LinkNumberOutOfRangeError, + .UNATCH => return LinuxGenericError.ProtocolDriverNotAttachedError, + .NOCSI => return LinuxGenericError.NoCSIStructureAvailableError, + .L2HLT => return LinuxGenericError.Level2HaltedError, + .BADE => return LinuxGenericError.InvalidExchangeError, + .BADR => return LinuxGenericError.InvalidRequestDescriptorError, + .XFULL => return LinuxGenericError.ExchangeFullError, + .NOANO => return LinuxGenericError.NoAnodeError, + .BADRQC => return LinuxGenericError.InvalidRequestCodeError, + .BADSLT => return LinuxGenericError.InvalidSlotError, + .BFONT => return LinuxGenericError.BadFontFileFormatError, + .NOSTR => return LinuxGenericError.DeviceNotAStreamError, + .NODATA => return LinuxGenericError.NoDataAvailableError, + .TIME => return LinuxGenericError.TimerExpiredError, + .NOSR => return LinuxGenericError.OutOfStreamsResourcesError, + .NONET => return LinuxGenericError.MachineIsNotOnTheNetworkError, + .NOPKG => return LinuxGenericError.PackageNotInstalledError, + .REMOTE => return LinuxGenericError.ObjectIsRemoteError, + .NOLINK => return LinuxGenericError.LinkHasBeenSeveredError, + .ADV => return LinuxGenericError.AdvertiseErrorError, + .SRMNT => return LinuxGenericError.SrmountErrorError, + .COMM => return LinuxGenericError.CommunicationErrorOnSendError, + .PROTO => return LinuxGenericError.ProtocolErrorError, + .MULTIHOP => return LinuxGenericError.MultihopAttemptedError, + .DOTDOT => return LinuxGenericError.RFSSpecificErrorError, + .BADMSG => return LinuxGenericError.NotADataMessageError, + .OVERFLOW => return LinuxGenericError.ValueTooLargeForDefinedDataTypeError, + .NOTUNIQ => return LinuxGenericError.NameNotUniqueOnNetworkError, + .BADFD => return LinuxGenericError.FileDescriptorInBadStateError, + .REMCHG => return LinuxGenericError.RemoteAddressChangedError, + .LIBACC => return LinuxGenericError.CanNotAccessANeededSharedLibraryError, + .LIBBAD => return LinuxGenericError.AccessingACorruptedSharedLibraryError, + .LIBSCN => return LinuxGenericError.LibSectionInAOoutCorruptedError, + .LIBMAX => return LinuxGenericError.AttemptingToLinkInTooManySharedLibrariesError, + .LIBEXEC => return LinuxGenericError.CannotExecASharedLibraryDirectlyError, + .ILSEQ => return LinuxGenericError.IllegalByteSequenceError, + .RESTART => return LinuxGenericError.InterruptedSystemCallShouldBeRestartedError, + .STRPIPE => return LinuxGenericError.StreamsPipeErrorError, + .USERS => return LinuxGenericError.TooManyUsersError, + .NOTSOCK => return LinuxGenericError.SocketOperationOnNonSocketError, + .DESTADDRREQ => return LinuxGenericError.DestinationAddressRequiredError, + .MSGSIZE => return LinuxGenericError.MessageTooLongError, + .PROTOTYPE => return LinuxGenericError.ProtocolWrongTypeForSocketError, + .NOPROTOOPT => return LinuxGenericError.ProtocolNotAvailableError, + .PROTONOSUPPORT => return LinuxGenericError.ProtocolNotSupportedError, + .SOCKTNOSUPPORT => return LinuxGenericError.SocketTypeNotSupportedError, + .OPNOTSUPP => return LinuxGenericError.OperationNotSupportedOnTransportEndpointError, + .PFNOSUPPORT => return LinuxGenericError.ProtocolFamilyNotSupportedError, + .AFNOSUPPORT => return LinuxGenericError.AddressFamilyNotSupportedByProtocolError, + .ADDRINUSE => return LinuxGenericError.AddressAlreadyInUseError, + .ADDRNOTAVAIL => return LinuxGenericError.CannotAssignRequestedAddressError, + .NETDOWN => return LinuxGenericError.NetworkIsDownError, + .NETUNREACH => return LinuxGenericError.NetworkIsUnreachableError, + .NETRESET => return LinuxGenericError.NetworkDroppedConnectionBecauseOfResetError, + .CONNABORTED => return LinuxGenericError.SoftwareCausedConnectionAbortError, + .CONNRESET => return LinuxGenericError.ConnectionResetByPeerError, + .NOBUFS => return LinuxGenericError.NoBufferSpaceAvailableError, + .ISCONN => return LinuxGenericError.TransportEndpointIsAlreadyConnectedError, + .NOTCONN => return LinuxGenericError.TransportEndpointIsNotConnectedError, + .SHUTDOWN => return LinuxGenericError.CannotSendAfterTransportEndpointShutdownError, + .TOOMANYREFS => return LinuxGenericError.TooManyReferencesCannotSpliceError, + .TIMEDOUT => return LinuxGenericError.ConnectionTimedOutError, + .CONNREFUSED => return LinuxGenericError.ConnectionRefusedError, + .HOSTDOWN => return LinuxGenericError.HostIsDownError, + .HOSTUNREACH => return LinuxGenericError.NoRouteToHostError, + .ALREADY => return LinuxGenericError.OperationAlreadyInProgressError, + .INPROGRESS => return LinuxGenericError.OperationNowInProgressError, + .STALE => return LinuxGenericError.StaleNFSFileHandleError, + .UCLEAN => return LinuxGenericError.StructureNeedsCleaningError, + .NOTNAM => return LinuxGenericError.NotAXENIXNamedTypeFileError, + .NAVAIL => return LinuxGenericError.NoXENIXSemaphoresAvailableError, + .ISNAM => return LinuxGenericError.IsANamedTypeFileError, + .REMOTEIO => return LinuxGenericError.RemoteIOErrorError, + .DQUOT => return LinuxGenericError.QuotaExceededError, + .NOMEDIUM => return LinuxGenericError.NoMediumFoundError, + .MEDIUMTYPE => return LinuxGenericError.WrongMediumTypeError, + .CANCELED => return LinuxGenericError.OperationCanceledError, + .NOKEY => return LinuxGenericError.RequiredKeyNotAvailableError, + .KEYEXPIRED => return LinuxGenericError.KeyHasExpiredError, + .KEYREVOKED => return LinuxGenericError.KeyHasBeenRevokedError, + .KEYREJECTED => return LinuxGenericError.KeyWasRejectedByServiceError, + .OWNERDEAD => return LinuxGenericError.OwnerDiedError, + .NOTRECOVERABLE => return LinuxGenericError.StateNotRecoverableError, + .RFKILL => return LinuxGenericError.OperationNotPossibleDueToRFKillError, + .HWPOISON => return LinuxGenericError.MemoryPageHasHardwareErrorError, + .NSRNODATA => return LinuxGenericError.DNSServerReturnedAnswerWithNoDataError, + .NSRFORMERR => return LinuxGenericError.DNSServerClaimsQueryWasMisformattedError, + .NSRSERVFAIL => return LinuxGenericError.DNSServerReturnedGeneralFailureError, + .NSRNOTFOUND => return LinuxGenericError.DomainNameNotFoundError, + .NSRNOTIMP => return LinuxGenericError.DNSServerDoesNotImplementRequestedOperationError, + .NSRREFUSED => return LinuxGenericError.DNSServerRefusedQueryError, + .NSRBADQUERY => return LinuxGenericError.MisformattedDNSQueryError, + .NSRBADNAME => return LinuxGenericError.MisformattedDomainNameError, + .NSRBADFAMILY => return LinuxGenericError.UnsupportedAddressFamilyError, + .NSRBADRESP => return LinuxGenericError.MisformattedDNSReplyError, + .NSRCONNREFUSED => return LinuxGenericError.CouldNotContactDNSServersError, + .NSRTIMEOUT => return LinuxGenericError.TimeoutWhileContactingDNSServersError, + .NSROF => return LinuxGenericError.EndOfFileError, + .NSRFILE => return LinuxGenericError.ErrorReadingFileError, + .NSRNOMEM => return LinuxGenericError.NameServerOutOfMemoryError, + .NSRDESTRUCTION => return LinuxGenericError.ApplicationTerminatedLookupError, + .NSRQUERYDOMAINTOOLONG => return LinuxGenericError.DomainNameIsTooLongError, + .NSRCNAMELOOP => return LinuxGenericError.NameServerCNameLoopError, + else => unreachable, + } + // zig fmt: on +} + +pub const LinuxGenericError = error{ + OperationNotPermittedError, + NoSuchFileOrDirectoryError, + NoSuchProcessError, + InterruptedSystemCallError, + IOError, + NoSuchDeviceOrAddressError, + ArgumentListTooLongError, + ExecFormatError, + BadFileNumberError, + NoChildProcessesError, + TryAgainError, + OutOfMemoryError, + PermissionDeniedError, + BadAddressError, + BlockDeviceRequiredError, + DeviceOrResourceBusyError, + FileExistsError, + CrossDeviceLinkError, + NoSuchDeviceError, + NotADirectoryError, + IsADirectoryError, + InvalidArgumentError, + FileTableOverflowError, + TooManyOpenFilesError, + NotATypewriterError, + TextFileBusyError, + FileTooLargeError, + NoSpaceLeftOnDeviceError, + IllegalSeekError, + ReadOnlyFilesystemError, + TooManyLinksError, + BrokenPipeError, + MathArgumentOutOfDomainOfFunctionError, + MathResultNotRepresentableError, + ResourceDeadLockWouldOccurError, + FileNameTooLongError, + NoRecordLocksAvailableError, + FunctionNotImplementedError, + DirectoryNotEmptyError, + TooManySymbolicLinksEncounteredError, + NoMessageOfDesiredTypeError, + IdentifierRemovedError, + ChannelNumberOutOfRangeError, + Level2NotSynchronizedError, + Level3HaltedError, + Level3ResetError, + LinkNumberOutOfRangeError, + ProtocolDriverNotAttachedError, + NoCSIStructureAvailableError, + Level2HaltedError, + InvalidExchangeError, + InvalidRequestDescriptorError, + ExchangeFullError, + NoAnodeError, + InvalidRequestCodeError, + InvalidSlotError, + BadFontFileFormatError, + DeviceNotAStreamError, + NoDataAvailableError, + TimerExpiredError, + OutOfStreamsResourcesError, + MachineIsNotOnTheNetworkError, + PackageNotInstalledError, + ObjectIsRemoteError, + LinkHasBeenSeveredError, + AdvertiseErrorError, + SrmountErrorError, + CommunicationErrorOnSendError, + ProtocolErrorError, + MultihopAttemptedError, + RFSSpecificErrorError, + NotADataMessageError, + ValueTooLargeForDefinedDataTypeError, + NameNotUniqueOnNetworkError, + FileDescriptorInBadStateError, + RemoteAddressChangedError, + CanNotAccessANeededSharedLibraryError, + AccessingACorruptedSharedLibraryError, + LibSectionInAOoutCorruptedError, + AttemptingToLinkInTooManySharedLibrariesError, + CannotExecASharedLibraryDirectlyError, + IllegalByteSequenceError, + InterruptedSystemCallShouldBeRestartedError, + StreamsPipeErrorError, + TooManyUsersError, + SocketOperationOnNonSocketError, + DestinationAddressRequiredError, + MessageTooLongError, + ProtocolWrongTypeForSocketError, + ProtocolNotAvailableError, + ProtocolNotSupportedError, + SocketTypeNotSupportedError, + OperationNotSupportedOnTransportEndpointError, + ProtocolFamilyNotSupportedError, + AddressFamilyNotSupportedByProtocolError, + AddressAlreadyInUseError, + CannotAssignRequestedAddressError, + NetworkIsDownError, + NetworkIsUnreachableError, + NetworkDroppedConnectionBecauseOfResetError, + SoftwareCausedConnectionAbortError, + ConnectionResetByPeerError, + NoBufferSpaceAvailableError, + TransportEndpointIsAlreadyConnectedError, + TransportEndpointIsNotConnectedError, + CannotSendAfterTransportEndpointShutdownError, + TooManyReferencesCannotSpliceError, + ConnectionTimedOutError, + ConnectionRefusedError, + HostIsDownError, + NoRouteToHostError, + OperationAlreadyInProgressError, + OperationNowInProgressError, + StaleNFSFileHandleError, + StructureNeedsCleaningError, + NotAXENIXNamedTypeFileError, + NoXENIXSemaphoresAvailableError, + IsANamedTypeFileError, + RemoteIOErrorError, + QuotaExceededError, + NoMediumFoundError, + WrongMediumTypeError, + OperationCanceledError, + RequiredKeyNotAvailableError, + KeyHasExpiredError, + KeyHasBeenRevokedError, + KeyWasRejectedByServiceError, + OwnerDiedError, + StateNotRecoverableError, + OperationNotPossibleDueToRFKillError, + MemoryPageHasHardwareErrorError, + DNSServerReturnedAnswerWithNoDataError, + DNSServerClaimsQueryWasMisformattedError, + DNSServerReturnedGeneralFailureError, + DomainNameNotFoundError, + DNSServerDoesNotImplementRequestedOperationError, + DNSServerRefusedQueryError, + MisformattedDNSQueryError, + MisformattedDomainNameError, + UnsupportedAddressFamilyError, + MisformattedDNSReplyError, + CouldNotContactDNSServersError, + TimeoutWhileContactingDNSServersError, + EndOfFileError, + ErrorReadingFileError, + NameServerOutOfMemoryError, + ApplicationTerminatedLookupError, + DomainNameIsTooLongError, + NameServerCNameLoopError, +}; diff --git a/src/main.zig b/src/main.zig index 0d6bb76..2355154 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,6 @@ const std = @import("std"); const display = @import("display.zig"); +const i2cnative = @import("i2cnative.zig"); // Disabling the image characters. To re-enabel, switch the import back and // adjust build.zig @@ -120,13 +121,11 @@ fn deinit() void { fn processArgs(args: [][:0]u8, line_array: *[display.LINES]*const [:0]u8) !Options { if (args.len < 2) try usage(args); - const prefix = "/dev/ttyUSB"; var opts = Options{ .device_file = args[1], .flip = false, .background_filename = @constCast(""), }; - if (!std.mem.eql(u8, opts.device_file, "-") and !std.mem.startsWith(u8, opts.device_file, prefix)) try usage(args); var is_filename = false; var line_number: ?usize = null; @@ -303,8 +302,12 @@ fn sendPixels(pixels: []const u8, file: [:0]const u8, device_id: u8) !void { if (is_i2cdriver) return sendPixelsThroughI2CDriver(pixels, file, device_id); + const is_i2cnative = std.mem.startsWith(u8, file, "/dev/i2c"); + if (is_i2cnative) + return i2cnative.sendPixels(pixels, file, device_id); + // Send through linux i2c native - return error.LinuxNativeNotImplemented; + return error.UnkownDeviceType; } fn sendPixelsToStdOut(pixels: []const u8) !void { @@ -323,7 +326,7 @@ fn sendPixelsToStdOut(pixels: []const u8) !void { fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id: u8) !void { var pixels_write_command = [_]u8{0x00} ** ((display.WIDTH * display.PAGES) + 1); pixels_write_command[0] = 0x40; - packPixelsToDeviceFormat(pixels, pixels_write_command[1..]); + display.packPixelsToDeviceFormat(pixels, pixels_write_command[1..]); var i2c = c.I2CDriver{ .connected = 0, .port = 0, @@ -378,51 +381,6 @@ fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id try stdout.print("done\n", .{}); } -fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void { - // Each u8 in pixels is a single bit. We need to pack these bits - for (packed_pixels, 0..) |*b, i| { - const column = i % display.WIDTH; - const page = i / display.WIDTH; - - // if (column == 0) std.debug.print("{d}: ", .{page}); - // pixel array will be 8x as "high" as the data array we are sending to - // the device. So the device column above is only our starter - // Display has 8 pages, which is a set of 8 pixels with LSB at top of page - // - // To convert from the pixel array above, we need to: - // 1. convert from device page to a base "row" in the pixel array - const row = page * display.PAGES; - // 2. We will have 8 rows for each base row - // 3. Multiple each row by the width to get the index of the start of - // the row - // 4. Add our current column index for the final pixel location in - // the pixel array. - // - // Now that we have the proper index in the pixel array, we need to - // convert that into our destination byte. Each index will be a u8, either - // 0xff for on or 0x00 for off. So... - // - // 1. We will take the value and bitwise and with 0x01 so we get one bit - // per source byte - // 2. Shift that bit into the proper position in our destination byte - - b.* = (pixels[(0 + row) * display.WIDTH + column] & 0x01) << 0 | - (pixels[(1 + row) * display.WIDTH + column] & 0x01) << 1 | - (pixels[(2 + row) * display.WIDTH + column] & 0x01) << 2 | - (pixels[(3 + row) * display.WIDTH + column] & 0x01) << 3 | - (pixels[(4 + row) * display.WIDTH + column] & 0x01) << 4 | - (pixels[(5 + row) * display.WIDTH + column] & 0x01) << 5 | - (pixels[(6 + row) * display.WIDTH + column] & 0x01) << 6 | - (pixels[(7 + row) * display.WIDTH + column] & 0x01) << 7; - - // std.debug.print("{s}", .{std.fmt.fmtSliceHexLower(&[_]u8{b.*})}); - // if (column == 127) std.debug.print("\n", .{}); - - // Last 2 pages are yellow...16 pixels vertical - // if (page == 6 or page == 7) b.* = 0xff; - // b.* = 0xf0; - } -} fn i2cWrite(i2c: *c.I2CDriver, bytes: []const u8) !void { var rc = c.i2c_write(i2c, @ptrCast([*c]const u8, bytes), bytes.len); // nn is size of array if (rc != 1)