const std = @import("std"); const VSTART = 8; // Theoretical max is 4095 (u12), but CV is only u8, so we have to divide by 16 const VHIGH = 63 * 16 / 16; // ~1000 const VMID = VHIGH * 60 / 100; pub const CV = packed struct { primary_address: u8, vstart: u8, acceleration_rate: u8 = 0, deceleration_rate: u8 = 0, vhigh: u8, vmid: u8, manufacturer_version: u8 = 0, manufacturer_id: u8 = 13, PWM_period: u8, EMF_feedback_cutout: u8, // CV10 packet_timeout: u8, power_source_conversion: u8, AMFS_F1_F8: u8 = 0, AMFS_FL_F9_F12: u8 = 0, decoder_lock: u16 = 0, extended_address: u16, consist_address: u8 = 0, NMRA_reserved20: u8 = 0, // CV20 consist_F1_F8: u8 = 0, consist_FL_F9_F12: u8 = 0, acceleration_adjustment: u8 = 0, deceleration_adjustment: u8 = 0, speed_table_select: u8 = 0, // TODO: not sure it worth the effort if speed PID is working... NMRA_reserved26: u8 = 0, decoder_automatic_stopping: u8 = 0, // TODO: disable setting these values if not implemented bidir_comm_config: u8 = 0, // TODO: disable setting these values if not implemented configuration_data: Configuration, error_information: u8 = 0, // CV30 index_high_byte: u8, index_low_byte: u8, output_loc: u112 = 0, // 33..46 => 14*8=112 manufacturer: Manufacturer = @bitCast(@as(u144, 0)), // 47..64 => 18*8=144 kick_start: u8 = 0, forward_trim: u8 = 0, speed_table: u224 = 0, // 67..94 => 28*8=224 reverse_trim: u8 = 0, NMRA_reserved96_104: u72 = 0, // 96..104 => 9*8=72 user_identifier1: u8 = 0, user_identifier2: u8 = 0, NMRA_reserved107_111: u40 = 0, // 107..111 => 5*8=40 manufacturer2: u1160 = 0, // 112..256 => 145*8=1160 /// Normally the CV table is 1024*8=8192 bits, but this part is unimplemented /// anyway, so we can just drop it for now for quarter size // indexed_area: u2048 = 0, // 257..512 => 256*8=2048 // NMRA_reserved513_879: u2936 = 0, // 513..879 => 367*8=2936 // NMRA_reserved880_891: u96 = 0, // 880..891 => 12*8=96 // decoder_load: u8 = 0, // dynamic_flags: u8 = 0, // fuel_coal: u8 = 0, // water: u8 = 0, // SUSI: u1032 = 0, // 896..1024 => 129*8=1032 const Manufacturer = packed struct(u144) { reserved: u112 = 0, emf: BackEMF = BackEMF{}, }; const BackEMF = packed struct { iterations: u8 = 100, // Number of measurements to calculate Back-EMF delay_us: u8 = 100, // Delay before measurement start low_cutoff: u8 = 15, // Number of discarded elements on the low side high_cutoff: u8 = 15, // Number of discarded elements on the high side }; const Configuration = packed struct { accessory_decoder: bool, reserved: u1 = 0, extended_addressing: bool, speed_table: u1, bidirectional_communication: bool, power_source_conversion: bool, FL_location: u1, direction: u1, }; pub const default: CV = .{ .primary_address = 3, .vstart = VSTART, .vhigh = VHIGH, .vmid = VMID, .PWM_period = 150, // 125_000_000 / PWM_period * 100 + 10_000 (125MHz -> 25kHz) .EMF_feedback_cutout = 1, .packet_timeout = 0, // TODO: S-9.2.4 Section C recommends 20 seconds, not implemented yet .power_source_conversion = 0, // TODO Go to analog mode when CV1==0 .extended_address = 0b11000011_11101000, .index_high_byte = 0b00010000, .index_low_byte = 0b00000000, .configuration_data = .{ .accessory_decoder = false, .extended_addressing = false, .speed_table = 0, // vstart-vmid-vhigh .bidirectional_communication = false, .power_source_conversion = false, .FL_location = 0, // TODO not implemented .direction = 0, // TODO not implemented }, .manufacturer = Manufacturer{}, .speed_table = genSpeedTable(VSTART, VMID, VHIGH), }; /// Expects [0..1023], so for CV29 `cv` should be 28 /// XXX: We cut the CVs, so actually it is [0..255] pub fn getCV(self: @This(), cv: u8) u8 { std.debug.assert(@bitSizeOf(CV) == 2048); std.debug.assert(@sizeOf(CV) == 256); return @truncate(@as(u2048, @bitCast(self)) >> cv * 8); } pub fn validate(self: @This()) void { std.debug.assert(self.primary_address < 128); std.debug.assert(self.extended_address >> 8 >= 0b11000000 and self.extended_address >> 8 <= 0b11100111); // 0b00000000..0b00001111 reserved by NMRA std.debug.assert(self.index_high_byte > 0b00001111); } // XXX: remove pub! pub fn genSpeedTable(start: u8, mid: u8, high: u8) u224 { _ = high; const steps = 28 - 4; var ret: u224 = 0; for (0..steps / 2) |i| { const inc = (mid - start) / (steps / 2); const mask: u8 = @intCast(VSTART + i * inc); const shift: u8 = @intCast(i * 8 + 4 * 8); ret |= @as(u224, mask) << shift; // std.log.debug("step: {d} mask: {d} shift: {d}", .{ i, mask, shift }); } // for (steps / 2..steps) |i| { // const inc = (VHIGH - VMID) / (steps / 2); // ret |= (VMID + i * inc) << (i * 8 + 4 * 8); // } return ret; } };