From 592e429e18965e8e4e89de4a329b5325fd9f3b10 Mon Sep 17 00:00:00 2001 From: Matt Knight Date: Wed, 3 Apr 2024 03:16:12 -0700 Subject: [PATCH] HAL and demos for verifying hardware subsystems --- build.zig | 91 ++-- src/badge.zig | 55 +++ src/badge/demos/audio.zig | 35 ++ src/badge/demos/blinky.zig | 21 + src/badge/demos/blinky_timer.zig | 23 ++ src/badge/demos/buttons.zig | 98 +++++ src/badge/demos/lcd.zig | 383 +++++++++++++++++ src/badge/demos/light_sensor.zig | 46 +++ src/badge/demos/neopixels.zig | 58 +++ src/badge/demos/qspi.zig | 25 ++ src/badge/demos/usb_cdc.zig | 683 +++++++++++++++++++++++++++++++ src/badge/demos/usb_storage.zig | 4 + src/board.zig | 77 ++++ src/hal.zig | 10 + src/hal/adc.zig | 147 +++++++ src/hal/clocks.zig | 132 ++++++ src/hal/gclk.zig | 95 +++++ src/hal/gpio.zig | 81 ++++ src/hal/mclk.zig | 194 +++++++++ src/hal/nvm.zig | 65 +++ src/hal/pins.zig | 109 +++++ src/hal/port.zig | 102 +++++ src/hal/qspi.zig | 47 +++ src/hal/sercom.zig | 170 ++++++++ src/hal/timer.zig | 177 ++++++++ src/hal/usb.zig | 153 +++++++ 26 files changed, 3056 insertions(+), 25 deletions(-) create mode 100644 src/badge.zig create mode 100644 src/badge/demos/audio.zig create mode 100644 src/badge/demos/blinky.zig create mode 100644 src/badge/demos/blinky_timer.zig create mode 100644 src/badge/demos/buttons.zig create mode 100644 src/badge/demos/lcd.zig create mode 100644 src/badge/demos/light_sensor.zig create mode 100644 src/badge/demos/neopixels.zig create mode 100644 src/badge/demos/qspi.zig create mode 100644 src/badge/demos/usb_cdc.zig create mode 100644 src/badge/demos/usb_storage.zig create mode 100644 src/board.zig create mode 100644 src/hal.zig create mode 100644 src/hal/adc.zig create mode 100644 src/hal/clocks.zig create mode 100644 src/hal/gclk.zig create mode 100644 src/hal/gpio.zig create mode 100644 src/hal/mclk.zig create mode 100644 src/hal/nvm.zig create mode 100644 src/hal/pins.zig create mode 100644 src/hal/port.zig create mode 100644 src/hal/qspi.zig create mode 100644 src/hal/sercom.zig create mode 100644 src/hal/timer.zig create mode 100644 src/hal/usb.zig diff --git a/build.zig b/build.zig index 4244f40..2d317c3 100644 --- a/build.zig +++ b/build.zig @@ -10,33 +10,74 @@ pub const py_badge: MicroZig.Target = .{ .hal = null, }; +pub const sycl_badge_2024 = MicroZig.Target{ + .preferred_format = .elf, + .chip = atsam.chips.atsamd51j19.chip, + .hal = .{ + .root_source_file = .{ .cwd_relative = "src/hal.zig" }, + }, + .board = .{ + .name = "SYCL Badge 2024", + .root_source_file = .{ .cwd_relative = "src/board.zig" }, + }, +}; + pub fn build(b: *Build) void { const mz = MicroZig.init(b, .{}); const optimize = b.standardOptimizeOption(.{}); - const fw_options = b.addOptions(); - fw_options.addOption(bool, "have_cart", false); - - const modified_memory_regions = b.allocator.dupe(MicroZig.MemoryRegion, py_badge.chip.memory_regions) catch @panic("out of memory"); - for (modified_memory_regions) |*memory_region| { - if (memory_region.kind != .ram) continue; - memory_region.offset += 0x19A0; - memory_region.length -= 0x19A0; - break; - } - var modified_py_badge = py_badge; - modified_py_badge.chip.memory_regions = modified_memory_regions; + //const fw_options = b.addOptions(); + //fw_options.addOption(bool, "have_cart", false); - const fw = mz.add_firmware(b, .{ - .name = "pybadge-io", - .target = modified_py_badge, + //const modified_memory_regions = b.allocator.dupe(MicroZig.MemoryRegion, py_badge.chip.memory_regions) catch @panic("out of memory"); + //for (modified_memory_regions) |*memory_region| { + // if (memory_region.kind != .ram) continue; + // memory_region.offset += 0x19A0; + // memory_region.length -= 0x19A0; + // break; + //} + //var modified_py_badge = py_badge; + //modified_py_badge.chip.memory_regions = modified_memory_regions; + + //const fw = mz.add_firmware(b, .{ + // .name = "pybadge-io", + // .target = modified_py_badge, + // .optimize = optimize, + // .source_file = .{ .path = "src/main.zig" }, + //}); + //fw.artifact.step.dependOn(&fw_options.step); + //fw.modules.app.addImport("options", fw_options.createModule());x + //mz.install_firmware(b, fw, .{}); + //mz.install_firmware(b, fw, .{ .format = .{ .uf2 = .SAMD51 } }); + + const badge = mz.add_firmware(b, .{ + .name = "badge", + .target = sycl_badge_2024, .optimize = optimize, - .source_file = .{ .path = "src/main.zig" }, + .root_source_file = .{ .path = "src/badge.zig" }, }); - fw.artifact.step.dependOn(&fw_options.step); - fw.modules.app.addImport("options", fw_options.createModule()); - mz.install_firmware(b, fw, .{}); - mz.install_firmware(b, fw, .{ .format = .{ .uf2 = .SAMD51 } }); + mz.install_firmware(b, badge, .{}); + + inline for (.{ + "blinky", + "blinky_timer", + "usb_cdc", + "usb_storage", + "buttons", + "lcd", + "audio", + "light_sensor", + "neopixels", + "qspi", + }) |name| { + const mvp = mz.add_firmware(b, .{ + .name = std.fmt.comptimePrint("badge.demo.{s}", .{name}), + .target = sycl_badge_2024, + .optimize = optimize, + .root_source_file = .{ .path = std.fmt.comptimePrint("src/badge/demos/{s}.zig", .{name}) }, + }); + mz.install_firmware(b, mvp, .{}); + } } pub const Cart = struct { @@ -47,7 +88,7 @@ pub const Cart = struct { pub const CartOptions = struct { name: []const u8, optimize: std.builtin.OptimizeMode, - source_file: Build.LazyPath, + root_source_file: Build.LazyPath, }; pub fn add_cart( @@ -57,7 +98,7 @@ pub fn add_cart( ) *Cart { const cart_lib = b.addStaticLibrary(.{ .name = "cart", - .root_source_file = options.source_file, + .root_source_file = options.root_source_file, .target = py_badge.chip.cpu.getDescriptor().target, .optimize = options.optimize, .link_libc = false, @@ -65,7 +106,7 @@ pub fn add_cart( .use_llvm = true, .use_lld = true, }); - cart_lib.addModule("wasm4", d.builder.createModule(.{ .source_file = .{ .path = "src/wasm4.zig" } })); + cart_lib.addModule("wasm4", d.builder.createModule(.{ .root_source_file = .{ .path = "src/wasm4.zig" } })); const fw_options = b.addOptions(); fw_options.addOption(bool, "have_cart", true); @@ -75,8 +116,8 @@ pub fn add_cart( .name = options.name, .target = py_badge, .optimize = .Debug, // TODO - .source_file = .{ .path = "src/main.zig" }, - .linker_script = .{ .source_file = .{ .path = "src/cart.ld" } }, + .root_source_file = .{ .path = "src/main.zig" }, + .linker_script = .{ .root_source_file = .{ .path = "src/cart.ld" } }, }); fw.artifact.linkLibrary(cart_lib); fw.artifact.step.dependOn(&fw_options.step); diff --git a/src/badge.zig b/src/badge.zig new file mode 100644 index 0000000..d6cfbc1 --- /dev/null +++ b/src/badge.zig @@ -0,0 +1,55 @@ +//! This is firmware for the SYCL badge. +//! +//! The badge image +//! For normal operation, the default app will run. Pressing select will go to +//! the main menu. The default app will display the SYCL logo, users will be +//! able to change the default app. +//! +//! Apps will have the option to to save state to non-volatile memory. This +//! will prompt the user. The user will either exit without saving, save and +//! exit, or cancel. +//! +//! TODO: +//! - USB mass drive +//! - USB CDC logging +const std = @import("std"); + +const microzig = @import("microzig"); +const board = microzig.board; +const hal = microzig.hal; +const usb = hal.usb; + +const peripherals = microzig.chip.peripherals; +const MCLK = peripherals.MCLK; + +const led_pin = board.D13; +const backlight = board.TFT_LITE; +const @"D+" = board.@"D+"; +const @"D-" = board.@"D-"; + +pub fn main() !void { + // Initialize clocks + MCLK.AHBMASK.modify(.{ .USB_ = 1 }); + MCLK.APBBMASK.modify(.{ .USB_ = 1 }); + + // Initialize pins + led_pin.set_dir(.out); + backlight.set_dir(.out); + @"D+".set_mux(.H); + @"D-".set_mux(.H); + + backlight.write(.low); + + const period = 200000; + while (true) { + delay_count(period); + led_pin.write(.high); + delay_count(period); + led_pin.write(.low); + } +} + +fn delay_count(count: u32) void { + var i: u32 = 0; + while (i < count) : (i += 1) {} +} diff --git a/src/badge/demos/audio.zig b/src/badge/demos/audio.zig new file mode 100644 index 0000000..668c65f --- /dev/null +++ b/src/badge/demos/audio.zig @@ -0,0 +1,35 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const gclk = hal.gclk; +const timer = hal.timer; + +const spkr_en_pin = microzig.board.SPKR_EN; +const analog_out_pin = microzig.board.A0; +const led_pin = microzig.board.D13; + +pub fn main() !void { + spkr_en_pin.set_dir(.out); + analog_out_pin.set_dir(.out); + led_pin.set_dir(.out); + + analog_out_pin.write(.low); + spkr_en_pin.write(.high); + + gclk.enable_generator(.GCLK1, .DFLL, .{ + .divsel = .DIV1, + .div = 48, + }); + + timer.init(); + while (true) { + led_pin.toggle(); + analog_out_pin.toggle(); + timer.delay_us(std.time.us_per_ms); + } +} + +fn delay_count(count: u32) void { + var i: u32 = 0; + while (i < count) : (i += 1) {} +} diff --git a/src/badge/demos/blinky.zig b/src/badge/demos/blinky.zig new file mode 100644 index 0000000..125a860 --- /dev/null +++ b/src/badge/demos/blinky.zig @@ -0,0 +1,21 @@ +const microzig = @import("microzig"); + +const led_pin = microzig.board.D13; + +pub fn main() !void { + // Initialize pins + led_pin.set_dir(.out); + + const period = 200000; + while (true) { + delay_count(period); + led_pin.write(.high); + delay_count(period); + led_pin.write(.low); + } +} + +fn delay_count(count: u32) void { + var i: u32 = 0; + while (i < count) : (i += 1) {} +} diff --git a/src/badge/demos/blinky_timer.zig b/src/badge/demos/blinky_timer.zig new file mode 100644 index 0000000..650a5b5 --- /dev/null +++ b/src/badge/demos/blinky_timer.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const timer = hal.timer; +const gclk = hal.gclk; + +const led_pin = microzig.board.D13; + +pub fn main() !void { + // Initialize pins + led_pin.set_dir(.out); + + gclk.enable_generator(.GCLK1, .DFLL, .{ + .divsel = .DIV1, + .div = 48, + }); + + timer.init(); + while (true) { + led_pin.toggle(); + timer.delay_us(1 * std.time.us_per_s); + } +} diff --git a/src/badge/demos/buttons.zig b/src/badge/demos/buttons.zig new file mode 100644 index 0000000..34011af --- /dev/null +++ b/src/badge/demos/buttons.zig @@ -0,0 +1,98 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const board = microzig.board; + +// pins +const Buttons = board.Buttons; +const led_pin = board.D13; + +const Symbol = enum { + dot, + dash, + + fn blink(symbol: Symbol, pin: microzig.hal.port.Pin) void { + pin.write(.high); + delay_count(switch (symbol) { + .dot => unit_period_count, + .dash => 3 * unit_period_count, + }); + pin.write(.low); + delay_count(unit_period_count); + } +}; + +const unit_period_count = 200000; + +const Mapping = struct { + character: u8, + symbols: []const Symbol, +}; + +const alphabet = [_]Mapping{ + .{ .character = 'a', .symbols = &.{ .dot, .dash } }, // A: .- + .{ .character = 'b', .symbols = &.{ .dash, .dot, .dot, .dot } }, // B: -... + .{ .character = 'c', .symbols = &.{ .dash, .dot, .dash, .dot } }, // C: -.-. + .{ .character = 'd', .symbols = &.{ .dash, .dot, .dot } }, // D: -.. + .{ .character = 'e', .symbols = &.{.dot} }, // E: . + .{ .character = 'f', .symbols = &.{ .dot, .dot, .dash, .dot } }, // F: ..-. + .{ .character = 'g', .symbols = &.{ .dash, .dash, .dot } }, // G: --. + .{ .character = 'h', .symbols = &.{ .dot, .dot, .dot, .dot } }, // H: .... + .{ .character = 'i', .symbols = &.{ .dot, .dot } }, // I: .. + .{ .character = 'j', .symbols = &.{ .dot, .dash, .dash, .dash } }, // J: .--- + .{ .character = 'k', .symbols = &.{ .dash, .dot, .dash } }, // K: -.- + .{ .character = 'l', .symbols = &.{ .dot, .dash, .dot, .dot } }, // L: .-.. + .{ .character = 'm', .symbols = &.{ .dash, .dash } }, // M: -- + .{ .character = 'n', .symbols = &.{ .dash, .dot } }, // N: -. + .{ .character = 'o', .symbols = &.{ .dash, .dash, .dash } }, // O: --- + .{ .character = 'p', .symbols = &.{ .dot, .dash, .dash, .dot } }, // P: .--. + .{ .character = 'q', .symbols = &.{ .dash, .dash, .dot, .dash } }, // Q: --.- + .{ .character = 'r', .symbols = &.{ .dot, .dash, .dot } }, // R: .-. + .{ .character = 's', .symbols = &.{ .dot, .dot, .dot } }, // S: ... + .{ .character = 't', .symbols = &.{.dash} }, // T: - + .{ .character = 'u', .symbols = &.{ .dot, .dot, .dash } }, // U: ..- + .{ .character = 'v', .symbols = &.{ .dot, .dot, .dot, .dash } }, // V: ...- + .{ .character = 'w', .symbols = &.{ .dot, .dash, .dash } }, // W: .-- + .{ .character = 'x', .symbols = &.{ .dash, .dot, .dot, .dash } }, // X: -..- + .{ .character = 'y', .symbols = &.{ .dash, .dot, .dash, .dash } }, // Y: -.-- + .{ .character = 'z', .symbols = &.{ .dash, .dash, .dot, .dot } }, // Z: --.. +}; + +fn get_symbols(character: u8) []const Symbol { + return for (alphabet) |entry| { + if (entry.character == character) + break entry.symbols; + } else unreachable; +} + +pub fn main() !void { + Buttons.configure(); + // Use morse code to convey which button is currently pressed + led_pin.set_dir(.out); + + while (true) { + const message: []const u8 = blk: { + const buttons = Buttons.read_from_port(); + inline for (@typeInfo(Buttons).Struct.fields) |field| { + if (@field(buttons, field.name) == 1) + break :blk field.name; + } + + continue; + }; + + for (message) |character| { + const symbols = get_symbols(character); + for (symbols) |symbol| symbol.blink(led_pin); + + // there's supposed to be 3, but we already wait one period after every symbol + delay_count(2 * unit_period_count); + } + + delay_count(5 * unit_period_count); + } +} + +fn delay_count(count: u32) void { + var i: u32 = 0; + while (i < count) : (i += 1) {} +} diff --git a/src/badge/demos/lcd.zig b/src/badge/demos/lcd.zig new file mode 100644 index 0000000..3b55029 --- /dev/null +++ b/src/badge/demos/lcd.zig @@ -0,0 +1,383 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +const hal = microzig.hal; +const mclk = hal.mclk; +const gclk = hal.gclk; +const sercom = hal.sercom; +const port = hal.port; +const timer = hal.timer; + +const board = microzig.board; +const tft_rst_pin = board.TFT_RST; +const tft_lite_pin = board.TFT_LITE; +const tft_dc_pin = board.TFT_DC; +const tft_cs_pin = board.TFT_CS; +const tft_sck_pin = board.TFT_SCK; +const tft_mosi_pin = board.TFT_MOSI; + +pub var fb: FrameBuffer = .{ .bpp24 = .{.{.{ .r = 0, .g = 0, .b = 0 }} ** height} ** width }; + +pub const FrameBuffer = union { + bpp12: [width][@divExact(height, 2)]Color12, + bpp16: [width][height]Color16, + bpp24: [width][height]Color24, +}; +pub const Bpp = std.meta.FieldEnum(FrameBuffer); +pub const Color12 = extern struct { + b0_g0: packed struct(u8) { b0: u4, g0: u4 }, + r0_b1: packed struct(u8) { r0: u4, b1: u4 }, + g1_r1: packed struct(u8) { g1: u4, r1: u4 }, +}; +pub const Color16 = packed struct(u16) { b: u5, g: u6, r: u5 }; +pub const Color24 = extern struct { b: u8, g: u8, r: u8 }; +pub const Rect = struct { x: u8, y: u8, width: u8, height: u8 }; + +pub const width = 160; +pub const height = 128; + +pub const black16: Color16 = .{ .r = 0x00, .g = 0x00, .b = 0x00 }; +pub const red16: Color16 = .{ .r = 0x1f, .g = 0x00, .b = 0x00 }; +pub const green16: Color16 = .{ .r = 0x00, .g = 0x3f, .b = 0x00 }; +pub const blue16: Color16 = .{ .r = 0x00, .g = 0x00, .b = 0x1f }; +pub const white16: Color16 = .{ .r = 0x1f, .g = 0x3f, .b = 0x1f }; + +pub const black24: Color24 = .{ .r = 0x00, .g = 0x00, .b = 0x00 }; +pub const red24: Color24 = .{ .r = 0xff, .g = 0x00, .b = 0x00 }; +pub const green24: Color24 = .{ .r = 0x00, .g = 0xff, .b = 0x00 }; +pub const blue24: Color24 = .{ .r = 0x00, .g = 0x00, .b = 0xff }; +pub const white24: Color24 = .{ .r = 0xff, .g = 0xff, .b = 0xff }; + +const Lcd = struct { + spi: sercom.spi.Master, + pins: Pins, + + pub const Pins = struct { + rst: port.Pin, + lite: port.Pin, + dc: port.Pin, + cs: port.Pin, + sck: port.Pin, + mosi: port.Pin, + }; + + pub const InitOptions = struct { + spi: sercom.spi.Master, + pins: Pins, + bpp: Bpp, + }; + + pub fn init(opts: InitOptions) Lcd { + // initialize pins + const lcd = Lcd{ + .spi = opts.spi, + .pins = opts.pins, + }; + + // TODO: I think this has to be initialized before init atm + //lcd.pins.rst.set_dir(.out); + //lcd.pins.lite.set_dir(.out); + //lcd.pins.dc.set_dir(.out); + //lcd.pins.cs.set_dir(.out); + //lcd.pins.sck.set_dir(.out); + //lcd.pins.mosi.set_dir(.out); + + lcd.pins.cs.write(.high); + lcd.pins.dc.write(.high); + lcd.pins.sck.write(.high); + lcd.pins.mosi.write(.high); + + lcd.pins.lite.write(.high); + lcd.pins.rst.write(.low); + timer.delay_us(20 * std.time.us_per_ms); + lcd.pins.rst.write(.high); + timer.delay_us(20 * std.time.us_per_ms); + + lcd.send_cmd(ST7735.SWRESET, &.{}, 120 * std.time.us_per_ms); + lcd.send_cmd(ST7735.SLPOUT, &.{}, 120 * std.time.us_per_ms); + lcd.send_cmd(ST7735.INVOFF, &.{}, 1); + lcd.send_cmd(ST7735.COLMOD, &.{@intFromEnum(@as(ST7735.COLMOD_PARAM0, switch (opts.bpp) { + .bpp12 => .@"12BPP", + .bpp16 => .@"16BPP", + .bpp24 => .@"24BPP", + }))}, 1); + lcd.send_cmd(ST7735.MADCTL, &.{@as(u8, @bitCast(ST7735.MADCTL_PARAM0{ + .MH = .LEFT_TO_RIGHT, + .RGB = .BGR, + .ML = .TOP_TO_BOTTOM, + .MV = false, + .MX = false, + .MY = true, + }))}, 1); + lcd.send_cmd(ST7735.GMCTRP1, &.{ + 0x02, 0x1c, 0x07, 0x12, + 0x37, 0x32, 0x29, 0x2d, + 0x29, 0x25, 0x2B, 0x39, + 0x00, 0x01, 0x03, 0x10, + }, 1); + lcd.send_cmd(ST7735.NORON, &.{}, 10 * std.time.us_per_ms); + lcd.send_cmd(ST7735.DISPON, &.{}, 10 * std.time.us_per_ms); + lcd.send_cmd(ST7735.RAMWR, &.{}, 1); + + //if (dma.enable) { + // Port.TFT_CS.write(.low); + // timer.delay(1); + // dma.init_lcd(bpp); + //} + + return lcd; + } + + fn send_cmd(lcd: Lcd, cmd: u8, params: []const u8, delay_us: u32) void { + timer.delay_us(1); + lcd.pins.cs.write(.low); + lcd.pins.dc.write(.low); + timer.delay_us(1); + lcd.spi.write_blocking(cmd); + timer.delay_us(1); + lcd.pins.dc.write(.high); + lcd.spi.write_all_blocking(params); + timer.delay_us(1); + lcd.pins.cs.write(.high); + timer.delay_us(delay_us); + } + + pub fn invert(lcd: Lcd) void { + lcd.stop(); + defer lcd.start(); + + inverted = !inverted; + lcd.send_cmd(switch (inverted) { + false => ST7735.INVOFF, + true => ST7735.INVON, + }, &.{}, 1); + } + + pub fn send_color(lcd: Lcd, color: Color16, count: u32) void { + timer.delay_us(1); + lcd.pins.cs.write(.low); + lcd.pins.dc.write(.low); + timer.delay_us(1); + lcd.spi.write_blocking(ST7735.RAMWR); + timer.delay_us(1); + lcd.pins.dc.write(.high); + for (0..count) |_| { + const raw: u16 = @bitCast(color); + lcd.spi.write_blocking(@truncate(raw >> 8)); + lcd.spi.write_blocking(@truncate(raw)); + } + + timer.delay_us(1); + lcd.pins.cs.write(.high); + timer.delay_us(1); + } + + pub fn clear_screen(lcd: Lcd, color: Color16) void { + lcd.set_window(0, 0, 128, 160); + lcd.send_color(color, 128 * 160); + } + + pub fn set_window(lcd: Lcd, x0: u8, y0: u8, x1: u8, y1: u8) void { + lcd.send_cmd(ST7735.CASET, &.{ 0x00, x0, 0x00, x1 }, 1); + lcd.send_cmd(ST7735.RASET, &.{ 0x00, y0, 0x00, y1 }, 1); + } + + pub fn fill16(color: Color16) void { + for (&fb.bpp16) |*col| @memset(col, color); + } + + pub fn fill24(color: Color24) void { + for (&fb.bpp24) |*col| @memset(col, color); + } + + pub fn rect16(rect: Rect, fill: Color16, line: Color16) void { + for (0..rect.height) |yo| { + fb.bpp16[rect.x][rect.y + yo] = line; + } + for (0..rect.width) |xo| { + fb.bpp16[rect.x + xo][rect.y] = line; + @memset(fb.bpp16[rect.x + xo][rect.y + 1 .. rect.y + rect.height - 1], fill); + fb.bpp16[rect.x + xo][rect.y + rect.height - 1] = line; + } + for (0..rect.height) |yo| { + fb.bpp16[rect.x + rect.width - 1][rect.y + yo] = line; + } + } + + pub fn rect24(rect: Rect, fill: Color24, line: Color24) void { + for (0..rect.height) |yo| { + fb.bpp24[rect.x][rect.y + yo] = line; + } + for (1..rect.width - 1) |xo| { + fb.bpp24[rect.x + xo][rect.y] = line; + @memset(fb.bpp24[rect.x + xo][rect.y + 1 .. rect.y + rect.height - 1], fill); + fb.bpp24[rect.x + xo][rect.y + rect.height - 1] = line; + } + for (0..rect.height) |yo| { + fb.bpp24[rect.x + rect.width - 1][rect.y + yo] = line; + } + } + + pub fn blit12(lcd: Lcd) void { + //if (!dma.enable) + lcd.send_cmd(ST7735.RAMWR, std.mem.asBytes(&fb.bpp12), 1); + } + + pub fn blit16(lcd: Lcd) void { + //if (!dma.enable) + lcd.send_cmd(ST7735.RAMWR, std.mem.asBytes(&fb.bpp16), 1); + } + + pub fn blit24(lcd: Lcd) void { + //if (!dma.enable) + lcd.send_cmd(ST7735.RAMWR, std.mem.asBytes(&fb.bpp24), 1); + } + + fn start(lcd: Lcd) void { + _ = lcd; + //if (dma.enable) { + // send_cmd(ST7735.RAMWR, &.{}, 1); + // Port.TFT_CS.write(.low); + // timer.delay(1); + // dma.start_lcd(); + //} + } + + fn stop(lcd: Lcd) void { + _ = lcd; + //if (dma.enable) { + // dma.stop_lcd(); + // timer.delay(1); + // Port.TFT_CS.write(.high); + // timer.delay(1); + //} + } +}; + +pub fn main() !void { + tft_rst_pin.set_dir(.out); + tft_lite_pin.set_dir(.out); + tft_dc_pin.set_dir(.out); + tft_cs_pin.set_dir(.out); + tft_sck_pin.set_dir(.out); + tft_mosi_pin.set_dir(.out); + + tft_sck_pin.set_mux(.C); + tft_mosi_pin.set_mux(.C); + + gclk.enable_generator(.GCLK1, .DFLL, .{ + .divsel = .DIV1, + .div = 48, + }); + + gclk.set_peripheral_clk_gen(.GCLK_SERCOM4_CORE, .GCLK0); + + // TODO: pin and clock configuration + mclk.set_apb_mask(.{ + .SERCOM4 = .enabled, + .TC0 = .enabled, + .TC1 = .enabled, + }); + + timer.init(); + const lcd = Lcd.init(.{ + .spi = sercom.spi.Master.init(.SERCOM4, .{ + .cpha = .LEADING_EDGE, + .cpol = .IDLE_LOW, + .dord = .MSB, + .dopo = .PAD2, + .ref_freq_hz = 48_000_000, + .baud_freq_hz = 4_000_000, + }), + .pins = .{ + .rst = tft_rst_pin, + .lite = tft_lite_pin, + .dc = tft_dc_pin, + .cs = tft_cs_pin, + .sck = tft_sck_pin, + .mosi = tft_mosi_pin, + }, + .bpp = .bpp16, + }); + + lcd.clear_screen(red16); + lcd.set_window(0, 0, 10, 10); + + //Lcd.fill16(red16); + timer.delay_us(5 * std.time.us_per_s); + lcd.invert(); + while (true) {} +} + +var inverted = false; + +const ST7735 = struct { + const NOP = 0x00; + const SWRESET = 0x01; + const RDDID = 0x04; + const RDDST = 0x09; + + const SLPIN = 0x10; + const SLPOUT = 0x11; + const PTLON = 0x12; + const NORON = 0x13; + + const INVOFF = 0x20; + const INVON = 0x21; + const DISPOFF = 0x28; + const DISPON = 0x29; + const CASET = 0x2A; + const RASET = 0x2B; + const RAMWR = 0x2C; + const RAMRD = 0x2E; + + const PTLAR = 0x30; + const COLMOD = 0x3A; + const COLMOD_PARAM0 = enum(u8) { + @"12BPP" = 0b011, + @"16BPP" = 0b101, + @"24BPP" = 0b110, + }; + const MADCTL = 0x36; + const MADCTL_PARAM0 = packed struct(u8) { + reserved: u2 = 0, + MH: enum(u1) { + LEFT_TO_RIGHT = 0, + RIGHT_TO_LEFT = 1, + }, + RGB: enum(u1) { + RGB = 0, + BGR = 1, + }, + ML: enum(u1) { + TOP_TO_BOTTOM = 0, + BOTTOM_TO_TOP = 1, + }, + MV: bool, + MX: bool, + MY: bool, + }; + + const FRMCTR1 = 0xB1; + const FRMCTR2 = 0xB2; + const FRMCTR3 = 0xB3; + const INVCTR = 0xB4; + const DISSET5 = 0xB6; + + const PWCTR1 = 0xC0; + const PWCTR2 = 0xC1; + const PWCTR3 = 0xC2; + const PWCTR4 = 0xC3; + const PWCTR5 = 0xC4; + const VMCTR1 = 0xC5; + + const RDID1 = 0xDA; + const RDID2 = 0xDB; + const RDID3 = 0xDC; + const RDID4 = 0xDD; + + const PWCTR6 = 0xFC; + + const GMCTRP1 = 0xE0; + const GMCTRN1 = 0xE1; +}; diff --git a/src/badge/demos/light_sensor.zig b/src/badge/demos/light_sensor.zig new file mode 100644 index 0000000..c77169b --- /dev/null +++ b/src/badge/demos/light_sensor.zig @@ -0,0 +1,46 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +const hal = microzig.hal; +const gclk = hal.gclk; +const mclk = hal.mclk; +const timer = hal.timer; + +const adc0 = hal.adc.num(0); + +const board = microzig.board; +const led_pin = microzig.board.D13; +const light_sensor_pin = microzig.board.A7_LIGHT; + +pub fn main() !void { + // enable pins + led_pin.set_dir(.out); + light_sensor_pin.set_mux(.B); + + // enable clocks + gclk.enable_generator(.GCLK1, .DFLL, .{ + .divsel = .DIV1, + .div = 48, + }); + + mclk.set_apb_mask(.{ .ADC0 = .enabled }); + gclk.set_peripheral_clk_gen(.GCLK_ADC0, .GCLK0); + + // configure ADC + adc0.init(); + timer.init(); + while (true) { + const reading = adc0.single_shot_blocking(.AIN6); + + led_pin.write(.high); + timer.delay_us(@as(u32, 100) * reading); + + led_pin.write(.low); + timer.delay_us(@as(u32, 100) * reading); + } +} + +fn delay_count(count: u32) void { + var i: u32 = 0; + while (i < count) : (i += 1) {} +} diff --git a/src/badge/demos/neopixels.zig b/src/badge/demos/neopixels.zig new file mode 100644 index 0000000..2cff3c6 --- /dev/null +++ b/src/badge/demos/neopixels.zig @@ -0,0 +1,58 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const gclk = hal.gclk; +const timer = hal.timer; + +const neopixel_pin = microzig.board.D8_NEOPIX; + +const white: u24 = 0xFFA; +const red: u24 = 0x0F0; + +pub fn main() !void { + neopixel_pin.set_dir(.out); + + gclk.enable_generator(.GCLK1, .DFLL, .{ + .divsel = .DIV1, + .div = 1, + }); + + timer.init(); + reset(); + + write_color(red); + write_color(white); + write_color(red); + write_color(white); + write_color(red); + + while (true) {} +} + +fn reset() void { + neopixel_pin.write(.low); + timer.delay_us(3840); +} + +fn write_color(color: u24) void { + for (0..24) |i| { + if (color & @as(u24, 1) << @as(u5, @intCast(i)) == 0) + write_zero() + else + write_one(); + } +} + +fn write_one() void { + neopixel_pin.write(.high); + timer.delay_us(14); + neopixel_pin.write(.low); + timer.delay_us(43); +} + +fn write_zero() void { + neopixel_pin.write(.high); + timer.delay_us(29); + neopixel_pin.write(.low); + timer.delay_us(29); +} diff --git a/src/badge/demos/qspi.zig b/src/badge/demos/qspi.zig new file mode 100644 index 0000000..93e0813 --- /dev/null +++ b/src/badge/demos/qspi.zig @@ -0,0 +1,25 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const board = microzig.board; +const hal = microzig.hal; +const gclk = hal.gclk; +const mclk = hal.mclk; +const qspi = hal.qspi; +const nvm = hal.nvm; + +const ext_flash: *u8 = @ptrFromInt(0x0400_0000); + +pub fn main() !void { + mclk.set_apb_mask(.{ + .QSPI = .enabled, + }); + mclk.set_ahb_mask(.{ + .QSPI = .enabled, + // only needed for DDR (TODO: what is that?) + .QSPI_2X = .stopped, + }); + + qspi.init(); + + while (true) {} +} diff --git a/src/badge/demos/usb_cdc.zig b/src/badge/demos/usb_cdc.zig new file mode 100644 index 0000000..ccaa7c9 --- /dev/null +++ b/src/badge/demos/usb_cdc.zig @@ -0,0 +1,683 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const board = microzig.board; +const hal = microzig.hal; +const usb = hal.usb; +const mclk = hal.mclk; +const gclk = hal.gclk; + +const io_types = microzig.chip.types.peripherals; + +const peripherals = microzig.chip.peripherals; +const MCLK = peripherals.MCLK; +const GCLK = peripherals.GCLK; +const USB = peripherals.USB; +const NVMCTRL = struct { + pub const SW0: *volatile io_types.FUSES.SW0_FUSES = @ptrFromInt(0x00800080); +}; + +// pins +const @"D+" = board.@"D+"; +const @"D-" = board.@"D-"; + +// USB state +var endpoint_buffer_storage: [8][2][64]u8 align(4) = .{.{.{0} ** 64} ** 2} ** 8; +const endpoint_buffer: *align(4) volatile [8][2][64]u8 = &endpoint_buffer_storage; +const setup = std.mem.bytesAsValue(Setup, endpoint_buffer[0][0][0..8]); + +var endpoint_table_storage: [8]io_types.USB.USB_DESCRIPTOR align(4) = undefined; +const endpoint_table: *align(4) volatile [8]io_types.USB.USB_DESCRIPTOR = &endpoint_table_storage; + +var current_configuration: u8 = 0; + +const cdc = struct { + var current_connection: u8 = 0; +}; + +const Setup = extern struct { + bmRequestType: packed struct(u8) { + recipient: enum(u5) { + device, + interface, + endpoint, + other, + _, + }, + kind: enum(u2) { + standard, + class, + vendor, + _, + }, + dir: enum(u1) { + out, + in, + }, + }, + bRequest: u8, + wValue: u16, + wIndex: u16, + wLength: u16, + + const standard = struct { + const Request = enum(u8) { + GET_STATUS = 0, + CLEAR_FEATURE = 1, + SET_FEATURE = 3, + SET_ADDRESS = 5, + GET_DESCRIPTOR = 6, + SET_DESCRIPTOR = 7, + GET_CONFIGURATION = 8, + SET_CONFIGURATION = 9, + GET_INTERFACE = 10, + SET_INTERFACE = 11, + SYNC_FRAME = 12, + _, + }; + const DescriptorType = enum(u8) { + DEVICE = 1, + CONFIGURATION = 2, + STRING = 3, + INTERFACE = 4, + ENDPOINT = 5, + DEVICE_QUALIFIER = 6, + OTHER_SPEED_CONFIGURATION = 7, + INTERFACE_POWER = 8, + _, + }; + }; + + const cdc = struct { + const Request = enum(u8) { + SEND_ENCAPSULATED_COMMAND = 0x00, + GET_ENCAPSULATED_RESPONSE = 0x01, + SET_COMM_FEATURE = 0x02, + GET_COMM_FEATURE = 0x03, + CLEAR_COMM_FEATURE = 0x04, + + SET_AUX_LINE_STATE = 0x10, + SET_HOOK_STATE = 0x11, + PULSE_SETUP = 0x12, + SEND_PULSE = 0x13, + SET_PULSE_TIME = 0x14, + RING_AUX_JACK = 0x15, + + SET_LINE_CODING = 0x20, + GET_LINE_CODING = 0x21, + SET_CONTROL_LINE_STATE = 0x22, + SEND_BREAK = 0x23, + + SET_RINGER_PARMS = 0x30, + GET_RINGER_PARMS = 0x31, + SET_OPERATION_PARMS = 0x32, + GET_OPERATION_PARMS = 0x33, + SET_LINE_PARMS = 0x34, + GET_LINE_PARMS = 0x35, + DIAL_DIGITS = 0x36, + SET_UNIT_PARAMETER = 0x37, + GET_UNIT_PARAMETER = 0x38, + CLEAR_UNIT_PARAMETER = 0x39, + GET_PROFILE = 0x3A, + + SET_ETHERNET_MULTICAST_FILTERS = 0x40, + SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41, + GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42, + SET_ETHERNET_PACKET_FILTER = 0x43, + GET_ETHERNET_STATISTIC = 0x44, + + SET_ATM_DATA_FORMAT = 0x50, + GET_ATM_DEVICE_STATISTICS = 0x51, + SET_ATM_DEFAULT_VC = 0x52, + GET_ATM_VC_STATISTICS = 0x53, + + GET_NTB_PARAMETERS = 0x80, + GET_NET_ADDRESS = 0x81, + SET_NET_ADDRESS = 0x82, + GET_NTB_FORMAT = 0x83, + SET_NTB_FORMAT = 0x84, + GET_NTB_INPUT_SIZE = 0x85, + SET_NTB_INPUT_SIZE = 0x86, + GET_MAX_DATAGRAM_SIZE = 0x87, + SET_MAX_DATAGRAM_SIZE = 0x88, + GET_CRC_MODE = 0x89, + SET_CRC_MODE = 0x8A, + + _, + }; + const DescriptorType = enum(u8) { + CS_INTERFACE = 0x24, + CS_ENDPOINT = 0x25, + _, + }; + }; +}; + +const EpType = enum(u3) { + disabled, + control, + isochronous, + bulk, + interrupt, + dual, +}; + +const pcksize = struct { + const Size = enum(u3) { + @"8", + @"16", + @"32", + @"64", + @"128", + @"256", + @"512", + @"1023", + }; +}; + +//pub const microzig_options = .{ +// .logFn = log, +//}; + +pub fn main() !void { + // Initialize pins + @"D+".set_mux(.H); + @"D-".set_mux(.H); + + // Load Pad Calibration + const pads = NVMCTRL.SW0.SW0_WORD_1.read(); + USB.DEVICE.PADCAL.write(.{ + .TRANSP = switch (pads.USB_TRANSP) { + 0...std.math.maxInt(u5) - 1 => |transp| transp, + std.math.maxInt(u5) => 29, + }, + .reserved6 = 0, + .TRANSN = switch (pads.USB_TRANSN) { + 0...std.math.maxInt(u5) - 1 => |transn| transn, + std.math.maxInt(u5) => 5, + }, + .reserved12 = 0, + .TRIM = switch (pads.USB_TRIM) { + 0...std.math.maxInt(u3) - 1 => |trim| trim, + std.math.maxInt(u3) => 3, + }, + .padding = 0, + }); + + // Initialize clocks + mclk.set_apb_mask(.{ .USB = .enabled }); + mclk.set_ahb_mask(.{ .USB = .enabled }); + gclk.set_peripheral_clk_gen(.GCLK_USB, .GCLK0); + + // enable USB + @memset(std.mem.sliceAsBytes(endpoint_table), 0x00); + microzig.cpu.dmb(); + USB.DEVICE.DESCADD.write(.{ .DESCADD = @intFromPtr(endpoint_table) }); + USB.DEVICE.CTRLA.modify(.{ .ENABLE = 0 }); + USB.DEVICE.CTRLB.modify(.{ + .SPDCONF = .{ .value = .FS }, + .DETACH = 0, + }); + USB.DEVICE.CTRLA.write(.{ + .SWRST = 0, + .ENABLE = 1, + .RUNSTDBY = 0, + .reserved7 = 0, + .MODE = .{ .value = .DEVICE }, + }); + + while (USB.DEVICE.SYNCBUSY.read().ENABLE != 0) {} + + while (true) { + tick(); + } +} + +fn tick() void { + // Check for End Of Reset flag + if (USB.DEVICE.INTFLAG.read().EORST != 0) { + // Clear the flag + USB.DEVICE.INTFLAG.write(.{ + .SUSPEND = 0, + .reserved2 = 0, + .SOF = 0, + .EORST = 1, + .WAKEUP = 0, + .EORSM = 0, + .UPRSM = 0, + .RAMACER = 0, + .LPMNYET = 0, + .LPMSUSP = 0, + .padding = 0, + }); + // Set Device address as 0 + USB.DEVICE.DADD.write(.{ .DADD = 0, .ADDEN = 1 }); + // Configure endpoint 0 + // Configure Endpoint 0 for Control IN and Control OUT + USB.DEVICE.DEVICE_ENDPOINT[0].DEVICE.EPCFG.write(.{ + .EPTYPE0 = @intFromEnum(EpType.control), + .reserved4 = 0, + .EPTYPE1 = @intFromEnum(EpType.control), + .padding = 0, + }); + USB.DEVICE.DEVICE_ENDPOINT[0].DEVICE.EPSTATUSSET.write(.{ + .DTGLOUT = 0, + .DTGLIN = 0, + .CURBK = 0, + .reserved4 = 0, + .STALLRQ0 = 0, + .STALLRQ1 = 0, + .BK0RDY = 1, + .BK1RDY = 0, + }); + USB.DEVICE.DEVICE_ENDPOINT[0].DEVICE.EPSTATUSCLR.write(.{ + .DTGLOUT = 0, + .DTGLIN = 0, + .CURBK = 0, + .reserved4 = 0, + .STALLRQ0 = 0, + .STALLRQ1 = 0, + .BK0RDY = 0, + .BK1RDY = 1, + }); + // Configure control OUT Packet size to 64 bytes + // Set Multipacket size to 8 for control OUT and byte count to 0 + endpoint_table[0].DEVICE.DEVICE_DESC_BANK[0].DEVICE.PCKSIZE.write(.{ + .BYTE_COUNT = 0, + .MULTI_PACKET_SIZE = 8, + .SIZE = @intFromEnum(pcksize.Size.@"64"), + .AUTO_ZLP = 0, + }); + // Configure control IN Packet size to 64 bytes + endpoint_table[0].DEVICE.DEVICE_DESC_BANK[1].DEVICE.PCKSIZE.write(.{ + .BYTE_COUNT = 0, + .MULTI_PACKET_SIZE = 0, + .SIZE = @intFromEnum(pcksize.Size.@"64"), + .AUTO_ZLP = 1, + }); + // Configure the data buffer address for control OUT + endpoint_table[0].DEVICE.DEVICE_DESC_BANK[0].DEVICE.ADDR.write(.{ + .ADDR = @intFromPtr(&endpoint_buffer[0][0]), + }); + // Configure the data buffer address for control IN + endpoint_table[0].DEVICE.DEVICE_DESC_BANK[1].DEVICE.ADDR.write(.{ + .ADDR = @intFromPtr(&endpoint_buffer[0][1]), + }); + microzig.cpu.dmb(); + USB.DEVICE.DEVICE_ENDPOINT[0].DEVICE.EPSTATUSCLR.write(.{ + .DTGLOUT = 0, + .DTGLIN = 0, + .CURBK = 0, + .reserved4 = 0, + .STALLRQ0 = 0, + .STALLRQ1 = 0, + .BK0RDY = 1, + .BK1RDY = 0, + }); + + // Reset current configuration value to 0 + current_configuration = 0; + cdc.current_connection = 0; + } + + // Check for End Of SETUP flag + if (USB.DEVICE.DEVICE_ENDPOINT[0].DEVICE.EPINTFLAG.read().RXSTP != 0) setup: { + // Clear the Received Setup flag + USB.DEVICE.DEVICE_ENDPOINT[0].DEVICE.EPINTFLAG.write(.{ + .TRCPT0 = 0, + .TRCPT1 = 0, + .TRFAIL0 = 0, + .TRFAIL1 = 0, + .RXSTP = 1, + .STALL0 = 0, + .STALL1 = 0, + .padding = 0, + }); + + // Clear the Bank 0 ready flag on Control OUT + USB.DEVICE.DEVICE_ENDPOINT[0].DEVICE.EPSTATUSCLR.write(.{ + .DTGLOUT = 0, + .DTGLIN = 0, + .CURBK = 0, + .reserved4 = 0, + .STALLRQ0 = 0, + .STALLRQ1 = 0, + .BK0RDY = 1, + .BK1RDY = 0, + }); + + microzig.cpu.dmb(); + switch (setup.bmRequestType.kind) { + .standard => switch (setup.bmRequestType.recipient) { + .device => switch (setup.bmRequestType.dir) { + .out => switch (@as(Setup.standard.Request, @enumFromInt(setup.bRequest))) { + .SET_ADDRESS => if (setup.wIndex == 0 and setup.wLength == 0) { + if (std.math.cast(u7, setup.wValue)) |addr| { + write_control(&[0]u8{}); + USB.DEVICE.DADD.write(.{ .DADD = addr, .ADDEN = 1 }); + break :setup; + } + }, + .SET_CONFIGURATION => { + if (std.math.cast(u8, setup.wValue)) |config| { + write_control(&[0]u8{}); + current_configuration = config; + cdc.current_connection = 0; + switch (config) { + 0 => {}, + 1 => { + USB.DEVICE.DEVICE_ENDPOINT[1].DEVICE.EPCFG.write(.{ + .EPTYPE0 = @intFromEnum(EpType.disabled), + .reserved4 = 0, + .EPTYPE1 = @intFromEnum(EpType.interrupt), + .padding = 0, + }); + endpoint_table[1].DEVICE.DEVICE_DESC_BANK[1].DEVICE.PCKSIZE.write(.{ + .BYTE_COUNT = 0, + .MULTI_PACKET_SIZE = 0, + .SIZE = @intFromEnum(pcksize.Size.@"8"), + .AUTO_ZLP = 1, + }); + endpoint_table[1].DEVICE.DEVICE_DESC_BANK[1].DEVICE.ADDR.write(.{ + .ADDR = @intFromPtr(&endpoint_buffer[1][1]), + }); + microzig.cpu.dmb(); + USB.DEVICE.DEVICE_ENDPOINT[1].DEVICE.EPSTATUSCLR.write(.{ + .DTGLOUT = 0, + .DTGLIN = 0, + .CURBK = 0, + .reserved4 = 0, + .STALLRQ0 = 0, + .STALLRQ1 = 0, + .BK0RDY = 0, + .BK1RDY = 1, + }); + + USB.DEVICE.DEVICE_ENDPOINT[2].DEVICE.EPCFG.write(.{ + .EPTYPE0 = @intFromEnum(EpType.bulk), + .reserved4 = 0, + .EPTYPE1 = @intFromEnum(EpType.bulk), + .padding = 0, + }); + endpoint_table[2].DEVICE.DEVICE_DESC_BANK[0].DEVICE.PCKSIZE.write(.{ + .BYTE_COUNT = 0, + .MULTI_PACKET_SIZE = 0, + .SIZE = @intFromEnum(pcksize.Size.@"64"), + .AUTO_ZLP = 0, + }); + endpoint_table[2].DEVICE.DEVICE_DESC_BANK[0].DEVICE.ADDR.write(.{ + .ADDR = @intFromPtr(&endpoint_buffer[2][0]), + }); + microzig.cpu.dmb(); + USB.DEVICE.DEVICE_ENDPOINT[2].DEVICE.EPSTATUSSET.write(.{ + .DTGLOUT = 0, + .DTGLIN = 0, + .CURBK = 0, + .reserved4 = 0, + .STALLRQ0 = 0, + .STALLRQ1 = 0, + .BK0RDY = 1, + .BK1RDY = 0, + }); + endpoint_table[2].DEVICE.DEVICE_DESC_BANK[1].DEVICE.PCKSIZE.write(.{ + .BYTE_COUNT = 0, + .MULTI_PACKET_SIZE = 0, + .SIZE = @intFromEnum(pcksize.Size.@"64"), + .AUTO_ZLP = 1, + }); + endpoint_table[2].DEVICE.DEVICE_DESC_BANK[1].DEVICE.ADDR.write(.{ + .ADDR = @intFromPtr(&endpoint_buffer[2][1]), + }); + microzig.cpu.dmb(); + USB.DEVICE.DEVICE_ENDPOINT[2].DEVICE.EPSTATUSCLR.write(.{ + .DTGLOUT = 0, + .DTGLIN = 0, + .CURBK = 0, + .reserved4 = 0, + .STALLRQ0 = 0, + .STALLRQ1 = 0, + .BK0RDY = 0, + .BK1RDY = 1, + }); + }, + else => {}, + } + break :setup; + } + }, + else => {}, + }, + .in => switch (@as(Setup.standard.Request, @enumFromInt(setup.bRequest))) { + .GET_DESCRIPTOR => { + switch (@as(Setup.standard.DescriptorType, @enumFromInt(setup.wValue >> 8))) { + .DEVICE => if (@as(u8, @truncate(setup.wValue)) == 0 and setup.wIndex == 0) { + write_control(&[0x12]u8{ + 0x12, @intFromEnum(Setup.standard.DescriptorType.DEVICE), // + 0x00, 0x02, // + 0xef, 0x02, 0x01, // + 64, // + 0x9a, 0x23, // Adafruit + 0x34, 0x00, // + 0x01, 0x42, // 42.01 + 0x01, 0x02, 0x00, // + 0x01, // + }); + break :setup; + }, + .CONFIGURATION => if (setup.wIndex == 0) { + switch (@as(u8, @truncate(setup.wValue))) { + 0 => { + write_control(&[0x003e]u8{ + 0x09, @intFromEnum(Setup.standard.DescriptorType.CONFIGURATION), // + 0x3e, 0x00, // + 0x02, 0x01, 0x00, // + 0x80, 500 / 2, // + // + 0x09, @intFromEnum(Setup.standard.DescriptorType.INTERFACE), // + 0x00, 0x00, 0x01, // + 0x02, 0x02, 0x00, // + 0x00, // + // + 0x05, @intFromEnum(Setup.cdc.DescriptorType.CS_INTERFACE), 0x00, // + 0x10, 0x01, // + // + 0x04, @intFromEnum(Setup.cdc.DescriptorType.CS_INTERFACE), 0x02, // + 0x00, // + // + 0x05, @intFromEnum(Setup.cdc.DescriptorType.CS_INTERFACE), 0x06, // + 0x00, 0x01, // + // + 0x07, @intFromEnum(Setup.standard.DescriptorType.ENDPOINT), // + 0x81, 0x03, // + 8, 0, std.math.maxInt(u8), // + // + 0x09, @intFromEnum(Setup.standard.DescriptorType.INTERFACE), // + 0x01, 0x00, 0x02, // + 0x0a, 0x02, 0x00, // + 0x00, // + // + 0x07, @intFromEnum(Setup.standard.DescriptorType.ENDPOINT), // + 0x02, 0x02, // + 64, 0, 0, // + // + 0x07, @intFromEnum(Setup.standard.DescriptorType.ENDPOINT), // + 0x82, 0x02, // + 64, 0, 0, // + }); + break :setup; + }, + else => {}, + } + }, + .STRING => switch (@as(u8, @truncate(setup.wValue))) { + 0 => switch (setup.wIndex) { + 0 => { + write_control(&[4]u8{ + 4, @intFromEnum(Setup.standard.DescriptorType.STRING), // + 0x09, 0x04, // English (United States) + }); + break :setup; + }, + else => {}, + }, + 1 => switch (setup.wIndex) { + 0x0409 => { // English (United States) + const manufacturer_name = comptime make_string_literal("Zig Embedded Group"); + write_control(manufacturer_name); + break :setup; + }, + else => {}, + }, + 2 => switch (setup.wIndex) { + 0x0409 => { // English (United States) + const name = comptime make_string_literal("SYCL Badge 2024"); + write_control(name); + break :setup; + }, + else => {}, + }, + else => {}, + }, + else => {}, + } + }, + else => {}, + }, + }, + .interface => {}, + .endpoint => {}, + .other => {}, + _ => {}, + }, + .class => switch (setup.bmRequestType.recipient) { + .device => {}, + .interface => switch (setup.wIndex) { + 0 => switch (setup.bmRequestType.dir) { + .out => switch (@as(Setup.cdc.Request, @enumFromInt(setup.bRequest))) { + .SET_LINE_CODING => if (setup.wValue == 0) { + write_control(&[0]u8{}); + break :setup; + }, + .SET_CONTROL_LINE_STATE => if (setup.wLength == 0) { + if (std.math.cast(u8, setup.wValue)) |conn| { + cdc.current_connection = conn; + write_control(&[0]u8{}); + break :setup; + } + }, + else => {}, + }, + .in => {}, + }, + else => {}, + }, + .endpoint => {}, + .other => {}, + _ => {}, + }, + .vendor => {}, + _ => {}, + } + // Stall control endpoint + USB.DEVICE.DEVICE_ENDPOINT[0].DEVICE.EPSTATUSSET.write(.{ + .DTGLOUT = 0, + .DTGLIN = 0, + .CURBK = 0, + .reserved4 = 0, + .STALLRQ0 = 0, + .STALLRQ1 = 1, + .BK0RDY = 0, + .BK1RDY = 0, + }); + + if (setup.bmRequestType.kind == .standard and setup.bmRequestType.dir == .in and + setup.bRequest == @intFromEnum(Setup.standard.Request.GET_DESCRIPTOR) and + setup.wValue >> 8 == @intFromEnum(Setup.standard.DescriptorType.DEVICE_QUALIFIER)) + {} else { + std.log.scoped(.usb).err("Unhandled request: 0x{X:0<2}", .{setup.bRequest}); + } + } +} + +fn write_control(data: []const u8) void { + write(0, data[0..@min(data.len, setup.wLength)]); +} + +fn write(ep: u3, data: []const u8) void { + const ep_buffer = &endpoint_buffer[ep][1]; + @memcpy(ep_buffer[0..data.len], data); + microzig.cpu.dmb(); + + const ep_descs: *volatile [8]io_types.USB.USB_DESCRIPTOR = @ptrFromInt(USB.DEVICE.DESCADD.read().DESCADD); + // Set the buffer address for ep data + const ep_desc = &ep_descs[ep].DEVICE.DEVICE_DESC_BANK[1].DEVICE; + ep_desc.ADDR.write(.{ .ADDR = @intFromPtr(ep_buffer) }); + // Set the byte count as zero + // Set the multi packet size as zero for multi-packet transfers where length > ep size + ep_desc.PCKSIZE.modify(.{ + .BYTE_COUNT = @as(u14, @intCast(data.len)), + .MULTI_PACKET_SIZE = 0, + }); + // Clear the transfer complete flag + const ep_ctrl = &USB.DEVICE.DEVICE_ENDPOINT[ep].DEVICE; + ep_ctrl.EPINTFLAG.write(.{ + .TRCPT0 = 0, + .TRCPT1 = 1, + .TRFAIL0 = 0, + .TRFAIL1 = 0, + .RXSTP = 0, + .STALL0 = 0, + .STALL1 = 0, + .padding = 0, + }); + // Set the bank as ready + ep_ctrl.EPSTATUSSET.write(.{ + .DTGLOUT = 0, + .DTGLIN = 0, + .CURBK = 0, + .reserved4 = 0, + .STALLRQ0 = 0, + .STALLRQ1 = 0, + .BK0RDY = 0, + .BK1RDY = 1, + }); + // Wait for transfer to complete + while (ep_ctrl.EPINTFLAG.read().TRCPT1 == 0) {} +} + +fn make_string_literal(comptime str: []const u8) []const u8 { + const len = str.len + 2; + var buf: [len]u8 = undefined; + buf[0] = len; + buf[1] = @intFromEnum(Setup.standard.DescriptorType.STRING); + for (buf[2..], 0..) |*b, i| + b.* = if (i % 2 == 0) str[i >> 1] else 0x00; + + const buf_const = buf; + return &buf_const; +} + +//pub fn log( +// comptime level: std.log.Level, +// comptime scope: @Type(.EnumLiteral), +// comptime format: []const u8, +// args: anytype, +//) void { +// out.print("[" ++ level.asText() ++ "] (" ++ @tagName(scope) ++ "): " ++ format ++ "\n", args) catch return; +//} + +//const InOutError = error{NoConnection}; +//pub const out: std.io.Writer(void, InOutError, struct { +// fn write(_: void, data: []const u8) InOutError!usize { +// if (usb.cdc.current_connection == 0) return error.NoConnection; +// if (data.len == 0) return data.len; +// var line_it = std.mem.splitScalar(u8, data, '\n'); +// var first = true; +// while (line_it.next()) |line| { +// if (!first) usb.write(2, "\r\n"); +// var chunk_it = std.mem.window(u8, line, 64, 64); +// while (chunk_it.next()) |chunk| usb.write(2, chunk); +// first = false; +// } +// return data.len; +// } +//}.write) = .{ .context = {} }; diff --git a/src/badge/demos/usb_storage.zig b/src/badge/demos/usb_storage.zig new file mode 100644 index 0000000..0e9f01e --- /dev/null +++ b/src/badge/demos/usb_storage.zig @@ -0,0 +1,4 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +pub fn main() !void {} diff --git a/src/board.zig b/src/board.zig new file mode 100644 index 0000000..c3f0488 --- /dev/null +++ b/src/board.zig @@ -0,0 +1,77 @@ +//! Hardware to interact with: +//! +//! - LCD Screen +//! - Speaker +//! - 5x Neopixels +//! - Light Sensor +//! - 4x Buttons +//! - Navstick +//! - Red LED +//! - Flash Memory +//! +const hal = @import("microzig").hal; +const port = hal.port; + +pub const pin_config = hal.pins.create_config(.{ + .{ .name = "tft_rst", .port = "PA0", .mode = .output }, + .{ .name = "tft_lite", .port = "PA1", .mode = .output }, + .{ .name = "audio", .port = "PA2", .mode = .dac }, + .{ .name = "battery_level", .port = "PA2", .mode = .adc }, + .{ .name = "led", .port = "PA5" }, + .{ .name = "light_sensor", .port = "PA6" }, + .{ .name = "neopixels", .port = "PA15" }, + .{ .name = "spkr_en", .port = "PA23" }, + .{ .name = "D-", .port = "PA24" }, + .{ .name = "D+", .port = "PA25" }, + // TODO: rest +}); + +pub const TFT_RST = port.pin(.a, 0); +pub const TFT_LITE = port.pin(.a, 1); +pub const A0 = port.pin(.a, 2); +pub const A6_VMEAS = port.pin(.a, 2); +pub const D13 = port.pin(.a, 5); +pub const A7_LIGHT = port.pin(.a, 6); + +pub const qspi = [_]port.Pin{ + port.pin(.a, 8), + port.pin(.a, 9), + port.pin(.a, 10), + port.pin(.a, 11), + port.pin(.b, 10), + port.pin(.b, 11), +}; + +pub const D8_NEOPIX = port.pin(.a, 15); +pub const SPKR_EN = port.pin(.a, 23); +pub const @"D-" = port.pin(.a, 24); +pub const @"D+" = port.pin(.a, 25); +pub const SWO = port.pin(.a, 27); +pub const SWCLK = port.pin(.a, 30); +pub const SWDIO = port.pin(.a, 31); +pub const TFT_DC = port.pin(.b, 12); +pub const TFT_SCK = port.pin(.b, 13); +pub const TFT_CS = port.pin(.b, 14); +pub const TFT_MOSI = port.pin(.b, 15); + +pub const Buttons = packed struct(u9) { + select: u1, + start: u1, + a: u1, + b: u1, + up: u1, + down: u1, + press: u1, + right: u1, + left: u1, + + pub const mask = port.mask(.b, 0x1FF); + + pub fn configure() void { + mask.set_dir(.in); + } + + pub fn read_from_port() Buttons { + return @bitCast(@as(u9, @truncate(mask.read()))); + } +}; diff --git a/src/hal.zig b/src/hal.zig new file mode 100644 index 0000000..85ac47b --- /dev/null +++ b/src/hal.zig @@ -0,0 +1,10 @@ +pub const adc = @import("hal/adc.zig"); +pub const port = @import("hal/port.zig"); +pub const nvm = @import("hal/nvm.zig"); +pub const usb = @import("hal/usb.zig"); +pub const gclk = @import("hal/gclk.zig"); +pub const mclk = @import("hal/mclk.zig"); +pub const pins = @import("hal/pins.zig"); +pub const timer = @import("hal/timer.zig"); +pub const sercom = @import("hal/sercom.zig"); +pub const qspi = @import("hal/qspi.zig"); diff --git a/src/hal/adc.zig b/src/hal/adc.zig new file mode 100644 index 0000000..33761f9 --- /dev/null +++ b/src/hal/adc.zig @@ -0,0 +1,147 @@ +const microzig = @import("microzig"); +const ADC = microzig.chip.types.peripherals.ADC; +const NVMCTRL = struct { + pub const SW0: *volatile microzig.chip.types.peripherals.FUSES.SW0_FUSES = @ptrFromInt(0x00800080); +}; + +pub const PositiveInput = ADC.ADC_INPUTCTRL__MUXPOS; +pub const NegativeInput = ADC.ADC_INPUTCTRL__MUXNEG; + +pub const Adc = enum(u1) { + ADC0, + ADC1, + + fn get_regs(adc: Adc) *volatile ADC { + return switch (adc) { + .ADC0 => microzig.chip.peripherals.ADC0, + .ADC1 => microzig.chip.peripherals.ADC1, + }; + } + + pub fn init(adc: Adc) void { + adc.wait_for_sync(.SWRST); + + adc.reset(); + defer adc.enable(); + + adc.calibrate(); + + // CTRLB + // REFCTRL + // EVCTRL + // INPUTCTRL + // AVGCTRL + // SAMPCTRL + // WINLT + // WINUT + // GAINCORR + // OFFSETCORR + // DBGCTRL + // CTRLA + } + + pub fn enable(adc: Adc) void { + const regs = adc.get_regs(); + regs.CTRLA.modify(.{ + .ENABLE = 1, + }); + + adc.wait_for_sync(.ENABLE); + } + + pub fn disable(adc: Adc) void { + const regs = adc.get_regs(); + regs.CTRLA.modify(.{ + .ENABLE = 0, + }); + + adc.wait_for_sync(.ENABLE); + } + + pub const Sync = enum { + SWRST, + ENABLE, + INPUTCTRL, + SWTRIG, + }; + + pub fn wait_for_sync(adc: Adc, sync: Sync) void { + const regs = adc.get_regs(); + while (true) { + const busy = regs.SYNCBUSY.read(); + if (switch (sync) { + .SWRST => busy.SWRST, + .ENABLE => busy.ENABLE, + .INPUTCTRL => busy.INPUTCTRL, + .SWTRIG => busy.SWTRIG, + } == 0) { + break; + } + } + } + + pub fn reset(adc: Adc) void { + const regs = adc.get_regs(); + regs.CTRLA.modify(.{ .SWRST = 1 }); + adc.wait_for_sync(.SWRST); + } + + pub fn calibrate(adc: Adc) void { + const fuses = NVMCTRL.SW0.SW0_WORD_0.read(); + const regs = adc.get_regs(); + regs.CALIB.write(.{ + .BIASCOMP = switch (adc) { + .ADC0 => fuses.ADC0_BIASCOMP, + .ADC1 => fuses.ADC1_BIASCOMP, + }, + .BIASR2R = switch (adc) { + .ADC0 => fuses.ADC0_BIASR2R, + .ADC1 => fuses.ADC1_BIASR2R, + }, + .BIASREFBUF = switch (adc) { + .ADC0 => fuses.ADC0_BIASREFBUF, + .ADC1 => fuses.ADC1_BIASREFBUF, + }, + + .reserved4 = 0, + .reserved8 = 0, + .padding = 0, + }); + } + + pub fn set_input(adc: Adc, input: PositiveInput) void { + const regs = adc.get_regs(); + regs.INPUTCTRL.modify(.{ + .MUXPOS = .{ .value = input }, + }); + + adc.wait_for_sync(.INPUTCTRL); + } + + pub fn start_conversion(adc: Adc) void { + const regs = adc.get_regs(); + regs.SWTRIG.modify(.{ + .START = 1, + }); + + adc.wait_for_sync(.SWTRIG); + } + + pub fn wait_for_result_blocking(adc: Adc) void { + const regs = adc.get_regs(); + while (regs.INTFLAG.read().RESRDY == 0) {} + } + + pub fn single_shot_blocking(adc: Adc, input: PositiveInput) u16 { + adc.set_input(input); + adc.start_conversion(); + adc.wait_for_result_blocking(); + + const regs = adc.get_regs(); + return regs.RESULT.read().RESULT; + } +}; + +pub fn num(n: u1) Adc { + return @enumFromInt(n); +} diff --git a/src/hal/clocks.zig b/src/hal/clocks.zig new file mode 100644 index 0000000..118fc51 --- /dev/null +++ b/src/hal/clocks.zig @@ -0,0 +1,132 @@ +//! Clock Management +//! +//! After Reset: +//! +//! On any Reset the synchronous clocks start to their initial state: +//! +//! • DFLL48M is enabled and configured to run at 48MHz +//! • Generic Generator 0 uses DFLL48M as source and generates GCLK_MAIN +//! • CPU and BUS clocks are undivided +//! +//! On a Power-on Reset, the 32KHz clock sources are reset and the GCLK module +//! starts to its initial state: +//! +//! • All Generic Clock Generators are disabled except +//! – Generator 0 is using DFLL48M at 48MHz as source and generates GCLK_MAIN +//! • All Peripheral Channels in GCLK are disabled. +//! +//! On a User Reset the GCLK module starts to its initial state, except for: +//! +//! • Generic Clocks that are write-locked, i.e., the according WRTLOCK is set +//! to 1 prior to Reset +//! +//! First items to implement: +//! - enable CLK_USB_AHB in main clock module (MCLK) +//! - note defaul state in `Peripheral Clock Masking` +//! - GCLK_USB clocking the USB, configured in the Generic Clock Controller. +//! - GCLK_USB must be 48 MHz +//! - GCLK_USB_AHB must be minimum 8 MHz. +//! - GCLK_USB is generated by DFLL48 using a reference clock. +//! +//! +//! GCLK +//! ==== +//! +//! The GCLK module is comprised of twelve generic clock generators, sourcing up +//! to 48 peripheral channels and the main clock signal. +//! +//! A clock source selected as an input to a generator can either be used +//! directly, or it can be prescaled in the generator. +//! +//! +//! Initialization +//! +//! Before a generator is enabled, the corresponding clock source should be +//! enabled. The Peripheral clock must be configured as outlined by the +//! following steps: +//! +//! 1. The generator must be enabled (GENCTRLn.GENEN=1) and the division factor +//! must be set (GENTRLn.DIVSEL and GENCTRLn.DIV) by performing a single +//! 32-bit write to the generator control register (GENCTRLn) +//! +//! 2. The Generic clock for a peripheral must be configured by writing to the +//! peripheral channel control register (PCHCTRLm) belonging to the +//! peripheral. The generator used as the source for the peripheral clock +//! must be written to the GEN bit field. +//! +//! Note: Each Generator n is configured by one dedicated register GENCTRLn +//! Note: Each Peripheral Channel m is configured by one dedicated register +//! PCHCTRLm. +//! +//! +//! Enabling, Disabling, and Resetting +//! +//! The GCLK module has no enabled/disable bit to enable or disable the whole +//! module. +//! +//! The GCLK is reset by setting the Software Reset bit in the control A +//! register to 1. All registers in the GCLK will be reset to their initial +//! state, except for peripheral channels and associated generators who are +//! locked. +//! +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; +const GCLK = peripherals.GCLK; + +/// Index into the PCHCTRL[m] array +pub const PeripheralIndex = enum(u5) { + GCLK_OSCCTRL_DFLL48 = 0, + GCLK_OSCCTRL_FDPLL0 = 1, + GCLK_OSCCTRL_FDPLL1 = 2, + // TODO: + //3 GCLK_OSCCTRL_FDPLL0_32K GCLK_OSCCTRL_FDPLL1_32K GCLK_SDHC0_SLOW GCLK_SDHC1_SLOW GCLK_SERCOM[0..7]_SLOW + //FDPLL0 32KHz clock for internal lock timer FDPLL1 32KHz clock for internal lock timer SDHC0 Slow + //SDHC1 Slow + //SERCOM[0..7] Slow + GCLK_EIC = 4, + GCLK_FREQM_MSR = 5, + GCLK_FREQM_REF = 6, + GCLK_SERCOM0_CORE = 7, + GCLK_SERCOM1_CORE = 8, + GCLK_TC0_TC1 = 9, + GCLK_USB = 10, + // TODO: + //22:11 GCLK_EVSYS[0..11] EVSYS[0..11] + GCLK_SERCOM2_CORE = 23, + GCLK_SERCOM3_CORE = 24, + GCLK_TCC0_TCC1 = 25, + GCLK_TC2_TC3 = 26, + GCLK_CAN0 = 27, + GCLK_CAN1 = 28, + GCLK_TCC2_TCC3 = 29, + GCLK_TC4_TC5 = 30, + GCLK_PDEC = 31, + GCLK_AC = 32, + GCLK_CCL = 33, + GCLK_SERCOM4_CORE = 34, + GCLK_SERCOM5_CORE = 35, + GCLK_SERCOM6_CORE = 36, + GCLK_SERCOM7_CORE = 37, + GCLK_TCC4 = 38, + GCLK_TC6_TC7 = 39, + GCLK_ADC0 = 40, + GCLK_ADC1 = 41, + GCLK_DAC = 42, + GCLK_I2S = 43, // TODO: and 44? + GCLK_SDHC0 = 45, + GCLK_SDHC1 = 46, + GCLK_CM4_TRACE = 47, +}; + +/// Set the Generic CLock Generator for a peripheral +pub fn set_peripheral_clk_gen(peripheral: PeripheralIndex, clk_gen: u4) void { + GCLK.PCHCTRL[@intFromEnum(peripheral)].modify(.{ + .GEN = clk_gen, + }); +} + +pub fn enable_apb(peripheral: Peripheral) void { +} + +pub fn enable_ahb(peripheral: Peripheral) void { +} diff --git a/src/hal/gclk.zig b/src/hal/gclk.zig new file mode 100644 index 0000000..b62d509 --- /dev/null +++ b/src/hal/gclk.zig @@ -0,0 +1,95 @@ +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; +const GCLK = peripherals.GCLK; +const types = microzig.chip.types; + +/// For SYNCBUSY +const Genctrl = types.peripherals.GCLK.GCLK_SYNCBUSY__GENCTRL; +pub const Generator = types.peripherals.GCLK.GCLK_PCHCTRL__GEN; +pub const Source = types.peripherals.GCLK.GCLK_GENCTRL__SRC; + +pub const PeripheralIndex = enum(u6) { + GCLK_OSCCTRL_DFLL48 = 0, + GCLK_OSCCTRL_FDPLL0 = 1, + GCLK_OSCCTRL_FDPLL1 = 2, + // TODO: + //3 GCLK_OSCCTRL_FDPLL0_32K GCLK_OSCCTRL_FDPLL1_32K GCLK_SDHC0_SLOW GCLK_SDHC1_SLOW GCLK_SERCOM[0..7]_SLOW + //FDPLL0 32KHz clock for internal lock timer FDPLL1 32KHz clock for internal lock timer SDHC0 Slow + //SDHC1 Slow + //SERCOM[0..7] Slow + GCLK_EIC = 4, + GCLK_FREQM_MSR = 5, + GCLK_FREQM_REF = 6, + GCLK_SERCOM0_CORE = 7, + GCLK_SERCOM1_CORE = 8, + GCLK_TC0_TC1 = 9, + GCLK_USB = 10, + // TODO: + //22:11 GCLK_EVSYS[0..11] EVSYS[0..11] + GCLK_SERCOM2_CORE = 23, + GCLK_SERCOM3_CORE = 24, + GCLK_TCC0_TCC1 = 25, + GCLK_TC2_TC3 = 26, + GCLK_CAN0 = 27, + GCLK_CAN1 = 28, + GCLK_TCC2_TCC3 = 29, + GCLK_TC4_TC5 = 30, + GCLK_PDEC = 31, + GCLK_AC = 32, + GCLK_CCL = 33, + GCLK_SERCOM4_CORE = 34, + GCLK_SERCOM5_CORE = 35, + GCLK_SERCOM6_CORE = 36, + GCLK_SERCOM7_CORE = 37, + GCLK_TCC4 = 38, + GCLK_TC6_TC7 = 39, + GCLK_ADC0 = 40, + GCLK_ADC1 = 41, + GCLK_DAC = 42, + GCLK_I2S = 43, // TODO: and 44? + GCLK_SDHC0 = 45, + GCLK_SDHC1 = 46, + GCLK_CM4_TRACE = 47, +}; + +pub const EnableGeneratorOptions = struct { + divsel: microzig.chip.types.peripherals.GCLK.GCLK_GENCTRL__DIVSEL, + div: u8, +}; + +pub fn enable_generator(gen: Generator, source: Source, opts: EnableGeneratorOptions) void { + GCLK.GENCTRL[@intFromEnum(gen)].write(.{ + .SRC = .{ .value = source }, + .reserved8 = 0, + .GENEN = 1, + .IDC = 0, + .OOV = 0, + .OE = 0, + .DIVSEL = .{ .value = opts.divsel }, + .RUNSTDBY = 0, + .reserved16 = 0, + .DIV = opts.div, + }); + + while ((GCLK.SYNCBUSY.raw & @as(u32, 1) << @intFromEnum(gen) + 2) == 1) {} +} + +/// Set the Generic Clock Generator for a peripheral, it will also enable the +/// peripheral channel. +pub fn set_peripheral_clk_gen(peripheral: PeripheralIndex, gen: Generator) void { + GCLK.PCHCTRL[@intFromEnum(peripheral)].write(.{ + .GEN = .{ .value = gen }, + // TODO: maybe change API to make this more explicit? + .CHEN = 1, + .WRTLOCK = 0, + + .reserved6 = 0, + .padding = 0, + }); +} + +pub fn disable_peripheral_channel(peripheral: PeripheralIndex) void { + GCLK.PCHCTRL[@intFromEnum(peripheral)].modify(.{ + .CHEN = 0, + }); +} diff --git a/src/hal/gpio.zig b/src/hal/gpio.zig new file mode 100644 index 0000000..5e76c35 --- /dev/null +++ b/src/hal/gpio.zig @@ -0,0 +1,81 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const io_types = microzig.chip.types; +const PORT = microzig.chip.peripherals.PORT; + +pub const PinCfg = @typeInfo(std.meta.FieldType(io_types.peripherals.PORT.GROUP, .PINCFG)).Array.child; +pub const Mux = enum(u4) { A, B, C, D, E, F, G, H, I, J, K, L, M, N }; +pub const Pin = packed struct(u6) { + num: u5, + group: Group, + + pub fn config_ptr(p: Pin) *volatile PinCfg { + return &p.group.ptr().PINCFG[p.num]; + } + + pub inline fn set_mux(p: Pin, mux: Mux) void { + const pmux = &p.group.ptr().PMUX[p.num / 2]; + switch (@as(u1, @truncate(p.num))) { + 0 => pmux.modify(.{ .PMUXE = .{ + .value = @as(io_types.peripherals.PORT.PORT_PMUX__PMUXE, @enumFromInt(@intFromEnum(mux))), + } }), + 1 => pmux.modify(.{ .PMUXO = .{ + .value = @as(io_types.peripherals.PORT.PORT_PMUX__PMUXO, @enumFromInt(@intFromEnum(mux))), + } }), + } + + p.config_ptr().modify(.{ .PMUXEN = 1 }); + } + + pub inline fn set_dir(p: Pin, dir: Direction) void { + switch (dir) { + .in => p.group.ptr().DIRCLR.write(.{ .DIRCLR = @as(u32, 1) << p.num }), + .out => p.group.ptr().DIRSET.write(.{ .DIRSET = @as(u32, 1) << p.num }), + } + } + + pub inline fn read(p: Pin) Level { + return @enumFromInt(p.group.ptr().IN.read().IN >> p.num & 1); + } + + pub inline fn write(p: Pin, level: Level) void { + switch (level) { + .low => p.group.ptr().OUTCLR.write(.{ .OUTCLR = @as(u32, 1) << p.num }), + .high => p.group.ptr().OUTSET.write(.{ .OUTSET = @as(u32, 1) << p.num }), + } + } +}; + +pub const Mask = packed struct(u33) { + pins: u32, + group: Group, + + pub inline fn read(m: Mask) u32 { + return m.group.ptr().IN.read().IN & m.pins; + } +}; + +pub fn pin(group: Group, num: u5) Pin { + return Pin{ + .num = num, + .group = group, + }; +} + +pub fn mask(group: Group, pins: u32) Mask { + return Mask{ + .pins = pins, + .group = group, + }; +} + +pub const Group = enum(u1) { + a, + b, + + pub fn ptr(group: Group) *volatile io_types.peripherals.PORT.GROUP { + return &PORT.GROUP[@intFromEnum(group)]; + } +}; +pub const Direction = enum(u1) { in, out }; +pub const Level = enum(u1) { low, high }; diff --git a/src/hal/mclk.zig b/src/hal/mclk.zig new file mode 100644 index 0000000..6d6eefb --- /dev/null +++ b/src/hal/mclk.zig @@ -0,0 +1,194 @@ +const microzig = @import("microzig"); +const MCLK = microzig.chip.peripherals.MCLK; + +pub const HsDiv = MCLK.MCLK_HSDIV__DIV; +pub const CpuDiv = MCLK.MCLK_CPUDIV__DIV; + +/// To keep as consistent with documentation as possible +pub const BusClockEnable = enum(u1) { + stopped = 0, + enabled = 1, +}; + +pub const ApbMask = packed struct { + // A + PAC: BusClockEnable = .enabled, + PM: BusClockEnable = .enabled, + MCLK: BusClockEnable = .enabled, + RSTC: BusClockEnable = .enabled, + OSCCTRL: BusClockEnable = .enabled, + OSC32KCTRL: BusClockEnable = .enabled, + SUPC: BusClockEnable = .enabled, + GCLK: BusClockEnable = .enabled, + WDT: BusClockEnable = .enabled, + RTC: BusClockEnable = .enabled, + EIC: BusClockEnable = .enabled, + FREQM: BusClockEnable = .stopped, + SERCOM0: BusClockEnable = .stopped, + SERCOM1: BusClockEnable = .stopped, + TC0: BusClockEnable = .stopped, + TC1: BusClockEnable = .stopped, + + // B + USB: BusClockEnable = .stopped, + DSU: BusClockEnable = .enabled, + NVMCTRL: BusClockEnable = .enabled, + PORT: BusClockEnable = .enabled, + EVSYS: BusClockEnable = .stopped, + SERCOM2: BusClockEnable = .stopped, + SERCOM3: BusClockEnable = .stopped, + TCC0: BusClockEnable = .stopped, + TCC1: BusClockEnable = .stopped, + TC2: BusClockEnable = .stopped, + TC3: BusClockEnable = .stopped, + RAMECC: BusClockEnable = .enabled, + + // C + TCC2: BusClockEnable = .stopped, + TCC3: BusClockEnable = .stopped, + TC4: BusClockEnable = .stopped, + TC5: BusClockEnable = .stopped, + PDEC: BusClockEnable = .stopped, + AC: BusClockEnable = .stopped, + AES: BusClockEnable = .stopped, + TRNG: BusClockEnable = .stopped, + ICM: BusClockEnable = .stopped, + QSPI: BusClockEnable = .enabled, + CCL: BusClockEnable = .stopped, + + // D + SERCOM4: BusClockEnable = .stopped, + SERCOM5: BusClockEnable = .stopped, + TCC4: BusClockEnable = .stopped, + ADC0: BusClockEnable = .stopped, + ADC1: BusClockEnable = .stopped, + DAC: BusClockEnable = .stopped, + I2S: BusClockEnable = .stopped, + PCC: BusClockEnable = .stopped, +}; + +pub const AhbMask = packed struct { + HPB0: BusClockEnable = .enabled, + HPB1: BusClockEnable = .enabled, + HPB2: BusClockEnable = .enabled, + HPB3: BusClockEnable = .enabled, + DSU: BusClockEnable = .enabled, + NVMCTRL: BusClockEnable = .enabled, + CMCC: BusClockEnable = .enabled, + DMAC: BusClockEnable = .enabled, + USB: BusClockEnable = .enabled, + PAC: BusClockEnable = .enabled, + QSPI: BusClockEnable = .enabled, + SDHC0: BusClockEnable = .enabled, + ICM: BusClockEnable = .enabled, + PUKCC: BusClockEnable = .enabled, + QSPI_2X: BusClockEnable = .enabled, + NVMCTRL_SMEEPROM: BusClockEnable = .enabled, + NVMCTRL_CACHE: BusClockEnable = .enabled, +}; + +pub fn set_apb_mask(mask: ApbMask) void { + MCLK.APBAMASK.write(.{ + .PAC_ = @intFromEnum(mask.PAC), + .PM_ = @intFromEnum(mask.PM), + .MCLK_ = @intFromEnum(mask.MCLK), + .RSTC_ = @intFromEnum(mask.RSTC), + .OSCCTRL_ = @intFromEnum(mask.OSCCTRL), + .OSC32KCTRL_ = @intFromEnum(mask.OSC32KCTRL), + .SUPC_ = @intFromEnum(mask.SUPC), + .GCLK_ = @intFromEnum(mask.GCLK), + .WDT_ = @intFromEnum(mask.WDT), + .RTC_ = @intFromEnum(mask.RTC), + .EIC_ = @intFromEnum(mask.EIC), + .FREQM_ = @intFromEnum(mask.FREQM), + .SERCOM0_ = @intFromEnum(mask.SERCOM0), + .SERCOM1_ = @intFromEnum(mask.SERCOM1), + .TC0_ = @intFromEnum(mask.TC0), + .TC1_ = @intFromEnum(mask.TC1), + + .padding = 0, + }); + + MCLK.APBBMASK.write(.{ + .USB_ = @intFromEnum(mask.USB), + .DSU_ = @intFromEnum(mask.DSU), + .NVMCTRL_ = @intFromEnum(mask.NVMCTRL), + .PORT_ = @intFromEnum(mask.PORT), + .EVSYS_ = @intFromEnum(mask.EVSYS), + .SERCOM2_ = @intFromEnum(mask.SERCOM2), + .SERCOM3_ = @intFromEnum(mask.SERCOM3), + .TCC0_ = @intFromEnum(mask.TCC0), + .TCC1_ = @intFromEnum(mask.TCC1), + .TC2_ = @intFromEnum(mask.TC2), + .TC3_ = @intFromEnum(mask.TC3), + .RAMECC_ = @intFromEnum(mask.RAMECC), + .reserved4 = 0, + .reserved7 = 0, + .reserved9 = 0, + .reserved16 = 0, + .padding = 0, + }); + + MCLK.APBCMASK.write(.{ + .TCC2_ = @intFromEnum(mask.TCC2), + .TCC3_ = @intFromEnum(mask.TCC3), + .TC4_ = @intFromEnum(mask.TC4), + .TC5_ = @intFromEnum(mask.TC5), + .PDEC_ = @intFromEnum(mask.PDEC), + .AC_ = @intFromEnum(mask.AC), + .AES_ = @intFromEnum(mask.AES), + .TRNG_ = @intFromEnum(mask.TRNG), + .ICM_ = @intFromEnum(mask.ICM), + .QSPI_ = @intFromEnum(mask.QSPI), + .CCL_ = @intFromEnum(mask.CCL), + + .reserved3 = 0, + .reserved13 = 0, + .padding = 0, + }); + + MCLK.APBDMASK.write(.{ + .SERCOM4_ = @intFromEnum(mask.SERCOM4), + .SERCOM5_ = @intFromEnum(mask.SERCOM5), + .TCC4_ = @intFromEnum(mask.TCC4), + .ADC0_ = @intFromEnum(mask.ADC0), + .ADC1_ = @intFromEnum(mask.ADC1), + .DAC_ = @intFromEnum(mask.DAC), + .I2S_ = @intFromEnum(mask.I2S), + .PCC_ = @intFromEnum(mask.PCC), + + .reserved4 = 0, + .reserved7 = 0, + .padding = 0, + }); +} + +pub fn set_ahb_mask(mask: AhbMask) void { + MCLK.AHBMASK.write(.{ + .HPB0_ = @intFromEnum(mask.HPB0), + .HPB1_ = @intFromEnum(mask.HPB1), + .HPB2_ = @intFromEnum(mask.HPB2), + .HPB3_ = @intFromEnum(mask.HPB3), + .DSU_ = @intFromEnum(mask.DSU), + .NVMCTRL_ = @intFromEnum(mask.NVMCTRL), + .CMCC_ = @intFromEnum(mask.CMCC), + .DMAC_ = @intFromEnum(mask.DMAC), + .USB_ = @intFromEnum(mask.USB), + .PAC_ = @intFromEnum(mask.PAC), + .QSPI_ = @intFromEnum(mask.QSPI), + .SDHC0_ = @intFromEnum(mask.SDHC0), + .ICM_ = @intFromEnum(mask.ICM), + .PUKCC_ = @intFromEnum(mask.PUKCC), + .QSPI_2X_ = @intFromEnum(mask.QSPI_2X), + .NVMCTRL_SMEEPROM_ = @intFromEnum(mask.NVMCTRL_SMEEPROM), + .NVMCTRL_CACHE_ = @intFromEnum(mask.NVMCTRL_CACHE), + + // Documentation says all reserved bits should be 1 and padding is 0 + .reserved6 = 1, + .reserved8 = 1, + .reserved12 = 1, + .reserved15 = 1, + .reserved19 = 7, + .padding = 0, + }); +} diff --git a/src/hal/nvm.zig b/src/hal/nvm.zig new file mode 100644 index 0000000..bf7b6ff --- /dev/null +++ b/src/hal/nvm.zig @@ -0,0 +1,65 @@ +//! Non-Volatile Memory Controller +pub const Command = enum(u7) { + /// EP: Only supported in the User page in the auxiliary space. + erase_page = 0x00, + /// EB: Erases the block addressed by the ADDR register, not supported in + /// the user page + erase_block = 0x01, + /// WP: Writes the contents of the page buffer to the page addressed by the + /// ADDR register, not supported in the user page + write_page = 0x03, + /// WQW: Writes a 128-bit word at the location addressed by the ADDR + /// register. + write_quad_word = 0x04, + /// SWRST: Power-Cycle the NVM memory and replay the device automatic + /// calibration procedure and resets the module configuration registers + software_reset = 0x10, + /// LR: Locks the region containing the address location in the ADDR + /// register until next reset. + lock_region = 0x11, + /// UR: Unlocks the region containing the address location in the ADDR + /// register until next reset. + unlock_region = 0x12, + /// SPRM + set_power_reduction_mode = 0x13, + /// CPRM + clear_power_reduction_mode = 0x14, + /// PBC: Clears the page buffer + page_buffer_clear = 0x15, + /// SSB + set_security_bit = 0x16, + /// BKSWRST: if SmartEEPROM is used also reallocate its data into the + /// opposite BANK + bank_swap_and_system_reset = 0x17, + /// CELCK: DSU CTRL.CE command is not available. As soon as the CELCK + /// command is successfully executed, the chip erase capability is disabled + /// and Microchip’s failure analysis capabilities are limited. Therefore, + /// the software has to ensure there is a way to unlock the chip erase by + /// executing the CEULCK command. + chip_erase_lock = 0x18, + /// CEULCK: The DSU CTRL.CE command is available. + chip_erase_unlock = 0x19, + /// SBPDIS: Sets STATUS.BPDIS, bootloader protection is discarded until + /// CBPDIS is issued or next start-up sequence. + disable_bootloader_protection = 0x1A, + /// CBPDIS: Clears STATUS.BPDIS, bootloader protection is not discarded. + enable_bootloader_protection = 0x1B, + /// ASEES0: Configure SmartEEPROM to use Sector 0 + smart_eeprom_use_sector_0 = 0x30, + /// ASEES1: Configure SmartEEPROM to use Sector 1 + smart_eeprom_use_sector_1 = 0x31, + /// SEERALOC: Starts SmartEEPROM sector reallocation algorithm + start_smart_eeprom_reallocation = 0x32, + /// SEEFLUSH: Flush SmartEEPROM data when in buffered mode + set_flush_smart_eeprom_when_buffered = 0x33, + /// LSEE: Lock access to SmartEEPROM data from any means + lock_smart_eeprom = 0x34, + /// USEE: Unlock access to SmartEEPROM data + unlock_smart_eeprom = 0x35, + /// LSEER: Lock access to the SmartEEPROM Register Address Space (above + /// 64KB) + lock_smart_eeprom_addr_space = 0x36, + /// USEER: Unock access to the SmartEEPROM Register Address Space (above + /// 64KB) + unlock_smart_eeprom_addr_space = 0x37, +}; diff --git a/src/hal/pins.zig b/src/hal/pins.zig new file mode 100644 index 0000000..726877e --- /dev/null +++ b/src/hal/pins.zig @@ -0,0 +1,109 @@ +//! I/O Multiplexing and Pin Configuration +const microzig = @import("microzig"); + +pub const Function = enum { ADC0_AIN, ADC1_AIN, ANAREF, AC, DAC, PTC, SERCOM0, SERCOM1, SERCOM2, SERCOM3, SERCOM4, SERCOM5, SERCOM6, TC, TCC0, TCC1, QSPI, CAN0, CAN1, USB, CORTEX_CM4, SDHC, I2S, PCC, GMAC, GCLK, CCL }; + +pub const Pin = enum { + PA00, + PA01, + PA02, + PA03, + PA04, + PA05, + PA06, + PA07, + PA08, + PA09, + PA10, + PA11, + PA12, + PA13, + PA14, + PA15, + PA16, + PA17, + PA18, + PA19, + PA20, + PA21, + PA22, + PA23, + PA24, + PA25, + PA27, + PA30, + PA31, + + PB00, + PB01, + PB02, + PB03, + PB04, + PB05, + PB06, + PB07, + PB08, + PB09, + PB10, + PB11, + PB12, + PB13, + PB14, + PB15, + PB16, + PB17, + PB18, + PB19, + PB20, + PB21, + PB22, + PB23, + PB24, + PB25, + PB26, + PB27, + PB28, + PB29, + PB30, + PB31, + + PC00, + PC01, + PC02, + PC03, + PC04, + PC05, + PC06, + PC07, + PC08, + PC10, + PC11, + PC12, + PC13, + PC14, + PC15, + PC16, + PC17, + PC18, + PC19, + PC20, + PC21, + PC22, + PC23, + PC24, + PC25, + PC26, + PC27, + PC28, + PC30, + + PD00, + PD01, + PD08, + PD09, + PD10, + PD11, + PD12, + PD20, + PD21, +}; diff --git a/src/hal/port.zig b/src/hal/port.zig new file mode 100644 index 0000000..2c04b93 --- /dev/null +++ b/src/hal/port.zig @@ -0,0 +1,102 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const io_types = microzig.chip.types; +const PORT = microzig.chip.peripherals.PORT; + +pub const PinCfg = @typeInfo(std.meta.FieldType(io_types.peripherals.PORT.GROUP, .PINCFG)).Array.child; +pub const Mux = enum(u4) { A, B, C, D, E, F, G, H, I, J, K, L, M, N }; +pub const Pin = packed struct(u6) { + num: u5, + group: Group, + + pub fn config_ptr(p: Pin) *volatile PinCfg { + return &p.group.ptr().PINCFG[p.num]; + } + + pub inline fn set_mux(p: Pin, mux: Mux) void { + const pmux = &p.group.ptr().PMUX[p.num / 2]; + switch (@as(u1, @truncate(p.num))) { + 0 => pmux.modify(.{ .PMUXE = .{ + .value = @as(io_types.peripherals.PORT.PORT_PMUX__PMUXE, @enumFromInt(@intFromEnum(mux))), + } }), + 1 => pmux.modify(.{ .PMUXO = .{ + .value = @as(io_types.peripherals.PORT.PORT_PMUX__PMUXO, @enumFromInt(@intFromEnum(mux))), + } }), + } + + p.config_ptr().modify(.{ .PMUXEN = 1 }); + } + + pub inline fn set_dir(p: Pin, dir: Direction) void { + switch (dir) { + .in => { + p.group.ptr().DIRCLR.write(.{ .DIRCLR = @as(u32, 1) << p.num }); + p.group.ptr().PINCFG[p.num].modify(.{ .INEN = 1 }); + }, + .out => p.group.ptr().DIRSET.write(.{ .DIRSET = @as(u32, 1) << p.num }), + } + } + + pub inline fn read(p: Pin) Level { + return @enumFromInt(p.group.ptr().IN.read().IN >> p.num & 1); + } + + pub inline fn write(p: Pin, level: Level) void { + switch (level) { + .low => p.group.ptr().OUTCLR.write(.{ .OUTCLR = @as(u32, 1) << p.num }), + .high => p.group.ptr().OUTSET.write(.{ .OUTSET = @as(u32, 1) << p.num }), + } + } + + pub fn toggle(p: Pin) void { + p.group.ptr().OUTTGL.write(.{ .OUTTGL = @as(u32, 1) << p.num }); + } +}; + +pub const Mask = packed struct(u33) { + pins: u32, + group: Group, + + pub inline fn read(m: Mask) u32 { + return m.group.ptr().IN.read().IN & m.pins; + } + + /// Sets direction for entire mask + pub fn set_dir(m: Mask, dir: Direction) void { + switch (dir) { + .in => { + m.group.ptr().DIRCLR.write(.{ .DIRCLR = m.pins }); + for (0..32) |i| { + if (m.pins & (@as(u32, 1) << 1) != 0) + m.group.ptr().PINCFG[i].modify(.{ .INEN = 1 }); + } + }, + .out => m.group.ptr().DIRSET.write(.{ .DIRSET = m.pins }), + } + } +}; + +pub fn pin(group: Group, num: u5) Pin { + return Pin{ + .num = num, + .group = group, + }; +} + +pub fn mask(group: Group, pins: u32) Mask { + return Mask{ + .pins = pins, + .group = group, + }; +} + +pub const Group = enum(u1) { + a, + b, + + pub fn ptr(group: Group) *volatile io_types.peripherals.PORT.GROUP { + return &PORT.GROUP[@intFromEnum(group)]; + } +}; +pub const Direction = enum(u1) { in, out }; +pub const Level = enum(u1) { low, high }; diff --git a/src/hal/qspi.zig b/src/hal/qspi.zig new file mode 100644 index 0000000..25198bc --- /dev/null +++ b/src/hal/qspi.zig @@ -0,0 +1,47 @@ +const microzig = @import("microzig"); +const QSPI = microzig.chip.peripherals.QSPI; + +const types = microzig.chip.types.peripherals.QSPI; +pub const CSMODE = types.QSPI_CTRLB__CSMODE; +pub const DATALEN = types.QSPI_CTRLB__DATALEN; +pub const MODE = types.QSPI_CTRLB__MODE; +pub const LOOPEN = types.QSPI_CTRLB__LOOPEN; +pub const ADDRLEN = types.QSPI_INSTRFRAME__ADDRLEN; +pub const OPTCODELEN = types.QSPI_INSTRFRAME__OPTCODELEN; +pub const TFRTYPE = types.QSPI_INSTRFRAME__TFRTYPE; +pub const WIDTH = types.QSPI_INSTRFRAME__WIDTH; + +pub fn init() void { + reset(); + defer enable(); + + QSPI.BAUD.modify(.{ + .BAUD = 2, + }); + QSPI.CTRLB.modify(.{ + .MODE = .{ .value = .MEMORY }, + .DATALEN = .{ .value = .@"8BITS" }, + .CSMODE = .{ .value = .LASTXFER }, + }); + + for (microzig.board.qspi) |qspi_pin| + qspi_pin.set_mux(.H); +} + +pub fn reset() void { + QSPI.CTRLA.modify(.{ + .SWRST = 1, + }); +} + +pub fn enable() void { + QSPI.CTRLA.modify(.{ + .ENABLE = 1, + }); +} + +pub fn disable() void { + QSPI.CTRLA.modify(.{ + .ENABLE = 0, + }); +} diff --git a/src/hal/sercom.zig b/src/hal/sercom.zig new file mode 100644 index 0000000..f119b3c --- /dev/null +++ b/src/hal/sercom.zig @@ -0,0 +1,170 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const port = hal.port; + +const peripherals = microzig.chip.peripherals; +const SERCOM = microzig.chip.types.peripherals.SERCOM; + +pub const Sercom = enum(u3) { + SERCOM0, + SERCOM1, + SERCOM2, + SERCOM3, + SERCOM4, + SERCOM5, + SERCOM6, + SERCOM7, + + fn get_regs(sercom: Sercom) *volatile SERCOM { + return switch (sercom) { + .SERCOM0 => if (@hasDecl(peripherals, "SERCOM0")) + peripherals.SERCOM0 + else + unreachable, // MCU does not have SERCOM0 + .SERCOM1 => if (@hasDecl(peripherals, "SERCOM1")) + peripherals.SERCOM1 + else + unreachable, // MCU does not have SERCOM1 + .SERCOM2 => if (@hasDecl(peripherals, "SERCOM2")) + peripherals.SERCOM2 + else + unreachable, // MCU does not have SERCOM2 + .SERCOM3 => if (@hasDecl(peripherals, "SERCOM3")) + peripherals.SERCOM3 + else + unreachable, // MCU does not have SERCOM3 + .SERCOM4 => if (@hasDecl(peripherals, "SERCOM4")) + peripherals.SERCOM4 + else + unreachable, // MCU does not have SERCOM4 + .SERCOM5 => if (@hasDecl(peripherals, "SERCOM5")) + peripherals.SERCOM5 + else + unreachable, // MCU does not have SERCOM5 + .SERCOM6 => if (@hasDecl(peripherals, "SERCOM6")) + peripherals.SERCOM6 + else + unreachable, // MCU does not have SERCOM6 + .SERCOM7 => if (@hasDecl(peripherals, "SERCOM7")) + peripherals.SERCOM7 + else + unreachable, // MCU does not have SERCOM7 + }; + } +}; + +pub const spi = struct { + pub const CPHA = SERCOM.SERCOM_SPIM_CTRLA__CPHA; + pub const CPOL = SERCOM.SERCOM_SPIM_CTRLA__CPOL; + pub const DORD = SERCOM.SERCOM_SPIM_CTRLA__DORD; + + pub const Master = struct { + sercom: Sercom, + + pub const ConfigureOptions = struct { + cpha: CPHA, + cpol: CPOL, + dord: DORD, + dopo: SERCOM.SERCOM_SPIM_CTRLA__DOPO, + ref_freq_hz: u32, + baud_freq_hz: u32, + }; + + const SPIM = for (@typeInfo(SERCOM).Union.fields) |field| { + if (std.mem.eql(u8, field.name, "SPIM")) + break field.type; + } else @compileError("no SPIM field"); + + fn get_regs(m: Master) *volatile SPIM { + return &m.sercom.get_regs().SPIM; + } + + // TODO: pin and clock configuration + pub fn init(sercom: Sercom, opts: ConfigureOptions) Master { + const master = Master{ .sercom = sercom }; + master.reset(); + + master.disable(); + defer master.enable(); + + const regs = master.get_regs(); + regs.CTRLA.modify(.{ + .MODE = .{ .value = .SPI_MASTER }, + .FORM = .{ .value = .SPI_FRAME }, + .CPOL = .{ .value = opts.cpol }, + .CPHA = .{ .value = opts.cpha }, + // TODO: allow for reception + .DIPO = .{ .value = .PAD0 }, + .DOPO = .{ .value = opts.dopo }, + .DORD = .{ .value = opts.dord }, + }); + // CTRLB only needs syncronization if the module is enabled. + regs.CTRLB.modify(.{ + .CHSIZE = .{ .value = .@"8_BIT" }, + .MSSEN = 0, + // TODO: configure RX + .RXEN = 0, + }); + + regs.BAUD.write(.{ + .BAUD = @intCast((opts.ref_freq_hz / (2 * opts.baud_freq_hz)) - 1), + }); + + return master; + } + + pub fn enable(m: Master) void { + const regs = m.get_regs(); + regs.CTRLA.modify(.{ + .ENABLE = 1, + }); + + while (regs.SYNCBUSY.read().ENABLE == 1) {} + } + + pub fn disable(m: Master) void { + const regs = m.get_regs(); + regs.CTRLA.modify(.{ + .ENABLE = 0, + }); + + while (regs.SYNCBUSY.read().ENABLE == 1) {} + } + + pub fn reset(m: Master) void { + const regs = m.get_regs(); + regs.CTRLA.modify(.{ + .SWRST = 1, + }); + + while (regs.SYNCBUSY.read().SWRST == 1) {} + } + + // Note: TXC is set when the data has been shifted and there's nothing + // in DATA + // + // DRE is set when DATA is empty and ready for new data to transmit + pub fn write_blocking(m: Master, byte: u8) void { + const regs = m.get_regs(); + regs.DATA.write(.{ .DATA = byte }); + while (regs.INTFLAG.read().TXC == 0) {} + } + + pub fn write_all_blocking(m: Master, bytes: []const u8) void { + const regs = m.get_regs(); + for (bytes) |b| { + regs.DATA.write(.{ .DATA = b }); + while (regs.INTFLAG.read().DRE == 0) {} + } + + while (regs.INTFLAG.read().TXC == 0) {} + } + + pub fn transfer_blocking(m: Master, byte: u8) u8 { + const regs = m.get_regs(); + m.write_blocking(byte); + return @truncate(regs.DATA.read().DATA); + } + }; +}; diff --git a/src/hal/timer.zig b/src/hal/timer.zig new file mode 100644 index 0000000..4a4d660 --- /dev/null +++ b/src/hal/timer.zig @@ -0,0 +1,177 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const mclk = hal.mclk; +const gclk = hal.gclk; + +const peripherals = microzig.chip.peripherals; +const TC0 = peripherals.TC0; +const TC1 = peripherals.TC1; +const MCLK = peripherals.MCLK; + +pub fn init() void { + MCLK.APBAMASK.modify(.{ .TC0_ = 1, .TC1_ = 1 }); + TC0.COUNT32.CTRLA.write(.{ + .SWRST = 0, + .ENABLE = 0, + .MODE = .{ .raw = 0 }, + .PRESCSYNC = .{ .raw = 0 }, + .RUNSTDBY = 0, + .ONDEMAND = 0, + .PRESCALER = .{ .raw = 0 }, + .ALOCK = 0, + .reserved16 = 0, + .CAPTEN0 = 0, + .CAPTEN1 = 0, + .reserved20 = 0, + .COPEN0 = 0, + .COPEN1 = 0, + .reserved24 = 0, + .CAPTMODE0 = .{ .raw = 0 }, + .reserved27 = 0, + .CAPTMODE1 = .{ .raw = 0 }, + .padding = 0, + }); + while (TC0.COUNT32.SYNCBUSY.read().ENABLE != 0) {} + TC1.COUNT32.CTRLA.write(.{ + .SWRST = 0, + .ENABLE = 0, + .MODE = .{ .raw = 0 }, + .PRESCSYNC = .{ .raw = 0 }, + .RUNSTDBY = 0, + .ONDEMAND = 0, + .PRESCALER = .{ .raw = 0 }, + .ALOCK = 0, + .reserved16 = 0, + .CAPTEN0 = 0, + .CAPTEN1 = 0, + .reserved20 = 0, + .COPEN0 = 0, + .COPEN1 = 0, + .reserved24 = 0, + .CAPTMODE0 = .{ .raw = 0 }, + .reserved27 = 0, + .CAPTMODE1 = .{ .raw = 0 }, + .padding = 0, + }); + while (TC1.COUNT32.SYNCBUSY.read().ENABLE != 0) {} + gclk.set_peripheral_clk_gen(.GCLK_TC0_TC1, .GCLK1); + + TC0.COUNT32.CTRLA.write(.{ + .SWRST = 1, + .ENABLE = 0, + .MODE = .{ .raw = 0 }, + .PRESCSYNC = .{ .raw = 0 }, + .RUNSTDBY = 0, + .ONDEMAND = 0, + .PRESCALER = .{ .raw = 0 }, + .ALOCK = 0, + .reserved16 = 0, + .CAPTEN0 = 0, + .CAPTEN1 = 0, + .reserved20 = 0, + .COPEN0 = 0, + .COPEN1 = 0, + .reserved24 = 0, + .CAPTMODE0 = .{ .raw = 0 }, + .reserved27 = 0, + .CAPTMODE1 = .{ .raw = 0 }, + .padding = 0, + }); + while (TC0.COUNT32.SYNCBUSY.read().SWRST != 0) {} + gclk.set_peripheral_clk_gen(.GCLK_TC0_TC1, .GCLK1); + + TC1.COUNT32.CTRLA.write(.{ + .SWRST = 1, + .ENABLE = 0, + .MODE = .{ .raw = 0 }, + .PRESCSYNC = .{ .raw = 0 }, + .RUNSTDBY = 0, + .ONDEMAND = 0, + .PRESCALER = .{ .raw = 0 }, + .ALOCK = 0, + .reserved16 = 0, + .CAPTEN0 = 0, + .CAPTEN1 = 0, + .reserved20 = 0, + .COPEN0 = 0, + .COPEN1 = 0, + .reserved24 = 0, + .CAPTMODE0 = .{ .raw = 0 }, + .reserved27 = 0, + .CAPTMODE1 = .{ .raw = 0 }, + .padding = 0, + }); + while (TC1.COUNT32.SYNCBUSY.read().SWRST != 0) {} + TC0.COUNT32.CTRLA.write(.{ + .SWRST = 0, + .ENABLE = 1, + .MODE = .{ .value = .COUNT32 }, + .PRESCSYNC = .{ .value = .GCLK }, + .RUNSTDBY = 0, + .ONDEMAND = 0, + .PRESCALER = .{ .value = .DIV1 }, + .ALOCK = 0, + .reserved16 = 0, + .CAPTEN0 = 0, + .CAPTEN1 = 0, + .reserved20 = 0, + .COPEN0 = 0, + .COPEN1 = 0, + .reserved24 = 0, + .CAPTMODE0 = .{ .raw = 0 }, + .reserved27 = 0, + .CAPTMODE1 = .{ .raw = 0 }, + .padding = 0, + }); + while (TC0.COUNT32.SYNCBUSY.read().ENABLE != 0) {} + TC0.COUNT32.CTRLBSET.write(.{ + .DIR = 1, + .LUPD = 0, + .ONESHOT = 1, + .reserved5 = 0, + .CMD = .{ .value = .STOP }, + }); + while (TC0.COUNT32.SYNCBUSY.read().CTRLB != 0) {} +} + +pub fn start_delay(us: u32) void { + TC0.COUNT32.COUNT.write(.{ .COUNT = us }); + while (TC0.COUNT32.SYNCBUSY.read().COUNT != 0) {} + TC0.COUNT32.CTRLBSET.write(.{ + .DIR = 1, + .LUPD = 0, + .ONESHOT = 1, + .reserved5 = 0, + .CMD = .{ .value = .RETRIGGER }, + }); + while (TC0.COUNT32.SYNCBUSY.read().CTRLB != 0) {} +} + +pub fn finish_delay() void { + while (TC0.COUNT32.STATUS.read().STOP != 1) {} +} + +pub fn delay_us(us: u32) void { + start_delay(us); + finish_delay(); +} + +pub const GCLK = struct { + pub const GEN = struct { + fn Gen(comptime id: u4) type { + const tag = std.fmt.comptimePrint("GCLK{d}", .{id}); + return struct { + pub const ID = id; + pub const SYNCBUSY_GENCTRL = @intFromEnum(@field(microzig.chip.types.peripherals.GCLK.GCLK_SYNCBUSY__GENCTRL, tag)); + pub const PCHCTRL_GEN = @field(microzig.chip.types.peripherals.GCLK.GCLK_PCHCTRL__GEN, tag); + }; + } + pub const @"120MHz" = Gen(0); + pub const @"76.8KHz" = Gen(1); + pub const @"48MHz" = Gen(2); + pub const @"8.4672MHz" = Gen(3); + pub const @"1MHz" = Gen(4); + pub const @"64KHz" = Gen(11); + }; +}; diff --git a/src/hal/usb.zig b/src/hal/usb.zig new file mode 100644 index 0000000..9eac718 --- /dev/null +++ b/src/hal/usb.zig @@ -0,0 +1,153 @@ +//! USB Controller +//! +//! Dependencies +//! ============ +//! +//! I/O Lines +//! +//! The USB pins may be multiplexed with the I/O lines controller. The user +//! must first configure the I/O controller to assign the USB pins to their +//! peripheral functions. +//! +//! +//! Clocks +//! +//! The USB bus clock (CLK_USB_AHB) can be enabled and disabled in the main +//! clock module (MCLK) and the default state of CLK_USB_AHB can be found in +//! _Peripheral Clock Masking_. +//! +//! A generic clock (GCLK_USB) is required the clock the USB. This clock must be +//! configured and enabled in the Generic Clock Controller before using the USB. +//! +//! The generic clock is asynchronous to the bus clock (CLK_USB_AHB). Due to +//! this asynchronicity, writes to certain registers will require +//! synchronization between clock domains. +//! +//! The USB module requires a GCLK_USB of 48 MHz clock for low speed and full +//! speed operation. To follow the USB data rate at 12 Mbit/s in full-speed +//! mode, the CLK_USB_AHB clock should be at minimum 8MHz. THe GCLK_USB clock is +//! generated by the DFLL48 using a reference clock. When the USB is disabled, +//! the GCLK used as DFLL reference should be disabled. +//! +//! Clock recover is achieved by a digital phase-locked loop in the USB module, +//! which complies with the USB jitter specifications. If crystal-less operation +//! is used in USB device mode, refer to _USB Clock Recovery Module_. +const std = @import("std"); +const microzig = @import("microzig"); +const core = microzig.core; + +const USB = microzig.chip.peripherals.USB; + +const ENDPOINT = microzig.chip.types.peripherals.USB.ENDPOINT; +const USB_DESCRIPTOR = microzig.chip.types.peripherals.USB.USB_DESCRIPTOR; + +var endpoint_buffer_storage: [8][2][64]u8 align(4) = .{.{.{0} ** 64} ** 2} ** 8; +const endpoint_buffer: *align(4) volatile [8][2][64]u8 = &endpoint_buffer_storage; + +pub const Endpoint = enum(u3) { + _, + + pub fn write(endpoint: Endpoint, data: []const u8) void { + const buffer = &endpoint_buffer[@intFromEnum(endpoint)][1]; + @memcpy(buffer[0..data.len], data); + microzig.cp.dmb(); + + const descriptors: *volatile [8]USB_DESCRIPTOR = @ptrFromInt(USB.DEVICE.DESCADD.read().DESCADD); + + // Set buffer address for ep data + const descriptor = &descriptors[@intFromEnum(endpoint)].DEVICE; + descriptor.ADDR.write(.{ .ADDR = @intFromPtr(buffer) }); + + // Set the byte count as zero + // Set the multi packetsize as zero for multi-packet transfers where + // length > ep size + // TODO: the comment and code don't line up? + descriptor.PCKSIZE.modify(.{ + .BYTE_COUNT = @as(u14, @intCast(data.len)), + .MULTI_PACKET_SIZE = 0, + }); + + endpoint.clear_int_flag(.TRCPT1); + endpoint.set_status_flag(.BLK1RDY); + endpoint.wait_for_transfer_complete_blocking(); + } + + pub fn wait_for_transfer_complete_blocking(endpoint: Endpoint) void { + const ctrl = endpoint.get_ctrl_regs(); + while (ctrl.EPINTFLAG.read().TRCPT1 == 0) {} + } + + pub fn set_bank_ready(endpoint: Endpoint) void { + endpoint.get_ctrl_regs().EPSTATUSSET.write(.{ + .DTGLOUT = 0, + .DTGLIN = 0, + .CURBK = 0, + .reserved4 = 0, + .STALLRQ0 = 0, + .STALLRQ1 = 0, + .BK0RDY = 0, + .BK1RDY = 1, + }); + } + + pub const IntFlag = enum { + TRCPT0, + TRCPT1, + TRFAIL0, + TRFAIL1, + RXSTP, + STALL0, + STALL1, + }; + + pub fn clear_int_flag(endpoint: Endpoint, flag: IntFlag) void { + endpoint.get_ctrl_regs().EPINTFLAG.write(.{ + .TRCPT0 = @intFromBool(flag == .TRCPT0), + .TRCPT1 = @intFromBool(flag == .TRCPT1), + .TRFAIL0 = @intFromBool(flag == .TRFAIL0), + .TRFAIL1 = @intFromBool(flag == .TRFAIL1), + .RXSTP = @intFromBool(flag == .RXSTP), + .STALL0 = @intFromBool(flag == .STALL0), + .STALL1 = @intFromBool(flag == .STALL1), + .padding = 0, + }); + } + + pub const StatusFlag = enum { + DTGLOUT, + DTGLIN, + CURBK, + STALLRQ0, + STALLRQ1, + BLK0RDY, + BLK1RDY, + }; + + pub fn clear_status_flag(endpoint: Endpoint, flag: StatusFlag) void { + endpoint.get_ctrl_regs().EPSTATUSCLR.write(.{ + .DTGLOUT = @intFromBool(flag == .DTGLOUT), + .DTGLIN = @intFromBool(flag == .DTGLIN), + .CURBK = @intFromBool(flag == .CURBK), + .STALLRQ0 = @intFromBool(flag == .STALLRQ0), + .STALLRQ1 = @intFromBool(flag == .STALLRQ1), + .BLK0RDY = @intFromBool(flag == .BLK0RDY), + .BLK1RDY = @intFromBool(flag == .BLK1RDY), + }); + } + + pub fn set_status_flag(endpoint: Endpoint, flag: StatusFlag) void { + endpoint.get_ctrl_regs().EPSTATUSSET.write(.{ + .DTGLOUT = @intFromBool(flag == .DTGLOUT), + .DTGLIN = @intFromBool(flag == .DTGLIN), + .CURBK = @intFromBool(flag == .CURBK), + .STALLRQ0 = @intFromBool(flag == .STALLRQ0), + .STALLRQ1 = @intFromBool(flag == .STALLRQ1), + .BLK0RDY = @intFromBool(flag == .BLK0RDY), + .BLK1RDY = @intFromBool(flag == .BLK1RDY), + }); + } + + fn get_ctrl_regs(endpoint: Endpoint) *volatile ENDPOINT { + return &USB.DEVICE.DEVICE_ENDPOINT[@intFromEnum(endpoint)]; + } +};