From 6cdf641f32de994b12f5e7507700726486147f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=28xq=29=20Quei=C3=9Fner?= Date: Wed, 28 Apr 2021 22:36:29 +0200 Subject: [PATCH] Continues blinky setup. --- build.zig | 92 ++++++++++--- src/core/gpio.zig | 122 ++++++++++++++++++ src/core/microzig.zig | 11 ++ src/core/pin.zig | 32 +++++ .../boards/mbed-lpc1768/mbed-lpc1768.zig | 47 ++++++- src/modules/chips/atmega328p/atmega328p.zig | 46 +++++++ src/modules/chips/lpc1768/lpc1768.zig | 47 +++++++ tests/blinky.zig | 37 ++++++ 8 files changed, 414 insertions(+), 20 deletions(-) create mode 100644 tests/blinky.zig diff --git a/build.zig b/build.zig index 3f3dda7..02b841b 100644 --- a/build.zig +++ b/build.zig @@ -17,17 +17,25 @@ pub fn build(b: *std.build.Builder) void { BuildConfig{ .name = "chips.lpc1768", .backing = Backing{ .chip = pkgs.chips.lpc1768 } }, }; + const Test = struct { name: []const u8, source: []const u8 }; + const all_tests = [_]Test{ + Test{ .name = "minimal", .source = "tests/minimal.zig" }, + Test{ .name = "blinky", .source = "tests/blinky.zig" }, + }; + inline for (all_backings) |cfg| { - const exe = addEmbeddedExecutable( - b, - "test-minimal-" ++ cfg.name, - "tests/minimal.zig", - cfg.backing, - ); - exe.setBuildMode(mode); - exe.install(); - - test_step.dependOn(&exe.step); + inline for (all_tests) |tst| { + const exe = addEmbeddedExecutable( + b, + "test-" ++ tst.name ++ "-" ++ cfg.name, + tst.source, + cfg.backing, + ); + exe.setBuildMode(mode); + exe.install(); + + test_step.dependOn(&exe.step); + } } } @@ -39,6 +47,8 @@ fn addEmbeddedExecutable(builder: *std.build.Builder, name: []const u8, source: .board => |b| b.chip, }; + const has_board = (backing == .board); + const chip_package = Pkg{ .name = "chip", .path = chip.path, @@ -81,6 +91,58 @@ fn addEmbeddedExecutable(builder: *std.build.Builder, name: []const u8, source: break :blk builder.dupe(filename); }; + const config_file_name = blk: { + const hash = hash_blk: { + var hasher = std.hash.SipHash128(1, 2).init("abcdefhijklmnopq"); + + hasher.update(chip.name); + hasher.update(chip.path); + hasher.update(chip.cpu.name); + hasher.update(chip.cpu.path); + + if (backing == .board) { + hasher.update(backing.board.name); + hasher.update(backing.board.path); + } + + var mac: [16]u8 = undefined; + hasher.final(&mac); + break :hash_blk mac; + }; + + const file_prefix = "zig-cache/microzig/config-"; + const file_suffix = ".zig"; + + var ld_file_name: [file_prefix.len + 2 * hash.len + file_suffix.len]u8 = undefined; + const filename = std.fmt.bufPrint(&ld_file_name, "{s}{}{s}", .{ + file_prefix, + std.fmt.fmtSliceHexLower(&hash), + file_suffix, + }) catch unreachable; + + break :blk builder.dupe(filename); + }; + + { + var config_file = std.fs.cwd().createFile(config_file_name, .{}) catch unreachable; + defer config_file.close(); + + var writer = config_file.writer(); + + writer.print("pub const has_board = {};\n", .{has_board}) catch unreachable; + if (has_board) { + writer.print("pub const board_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(backing.board.name)}) catch unreachable; + } + + writer.print("pub const chip_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(chip.name)}) catch unreachable; + writer.print("pub const cpu_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(chip.cpu.name)}) catch unreachable; + } + + const config_pkg = Pkg{ + .name = "microzig-config", + .path = config_file_name, + }; + const linkerscript_gen = builder.addExecutable("linkerscript-gen", "src/tools/linkerscript-gen.zig"); linkerscript_gen.addPackage(chip_package); linkerscript_gen.addPackage(Pkg{ @@ -113,24 +175,18 @@ fn addEmbeddedExecutable(builder: *std.build.Builder, name: []const u8, source: switch (backing) { .chip => { - exe.addBuildOption(bool, "microzig_has_board", false); - exe.addBuildOption([]const u8, "microzig_chip_name", chip.name); - exe.addBuildOption([]const u8, "microzig_cpu_name", chip.cpu.name); exe.addPackage(Pkg{ .name = "microzig", .path = "src/core/microzig.zig", - .dependencies = &[_]Pkg{chip_package}, + .dependencies = &[_]Pkg{ config_pkg, chip_package }, }); }, .board => |board| { - exe.addBuildOption(bool, "microzig_has_board", true); - exe.addBuildOption([]const u8, "microzig_board_name", board.name); - exe.addBuildOption([]const u8, "microzig_chip_name", chip.name); - exe.addBuildOption([]const u8, "microzig_cpu_name", chip.cpu.name); exe.addPackage(Pkg{ .name = "microzig", .path = "src/core/microzig.zig", .dependencies = &[_]Pkg{ + config_pkg, chip_package, Pkg{ .name = "board", diff --git a/src/core/gpio.zig b/src/core/gpio.zig index 718e6fb..fc5665f 100644 --- a/src/core/gpio.zig +++ b/src/core/gpio.zig @@ -1,2 +1,124 @@ const std = @import("std"); const micro = @import("microzig.zig"); +const chip = @import("chip"); + +pub const Mode = enum { + input, + output, + input_output, + open_drain, + generic, +}; + +pub const State = enum(u1) { + low = 0, + high = 1, +}; + +pub const Drive = enum(u1) { + disabled = 0, + enabled = 1, +}; + +pub const Direction = enum(u1) { + input = 0, + output = 1, +}; + +pub fn Gpio(comptime pin: type, config: anytype) type { + const mode = @as(Mode, config.mode); + const Generic = struct { + // all pins: + fn init() void { + switch (mode) { + .input, .generic, .input_output => { + setDirection(.input, undefined); + }, + .output => { + if (comptime !@hasField(@TypeOf(config), "initial_state")) + @compileError("An output pin requires initial_state to be either .high or .low"); + setDirection(.output, switch (config.initial_state) { + .low => State.low, + .high => State.high, + else => @compileError("An output pin requires initial_state to be either .high or .low"), + }); + }, + .open_drain => { + if (comptime !@hasField(@TypeOf(config), "initial_state")) + @compileError("An open_drain pin requires initial_state to be either .floating or .driven"); + setDirection(.input, undefined); + setDrive(switch (config.initial_state) { + .floating => Drive.disabled, + .driven => Drive.enabled, + else => @compileError("An open_drain pin requires initial_state to be either .floating or .driven"), + }); + }, + } + } + + fn read() State { + return @intToEnum(State, chip.gpio.read(pin.source_pin)); + } + + // outputs: + fn write(state: State) void { + chip.gpio.write(pin.source_pin, @enumToInt(state)); + } + + fn setToHigh() void { + write(.high); + } + fn setToLow() void { + write(.low); + } + fn toggle() void { + if (comptime @hasDecl(chip.gpio, "toggle")) { + chip.gpio.toggle(pin.source_pin); + } else { + write(switch (read()) { + .low => State.high, + .high => State.low, + }); + } + } + + // bi-di: + fn setDirection(dir: Direction, output_state: State) void {} + fn getDirection() Direction {} + + // open drain + fn setDrive(drive: Drive) void {} + fn getDrive() Drive {} + }; + // return only a subset of Generic for the requested pin. + switch (mode) { + .input => return struct { + pub const init = Generic.init; + pub const read = Generic.read; + }, + .output => return struct { + pub const init = Generic.init; + pub const read = Generic.read; + pub const write = Generic.write; + pub const setToHigh = Generic.setToHigh; + pub const setToLow = Generic.setToLow; + pub const toggle = Generic.toggle; + }, + .input_output => return struct { + pub const init = Generic.init; + pub const read = Generic.read; + pub const write = Generic.write; + pub const setToHigh = Generic.setToHigh; + pub const setToLow = Generic.setToLow; + pub const setDirection = Generic.setDirection; + pub const getDirection = Generic.getDirection; + }, + .open_drain => return struct { + pub const init = Generic.init; + pub const read = Generic.read; + pub const setDrive = Generic.setDrive; + pub const getDrive = Generic.getDrive; + }, + .generic => Generic, + } +} diff --git a/src/core/microzig.zig b/src/core/microzig.zig index 3c5ee8f..f3d23ad 100644 --- a/src/core/microzig.zig +++ b/src/core/microzig.zig @@ -1,6 +1,11 @@ const std = @import("std"); const root = @import("root"); +/// Contains build-time generated configuration options for microzig. +/// Contains a CPU target description, chip, board and cpu information +/// and so on. +pub const config = @import("microzig-config"); + /// Provides access to the low level features of the current microchip. pub const chip = @import("chip"); @@ -10,6 +15,12 @@ pub const cpu = chip.cpu; /// Module that helps with interrupt handling. pub const interrupts = @import("interrupts.zig"); +const gpio = @import("gpio.zig"); +pub const Gpio = gpio.Gpio; + +const pin = @import("pin.zig"); +pub const Pin = pin.Pin; + /// The microzig panic handler. Will disable interrupts and loop endlessly. /// Export this symbol from your main file to enable microzig: /// ``` diff --git a/src/core/pin.zig b/src/core/pin.zig index 718e6fb..c7c5db4 100644 --- a/src/core/pin.zig +++ b/src/core/pin.zig @@ -1,2 +1,34 @@ const std = @import("std"); const micro = @import("microzig.zig"); +const chip = @import("chip"); +const board = @import("board"); + +/// Returns a type that will manage the Pin defined by `spec`. +/// Spec is either the pin as named in the datasheet of the chip +/// or, if a board is present, as named on the board. +/// +/// When a name conflict happens, pin names can be prefixed with `board:` +/// to force-select a pin from the board and with `chip:` to force-select +/// the pin from the chip. +pub fn Pin(comptime spec: []const u8) type { + // TODO: Depened on board and chip here + + // Pins can be namespaced with "Board:" for board and "Chip:" for chip + const pin = if (std.mem.startsWith(u8, spec, "board:")) + chip.parsePin(@field(board.pin_map, spec)) + else if (std.mem.startsWith(u8, spec, "chip:")) + chip.parsePin(spec) + else if (micro.config.has_board and @hasField(@TypeOf(board.pin_map), spec)) + chip.parsePin(@field(board.pin_map, spec)) + else + chip.parsePin(spec); + + return struct { + pub const name = spec; + pub const source_pin = pin; + + pub fn route(target: pin.Targets) void { + unreachable; + } + }; +} diff --git a/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig b/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig index 09e63c0..4994c7f 100644 --- a/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig +++ b/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig @@ -1,6 +1,49 @@ pub const chip = @import("chip"); -pub const chip = @import("chip"); pub const pin_map = .{ - // TODO: Fill this + // Onboard-LEDs + .@"LED-1" = "P1.18", + .@"LED-2" = "P1.20", + .@"LED-3" = "P1.21", + .@"LED-4" = "P1.23", + .@"LED_LINK" = "P1.25", + .@"LED_SPEED" = "P1.26", + + // Ethernet + .@"TD+" = "P1.0", + .@"TD-" = "P1.1", + .@"RD+" = "P1.9", + .@"RD-" = "P1.10", + + // USB + .@"D+" = "P0.29", + .@"D-" = "P0.30", + + // GPIO pins + .@"DIP5" = "P0.9", + .@"DIP6" = "P0.8", + .@"DIP7" = "P0.7", + .@"DIP8" = "P0.6", + .@"DIP9" = "P0.0", + .@"DIP10" = "P0.1", + .@"DIP11" = "P0.18", + .@"DIP12" = "P0.17", + .@"DIP13" = "P0.15", + .@"DIP14" = "P0.16", + .@"DIP15" = "P0.23", + .@"DIP16" = "P0.24", + .@"DIP17" = "P0.25", + .@"DIP18" = "P0.26", + .@"DIP19" = "P1.30", + .@"DIP20" = "P1.31", + .@"DIP21" = "P2.5", + .@"DIP22" = "P2.4", + .@"DIP23" = "P2.3", + .@"DIP24" = "P2.2", + .@"DIP25" = "P2.1", + .@"DIP26" = "P2.0", + .@"DIP27" = "P0.11", + .@"DIP28" = "P0.10", + .@"DIP29" = "P0.5", + .@"DIP30" = "P0.4", }; diff --git a/src/modules/chips/atmega328p/atmega328p.zig b/src/modules/chips/atmega328p/atmega328p.zig index 3277db7..876679d 100644 --- a/src/modules/chips/atmega328p/atmega328p.zig +++ b/src/modules/chips/atmega328p/atmega328p.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const micro_linker = @import("microzig-linker"); pub const cpu = @import("cpu"); @@ -6,3 +7,48 @@ pub const memory_regions = [_]micro_linker.MemoryRegion{ micro_linker.MemoryRegion{ .offset = 0x000000, .length = 32 * 1024, .kind = .flash }, micro_linker.MemoryRegion{ .offset = 0x800100, .length = 2048, .kind = .ram }, }; + +pub fn parsePin(comptime spec: []const u8) type { + const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}.{Pin}\" scheme."; + + if (spec.len != 3) + @compileError(invalid_format_msg); + if (spec[0] != 'P') + @compileError(invalid_format_msg); + + return struct { + pub const port: u8 = std.ascii.toUpper(spec[1]) - 'A'; + pub const pin: u3 = @intCast(u3, spec[2] - '0'); + }; +} + +pub const gpio = struct { + fn dirReg(comptime pin: type) *volatile u8 { + return @intToPtr(*volatile u8, 0x01); + } + fn portReg(comptime pin: type) *volatile u8 { + return @intToPtr(*volatile u8, 0x01); + } + fn pinReg(comptime pin: type) *volatile u8 { + return @intToPtr(*volatile u8, 0x01); + } + + pub fn read(comptime pin: type) u1 { + return if ((pinReg(pin).* & (1 << pin.pin)) != 0) + @as(u1, 1) + else + 0; + } + + pub fn write(comptime pin: type, state: u1) void { + if (state == 1) { + portReg(pin).* |= (1 << pin.pin); + } else { + portReg(pin).* &= ~@as(u8, 1 << pin.pin); + } + } + + pub fn toggle(comptime pin: type) void { + portReg(pin).* ^= (1 << pin.pin); + } +}; diff --git a/src/modules/chips/lpc1768/lpc1768.zig b/src/modules/chips/lpc1768/lpc1768.zig index 5fa7874..7bde22e 100644 --- a/src/modules/chips/lpc1768/lpc1768.zig +++ b/src/modules/chips/lpc1768/lpc1768.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const micro_linker = @import("microzig-linker"); pub const cpu = @import("cpu"); @@ -7,3 +8,49 @@ pub const memory_regions = [_]micro_linker.MemoryRegion{ micro_linker.MemoryRegion{ .offset = 0x10000000, .length = 32 * 1024, .kind = .ram }, micro_linker.MemoryRegion{ .offset = 0x2007C000, .length = 32 * 1024, .kind = .ram }, }; + +pub fn parsePin(comptime spec: []const u8) type { + const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}.{Pin}\" scheme."; + if (spec[0] != 'P') + @compileError(invalid_format_msg); + + const index = std.mem.indexOfScalar(u8, spec, '.') orelse @compileError(invalid_format_msg); + + return struct { + pub const port: u3 = std.fmt.parseInt(u3, spec[1..index], 10) catch @compileError(invalid_format_msg); + pub const pin: u5 = std.fmt.parseInt(u5, spec[index + 1 ..], 10) catch @compileError(invalid_format_msg); + + const gpio_register = @intToPtr(*volatile registers.GPIO, registers.gpio_base + 0x20 * @as(u32, port)); + const gpio_mask: u32 = (1 << pin); + }; +} + +pub const gpio = struct { + pub fn read(comptime pin: type) u1 { + return if ((pin.gpio_register.pin & pin.gpio_mask) != 0) + @as(u1, 1) + else + 0; + } + + pub fn write(comptime pin: type, state: u1) void { + if (state == 1) { + pin.gpio_register.set = pin.gpio_mask; + } else { + pin.gpio_register.clr = pin.gpio_mask; + } + } +}; + +pub const registers = struct { + pub const GPIO = extern struct { + dir: u32, // 0x00 + pad: [3]u32, + mask: u32, // 0x10 + pin: u32, // 0x14, + set: u32, // 0x18, + clr: u32, // 0x1C, + }; + + pub const gpio_base = 0x2009_C000; +}; diff --git a/tests/blinky.zig b/tests/blinky.zig new file mode 100644 index 0000000..7b30cee --- /dev/null +++ b/tests/blinky.zig @@ -0,0 +1,37 @@ +const micro = @import("microzig"); + +// this will instantiate microzig and pull in all dependencies +pub const panic = micro.panic; + +// Configures the led_pin to a hardware pin +const led_pin = switch (@import("builtin").cpu.arch) { + .avr => if (micro.config.has_board) + micro.Pin("D13") + else + micro.Pin("PB5"), + .arm => if (micro.config.has_board) + micro.Pin("LED-1") + else + micro.Pin("P1.18"), + else => @compileError("Unsupported platform!"), +}; + +pub fn main() void { + const led = micro.Gpio(led_pin, .{ + .mode = .output, + .initial_state = .low, + }); + led.init(); + + while (true) { + busyloop(); + led.toggle(); + } +} + +fn busyloop() void { + var i: u24 = 0; + while (i < 100_000) : (i += 1) { + @import("std").mem.doNotOptimizeAway(i); + } +}