From c106223970b934aefb5de74fad2e0981374c8149 Mon Sep 17 00:00:00 2001 From: RecursiveError Date: Mon, 12 Jan 2026 20:50:52 -0300 Subject: [PATCH 1/4] - start basic h7 support - add GPIO N-K in common/gpio_v2 - move put,toggle and read func to from common/pins_v2 common/gpio_v2 --- examples/stmicro/stm32/build.zig | 2 + examples/stmicro/stm32/src/stm32h723/rcc.zig | 70 + port/stmicro/stm32/build.zig.zon | 4 +- port/stmicro/stm32/src/Chips.zig | 16 + port/stmicro/stm32/src/generate.zig | 11 +- port/stmicro/stm32/src/hals/STM32H723.zig | 16 + port/stmicro/stm32/src/hals/STM32H723/rcc.zig | 1224 +++++++++++++++++ .../stmicro/stm32/src/hals/common/gpio_v2.zig | 36 +- .../stmicro/stm32/src/hals/common/pins_v2.zig | 36 +- port/stmicro/stm32/src/hals/common/util.zig | 4 +- 10 files changed, 1390 insertions(+), 29 deletions(-) create mode 100644 examples/stmicro/stm32/src/stm32h723/rcc.zig create mode 100644 port/stmicro/stm32/src/hals/STM32H723.zig create mode 100644 port/stmicro/stm32/src/hals/STM32H723/rcc.zig diff --git a/examples/stmicro/stm32/build.zig b/examples/stmicro/stm32/build.zig index 8a63f0788..b0193cdd9 100644 --- a/examples/stmicro/stm32/build.zig +++ b/examples/stmicro/stm32/build.zig @@ -46,6 +46,8 @@ pub fn build(b: *std.Build) void { .{ .target = stm32.chips.STM32F103CB, .name = "STM32F1xx_timer_capture", .file = "src/stm32f1xx/timer_capture.zig" }, .{ .target = stm32.chips.STM32F103CB, .name = "STM32F1xx_rtc", .file = "src/stm32f1xx/rtc.zig" }, .{ .target = stm32.chips.STM32F103CB, .name = "STM32F1xx_EXTI", .file = "src/stm32f1xx/EXTI.zig" }, + + .{ .target = stm32.chips.STM32H723VG, .name = "STM32H723_rcc", .file = "src/stm32h723/rcc.zig" }, }; for (available_examples) |example| { diff --git a/examples/stmicro/stm32/src/stm32h723/rcc.zig b/examples/stmicro/stm32/src/stm32h723/rcc.zig new file mode 100644 index 000000000..0dc4133c2 --- /dev/null +++ b/examples/stmicro/stm32/src/stm32h723/rcc.zig @@ -0,0 +1,70 @@ +// This is an example RCC configuration for the STM32H723 +// Configurations and procedures may vary for other targets + +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const pins = hal.pins; +const rcc = hal.rcc; +const PWR = microzig.chip.peripherals.PWR; + +//set Master Clock Output(MCO) pins +const global_pins = pins.GlobalConfiguration{ + .GPIOA = .{ + .PIN8 = .{ + .mode = .{ + .alternate_function = .{ + .afr = .AF0, + }, + }, + .name = "MCO1", + }, + }, + .GPIOC = .{ + .PIN9 = .{ + .mode = .{ + .alternate_function = .{ .afr = .AF0 }, + }, + .name = "MCO2", + }, + }, +}; + +pub fn main() !void { + + // set inital power state + PWR.CR3.modify(.{ + .LDOEN = 1, + .SDEN = 1, + }); + + //configure cpu clock to 550 Mhz + _ = try rcc.apply(.{ + .HSE_VALUE = 25_000_000, + .PLLSource = .RCC_PLLSOURCE_HSE, + .DIVM1 = 5, + .DIVN1 = 110, + .DIVP1 = 1, + .DIVQ1 = 10, + .SYSCLKSource = .RCC_SYSCLKSOURCE_PLLCLK, + .RCC_MCO1Source = .RCC_MCO1SOURCE_PLL1QCLK, + .RCC_MCO2Source = .RCC_MCO2SOURCE_LSICLK, + .RCC_MCODiv2 = .RCC_MCODIV_10, + .RCC_MCODiv1 = .RCC_MCODIV_10, + .D2PPRE2 = .RCC_APB2_DIV2, + .D2PPRE1 = .RCC_APB1_DIV2, + .D3PPRE = .RCC_APB4_DIV2, + .D1PPRE = .RCC_APB3_DIV2, + .HPRE = .RCC_HCLK_DIV2, + .flags = .{ + .MCO1Config = true, + .MCO2Config = true, + .HSEOscillator = true, + }, + }); + + //apply pins config + _ = global_pins.apply(); + + while (true) {} +} diff --git a/port/stmicro/stm32/build.zig.zon b/port/stmicro/stm32/build.zig.zon index 1c07ecd74..59095365f 100644 --- a/port/stmicro/stm32/build.zig.zon +++ b/port/stmicro/stm32/build.zig.zon @@ -10,8 +10,8 @@ .hash = "N-V-__8AAFi8WBlOh-NikHFVBjzQE0F1KixgKjVWYnlijPNm", }, .ClockHelper = .{ - .url = "git+https://github.com/ZigEmbeddedGroup/ClockHelper#7fd073b1be9544941c15f9a63032ed06149ddb70", - .hash = "ClockHelper-2.0.0-RcMaOSniGQHXH_qeoZbQDG64XThqpXTVPMfJ6P7LHpYY", + .url = "/home/guilherme/\xc3\x81rea de trabalho/mz-clockhelper/ClockHelper/", + .hash = "ClockHelper-2.0.0-RcMaOUMGIwEUEHQJ0C2Q-KbWdKXN2GqzQcs9MpVSTgm3", }, }, .paths = .{ diff --git a/port/stmicro/stm32/src/Chips.zig b/port/stmicro/stm32/src/Chips.zig index f776db273..c6c283f4d 100644 --- a/port/stmicro/stm32/src/Chips.zig +++ b/port/stmicro/stm32/src/Chips.zig @@ -21522,6 +21522,10 @@ pub fn init(dep: *std.Build.Dependency, hal_imports: []std.Build.Module.Import) .{ .name = "RAM_D3", .tag = .ram, .offset = 0x38000000, .length = 0x4000, .access = .rwx }, }, }, + .hal = .{ + .root_source_file = b.path("src/hals/STM32H723.zig"), + .imports = hal_imports, + }, }; ret.STM32H723VG = b.allocator.create(microzig.Target) catch @panic("out of memory"); @@ -21549,6 +21553,10 @@ pub fn init(dep: *std.Build.Dependency, hal_imports: []std.Build.Module.Import) .{ .name = "RAM_D3", .tag = .ram, .offset = 0x38000000, .length = 0x4000, .access = .rwx }, }, }, + .hal = .{ + .root_source_file = b.path("src/hals/STM32H723.zig"), + .imports = hal_imports, + }, }; ret.STM32H723ZE = b.allocator.create(microzig.Target) catch @panic("out of memory"); @@ -21576,6 +21584,10 @@ pub fn init(dep: *std.Build.Dependency, hal_imports: []std.Build.Module.Import) .{ .name = "RAM_D3", .tag = .ram, .offset = 0x38000000, .length = 0x4000, .access = .rwx }, }, }, + .hal = .{ + .root_source_file = b.path("src/hals/STM32H723.zig"), + .imports = hal_imports, + }, }; ret.STM32H723ZG = b.allocator.create(microzig.Target) catch @panic("out of memory"); @@ -21603,6 +21615,10 @@ pub fn init(dep: *std.Build.Dependency, hal_imports: []std.Build.Module.Import) .{ .name = "RAM_D3", .tag = .ram, .offset = 0x38000000, .length = 0x4000, .access = .rwx }, }, }, + .hal = .{ + .root_source_file = b.path("src/hals/STM32H723.zig"), + .imports = hal_imports, + }, }; ret.STM32H725AE = b.allocator.create(microzig.Target) catch @panic("out of memory"); diff --git a/port/stmicro/stm32/src/generate.zig b/port/stmicro/stm32/src/generate.zig index a34b79f00..84b023e74 100644 --- a/port/stmicro/stm32/src/generate.zig +++ b/port/stmicro/stm32/src/generate.zig @@ -278,8 +278,7 @@ fn generate_chips_file( \\ }, \\ ); - } - if (std.mem.startsWith(u8, chip_file.name, "STM32L47")) { + } else if (std.mem.startsWith(u8, chip_file.name, "STM32L47")) { try writer.writeAll( \\ .hal = .{ \\ .root_source_file = b.path("src/hals/STM32L47X.zig"), @@ -287,6 +286,14 @@ fn generate_chips_file( \\ }, \\ ); + } else if (std.mem.startsWith(u8, chip_file.name, "STM32H723")) { + try writer.writeAll( + \\ .hal = .{ + \\ .root_source_file = b.path("src/hals/STM32H723.zig"), + \\ .imports = hal_imports, + \\ }, + \\ + ); } try writer.writeAll( diff --git a/port/stmicro/stm32/src/hals/STM32H723.zig b/port/stmicro/stm32/src/hals/STM32H723.zig new file mode 100644 index 000000000..f86d61a20 --- /dev/null +++ b/port/stmicro/stm32/src/hals/STM32H723.zig @@ -0,0 +1,16 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +pub const systick_timer = @import("./common/systick_timer.zig"); +pub const systick = @import("./common/systick.zig"); +pub const gpio = @import("./common/gpio_v2.zig"); +pub const pins = @import("./common/pins_v2.zig"); +pub const rcc = @import("./STM32H723/rcc.zig"); + +pub fn get_sys_clk() u32 { + return @intFromFloat(rcc.current_clocks.SysCLKOutput); +} + +pub fn get_systick_clk() u32 { + return @intFromFloat(rcc.current_clocks.CortexSysOutput); +} diff --git a/port/stmicro/stm32/src/hals/STM32H723/rcc.zig b/port/stmicro/stm32/src/hals/STM32H723/rcc.zig new file mode 100644 index 000000000..214790b3c --- /dev/null +++ b/port/stmicro/stm32/src/hals/STM32H723/rcc.zig @@ -0,0 +1,1224 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const enums = @import("../common/enums.zig"); +const util = @import("../common/util.zig"); + +//Instances +const Peripherals = enums.Peripherals; +const FLASH = microzig.chip.peripherals.FLASH; +const RCC = microzig.chip.peripherals.RCC; +const PWR = microzig.chip.peripherals.PWR; + +//RCC types +const VOS = microzig.chip.types.peripherals.pwr_h7rm0468.VOS; +const HSIDIV = microzig.chip.types.peripherals.rcc_h7.HSIDIV; +const SW = microzig.chip.types.peripherals.rcc_h7.SW; +const HPRE = microzig.chip.types.peripherals.rcc_h7.HPRE; +const PPRE = microzig.chip.types.peripherals.rcc_h7.PPRE; +const PLLM = microzig.chip.types.peripherals.rcc_h7.PLLM; +const PLLN = microzig.chip.types.peripherals.rcc_h7.PLLN; +const PLLDIV = microzig.chip.types.peripherals.rcc_h7.PLLDIV; +const PLLRGE = microzig.chip.types.peripherals.rcc_h7.PLLRGE; +const PLLVCOSEL = microzig.chip.types.peripherals.rcc_h7.PLLVCOSEL; +const PLLSRC = microzig.chip.types.peripherals.rcc_h7.PLLSRC; +const LSEDRV = microzig.chip.types.peripherals.rcc_h7.LSEDRV; +const MCOPRE = microzig.chip.types.peripherals.rcc_h7.MCOPRE; +const MCO1SEL = microzig.chip.types.peripherals.rcc_h7.MCO1SEL; +const MCO2SEL = microzig.chip.types.peripherals.rcc_h7.MCO2SEL; +const TIMPRE = microzig.chip.types.peripherals.rcc_h7.TIMPRE; +const RTCSEL = microzig.chip.types.peripherals.rcc_h7.RTCSEL; +const PERSEL = microzig.chip.types.peripherals.rcc_h7.PERSEL; +const SDMMCSEL = microzig.chip.types.peripherals.rcc_h7.SDMMCSEL; +const FMCSEL = microzig.chip.types.peripherals.rcc_h7.FMCSEL; +const SAISEL = microzig.chip.types.peripherals.rcc_h7.SAISEL; +const SAIASEL = microzig.chip.types.peripherals.rcc_h7.SAIASEL; +const SPI6SEL = microzig.chip.types.peripherals.rcc_h7.SPI6SEL; +const ADCSEL = microzig.chip.types.peripherals.rcc_h7.ADCSEL; +const LPTIMSEL = microzig.chip.types.peripherals.rcc_h7.LPTIM1SEL; +const LPTIM2SEL = microzig.chip.types.peripherals.rcc_h7.LPTIM2SEL; +const LPUARTSEL = microzig.chip.types.peripherals.rcc_h7.LPUARTSEL; +const I2C4SEL = microzig.chip.types.peripherals.rcc_h7.I2C4SEL; +const CECSEL = microzig.chip.types.peripherals.rcc_h7.CECSEL; +const USBSEL = microzig.chip.types.peripherals.rcc_h7.USBSEL; +const I2C1235SEL = microzig.chip.types.peripherals.rcc_h7.I2C1235SEL; +const RNGSEL = microzig.chip.types.peripherals.rcc_h7.RNGSEL; +const USART16910SEL = microzig.chip.types.peripherals.rcc_h7.USART16910SEL; +const USART234578SEL = microzig.chip.types.peripherals.rcc_h7.USART234578SEL; +const SWPMMISEL = microzig.chip.types.peripherals.rcc_h7.SWPMISEL; +const FDCANSEL = microzig.chip.types.peripherals.rcc_h7.FDCANSEL; +const DFSDMSEL = microzig.chip.types.peripherals.rcc_h7.DFSDMSEL; +const SPDIFRXSEL = microzig.chip.types.peripherals.rcc_h7.SPDIFRXSEL; +const SPI45SEL = microzig.chip.types.peripherals.rcc_h7.SPI45SEL; + +//clocktree type shortcuts +const ClockTree = @field(@import("ClockTree"), microzig.config.chip_name); +const Config = ClockTree.Config; +const Clock_Output = ClockTree.Clock_Output; +const Config_Output = ClockTree.Config_Output; + +var current_clk: Clock_Output = blk: { + const ret = ClockTree.get_clocks(.{}) catch unreachable; + break :blk ret.clock; +}; + +/// Configuration for a PLL instance +/// optional DIVP, DIVQ, DIVR can be null if not used. +/// (this means that the respective output is going to be disabled!) +pub const PLL_Config = struct { + DIVM: PLLM, + DIVN: PLLN, + VCI_range: PLLRGE, + VCO_selection: PLLVCOSEL, + DIVP: ?PLLDIV, + DIVQ: ?PLLDIV, + DIVR: ?PLLDIV, + fractional: ?u13, +}; + +pub const MCO_Config = struct { + mco1_src: MCO1SEL, + mco2_src: MCO2SEL, + + mco1_pre: MCOPRE, + mco2_pre: MCOPRE, +}; + +pub const Sysclk_Config = struct { + upscale: bool, + falsh_latency: u3, + flash_signal_delay: u2, + power_scale: VOS, + clk_src: SW, +}; + +pub const D1_Kernel_Config = struct { + peri_src: PERSEL, + sdmcc_src: SDMMCSEL, + octospi_src: FMCSEL, + fmc_src: FMCSEL, +}; + +pub const D2_Kernel_Config = struct { + //CCIP1R + spwmi_src: SWPMMISEL, + dfsdm_src: DFSDMSEL, + fdcan_src: FDCANSEL, + spdifrx_src: SPDIFRXSEL, + spi45_src: SPI45SEL, + spi123_src: SAISEL, + sai23_src: SAISEL, + sai1_src: SAISEL, + //CCIP2R + lptim1_src: LPTIMSEL, + usart234578_src: USART234578SEL, + usart16910_src: USART16910SEL, + rng_src: RNGSEL, + i2c1235_src: I2C1235SEL, + usb_src: USBSEL, + cec_src: CECSEL, +}; + +pub const D3_Kernel_Config = struct { + lpuart1: LPUARTSEL, + i2c4_src: I2C4SEL, + lptim2_src: LPTIM2SEL, + lptim345_src: LPTIM2SEL, + adc_src: ADCSEL, + sai4a_src: SAIASEL, + sai4b_src: SAIASEL, + spi6_src: SPI6SEL, +}; +/// configure clocks +/// NOTE: this function expects the current clock to be in a valid state and free of glitches in the flash and PWR domains +/// it is important that any external change (whether manual or caused by hardware events) to the clock tree be restored before calling this function +pub fn apply(comptime config: Config) !Clock_Output { + const clk = comptime ClockTree.get_clocks(config) catch unreachable; + current_clk = clk.clock; + + //verify if we need to upscale or downscale + const HSI_CLK = if (clk.clock.HSIDiv == 0) 64_000_000 else clk.clock.HSIDiv; + const secure_sys_upscale: bool = current_clk.CpuClockOutput >= HSI_CLK; + const target_sys_upscale: bool = clk.clock.CpuClockOutput >= HSI_CLK; + + const hsi_div: HSIDIV = if (clk.config.HSIDiv) |d| @as(HSIDIV, @enumFromInt(@as(u2, @intFromEnum(d)))) else .Div1; + const trim = if (clk.config.HSICalibrationValue) |v| @as(u7, @intCast(@as(u32, @intFromFloat(v)))) else null; + secure_enable(secure_sys_upscale, hsi_div, trim); //force sysclk into secure state + + try apply_internal(clk.config, target_sys_upscale); + return clk.clock; +} + +/// forces sysclk into a secure state, by using HSI at 64/hsi_div MHz as the temporary sysclk source. +/// upscale: true if the HSI frequency is grater than the current sysclk frequency +fn secure_enable(upscale: bool, hsi_div: HSIDIV, trim: ?u7) void { + enable_hsi(null, null); + + set_sysclk(.{ + .upscale = upscale, + .falsh_latency = 0b111, + .flash_signal_delay = 0b11, + .power_scale = .Scale3, + .clk_src = .HSI, + }); + + //now that sysclock is HSI, we can safely set the dividers + const pll_states = RCC.CR.read(); + //disable all PLLs to safely change HSI dividers + RCC.CR.modify(.{ + .@"PLLON[0]" = 0, + .@"PLLON[1]" = 0, + .@"PLLON[2]" = 0, + }); + + while (RCC.CR.read().@"PLLRDY[0]" != 0) { + asm volatile ("" ::: .{ .memory = true }); + } + + //configure and enable HSI with desired trim and division + enable_hsi(trim, hsi_div); + + //set PLL states back to previous values + //we dont need to wait for PLLs to lock here, as we are not using them yet + RCC.CR.modify(.{ + .@"PLLON[0]" = pll_states.@"PLLON[0]", + .@"PLLON[1]" = pll_states.@"PLLON[1]", + .@"PLLON[2]" = pll_states.@"PLLON[2]", + }); + + //set D1 domain prescalers to no division + set_D1_prescaler(.Div1, .Div1, .Div1); +} + +fn apply_internal(comptime config: Config_Output, upscale: bool) !void { + const actual_state = RCC.CR.read(); + + const d1_core_pre: HPRE = blk: { + if (config.D1CPRE) |p| { + break :blk switch (p) { + .RCC_SYSCLK_DIV1 => HPRE.Div1, + .RCC_SYSCLK_DIV2 => HPRE.Div2, + .RCC_SYSCLK_DIV4 => HPRE.Div4, + .RCC_SYSCLK_DIV8 => HPRE.Div8, + .RCC_SYSCLK_DIV16 => HPRE.Div16, + .RCC_SYSCLK_DIV64 => HPRE.Div64, + .RCC_SYSCLK_DIV128 => HPRE.Div128, + .RCC_SYSCLK_DIV256 => HPRE.Div256, + .RCC_SYSCLK_DIV512 => HPRE.Div512, + }; + } + break :blk HPRE.Div1; //default reset + }; + + const d1_ahb_pre: HPRE = blk: { + if (config.HPRE) |p| { + break :blk switch (p) { + .RCC_HCLK_DIV1 => HPRE.Div1, + .RCC_HCLK_DIV2 => HPRE.Div2, + .RCC_HCLK_DIV4 => HPRE.Div4, + .RCC_HCLK_DIV8 => HPRE.Div8, + .RCC_HCLK_DIV16 => HPRE.Div16, + .RCC_HCLK_DIV64 => HPRE.Div64, + .RCC_HCLK_DIV128 => HPRE.Div128, + .RCC_HCLK_DIV256 => HPRE.Div256, + .RCC_HCLK_DIV512 => HPRE.Div512, + }; + } + break :blk HPRE.Div1; //default reset + + }; + + const d1_apb_pre: PPRE = blk: { + if (config.D1PPRE) |p| { + break :blk switch (p) { + .RCC_APB3_DIV1 => PPRE.Div1, + .RCC_APB3_DIV2 => PPRE.Div2, + .RCC_APB3_DIV4 => PPRE.Div4, + .RCC_APB3_DIV8 => PPRE.Div8, + .RCC_APB3_DIV16 => PPRE.Div16, + }; + } + break :blk PPRE.Div1; //default reset + + }; + + const d2_apb1: PPRE = blk: { + if (config.D2PPRE1) |p| { + break :blk switch (p) { + .RCC_APB1_DIV1 => PPRE.Div1, + .RCC_APB1_DIV2 => PPRE.Div2, + .RCC_APB1_DIV4 => PPRE.Div4, + .RCC_APB1_DIV8 => PPRE.Div8, + .RCC_APB1_DIV16 => PPRE.Div16, + }; + } + break :blk PPRE.Div1; //default reset + + }; + + const d2_apb2: PPRE = blk: { + if (config.D2PPRE2) |p| { + break :blk switch (p) { + .RCC_APB2_DIV1 => PPRE.Div1, + .RCC_APB2_DIV2 => PPRE.Div2, + .RCC_APB2_DIV4 => PPRE.Div4, + .RCC_APB2_DIV8 => PPRE.Div8, + .RCC_APB2_DIV16 => PPRE.Div16, + }; + } + break :blk PPRE.Div1; //default reset + + }; + + const d3_apb1: PPRE = blk: { + if (config.D3PPRE) |p| { + break :blk switch (p) { + .RCC_APB4_DIV1 => PPRE.Div1, + .RCC_APB4_DIV2 => PPRE.Div2, + .RCC_APB4_DIV4 => PPRE.Div4, + .RCC_APB4_DIV8 => PPRE.Div8, + .RCC_APB4_DIV16 => PPRE.Div16, + }; + } + break :blk PPRE.Div1; //default reset + + }; + + const tim_pre: TIMPRE = if (config.RCC_TIM_PRescaler_Selection.? == .RCC_TIMPRES_ACTIVATED) TIMPRE.DefaultX4 else TIMPRE.DefaultX2; + + //sys main + const vos: VOS = switch (config.PWR_Regulator_Voltage_Scale.?) { + .PWR_REGULATOR_VOLTAGE_SCALE3 => VOS.Scale3, + .PWR_REGULATOR_VOLTAGE_SCALE2 => VOS.Scale2, + .PWR_REGULATOR_VOLTAGE_SCALE1 => VOS.Scale1, + .PWR_REGULATOR_VOLTAGE_SCALE0 => VOS.Scale0, + }; + // NOTE: the system does not validate correct WRHIGHFREQ + flash latency configurations, + // so CubeMX always sets them to the closest recommended values. + // Manual changes can be made without affecting the clock tree + const flantency: u2 = @intFromEnum(config.FLatency.?); // the same value is valid for WRHIGHFREQ and flash latency + + const sysclk: SW = @enumFromInt(@as(u3, @intFromEnum(config.SYSCLKSource.?))); + + //D1 kernel clocks + const fmc_src: FMCSEL = @enumFromInt(@as(u2, @intFromEnum(config.FMCCLockSelection.?))); + const ospi_src: FMCSEL = if (config.flags.OCTOSPIEnable) @enumFromInt(@as(u2, @intFromEnum(config.QSPICLockSelection.?))) else FMCSEL.HCLK3; + const mmc_src: SDMMCSEL = @enumFromInt(@as(u1, @intFromEnum(config.SDMMC1CLockSelection.?))); + const presel_src: PERSEL = @enumFromInt(@as(u1, @intFromEnum(config.CKPERSourceSelection.?))); + + //D2 kernel clocks + const sai1_src: SAISEL = @enumFromInt(@as(u3, @intFromEnum(config.SAI1CLockSelection.?))); + const spi123_src: SAISEL = @enumFromInt(@as(u3, @intFromEnum(config.SPI123CLockSelection.?))); + const spi45_src: SPI45SEL = @enumFromInt(@as(u3, @intFromEnum(config.Spi45ClockSelection.?))); + const spdifrx_src: SPDIFRXSEL = @enumFromInt(@as(u3, @intFromEnum(config.SPDIFCLockSelection.?))); + const dfsdm_src: DFSDMSEL = @enumFromInt(@as(u1, @intFromEnum(config.DFSDMCLockSelection.?))); + const fdcan_src: FDCANSEL = @enumFromInt(@as(u2, @intFromEnum(config.FDCANCLockSelection.?))); + const swpmi: SWPMMISEL = @enumFromInt(@as(u1, @intFromEnum(config.SWPCLockSelection.?))); + const usart234578_src: USART234578SEL = @enumFromInt(@as(u3, @intFromEnum(config.USART234578CLockSelection.?))); + const usart16910_src: USART16910SEL = @enumFromInt(@as(u3, @intFromEnum(config.USART16CLockSelection.?))); + const rng_src: RNGSEL = @enumFromInt(@as(u2, @intFromEnum(config.RNGCLockSelection.?))); + const i2c1235_src: I2C1235SEL = @enumFromInt(@as(u2, @intFromEnum(config.I2C123CLockSelection.?))); + const usb_src: USBSEL = if (config.flags.USBEnable) + @enumFromInt(@as(u3, @intFromEnum(config.USBCLockSelection.?)) + 1) + else + USBSEL.DISABLE; + + const cec_src: CECSEL = @enumFromInt(@as(u2, @intFromEnum(config.CECCLockSelection.?))); + const tim1_src: LPTIMSEL = @enumFromInt(@as(u3, @intFromEnum(config.LPTIM1CLockSelection.?))); + + //D3 kernel clocks + const lpuart1_src: LPUARTSEL = @enumFromInt(@as(u3, @intFromEnum(config.LPUART1CLockSelection.?))); + const i2c4_src: I2C4SEL = @enumFromInt(@as(u2, @intFromEnum(config.I2C4CLockSelection.?))); + const lptim2_src: LPTIM2SEL = @enumFromInt(@as(u3, @intFromEnum(config.LPTIM2CLockSelection.?))); + const lptim34_src: LPTIM2SEL = @enumFromInt(@as(u3, @intFromEnum(config.LPTIM345CLockSelection.?))); + const adc_src: ADCSEL = @enumFromInt(@as(u2, @intFromEnum(config.ADCCLockSelection.?))); + const sai4a_src: SAIASEL = @enumFromInt(@as(u3, @intFromEnum(config.SAI4ACLockSelection.?))); + const sai4b_src: SAIASEL = @enumFromInt(@as(u3, @intFromEnum(config.SAI4BCLockSelection.?))); + const spi6_src: SPI6SEL = @enumFromInt(@as(u3, @intFromEnum(config.SPI6CLockSelection.?))); + + PWR.CR1.modify_one("DBP", 1); //enable access to backup domain + defer PWR.CR1.modify_one("DBP", 0); + + //first, enable the required oscillators + // we cannot disable unused oscillators in this stage, because switching clock requires both new and old clock to be enabled + //HSI is always enabled from secure_enable + + if (!config.flags.HSIUsed) { + defer disable_hsi(); + } + + if (config.flags.HSI48Used) { + enable_hsi48(); + } else { + defer disable_hsi48(); + } + + if (config.flags.CSIUsed) { + const trim = if (config.CSICalibrationValue) |v| @as(u6, @intCast(@as(u32, @intFromFloat(v)))) else null; + enable_csi(trim); + } else { + defer disable_csi(); + } + + if (config.flags.LSIEnable) { + enable_lsi(); + } else { + defer disable_lsi(); + } + + if (config.flags.EnableHSE) { + const css = config.flags.EnbaleCSS; + const bypass = config.flags.HSEByPass; + const timeout = if (config.HSE_Timout) |t| @as(usize, @intFromFloat(t)) else null; + try enable_hse(css, bypass, timeout); + } else { + defer disable_hse(); + } + + if (config.flags.LSEUsed) { + //const css = config. + const bypass = config.flags.LSEByPass; + const timeout = if (config.LSE_Timout) |t| @as(usize, @intFromFloat(t)) else null; + const css = config.flags.EnableCSSLSE; + const driver: LSEDRV = blk: { + if (config.LSE_Drive_Capability) |drv| { + break :blk @as(LSEDRV, @enumFromInt(@as(u2, @intFromEnum(drv)))); + } else break :blk LSEDRV.MediumLow; + }; + + try enable_lse(css, bypass, driver, timeout); + } else { + defer disable_lse(); + } + // Second: configure the PLLs + // even if a PLL is not used in this configuration, its state must be restored after this stage + // and it can only be actually disabled after finishing this configuration to avoid putting the clock into an invalid state. + + //force disable all PLL before config + + disable_PLL1(); + disable_PLL2(); + disable_PLL3(); + + if (config.PLLSource) |s| { + const src: PLLSRC = @enumFromInt(@intFromEnum(s)); + set_plls_source(src); + } + + if (config.flags.PLLUsed) { + // if a PLL is active and valid, these values will never be null + const divm: PLLM = @enumFromInt(@as(u6, @intFromFloat(config.DIVM1.?))); + const divn: PLLN = @enumFromInt(@as(u9, @intFromFloat(config.DIVN1.?)) - 1); + const vci: PLLRGE = @enumFromInt(@intFromEnum(config.PLL1_VCI_Range.?)); + const vco: PLLVCOSEL = switch (config.PLL1_VCO_SEL.?) { + .RCC_PLL1VCOMEDIUM => PLLVCOSEL.MediumVCO, + .RCC_PLL1VCOWIDE => PLLVCOSEL.WideVCO, + }; + + // these values are optional + const frac = if (config.PLLFRACN) |f| @as(u13, @intCast(@as(u32, @intFromFloat(f)))) else null; + const divq: ?PLLDIV = blk: { + if (config.flags.DIVQ1Enable) { + if (config.DIVQ1) |d| { + const v: u7 = @intCast(@as(u32, @intFromFloat(d)) - 1); + break :blk @enumFromInt(v); + } + } + break :blk null; + }; + + // DIVP1 does not have an enable flag in CubeMX, therefore there is no flag to check here + const divp: ?PLLDIV = blk: { + if (config.DIVP1) |d| { + const v: u7 = @intCast(@as(u32, @intFromFloat(d)) - 1); + break :blk @enumFromInt(v); + } + break :blk null; + }; + const divr: ?PLLDIV = blk: { + if (config.flags.DIVR1Enable) { + if (config.DIVR1) |d| { + const v: u7 = @intCast(@as(u32, @intFromFloat(d)) - 1); + break :blk @enumFromInt(v); + } + } + break :blk null; + }; + + enable_PLL1(PLL_Config{ + .DIVM = divm, + .DIVN = divn, + .VCI_range = vci, + .VCO_selection = vco, + .DIVP = divp, + .DIVQ = divq, + .DIVR = divr, + .fractional = frac, + }); + } else { + //Restore PLL state and set to disable after clock config + RCC.CR.modify_one("PLLON[0]", actual_state.@"PLLON[0]"); + disable_PLL1(); + } + + if (config.flags.PLL2Used) { + // if a PLL is active and valid, these values will never be null + const divm: PLLM = @enumFromInt(@as(u6, @intFromFloat(config.DIVM2.?))); + const divn: PLLN = @enumFromInt(@as(u9, @intFromFloat(config.DIVN2.?)) - 1); + const vci: PLLRGE = @enumFromInt(@intFromEnum(config.PLL2_VCI_Range.?)); + const vco: PLLVCOSEL = switch (config.PLL2_VCO_SEL.?) { + .RCC_PLL2VCOMEDIUM => PLLVCOSEL.MediumVCO, + .RCC_PLL2VCOWIDE => PLLVCOSEL.WideVCO, + }; + + // these values are optional + const frac = if (config.PLL2FRACN) |f| @as(u13, @intCast(@as(u32, @intFromFloat(f)))) else null; + const divq: ?PLLDIV = blk: { + if (config.flags.DIVQ2Enable) { + if (config.DIVQ2) |d| { + const v: u7 = @intCast(@as(u32, @intFromFloat(d)) - 1); + break :blk @enumFromInt(v); + } + } + break :blk null; + }; + + const divp: ?PLLDIV = blk: { + if (config.flags.DIVP2Enable) { + if (config.DIVP2) |d| { + const v: u7 = @intCast(@as(u32, @intFromFloat(d)) - 1); + break :blk @enumFromInt(v); + } + } + break :blk null; + }; + + const divr: ?PLLDIV = blk: { + if (config.flags.DIVR2Enable) { + if (config.DIVR2) |d| { + const v: u7 = @intCast(@as(u32, @intFromFloat(d)) - 1); + break :blk @enumFromInt(v); + } + } + break :blk null; + }; + + enable_PLL2(PLL_Config{ + .DIVM = divm, + .DIVN = divn, + .VCI_range = vci, + .VCO_selection = vco, + .DIVP = divp, + .DIVQ = divq, + .DIVR = divr, + .fractional = frac, + }); + } else { + //Restore PLL state and set to disable after clock config + RCC.CR.modify_one("PLLON[1]", actual_state.@"PLLON[1]"); + disable_PLL2(); + } + + if (config.flags.PLL3Used) { + // if a PLL is active and valid, these values will never be null + const divm: PLLM = @enumFromInt(@as(u6, @intFromFloat(config.DIVM3.?))); + const divn: PLLN = @enumFromInt(@as(u9, @intFromFloat(config.DIVN3.?)) - 1); + const vci: PLLRGE = @enumFromInt(@intFromEnum(config.PLL3_VCI_Range.?)); + const vco: PLLVCOSEL = switch (config.PLL3_VCO_SEL.?) { + .RCC_PLL3VCOMEDIUM => PLLVCOSEL.MediumVCO, + .RCC_PLL3VCOWIDE => PLLVCOSEL.WideVCO, + }; + + // these values are optional + const frac = if (config.PLL3FRACN) |f| @as(u13, @intCast(@as(u32, @intFromFloat(f)))) else null; + const divq: ?PLLDIV = blk: { + if (config.flags.DIVQ3Enable) { + if (config.DIVQ3) |d| { + const v: u7 = @intCast(@as(u32, @intFromFloat(d)) - 1); + break :blk @enumFromInt(v); + } + } + break :blk null; + }; + + const divp: ?PLLDIV = blk: { + if (config.flags.DIVP3Enable) { + if (config.DIVP3) |d| { + const v: u7 = @intCast(@as(u32, @intFromFloat(d)) - 1); + break :blk @enumFromInt(v); + } + } + break :blk null; + }; + + const divr: ?PLLDIV = blk: { + if (config.flags.DIVR3Enable) { + if (config.DIVR3) |d| { + const v: u7 = @intCast(@as(u32, @intFromFloat(d)) - 1); + break :blk @enumFromInt(v); + } + } + break :blk null; + }; + + enable_PLL3(PLL_Config{ + .DIVM = divm, + .DIVN = divn, + .VCI_range = vci, + .VCO_selection = vco, + .DIVP = divp, + .DIVQ = divq, + .DIVR = divr, + .fractional = frac, + }); + } else { + //Restore PLL state and set to disable after clock config + RCC.CR.modify_one("PLLON[2]", actual_state.@"PLLON[2]"); + disable_PLL3(); + } + + // Third: apply prescalers (except for the D1 prescaler, these will be applied only during the sysclk clock switch) + + set_D2_prescaler(d2_apb1, d2_apb2); + set_D3_prescaler(d3_apb1); + set_rcc_tim_pre(tim_pre); + // final stage: apply clock switches and configure the system properly + + // changing D1 without checking for upscale is safe here, since both VOS and FLASH are configured for maximum HSI(secure_enable clock) + set_D1_prescaler(d1_core_pre, d1_ahb_pre, d1_apb_pre); + set_sysclk(Sysclk_Config{ + .clk_src = sysclk, + .falsh_latency = flantency, + .flash_signal_delay = flantency, + .power_scale = vos, + .upscale = upscale, + }); + + if (config.flags.MCO1OutPutEnable) { + const src: MCO1SEL = switch (config.RCC_MCO1Source.?) { + .RCC_MCO1SOURCE_LSE => MCO1SEL.LSE, + .RCC_MCO1SOURCE_HSE => MCO1SEL.HSE, + .RCC_MCO1SOURCE_HSI => MCO1SEL.HSI, + .RCC_MCO1SOURCE_HSI48 => MCO1SEL.HSI48, + .RCC_MCO1SOURCE_PLL1QCLK => MCO1SEL.PLL1_Q, + }; + + const mco1_pre: MCOPRE = blk: { + if (config.RCC_MCODiv1) |p| { + const n: u4 = @intFromEnum(p); + break :blk @enumFromInt(n + 1); + } + break :blk MCOPRE.Div1; + }; + + set_mco1_params(src, mco1_pre); + } + + if (config.flags.MCO2OutPutEnable) { + const src: MCO2SEL = switch (config.RCC_MCO2Source.?) { + .RCC_MCO2SOURCE_SYSCLK => MCO2SEL.SYS, + .RCC_MCO2SOURCE_PLL2PCLK => MCO2SEL.PLL2_P, + .RCC_MCO2SOURCE_HSE => MCO2SEL.HSE, + .RCC_MCO2SOURCE_PLLCLK => MCO2SEL.PLL1_P, + .RCC_MCO2SOURCE_CSICLK => MCO2SEL.CSI, + .RCC_MCO2SOURCE_LSICLK => MCO2SEL.LSI, + }; + + const mco2_pre: MCOPRE = blk: { + if (config.RCC_MCODiv2) |p| { + const n: u4 = @intFromEnum(p); + break :blk @enumFromInt(n + 1); + } + break :blk MCOPRE.Div1; + }; + + set_mco2_params(src, mco2_pre); + } + + if (config.flags.RTCEnable) { + const pre: u6 = if (config.RCC_RTC_Clock_Source_FROM_HSE) |d| @intCast(@as(u32, @intFromFloat(d.get()))) else 0; + const src: RTCSEL = blk: { + break :blk switch (config.RTCClockSelection.?) { + .HSERTCDevisor => RTCSEL.HSE, + .RCC_RTCCLKSOURCE_LSE => RTCSEL.LSE, + .RCC_RTCCLKSOURCE_LSI => RTCSEL.LSI, + }; + }; + set_RTC_params(src, pre); + } else { + set_RTC_params(.DISABLE, 63); + } + + config_d1_kernel_srcs(.{ + .fmc_src = fmc_src, + .octospi_src = ospi_src, + .peri_src = presel_src, + .sdmcc_src = mmc_src, + }); + + config_d2_kernel_src(.{ + .cec_src = cec_src, + .dfsdm_src = dfsdm_src, + .fdcan_src = fdcan_src, + .i2c1235_src = i2c1235_src, + .lptim1_src = tim1_src, + .rng_src = rng_src, + .sai1_src = sai1_src, + .sai23_src = .PER, // not present in STM32H723VG + .spdifrx_src = spdifrx_src, + .spi123_src = spi123_src, + .spi45_src = spi45_src, + .spwmi_src = swpmi, + .usart16910_src = usart16910_src, + .usart234578_src = usart234578_src, + .usb_src = usb_src, + }); + + config_d3_kernel_src(D3_Kernel_Config{ + .adc_src = adc_src, + .i2c4_src = i2c4_src, + .lptim2_src = lptim2_src, + .lptim345_src = lptim34_src, + .lpuart1 = lpuart1_src, + .sai4a_src = sai4a_src, + .sai4b_src = sai4b_src, + .spi6_src = spi6_src, + }); +} + +pub fn set_power_scale(scale: VOS) void { + PWR.D3CR.modify_one("VOS", scale); //set VOS scale + + //wait until voltage scaling is applied + while (@as(u2, @intFromEnum(PWR.D3CR.read().VOS)) != PWR.CSR1.read().ACTVOS) { + asm volatile ("" ::: .{ .memory = true }); + } + + //wait until voltage scaling is ready + while (PWR.CSR1.read().ACTVOSRDY != 1) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub fn set_flash(latency: u3, signal_delay: u2) void { + FLASH.ACR.modify(.{ + .LATENCY = latency, + .WRHIGHFREQ = signal_delay, + }); +} + +/// enables the HSI oscillator with optional trimming and division +/// NOTE: Division can only be applied when the HSI is not used by some PLL +pub fn enable_hsi(trim: ?u7, div: ?HSIDIV) void { + if (trim) |t| { + RCC.HSICFGR.modify_one("HSITRIM", t); + } + + if (div) |d| { + RCC.CR.modify_one("HSIDIV", d); + } + + RCC.CR.modify_one("HSION", 1); + while (RCC.CR.read().HSIRDY != 1) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub inline fn disable_hsi() void { + RCC.CR.modify_one("HSION", 0); +} + +pub fn enable_hsi48() void { + RCC.CR.modify_one("HSI48ON", 1); + while (RCC.CR.read().HSI48RDY != 1) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub fn disable_hsi48() void { + RCC.CR.modify_one("HSI48ON", 0); +} + +pub fn enable_csi(trim: ?u6) void { + if (trim) |t| { + RCC.CSICFGR.modify_one("CSITRIM", t); + } + RCC.CR.modify_one("CSION", 1); + while (RCC.CR.read().CSIRDY != 1) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub inline fn disable_csi() void { + RCC.CR.modify_one("CSION", 0); +} + +pub fn enable_lsi() void { + RCC.CSR.modify_one("LSION", 1); + + while (RCC.CSR.read().LSIRDY != 1) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub inline fn disable_lsi() void { + RCC.CSR.modify_one("LSION", 0); +} + +pub fn enable_hse(css: bool, bypass: bool, timeout: ?usize) error{HSE_Timeout}!void { + var wait_ticks: usize = blk: { + if (timeout) |t| { + break :blk calc_wait_ticks(t); + } else { + break :blk std.math.maxInt(usize); + } + }; + + RCC.CR.modify(.{ + .HSEBYP = @intFromBool(bypass), + .HSECSSON = @intFromBool(css), + .HSEON = 1, + }); + + while (RCC.CR.read().HSERDY != 1) : (wait_ticks -= 1) { + if (wait_ticks == 0) { + //timeout reached + return error.HSE_Timeout; + } + + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub inline fn disable_hse() void { + RCC.CR.modify_one("HSEON", 0); +} + +pub fn enable_lse(css: bool, bypass: bool, drive: LSEDRV, timeout: ?usize) error{LSE_Timeout}!void { + var wait_ticks: usize = blk: { + if (timeout) |t| { + break :blk calc_wait_ticks(t); + } else { + break :blk std.math.maxInt(usize); + } + }; + + RCC.BDCR.modify(.{ + .LSEBYP = @intFromBool(bypass), + .LSECSSON = @intFromBool(css), + .LSEON = 1, + .LSEDRV = drive, + }); + + while (RCC.BDCR.read().LSERDY != 1) : (wait_ticks -= 1) { + if (wait_ticks == 0) { + //timeout reached + return error.LSE_Timeout; + } + + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub inline fn disable_lse() void { + PWR.CR1.modify_one("DBP", 1); //enable access to backup domain + RCC.BDCR.modify_one("LSEON", 0); +} +/// Change the PLL clock source +/// NOTE: This function should only be used while ALL PLLs are disabled +pub fn set_plls_source(src: PLLSRC) void { + RCC.PLLCKSELR.modify_one("PLLSRC", src); +} + +pub fn enable_PLL1(config: PLL_Config) void { + // first set the dividers and then enable the clocks + RCC.PLLCKSELR.modify_one("DIVM[0]", config.DIVM); + RCC.PLLDIVR.modify_one("PLLN", config.DIVN); + RCC.PLLCFGR.modify(.{ + .@"PLLVCOSEL[0]" = config.VCO_selection, + .@"PLLRGE[0]" = config.VCI_range, + }); + + if (config.DIVP) |c| { + RCC.PLLDIVR.modify_one("PLLP", c); + RCC.PLLCFGR.modify_one("DIVPEN[0]", 1); + } + + if (config.DIVQ) |c| { + RCC.PLLDIVR.modify_one("PLLQ", c); + RCC.PLLCFGR.modify_one("DIVQEN[0]", 1); + } + + if (config.DIVR) |c| { + RCC.PLLDIVR.modify_one("PLLR", c); + RCC.PLLCFGR.modify_one("DIVREN[0]", 1); + } + + if (config.fractional) |f| { + RCC.PLLCFGR.modify_one("PLLFRACEN[0]", 0); + RCC.PLLFRACR.modify_one("FRACN", f); + RCC.PLLCFGR.modify_one("PLLFRACEN[0]", 1); + } + + RCC.CR.modify_one("PLLON[0]", 1); + while (RCC.CR.read().@"PLLRDY[0]" != 1) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +// TODO: maybe create a function to disable PLL dividers individually +pub fn disable_PLL1() void { + RCC.CR.modify_one("PLLON[0]", 0); + while (RCC.CR.read().@"PLLRDY[0]" != 0) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +// TODO: fix MMIO to include registers for PLL2 and PLL3 +pub fn enable_PLL2(config: PLL_Config) void { + const PLL2DIVR: *volatile @TypeOf(RCC.PLLDIVR) = @ptrFromInt(@intFromPtr(RCC) + 0x38); + const PLL2FRACR: *volatile @TypeOf(RCC.PLLFRACR) = @ptrFromInt(@intFromPtr(RCC) + 0x3C); + + // first set the dividers and then enable the clocks + RCC.PLLCKSELR.modify_one("DIVM[1]", config.DIVM); + PLL2DIVR.modify_one("PLLN", config.DIVN); + RCC.PLLCFGR.modify(.{ + .@"PLLVCOSEL[1]" = config.VCO_selection, + .@"PLLRGE[1]" = config.VCI_range, + }); + + if (config.DIVP) |c| { + PLL2DIVR.modify_one("PLLP", c); + RCC.PLLCFGR.modify_one("DIVPEN[1]", 1); + } + + if (config.DIVQ) |c| { + PLL2DIVR.modify_one("PLLQ", c); + RCC.PLLCFGR.modify_one("DIVQEN[1]", 1); + } + + if (config.DIVR) |c| { + PLL2DIVR.modify_one("PLLR", c); + RCC.PLLCFGR.modify_one("DIVREN[1]", 1); + } + + if (config.fractional) |f| { + RCC.PLLCFGR.modify_one("PLLFRACEN[1]", 0); + PLL2FRACR.modify_one("FRACN", f); + RCC.PLLCFGR.modify_one("PLLFRACEN[1]", 1); + } + + RCC.CR.modify_one("PLLON[1]", 1); + while (RCC.CR.read().@"PLLRDY[1]" != 1) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub fn disable_PLL2() void { + RCC.CR.modify_one("PLLON[1]", 0); + while (RCC.CR.read().@"PLLRDY[1]" != 0) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub fn enable_PLL3(config: PLL_Config) void { + const PLL3DIVR: *volatile @TypeOf(RCC.PLLDIVR) = @ptrFromInt(@intFromPtr(RCC) + 0x40); + const PLL3FRACR: *volatile @TypeOf(RCC.PLLFRACR) = @ptrFromInt(@intFromPtr(RCC) + 0x44); + + // first set the dividers and then enable the clocks + RCC.PLLCKSELR.modify_one("DIVM[2]", config.DIVM); + PLL3DIVR.modify_one("PLLN", config.DIVN); + RCC.PLLCFGR.modify(.{ + .@"PLLVCOSEL[2]" = config.VCO_selection, + .@"PLLRGE[2]" = config.VCI_range, + }); + + if (config.DIVP) |c| { + PLL3DIVR.modify_one("PLLP", c); + RCC.PLLCFGR.modify_one("DIVPEN[2]", 1); + } + + if (config.DIVQ) |c| { + PLL3DIVR.modify_one("PLLQ", c); + RCC.PLLCFGR.modify_one("DIVQEN[2]", 1); + } + + if (config.DIVR) |c| { + PLL3DIVR.modify_one("PLLR", c); + RCC.PLLCFGR.modify_one("DIVREN[2]", 1); + } + + if (config.fractional) |f| { + RCC.PLLCFGR.modify_one("PLLFRACEN[2]", 0); + PLL3FRACR.modify_one("FRACN", f); + RCC.PLLCFGR.modify_one("PLLFRACEN[2]", 1); + } + + RCC.CR.modify_one("PLLON[2]", 1); + while (RCC.CR.read().@"PLLRDY[2]" != 1) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub fn disable_PLL3() void { + RCC.CR.modify_one("PLLON[2]", 0); + while (RCC.CR.read().@"PLLRDY[2]" != 0) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub fn set_sysclock_source(src: SW) void { + RCC.CFGR.modify_one("SW", src); + while (RCC.CFGR.read().SW != RCC.CFGR.read().SWS) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub fn set_D1_prescaler(core: HPRE, AHB: HPRE, APB3: PPRE) void { + RCC.D1CFGR.modify(.{ + .D1CPRE = core, + .HPRE = AHB, + .D1PPRE = APB3, + }); + + //wait until core prescaler is applied + while (RCC.D1CFGR.read().D1CPRE != core) { + asm volatile ("" ::: .{ .memory = true }); + } +} + +pub fn set_D2_prescaler(d2_apb1: PPRE, d2_apb2: PPRE) void { + RCC.D2CFGR.modify(.{ + .D2PPRE1 = d2_apb1, + .D2PPRE2 = d2_apb2, + }); +} + +pub fn set_D3_prescaler(d3_apb: PPRE) void { + RCC.D3CFGR.modify_one("D3PPRE", d3_apb); +} + +pub fn set_sysclk(config: Sysclk_Config) void { + + //if upscaling, set voltage scaling and flash latency before changing sysclock + //else, set them after changing sysclock + if (config.upscale) { + set_flash(config.falsh_latency, config.flash_signal_delay); + set_power_scale(config.power_scale); + } else { + defer set_flash(config.falsh_latency, config.flash_signal_delay); + defer set_power_scale(config.power_scale); + } + + set_sysclock_source(config.clk_src); +} + +pub fn set_mco1_params(src: MCO1SEL, div: MCOPRE) void { + RCC.CFGR.modify(.{ + .MCO1PRE = div, + .MCO1SEL = src, + }); +} + +pub fn set_mco2_params(src: MCO2SEL, div: MCOPRE) void { + RCC.CFGR.modify(.{ + .MCO2PRE = div, + .MCO2SEL = src, + }); +} + +///set RTC config params +/// NOTE: Div only applies when the RTC clock source is HSE, +/// otherwise the value is simply ignored +pub fn set_RTC_params(src: RTCSEL, div: u6) void { + RCC.CFGR.modify_one("RTCPRE", div); + RCC.BDCR.modify_one("RTCSEL", src); +} + +pub fn set_rcc_tim_pre(p: TIMPRE) void { + RCC.CFGR.modify_one("TIMPRE", p); +} + +pub fn config_d1_kernel_srcs(config: D1_Kernel_Config) void { + RCC.D1CCIPR.modify(.{ + .FMCSEL = config.fmc_src, + .OCTOSPISEL = config.octospi_src, + .SDMMCSEL = config.sdmcc_src, + .PERSEL = config.peri_src, + }); +} + +pub fn config_d2_kernel_src(config: D2_Kernel_Config) void { + RCC.D2CCIP1R.modify(.{ + .SAI1SEL = config.sai1_src, + .SAI23SEL = config.sai23_src, + .SPI123SEL = config.spi123_src, + .SPI45SEL = config.spi45_src, + .SPDIFRXSEL = config.spdifrx_src, + .DFSDM1SEL = config.dfsdm_src, + .FDCANSEL = config.fdcan_src, + .SWPMISEL = config.spwmi_src, + }); + + RCC.D2CCIP2R.modify(.{ + .USART234578SEL = config.usart234578_src, + .USART16910SEL = config.usart16910_src, + .RNGSEL = config.rng_src, + .I2C1235SEL = config.i2c1235_src, + .USBSEL = config.usb_src, + .CECSEL = config.cec_src, + .LPTIM1SEL = config.lptim1_src, + }); +} + +pub fn config_d3_kernel_src(config: D3_Kernel_Config) void { + RCC.D3CCIPR.modify(.{ + .LPUART1SEL = config.lpuart1, + .I2C4SEL = config.i2c4_src, + .LPTIM2SEL = config.lptim2_src, + .LPTIM345SEL = config.lptim345_src, + .ADCSEL = config.adc_src, + .SAI4ASEL = config.sai4a_src, + .SAI4BSEL = config.sai4b_src, + .SPI6SEL = config.spi6_src, + }); +} + +inline fn calc_wait_ticks(val: usize) usize { + const sysclk: usize = @intFromFloat(current_clk.CpuClockOutput); + const ms_per_tick = sysclk / 1000; + return ms_per_tick * val; +} + +//TODO: OTF, MDMA, USB_OTG_PHY, ETH_RC/TX +pub fn set_clock(comptime peri: Peripherals, state: u1) void { + const peri_name = @tagName(peri); + //microzig.chip.peripherals + + const field = comptime if (util.match_name(peri_name, &.{ + "OCTOSPIM", + })) "IOMNGREN" else if (util.match_name(peri_name, &.{ + "ADC1", + "ADC2", + })) "ADC12EN" else if (util.match_name(peri_name, &.{ + "PSSI", + })) "DCMIEN" else if (util.match_name(peri_name, &.{ + "DAC1", + "DAC2", + })) "DAC12EN" else peri_name ++ "EN"; + + const rcc_register_name = comptime if (util.match_name(peri_name, &.{ + "OCTOSPIM", + "OCTOSPI", + "SDMMC1", + "FMC", + "DMA2D", + })) "AHB3ENR" else if (util.match_name(peri_name, &.{ + "DMA", + "ADC", + "ETH", + "USB_OTG_HS", + })) "AHB1ENR" else if (util.match_name(peri_name, &.{ + "PSSI", + "DCMI", + "RNG", + "SDMMC2", + "FMAC", + "CORDIC", + })) "AHB2ENR" else if (util.match_name(peri_name, &.{ + "GPIOA", + "GPIOB", + "GPIOC", + "GPIOD", + "GPIOE", + "GPIOF", + "GPIOG", + "GPIOH", + "GPIOI", + "GPIOJ", + "GPIOK", + "CRC", + "BDMA", + "ADC3", + })) "AHB4ENR" else if (util.match_name(peri_name, &.{ + "LTDC", + "WWDG1", + })) "APB3ENR" else if (util.match_name(peri_name, &.{ + "TIM2", + "TIM3", + "TIM4", + "TIM5", + "TIM6", + "TIM7", + "TIM12", + "TIM13", + "TIM14", + "LPTIM1", + "WWDG2", + "SPI2", + "SPI3", + "SPDIFRX", + "USART2", + "USART3", + "UART4", + "UART5", + "I2C1", + "I2C2", + "I2C3", + "I2C5", + "CEC", + "DAC", + "UART7", + "UART8", + })) "APB1LENR" else if (util.match_name(peri_name, &.{ + "CRS", + "SWPMI", + "OPAMP", + "MDIOS", + "FDCAN", + "TIM23", + "TIM24", + })) "APB1HENR" else if (util.match_name(peri_name, &.{ + "TIM1", + "TIM8", + "USART1", + "USART6", + "UART9", + "USART10", + "SPI1", + "SPI4", + "TIM15", + "TIM16", + "TIM17", + "SPI5", + "SAI1", + "SAI2", + "SAI3", + "DFSDM1", + "HRTIM", + })) "APB2ENR" else if (util.match_name(peri_name, &.{ + "SYSCFG", + "LPUART1", + "SPI6", + "I2C4", + "LPTIM2", + "LPTIM3", + "LPTIM4", + "LPTIM5", + "DAC2", + "COMP12", + "VREF", + "RTCAPB", + "SAI4", + "DTS", + })) "APB4ENR"; + + @field(RCC, rcc_register_name).modify_one(field, state); +} + +pub fn enable_clock(comptime peri: Peripherals) void { + set_clock(peri, 1); +} + +pub fn disable_clock(comptime peri: Peripherals) void { + set_clock(peri, 0); +} diff --git a/port/stmicro/stm32/src/hals/common/gpio_v2.zig b/port/stmicro/stm32/src/hals/common/gpio_v2.zig index fba4909f6..0242de599 100644 --- a/port/stmicro/stm32/src/hals/common/gpio_v2.zig +++ b/port/stmicro/stm32/src/hals/common/gpio_v2.zig @@ -20,6 +20,10 @@ pub const Port = enum { E, F, G, + H, + I, + J, + K, }; pub const Mode = union(enum) { @@ -127,7 +131,11 @@ pub const Pin = enum(usize) { 4 => return if (@hasDecl(peripherals, "GPIOE")) peripherals.GPIOE else @panic("Invalid Pin"), 5 => return if (@hasDecl(peripherals, "GPIOF")) peripherals.GPIOF else @panic("Invalid Pin"), 6 => return if (@hasDecl(peripherals, "GPIOG")) peripherals.GPIOG else @panic("Invalid Pin"), - else => @panic("The STM32 only has ports 0..6 (A..G)"), + 7 => return if (@hasDecl(peripherals, "GPIOH")) peripherals.GPIOH else @panic("Invalid Pin"), + 8 => return if (@hasDecl(peripherals, "GPIOI")) peripherals.GPIOI else @panic("Invalid Pin"), + 9 => return if (@hasDecl(peripherals, "GPIOJ")) peripherals.GPIOJ else @panic("Invalid Pin"), + 10 => return if (@hasDecl(peripherals, "GPIOK")) peripherals.GPIOK else @panic("Invalid Pin"), + else => @panic("The STM32 GPIO_v2 only has ports 0..10 (A..K)"), } } @@ -174,4 +182,30 @@ pub const Pin = enum(usize) { const value: usize = pin + (@as(usize, 16) * @intFromEnum(port)); return @enumFromInt(value); } + + pub inline fn put(self: Pin, value: u1) void { + var port = self.get_port(); + switch (value) { + 0 => port.BSRR.raw = @intCast(self.mask() << 16), + 1 => port.BSRR.raw = self.mask(), + } + } + + pub inline fn low(self: @This()) void { + self.put(0); + } + + pub inline fn high(self: @This()) void { + self.put(1); + } + + pub inline fn toggle(self: Pin) void { + var port = self.get_port(); + port.ODR.raw ^= self.mask(); + } + + pub inline fn read(self: Pin) u1 { + const port = self.get_port(); + return @intFromBool((port.IDR.raw & self.mask() != 0)); + } }; diff --git a/port/stmicro/stm32/src/hals/common/pins_v2.zig b/port/stmicro/stm32/src/hals/common/pins_v2.zig index 198831cfd..22fb7bfab 100644 --- a/port/stmicro/stm32/src/hals/common/pins_v2.zig +++ b/port/stmicro/stm32/src/hals/common/pins_v2.zig @@ -47,11 +47,7 @@ pub const Pin = enum { pub const InputGPIO = struct { pin: gpio.Pin, pub inline fn read(self: @This()) u1 { - const port = self.pin.get_port(); - return if (port.IDR.raw & self.pin.mask() != 0) - 1 - else - 0; + return self.pin.read(); } }; @@ -59,11 +55,7 @@ pub const OutputGPIO = struct { pin: gpio.Pin, pub inline fn put(self: @This(), value: u1) void { - var port = self.pin.get_port(); - switch (value) { - 0 => port.BSRR.raw = @intCast(self.pin.mask() << 16), - 1 => port.BSRR.raw = self.pin.mask(), - } + self.pin.put(value); } pub inline fn low(self: @This()) void { @@ -75,8 +67,7 @@ pub const OutputGPIO = struct { } pub inline fn toggle(self: @This()) void { - var port = self.pin.get_port(); - port.ODR.raw ^= self.pin.mask(); + self.pin.toggle(); } }; @@ -114,19 +105,12 @@ pub const Digital_IO_Pin = struct { } pub fn write_fn(ptr: *anyopaque, state: State) WriteError!void { const self: *@This() = @ptrCast(@alignCast(ptr)); - var port = self.pin.get_port(); - switch (state) { - .low => port.BSRR.raw = @intCast(self.pin.mask() << 16), - .high => port.BSRR.raw = self.pin.mask(), - } + self.pin.put(@intFromEnum(state)); } + pub fn read_fn(ptr: *anyopaque) ReadError!State { const self: *@This() = @ptrCast(@alignCast(ptr)); - const port = self.pin.get_port(); - return if (port.IDR.raw & self.pin.mask() != 0) - .high - else - .low; + return @as(State, @enumFromInt(self.pin.read())); } pub fn digital_io(ptr: *@This()) Digital_IO { @@ -194,6 +178,10 @@ pub const Port = enum { GPIOE, GPIOF, GPIOG, + GPIOH, + GPIOI, + GPIOJ, + GPIOK, pub const Configuration = struct { PIN0: ?Pin.Configuration = null, PIN1: ?Pin.Configuration = null, @@ -229,6 +217,10 @@ pub const GlobalConfiguration = struct { GPIOE: ?Port.Configuration = null, GPIOF: ?Port.Configuration = null, GPIOG: ?Port.Configuration = null, + GPIOH: ?Port.Configuration = null, + GPIOI: ?Port.Configuration = null, + GPIOJ: ?Port.Configuration = null, + GPIOK: ?Port.Configuration = null, comptime { const port_field_count = @typeInfo(Port).@"enum".fields.len; diff --git a/port/stmicro/stm32/src/hals/common/util.zig b/port/stmicro/stm32/src/hals/common/util.zig index 8713df969..545421a96 100644 --- a/port/stmicro/stm32/src/hals/common/util.zig +++ b/port/stmicro/stm32/src/hals/common/util.zig @@ -12,10 +12,10 @@ pub fn match_name(heystack: []const u8, needles: []const []const u8) bool { } pub fn create_peripheral_enum(comptime bases_name: []const []const u8) type { - var names: [70]std.builtin.Type.EnumField = undefined; + var names: [80]std.builtin.Type.EnumField = undefined; var names_index = 0; const peripheral = @typeInfo(peripherals); - @setEvalBranchQuota(10_000); + @setEvalBranchQuota(20_000); switch (peripheral) { .@"struct" => |data| { for (data.decls) |decls| { From 1f94eb09f18b3775331549bcdb3a56875ff1842e Mon Sep 17 00:00:00 2001 From: RecursiveError Date: Mon, 12 Jan 2026 22:52:27 -0300 Subject: [PATCH 2/4] update build.zig.zon --- port/stmicro/stm32/build.zig.zon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/port/stmicro/stm32/build.zig.zon b/port/stmicro/stm32/build.zig.zon index 59095365f..b0e344bbf 100644 --- a/port/stmicro/stm32/build.zig.zon +++ b/port/stmicro/stm32/build.zig.zon @@ -10,7 +10,7 @@ .hash = "N-V-__8AAFi8WBlOh-NikHFVBjzQE0F1KixgKjVWYnlijPNm", }, .ClockHelper = .{ - .url = "/home/guilherme/\xc3\x81rea de trabalho/mz-clockhelper/ClockHelper/", + .url = "git+https://github.com/ZigEmbeddedGroup/ClockHelper#922c35eb54f0417318ccfcc32367bdcf11823ede", .hash = "ClockHelper-2.0.0-RcMaOUMGIwEUEHQJ0C2Q-KbWdKXN2GqzQcs9MpVSTgm3", }, }, From 7e5fb7a52856e9b3a0d124450f0abbf2786373e1 Mon Sep 17 00:00:00 2001 From: RecursiveError Date: Mon, 19 Jan 2026 20:16:45 -0300 Subject: [PATCH 3/4] update h7 rcc --- port/stmicro/stm32/src/hals/STM32H723.zig | 12 +++ port/stmicro/stm32/src/hals/STM32H723/rcc.zig | 98 ++++++++++++++++--- 2 files changed, 95 insertions(+), 15 deletions(-) diff --git a/port/stmicro/stm32/src/hals/STM32H723.zig b/port/stmicro/stm32/src/hals/STM32H723.zig index f86d61a20..f5116a43e 100644 --- a/port/stmicro/stm32/src/hals/STM32H723.zig +++ b/port/stmicro/stm32/src/hals/STM32H723.zig @@ -14,3 +14,15 @@ pub fn get_sys_clk() u32 { pub fn get_systick_clk() u32 { return @intFromFloat(rcc.current_clocks.CortexSysOutput); } + +pub var Reset_Reason: rcc.ResetReason = .unknown; +pub fn init() void { + const reset = rcc.get_reset_reason(); + Reset_Reason = reset; + + // force default configuration; required for clock configuration + // Power control (PWR) 6.4.1 System supply startup - page: 238 RM0468 Rev 3 + if (reset == .power_on) { + microzig.chip.peripherals.PWR.CR3.raw = 0x0000_0046; + } +} diff --git a/port/stmicro/stm32/src/hals/STM32H723/rcc.zig b/port/stmicro/stm32/src/hals/STM32H723/rcc.zig index 214790b3c..38427d1aa 100644 --- a/port/stmicro/stm32/src/hals/STM32H723/rcc.zig +++ b/port/stmicro/stm32/src/hals/STM32H723/rcc.zig @@ -53,10 +53,10 @@ const SPI45SEL = microzig.chip.types.peripherals.rcc_h7.SPI45SEL; //clocktree type shortcuts const ClockTree = @field(@import("ClockTree"), microzig.config.chip_name); const Config = ClockTree.Config; -const Clock_Output = ClockTree.Clock_Output; -const Config_Output = ClockTree.Config_Output; +const ClockOutput = ClockTree.Clock_Output; +const ConfigOutput = ClockTree.Config_Output; -var current_clk: Clock_Output = blk: { +var current_clk: ClockOutput = blk: { const ret = ClockTree.get_clocks(.{}) catch unreachable; break :blk ret.clock; }; @@ -83,7 +83,7 @@ pub const MCO_Config = struct { mco2_pre: MCOPRE, }; -pub const Sysclk_Config = struct { +pub const SYSCLK_Config = struct { upscale: bool, falsh_latency: u3, flash_signal_delay: u2, @@ -91,14 +91,14 @@ pub const Sysclk_Config = struct { clk_src: SW, }; -pub const D1_Kernel_Config = struct { +pub const D1_KernelConfig = struct { peri_src: PERSEL, sdmcc_src: SDMMCSEL, octospi_src: FMCSEL, fmc_src: FMCSEL, }; -pub const D2_Kernel_Config = struct { +pub const D2_KernelConfig = struct { //CCIP1R spwmi_src: SWPMMISEL, dfsdm_src: DFSDMSEL, @@ -118,7 +118,7 @@ pub const D2_Kernel_Config = struct { cec_src: CECSEL, }; -pub const D3_Kernel_Config = struct { +pub const D3_KernelConfig = struct { lpuart1: LPUARTSEL, i2c4_src: I2C4SEL, lptim2_src: LPTIM2SEL, @@ -128,10 +128,38 @@ pub const D3_Kernel_Config = struct { sai4b_src: SAIASEL, spi6_src: SPI6SEL, }; + +pub const ResetBits = packed struct(u10) { + CPU: u1, + D1: u1, + D2: u1, + BOR: u1, + PIN: u1, + POR: u1, + SFT: u1, + IWDG1: u1, + WWDG1: u1, + LPWR: u1, +}; + +pub const ResetReason = enum { + power_on, + pin_rest, + brownout, + system_reset, + cpu_reset, + window_watchdog, + independent_watchdog, + d1_DStandby_exit, + d2_DStandby_exit, + d1_or_cpu_invalid_mode, //D1 erroneously enters DStandby mode or CPU erroneously enters CStop mode + unknown, // probably the RCC.RSR wasn't cleared, preventing reading the reset reason +}; + /// configure clocks /// NOTE: this function expects the current clock to be in a valid state and free of glitches in the flash and PWR domains /// it is important that any external change (whether manual or caused by hardware events) to the clock tree be restored before calling this function -pub fn apply(comptime config: Config) !Clock_Output { +pub fn apply(comptime config: Config) !ClockOutput { const clk = comptime ClockTree.get_clocks(config) catch unreachable; current_clk = clk.clock; @@ -189,7 +217,7 @@ fn secure_enable(upscale: bool, hsi_div: HSIDIV, trim: ?u7) void { set_D1_prescaler(.Div1, .Div1, .Div1); } -fn apply_internal(comptime config: Config_Output, upscale: bool) !void { +fn apply_internal(comptime config: ConfigOutput, upscale: bool) !void { const actual_state = RCC.CR.read(); const d1_core_pre: HPRE = blk: { @@ -585,7 +613,7 @@ fn apply_internal(comptime config: Config_Output, upscale: bool) !void { // changing D1 without checking for upscale is safe here, since both VOS and FLASH are configured for maximum HSI(secure_enable clock) set_D1_prescaler(d1_core_pre, d1_ahb_pre, d1_apb_pre); - set_sysclk(Sysclk_Config{ + set_sysclk(SYSCLK_Config{ .clk_src = sysclk, .falsh_latency = flantency, .flash_signal_delay = flantency, @@ -673,7 +701,7 @@ fn apply_internal(comptime config: Config_Output, upscale: bool) !void { .usb_src = usb_src, }); - config_d3_kernel_src(D3_Kernel_Config{ + config_d3_kernel_src(D3_KernelConfig{ .adc_src = adc_src, .i2c4_src = i2c4_src, .lptim2_src = lptim2_src, @@ -997,7 +1025,7 @@ pub fn set_D3_prescaler(d3_apb: PPRE) void { RCC.D3CFGR.modify_one("D3PPRE", d3_apb); } -pub fn set_sysclk(config: Sysclk_Config) void { +pub fn set_sysclk(config: SYSCLK_Config) void { //if upscaling, set voltage scaling and flash latency before changing sysclock //else, set them after changing sysclock @@ -1038,7 +1066,7 @@ pub fn set_rcc_tim_pre(p: TIMPRE) void { RCC.CFGR.modify_one("TIMPRE", p); } -pub fn config_d1_kernel_srcs(config: D1_Kernel_Config) void { +pub fn config_d1_kernel_srcs(config: D1_KernelConfig) void { RCC.D1CCIPR.modify(.{ .FMCSEL = config.fmc_src, .OCTOSPISEL = config.octospi_src, @@ -1047,7 +1075,7 @@ pub fn config_d1_kernel_srcs(config: D1_Kernel_Config) void { }); } -pub fn config_d2_kernel_src(config: D2_Kernel_Config) void { +pub fn config_d2_kernel_src(config: D2_KernelConfig) void { RCC.D2CCIP1R.modify(.{ .SAI1SEL = config.sai1_src, .SAI23SEL = config.sai23_src, @@ -1070,7 +1098,7 @@ pub fn config_d2_kernel_src(config: D2_Kernel_Config) void { }); } -pub fn config_d3_kernel_src(config: D3_Kernel_Config) void { +pub fn config_d3_kernel_src(config: D3_KernelConfig) void { RCC.D3CCIPR.modify(.{ .LPUART1SEL = config.lpuart1, .I2C4SEL = config.i2c4_src, @@ -1222,3 +1250,43 @@ pub fn enable_clock(comptime peri: Peripherals) void { pub fn disable_clock(comptime peri: Peripherals) void { set_clock(peri, 0); } + +pub fn get_reset_bits() ResetBits { + const RSR = RCC.RSR.read(); + return ResetBits{ + .CPU = RSR.CPURSTF, + .D1 = RSR.D1RSTF, + .D2 = RSR.D2RSTF, + .BOR = RSR.BORRSTF, + .PIN = RSR.PINRSTF, + .POR = RSR.PORRSTF, + .SFT = RSR.SFTRSTF, + .IWDG1 = RSR.IWDG1RSTF, + .WWDG1 = RSR.WWDG1RSTF, + .LPWR = RSR.LPWRRSTF, + }; +} + +pub fn clear_reset_bits() void { + RCC.RSR.modify_one("RMVF", 1); + asm volatile ("" ::: .{ .memory = true }); + RCC.RSR.modify_one("RMVF", 0); +} + +pub fn get_reset_reason() ResetReason { + const rst_bits: u10 = @bitCast(get_reset_bits()); + + return switch (rst_bits) { + 0b0000111111 => .power_on, + 0b0000010001 => .pin_rest, + 0b0000011001 => .brownout, + 0b0001010001 => .system_reset, + 0b0000000001 => .cpu_reset, + 0b0100010001 => .window_watchdog, + 0b0010010001 => .independent_watchdog, + 0b0000000010 => .d1_DStandby_exit, + 0b0000000100 => .d2_DStandby_exit, + 0b1000010001 => .d1_or_cpu_invalid_mode, + else => .unknown, + }; +} From 4e225f5e76ed612975c301e7c10a0962086b11be Mon Sep 17 00:00:00 2001 From: RecursiveError Date: Sat, 7 Feb 2026 01:30:53 -0300 Subject: [PATCH 4/4] - add spi_v3 - change sub_peripheral_enum limit from 10 to 15 - fix rcc naming --- port/stmicro/stm32/src/hals/STM32H723.zig | 1 + port/stmicro/stm32/src/hals/STM32H723/rcc.zig | 8 +- port/stmicro/stm32/src/hals/common/spi_v3.zig | 542 ++++++++++++++++++ port/stmicro/stm32/src/hals/common/util.zig | 2 +- 4 files changed, 548 insertions(+), 5 deletions(-) create mode 100644 port/stmicro/stm32/src/hals/common/spi_v3.zig diff --git a/port/stmicro/stm32/src/hals/STM32H723.zig b/port/stmicro/stm32/src/hals/STM32H723.zig index f5116a43e..c92cbe64f 100644 --- a/port/stmicro/stm32/src/hals/STM32H723.zig +++ b/port/stmicro/stm32/src/hals/STM32H723.zig @@ -6,6 +6,7 @@ pub const systick = @import("./common/systick.zig"); pub const gpio = @import("./common/gpio_v2.zig"); pub const pins = @import("./common/pins_v2.zig"); pub const rcc = @import("./STM32H723/rcc.zig"); +pub const spi = @import("./common/spi_v3.zig"); pub fn get_sys_clk() u32 { return @intFromFloat(rcc.current_clocks.SysCLKOutput); diff --git a/port/stmicro/stm32/src/hals/STM32H723/rcc.zig b/port/stmicro/stm32/src/hals/STM32H723/rcc.zig index 38427d1aa..4170c580e 100644 --- a/port/stmicro/stm32/src/hals/STM32H723/rcc.zig +++ b/port/stmicro/stm32/src/hals/STM32H723/rcc.zig @@ -56,7 +56,7 @@ const Config = ClockTree.Config; const ClockOutput = ClockTree.Clock_Output; const ConfigOutput = ClockTree.Config_Output; -var current_clk: ClockOutput = blk: { +pub var current_clocks: ClockOutput = blk: { const ret = ClockTree.get_clocks(.{}) catch unreachable; break :blk ret.clock; }; @@ -161,11 +161,11 @@ pub const ResetReason = enum { /// it is important that any external change (whether manual or caused by hardware events) to the clock tree be restored before calling this function pub fn apply(comptime config: Config) !ClockOutput { const clk = comptime ClockTree.get_clocks(config) catch unreachable; - current_clk = clk.clock; + current_clocks = clk.clock; //verify if we need to upscale or downscale const HSI_CLK = if (clk.clock.HSIDiv == 0) 64_000_000 else clk.clock.HSIDiv; - const secure_sys_upscale: bool = current_clk.CpuClockOutput >= HSI_CLK; + const secure_sys_upscale: bool = current_clocks.CpuClockOutput >= HSI_CLK; const target_sys_upscale: bool = clk.clock.CpuClockOutput >= HSI_CLK; const hsi_div: HSIDIV = if (clk.config.HSIDiv) |d| @as(HSIDIV, @enumFromInt(@as(u2, @intFromEnum(d)))) else .Div1; @@ -1112,7 +1112,7 @@ pub fn config_d3_kernel_src(config: D3_KernelConfig) void { } inline fn calc_wait_ticks(val: usize) usize { - const sysclk: usize = @intFromFloat(current_clk.CpuClockOutput); + const sysclk: usize = @intFromFloat(current_clocks.CpuClockOutput); const ms_per_tick = sysclk / 1000; return ms_per_tick * val; } diff --git a/port/stmicro/stm32/src/hals/common/spi_v3.zig b/port/stmicro/stm32/src/hals/common/spi_v3.zig new file mode 100644 index 000000000..b6a56c429 --- /dev/null +++ b/port/stmicro/stm32/src/hals/common/spi_v3.zig @@ -0,0 +1,542 @@ +//implementação do SPI v3 +//TODO LIST: +// - batter slave support +// - CRC +// - SPI frame < 8bits + +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const enums = @import("enums.zig"); +pub const Instances = enums.SPI_Type; + +const Digital_IO = microzig.drivers.base.Digital_IO; +pub const spi_v3 = microzig.chip.types.peripherals.spi_v3; +pub const SPI_Peripheral = spi_v3.SPI; +pub const FTHLV = spi_v3.FTHLV; +pub const MBR = spi_v3.MBR; +pub const UDRCFG = spi_v3.UDRCFG; +pub const UDRDET = spi_v3.UDRDET; +pub const COMM = spi_v3.COMM; +pub const SP = spi_v3.SP; +pub const MASTER = spi_v3.MASTER; +pub const LSBFIRST = spi_v3.LSBFIRST; +pub const CPHA = spi_v3.CPHA; +pub const CPOL = spi_v3.CPOL; +pub const SSIOP = spi_v3.SSIOP; +pub const SSOM = spi_v3.SSOM; +pub const HDDIR = spi_v3.HDDIR; + +/// Software management of CS signal input +pub const SSM = enum(u1) { + /// SS pin (SPI CS) is used as an input to detect multiple SPI masters. + internal = 0, + + /// User controls the input signal via software. + external = 1, +}; + +pub const CS_Pin = union(enum) { + /// CS is controlled internally by the hardware. + internal: SSOM, + + /// CS is controlled internally by the HAL. + soft_internal: *Digital_IO, + + /// CS is controlled externally by the user. + external: void, +}; + +pub const UnderrunBehavior = union(enum) { + send_pattern: u32, + repeat_last_receive: void, + repeat_last_transmit: void, +}; + +//SPI master only configs +pub const SPI_Master = struct { + chip_select: CS_Pin, + + /// Defines which level represents an active CS signal. + chip_select_polarity: SSIOP = .ActiveLow, + + /// When non-null, enables multi-master mode. + /// NOTE: when CS is controlled internally by the hardware, + /// multi-master can only be achieved via software. + multi_master_support: ?SSM = null, + + ///When SPI master has to be disabled temporary for a specific configuration reason (e.g. CRC + ///reset, CPHA or HDDIR change) setting this bit prevents any glitches on the associated + ///outputs configured at alternate function mode by keeping them forced at state corresponding the current SPI configuration + lock_gpios: bool = false, + + /// defines the delay (in SPI clocks) between frames + inter_data_dalay: u4 = 0, + + /// defines the delay (in SPI clocks) between the first frame and CS activation + cs_delay: u4 = 0, + + /// SPI flow is suspended temporary on RxFIFO full condition. + automatic_rx_suspend: bool = false, +}; + +//SPI slave only configs +pub const SPI_Slave = struct { + underrun_detection: UDRDET = .StartOfFrame, + underrun_behavior: UnderrunBehavior = .{ .send_pattern = 0 }, +}; + +pub const SPI_Mode = union(enum) { + slave: SPI_Slave, + master: SPI_Master, +}; + +pub const SPI_Interrupts = struct { + /// RXP Interrupt Enable + rxp: bool = false, + /// TXP interrupt enable + txp: bool = false, + /// DXP interrupt enabled + dxp: bool = false, + /// EOT, SUSP and TXC interrupt enable + eot_susp_txc: bool = false, + /// TXTFIE interrupt enable + txtf: bool = false, + /// UDR interrupt enable + underrun: bool = false, + /// OVR interrupt enable + overrun: bool = false, + /// CRC Interrupt enable + crc_error: bool = false, + /// TIFRE interrupt enable + tifre: bool = false, + /// Mode Fault interrupt enable + mode_fault: bool = false, + /// Additional number of transactions reload interrupt enable + additional_transactions_reload: bool = false, +}; + +pub const SPI_Config = struct { + spi_mode: SPI_Mode, + baud_rate_div: MBR = .Div2, //SPI Master or Slave in TI mode only + + //commum SPI configs + cpol: CPOL = .IdleLow, + cpha: CPHA = .FirstEdge, + frame_format: LSBFIRST = .MSBFirst, + comm_mode: COMM = .FullDuplex, + data_size: u5 = 7, //8bits + + //hardware SPI configs + io_swap: bool = false, + fifo_threshold_level: FTHLV = .OneFrame, + protocol: SP = .Motorola, + + //DMA and Interrupts + enable_tx_dma: bool = false, + enable_rx_dma: bool = false, + interrupts: SPI_Interrupts = .{}, +}; + +pub const SPI_ConfigError = error{ + Invalid_DSIZE, + Invalid_Multimaster, +} || Digital_IO.SetDirError || Digital_IO.SetBiasError; + +pub const SPI_TransferError = error{ + InvalidMode, + Suspended, + InvalidTransactionLength, + + ///this error only occurs when mixing Blocking and Non-Blocking operations, + ///it happens when CTSIZE (current transaction size) reaches 0 but a + ///blocking operation is still has data to send/receive. + IncompleteTransaction, + + ///Check `get_error_flags()` for more details + TransactionError, +} || Digital_IO.WriteError; + +pub const SPI_ErrorStatus = packed struct(u5) { + overrun: bool, + underrun: bool, + mode_fault: bool, + crc_error: bool, + tiref: bool, +}; + +pub const SPI = struct { + instance: Instances, + spi: *volatile SPI_Peripheral, + nss: CS_Pin = .external, + + fn check_config(config: *const SPI_Config) SPI_ConfigError!void { + if (config.data_size < 0b11) return SPI_ConfigError.Invalid_DSIZE; + + if (config.spi_mode == .master) { + const master_conf = config.spi_mode.master; + switch (master_conf.chip_select) { + .internal => { + if (master_conf.multi_master_support) |ssm| { + if (ssm == .internal) return SPI_ConfigError.Invalid_Multimaster; + } + }, + else => {}, + } + } + } + + pub fn apply(self: *SPI, config: SPI_Config) SPI_ConfigError!void { + try check_config(&config); + + self.spi.CR1.raw = 0; //force reset SPI + defer self.spi.CR1.modify_one("SPE", 1); + + self.spi.CFG1.modify(.{ + .DSIZE = config.data_size, + .FTHLV = config.fifo_threshold_level, + .RXDMAEN = @intFromBool(config.enable_rx_dma), + .TXDMAEN = @intFromBool(config.enable_tx_dma), + .MBR = config.baud_rate_div, + }); + + self.spi.CFG2.modify(.{ + .COMM = config.comm_mode, + .SP = config.protocol, + .LSBFIRST = config.frame_format, + .CPHA = config.cpha, + .CPOL = config.cpol, + .IOSWP = @intFromBool(config.io_swap), + }); + + switch (config.spi_mode) { + .slave => |conf| self.slave_config(conf), + .master => |conf| try self.master_config(conf), + } + + self.spi.IER.modify(.{ + .RXPIE = @intFromBool(config.interrupts.rxp), + .TXPIE = @intFromBool(config.interrupts.txp), + .DXPIE = @intFromBool(config.interrupts.dxp), + .EOTIE = @intFromBool(config.interrupts.eot_susp_txc), + .TXTFIE = @intFromBool(config.interrupts.txtf), + .UDRIE = @intFromBool(config.interrupts.underrun), + .OVRIE = @intFromBool(config.interrupts.overrun), + .CRCEIE = @intFromBool(config.interrupts.crc_error), + .TIFREIE = @intFromBool(config.interrupts.tifre), + .MODFIE = @intFromBool(config.interrupts.mode_fault), + .TSERFIE = @intFromBool(config.interrupts.additional_transactions_reload), + }); + } + + fn slave_config(self: *const SPI, config: SPI_Slave) void { + const bhvr = @intFromEnum(config.underrun_behavior); + self.spi.CFG1.modify(.{ + .UDRDET = config.underrun_detection, + .UDRCFG = @as(UDRCFG, @enumFromInt(bhvr)), + }); + + switch (config.underrun_behavior) { + .send_pattern => |ptrn| { + self.spi.UDRDR.raw = ptrn; + }, + else => {}, + } + self.spi.CFG2.modify_one("MASTER", MASTER.Slave); + } + + fn master_config(self: *SPI, config: SPI_Master) SPI_ConfigError!void { + self.spi.CR1.modify_one("MASRX", @intFromBool(config.automatic_rx_suspend)); + self.nss = config.chip_select; + + switch (config.chip_select) { + .internal => |ssom| { + self.spi.CFG2.modify(.{ + .SSOM = ssom, + .SSOE = 1, + }); + }, + .external => { + self.spi.CFG2.modify(.{ + .SSOE = 0, + }); + }, + .soft_internal => |pin| { + try pin.set_direction(.output); + try pin.set_bias(.low); + + self.spi.CFG2.modify(.{ + .SSOE = 0, + }); + }, + } + + if (config.multi_master_support) |ssm| { + self.spi.CR1.modify_one("SSI", 1); + self.spi.CFG2.modify_one("SSM", @as(u1, @intFromEnum(ssm))); + } else { + self.spi.CFG2.modify_one("SSOE", 1); + self.spi.CFG2.modify_one("SSM", 0); + } + + self.spi.CFG2.modify(.{ + .MIDI = config.inter_data_dalay, + .MSSI = config.cs_delay, + .AFCNTR = @intFromBool(config.lock_gpios), + .MASTER = MASTER.Master, + .SSIOP = config.chip_select_polarity, + }); + } + + fn set_chip_select(self: *const SPI, active: bool) SPI_TransferError!void { + switch (self.nss) { + .soft_internal => |pin| { + try pin.write(if (active) .low else .high); + }, + else => {}, + } + } + + pub inline fn disable_spi(self: *const SPI) void { + self.spi.CR1.modify_one("SPE", 0); + } + + pub inline fn enable_spi(self: *const SPI) void { + self.spi.CR1.modify_one("SPE", 1); + } + + pub inline fn check_end_of_transfer(self: *const SPI) bool { + return self.spi.SR.read().EOT == 1; + } + + pub inline fn clear_end_of_transfer_flag(self: *const SPI) void { + self.spi.IFCR.modify_one("EOTC", 1); + } + + pub inline fn check_rx(self: *const SPI) bool { + return self.spi.SR.read().RXP == 1; + } + + pub inline fn check_tx(self: *const SPI) bool { + return self.spi.SR.read().TXP == 1; + } + + pub inline fn flush_rx_FIFO(self: *const SPI) void { + while (self.check_rx()) { + std.mem.doNotOptimizeAway(self.spi.RXDR8); + } + } + + pub inline fn start_transaction(self: *const SPI) void { + self.spi.CR1.modify_one("CSTART", 1); + } + + pub inline fn check_transfer_filled(self: *const SPI) bool { + return self.spi.SR.read().TXTF == 1; + } + + pub inline fn clear_filled_flag(self: *const SPI) void { + self.spi.IFCR.modify_one("TXTFC", 1); + } + + pub inline fn suspend_transaction(self: *const SPI) void { + self.spi.CR1.modify_one("CSUSP", 1); + } + + pub inline fn check_transaction_suspended(self: *const SPI) bool { + return self.spi.SR.read().SUSP == 1; + } + + // Suspends the current SPI transaction and blocks until the suspension is confirmed. + pub fn suspend_blocking(self: *const SPI) void { + self.suspend_transaction(); + while (!self.check_transaction_suspended()) {} + } + + pub fn get_error_flags(self: *const SPI) SPI_ErrorStatus { + return SPI_ErrorStatus{ + .overrun = self.spi.SR.read().OVR == 1, + .underrun = self.spi.SR.read().UDR == 1, + .mode_fault = self.spi.SR.read().MODF == 1, + .crc_error = self.spi.SR.read().CRCE == 1, + .tiref = self.spi.SR.read().TIFRE == 1, + }; + } + + pub fn clear_error_flags(self: *const SPI) void { + self.spi.IFCR.modify(.{ + .OVRC = 1, + .UDRC = 1, + .MODFC = 1, + .CRCEC = 1, + .TIFREC = 1, + }); + } + + pub fn clear_all_flags(self: *const SPI) void { + self.spi.IFCR.raw = 0xFFFFFFFF; + } + + pub fn set_transaction_length(self: *const SPI, length: u16, reload: u16) void { + self.disable_spi(); + defer self.enable_spi(); + + self.spi.CR2.modify(.{ + .TSIZE = length, + .TSER = reload, + }); + } + + inline fn check_for_error(self: *const SPI) SPI_TransferError!void { + if (@as(u5, @bitCast(self.get_error_flags())) != 0) { + return SPI_TransferError.TransactionError; + } + if (self.check_transaction_suspended()) return SPI_TransferError.Suspended; + } + + ///Blocking write operation. + /// + ///This function only supports `FullDuplex`, `HalfDuplex` and `Transmitter-Only` modes. + ///If used in HalfDuplex mode, the `HDDIR` bit is set to Transmitter. + ///If used in `Receiver-Only` mode, returns an InvalidMode error. + /// + ///NOTE: for correct use of Output Management in Internal CS mode, + ///the maximum transaction length is set to 16bits (65535). (max TSIZE value) + /// + ///NOTE: this function requires that the SPI peripheral is already configured and + ///in a valid state for a transaction. + pub fn write_blocking(self: *const SPI, data: []const u8) SPI_TransferError!void { + const mode = self.spi.CFG2.read().COMM; + + if (data.len == 0 or data.len > 65535) return SPI_TransferError.InvalidTransactionLength; + + //check current mode + if (mode == .HalfDuplex) { + self.spi.CR1.modify_one("HDDIR", HDDIR.Transmitter); + } else if (mode == .Receiver) return SPI_TransferError.InvalidMode; + + //set transaction length, CS and start transaction + self.set_transaction_length(@truncate(data.len), 0); + try self.set_chip_select(true); + defer self.set_chip_select(false) catch {}; + self.start_transaction(); + + defer self.clear_filled_flag(); + defer self.clear_end_of_transfer_flag(); + + for (data, 0..) |byte, idx| { + if (mode == .FullDuplex) self.flush_rx_FIFO(); + try self.check_for_error(); + + if (self.check_transfer_filled() and idx != data.len - 1) { + return SPI_TransferError.IncompleteTransaction; + } + + while (!self.check_tx()) {} + self.spi.TXDR8 = byte; + } + + while (!self.check_end_of_transfer()) { + if (mode == .FullDuplex) self.flush_rx_FIFO(); + try self.check_for_error(); + } + } + + ///Blocking read operation. + /// + ///This function only supports `FullDuplex`, `HalfDuplex` and `Receiver-Only` modes. + ///If used in HalfDuplex mode, the `HDDIR` bit is set to Receiver. + ///If used in `Transmitter-Only` mode, returns an InvalidMode error. + /// + ///NOTE: for correct use of Output Management in Internal CS mode, + ///the maximum transaction length is set to 16bits (65535). (max TSIZE value) + /// + ///NOTE: this function requires that the SPI peripheral is already configured and + ///in a valid state for a transaction. + pub fn read_blocking(self: *const SPI, buffer: []u8) SPI_TransferError!void { + const mode = self.spi.CFG2.read().COMM; + + if (buffer.len == 0 or buffer.len > 65535) return SPI_TransferError.InvalidTransactionLength; + + //check current mode + if (mode == .HalfDuplex) { + self.spi.CR1.modify_one("HDDIR", HDDIR.Receiver); + } else if (mode == .Transmitter) return SPI_TransferError.InvalidMode; + + //set transaction length, CS and start transaction + self.set_transaction_length(@truncate(buffer.len), 0); + try self.set_chip_select(true); + defer self.set_chip_select(false) catch {}; + + self.start_transaction(); + + defer self.clear_end_of_transfer_flag(); + + for (buffer) |*byte| { + if (mode == .FullDuplex) { + while (!self.check_tx()) {} + self.spi.TXDR8 = 0xFF; //send dummy byte + } + + try self.check_for_error(); + + while (!self.check_rx()) {} + byte.* = self.spi.RXDR8; + } + + while (!self.check_end_of_transfer()) { + if (mode == .FullDuplex) { + while (!self.check_tx()) {} + self.spi.TXDR8 = 0xFF; //send dummy byte + } + try self.check_for_error(); + } + } + + ///Blocking transceive operation. + ///Full-Duplex mode only. + ///both data and buffer must have the same length. + pub fn transceive_blocking(self: *const SPI, data: []const u8, buffer: []u8) SPI_TransferError!void { + const mode = self.spi.CFG2.read().COMM; + + if (data.len != buffer.len) return SPI_TransferError.InvalidTransactionLength; + if (data.len == 0 or data.len > 65535) return SPI_TransferError.InvalidTransactionLength; + + //check current mode + if (mode != .FullDuplex) { + return SPI_TransferError.InvalidMode; + } + + //set transaction length, CS and start transaction + self.set_transaction_length(@truncate(data.len), 0); + try self.set_chip_select(true); + defer self.set_chip_select(false) catch {}; + self.start_transaction(); + + defer self.clear_end_of_transfer_flag(); + defer self.clear_filled_flag(); + + for (data, 0..) |byte, idx| { + try self.check_for_error(); + + if (self.check_transfer_filled() and idx != data.len - 1) { + return SPI_TransferError.IncompleteTransaction; + } + + while (!self.check_tx()) {} + self.spi.TXDR8 = byte; + + while (!self.check_rx()) {} + buffer[idx] = self.spi.RXDR8; + } + + while (!self.check_end_of_transfer()) { + try self.check_for_error(); + } + } + + pub fn init(comptime spi_inst: Instances) SPI { + hal.rcc.enable_clock(enums.to_peripheral(spi_inst)); + return .{ .spi = enums.get_regs(SPI_Peripheral, spi_inst), .instance = spi_inst }; + } +}; diff --git a/port/stmicro/stm32/src/hals/common/util.zig b/port/stmicro/stm32/src/hals/common/util.zig index 545421a96..cd0f89375 100644 --- a/port/stmicro/stm32/src/hals/common/util.zig +++ b/port/stmicro/stm32/src/hals/common/util.zig @@ -46,7 +46,7 @@ pub fn sub_peripheral_enum(comptime T: type, comptime keep_name: []const []const const enum_info = @typeInfo(T); var names_index = 0; - var names: [10]std.builtin.Type.EnumField = undefined; + var names: [15]std.builtin.Type.EnumField = undefined; @setEvalBranchQuota(10_000); switch (enum_info) {