add i2c native implementation

Fixes #1
This commit is contained in:
Emil Lerch 2023-04-10 17:38:54 -07:00
parent 6d41563226
commit c2ba2efd76
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
4 changed files with 447 additions and 49 deletions

View File

@ -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;
}
}

74
src/i2cnative.zig Normal file
View File

@ -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);
}

317
src/linux.zig Normal file
View File

@ -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,
};

View File

@ -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)