diff --git a/.gitignore b/.gitignore index 6a4c180..c6e2820 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ zig-cache/ zig-out/ core +src/images/* diff --git a/AsciiPrintableStep.zig b/AsciiPrintableStep.zig new file mode 100644 index 0000000..cb7d539 --- /dev/null +++ b/AsciiPrintableStep.zig @@ -0,0 +1,168 @@ +//! Publish Date: 2021_10_17 +//! This file is hosted at github.com/marler8997/zig-build-repos and is meant to be copied +//! to projects that use it. +const std = @import("std"); +const AsciiPrintableStep = @This(); + +step: std.build.Step, +builder: *std.build.Builder, +path: []const u8, + +pub fn create(b: *std.build.Builder, opt: struct { + path: []const u8, +}) *AsciiPrintableStep { + var result = b.allocator.create(AsciiPrintableStep) catch @panic("OOM"); + result.* = AsciiPrintableStep{ + .step = std.build.Step.init(.{ + .id = .custom, + .name = "AsciiPrintable", + .owner = b, + .makeFn = make, + }), + .builder = b, + .path = std.fs.path.resolve(b.allocator, &[_][]const u8{ + b.build_root.path.?, opt.path, + }) catch @panic("memory"), + }; + return result; +} + +// TODO: this should be included in std.build, it helps find bugs in build files +fn hasDependency(step: *const std.build.Step, dep_candidate: *const std.build.Step) bool { + for (step.dependencies.items) |dep| { + // TODO: should probably use step.loop_flag to prevent infinite recursion + // when a circular reference is encountered, or maybe keep track of + // the steps encounterd with a hash set + if (dep == dep_candidate or hasDependency(dep, dep_candidate)) + return true; + } + return false; +} + +fn make(step: *std.build.Step, _: *std.Progress.Node) !void { + const self = @fieldParentPtr(AsciiPrintableStep, "step", step); + + const zig_file = std.fmt.allocPrint(self.builder.allocator, "{s}/images.zig", .{self.path}) catch @panic("OOM"); + defer self.builder.allocator.free(zig_file); + std.fs.accessAbsolute(zig_file, .{ .mode = .read_only }) catch { + // Printables file does not exist + // ASCII printables from 32 to 126 + const file = try std.fs.createFileAbsolute(zig_file, .{ + .read = false, + .truncate = true, + .lock = .Exclusive, + .lock_nonblocking = false, + .mode = 0o666, + .intended_io_mode = .blocking, + }); + defer file.close(); + const writer = file.writer(); + try writer.print("const chars = [_][]u8 {{\n", .{}); + + for (0..32) |_| { + try writer.print(" \"\",\n", .{}); + } + for (32..127) |i| { + // if (i == 32) { + // try writer.print(" \"\",\n", .{}); + // continue; + // } + const char_str = [_]u8{@intCast(u8, i)}; + // Need to escape the following chars: 32 (' ') 92 ('\') + const label_param = parm: { + switch (i) { + 32 => break :parm "label:\\ ", + 92 => break :parm "label:\\\\", + else => break :parm "label:" ++ char_str, + } + }; + + const dest_file = std.fmt.allocPrint(self.builder.allocator, "{s}/{d}.bmp", .{ self.path, i }) catch @panic("OOM"); + defer self.builder.allocator.free(dest_file); + + // generate the file + // magick -background transparent -fill black -font Hack-Regular -density 72 -pointsize 8 label:42 test.bmp + try run(self.builder, &[_][]const u8{ + "magick", + "-background", + "white", + "-fill", + "black", + "-font", + "Hack-Regular", + "-density", + "72", + "-pointsize", + "8", + label_param, + "-extent", + "5x8", + dest_file, + }); + // 36 ($) and 81 (Q) are widest and only 9 wide + // Can chop right pixel I think + // try writer.print("{s}\n", .{[_]u8{@intCast(u8, i)}}); + // add the embed + try writer.print(" @embedFile(\"{d}.bmp\"),\n", .{i}); + } + try writer.print("}};\n", .{}); + // if (!self.fetch_enabled) { + // step.addError(" Use -Dfetch to download it automatically, or run the following to clone it:", .{}); + // std.os.exit(1); + // } + }; +} + +fn run(builder: *std.build.Builder, argv: []const []const u8) !void { + // { + // var msg = std.ArrayList(u8).init(builder.allocator); + // defer msg.deinit(); + // const writer = msg.writer(); + // var prefix: []const u8 = ""; + // for (argv) |arg| { + // try writer.print("{s}\"{s}\"", .{ prefix, arg }); + // prefix = " "; + // } + // std.log.debug("[RUN] {s}", .{msg.items}); + // } + + var child = std.ChildProcess.init(argv, builder.allocator); + + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + child.cwd = builder.build_root.path; + child.env_map = builder.env_map; + + try child.spawn(); + const result = try child.wait(); + switch (result) { + .Exited => |code| if (code != 0) { + std.log.err("command failed with exit code {}", .{code}); + { + var msg = std.ArrayList(u8).init(builder.allocator); + defer msg.deinit(); + const writer = msg.writer(); + var prefix: []const u8 = ""; + for (argv) |arg| { + try writer.print("{s}\"{s}\"", .{ prefix, arg }); + prefix = " "; + } + std.log.debug("[RUN] {s}", .{msg.items}); + } + std.os.exit(0xff); + }, + else => { + std.log.err("command failed with: {}", .{result}); + std.os.exit(0xff); + }, + } +} + +// Get's the repository path and also verifies that the step requesting the path +// is dependent on this step. +pub fn getPath(self: anytype, who_wants_to_know: *const std.build.Step) []const u8 { + if (!hasDependency(who_wants_to_know, &self.step)) + @panic("a step called AsciiPrintableStep.getPath but has not added it as a dependency"); + return self.path; +} diff --git a/build.zig b/build.zig index d4ea5ee..107dd60 100644 --- a/build.zig +++ b/build.zig @@ -1,6 +1,7 @@ const std = @import("std"); +const AsciiPrintableStep = @import("AsciiPrintableStep.zig"); -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.build.Builder) !void { // comptime { // const current_zig = builtin.zig_version; // const min_zig = std.SemanticVersion.parse("0.11.0-dev.1254+1f8f79cd5") catch return; // add helper functions to std.zig.Ast @@ -45,6 +46,8 @@ pub fn build(b: *std.build.Builder) void { exe.addIncludePath("lib/i2cdriver"); exe.install(); + exe.step.dependOn(&AsciiPrintableStep.create(b, .{ .path = "src/images" }).step); + // exe.step.dependOn((try fontGeneration(b, target))); const run_cmd = exe.run(); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { @@ -63,3 +66,14 @@ pub fn build(b: *std.build.Builder) void { const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&exe_tests.step); } + +// Should be able to remove this +fn fontGeneration(b: *std.build.Builder, target: anytype) !*std.build.Step { + if (target.getOs().tag != .linux) return error.UnsupportedBuildOS; + const fontgen = b.step("gen", "Generate font image files"); + fontgen.dependOn(&b.addSystemCommand(&.{ "/bin/sh", "-c", "./fontgen" }).step); + + // This can probably be triggered instead by GitRepoStep cloning the repo + // exe.step.dependOn(cg); + return fontgen; +} diff --git a/lib/i2cdriver/i2cdriver.c b/lib/i2cdriver/i2cdriver.c new file mode 100644 index 0000000..a3a5ac2 --- /dev/null +++ b/lib/i2cdriver/i2cdriver.c @@ -0,0 +1,561 @@ +#include +#include +#include +#include +#include +#if !defined(WIN32) +#include +#include +#endif +#include +#define __STDC_FORMAT_MACROS +#include +#include + +#include "i2cdriver.h" + +// **************************** Serial port ******************************** + +#if defined(WIN32) // { + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include + +void ErrorExit(const char *func_name) +{ + // Retrieve the system error message for the last-error code + + LPVOID lpMsgBuf; + DWORD dw = GetLastError(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + + // Display the error message and exit the process + + char mm[256]; + snprintf(mm, sizeof(mm), "%s failed with error %lu:\n%s", func_name, dw, (char*)lpMsgBuf); + MessageBox(NULL, (LPCTSTR)mm, TEXT("Error"), MB_OK); + + LocalFree(lpMsgBuf); + ExitProcess(dw); +} + +HANDLE openSerialPort(const char *portname) +{ + char fullname[10]; + const char *fmt; + if (portname[0] == 'C') + fmt = "\\\\.\\%s"; + else + fmt = "%s"; + snprintf(fullname, sizeof(fullname), fmt, portname); + DWORD accessdirection = GENERIC_READ | GENERIC_WRITE; + HANDLE hSerial = CreateFile((LPCSTR)fullname, + accessdirection, + 0, + 0, + OPEN_EXISTING, + 0, + 0); + if (hSerial == INVALID_HANDLE_VALUE) { + ErrorExit("CreateFile"); + } + DCB dcbSerialParams = {0}; + dcbSerialParams.DCBlength=sizeof(dcbSerialParams); + if (!GetCommState(hSerial, &dcbSerialParams)) { + ErrorExit("GetCommState"); + } + dcbSerialParams.BaudRate = 1000000; + dcbSerialParams.ByteSize = 8; + dcbSerialParams.StopBits = ONESTOPBIT; + dcbSerialParams.Parity = NOPARITY; + if (!SetCommState(hSerial, &dcbSerialParams)) { + ErrorExit("SetCommState"); + } + COMMTIMEOUTS timeouts = {0}; + timeouts.ReadIntervalTimeout = 50; + timeouts.ReadTotalTimeoutConstant = 50; + timeouts.ReadTotalTimeoutMultiplier = 10; + timeouts.WriteTotalTimeoutConstant = 50; + timeouts.WriteTotalTimeoutMultiplier = 10; + if (!SetCommTimeouts(hSerial, &timeouts)) { + ErrorExit("SetCommTimeouts"); + } + return hSerial; +} + +DWORD readFromSerialPort(HANDLE hSerial, uint8_t * buffer, int buffersize) +{ + DWORD dwBytesRead = 0; + if (!ReadFile(hSerial, buffer, buffersize, &dwBytesRead, NULL)) { + ErrorExit("ReadFile"); + } + return dwBytesRead; +} + +DWORD writeToSerialPort(HANDLE hSerial, const uint8_t * data, int length) +{ + DWORD dwBytesRead = 0; + if (!WriteFile(hSerial, data, length, &dwBytesRead, NULL)) { + ErrorExit("WriteFile"); + } + return dwBytesRead; +} + +void closeSerialPort(HANDLE hSerial) +{ + CloseHandle(hSerial); +} + +#else // }{ + +#include + +int openSerialPort(const char *portname) +{ + struct termios Settings; + int fd; + + fd = open(portname, O_RDWR | O_NOCTTY); + if (fd == -1) { + perror(portname); + return -1; + } + tcgetattr(fd, &Settings); + +#if defined(__APPLE__) && !defined(B1000000) + #include +#else + cfsetispeed(&Settings, B1000000); + cfsetospeed(&Settings, B1000000); +#endif + + + cfmakeraw(&Settings); + Settings.c_cc[VMIN] = 1; + if (tcsetattr(fd, TCSANOW, &Settings) != 0) { + perror("Serial settings"); + return -1; + } + +#if defined(__APPLE__) && !defined(B1000000) + speed_t speed = (speed_t)1000000; + ioctl(fd, IOSSIOSPEED, &speed); +#endif + + return fd; +} + +int readFromSerialPort(int fd, uint8_t *b, size_t s) +{ + ssize_t n, t; + t = 0; + while (t < s) { + n = read(fd, b + t, s); + if (n > 0) + t += n; + } +#ifdef VERBOSE + printf(" READ %d %d: ", (int)s, (int)n); + int i; + for (i = 0; i < s; i++) + printf("%02x ", 0xff & b[i]); + printf("\n"); +#endif + return s; +} + +void writeToSerialPort(int fd, const uint8_t *b, size_t s) +{ + write(fd, b, s); +#ifdef VERBOSE + printf("WRITE %u: ", (int)s); + int i; + for (i = 0; i < s; i++) + printf("%02x ", 0xff & b[i]); + printf("\n"); +#endif +} +#endif // } + +// ****************************** CCITT CRC ********************************* + +static const uint16_t crc_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +static void crc_update(I2CDriver *sd, const uint8_t *data, size_t data_len) +{ + unsigned int tbl_idx; + uint16_t crc = sd->e_ccitt_crc; + + while (data_len--) { + tbl_idx = ((crc >> 8) ^ *data) & 0xff; + crc = (crc_table[tbl_idx] ^ (crc << 8)) & 0xffff; + data++; + } + sd->e_ccitt_crc = crc; +} + +// ****************************** I2CDriver ********************************* + +void i2c_connect(I2CDriver *sd, const char* portname) +{ + int i; + + sd->connected = 0; + sd->port = openSerialPort(portname); +#if !defined(WIN32) + if (sd->port == -1) + return; +#endif + writeToSerialPort(sd->port, + (uint8_t*)"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", 64); + + const uint8_t tests[] = "A\r\n\0xff"; + for (i = 0; i < 4; i++) { + uint8_t tx[2] = {'e', tests[i]}; + writeToSerialPort(sd->port, tx, 2); + uint8_t rx[1]; + int n = readFromSerialPort(sd->port, rx, 1); + if ((n != 1) || (rx[0] != tests[i])) + return; + } + + sd->connected = 1; + i2c_getstatus(sd); + sd->e_ccitt_crc = sd->ccitt_crc; +} + +static void charCommand(I2CDriver *sd, char c) +{ + writeToSerialPort(sd->port, (uint8_t*)&c, 1); +} + +static int i2c_ack(I2CDriver *sd) +{ + uint8_t a[1]; + if (readFromSerialPort(sd->port, a, 1) != 1) + return 0; + return (a[0] & 1) != 0; +} + +void i2c_getstatus(I2CDriver *sd) +{ + uint8_t readbuffer[100]; + int bytesRead; + uint8_t mode[80]; + + charCommand(sd, '?'); + bytesRead = readFromSerialPort(sd->port, readbuffer, 80); + readbuffer[bytesRead] = 0; + // printf("%d Bytes were read: %.*s\n", bytesRead, bytesRead, readbuffer); + sscanf((char*)readbuffer, "[%15s %8s %" SCNu64 " %f %f %f %c %d %d %d %d %x ]", + sd->model, + sd->serial, + &sd->uptime, + &sd->voltage_v, + &sd->current_ma, + &sd->temp_celsius, + mode, + &sd->sda, + &sd->scl, + &sd->speed, + &sd->pullups, + &sd->ccitt_crc + ); + sd->mode = mode[0]; +} + +void i2c_scan(I2CDriver *sd, uint8_t devices[128]) +{ + charCommand(sd, 'd'); + (void)readFromSerialPort(sd->port, devices + 8, 112); +} + +uint8_t i2c_reset(I2CDriver *sd) +{ + charCommand(sd, 'x'); + uint8_t a[1]; + if (readFromSerialPort(sd->port, a, 1) != 1) + return 0; + return a[0]; +} + +int i2c_start(I2CDriver *sd, uint8_t dev, uint8_t op) +{ + uint8_t start[2] = {'s', (uint8_t)((dev << 1) | op)}; + writeToSerialPort(sd->port, start, sizeof(start)); + return i2c_ack(sd); +} + +void i2c_stop(I2CDriver *sd) +{ + charCommand(sd, 'p'); +} + +int i2c_write(I2CDriver *sd, const uint8_t bytes[], size_t nn) +{ + size_t i; + int ack = 1; + + for (i = 0; i < nn; i += 64) { + size_t len = ((nn - i) < 64) ? (nn - i) : 64; + uint8_t cmd[65] = {(uint8_t)(0xc0 + len - 1)}; + memcpy(cmd + 1, bytes + i, len); + writeToSerialPort(sd->port, cmd, 1 + len); + ack = i2c_ack(sd); + } + crc_update(sd, bytes, nn); + return ack; +} + +void i2c_read(I2CDriver *sd, uint8_t bytes[], size_t nn) +{ + size_t i; + + for (i = 0; i < nn; i += 64) { + size_t len = ((nn - i) < 64) ? (nn - i) : 64; + uint8_t cmd[1] = {(uint8_t)(0x80 + len - 1)}; + writeToSerialPort(sd->port, cmd, 1); + readFromSerialPort(sd->port, bytes + i, len); + crc_update(sd, bytes + i, len); + } +} + +void i2c_monitor(I2CDriver *sd, int enable) +{ + charCommand(sd, enable ? 'm' : '@'); +} + +void i2c_capture(I2CDriver *sd) +{ + printf("Capture started\n"); + charCommand(sd, 'c'); + uint8_t bytes[1]; + + int starting = 0; + int nbits = 0, bits = 0; + while (1) { + int i; + readFromSerialPort(sd->port, bytes, 1); + for (i = 0; i < 2; i++) { + int symbol = (i == 0) ? (bytes[0] >> 4) : (bytes[0] & 0xf); + switch (symbol) { + case 0: + break; + case 1: + starting = 1; + break; + case 2: + printf("STOP\n"); + starting = 1; + break; + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + bits = (bits << 3) | (symbol & 7); + nbits += 3; + if (nbits == 9) { + int b8 = (bits >> 1), ack = !(bits & 1); + if (starting) { + starting = 0; + printf("START %02x %s", b8 >> 1, (b8 & 1) ? "READ" : "WRITE"); + } else { + printf("BYTE %02x", b8); + } + printf(" %s\n", ack ? "ACK" : "NAK"); + nbits = 0; + bits = 0; + } + } + } + } +} + +int i2c_commands(I2CDriver *sd, int argc, char *argv[]) +{ + int i; + + for (i = 0; i < argc; i++) { + char *token = argv[i]; + // printf("token [%s]\n", token); + if (strlen(token) != 1) + goto badcommand; + switch (token[0]) { + + case 'i': + i2c_getstatus(sd); + printf("uptime %" SCNu64" %.3f V %.0f mA %.1f C SDA=%d SCL=%d speed=%d kHz\n", + sd->uptime, + sd->voltage_v, + sd->current_ma, + sd->temp_celsius, + sd->sda, + sd->scl, + sd->speed + ); + break; + + case 'x': + { + uint8_t sda_scl = i2c_reset(sd); + printf("Bus reset. SDA = %d, SCL = %d\n", + 1 & (sda_scl >> 1), + 1 & sda_scl); + } + break; + + case 'd': + { + uint8_t devices[128]; + int i; + + i2c_scan(sd, devices); + printf("\n"); + for (i = 8; i < 0x78; i++) { + if (devices[i] == '1') + printf("%02x ", i); + else + printf("-- "); + if ((i % 8) == 7) + printf("\n"); + } + printf("\n"); + } + break; + + case 'w': + { + token = argv[++i]; + unsigned int dev = strtol(token, NULL, 0); + + token = argv[++i]; + uint8_t bytes[8192]; + char *endptr = token; + size_t nn = 0; + while (nn < sizeof(bytes)) { + bytes[nn++] = strtol(endptr, &endptr, 0); + if (*endptr == '\0') + break; + if (*endptr != ',') { + fprintf(stderr, "Invalid bytes '%s'\n", token); + return 1; + } + endptr++; + } + + i2c_start(sd, dev, 0); + i2c_write(sd, bytes, nn); + } + break; + + case 'r': + { + token = argv[++i]; + unsigned int dev = strtol(token, NULL, 0); + + token = argv[++i]; + size_t nn = strtol(token, NULL, 0); + uint8_t bytes[8192]; + + i2c_start(sd, dev, 1); + i2c_read(sd, bytes, nn); + i2c_stop(sd); + + size_t i; + for (i = 0; i < nn; i++) + printf("%s0x%02x", i ? "," : "", 0xff & bytes[i]); + printf("\n"); + } + break; + + case 'p': + i2c_stop(sd); + break; + + case 'm': + { + char line[100]; + + i2c_monitor(sd, 1); + printf("[Hit return to exit monitor mode]\n"); + fgets(line, sizeof(line) - 1, stdin); + i2c_monitor(sd, 0); + } + break; + + case 'c': + { + i2c_capture(sd); + } + break; + + default: + badcommand: + fprintf(stderr, "Bad command '%s'\n", token); + fprintf(stderr, "\n"); + fprintf(stderr, "Commands are:"); + fprintf(stderr, "\n"); + fprintf(stderr, " i display status information (uptime, voltage, current, temperature)\n"); + fprintf(stderr, " x I2C bus reset\n"); + fprintf(stderr, " d device scan\n"); + fprintf(stderr, " w dev write bytes to I2C device dev\n"); + fprintf(stderr, " p send a STOP\n"); + fprintf(stderr, " r dev N read N bytes from I2C device dev, then STOP\n"); + fprintf(stderr, " m enter I2C bus monitor mode\n"); + fprintf(stderr, " c enter I2C bus capture mode\n"); + fprintf(stderr, "\n"); + + return 1; + } + } + + return 0; +} diff --git a/lib/i2cdriver/i2cdriver.h b/lib/i2cdriver/i2cdriver.h new file mode 100644 index 0000000..aa14652 --- /dev/null +++ b/lib/i2cdriver/i2cdriver.h @@ -0,0 +1,43 @@ +#ifndef I2CDRIVER_H +#define I2CDRIVER_H + +#include + +#if defined(WIN32) +#include +#else +#define HANDLE int +#endif + +typedef struct { + int connected; // Set to 1 when connected + HANDLE port; + char model[16], + serial[9]; // Serial number of USB device + uint64_t uptime; // time since boot (seconds) + float voltage_v, // USB voltage (Volts) + current_ma, // device current (mA) + temp_celsius; // temperature (C) + unsigned int mode; // I2C 'I' or bitbang 'B' mode + unsigned int sda; // SDA state, 0 or 1 + unsigned int scl; // SCL state, 0 or 1 + unsigned int speed; // I2C line speed (in kHz) + unsigned int pullups; // pullup state (6 bits, 1=enabled) + unsigned int + ccitt_crc, // Hardware CCITT CRC + e_ccitt_crc; // Host CCITT CRC, should match +} I2CDriver; + +void i2c_connect(I2CDriver *sd, const char* portname); +void i2c_getstatus(I2CDriver *sd); +int i2c_write(I2CDriver *sd, const uint8_t bytes[], size_t nn); +void i2c_read(I2CDriver *sd, uint8_t bytes[], size_t nn); +int i2c_start(I2CDriver *sd, uint8_t dev, uint8_t op); +void i2c_stop(I2CDriver *sd); + +void i2c_monitor(I2CDriver *sd, int enable); +void i2c_capture(I2CDriver *sd); + +int i2c_commands(I2CDriver *sd, int argc, char *argv[]); + +#endif diff --git a/src/main.zig b/src/main.zig index 71e5555..3beaddf 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const chars = @import("images/images.zig").chars; // The package manager will install headers from our dependency in zig's build // cache and include the cache directory as a "-I" option on the build command @@ -8,9 +9,18 @@ const c = @cImport({ @cInclude("i2cdriver.h"); }); +// Image specifications const WIDTH = 128; const HEIGHT = 64; +// Text specifications +const FONT_WIDTH = 5; +const FONT_HEIGHT = 8; + +const CHARS_PER_LINE = 21; // 21 * 6 = 126 so we have 2px left over +const LINES = 8; + +// Device specifications const PAGES = 8; fn usage(args: [][]u8) !void { @@ -263,7 +273,6 @@ fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HE -@intCast(isize, (HEIGHT - resize_dimensions.height) / 2), ); - // status = c.MagickExtentImage(mw, WIDTH, HEIGHT, null, null); if (status == c.MagickFalse) return error.CouldNotSetExtent; @@ -283,6 +292,37 @@ fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HE status = c.MagickReadImageBlob(cw, characters, characters.len); if (status == c.MagickFalse) unreachable; // Something is terribly wrong if this fails + // For character placement, we need to set the image to the correct + // extent, and offset the image as appropriate. When we set the extent, + // we need the fill background to be transparent so we don't overwrite + // the background. This also means our font needs a transparent background + // (maybe?) + { + var pwc = c.NewPixelWand(); + defer { + if (pwc) |pixwc| pwc = c.DestroyPixelWand(pixwc); + } + status = c.PixelSetColor(pwc, "transparent"); + if (status == c.MagickFalse) + return error.CouldNotSetColor; + + status = c.MagickSetImageBackgroundColor(cw, pwc); + if (status == c.MagickFalse) + return error.CouldNotSetBackgroundColor; + // I think our characters are offset by 6px in the x and 8 in the y + status = c.MagickExtentImage( + cw, + WIDTH, + HEIGHT, + -6 * 8, + -8, + // -@intCast(isize, (WIDTH - resize_dimensions.width) / 2), + // -@intCast(isize, (HEIGHT - resize_dimensions.height) / 2), + ); + if (status == c.MagickFalse) + return error.CouldNotSetExtent; + } + // I think I need to add the image, then flatten this status = c.MagickAddImage(mw, cw); if (status == c.MagickFalse) return error.CouldNotAddImage;