//! DCC support const std = @import("std"); const microzig = @import("microzig"); const rp2xxx = microzig.hal; const peripherals = microzig.chip.peripherals; const CV = @import("cv.zig").CV; const log = std.log.scoped(.dcc); pub const Direction = enum { backward, forward, }; pub const Speed = enum(u5) { stop, stopi, estop, estopi, step1, step2, step3, step4, step5, step6, step7, step8, step9, step10, step11, step12, step13, step14, step15, step16, step17, step18, step19, step20, step21, step22, step23, step24, step25, step26, step27, step28 }; const Packet = struct { address: u8, instruction: Instruction, /// Defined in S-9.2.1, Chapter 2.3 const InstructionType = enum(u3) { decoder_control, advanced_operating, speed_reverse, speed_forward, function_group1, function_group2, feature_expansion, cv_access, }; const Instruction = packed struct(u8) { speed: u4, headlight: u1, // lsb of speed itype: InstructionType, }; pub fn speed(self: @This()) Speed { // TODO: Based on CV29 we should add support for 14 step speed control var ret: u5 = self.instruction.speed << 1; ret |= self.instruction.headlight; return @enumFromInt(ret); } }; // TODO: extended address byte support (2.3) pub const Parser = struct { const TR1_MIN_US = 52; // const TR1_MAX_US = 64; const TR1_MAX_US = 87; // zig fmt: off const MASK_PREAMBLE = 0b1111111111_0; const MASK_3BYTES = 0b1111111111_1_00000000_1_00000000_1_00000000_1; const COMPARE_3BYTES = 0b1111111111_0_00000000_0_00000000_0_00000000_1; const MASK_4BYTES = 0b1111111111_1_00000000_1_00000000_1_00000000_1_00000000_1; const COMPARE_4BYTES = 0b1111111111_0_00000000_0_00000000_0_00000000_0_00000000_1; const MASK_5BYTES = 0b1111111111_1_00000000_1_00000000_1_00000000_1_00000000_1_00000000_1; const COMPARE_5BYTES = 0b1111111111_0_00000000_0_00000000_0_00000000_0_00000000_0_00000000_1; // zig fmt: on const ParserState = enum { preamble, package, }; const BitType = enum(u1) { zero, one, }; const BUFFER_TYPE = u56; // 10+1 + 5x(8+1) state: ParserState = .preamble, time: microzig.drivers.time.Absolute = @enumFromInt(0), buffer: BUFFER_TYPE = 0, packets: std.fifo.LinearFifo(Packet, .{ .Static = 8 }) = std.fifo.LinearFifo(Packet, .{ .Static = 8 }).init(), pub fn signalRising(self: *@This()) void { self.time = rp2xxx.time.get_time_since_boot(); } pub fn signalFalling(self: *@This()) void { const time_end = rp2xxx.time.get_time_since_boot(); const diff = time_end.diff(self.time); // if (diff.less_than(@enumFromInt(TR1_MIN_US))) { // log.debug("Diff {d} is too short, ignoring", .{diff.to_us()}); // self.time = rp2xxx.time.get_time_since_boot(); // return; // } // log.debug("DIFF: {d}", .{diff.to_us()}); const bit: BitType = if (diff.less_than(@enumFromInt(TR1_MAX_US))) .one else .zero; switch (self.state) { .preamble => { switch (bit) { .one => { self.writeToBuffer(bit); }, .zero => { self.writeToBuffer(bit); log.debug("DDC buffer: {b}", .{self.buffer}); if (self.buffer & MASK_PREAMBLE == MASK_PREAMBLE) { self.state = .package; log.debug("DDC state switched to: {}", .{self.state}); } else { log.info("DDC preamble is invalid: {b}", .{self.buffer}); self.buffer = 0; } }, } }, .package => { self.writeToBuffer(bit); // TODO: consider using @clz before ckecking masks? // check for valid DCC packets if (self.buffer & @as(BUFFER_TYPE, MASK_3BYTES) == @as(BUFFER_TYPE, COMPARE_3BYTES)) { self.write3bytes(); self.reset(); } else if (self.buffer & @as(BUFFER_TYPE, MASK_4BYTES) == @as(BUFFER_TYPE, COMPARE_4BYTES)) { log.debug("4 byte long DCC package detected!", .{}); } else if (self.buffer & @as(BUFFER_TYPE, MASK_5BYTES) == @as(BUFFER_TYPE, COMPARE_5BYTES)) { log.debug("5 byte long DCC package detected!", .{}); } }, } } fn write3bytes(self: *@This()) void { log.debug("3 byte long DCC package detected!", .{}); defer self.reset(); const address: u8 = @truncate(self.buffer >> (2 * (8 + 1) + 1)); const instruction: u8 = @truncate(self.buffer >> (8 + 1) + 1); const checksum: u8 = @truncate(self.buffer >> 1); if (address ^ instruction != checksum) { log.debug("DDC packet invalid, dropping it", .{}); return; } const packet = Packet{ .address = @bitCast(address), .instruction = @bitCast(instruction), }; self.packets.writeItem(packet) catch { log.err("DDC packet FIFO full, dropping packet", .{}); return; }; log.debug("DCC packet added to FIFO", .{}); } fn writeToBuffer(self: *@This(), bit: BitType) void { self.buffer <<= 1; self.buffer |= @intFromEnum(bit); } fn reset(self: *@This()) void { self.buffer = 0; self.state = .preamble; } }; /// Helper functions for testing the DDC parser pub const Test = struct { pub fn sendOne() void { peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_HIGH = 1 }); rp2xxx.time.sleep_us(Parser.TIME_ONEMAX_US / 2); peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_LOW = 1 }); } pub fn sendZero() void { peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_HIGH = 1 }); rp2xxx.time.sleep_us(Parser.TIME_ONEMAX_US * 2); peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_LOW = 1 }); } pub fn sendByte(bits: u8) void { for (0..8) |i| { const msb = bits >> (7 - @as(u3, @intCast(i))); if (@as(u1, @truncate(msb)) == 0) { sendZero(); } else { sendOne(); } } } };