diff --git a/bsp/gigadevice/gd32/build.zig b/bsp/gigadevice/gd32/build.zig index 8d977b2..ec39c87 100644 --- a/bsp/gigadevice/gd32/build.zig +++ b/bsp/gigadevice/gd32/build.zig @@ -9,7 +9,7 @@ fn path(comptime suffix: []const u8) std.Build.LazyPath { } const hal = .{ - .root_source_file = path("/src/hals/GD32VF103.zig"), + .root_source_file = path("/src/hals/GD32VF103/hal.zig"), }; pub const chips = struct { diff --git a/bsp/gigadevice/gd32/src/hals/GD32VF103/gpio.zig b/bsp/gigadevice/gd32/src/hals/GD32VF103/gpio.zig new file mode 100644 index 0000000..9be798a --- /dev/null +++ b/bsp/gigadevice/gd32/src/hals/GD32VF103/gpio.zig @@ -0,0 +1,158 @@ +const std = @import("std"); +const assert = std.debug.assert; + +const microzig = @import("microzig"); +pub const peripherals = microzig.chip.peripherals; + +const GPIOA = peripherals.GPIOA; +const GPIOB = peripherals.GPIOB; +const GPIOC = peripherals.GPIOC; +const GPIOD = peripherals.GPIOD; +const GPIOE = peripherals.GPIOE; + +const GPIO = @TypeOf(GPIOA); + +const log = std.log.scoped(.gpio); + +pub const Function = enum {}; + +pub const Mode = union(enum) { + input: InputMode, + output: OutputMode, +}; + +pub const InputMode = enum(u2) { + analog, + floating, + pull, + reserved, +}; + +pub const OutputMode = enum(u2) { + general_purpose_push_pull, + general_purpose_open_drain, + alternate_function_push_pull, + alternate_function_open_drain, +}; + +pub const Speed = enum(u2) { + reserved, + max_10MHz, + max_2MHz, + max_50MHz, +}; + +pub const IrqLevel = enum(u2) { + low, + high, + fall, + rise, +}; + +pub const IrqCallback = fn (gpio: u32, events: u32) callconv(.C) void; + +pub const Enabled = enum { + disabled, + enabled, +}; + +pub const Pull = enum { + up, + down, +}; + +// NOTE: With this current setup, every time we want to do anythting we go through a switch +// Do we want this? +pub const Pin = packed struct(u8) { + number: u4, + port: u3, + padding: u1, + + pub fn init(port: u3, number: u4) Pin { + return Pin{ + .number = number, + .port = port, + .padding = 0, + }; + } + inline fn write_pin_config(gpio: Pin, config: u32) void { + const port = gpio.get_port(); + if (gpio.number <= 7) { + const offset = @as(u5, gpio.number) << 2; + port.CTL0.raw &= ~(@as(u32, 0b1111) << offset); + port.CTL0.raw |= config << offset; + } else { + const offset = (@as(u5, gpio.number) - 8) << 2; + port.CTL1.raw &= ~(@as(u32, 0b1111) << offset); + port.CTL1.raw |= config << offset; + } + } + + fn mask(gpio: Pin) u16 { + return @as(u16, 1) << gpio.number; + } + + // NOTE: Im not sure I like this + // We could probably calculate an offset from GPIOA? + pub fn get_port(gpio: Pin) GPIO { + return switch (gpio.port) { + 0 => GPIOA, + 1 => GPIOB, + 2 => GPIOC, + 3 => GPIOD, + 4 => GPIOE, + 5 => @panic("The STM32 only has ports 0..6 (A..G)"), + 6 => @panic("The STM32 only has ports 0..6 (A..G)"), + 7 => @panic("The STM32 only has ports 0..6 (A..G)"), + }; + } + + pub inline fn set_mode(gpio: Pin, mode: Mode) void { + switch (mode) { + .input => |in| gpio.set_input_mode(in), + .output => |out| gpio.set_output_mode(out, .max_2MHz), + } + } + + pub inline fn set_input_mode(gpio: Pin, mode: InputMode) void { + const m_mode = @as(u32, @intFromEnum(mode)); + const config: u32 = m_mode << 2; + gpio.write_pin_config(config); + } + + pub inline fn set_output_mode(gpio: Pin, mode: OutputMode, speed: Speed) void { + const s_speed = @as(u32, @intFromEnum(speed)); + const m_mode = @as(u32, @intFromEnum(mode)); + const config: u32 = s_speed + (m_mode << 2); + gpio.write_pin_config(config); + } + + pub inline fn set_pull(gpio: Pin, pull: Pull) void { + var port = gpio.get_port(); + switch (pull) { + .up => port.BOP.raw = gpio.mask(), + .down => port.BC.raw = gpio.mask(), + } + } + + pub inline fn read(gpio: Pin) u1 { + const port = gpio.get_port(); + return if (port.ISTAT.raw & gpio.mask() != 0) + 1 + else + 0; + } + + pub inline fn put(gpio: Pin, value: u1) void { + var port = gpio.get_port(); + switch (value) { + 0 => port.OCTL.raw &= ~gpio.mask(), + 1 => port.OCTL.raw |= gpio.mask(), + } + } + + pub inline fn toggle(gpio: Pin) void { + var port = gpio.get_port(); + port.OCTL.raw ^= gpio.mask(); + } +}; diff --git a/bsp/gigadevice/gd32/src/hals/GD32VF103/hal.zig b/bsp/gigadevice/gd32/src/hals/GD32VF103/hal.zig new file mode 100644 index 0000000..a08ecd6 --- /dev/null +++ b/bsp/gigadevice/gd32/src/hals/GD32VF103/hal.zig @@ -0,0 +1,7 @@ +pub const pins = @import("pins.zig"); + +pub const clock_frequencies = .{ + .cpu = 8_000_000, // 8 MHz +}; + +pub fn init() void {} diff --git a/bsp/gigadevice/gd32/src/hals/GD32VF103/pins.zig b/bsp/gigadevice/gd32/src/hals/GD32VF103/pins.zig new file mode 100644 index 0000000..60adf83 --- /dev/null +++ b/bsp/gigadevice/gd32/src/hals/GD32VF103/pins.zig @@ -0,0 +1,233 @@ +const std = @import("std"); +const assert = std.debug.assert; +const comptimePrint = std.fmt.comptimePrint; +const StructField = std.builtin.Type.StructField; + +const microzig = @import("microzig"); + +const RCU = microzig.chip.peripherals.RCU; + +const gpio = @import("gpio.zig"); +// const pwm = @import("pwm.zig"); +// const adc = @import("adc.zig"); +// const resets = @import("resets.zig"); + +pub const Pin = enum { + PIN0, + PIN1, + PIN2, + PIN3, + PIN4, + PIN5, + PIN6, + PIN7, + PIN8, + PIN9, + PIN10, + PIN11, + PIN12, + PIN13, + PIN14, + PIN15, + pub const Configuration = struct { + name: ?[:0]const u8 = null, + // function: Function = .SIO, + mode: ?gpio.Mode = null, + speed: ?gpio.Speed = null, + pull: ?gpio.Pull = null, + // input/output enable + // schmitt trigger + // hysteresis + + pub fn get_mode(comptime config: Configuration) gpio.Mode { + return if (config.mode) |mode| + mode + // else if (comptime config.function.is_pwm()) + // .out + // else if (comptime config.function.is_uart_tx()) + // .out + // else if (comptime config.function.is_uart_rx()) + // .in + // else if (comptime config.function.is_adc()) + // .in + else + @panic("TODO"); + } + }; +}; + +pub fn GPIO(comptime port: u3, comptime num: u4, comptime mode: gpio.Mode) type { + return switch (mode) { + .input => struct { + const pin = gpio.Pin.init(port, num); + + pub inline fn read(self: @This()) u1 { + _ = self; + return pin.read(); + } + }, + .output => struct { + const pin = gpio.Pin.init(port, num); + + pub inline fn put(self: @This(), value: u1) void { + _ = self; + pin.put(value); + } + + pub inline fn toggle(self: @This()) void { + _ = self; + pin.toggle(); + } + }, + }; +} + +pub fn Pins(comptime config: GlobalConfiguration) type { + comptime { + var fields: []const StructField = &.{}; + for (@typeInfo(GlobalConfiguration).Struct.fields) |port_field| { + if (@field(config, port_field.name)) |port_config| { + for (@typeInfo(Port.Configuration).Struct.fields) |field| { + if (@field(port_config, field.name)) |pin_config| { + var pin_field = StructField{ + .is_comptime = false, + .default_value = null, + + // initialized below: + .name = undefined, + .type = undefined, + .alignment = undefined, + }; + + pin_field.name = pin_config.name orelse field.name; + pin_field.type = GPIO(@intFromEnum(@field(Port, port_field.name)), @intFromEnum(@field(Pin, field.name)), pin_config.mode orelse .{ .input = .{.floating} }); + pin_field.alignment = @alignOf(field.type); + + fields = fields ++ &[_]StructField{pin_field}; + } + } + } + } + + return @Type(.{ + .Struct = .{ + .layout = .auto, + .is_tuple = false, + .fields = fields, + .decls = &.{}, + }, + }); + } +} + +pub const Port = enum { + GPIOA, + GPIOB, + GPIOC, + GPIOD, + GPIOE, + pub const Configuration = struct { + PIN0: ?Pin.Configuration = null, + PIN1: ?Pin.Configuration = null, + PIN2: ?Pin.Configuration = null, + PIN3: ?Pin.Configuration = null, + PIN4: ?Pin.Configuration = null, + PIN5: ?Pin.Configuration = null, + PIN6: ?Pin.Configuration = null, + PIN7: ?Pin.Configuration = null, + PIN8: ?Pin.Configuration = null, + PIN9: ?Pin.Configuration = null, + PIN10: ?Pin.Configuration = null, + PIN11: ?Pin.Configuration = null, + PIN12: ?Pin.Configuration = null, + PIN13: ?Pin.Configuration = null, + PIN14: ?Pin.Configuration = null, + PIN15: ?Pin.Configuration = null, + + comptime { + const pin_field_count = @typeInfo(Pin).Enum.fields.len; + const config_field_count = @typeInfo(Configuration).Struct.fields.len; + if (pin_field_count != config_field_count) + @compileError(comptimePrint("{} {}", .{ pin_field_count, config_field_count })); + } + }; +}; + +pub const GlobalConfiguration = struct { + GPIOA: ?Port.Configuration = null, + GPIOB: ?Port.Configuration = null, + GPIOC: ?Port.Configuration = null, + GPIOD: ?Port.Configuration = null, + GPIOE: ?Port.Configuration = null, + + comptime { + const port_field_count = @typeInfo(Port).Enum.fields.len; + const config_field_count = @typeInfo(GlobalConfiguration).Struct.fields.len; + if (port_field_count != config_field_count) + @compileError(comptimePrint("{} {}", .{ port_field_count, config_field_count })); + } + + pub fn apply(comptime config: GlobalConfiguration) Pins(config) { + inline for (@typeInfo(GlobalConfiguration).Struct.fields) |port_field| { + if (@field(config, port_field.name)) |port_config| { + comptime var input_gpios: u16 = 0; + comptime var output_gpios: u16 = 0; + comptime { + for (@typeInfo(Port.Configuration).Struct.fields) |field| + if (@field(port_config, field.name)) |pin_config| { + const gpio_num = @intFromEnum(@field(Pin, field.name)); + + switch (pin_config.get_mode()) { + .input => input_gpios |= 1 << gpio_num, + .output => output_gpios |= 1 << gpio_num, + } + }; + } + + // TODO: ensure only one instance of an input function exists + const used_gpios = comptime input_gpios | output_gpios; + + if (used_gpios != 0) { + const offset = @intFromEnum(@field(Port, port_field.name)) + 2; + const bit = @as(u32, 1 << offset); + RCU.APB2EN.raw |= bit; + // Delay after setting + _ = RCU.APB2EN.raw & bit; + } + + inline for (@typeInfo(Port.Configuration).Struct.fields) |field| { + if (@field(port_config, field.name)) |pin_config| { + var pin = gpio.Pin.init(@intFromEnum(@field(Port, port_field.name)), @intFromEnum(@field(Pin, field.name))); + pin.set_mode(pin_config.mode.?); + } + } + + if (input_gpios != 0) { + inline for (@typeInfo(Port.Configuration).Struct.fields) |field| + if (@field(port_config, field.name)) |pin_config| { + var pin = gpio.Pin.init(@intFromEnum(@field(Port, port_field.name)), @intFromEnum(@field(Pin, field.name))); + const pull = pin_config.pull orelse continue; + if (comptime pin_config.get_mode() != .input) + @compileError("Only input pins can have pull up/down enabled"); + + pin.set_pull(pull); + }; + } + } + } + + // fields in the Pins(config) type should be zero sized, so we just + // default build them all (wasn't sure how to do that cleanly in + // `Pins()` + var ret: Pins(config) = undefined; + inline for (@typeInfo(Pins(config)).Struct.fields) |field| { + if (field.default_value) |default_value| { + @field(ret, field.name) = @as(*const field.field_type, @ptrCast(default_value)).*; + } else { + @field(ret, field.name) = .{}; + } + } + return ret; + // validate selected function + } +}; diff --git a/bsp/gigadevice/gd32/src/hals/GD32VF103/uart.zig b/bsp/gigadevice/gd32/src/hals/GD32VF103/uart.zig new file mode 100644 index 0000000..457a3f9 --- /dev/null +++ b/bsp/gigadevice/gd32/src/hals/GD32VF103/uart.zig @@ -0,0 +1,62 @@ +const micro = @import("microzig"); +const peripherals = micro.chip.peripherals; +const UART3 = peripherals.UART3; +const UART4 = peripherals.UART4; + +pub const uart = struct { + pub const DataBits = enum(u2) { + five = 0, + six = 1, + seven = 2, + eight = 3, + }; + + pub const StopBits = enum(u1) { + one = 0, + two = 1, + }; + + pub const Parity = enum(u2) { + odd = 0, + even = 1, + mark = 2, + space = 3, + }; +}; + +pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type { + if (pins.tx != null or pins.rx != null) + @compileError("TODO: custom pins are not currently supported"); + + return struct { + const UARTn = switch (index) { + 0 => UART3, + 1 => UART4, + else => @compileError("GD32VF103 has 2 UARTs available."), + }; + const Self = @This(); + + pub fn init(config: micro.uart.Config) !Self { + _ = config; + return Self{}; + } + + pub fn can_write(self: Self) bool { + _ = self; + return false; + } + pub fn tx(self: Self, ch: u8) void { + _ = ch; + while (!self.can_write()) {} // Wait for Previous transmission + } + + pub fn can_read(self: Self) bool { + _ = self; + return false; + } + pub fn rx(self: Self) u8 { + while (!self.can_read()) {} // Wait till the data is received + return 1; // Read received data + } + }; +} diff --git a/examples/gigadevice/gd32/build.zig b/examples/gigadevice/gd32/build.zig index fdecf33..a4c9963 100644 --- a/examples/gigadevice/gd32/build.zig +++ b/examples/gigadevice/gd32/build.zig @@ -6,6 +6,7 @@ const available_examples = [_]Example{ .{ .target = gd32.chips.gd32vf103xb, .name = "gd32vf103xb", .file = "src/empty.zig" }, .{ .target = gd32.chips.gd32vf103x8, .name = "gd32vf103x8", .file = "src/empty.zig" }, .{ .target = gd32.boards.sipeed.longan_nano, .name = "sipeed-longan_nano", .file = "src/empty.zig" }, + .{ .target = gd32.boards.sipeed.longan_nano, .name = "sipeed-longan_nano_blinky", .file = "src/blinky.zig" }, }; pub fn build(b: *std.Build) void { @@ -32,7 +33,7 @@ pub fn build(b: *std.Build) void { microzig.install_firmware(b, firmware, .{}); // For debugging, we also always install the firmware as an ELF file - microzig.install_firmware(b, firmware, .{ .format = .elf }); + microzig.install_firmware(b, firmware, .{ .format = .bin }); } } diff --git a/examples/gigadevice/gd32/src/blinky.zig b/examples/gigadevice/gd32/src/blinky.zig new file mode 100644 index 0000000..88bd5cd --- /dev/null +++ b/examples/gigadevice/gd32/src/blinky.zig @@ -0,0 +1,30 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const gd32 = microzig.hal; + +const pin_config = gd32.pins.GlobalConfiguration{ + .GPIOC = .{ + .PIN13 = .{ .name = "led", .mode = .{ .output = .general_purpose_push_pull } }, + }, + .GPIOA = .{ + .PIN1 = .{ .name = "green", .mode = .{ .output = .general_purpose_push_pull } }, + .PIN2 = .{ .name = "blue", .mode = .{ .output = .general_purpose_push_pull } }, + }, +}; + +pub fn main() !void { + const pins = pin_config.apply(); + pins.green.put(0); + pins.blue.put(1); + + while (true) { + var i: u32 = 0; + while (i < 800_000) { + asm volatile ("nop"); + i += 1; + } + pins.led.toggle(); + pins.green.toggle(); + pins.blue.toggle(); + } +}