const std = @import("std");
const builtin = @import("builtin");
const microzig = @import("microzig");
const pico = microzig.hal;
const peripherals = microzig.chip.peripherals;
const interrupt = microzig.cpu.interrupt;
const BAUD_RATE = 115200;
const UART_DEV = 0;
const UART_TX_PIN = 0;
const uart = pico.uart.instance.num(UART_DEV);
const uart_tx_pin = pico.gpio.num(UART_TX_PIN);
const chip = pico.compatibility.chip;
pub const microzig_options: microzig.Options = .{
.log_level = .debug,
.logFn = pico.uart.log,
.interrupts = .{
.TIMER_IRQ_0 = .{ .c = timerInterrupt },
.IO_IRQ_BANK0 = .{ .c = gpioInterrupt },
},
};
const DDC = struct {
const PREAMBLE_MASK = 0b11111111110;
const TIME_ONEMAX = 87 * 1000;
// zig fmt: off
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 PacketStateEnum = enum {
preamble,
package,
};
const BitStateEnum = enum(u1) {
zero,
one,
};
const BUFFER_TYPE = u32; // 10+1 + 5x(8+1)
state: PacketStateEnum = .preamble,
time: microzig.drivers.time.Absolute = @enumFromInt(0),
buffer: BUFFER_TYPE = 0,
fn writeToBuffer(self: *@This(), bit: BitStateEnum) void {
self.buffer <<= 1;
self.buffer |= @intFromEnum(bit);
}
const Test = struct {
fn sendOne() void {
peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_HIGH = 1 });
pico.time.sleep_us(DDC.TIME_ONEMAX / 2);
peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_LOW = 1 });
}
fn sendZero() void {
peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_HIGH = 1 });
pico.time.sleep_us(DDC.TIME_ONEMAX * 2);
peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_LOW = 1 });
}
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();
}
}
}
};
};
var ddc = DDC{};
fn timerInterrupt() callconv(.c) void {
const cs = microzig.interrupt.enter_critical_section();
defer cs.leave();
std.log.info("timer_interrupt called!", .{});
peripherals.TIMER.INTR.modify(.{ .ALARM_0 = 1 });
setAlarm(1_000);
}
fn gpioInterrupt() callconv(.c) void {
// const cs = microzig.interrupt.enter_critical_section();
// defer cs.leave();
// XXX: this could be const without the .Debug code...
var state = peripherals.IO_BANK0.PROC0_INTS1.read();
if (builtin.mode == .Debug) {
const fstate = peripherals.IO_BANK0.PROC0_INTF1.read();
if (fstate.GPIO15_LEVEL_LOW == 1) state.GPIO15_EDGE_LOW = 1;
if (fstate.GPIO15_LEVEL_HIGH == 1) state.GPIO15_EDGE_HIGH = 1;
// disable forcing
peripherals.IO_BANK0.PROC0_INTF1.modify(.{
.GPIO15_EDGE_HIGH = 0,
.GPIO15_EDGE_LOW = 0,
});
}
// aknowledge interrupt
peripherals.IO_BANK0.INTR1.modify(.{ .GPIO15_EDGE_LOW = 1, .GPIO15_EDGE_HIGH = 1 });
// std.log.debug("gpioInterrupt LOW: {d} HIGH: {d}", .{ state.GPIO15_EDGE_LOW, state.GPIO15_EDGE_HIGH });
// rising DDC signal
if (state.GPIO15_EDGE_HIGH == 1) {
ddc.time = pico.time.get_time_since_boot();
return;
}
// falling DDC signal
if (state.GPIO15_EDGE_LOW == 1) {
const time_end = pico.time.get_time_since_boot();
const diff = time_end.diff(ddc.time);
const bit: DDC.BitStateEnum = if (diff.less_than(@enumFromInt(DDC.TIME_ONEMAX))) .one else .zero;
switch (ddc.state) {
.preamble => {
switch (bit) {
.one => {
ddc.writeToBuffer(bit);
},
.zero => {
ddc.writeToBuffer(bit);
std.log.debug("DDC buffer: {b}", .{ddc.buffer});
if (ddc.buffer & DDC.PREAMBLE_MASK == DDC.PREAMBLE_MASK) {
ddc.state = .package;
std.log.debug("DDC state switched to: {}", .{ddc.state});
} else {
std.log.info("DDC preamble is invalid: {b}", .{ddc.buffer});
ddc.buffer = 0;
}
},
}
},
.package => {
ddc.writeToBuffer(bit);
std.log.debug("DDC buffer: {b}", .{ddc.buffer});
if (ddc.buffer & @as(DDC.BUFFER_TYPE, @truncate(DDC.MASK_3BYTES)) == @as(DDC.BUFFER_TYPE, @truncate(DDC.COMPARE_3BYTES))) {
std.log.debug("3 byte long DCC package detected!", .{});
}
},
}
}
}
fn setAlarm(ms: u32) void {
const current = pico.time.get_time_since_boot();
const target = current.add_duration(microzig.drivers.time.Duration.from_ms(ms));
// peripherals.TIMER.ALARM0.write_raw(@intCast(@intFromEnum(target) & 0xffffffff));
peripherals.TIMER.ALARM0.write_raw(@truncate(@intFromEnum(target)));
}
pub fn main() !void {
// init uart logging
uart_tx_pin.set_function(.uart);
uart.apply(.{
.baud_rate = BAUD_RATE,
.clock_config = pico.clock_config,
});
pico.uart.init_logger(uart);
// TODO: could be useful for watchdog updating
// timer interrupt
// setAlarm(1_000);
// peripherals.TIMER.INTE.toggle(.{ .ALARM_0 = 1 });
// interrupt.enable(.TIMER_IRQ_0);
// gpio interrupt
const pin = pico.gpio.num(15);
pin.set_direction(.in);
peripherals.IO_BANK0.PROC0_INTE1.modify(.{ .GPIO15_EDGE_LOW = 1, .GPIO15_EDGE_HIGH = 1 });
interrupt.enable(.IO_IRQ_BANK0);
microzig.cpu.interrupt.enable_interrupts();
// while (true) {
// asm volatile ("wfi");
// }
// preamble
for (0..10) |_| {
DDC.Test.sendOne();
}
DDC.Test.sendZero();
// DDC.Test.send(0b00110111_0_01110100_0_01000011_1);
// address
DDC.Test.sendByte(0b00110111);
DDC.Test.sendZero();
DDC.Test.sendByte(0b01110100);
DDC.Test.sendZero();
DDC.Test.sendByte(0b01000011);
DDC.Test.sendOne();
std.log.info("DDC.buffer: {b}", .{ddc.buffer});
}