From e8d5817a6296d2217e51930db9751ca0b031e670 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 03:42:29 +0200 Subject: [PATCH 01/21] feat(ClockDomain)!: Implement ClockDomain --- Inc/HALAL/Models/Clocks/ClockDomain.hpp | 534 ++++++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 Inc/HALAL/Models/Clocks/ClockDomain.hpp diff --git a/Inc/HALAL/Models/Clocks/ClockDomain.hpp b/Inc/HALAL/Models/Clocks/ClockDomain.hpp new file mode 100644 index 000000000..856b26e11 --- /dev/null +++ b/Inc/HALAL/Models/Clocks/ClockDomain.hpp @@ -0,0 +1,534 @@ +#pragma once + +#include +#include +#include +#include +#include "ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp" + +namespace ST_LIB { +extern void compile_error(const char* msg); + +struct ClockDomain { + +/** + * ========================================= + * Inner Workings + * ========================================= + */ + + enum class ClockGroup : uint8_t { + SPI123_G, SPI45_G, SPI6_G, SDMMC_G, ADC_G, FDCAN_G, + USART16_G, USART234578_G, I2C1235_G, I2C4_G, + LPUART1_G, LPTIM_G, RNG_G, USB_G, DFSDM1_G, SAI1_G, SAI4_G, + ETH_MAC_G, RTC_G, CEC_G, + APB1_TIM_G, APB2_TIM_G, D1_BUS_G, D2_BUS_G, D3_BUS_G, + }; + + static constexpr uint32_t PLLM_MIN = 1; + static constexpr uint32_t PLLM_MAX = 63; + static constexpr uint32_t PLLN_MIN = 4; + static constexpr uint32_t PLLN_MAX = 512; + static constexpr uint32_t PLLP_MIN = 1; + static constexpr uint32_t PLLP_MAX = 128; + static constexpr uint32_t PLLQ_MIN = 1; + static constexpr uint32_t PLLQ_MAX = 128; + static constexpr uint32_t PLLR_MIN = 1; + static constexpr uint32_t PLLR_MAX = 128; + + static consteval bool is_valid_pll1p(uint32_t p) { + return p == PLLP_MIN || (p <= PLLP_MAX && p % 2 == 0); // 1 is bypass, then even numbers up to 128 + } + + static constexpr uint32_t PLL_IN_MIN = 1'000'000; + static constexpr uint32_t PLL_IN_MAX = 16'000'000; + + static constexpr uint32_t VCOH_MIN = 192'000'000; + static constexpr uint32_t VCOH_MAX = 836'000'000; + static constexpr uint32_t VCOL_MIN = 150'000'000; + static constexpr uint32_t VCOL_MAX = 420'000'000; + + static constexpr uint32_t SYSCLK_MAX = 550'000'000; + static constexpr uint32_t HCLK_MAX = 275'000'000; + + static constexpr uint32_t d1cpre_values[] = {1, 2, 4, 8, 16, 64, 128, 256, 512}; + + + struct ClockTree { + uint32_t hse_frequency = 0; + bool hse_bypass = false; + + uint32_t pll1_m = 0; + uint32_t pll1_n = 0; + uint32_t pll1_p = 0; + uint32_t pll1_q = 0; + uint32_t pll1_r = 0; + uint32_t pll1_fracn = 0; + + uint32_t pll2_m = 0; + uint32_t pll2_n = 0; + uint32_t pll2_p = 0; + uint32_t pll2_q = 0; + uint32_t pll2_r = 0; + uint32_t pll2_fracn = 0; + + uint32_t pll3_m = 0; + uint32_t pll3_n = 0; + uint32_t pll3_p = 0; + uint32_t pll3_q = 0; + uint32_t pll3_r = 0; + uint32_t pll3_fracn = 0; + + uint32_t d1cpre = 1; + uint32_t d2ppre1 = 2; + uint32_t d2ppre2 = 2; + + enum class Source : uint8_t { + HSI, HSE, CSI, PCLK1, PCLK2, PLL1Q, PLL1R, + PLL2P, PLL2Q, PLL2R, PLL3P, PLL3Q, PLL3R, + }; + Source spi123_src = Source::HSI; + Source spi45_src = Source::HSI; + Source spi6_src = Source::HSI; + Source adc_src = Source::HSI; + Source fdcan_src = Source::HSI; + Source sdmmc_src = Source::HSI; + }; + + static constexpr uint32_t find_rge(uint32_t pll_input) { + if (pll_input >= 1'000'000 && pll_input <= 2'000'000) return 0; + if (pll_input > 2'000'000 && pll_input <= 4'000'000) return 1; + if (pll_input > 4'000'000 && pll_input <= 8'000'000) return 2; + if (pll_input > 8'000'000 && pll_input <= 16'000'000) return 3; + return 0; + } + + static constexpr uint32_t flash_ws(uint32_t hclk) { + if (hclk <= 70'000'000) return 1; + if (hclk <= 140'000'000) return 2; + if (hclk <= 210'000'000) return 3; + return 4; + } + + static constexpr uint32_t pll1_input(const ClockTree& t) { return t.hse_frequency / t.pll1_m; } + static constexpr uint32_t pll2_input(const ClockTree& t) { return t.hse_frequency / t.pll2_m; } + static constexpr uint32_t pll3_input(const ClockTree& t) { return t.hse_frequency / t.pll3_m; } + + static constexpr uint32_t pll1_vco(const ClockTree& t) { + uint32_t in = pll1_input(t); + return in * t.pll1_n + in * t.pll1_fracn / 8192; + } + static constexpr uint32_t pll2_vco(const ClockTree& t) { + uint32_t in = pll2_input(t); + return in * t.pll2_n + in * t.pll2_fracn / 8192; + } + static constexpr uint32_t pll3_vco(const ClockTree& t) { + uint32_t in = pll3_input(t); + return in * t.pll3_n + in * t.pll3_fracn / 8192; + } + + static constexpr bool pll1_vco_wide(const ClockTree& t) { return pll1_input(t) >= 2'000'000; } + static constexpr uint32_t pll1_rge(const ClockTree& t) { return find_rge(pll1_input(t)); } + static constexpr uint32_t pll2_rge(const ClockTree& t) { return find_rge(pll2_input(t)); } + static constexpr uint32_t pll3_rge(const ClockTree& t) { return find_rge(pll3_input(t)); } + + static constexpr uint32_t sysclk(const ClockTree& t) { return pll1_vco(t) / t.pll1_p; } + static constexpr uint32_t hclk(const ClockTree& t) { return sysclk(t) / t.d1cpre; } + static constexpr uint32_t pclk1(const ClockTree& t) { return hclk(t) / t.d2ppre1; } + static constexpr uint32_t pclk2(const ClockTree& t) { return hclk(t) / t.d2ppre2; } + static constexpr uint32_t timer_apb1(const ClockTree& t) { return pclk1(t) * (t.d2ppre1 == 1 ? 1 : 2); } + static constexpr uint32_t timer_apb2(const ClockTree& t) { return pclk2(t) * (t.d2ppre2 == 1 ? 1 : 2); } + static constexpr uint32_t flash_latency(const ClockTree& t) { return flash_ws(hclk(t)); } + + static constexpr uint32_t source_frequency(const ClockTree& t, ClockTree::Source src) { + switch (src) { + case ClockTree::Source::HSI: return 64'000'000; + case ClockTree::Source::HSE: return t.hse_frequency; + case ClockTree::Source::CSI: return 4'000'000; + case ClockTree::Source::PCLK1: return pclk1(t); + case ClockTree::Source::PCLK2: return pclk2(t); + case ClockTree::Source::PLL1Q: return pll1_vco(t) / t.pll1_q; + case ClockTree::Source::PLL1R: return pll1_vco(t) / t.pll1_r; + case ClockTree::Source::PLL2P: return t.pll2_p != 0 ? pll2_vco(t) / t.pll2_p : 0; + case ClockTree::Source::PLL2Q: return t.pll2_q != 0 ? pll2_vco(t) / t.pll2_q : 0; + case ClockTree::Source::PLL2R: return t.pll2_r != 0 ? pll2_vco(t) / t.pll2_r : 0; + case ClockTree::Source::PLL3P: return t.pll3_p != 0 ? pll3_vco(t) / t.pll3_p : 0; + case ClockTree::Source::PLL3Q: return t.pll3_q != 0 ? pll3_vco(t) / t.pll3_q : 0; + case ClockTree::Source::PLL3R: return t.pll3_r != 0 ? pll3_vco(t) / t.pll3_r : 0; + default: break; + } + if consteval { + compile_error("Unknown clock source"); + } else { + PANIC("Unknown clock source"); + } + return 0; + } + + static constexpr uint32_t get_kernel_clock(const ClockTree& t, ClockGroup group) { + switch (group) { + case ClockGroup::SPI123_G: return source_frequency(t, t.spi123_src); + case ClockGroup::SPI45_G: return source_frequency(t, t.spi45_src); + case ClockGroup::SPI6_G: return source_frequency(t, t.spi6_src); + case ClockGroup::ADC_G: return source_frequency(t, t.adc_src); + case ClockGroup::FDCAN_G: return source_frequency(t, t.fdcan_src); + case ClockGroup::SDMMC_G: return source_frequency(t, t.sdmmc_src); + case ClockGroup::APB1_TIM_G: return timer_apb1(t); + case ClockGroup::APB2_TIM_G: return timer_apb2(t); + case ClockGroup::D1_BUS_G: return sysclk(t); + case ClockGroup::D2_BUS_G: return pclk1(t); + case ClockGroup::D3_BUS_G: return pclk2(t); + default: break; + } + if consteval { + compile_error("Unknown clock group"); + } else { + PANIC("Unknown clock group"); + } + return 0; + } + +/** + * ========================================= + * Domain Contract + * ========================================= + */ + + static constexpr std::size_t max_instances = 32; + + struct Entry { + ClockGroup group; + using TrySolveFn = bool (*)(uint32_t kernel_clk); + TrySolveFn try_solve; + }; + + struct Device { + ClockGroup group; + Entry::TrySolveFn try_solve; + + template + constexpr std::size_t inscribe(Ctx& ctx) const { + if (try_solve == nullptr) { + compile_error("ClockDomain::Device: try_solve function pointer is null"); + } + return ctx.template add(Entry{ + .group = group, + .try_solve = try_solve, + }, this); + } + }; + + static consteval void validate(const ClockTree& t, std::span entries) { + if (t.hse_frequency == 0) compile_error("HSE frequency is zero"); + if (t.pll1_m == 0) compile_error("PLL1M is zero"); + + uint32_t pll_in = pll1_input(t); + uint32_t vco = pll1_vco(t); + uint32_t sclk = sysclk(t); + uint32_t h_clk = hclk(t); + + if (sclk > SYSCLK_MAX) compile_error("SYSCLK exceeds maximum (550 MHz)"); + if (h_clk > HCLK_MAX) compile_error("HCLK exceeds maximum (275 MHz)"); + if (pll_in < PLL_IN_MIN || pll_in > PLL_IN_MAX) + compile_error("PLL1 input outside valid range (1-16 MHz)"); + if (t.pll1_m < PLLM_MIN || t.pll1_m > PLLM_MAX) compile_error("PLL1M out of range"); + if (t.pll1_n < PLLN_MIN || t.pll1_n > PLLN_MAX) compile_error("PLL1N out of range"); + if (!is_valid_pll1p(t.pll1_p)) compile_error("Invalid PLL1P divider"); + + if (pll1_vco_wide(t)) { + if (vco < VCOH_MIN || vco > VCOH_MAX) + compile_error("PLL1 VCO outside wide range (192-836 MHz)"); + } else { + if (vco < VCOL_MIN || vco > VCOL_MAX) + compile_error("PLL1 VCO outside medium range (150-420 MHz)"); + } + + bool d1cpre_valid = false; + for (auto d : d1cpre_values) { + if (t.d1cpre == d) { d1cpre_valid = true; break; } + } + if (!d1cpre_valid) compile_error("D1CPRE is not a valid divider"); + + // Peripheral requirements + for (size_t i = 0; i < entries.size(); i++) { + const auto& ent = entries[i]; + uint32_t ker = get_kernel_clock(t, ent.group); + if (ker == 0) compile_error("No kernel clock assigned for peripheral group"); + if (!ent.try_solve(ker)) + compile_error("Kernel clock does not satisfy peripheral requirements"); + } + + // PLL2/3 source consistency + if (t.spi123_src != ClockTree::Source::HSI) { + if (t.spi123_src == ClockTree::Source::PLL2P && (t.pll2_m == 0 || t.pll2_p == 0)) + compile_error("SPI123 uses PLL2P but PLL2 not configured"); + if (t.spi123_src == ClockTree::Source::PLL3P && (t.pll3_m == 0 || t.pll3_p == 0)) + compile_error("SPI123 uses PLL3P but PLL3 not configured"); + } + if (t.spi45_src != ClockTree::Source::HSI) { + if (t.spi45_src == ClockTree::Source::PLL2Q && (t.pll2_m == 0 || t.pll2_q == 0)) + compile_error("SPI45 uses PLL2Q but PLL2 not configured"); + if (t.spi45_src == ClockTree::Source::PLL3Q && (t.pll3_m == 0 || t.pll3_q == 0)) + compile_error("SPI45 uses PLL3Q but PLL3 not configured"); + } + if (t.spi6_src != ClockTree::Source::HSI) { + if (t.spi6_src == ClockTree::Source::PLL2P && (t.pll2_m == 0 || t.pll2_p == 0)) + compile_error("SPI6 uses PLL2P but PLL2 not configured"); + if (t.spi6_src == ClockTree::Source::PLL3P && (t.pll3_m == 0 || t.pll3_p == 0)) + compile_error("SPI6 uses PLL3P but PLL3 not configured"); + } + if (t.adc_src == ClockTree::Source::PLL2R && (t.pll2_m == 0 || t.pll2_r == 0)) + compile_error("ADC uses PLL2R but PLL2 not configured"); + if (t.adc_src == ClockTree::Source::PLL3R && (t.pll3_m == 0 || t.pll3_r == 0)) + compile_error("ADC uses PLL3R but PLL3 not configured"); + if (t.sdmmc_src == ClockTree::Source::PLL2R && (t.pll2_m == 0 || t.pll2_r == 0)) + compile_error("SDMMC uses PLL2R but PLL2 not configured"); + } + + template + static consteval void build(std::span entries, const ClockTree& tree) { + validate(tree, entries); + } + +#ifndef SIM_ON + + static constexpr uint32_t to_hal_rge(uint32_t rge) { + switch (rge) { + case 0: return RCC_PLL1VCIRANGE_0; + case 1: return RCC_PLL1VCIRANGE_1; + case 2: return RCC_PLL1VCIRANGE_2; + case 3: return RCC_PLL1VCIRANGE_3; + default: return RCC_PLL1VCIRANGE_0; + } + } + + static constexpr uint32_t to_hal_d1cpre(uint32_t d) { + switch (d) { + case 1: return RCC_SYSCLK_DIV1; + case 2: return RCC_SYSCLK_DIV2; + case 4: return RCC_SYSCLK_DIV4; + case 8: return RCC_SYSCLK_DIV8; + case 16: return RCC_SYSCLK_DIV16; + case 64: return RCC_SYSCLK_DIV64; + case 128: return RCC_SYSCLK_DIV128; + case 256: return RCC_SYSCLK_DIV256; + case 512: return RCC_SYSCLK_DIV512; + default: return RCC_SYSCLK_DIV1; + } + } + + static constexpr uint32_t to_hal_apb_pre(uint32_t d) { + switch (d) { + case 1: return RCC_APB1_DIV1; + case 2: return RCC_APB1_DIV2; + case 4: return RCC_APB1_DIV4; + case 8: return RCC_APB1_DIV8; + case 16: return RCC_APB1_DIV16; + default: return RCC_APB1_DIV2; + } + } + + static constexpr uint32_t to_hal_flash_latency(uint32_t ws) { + return (ws >= 1 && ws <= 4) ? (FLASH_LATENCY_0 + ws) : FLASH_LATENCY_4; + } + + static void apply_system_clocks(const ClockTree& t) { + RCC_OscInitTypeDef osc = {}; + RCC_ClkInitTypeDef clk = {}; + + HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY); + __HAL_PWR_VOLTAGESCALING_CONFIG( + sysclk(t) > 480'000'000 ? PWR_REGULATOR_VOLTAGE_SCALE0 + : PWR_REGULATOR_VOLTAGE_SCALE1 + ); + while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {} + + __HAL_RCC_PLL_PLLSOURCE_CONFIG(RCC_PLLSOURCE_HSE); + + osc.OscillatorType = RCC_OSCILLATORTYPE_HSI | RCC_OSCILLATORTYPE_LSI + | RCC_OSCILLATORTYPE_HSE; + osc.HSIState = RCC_HSI_DIV1; + osc.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; + osc.LSIState = RCC_LSI_ON; + osc.HSEState = t.hse_bypass ? RCC_HSE_BYPASS : RCC_HSE_ON; + + osc.PLL.PLLState = RCC_PLL_ON; + osc.PLL.PLLSource = RCC_PLLSOURCE_HSE; + osc.PLL.PLLM = t.pll1_m; + osc.PLL.PLLN = t.pll1_n; + osc.PLL.PLLP = t.pll1_p; + osc.PLL.PLLQ = t.pll1_q != 0 ? t.pll1_q : 1; + osc.PLL.PLLR = t.pll1_r != 0 ? t.pll1_r : 1; + osc.PLL.PLLRGE = to_hal_rge(pll1_rge(t)); + osc.PLL.PLLVCOSEL = pll1_vco_wide(t) ? RCC_PLL1VCOWIDE : RCC_PLL1VCOMEDIUM; + osc.PLL.PLLFRACN = t.pll1_fracn; + + if (HAL_RCC_OscConfig(&osc) != HAL_OK) { + PANIC("ClockDomain: HAL_RCC_OscConfig failed"); + } + + clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK + | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2 + | RCC_CLOCKTYPE_D3PCLK1 | RCC_CLOCKTYPE_D1PCLK1; + clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; + clk.SYSCLKDivider = to_hal_d1cpre(t.d1cpre); + clk.AHBCLKDivider = RCC_HCLK_DIV1; + clk.APB3CLKDivider = RCC_APB3_DIV2; + clk.APB1CLKDivider = to_hal_apb_pre(t.d2ppre1); + clk.APB2CLKDivider = to_hal_apb_pre(t.d2ppre2); + clk.APB4CLKDivider = RCC_APB4_DIV2; + + if (HAL_RCC_ClockConfig(&clk, to_hal_flash_latency(flash_latency(t))) != HAL_OK) { + PANIC("ClockDomain: HAL_RCC_ClockConfig failed"); + } + } + + static void apply_peripheral_clocks(const ClockTree& t) { + RCC_PeriphCLKInitTypeDef pclk = {}; + + // PLL2/PLL3 register config — HAL requires RCC_PERIPHCLK_* to enable each PLL block + if (t.pll2_m != 0) { + pclk.PeriphClockSelection |= RCC_PERIPHCLK_ADC; // triggers PLL2 register write + pclk.PLL2.PLL2M = t.pll2_m; + pclk.PLL2.PLL2N = t.pll2_n; + pclk.PLL2.PLL2P = t.pll2_p != 0 ? t.pll2_p : 1; + pclk.PLL2.PLL2Q = t.pll2_q != 0 ? t.pll2_q : 1; + pclk.PLL2.PLL2R = t.pll2_r != 0 ? t.pll2_r : 1; + pclk.PLL2.PLL2RGE = to_hal_rge(pll2_rge(t)); + pclk.PLL2.PLL2VCOSEL = RCC_PLL2VCOMEDIUM; + pclk.PLL2.PLL2FRACN = t.pll2_fracn; + } + if (t.pll3_m != 0) { + pclk.PeriphClockSelection |= RCC_PERIPHCLK_FDCAN; // triggers PLL3 register write + pclk.PLL3.PLL3M = t.pll3_m; + pclk.PLL3.PLL3N = t.pll3_n; + pclk.PLL3.PLL3P = t.pll3_p != 0 ? t.pll3_p : 1; + pclk.PLL3.PLL3Q = t.pll3_q != 0 ? t.pll3_q : 1; + pclk.PLL3.PLL3R = t.pll3_r != 0 ? t.pll3_r : 1; + pclk.PLL3.PLL3RGE = to_hal_rge(pll3_rge(t)); + pclk.PLL3.PLL3VCOSEL = RCC_PLL3VCOMEDIUM; + pclk.PLL3.PLL3FRACN = t.pll3_fracn; + } + + pclk.PeriphClockSelection |= RCC_PERIPHCLK_SPI1; + switch (t.spi123_src) { + case ClockTree::Source::PLL1Q: pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL; break; + case ClockTree::Source::PLL2P: pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL2; break; + case ClockTree::Source::PLL3P: pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL3; break; + case ClockTree::Source::HSI: pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_CLKP; break; + default: PANIC("ClockDomain: SPI123 clock source not supported"); break; + } + + pclk.PeriphClockSelection |= RCC_PERIPHCLK_SPI4; + switch (t.spi45_src) { + case ClockTree::Source::PCLK2: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_D2PCLK2; break; + case ClockTree::Source::PLL2Q: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_PLL2; break; + case ClockTree::Source::PLL3Q: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_PLL3; break; + case ClockTree::Source::HSI: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_HSI; break; + case ClockTree::Source::CSI: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_CSI; break; + case ClockTree::Source::HSE: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_HSE; break; + default: PANIC("ClockDomain: SPI45 clock source not supported"); break; + } + + pclk.PeriphClockSelection |= RCC_PERIPHCLK_SPI6; + switch (t.spi6_src) { + case ClockTree::Source::PCLK2: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_D3PCLK1; break; + case ClockTree::Source::PLL2P: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_PLL2; break; + case ClockTree::Source::PLL3P: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_PLL3; break; + case ClockTree::Source::HSI: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_HSI; break; + case ClockTree::Source::CSI: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_CSI; break; + case ClockTree::Source::HSE: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_HSE; break; + default: PANIC("ClockDomain: SPI6 clock source not supported"); break; + } + + pclk.PeriphClockSelection |= RCC_PERIPHCLK_SDMMC; + switch (t.sdmmc_src) { + case ClockTree::Source::PLL1Q: pclk.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL; break; + case ClockTree::Source::PLL2R: pclk.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL2; break; + default: PANIC("ClockDomain: SDMMC clock source not supported"); break; + } + __HAL_RCC_PLLCLKOUT_ENABLE(RCC_PLL1_DIVQ); + + // Enable PLL2/PLL3 outputs that peripherals depend on + if (t.spi123_src == ClockTree::Source::PLL2P || t.spi6_src == ClockTree::Source::PLL2P) + __HAL_RCC_PLL2CLKOUT_ENABLE(RCC_PLL2_DIVP); + if (t.spi45_src == ClockTree::Source::PLL2Q || t.fdcan_src == ClockTree::Source::PLL2Q) + __HAL_RCC_PLL2CLKOUT_ENABLE(RCC_PLL2_DIVQ); + if (t.adc_src == ClockTree::Source::PLL2R || t.sdmmc_src == ClockTree::Source::PLL2R) + __HAL_RCC_PLL2CLKOUT_ENABLE(RCC_PLL2_DIVR); + + if (t.spi123_src == ClockTree::Source::PLL3P || t.spi6_src == ClockTree::Source::PLL3P) + __HAL_RCC_PLL3CLKOUT_ENABLE(RCC_PLL3_DIVP); + if (t.spi45_src == ClockTree::Source::PLL3Q) + __HAL_RCC_PLL3CLKOUT_ENABLE(RCC_PLL3_DIVQ); + if (t.adc_src == ClockTree::Source::PLL3R) + __HAL_RCC_PLL3CLKOUT_ENABLE(RCC_PLL3_DIVR); + + pclk.PeriphClockSelection |= RCC_PERIPHCLK_ADC; + switch (t.adc_src) { + case ClockTree::Source::HSE: pclk.AdcClockSelection = RCC_ADCCLKSOURCE_CLKP; break; + case ClockTree::Source::PLL2R: pclk.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2; break; + case ClockTree::Source::PLL3R: pclk.AdcClockSelection = RCC_ADCCLKSOURCE_PLL3; break; + default: PANIC("ClockDomain: ADC clock source not supported"); break; + } + + pclk.PeriphClockSelection |= RCC_PERIPHCLK_FDCAN; + switch (t.fdcan_src) { + case ClockTree::Source::PLL2Q: pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL2; break; + case ClockTree::Source::PLL1Q: pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL; break; + case ClockTree::Source::HSE: pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE; break; + default: PANIC("ClockDomain: FDCAN clock source not supported"); break; + } + + if (pclk.PeriphClockSelection != 0) { + if (HAL_RCCEx_PeriphCLKConfig(&pclk) != HAL_OK) { + PANIC("ClockDomain: HAL_RCCEx_PeriphCLKConfig failed"); + } + } + } + +#endif // SIM_ON + + struct Init { + static inline void init(const ClockTree& tree) { +#ifndef SIM_ON + apply_system_clocks(tree); + apply_peripheral_clocks(tree); +#endif + } + }; +}; + +// ─── Default clock tree ─── + +#ifndef HSE_VALUE +#define HSE_VALUE 25'000'000 +#endif + +#if HSE_VALUE == 8'000'000 +inline constexpr ClockDomain::ClockTree default_clock_tree{ + .hse_frequency = 8'000'000, + .hse_bypass = true, + .pll1_m = 4, + .pll1_n = 275, + .pll1_p = 1, + .pll1_q = 4, + .pll1_r = 2, + .d1cpre = 2, +}; +#elif HSE_VALUE == 25'000'000 +inline constexpr ClockDomain::ClockTree default_clock_tree{ + .hse_frequency = 25'000'000, + .hse_bypass = false, + .pll1_m = 5, + .pll1_n = 110, + .pll1_p = 1, + .pll1_q = 4, + .pll1_r = 2, + .d1cpre = 2, +}; +#else +inline constexpr ClockDomain::ClockTree default_clock_tree{}; +#endif + +} // namespace ST_LIB From d9f3340f62bdaa41a7d0963009aeba7cd39c908a Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 03:43:37 +0200 Subject: [PATCH 02/21] feat(ClockDomain): Replace HALconfig with ClockDomain --- CMakeLists.txt | 1 - Inc/HALAL/HALAL.hpp | 3 +- Inc/HALAL/Models/HALconfig/HALconfig.hpp | 19 ---- Inc/ST-LIB.hpp | 15 ++- Src/HALAL/Models/HALconfig/Halconfig.cpp | 129 ----------------------- 5 files changed, 14 insertions(+), 153 deletions(-) delete mode 100644 Inc/HALAL/Models/HALconfig/HALconfig.hpp delete mode 100644 Src/HALAL/Models/HALconfig/Halconfig.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f4d629e3e..473c351a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,7 +273,6 @@ set(HALAL_C_NO_ETH set(HALAL_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/HALAL.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/DMA/DMA.cpp - ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/HALconfig/Halconfig.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/LowPowerTimer/LowPowerTimer.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MDMA/MDMA.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MPUManager/MPUManager.cpp diff --git a/Inc/HALAL/HALAL.hpp b/Inc/HALAL/HALAL.hpp index 9c3cf5fca..3eed7d228 100644 --- a/Inc/HALAL/HALAL.hpp +++ b/Inc/HALAL/HALAL.hpp @@ -1,10 +1,11 @@ #pragma once +#include "HALAL/Models/Clocks/ClockDomain.hpp" + #include "HALAL/Models/GPIO.hpp" #include "HALAL/Models/Pin.hpp" #include "HALAL/Models/DMA/DMA2.hpp" -#include "HALAL/Models/HALconfig/HALconfig.hpp" #include "HALAL/Services/DigitalInputService/DigitalInputService.hpp" #include "HALAL/Services/DigitalOutputService/DigitalOutputService.hpp" diff --git a/Inc/HALAL/Models/HALconfig/HALconfig.hpp b/Inc/HALAL/Models/HALconfig/HALconfig.hpp deleted file mode 100644 index d8ea1fd3b..000000000 --- a/Inc/HALAL/Models/HALconfig/HALconfig.hpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - * HALconfig.hpp - * - * Created on: 5 ene. 2023 - * Author: aleja - */ - -#pragma once - -#include "stm32h7xx_hal.h" -#include "ErrorHandler/ErrorHandler.hpp" - -enum TARGET { Nucleo, Board }; - -namespace HALconfig { -void system_clock(); -void peripheral_clock(); - -} // namespace HALconfig diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 7315e1cae..6e70770e2 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -88,6 +88,7 @@ template struct BuildCtx { }; using DomainsCtx = BuildCtx< + ClockDomain, MPUDomain, GPIODomain, TimerDomain, @@ -169,7 +170,7 @@ using ProtectionEngineForRequests = } // namespace BuildUtils -template struct Board { +template struct Board { public: using ProtectionEngine = BuildUtils::ProtectionEngineForRequests; @@ -225,6 +226,7 @@ template struct Board { // ... struct ConfigBundle { + ClockDomain::ClockTree clock_tree; std::array mpu_cfgs; std::array gpio_cfgs; std::array tim_cfgs; @@ -242,7 +244,11 @@ template struct Board { // ... }; + // Build: validate the tree against peripheral requirements + ClockDomain::build(ctx.template span(), ClockTreeDef); + return ConfigBundle{ + .clock_tree = ClockTreeDef, .mpu_cfgs = MPUDomain::template build(ctx.template span()), .gpio_cfgs = GPIODomain::template build(ctx.template span()), .tim_cfgs = TimerDomain::template build(ctx.template span()), @@ -273,6 +279,10 @@ template struct Board { static constexpr auto cfg = build(); + static constexpr auto& clock_tree() { + return cfg.clock_tree; + } + static void init() { constexpr std::size_t mpuN = domain_size(); constexpr std::size_t gpioN = domain_size(); @@ -298,8 +308,7 @@ template struct Board { FaultController::template install_runtime(); HAL_Init(); - HALconfig::system_clock(); - HALconfig::peripheral_clock(); + ClockDomain::Init::init(cfg.clock_tree); #ifdef HAL_RTC_MODULE_ENABLED (void)Global_RTC::ensure_started(); diff --git a/Src/HALAL/Models/HALconfig/Halconfig.cpp b/Src/HALAL/Models/HALconfig/Halconfig.cpp deleted file mode 100644 index aa1d334b2..000000000 --- a/Src/HALAL/Models/HALconfig/Halconfig.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Halconfig.cpp - * - * Created on: 5 ene. 2023 - * Author: aleja - */ - -#include "HALAL/Models/HALconfig/HALconfig.hpp" - -void HALconfig::system_clock() { - RCC_OscInitTypeDef RCC_OscInitStruct = {0}; - RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; - - HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY); - __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0); - - while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) { - } - - __HAL_RCC_PLL_PLLSOURCE_CONFIG(RCC_PLLSOURCE_HSE); - - RCC_OscInitStruct.OscillatorType = - RCC_OSCILLATORTYPE_HSI | RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_HSE; - RCC_OscInitStruct.HSIState = RCC_HSI_DIV1; - RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; - RCC_OscInitStruct.LSIState = RCC_LSI_ON; - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; - RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; - -#ifdef NUCLEO - RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS; - RCC_OscInitStruct.PLL.PLLM = 4; - RCC_OscInitStruct.PLL.PLLN = 275; - RCC_OscInitStruct.PLL.PLLP = 1; - RCC_OscInitStruct.PLL.PLLQ = 4; - RCC_OscInitStruct.PLL.PLLR = 2; - RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_1; - RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE; - RCC_OscInitStruct.PLL.PLLFRACN = 0; - -#else -#ifdef BOARD - RCC_OscInitStruct.HSEState = RCC_HSE_ON; - RCC_OscInitStruct.PLL.PLLM = 5; - RCC_OscInitStruct.PLL.PLLN = 110; - RCC_OscInitStruct.PLL.PLLP = 1; - RCC_OscInitStruct.PLL.PLLQ = 4; - RCC_OscInitStruct.PLL.PLLR = 2; - RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2; - RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE; - RCC_OscInitStruct.PLL.PLLFRACN = 0; -#else - static_assert(false, "No TARGET is choosen. Choose NUCLEO or BOARD"); -#endif -#endif - - if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { - PANIC("The RCC Osc config did not start correctly"); - } - - RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | - RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_D3PCLK1 | - RCC_CLOCKTYPE_D1PCLK1; - RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; - RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1; - RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2; - RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2; - RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2; - RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2; - RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2; - if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK) { - PANIC("The RCC clock config did not start correctly"); - } -} - -void HALconfig::peripheral_clock() { - RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; - -#ifdef NUCLEO - PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC | RCC_PERIPHCLK_FDCAN; - PeriphClkInitStruct.PLL2.PLL2M = 8; - PeriphClkInitStruct.PLL2.PLL2N = 200; - PeriphClkInitStruct.PLL2.PLL2P = 2; - PeriphClkInitStruct.PLL2.PLL2Q = 10; - PeriphClkInitStruct.PLL2.PLL2R = 2; - PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0; - PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOMEDIUM; - PeriphClkInitStruct.PLL2.PLL2FRACN = 0; - PeriphClkInitStruct.PLL3.PLL3M = 8; - PeriphClkInitStruct.PLL3.PLL3N = 192; - PeriphClkInitStruct.PLL3.PLL3P = 2; - PeriphClkInitStruct.PLL3.PLL3Q = 2; - PeriphClkInitStruct.PLL3.PLL3R = 2; - PeriphClkInitStruct.PLL3.PLL3RGE = RCC_PLL3VCIRANGE_0; - PeriphClkInitStruct.PLL3.PLL3VCOSEL = RCC_PLL3VCOMEDIUM; - PeriphClkInitStruct.PLL3.PLL3FRACN = 0; - PeriphClkInitStruct.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL2; - PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL3; -#else -#ifdef BOARD - PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC | RCC_PERIPHCLK_FDCAN; - PeriphClkInitStruct.PLL2.PLL2M = 25; - PeriphClkInitStruct.PLL2.PLL2N = 200; - PeriphClkInitStruct.PLL2.PLL2P = 2; - PeriphClkInitStruct.PLL2.PLL2Q = 10; - PeriphClkInitStruct.PLL2.PLL2R = 2; - PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0; - PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOMEDIUM; - PeriphClkInitStruct.PLL2.PLL2FRACN = 0; - PeriphClkInitStruct.PLL3.PLL3M = 25; - PeriphClkInitStruct.PLL3.PLL3N = 192; - PeriphClkInitStruct.PLL3.PLL3P = 2; - PeriphClkInitStruct.PLL3.PLL3Q = 2; - PeriphClkInitStruct.PLL3.PLL3R = 2; - PeriphClkInitStruct.PLL3.PLL3RGE = RCC_PLL3VCIRANGE_0; - PeriphClkInitStruct.PLL3.PLL3VCOSEL = RCC_PLL3VCOMEDIUM; - PeriphClkInitStruct.PLL3.PLL3FRACN = 0; - PeriphClkInitStruct.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL2; - PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL3; - -#else - static_assert(false, "No TARGET is choosen. Choose NUCLEO or BOARD"); -#endif -#endif - - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - PANIC("The RCCEx peripheral clock did not start correctly"); - } -} From 97eb2d560697cfab0488f39cc3c91384a0b35e5c Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 03:44:17 +0200 Subject: [PATCH 03/21] feat(SPI): Implement ClockDomain integration for SPI peripheral configuration --- Inc/HALAL/Models/SPI/SPI2.hpp | 123 +++++++++++++++++++++------------- Inc/ST-LIB.hpp | 3 +- 2 files changed, 80 insertions(+), 46 deletions(-) diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index 55c145b60..e4e0a2eee 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -15,11 +15,45 @@ #include "ErrorHandler/ErrorHandler.hpp" #include "HALAL/Models/DMA/DMA2.hpp" #include "HALAL/Models/SPI/SPIConfig.hpp" +#include "HALAL/Models/Clocks/ClockDomain.hpp" using ST_LIB::DMADomain; using ST_LIB::GPIODomain; using ST_LIB::SPIConfigTypes; +namespace ST_LIB { + +// ───────────────────────────────────────────── +// SPI clock model — prescaler search for the solver +// ───────────────────────────────────────────── + +template +struct SPIClockModel { + static_assert( + Group == ClockDomain::ClockGroup::SPI123_G || + Group == ClockDomain::ClockGroup::SPI45_G || + Group == ClockDomain::ClockGroup::SPI6_G, + "SPIClockModel: group must be SPI123, SPI45, or SPI6" + ); + + static constexpr auto group = Group; + + static constexpr uint32_t prescalers[] = {2, 4, 8, 16, 32, 64, 128, 256}; + static constexpr uint32_t prescaler_count = sizeof(prescalers) / sizeof(prescalers[0]); + + static consteval bool try_solve(uint32_t kernel_clk) { + for (uint32_t i = 0; i < prescaler_count; i++) { + uint32_t baud = kernel_clk / prescalers[i]; + if (baud <= MaxBaud && baud >= MinBaud) { + return true; + } + } + return false; + } +}; + +} // namespace ST_LIB + // Forward declaration of IRQ handlers and HAL callbacks extern "C" { void SPI1_IRQHandler(void); @@ -156,6 +190,16 @@ struct SPIDomain { // Forward declaration static uint32_t calculate_prescaler(uint32_t src_freq, uint32_t max_baud); + static constexpr ClockDomain::ClockGroup spi_group(SPIPeripheral p) { + using enum SPIPeripheral; + using enum ClockDomain::ClockGroup; + switch (p) { + case spi1: case spi2: case spi3: return SPI123_G; + case spi4: case spi5: return SPI45_G; + case spi6: return SPI6_G; + } + } + static constexpr std::size_t max_instances{6}; struct Entry { @@ -170,8 +214,9 @@ struct SPIDomain { std::size_t dma_rx_idx; std::size_t dma_tx_idx; - uint32_t max_baudrate; // Will set the baudrate as fast as possible under this value - SPIConfig config; // User-defined SPI configuration + uint32_t max_baudrate; + uint32_t min_baudrate; + SPIConfig config; }; struct Config { @@ -186,8 +231,9 @@ struct SPIDomain { std::size_t dma_rx_idx; std::size_t dma_tx_idx; - uint32_t max_baudrate; // Will set the baudrate as fast as possible under this value - SPIConfig config; // User-defined SPI configuration + uint32_t max_baudrate; + uint32_t min_baudrate; + SPIConfig config; }; /** @@ -200,8 +246,9 @@ struct SPIDomain { SPIPeripheral peripheral; SPIMode mode; - uint32_t max_baudrate; // Will set the baudrate as fast as possible under this value - SPIConfig config; // User-defined SPI configuration + uint32_t max_baudrate; + uint32_t min_baudrate; + SPIConfig config; GPIODomain::GPIO sck_gpio; GPIODomain::GPIO miso_gpio; @@ -214,13 +261,14 @@ struct SPIDomain { SPIMode mode, SPIPeripheral peripheral, uint32_t max_baudrate, + uint32_t min_baudrate, GPIODomain::Pin sck_pin, GPIODomain::Pin miso_pin, GPIODomain::Pin mosi_pin, GPIODomain::Pin nss_pin, SPIConfig config = SPIConfig{} ) - : peripheral{peripheral}, mode{mode}, max_baudrate{max_baudrate}, config{config}, + : peripheral{peripheral}, mode{mode}, max_baudrate{max_baudrate}, min_baudrate{min_baudrate}, config{config}, sck_gpio( sck_pin, GPIODomain::OperationMode::ALT_PP, @@ -267,12 +315,13 @@ struct SPIDomain { SPIMode mode, SPIPeripheral peripheral, uint32_t max_baudrate, + uint32_t min_baudrate, GPIODomain::Pin sck_pin, GPIODomain::Pin miso_pin, GPIODomain::Pin mosi_pin, SPIConfig config ) - : peripheral{peripheral}, mode{mode}, max_baudrate{max_baudrate}, config{config}, + : peripheral{peripheral}, mode{mode}, max_baudrate{max_baudrate}, min_baudrate{min_baudrate}, config{config}, sck_gpio( sck_pin, GPIODomain::OperationMode::ALT_PP, @@ -327,8 +376,18 @@ struct SPIDomain { e.dma_rx_idx = dma_indices[0]; e.dma_tx_idx = dma_indices[1]; e.max_baudrate = max_baudrate; + e.min_baudrate = min_baudrate; e.config = config; + // Register clock requirement with ClockDomain (don't need the index) + using Model = SPIClockModel; + auto clock_device = ClockDomain::Device{ + .group = Model::group, + .try_solve = &Model::try_solve, + .owner = this, + }; + clock_device.inscribe(ctx); + return ctx.template add(e, this); } @@ -1285,6 +1344,7 @@ struct SPIDomain { cfgs[i].dma_rx_idx = entries[i].dma_rx_idx; cfgs[i].dma_tx_idx = entries[i].dma_tx_idx; cfgs[i].max_baudrate = entries[i].max_baudrate; + cfgs[i].min_baudrate = entries[i].min_baudrate; cfgs[i].config = entries[i].config; auto peripheral = entries[i].peripheral; @@ -1331,7 +1391,8 @@ struct SPIDomain { static void init( std::span cfgs, std::span gpio_instances, - std::span dma_peripherals + std::span dma_peripherals, + const ClockDomain::ClockTree& tree ) { for (std::size_t i = 0; i < N; ++i) { const auto& e = cfgs[i]; @@ -1342,55 +1403,24 @@ struct SPIDomain { instances[i].error_count = 0; instances[i].was_aborted = false; - // Configure clock and store handle - RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; + // Enable bus clock (mux configured centrally by ClockDomain) uint8_t spi_number = 0; if (peripheral == SPIPeripheral::spi1) { - PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI1; - PeriphClkInitStruct.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL; - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - PANIC("Unable to configure SPI1 clock"); - } __HAL_RCC_SPI1_CLK_ENABLE(); spi_number = 1; } else if (peripheral == SPIPeripheral::spi2) { - PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI2; - PeriphClkInitStruct.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL; - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - PANIC("Unable to configure SPI2 clock"); - } __HAL_RCC_SPI2_CLK_ENABLE(); spi_number = 2; } else if (peripheral == SPIPeripheral::spi3) { - PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI3; - PeriphClkInitStruct.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL; - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - PANIC("Unable to configure SPI3 clock"); - } __HAL_RCC_SPI3_CLK_ENABLE(); spi_number = 3; } else if (peripheral == SPIPeripheral::spi4) { - PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI4; - PeriphClkInitStruct.Spi45ClockSelection = RCC_SPI45CLKSOURCE_HSI; - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - PANIC("Unable to configure SPI4 clock"); - } __HAL_RCC_SPI4_CLK_ENABLE(); spi_number = 4; } else if (peripheral == SPIPeripheral::spi5) { - PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI5; - PeriphClkInitStruct.Spi45ClockSelection = RCC_SPI45CLKSOURCE_HSI; - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - PANIC("Unable to configure SPI5 clock"); - } __HAL_RCC_SPI5_CLK_ENABLE(); spi_number = 5; } else if (peripheral == SPIPeripheral::spi6) { - PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI6; - PeriphClkInitStruct.Spi6ClockSelection = RCC_SPI6CLKSOURCE_PLL2; - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - PANIC("Unable to configure SPI6 clock"); - } __HAL_RCC_SPI6_CLK_ENABLE(); spi_number = 6; } @@ -1427,15 +1457,18 @@ struct SPIDomain { auto& init = hspi.Init; if (e.mode == SPIMode::MASTER) { init.Mode = SPI_MODE_MASTER; - // Baudrate prescaler calculation + // Baudrate prescaler from solved tree uint32_t pclk_freq; if (peripheral == SPIPeripheral::spi1 || peripheral == SPIPeripheral::spi2 || peripheral == SPIPeripheral::spi3) { - pclk_freq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI123); + pclk_freq = ClockDomain::source_frequency(tree, tree.spi123_src); } else if (peripheral == SPIPeripheral::spi4 || peripheral == SPIPeripheral::spi5) { - pclk_freq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI45); + pclk_freq = ClockDomain::source_frequency(tree, tree.spi45_src); } else { - pclk_freq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI6); + pclk_freq = ClockDomain::source_frequency(tree, tree.spi6_src); + } + if (pclk_freq == 0) { + PANIC("SPI kernel clock not configured by ClockDomain"); } init.BaudRatePrescaler = calculate_prescaler(pclk_freq, e.max_baudrate); } else { diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 6e70770e2..ea2fae196 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -321,7 +321,8 @@ template stru SPIDomain::Init::init( cfg.spi_cfgs, GPIODomain::Init::instances, - DMADomain::Init::instances + DMADomain::Init::instances, + cfg.clock_tree ); DigitalOutputDomain::Init::init(cfg.dout_cfgs, GPIODomain::Init::instances); DigitalInputDomain::Init::init(cfg.din_cfgs, GPIODomain::Init::instances); From 752742bca87546017752d16a24ad7122fa8a5b3f Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 03:44:36 +0200 Subject: [PATCH 04/21] feat(ADC): Implement ClockDomain integration for ADC peripheral configuration --- Inc/HALAL/Services/ADC/ADC.hpp | 111 ++++++++++++++++++++++----------- Inc/ST-LIB.hpp | 3 +- 2 files changed, 78 insertions(+), 36 deletions(-) diff --git a/Inc/HALAL/Services/ADC/ADC.hpp b/Inc/HALAL/Services/ADC/ADC.hpp index 8f6dbc920..34ddb1bb4 100644 --- a/Inc/HALAL/Services/ADC/ADC.hpp +++ b/Inc/HALAL/Services/ADC/ADC.hpp @@ -9,6 +9,7 @@ #include "ErrorHandler/ErrorHandler.hpp" #include "HALAL/Models/DMA/DMA2.hpp" +#include "HALAL/Models/Clocks/ClockDomain.hpp" #include "HALAL/Models/GPIO.hpp" #include "HALAL/Models/Pin.hpp" @@ -60,21 +61,6 @@ struct ADCDomain { CYCLES_810_5 = ADC_SAMPLETIME_810CYCLES_5, }; - enum class ClockPrescaler : uint32_t { - DIV1 = ADC_CLOCK_ASYNC_DIV1, - DIV2 = ADC_CLOCK_ASYNC_DIV2, - DIV4 = ADC_CLOCK_ASYNC_DIV4, - DIV6 = ADC_CLOCK_ASYNC_DIV6, - DIV8 = ADC_CLOCK_ASYNC_DIV8, - DIV10 = ADC_CLOCK_ASYNC_DIV10, - DIV12 = ADC_CLOCK_ASYNC_DIV12, - DIV16 = ADC_CLOCK_ASYNC_DIV16, - DIV32 = ADC_CLOCK_ASYNC_DIV32, - DIV64 = ADC_CLOCK_ASYNC_DIV64, - DIV128 = ADC_CLOCK_ASYNC_DIV128, - DIV256 = ADC_CLOCK_ASYNC_DIV256, - }; - enum class Channel : uint32_t { AUTO = 0xFFFFFFFFu, CH0 = ADC_CHANNEL_0, @@ -109,11 +95,58 @@ struct ADCDomain { Channel channel; Resolution resolution; SampleTime sample_time; - ClockPrescaler prescaler; + uint32_t sample_rate_hz; float* output; }; + template + struct ADCClockModel { + static constexpr auto group = ClockDomain::ClockGroup::ADC_G; + + static constexpr uint32_t prescalers[] = + {1, 2, 4, 6, 8, 10, 12, 16, 32, 64, 128, 256}; + static constexpr uint32_t ADC_CLK_MIN = 500'000; + + static consteval bool try_solve(uint32_t kernel_clk) { + for (uint32_t p : prescalers) { + uint32_t adc_clk = kernel_clk / p; + if (adc_clk >= ADC_CLK_MIN && adc_clk <= MaxADCCLK) + return true; + } + return false; + } + }; + + static constexpr uint32_t adc_max_clk(Resolution res) { + switch (res) { + case Resolution::BITS_16: return 8'333'333; + case Resolution::BITS_14: return 16'666'666; + case Resolution::BITS_12: return 36'000'000; + case Resolution::BITS_10: return 50'000'000; + case Resolution::BITS_8: return 50'000'000; + } + return 36'000'000; + } + + static constexpr uint32_t prescaler_to_hal(uint32_t p) { + switch (p) { + case 1: return ADC_CLOCK_ASYNC_DIV1; + case 2: return ADC_CLOCK_ASYNC_DIV2; + case 4: return ADC_CLOCK_ASYNC_DIV4; + case 6: return ADC_CLOCK_ASYNC_DIV6; + case 8: return ADC_CLOCK_ASYNC_DIV8; + case 10: return ADC_CLOCK_ASYNC_DIV10; + case 12: return ADC_CLOCK_ASYNC_DIV12; + case 16: return ADC_CLOCK_ASYNC_DIV16; + case 32: return ADC_CLOCK_ASYNC_DIV32; + case 64: return ADC_CLOCK_ASYNC_DIV64; + case 128: return ADC_CLOCK_ASYNC_DIV128; + case 256: return ADC_CLOCK_ASYNC_DIV256; + default: return ADC_CLOCK_ASYNC_DIV4; + } + } + struct ADC { GPIODomain::GPIO gpio; using domain = ADCDomain; @@ -125,7 +158,6 @@ struct ADCDomain { float& output, Resolution resolution = Resolution::BITS_12, SampleTime sample_time = SampleTime::CYCLES_8_5, - ClockPrescaler prescaler = ClockPrescaler::DIV1, uint32_t sample_rate_hz = 0, Peripheral peripheral = Peripheral::AUTO, Channel channel = Channel::AUTO @@ -137,7 +169,6 @@ struct ADCDomain { .channel = channel, .resolution = resolution, .sample_time = sample_time, - .prescaler = prescaler, .sample_rate_hz = sample_rate_hz, .output = &output} {} @@ -145,7 +176,6 @@ struct ADCDomain { const GPIODomain::Pin& pin, Resolution resolution = Resolution::BITS_12, SampleTime sample_time = SampleTime::CYCLES_8_5, - ClockPrescaler prescaler = ClockPrescaler::DIV1, uint32_t sample_rate_hz = 0, Peripheral peripheral = Peripheral::AUTO, Channel channel = Channel::AUTO @@ -157,7 +187,6 @@ struct ADCDomain { .channel = channel, .resolution = resolution, .sample_time = sample_time, - .prescaler = prescaler, .sample_rate_hz = sample_rate_hz, .output = nullptr} {} @@ -168,14 +197,12 @@ struct ADCDomain { float& output, Resolution resolution = Resolution::BITS_12, SampleTime sample_time = SampleTime::CYCLES_8_5, - ClockPrescaler prescaler = ClockPrescaler::DIV1, uint32_t sample_rate_hz = 0 ) : ADC(pin, output, resolution, sample_time, - prescaler, sample_rate_hz, peripheral, channel) {} @@ -186,10 +213,9 @@ struct ADCDomain { Channel channel, Resolution resolution = Resolution::BITS_12, SampleTime sample_time = SampleTime::CYCLES_8_5, - ClockPrescaler prescaler = ClockPrescaler::DIV1, uint32_t sample_rate_hz = 0 ) - : ADC(pin, resolution, sample_time, prescaler, sample_rate_hz, peripheral, channel) {} + : ADC(pin, resolution, sample_time, sample_rate_hz, peripheral, channel) {} template consteval std::size_t inscribe(Ctx& ctx) const { const auto gpio_idx = gpio.inscribe(ctx); @@ -198,6 +224,15 @@ struct ADCDomain { const auto resolved = resolve_mapping(entry); entry.peripheral = resolved.first; entry.channel = resolved.second; + + // Register clock requirement with ClockDomain + using Model = ADCClockModel; + auto clock_device = ClockDomain::Device{ + .group = Model::group, + .try_solve = &Model::try_solve, + }; + clock_device.inscribe(ctx); + return ctx.template add(entry, this); } }; @@ -210,7 +245,7 @@ struct ADCDomain { Channel channel; Resolution resolution; SampleTime sample_time; - ClockPrescaler prescaler; + uint32_t sample_rate_hz; uint32_t dma_request; float* output; @@ -552,7 +587,6 @@ struct ADCDomain { array cfgs{}; array periph_seen{}; array periph_resolution{}; - array periph_prescaler{}; array periph_rate{}; array periph_counts{}; @@ -578,15 +612,11 @@ struct ADCDomain { if (!periph_seen[pidx]) { periph_seen[pidx] = true; periph_resolution[pidx] = e.resolution; - periph_prescaler[pidx] = e.prescaler; periph_rate[pidx] = e.sample_rate_hz; } else { if (periph_resolution[pidx] != e.resolution) { compile_error("ADC: resolution mismatch on same peripheral"); } - if (periph_prescaler[pidx] != e.prescaler) { - compile_error("ADC: prescaler mismatch on same peripheral"); - } if (periph_rate[pidx] != e.sample_rate_hz) { compile_error("ADC: sample rate mismatch on same peripheral"); } @@ -614,7 +644,6 @@ struct ADCDomain { .channel = channel, .resolution = e.resolution, .sample_time = e.sample_time, - .prescaler = e.prescaler, .sample_rate_hz = e.sample_rate_hz, .dma_request = dma_request(peripheral), .output = e.output, @@ -856,7 +885,7 @@ struct ADCDomain { return &dma_buffer_pool[buffer_offset + index]; } - static void configure_peripheral(const Config& cfg, uint8_t channel_count) { + static void configure_peripheral(const Config& cfg, uint8_t channel_count, const ClockDomain::ClockTree& tree) { ADC_HandleTypeDef* hadc = handle_for(cfg.peripheral); if (cfg.peripheral == Peripheral::ADC_1 || cfg.peripheral == Peripheral::ADC_2) { @@ -869,7 +898,18 @@ struct ADCDomain { HAL_SYSCFG_AnalogSwitchConfig(SYSCFG_SWITCH_PC3, SYSCFG_SWITCH_PC3_OPEN); } - hadc->Init.ClockPrescaler = static_cast(cfg.prescaler); + uint32_t kernel_clk = ClockDomain::source_frequency(tree, tree.adc_src); + uint32_t max_clk = adc_max_clk(cfg.resolution); + uint32_t prescaler = 4; + constexpr uint32_t prescalers[] = {1, 2, 4, 6, 8, 10, 12, 16, 32, 64, 128, 256}; + for (uint32_t p : prescalers) { + uint32_t adc_clk = kernel_clk / p; + if (adc_clk >= 500'000 && adc_clk <= max_clk) { + prescaler = p; + break; + } + } + hadc->Init.ClockPrescaler = prescaler_to_hal(prescaler); hadc->Init.Resolution = static_cast(cfg.resolution); hadc->Init.ScanConvMode = (channel_count > 1U) ? ADC_SCAN_ENABLE : ADC_SCAN_DISABLE; hadc->Init.EOCSelection = (channel_count > 1U) ? ADC_EOC_SEQ_CONV : ADC_EOC_SINGLE_CONV; @@ -893,7 +933,8 @@ struct ADCDomain { static void init( std::span runtime_cfgs, std::span gpio_instances = std::span{}, - std::span dma_peripherals = std::span{} + std::span dma_peripherals = std::span{}, + const ClockDomain::ClockTree& tree = {} ) { std::array periph_ready{false, false, false}; std::array periph_channel_counts{0, 0, 0}; @@ -952,7 +993,7 @@ struct ADCDomain { hadc->DMA_Handle = &dma_instance->dma; dma_instance->dma.Parent = hadc; - configure_peripheral(*first_cfg, channel_count); + configure_peripheral(*first_cfg, channel_count, tree); if (HAL_ADC_Init(hadc) != HAL_OK) { PANIC("ADC Init failed"); diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index ea2fae196..168dacd70 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -339,7 +339,8 @@ template stru ADCDomain::Init::init( cfg.adc_cfgs, GPIODomain::Init::instances, - DMADomain::Init::instances + DMADomain::Init::instances, + cfg.clock_tree ); EXTIDomain::Init::init(cfg.exti_cfgs, GPIODomain::Init::instances); From cfc4e90659fe25bcce14cfa651852ec5ba079735 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 03:45:18 +0200 Subject: [PATCH 05/21] feat(Timer): Implement ClockDomain integration for TimerDomain diff --git c/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp i/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 680bf82b..4c09a80d 100644 --- c/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ i/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -18,6 +18,7 @@ #include #include "ErrorHandler/ErrorHandler.hpp" +#include "HALAL/Models/Clocks/ClockDomain.hpp" #ifndef SCHEDULER_TIMER_DOMAIN /* default is tim2 */ @@ -293,6 +294,7 @@ struct TimerDomain { struct Config { uint8_t timer_idx; + TimerRequest request; SelectionTrigger1 trgo1; SelectionTrigger2 trgo2; }; @@ -639,6 +641,7 @@ struct TimerDomain { Config cfg = { .timer_idx = timer_idxmap[reqint], + .request = requests[i].request, .trgo1 = requests[i].trgo1, .trgo2 = requests[i].trgo2 }; @@ -672,7 +675,7 @@ struct TimerDomain { uint8_t reqint = remaining_32bit_timers[count_32bit_requests]; Config cfg = - {.timer_idx = timer_idxmap[reqint], .trgo1 = e.trgo1, .trgo2 = e.trgo2}; + {.timer_idx = timer_idxmap[reqint], .request = e.request, .trgo1 = e.trgo1, .trgo2 = e.trgo2}; cfgs[cfg_idx++] = cfg; // unordered remove @@ -719,7 +722,7 @@ struct TimerDomain { ST_LIB::compile_error("This only processes TimerRequest::AnyGeneralPurpose"); } uint8_t reqint = remaining_timers[i]; - Config cfg = {.timer_idx = timer_idxmap[reqint], .trgo1 = e.trgo1, .trgo2 = e.trgo2}; + Config cfg = {.timer_idx = timer_idxmap[reqint], .request = e.request, .trgo1 = e.trgo1, .trgo2 = e.trgo2}; cfgs[cfg_idx++] = cfg; } @@ -732,8 +735,27 @@ struct TimerDomain { TIM_HandleTypeDef* hal_tim; TIM_MasterConfigTypeDef master{}; uint8_t timer_idx; + uint32_t clock_frequency = 0; // from ClockTree }; + static constexpr bool is_timer_on_apb1(TimerRequest r) { + switch (r) { + case TimerRequest::GeneralPurpose32bit_2: + case TimerRequest::GeneralPurpose_3: + case TimerRequest::GeneralPurpose_4: + case TimerRequest::GeneralPurpose32bit_5: + case TimerRequest::Basic_6: + case TimerRequest::Basic_7: + case TimerRequest::SlaveTimer_12: + case TimerRequest::SlaveTimer_13: + case TimerRequest::SlaveTimer_14: + case TimerRequest::GeneralPurpose32bit_23: + case TimerRequest::GeneralPurpose32bit_24: + return true; + default: return false; + } + } + static void (*callbacks[TimerDomain::max_instances])(void*); static void* callback_data[TimerDomain::max_instances]; @@ -742,7 +764,7 @@ struct TimerDomain { static void TIM_Default_Callback(void* raw) { (void)raw; } - static void init(std::span cfgs) { + static void init(std::span cfgs, const ClockDomain::ClockTree& tree) { Scheduler_global_timer = cmsis_timers[timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; rcc_enable_timer(Scheduler_global_timer); @@ -802,6 +824,8 @@ struct TimerDomain { inst->tim = tim; inst->hal_tim = handle; inst->timer_idx = e.timer_idx; + inst->clock_frequency = is_timer_on_apb1(e.request) + ? ClockDomain::timer_apb1(tree) : ClockDomain::timer_apb2(tree); TIM_MasterConfigTypeDef sMasterConfig = {}; sMasterConfig.MasterOutputTrigger = static_cast(e.trgo1); sMasterConfig.MasterOutputTrigger2 = static_cast(e.trgo2); diff --git c/Inc/HALAL/Services/Time/TimerWrapper.hpp i/Inc/HALAL/Services/Time/TimerWrapper.hpp index 051849bb..d41878cf 100644 --- c/Inc/HALAL/Services/Time/TimerWrapper.hpp +++ i/Inc/HALAL/Services/Time/TimerWrapper.hpp @@ -135,19 +135,10 @@ template struct TimerWrapper { } inline uint32_t get_clock_frequency() { - uint32_t result; - if constexpr (this->is_on_APB1) { - result = HAL_RCC_GetPCLK1Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) != RCC_HCLK_DIV1) { - result *= 2; - } - } else { - result = HAL_RCC_GetPCLK2Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { - result *= 2; - } + if (instance->clock_frequency == 0) { + PANIC("Timer clock not configured by ClockDomain"); } - return result; + return instance->clock_frequency; } template diff --git c/Inc/ST-LIB.hpp i/Inc/ST-LIB.hpp index 168dacd7..e65ec552 100644 --- c/Inc/ST-LIB.hpp +++ i/Inc/ST-LIB.hpp @@ -316,7 +316,7 @@ public: MPUDomain::Init::init(); GPIODomain::Init::init(cfg.gpio_cfgs); - TimerDomain::Init::init(cfg.tim_cfgs); + TimerDomain::Init::init(cfg.tim_cfgs, cfg.clock_tree); DMADomain::Init::init(cfg.dma_cfgs); SPIDomain::Init::init( cfg.spi_cfgs, --- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 30 ++++++++++++++++++-- Inc/HALAL/Services/Time/TimerWrapper.hpp | 15 ++-------- Inc/ST-LIB.hpp | 2 +- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 680bf82bf..4c09a80da 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -18,6 +18,7 @@ #include #include "ErrorHandler/ErrorHandler.hpp" +#include "HALAL/Models/Clocks/ClockDomain.hpp" #ifndef SCHEDULER_TIMER_DOMAIN /* default is tim2 */ @@ -293,6 +294,7 @@ struct TimerDomain { struct Config { uint8_t timer_idx; + TimerRequest request; SelectionTrigger1 trgo1; SelectionTrigger2 trgo2; }; @@ -639,6 +641,7 @@ struct TimerDomain { Config cfg = { .timer_idx = timer_idxmap[reqint], + .request = requests[i].request, .trgo1 = requests[i].trgo1, .trgo2 = requests[i].trgo2 }; @@ -672,7 +675,7 @@ struct TimerDomain { uint8_t reqint = remaining_32bit_timers[count_32bit_requests]; Config cfg = - {.timer_idx = timer_idxmap[reqint], .trgo1 = e.trgo1, .trgo2 = e.trgo2}; + {.timer_idx = timer_idxmap[reqint], .request = e.request, .trgo1 = e.trgo1, .trgo2 = e.trgo2}; cfgs[cfg_idx++] = cfg; // unordered remove @@ -719,7 +722,7 @@ struct TimerDomain { ST_LIB::compile_error("This only processes TimerRequest::AnyGeneralPurpose"); } uint8_t reqint = remaining_timers[i]; - Config cfg = {.timer_idx = timer_idxmap[reqint], .trgo1 = e.trgo1, .trgo2 = e.trgo2}; + Config cfg = {.timer_idx = timer_idxmap[reqint], .request = e.request, .trgo1 = e.trgo1, .trgo2 = e.trgo2}; cfgs[cfg_idx++] = cfg; } @@ -732,8 +735,27 @@ struct TimerDomain { TIM_HandleTypeDef* hal_tim; TIM_MasterConfigTypeDef master{}; uint8_t timer_idx; + uint32_t clock_frequency = 0; // from ClockTree }; + static constexpr bool is_timer_on_apb1(TimerRequest r) { + switch (r) { + case TimerRequest::GeneralPurpose32bit_2: + case TimerRequest::GeneralPurpose_3: + case TimerRequest::GeneralPurpose_4: + case TimerRequest::GeneralPurpose32bit_5: + case TimerRequest::Basic_6: + case TimerRequest::Basic_7: + case TimerRequest::SlaveTimer_12: + case TimerRequest::SlaveTimer_13: + case TimerRequest::SlaveTimer_14: + case TimerRequest::GeneralPurpose32bit_23: + case TimerRequest::GeneralPurpose32bit_24: + return true; + default: return false; + } + } + static void (*callbacks[TimerDomain::max_instances])(void*); static void* callback_data[TimerDomain::max_instances]; @@ -742,7 +764,7 @@ struct TimerDomain { static void TIM_Default_Callback(void* raw) { (void)raw; } - static void init(std::span cfgs) { + static void init(std::span cfgs, const ClockDomain::ClockTree& tree) { Scheduler_global_timer = cmsis_timers[timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; rcc_enable_timer(Scheduler_global_timer); @@ -802,6 +824,8 @@ struct TimerDomain { inst->tim = tim; inst->hal_tim = handle; inst->timer_idx = e.timer_idx; + inst->clock_frequency = is_timer_on_apb1(e.request) + ? ClockDomain::timer_apb1(tree) : ClockDomain::timer_apb2(tree); TIM_MasterConfigTypeDef sMasterConfig = {}; sMasterConfig.MasterOutputTrigger = static_cast(e.trgo1); sMasterConfig.MasterOutputTrigger2 = static_cast(e.trgo2); diff --git a/Inc/HALAL/Services/Time/TimerWrapper.hpp b/Inc/HALAL/Services/Time/TimerWrapper.hpp index 051849bb4..d41878cf9 100644 --- a/Inc/HALAL/Services/Time/TimerWrapper.hpp +++ b/Inc/HALAL/Services/Time/TimerWrapper.hpp @@ -135,19 +135,10 @@ template struct TimerWrapper { } inline uint32_t get_clock_frequency() { - uint32_t result; - if constexpr (this->is_on_APB1) { - result = HAL_RCC_GetPCLK1Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) != RCC_HCLK_DIV1) { - result *= 2; - } - } else { - result = HAL_RCC_GetPCLK2Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { - result *= 2; - } + if (instance->clock_frequency == 0) { + PANIC("Timer clock not configured by ClockDomain"); } - return result; + return instance->clock_frequency; } template diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 168dacd70..e65ec5523 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -316,7 +316,7 @@ template stru MPUDomain::Init::init(); GPIODomain::Init::init(cfg.gpio_cfgs); - TimerDomain::Init::init(cfg.tim_cfgs); + TimerDomain::Init::init(cfg.tim_cfgs, cfg.clock_tree); DMADomain::Init::init(cfg.dma_cfgs); SPIDomain::Init::init( cfg.spi_cfgs, From 204334818c70434a709f768519645f023021bbf6 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 03:45:40 +0200 Subject: [PATCH 06/21] feat(SD): Implement ClockDomain integration for SDMMC peripheral --- Inc/ST-LIB.hpp | 3 +- Inc/ST-LIB_LOW/Sd/Sd.hpp | 75 ++++++++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index e65ec5523..485c83fec 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -333,7 +333,8 @@ template stru SdDomain::Init::init( cfg.sd_cfgs, MPUDomain::Init::instances, - DigitalInputDomain::Init::instances + DigitalInputDomain::Init::instances, + cfg.clock_tree ); EthernetDomain::Init::init(cfg.eth_cfgs, DigitalOutputDomain::Init::instances); ADCDomain::Init::init( diff --git a/Inc/ST-LIB_LOW/Sd/Sd.hpp b/Inc/ST-LIB_LOW/Sd/Sd.hpp index 6f38bc045..edcccd7ef 100644 --- a/Inc/ST-LIB_LOW/Sd/Sd.hpp +++ b/Inc/ST-LIB_LOW/Sd/Sd.hpp @@ -21,6 +21,7 @@ #include "HALAL/Models/Pin.hpp" #include "HALAL/Models/MPU.hpp" #include "ST-LIB_LOW/DigitalInput2.hpp" +#include "HALAL/Models/Clocks/ClockDomain.hpp" using ST_LIB::DigitalInputDomain; using ST_LIB::GPIODomain; @@ -31,6 +32,26 @@ extern void* g_sdmmc1_instance_ptr; extern void* g_sdmmc2_instance_ptr; namespace ST_LIB { + +// SDMMC clock model — validates that a kernel clock can produce a valid +// SDMMC clock (sdmmc_ker_ck / (2 × CLKDIV)) in [MinFreq, MaxFreq]. +template +struct SDClockModel { + static constexpr auto group = ClockDomain::ClockGroup::SDMMC_G; + + static constexpr uint32_t CLKDIV_MIN = 0; + static constexpr uint32_t CLKDIV_MAX = 1023; + + static consteval bool try_solve(uint32_t kernel_clk) { + for (uint32_t div = CLKDIV_MIN; div <= CLKDIV_MAX; div++) { + uint32_t sdmmc_clk = div == 0 ? kernel_clk : kernel_clk / (2 * div); + if (sdmmc_clk >= MinFreq && sdmmc_clk <= MaxFreq) + return true; + } + return false; + } +}; + struct SdDomain { enum class Peripheral : uint32_t { @@ -40,6 +61,8 @@ struct SdDomain { struct Entry { Peripheral peripheral; + uint32_t max_freq; + uint32_t min_freq; std::size_t mpu_buffer0_idx; std::size_t mpu_buffer1_idx; std::optional> @@ -95,9 +118,13 @@ struct SdDomain { card_detect_config, std::optional> write_protect_config, + uint32_t max_freq = 50'000'000, + uint32_t min_freq = 1'000'000, GPIODomain::Pin d0_pin_for_sdmmc1 = ST_LIB::PC8 ) - : e{.peripheral = sdmmc_peripheral}, peripheral(sdmmc_peripheral), + : e{.peripheral = sdmmc_peripheral, + .max_freq = max_freq, + .min_freq = min_freq}, peripheral(sdmmc_peripheral), buffer0(MPUDomain::Buffer>( MPUDomain::MemoryType::NonCached, MPUDomain::MemoryDomain::D1 @@ -227,6 +254,14 @@ struct SdDomain { local_e.d2_pin_idx = d2.inscribe(ctx); local_e.d3_pin_idx = d3.inscribe(ctx); + // Register clock requirement with ClockDomain + using Model = SDClockModel; + auto clock_device = ClockDomain::Device{ + .group = Model::group, + .try_solve = &Model::try_solve, + }; + clock_device.inscribe(ctx); + return ctx.template add(local_e, this); } }; @@ -235,6 +270,8 @@ struct SdDomain { struct Config { Peripheral peripheral; + uint32_t max_freq; + uint32_t min_freq; std::size_t mpu_buffer0_idx; std::size_t mpu_buffer1_idx; std::optional> cd_pin_idx; @@ -265,6 +302,8 @@ struct SdDomain { peripheral_used[peripheral_index] = true; cfgs[i].peripheral = e.peripheral; + cfgs[i].max_freq = e.max_freq; + cfgs[i].min_freq = e.min_freq; cfgs[i].mpu_buffer0_idx = e.mpu_buffer0_idx; cfgs[i].mpu_buffer1_idx = e.mpu_buffer1_idx; cfgs[i].cd_pin_idx = e.cd_pin_idx; @@ -450,7 +489,8 @@ struct SdDomain { static void init( std::span cfgs, std::span mpu_buffer_instances, - std::span digital_input_instances + std::span digital_input_instances, + const ClockDomain::ClockTree& tree ) { if (N == 0) { @@ -486,16 +526,15 @@ struct SdDomain { inst.hsd.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE; inst.hsd.Init.BusWide = SDMMC_BUS_WIDE_4B; inst.hsd.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_ENABLE; - uint32_t target_freq = 50000000; // Target frequency 50 MHz + uint32_t target_freq = cfg.max_freq; #ifdef SD_DEBUG_ENABLE - inst.hsd.Init.BusWide = SDMMC_BUS_WIDE_1B; // For debugging, use 1-bit bus - target_freq = 400000; // For debugging, use 400 kHz -#endif // SD_DEBUG_ENABLE - - PLL1_ClocksTypeDef pll1_clock; - HAL_RCCEx_GetPLL1ClockFreq(&pll1_clock); - uint32_t sdmmc_clk = pll1_clock.PLL1_Q_Frequency; + inst.hsd.Init.BusWide = SDMMC_BUS_WIDE_1B; +#endif + uint32_t sdmmc_clk = ClockDomain::source_frequency(tree, tree.sdmmc_src); + if (sdmmc_clk == 0) { + PANIC("SDMMC clock not configured by ClockDomain"); + } uint32_t target_div = (sdmmc_clk + target_freq - 1) / target_freq; // Target divider rounded up (target_freq is the maximum frequency) @@ -525,22 +564,6 @@ struct SdDomain { inst.card_initialized = false; inst.current_buffer = BufferSelect::Buffer0; } - - // Initialize HAL SD - RCC_PeriphCLKInitTypeDef RCC_PeriphCLKInitStruct; - RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SDMMC; - RCC_PeriphCLKInitStruct.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL; - if (HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct) != HAL_OK) { - PANIC("SDMMC clock configuration failed, maybe try with a slower clock or " - "higher divider?"); - } - - // Ensure PLL1Q output is enabled - __HAL_RCC_PLLCLKOUT_ENABLE(RCC_PLL1_DIVQ); - - if (HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SDMMC) == 0) { - PANIC("SDMMC clock frequency is 0"); - } } }; }; From cee4db6df8121b8aa9e0af5e5cdcafdb8085e830 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 03:46:06 +0200 Subject: [PATCH 07/21] chore(CI): Fix tests to work with new clock tree --- Tests/adc_sensor_test.cpp | 10 ------ Tests/adc_test.cpp | 35 ------------------- .../board_protection_contract.cpp | 2 +- Tests/spi2_test.cpp | 4 ++- 4 files changed, 4 insertions(+), 47 deletions(-) diff --git a/Tests/adc_sensor_test.cpp b/Tests/adc_sensor_test.cpp index 907bb246b..194b5c9df 100644 --- a/Tests/adc_sensor_test.cpp +++ b/Tests/adc_sensor_test.cpp @@ -40,7 +40,6 @@ constexpr std::array single_adc1_init_cfgs{{ .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &adc_sensor_template_output_0}, @@ -52,7 +51,6 @@ constexpr std::array split_adc12_init_cfgs{{ .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &adc_sensor_template_output_0}, @@ -61,7 +59,6 @@ constexpr std::array split_adc12_init_cfgs{{ .channel = ST_LIB::ADCDomain::Channel::CH2, .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC2, .output = &adc_sensor_template_output_1}, @@ -113,7 +110,6 @@ TEST_F(ADCSensorTest, LinearSensorUsesNormalizedADCVoltageForItsTransferFunction .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_10, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &output}, @@ -136,7 +132,6 @@ TEST_F(ADCSensorTest, FilteredLinearSensorReusesTheSameADCConversionPath) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &output}, @@ -164,7 +159,6 @@ TEST_F(ADCSensorTest, LookupSensorMapsEquivalentNormalizedReadingsAcrossResoluti .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = nullptr}, @@ -173,7 +167,6 @@ TEST_F(ADCSensorTest, LookupSensorMapsEquivalentNormalizedReadingsAcrossResoluti .channel = ST_LIB::ADCDomain::Channel::CH2, .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC2, .output = nullptr}, @@ -210,7 +203,6 @@ TEST_F(ADCSensorTest, PT100ReadsFromADCVoltageAndSupportsFilteredMode) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &direct_output}, @@ -243,7 +235,6 @@ TEST_F(ADCSensorTest, NTCUsesNormalizedADCCountsAcrossResolutions) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = nullptr}, @@ -252,7 +243,6 @@ TEST_F(ADCSensorTest, NTCUsesNormalizedADCCountsAcrossResolutions) { .channel = ST_LIB::ADCDomain::Channel::CH2, .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC2, .output = nullptr}, diff --git a/Tests/adc_test.cpp b/Tests/adc_test.cpp index 88b113c4f..358f7b818 100644 --- a/Tests/adc_test.cpp +++ b/Tests/adc_test.cpp @@ -61,7 +61,6 @@ constexpr std::array auto_entry{{ .channel = ST_LIB::ADCDomain::Channel::AUTO, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .output = &compile_time_output}, }}; @@ -80,7 +79,6 @@ constexpr std::array auto_pf13_entry{{ .channel = ST_LIB::ADCDomain::Channel::AUTO, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .output = &compile_time_output}, }}; @@ -97,7 +95,6 @@ constexpr std::array auto_pc0_16bit_entry{{ .channel = ST_LIB::ADCDomain::Channel::AUTO, .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .output = &compile_time_output}, }}; @@ -142,7 +139,6 @@ constexpr std::array shared_adc_cfgs{{ .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &synthesized_dma_output_0}, @@ -151,7 +147,6 @@ constexpr std::array shared_adc_cfgs{{ .channel = ST_LIB::ADCDomain::Channel::CH15, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &synthesized_dma_output_1}, @@ -248,7 +243,6 @@ constexpr std::array single_adc1_init_cfgs{{ .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &adc_test_template_output_0}, @@ -260,7 +254,6 @@ constexpr std::array shared_adc1_init_cfgs{{ .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &adc_test_template_output_0}, @@ -269,7 +262,6 @@ constexpr std::array shared_adc1_init_cfgs{{ .channel = ST_LIB::ADCDomain::Channel::CH15, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &adc_test_template_output_1}, @@ -281,7 +273,6 @@ constexpr std::array split_adc12_init_cfgs{{ .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &adc_test_template_output_0}, @@ -290,7 +281,6 @@ constexpr std::array split_adc12_init_cfgs{{ .channel = ST_LIB::ADCDomain::Channel::CH2, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC2, .output = &adc_test_template_output_1}, @@ -372,7 +362,6 @@ TEST_F(ADCTest, InitWithExternalDMAStartsCircularTransferAndLinksHandle) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &output}, @@ -408,7 +397,6 @@ TEST_F(ADCTest, ReadUsesLatestDMABufferValueWithoutPolling) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &output}, @@ -442,7 +430,6 @@ TEST_F(ADCTest, MultiChannelDMAUsesSequenceSlotsPerPeripheral) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &out0}, @@ -451,7 +438,6 @@ TEST_F(ADCTest, MultiChannelDMAUsesSequenceSlotsPerPeripheral) { .channel = ST_LIB::ADCDomain::Channel::CH15, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &out1}, @@ -488,7 +474,6 @@ TEST_F(ADCTest, SeparatePeripheralsUseIndependentDMAHandlesAndBuffers) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &out1}, @@ -497,7 +482,6 @@ TEST_F(ADCTest, SeparatePeripheralsUseIndependentDMAHandlesAndBuffers) { .channel = ST_LIB::ADCDomain::Channel::CH2, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC2, .output = &out2}, @@ -528,7 +512,6 @@ TEST_F(ADCTest, Resolution10BitDMAClampsRawBufferToResolutionRange) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_10, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &output}, @@ -554,7 +537,6 @@ TEST_F(ADCTest, InitWithoutDMAInstancesFailsInsteadOfConfiguringDMAAtRuntime) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &output}, @@ -579,7 +561,6 @@ TEST_F(ADCTest, DMAStartFailureTriggersErrorPathAndLeavesInstanceUnreadable) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &output}, @@ -604,7 +585,6 @@ TEST_F(ADCTest, UnresolvedConfigDoesNotAliasAResolvedPeripheralInstance) { .channel = ST_LIB::ADCDomain::Channel::AUTO, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &unresolved}, @@ -613,7 +593,6 @@ TEST_F(ADCTest, UnresolvedConfigDoesNotAliasAResolvedPeripheralInstance) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &resolved}, @@ -636,7 +615,6 @@ TEST_F(ADCTest, TimedDMAWaitsForSequenceAndTransferCompletionBeforeUpdatingBuffe .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &output}, @@ -684,7 +662,6 @@ TEST_F(ADCTest, TimedDMADetectsOverrunWhenTransferCannotKeepUp) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_1_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &out0}, @@ -693,7 +670,6 @@ TEST_F(ADCTest, TimedDMADetectsOverrunWhenTransferCannotKeepUp) { .channel = ST_LIB::ADCDomain::Channel::CH15, .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_1_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &out1}, @@ -727,7 +703,6 @@ TEST_F(ADCTest, TimedDMASharedBusContentionShowsUpWithSimultaneousADCs) { .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_1_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &out1}, @@ -736,7 +711,6 @@ TEST_F(ADCTest, TimedDMASharedBusContentionShowsUpWithSimultaneousADCs) { .channel = ST_LIB::ADCDomain::Channel::CH2, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_1_5, - .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC2, .output = &out2}, @@ -780,18 +754,11 @@ TEST_F(ADCTest, TimedDMAFrequencySweepSeparatesStableAndUnstableOperatingRegions ST_LIB::ADCDomain::SampleTime::CYCLES_32_5, ST_LIB::ADCDomain::SampleTime::CYCLES_387_5, }; - constexpr std::array prescalers{ - ST_LIB::ADCDomain::ClockPrescaler::DIV1, - ST_LIB::ADCDomain::ClockPrescaler::DIV4, - ST_LIB::ADCDomain::ClockPrescaler::DIV16, - }; for (const auto resolution : resolutions) { for (const auto sample_time : sample_times) { - for (const auto prescaler : prescalers) { SCOPED_TRACE(static_cast(resolution)); SCOPED_TRACE(static_cast(sample_time)); - SCOPED_TRACE(static_cast(prescaler)); reset_runtime_state(); @@ -802,7 +769,6 @@ TEST_F(ADCTest, TimedDMAFrequencySweepSeparatesStableAndUnstableOperatingRegions .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = resolution, .sample_time = sample_time, - .prescaler = prescaler, .sample_rate_hz = 0, .dma_request = DMA_REQUEST_ADC1, .output = &output}, @@ -835,7 +801,6 @@ TEST_F(ADCTest, TimedDMAFrequencySweepSeparatesStableAndUnstableOperatingRegions ST_LIB::MockedHAL::adc_advance_time_ns(unstable_period_ns * 8ULL); EXPECT_GT(ST_LIB::MockedHAL::adc_get_overrun_count(ADC1), 0U); EXPECT_LT(ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1), 8U); - } } } } diff --git a/Tests/compile_checks/board_protection_contract.cpp b/Tests/compile_checks/board_protection_contract.cpp index c49f0fa8c..1bb0e0198 100644 --- a/Tests/compile_checks/board_protection_contract.cpp +++ b/Tests/compile_checks/board_protection_contract.cpp @@ -7,7 +7,7 @@ float protected_value = 1.0f; inline constexpr auto protected_value_protection = Protections::protection<"protected_value", protected_value>(Protections::Rules::above(10.0f)); -using ContractBoard = ST_LIB::Board; +using ContractBoard = ST_LIB::Board; static_assert(ContractBoard::ProtectionEngine::protection_count == 1); static_assert(std::same_as< diff --git a/Tests/spi2_test.cpp b/Tests/spi2_test.cpp index 1c44348e7..4a14ab8ea 100644 --- a/Tests/spi2_test.cpp +++ b/Tests/spi2_test.cpp @@ -125,10 +125,12 @@ class SPI2Test : public ::testing::Test { .config = config}, }}; + auto tree = ST_LIB::ClockDomain::ClockTree{}; ST_LIB::SPIDomain::Init<1>::init( cfgs, std::span{}, - std::span(ST_LIB::DMADomain::Init<2>::instances) + std::span(ST_LIB::DMADomain::Init<2>::instances), + tree ); return ST_LIB::SPIDomain::Init<1>::instances[0]; } From 7b95f8252196dbfbf91f056f2fa5737866f00786 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 03:46:26 +0200 Subject: [PATCH 08/21] chore(CI): Add ClockDomain tests --- Tests/CMakeLists.txt | 2 + Tests/Clocks/clock_group_test.cpp | 100 +++++++++++++++++++++++++++++ Tests/Clocks/clock_stress_test.cpp | 90 ++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 Tests/Clocks/clock_group_test.cpp create mode 100644 Tests/Clocks/clock_stress_test.cpp diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index f2e81e4d4..855e46496 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -38,6 +38,8 @@ add_executable(${STLIB_TEST_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/spi2_test.cpp ${CMAKE_CURRENT_LIST_DIR}/dma2_test.cpp ${CMAKE_CURRENT_LIST_DIR}/dfsdm_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/Clocks/clock_group_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/Clocks/clock_stress_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/common_tests.cpp ) diff --git a/Tests/Clocks/clock_group_test.cpp b/Tests/Clocks/clock_group_test.cpp new file mode 100644 index 000000000..52bc20df1 --- /dev/null +++ b/Tests/Clocks/clock_group_test.cpp @@ -0,0 +1,100 @@ +#include "HALAL/Models/Clocks/ClockDomain.hpp" +#include "HALAL/Models/SPI/SPI2.hpp" + +using namespace ST_LIB; + +namespace { +constexpr ClockDomain::ClockTree tree_8m{ + .hse_frequency = 8'000'000, + .hse_bypass = true, + .pll1_m = 4, + .pll1_n = 275, + .pll1_p = 1, + .pll1_q = 4, + .pll1_r = 2, + .d1cpre = 2, +}; +} // namespace + +static_assert([] { + using SpiModel = SPIClockModel; + + auto tree = tree_8m; + tree.spi123_src = ClockDomain::ClockTree::Source::HSI; + + std::array entries{{ + {.group = SpiModel::group, .try_solve = &SpiModel::try_solve}, + }}; + ClockDomain::validate(tree, std::span{entries}); + return true; +}()); + +static_assert([] { + using SpiModel = SPIClockModel; + + auto tree = tree_8m; + tree.spi45_src = ClockDomain::ClockTree::Source::HSI; + + std::array entries{{ + {.group = SpiModel::group, .try_solve = &SpiModel::try_solve}, + }}; + ClockDomain::validate(tree, std::span{entries}); + return true; +}()); + +static_assert([] { + using SpiA = SPIClockModel; + using SpiB = SPIClockModel; + + auto tree = tree_8m; + tree.spi45_src = ClockDomain::ClockTree::Source::HSI; + + std::array entries{{ + {.group = SpiA::group, .try_solve = &SpiA::try_solve}, + {.group = SpiB::group, .try_solve = &SpiB::try_solve}, + }}; + ClockDomain::validate(tree, std::span{entries}); + return true; +}()); + +static_assert([] { + using SpiA = SPIClockModel; + using SpiB = SPIClockModel; + + auto tree = tree_8m; + tree.spi123_src = ClockDomain::ClockTree::Source::HSI; + tree.spi45_src = ClockDomain::ClockTree::Source::PCLK2; + + std::array entries{{ + {.group = SpiA::group, .try_solve = &SpiA::try_solve}, + {.group = SpiB::group, .try_solve = &SpiB::try_solve}, + }}; + ClockDomain::validate(tree, std::span{entries}); + return true; +}()); + +static_assert([] { + auto tree = tree_8m; + std::array entries{}; + ClockDomain::validate(tree, std::span{entries}); + return true; +}()); + +static_assert([] { + using Spi123 = SPIClockModel; + using Spi45 = SPIClockModel; + using Spi6 = SPIClockModel; + + auto tree = tree_8m; + tree.spi123_src = ClockDomain::ClockTree::Source::HSI; + tree.spi45_src = ClockDomain::ClockTree::Source::HSI; + tree.spi6_src = ClockDomain::ClockTree::Source::HSI; + + std::array entries{{ + {.group = Spi123::group, .try_solve = &Spi123::try_solve}, + {.group = Spi45::group, .try_solve = &Spi45::try_solve}, + {.group = Spi6::group, .try_solve = &Spi6::try_solve}, + }}; + ClockDomain::validate(tree, std::span{entries}); + return true; +}()); diff --git a/Tests/Clocks/clock_stress_test.cpp b/Tests/Clocks/clock_stress_test.cpp new file mode 100644 index 000000000..aabac51fc --- /dev/null +++ b/Tests/Clocks/clock_stress_test.cpp @@ -0,0 +1,90 @@ +#include "HALAL/Models/Clocks/ClockDomain.hpp" +#include "HALAL/Models/SPI/SPI2.hpp" + +using namespace ST_LIB; + +namespace { +constexpr ClockDomain::ClockTree tree_8m{ + .hse_frequency = 8'000'000, + .hse_bypass = true, + .pll1_m = 4, + .pll1_n = 275, + .pll1_p = 1, + .pll1_q = 4, + .pll1_r = 2, + .d1cpre = 2, +}; +} // namespace + +static_assert([] { + using Spi = SPIClockModel; + return Spi::try_solve(64'000'000); +}()); + +static_assert([] { + using Spi = SPIClockModel; + return Spi::try_solve(64'000'000) && + !Spi::try_solve(4'000'000); +}()); + +static_assert([] { + using Spi = SPIClockModel; + return Spi::try_solve(64'000'000) && + !Spi::try_solve(30'000'000); +}()); + +static_assert([] { + using SpiA = SPIClockModel; + using SpiB = SPIClockModel; + + auto tree = tree_8m; + tree.spi45_src = ClockDomain::ClockTree::Source::PCLK2; + + std::array entries{{ + {.group = SpiA::group, .try_solve = &SpiA::try_solve}, + {.group = SpiB::group, .try_solve = &SpiB::try_solve}, + }}; + ClockDomain::validate(tree, std::span{entries}); + return true; +}()); + +static_assert([] { + using Spi1 = SPIClockModel; + using Spi4 = SPIClockModel; + using Spi6 = SPIClockModel; + + auto tree = tree_8m; + tree.spi123_src = ClockDomain::ClockTree::Source::HSI; + tree.spi45_src = ClockDomain::ClockTree::Source::HSI; + tree.spi6_src = ClockDomain::ClockTree::Source::HSI; + + std::array entries{{ + {.group = Spi1::group, .try_solve = &Spi1::try_solve}, + {.group = Spi4::group, .try_solve = &Spi4::try_solve}, + {.group = Spi6::group, .try_solve = &Spi6::try_solve}, + }}; + ClockDomain::validate(tree, std::span{entries}); + return true; +}()); + +static_assert([] { + using Spi = SPIClockModel; + return !Spi::try_solve(64'000'000) && + !Spi::try_solve(4'000'000) && + !Spi::try_solve(8'000'000) && + !Spi::try_solve(137'500'000); +}()); + +static_assert([] { + using Spi = SPIClockModel; + + auto tree = tree_8m; + tree.spi123_src = ClockDomain::ClockTree::Source::HSI; + + std::array entries; + for (size_t i = 0; i < 10; i++) { + entries[i] = {.group = Spi::group, .try_solve = &Spi::try_solve}; + } + ClockDomain::validate(tree, std::span{entries}); + return true; +}()); From 36cef8beca72edd5c7bdbf64beaf7d22bc529273 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 03:54:36 +0200 Subject: [PATCH 09/21] docs(ClockDomain): Add ClockDomain documentation and update contract --- docs/clockdomain.md | 143 ++++++++++++++++++++++++++++++++++ docs/st-lib-board-contract.md | 21 ++++- 2 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 docs/clockdomain.md diff --git a/docs/clockdomain.md b/docs/clockdomain.md new file mode 100644 index 000000000..c736c478b --- /dev/null +++ b/docs/clockdomain.md @@ -0,0 +1,143 @@ +# ClockDomain Contract + +This document defines the contract of +[`Inc/HALAL/Models/Clocks/ClockDomain.hpp`](../Inc/HALAL/Models/Clocks/ClockDomain.hpp). + +## 1. Architecture + +ClockDomain owns the entire clock configuration of the MCU. The clock tree is a precomputed artifact produced by the host tool (or hand-written). The firmware validates it at compile time and applies it at runtime. There is no solver in the firmware. + +```mermaid +flowchart LR + HT[Host Tool] -->|generates| CT[ClockTree] + CT -->|compile-time| V[validate] + V -->|pass| R[apply_*] + R --> HAL[HAL registers] + V -->|fail| CE[compile error] +``` + +- **Host tool**: takes peripheral requirements → produces a complete `ClockTree` with all PLL parameters, bus prescalers, and peripheral source assignments. +- **Firmware (`validate`)**: checks all PLL ranges, bus clock consistency, and every peripheral's `try_solve` against its assigned kernel clock. Refuses to compile on failure. +- **Firmware (`apply_*`)**: writes the validated tree to HAL registers at runtime. + +## 2. ClockTree + +`ClockTree` stores **only decisions**. Everything derivable from those decisions is computed via static accessor functions. + +| Storage | Accessor | +| ------------------------------------------ | ------------------------------------------------------- | +| `pll1_m`, `pll1_n`, `pll1_p`, `pll1_fracn` | `pll1_vco(t)`, `pll1_input(t)`, `pll1_rge(t)` | +| `pll1_q`, `pll1_r` | `source_frequency(t, PLL1Q)` | +| `d1cpre`, `d2ppre1`, `d2ppre2` | `sysclk(t)`, `hclk(t)`, `pclk1/2(t)`, `timer_apb1/2(t)` | +| `spi123_src`, `spi45_src`, … | `source_frequency(t, src)` → kernel clock | +| `adc_src`, `fdcan_src`, `sdmmc_src` | same | +| `pll2_m/n/p/q/r/fracn` | `pll2_vco(t)`, `pll2_rge(t)` + per-output frequencies | +| `pll3_m/n/p/q/r/fracn` | same for PLL3 | + +No peripheral reads a clock frequency from the tree directly — they call `ClockDomain::source_frequency(t, t.spi123_src)`, `ClockDomain::timer_apb1(t)`, etc. + +## 3. Domain Contract + +ClockDomain follows the standard ST-LIB domain contract defined in +[`st-lib-board-contract.md`](st-lib-board-contract.md). + +### 3.1 Entry + +```cpp +struct Entry { + ClockGroup group; + TrySolveFn try_solve; // bool (*)(uint32_t kernel_clk) +}; +``` + +Every `Entry` represents a peripheral that needs a kernel clock. `try_solve` returns true if the +given kernel clock frequency can satisfy the peripheral's requirements (e.g. can a valid baudrate +be derived via prescaler). + +`try_solve` is **mandatory** — no null pointers. Every entry in ClockDomain is a real clock +requirement. + +### 3.2 Device + +Other domains use `ClockDomain::Device` to inscribe their clock requirements: + +Example (SPI): + +```cpp +using Model = SPIClockModel; +auto clock_device = ClockDomain::Device{ + .group = Model::group, + .try_solve = &Model::try_solve, +}; +clock_device.inscribe(ctx); +``` + +### 3.3 build() — Validation + +```cpp +template +static consteval void build( + std::span entries, + const ClockTree& tree +); +``` + +Called by `Board::build()`. Validates: +- All PLL parameters in range, inputs in range. +- `sysclk`/`hclk`/`pclk`/timers consistent and within chip maximums. +- `d1cpre` is a valid divider value. +- Every peripheral's `try_solve(get_kernel_clock(tree, group))` returns true. +- PLL2/PLL3 outputs are correctly configured when a source references them. + +Failure calls `compile_error()` which aborts compilation. + +Peripheral source-to-frequency consistency is guaranteed by construction (all frequencies are derived from the source via `source_frequency`). + +### 3.4 Init — Runtime Application + +```cpp +struct Init { + static void init(const ClockTree& tree); +}; +``` + +Writes the validated `ClockTree` to STM32 HAL registers: +- `apply_system_clocks(tree)` — configures PLL1, system clocks, flash latency, voltage scaling. +- `apply_peripheral_clocks(tree)` — configures all peripheral kernel clock muxes and enables PLL output lines. + +Peripheral bus clock gates (`__HAL_RCC_*_CLK_ENABLE`) are handled by the respective peripheral domains — they're per-instance wiring, not clock architecture. + +## 4. Clock Models + +Peripheral domains define clock models that implement `try_solve`. Models validate that a candidate kernel clock can satisfy the peripheral's requirements. + +| Peripheral | Model | Checks | +| ---------- | ---------------------------------------- | ------------------------------------------------------------------- | +| SPI | `SPIClockModel` | `∃ prescaler ∈ {2,4,…,256}: kernel/prescaler ∈ [MinBaud, MaxBaud]` | +| ADC | `ADCClockModel` | `∃ prescaler ∈ {1,2,…,256}: kernel/prescaler ∈ [0.5MHz, MaxADCCLK]` | +| SDMMC | `SDClockModel` | `∃ CLKDIV ∈ [0,1023]: kernel/(2·CLKDIV) ∈ [MinFreq, MaxFreq]` | + +Models are instantiated in the peripheral's `inscribe()` via the device's configuration (baudrate, resolution, target frequency). + +## 5. Board Integration + +`Board` takes the `ClockTree` as a mandatory second template parameter. Users who don't have a host-tool-generated tree can use `default_clock_tree` (hardcoded for 8 MHz or 25 MHz HSE with 550 MHz SYSCLK). + +```cpp +using Board = ST_LIB::Board< + ST_LIB::DefaultFaultPolicy, + my_clock_tree, // or ST_LIB::default_clock_tree + spi_device, led, timer +>; +``` + +`Board::build()` calls `ClockDomain::build(span, tree)` which runs `validate()`. + +## 6. Adding a New Peripheral Clock Requirement + +1. Define a model (like `SPIClockModel`) with `group`, `try_solve`, and any needed prescaler checking logic. +2. In the peripheral's `Device::inscribe`, call `ClockDomain::Device{...}.inscribe(ctx)` with the model's `group` and `try_solve`. +3. Add the new `ClockGroup` value to the `ClockGroup` enum. +4. Add the group → source mapping in `get_kernel_clock()`. +5. Add the source to the `ClockTree` struct and its mux case to `apply_peripheral_clocks()`. +6. Add any PLL output enable calls in `apply_peripheral_clocks()` if the source is a PLL output. diff --git a/docs/st-lib-board-contract.md b/docs/st-lib-board-contract.md index 0575a8a95..d0b81d80c 100644 --- a/docs/st-lib-board-contract.md +++ b/docs/st-lib-board-contract.md @@ -7,14 +7,13 @@ If you change a domain, add a new domain, or add a cross-domain composition rule ## 1. Mental Model -`Board` is a compile-time build pipeline plus a runtime init pipeline. +`Board` is a compile-time build pipeline plus a runtime init pipeline. - Compile time decides what exists and how it must be configured. - Runtime only materializes already-built configurations and links HAL handles. `Board` is intentionally declarative. Request objects describe intent; domains convert that intent -into concrete configs. The first template argument is not a request object: it is the global fault -runtime policy type used by `FaultController`. +into concrete configs. The first template argument is the global fault runtime policy type used by `FaultController`. The second is a precomputed `ClockDomain::ClockTree`. Protection declarations are also request objects. They do not inscribe hardware into `BuildCtx`; instead, `Board` filters them into a board-specific `ProtectionEngine` type. @@ -32,6 +31,22 @@ That type must expose: `FaultPolicy<...>`, `FaultPolicyNoMachine<...>`, and `DefaultFaultPolicy` are the intended public helpers for this contract. +## 1.2 Board Clock Tree + +The second template argument of `Board` must be a `ClockDomain::ClockTree`. + +```cpp +template struct Board { ... } +``` + +- Use `ST_LIB::default_clock_tree` (hardcoded for 8 MHz or 25 MHz HSE, 550 MHz SYSCLK) if no host-tool-generated tree is available. +- `Board::build()` passes the tree to `ClockDomain::build()` for compile-time validation. +- `Board::init()` passes the tree to `ClockDomain::Init::init()` for runtime HAL configuration. +- Domains that depend on clock frequencies (Timer, SPI, ADC, SDMMC) receive the tree in their own + `Init::init()`. + +See [`clockdomain.md`](clockdomain.md) for the full ClockDomain contract. + ## 2. Domain Contract Every domain used by `BuildCtx` and `Board` must provide: From 020b2d4faa414ec76c73c856b9141f5b1096c0dd Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 03:56:12 +0200 Subject: [PATCH 10/21] style: Run pre-commit --- Inc/HALAL/Models/Clocks/ClockDomain.hpp | 526 ++++++++++++------ Inc/HALAL/Models/SPI/SPI2.hpp | 52 +- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 48 +- Inc/HALAL/Services/ADC/ADC.hpp | 74 ++- Inc/ST-LIB.hpp | 4 +- Inc/ST-LIB_LOW/Sd/Sd.hpp | 8 +- Tests/Clocks/clock_group_test.cpp | 14 +- Tests/Clocks/clock_stress_test.cpp | 16 +- Tests/adc_test.cpp | 86 ++- .../board_protection_contract.cpp | 3 +- 10 files changed, 506 insertions(+), 325 deletions(-) diff --git a/Inc/HALAL/Models/Clocks/ClockDomain.hpp b/Inc/HALAL/Models/Clocks/ClockDomain.hpp index 856b26e11..bfdf3fc01 100644 --- a/Inc/HALAL/Models/Clocks/ClockDomain.hpp +++ b/Inc/HALAL/Models/Clocks/ClockDomain.hpp @@ -11,33 +11,54 @@ extern void compile_error(const char* msg); struct ClockDomain { -/** - * ========================================= - * Inner Workings - * ========================================= - */ + /** + * ========================================= + * Inner Workings + * ========================================= + */ enum class ClockGroup : uint8_t { - SPI123_G, SPI45_G, SPI6_G, SDMMC_G, ADC_G, FDCAN_G, - USART16_G, USART234578_G, I2C1235_G, I2C4_G, - LPUART1_G, LPTIM_G, RNG_G, USB_G, DFSDM1_G, SAI1_G, SAI4_G, - ETH_MAC_G, RTC_G, CEC_G, - APB1_TIM_G, APB2_TIM_G, D1_BUS_G, D2_BUS_G, D3_BUS_G, + SPI123_G, + SPI45_G, + SPI6_G, + SDMMC_G, + ADC_G, + FDCAN_G, + USART16_G, + USART234578_G, + I2C1235_G, + I2C4_G, + LPUART1_G, + LPTIM_G, + RNG_G, + USB_G, + DFSDM1_G, + SAI1_G, + SAI4_G, + ETH_MAC_G, + RTC_G, + CEC_G, + APB1_TIM_G, + APB2_TIM_G, + D1_BUS_G, + D2_BUS_G, + D3_BUS_G, }; - static constexpr uint32_t PLLM_MIN = 1; - static constexpr uint32_t PLLM_MAX = 63; - static constexpr uint32_t PLLN_MIN = 4; - static constexpr uint32_t PLLN_MAX = 512; - static constexpr uint32_t PLLP_MIN = 1; + static constexpr uint32_t PLLM_MIN = 1; + static constexpr uint32_t PLLM_MAX = 63; + static constexpr uint32_t PLLN_MIN = 4; + static constexpr uint32_t PLLN_MAX = 512; + static constexpr uint32_t PLLP_MIN = 1; static constexpr uint32_t PLLP_MAX = 128; - static constexpr uint32_t PLLQ_MIN = 1; - static constexpr uint32_t PLLQ_MAX = 128; - static constexpr uint32_t PLLR_MIN = 1; - static constexpr uint32_t PLLR_MAX = 128; + static constexpr uint32_t PLLQ_MIN = 1; + static constexpr uint32_t PLLQ_MAX = 128; + static constexpr uint32_t PLLR_MIN = 1; + static constexpr uint32_t PLLR_MAX = 128; static consteval bool is_valid_pll1p(uint32_t p) { - return p == PLLP_MIN || (p <= PLLP_MAX && p % 2 == 0); // 1 is bypass, then even numbers up to 128 + return p == PLLP_MIN || + (p <= PLLP_MAX && p % 2 == 0); // 1 is bypass, then even numbers up to 128 } static constexpr uint32_t PLL_IN_MIN = 1'000'000; @@ -49,64 +70,81 @@ struct ClockDomain { static constexpr uint32_t VCOL_MAX = 420'000'000; static constexpr uint32_t SYSCLK_MAX = 550'000'000; - static constexpr uint32_t HCLK_MAX = 275'000'000; + static constexpr uint32_t HCLK_MAX = 275'000'000; static constexpr uint32_t d1cpre_values[] = {1, 2, 4, 8, 16, 64, 128, 256, 512}; - struct ClockTree { - uint32_t hse_frequency = 0; - bool hse_bypass = false; - - uint32_t pll1_m = 0; - uint32_t pll1_n = 0; - uint32_t pll1_p = 0; - uint32_t pll1_q = 0; - uint32_t pll1_r = 0; - uint32_t pll1_fracn = 0; - - uint32_t pll2_m = 0; - uint32_t pll2_n = 0; - uint32_t pll2_p = 0; - uint32_t pll2_q = 0; - uint32_t pll2_r = 0; - uint32_t pll2_fracn = 0; - - uint32_t pll3_m = 0; - uint32_t pll3_n = 0; - uint32_t pll3_p = 0; - uint32_t pll3_q = 0; - uint32_t pll3_r = 0; - uint32_t pll3_fracn = 0; - - uint32_t d1cpre = 1; - uint32_t d2ppre1 = 2; - uint32_t d2ppre2 = 2; + uint32_t hse_frequency = 0; + bool hse_bypass = false; + + uint32_t pll1_m = 0; + uint32_t pll1_n = 0; + uint32_t pll1_p = 0; + uint32_t pll1_q = 0; + uint32_t pll1_r = 0; + uint32_t pll1_fracn = 0; + + uint32_t pll2_m = 0; + uint32_t pll2_n = 0; + uint32_t pll2_p = 0; + uint32_t pll2_q = 0; + uint32_t pll2_r = 0; + uint32_t pll2_fracn = 0; + + uint32_t pll3_m = 0; + uint32_t pll3_n = 0; + uint32_t pll3_p = 0; + uint32_t pll3_q = 0; + uint32_t pll3_r = 0; + uint32_t pll3_fracn = 0; + + uint32_t d1cpre = 1; + uint32_t d2ppre1 = 2; + uint32_t d2ppre2 = 2; enum class Source : uint8_t { - HSI, HSE, CSI, PCLK1, PCLK2, PLL1Q, PLL1R, - PLL2P, PLL2Q, PLL2R, PLL3P, PLL3Q, PLL3R, + HSI, + HSE, + CSI, + PCLK1, + PCLK2, + PLL1Q, + PLL1R, + PLL2P, + PLL2Q, + PLL2R, + PLL3P, + PLL3Q, + PLL3R, }; - Source spi123_src = Source::HSI; - Source spi45_src = Source::HSI; - Source spi6_src = Source::HSI; - Source adc_src = Source::HSI; - Source fdcan_src = Source::HSI; - Source sdmmc_src = Source::HSI; + Source spi123_src = Source::HSI; + Source spi45_src = Source::HSI; + Source spi6_src = Source::HSI; + Source adc_src = Source::HSI; + Source fdcan_src = Source::HSI; + Source sdmmc_src = Source::HSI; }; static constexpr uint32_t find_rge(uint32_t pll_input) { - if (pll_input >= 1'000'000 && pll_input <= 2'000'000) return 0; - if (pll_input > 2'000'000 && pll_input <= 4'000'000) return 1; - if (pll_input > 4'000'000 && pll_input <= 8'000'000) return 2; - if (pll_input > 8'000'000 && pll_input <= 16'000'000) return 3; + if (pll_input >= 1'000'000 && pll_input <= 2'000'000) + return 0; + if (pll_input > 2'000'000 && pll_input <= 4'000'000) + return 1; + if (pll_input > 4'000'000 && pll_input <= 8'000'000) + return 2; + if (pll_input > 8'000'000 && pll_input <= 16'000'000) + return 3; return 0; } static constexpr uint32_t flash_ws(uint32_t hclk) { - if (hclk <= 70'000'000) return 1; - if (hclk <= 140'000'000) return 2; - if (hclk <= 210'000'000) return 3; + if (hclk <= 70'000'000) + return 1; + if (hclk <= 140'000'000) + return 2; + if (hclk <= 210'000'000) + return 3; return 4; } @@ -127,35 +165,53 @@ struct ClockDomain { return in * t.pll3_n + in * t.pll3_fracn / 8192; } - static constexpr bool pll1_vco_wide(const ClockTree& t) { return pll1_input(t) >= 2'000'000; } + static constexpr bool pll1_vco_wide(const ClockTree& t) { return pll1_input(t) >= 2'000'000; } static constexpr uint32_t pll1_rge(const ClockTree& t) { return find_rge(pll1_input(t)); } static constexpr uint32_t pll2_rge(const ClockTree& t) { return find_rge(pll2_input(t)); } static constexpr uint32_t pll3_rge(const ClockTree& t) { return find_rge(pll3_input(t)); } static constexpr uint32_t sysclk(const ClockTree& t) { return pll1_vco(t) / t.pll1_p; } - static constexpr uint32_t hclk(const ClockTree& t) { return sysclk(t) / t.d1cpre; } - static constexpr uint32_t pclk1(const ClockTree& t) { return hclk(t) / t.d2ppre1; } - static constexpr uint32_t pclk2(const ClockTree& t) { return hclk(t) / t.d2ppre2; } - static constexpr uint32_t timer_apb1(const ClockTree& t) { return pclk1(t) * (t.d2ppre1 == 1 ? 1 : 2); } - static constexpr uint32_t timer_apb2(const ClockTree& t) { return pclk2(t) * (t.d2ppre2 == 1 ? 1 : 2); } + static constexpr uint32_t hclk(const ClockTree& t) { return sysclk(t) / t.d1cpre; } + static constexpr uint32_t pclk1(const ClockTree& t) { return hclk(t) / t.d2ppre1; } + static constexpr uint32_t pclk2(const ClockTree& t) { return hclk(t) / t.d2ppre2; } + static constexpr uint32_t timer_apb1(const ClockTree& t) { + return pclk1(t) * (t.d2ppre1 == 1 ? 1 : 2); + } + static constexpr uint32_t timer_apb2(const ClockTree& t) { + return pclk2(t) * (t.d2ppre2 == 1 ? 1 : 2); + } static constexpr uint32_t flash_latency(const ClockTree& t) { return flash_ws(hclk(t)); } static constexpr uint32_t source_frequency(const ClockTree& t, ClockTree::Source src) { switch (src) { - case ClockTree::Source::HSI: return 64'000'000; - case ClockTree::Source::HSE: return t.hse_frequency; - case ClockTree::Source::CSI: return 4'000'000; - case ClockTree::Source::PCLK1: return pclk1(t); - case ClockTree::Source::PCLK2: return pclk2(t); - case ClockTree::Source::PLL1Q: return pll1_vco(t) / t.pll1_q; - case ClockTree::Source::PLL1R: return pll1_vco(t) / t.pll1_r; - case ClockTree::Source::PLL2P: return t.pll2_p != 0 ? pll2_vco(t) / t.pll2_p : 0; - case ClockTree::Source::PLL2Q: return t.pll2_q != 0 ? pll2_vco(t) / t.pll2_q : 0; - case ClockTree::Source::PLL2R: return t.pll2_r != 0 ? pll2_vco(t) / t.pll2_r : 0; - case ClockTree::Source::PLL3P: return t.pll3_p != 0 ? pll3_vco(t) / t.pll3_p : 0; - case ClockTree::Source::PLL3Q: return t.pll3_q != 0 ? pll3_vco(t) / t.pll3_q : 0; - case ClockTree::Source::PLL3R: return t.pll3_r != 0 ? pll3_vco(t) / t.pll3_r : 0; - default: break; + case ClockTree::Source::HSI: + return 64'000'000; + case ClockTree::Source::HSE: + return t.hse_frequency; + case ClockTree::Source::CSI: + return 4'000'000; + case ClockTree::Source::PCLK1: + return pclk1(t); + case ClockTree::Source::PCLK2: + return pclk2(t); + case ClockTree::Source::PLL1Q: + return pll1_vco(t) / t.pll1_q; + case ClockTree::Source::PLL1R: + return pll1_vco(t) / t.pll1_r; + case ClockTree::Source::PLL2P: + return t.pll2_p != 0 ? pll2_vco(t) / t.pll2_p : 0; + case ClockTree::Source::PLL2Q: + return t.pll2_q != 0 ? pll2_vco(t) / t.pll2_q : 0; + case ClockTree::Source::PLL2R: + return t.pll2_r != 0 ? pll2_vco(t) / t.pll2_r : 0; + case ClockTree::Source::PLL3P: + return t.pll3_p != 0 ? pll3_vco(t) / t.pll3_p : 0; + case ClockTree::Source::PLL3Q: + return t.pll3_q != 0 ? pll3_vco(t) / t.pll3_q : 0; + case ClockTree::Source::PLL3R: + return t.pll3_r != 0 ? pll3_vco(t) / t.pll3_r : 0; + default: + break; } if consteval { compile_error("Unknown clock source"); @@ -167,18 +223,30 @@ struct ClockDomain { static constexpr uint32_t get_kernel_clock(const ClockTree& t, ClockGroup group) { switch (group) { - case ClockGroup::SPI123_G: return source_frequency(t, t.spi123_src); - case ClockGroup::SPI45_G: return source_frequency(t, t.spi45_src); - case ClockGroup::SPI6_G: return source_frequency(t, t.spi6_src); - case ClockGroup::ADC_G: return source_frequency(t, t.adc_src); - case ClockGroup::FDCAN_G: return source_frequency(t, t.fdcan_src); - case ClockGroup::SDMMC_G: return source_frequency(t, t.sdmmc_src); - case ClockGroup::APB1_TIM_G: return timer_apb1(t); - case ClockGroup::APB2_TIM_G: return timer_apb2(t); - case ClockGroup::D1_BUS_G: return sysclk(t); - case ClockGroup::D2_BUS_G: return pclk1(t); - case ClockGroup::D3_BUS_G: return pclk2(t); - default: break; + case ClockGroup::SPI123_G: + return source_frequency(t, t.spi123_src); + case ClockGroup::SPI45_G: + return source_frequency(t, t.spi45_src); + case ClockGroup::SPI6_G: + return source_frequency(t, t.spi6_src); + case ClockGroup::ADC_G: + return source_frequency(t, t.adc_src); + case ClockGroup::FDCAN_G: + return source_frequency(t, t.fdcan_src); + case ClockGroup::SDMMC_G: + return source_frequency(t, t.sdmmc_src); + case ClockGroup::APB1_TIM_G: + return timer_apb1(t); + case ClockGroup::APB2_TIM_G: + return timer_apb2(t); + case ClockGroup::D1_BUS_G: + return sysclk(t); + case ClockGroup::D2_BUS_G: + return pclk1(t); + case ClockGroup::D3_BUS_G: + return pclk2(t); + default: + break; } if consteval { compile_error("Unknown clock group"); @@ -188,11 +256,11 @@ struct ClockDomain { return 0; } -/** - * ========================================= - * Domain Contract - * ========================================= - */ + /** + * ========================================= + * Domain Contract + * ========================================= + */ static constexpr std::size_t max_instances = 32; @@ -206,34 +274,43 @@ struct ClockDomain { ClockGroup group; Entry::TrySolveFn try_solve; - template - constexpr std::size_t inscribe(Ctx& ctx) const { + template constexpr std::size_t inscribe(Ctx& ctx) const { if (try_solve == nullptr) { compile_error("ClockDomain::Device: try_solve function pointer is null"); } - return ctx.template add(Entry{ - .group = group, - .try_solve = try_solve, - }, this); + return ctx.template add( + Entry{ + .group = group, + .try_solve = try_solve, + }, + this + ); } }; static consteval void validate(const ClockTree& t, std::span entries) { - if (t.hse_frequency == 0) compile_error("HSE frequency is zero"); - if (t.pll1_m == 0) compile_error("PLL1M is zero"); + if (t.hse_frequency == 0) + compile_error("HSE frequency is zero"); + if (t.pll1_m == 0) + compile_error("PLL1M is zero"); uint32_t pll_in = pll1_input(t); - uint32_t vco = pll1_vco(t); - uint32_t sclk = sysclk(t); - uint32_t h_clk = hclk(t); - - if (sclk > SYSCLK_MAX) compile_error("SYSCLK exceeds maximum (550 MHz)"); - if (h_clk > HCLK_MAX) compile_error("HCLK exceeds maximum (275 MHz)"); + uint32_t vco = pll1_vco(t); + uint32_t sclk = sysclk(t); + uint32_t h_clk = hclk(t); + + if (sclk > SYSCLK_MAX) + compile_error("SYSCLK exceeds maximum (550 MHz)"); + if (h_clk > HCLK_MAX) + compile_error("HCLK exceeds maximum (275 MHz)"); if (pll_in < PLL_IN_MIN || pll_in > PLL_IN_MAX) compile_error("PLL1 input outside valid range (1-16 MHz)"); - if (t.pll1_m < PLLM_MIN || t.pll1_m > PLLM_MAX) compile_error("PLL1M out of range"); - if (t.pll1_n < PLLN_MIN || t.pll1_n > PLLN_MAX) compile_error("PLL1N out of range"); - if (!is_valid_pll1p(t.pll1_p)) compile_error("Invalid PLL1P divider"); + if (t.pll1_m < PLLM_MIN || t.pll1_m > PLLM_MAX) + compile_error("PLL1M out of range"); + if (t.pll1_n < PLLN_MIN || t.pll1_n > PLLN_MAX) + compile_error("PLL1N out of range"); + if (!is_valid_pll1p(t.pll1_p)) + compile_error("Invalid PLL1P divider"); if (pll1_vco_wide(t)) { if (vco < VCOH_MIN || vco > VCOH_MAX) @@ -245,15 +322,20 @@ struct ClockDomain { bool d1cpre_valid = false; for (auto d : d1cpre_values) { - if (t.d1cpre == d) { d1cpre_valid = true; break; } + if (t.d1cpre == d) { + d1cpre_valid = true; + break; + } } - if (!d1cpre_valid) compile_error("D1CPRE is not a valid divider"); + if (!d1cpre_valid) + compile_error("D1CPRE is not a valid divider"); // Peripheral requirements for (size_t i = 0; i < entries.size(); i++) { const auto& ent = entries[i]; uint32_t ker = get_kernel_clock(t, ent.group); - if (ker == 0) compile_error("No kernel clock assigned for peripheral group"); + if (ker == 0) + compile_error("No kernel clock assigned for peripheral group"); if (!ent.try_solve(ker)) compile_error("Kernel clock does not satisfy peripheral requirements"); } @@ -294,37 +376,58 @@ struct ClockDomain { static constexpr uint32_t to_hal_rge(uint32_t rge) { switch (rge) { - case 0: return RCC_PLL1VCIRANGE_0; - case 1: return RCC_PLL1VCIRANGE_1; - case 2: return RCC_PLL1VCIRANGE_2; - case 3: return RCC_PLL1VCIRANGE_3; - default: return RCC_PLL1VCIRANGE_0; + case 0: + return RCC_PLL1VCIRANGE_0; + case 1: + return RCC_PLL1VCIRANGE_1; + case 2: + return RCC_PLL1VCIRANGE_2; + case 3: + return RCC_PLL1VCIRANGE_3; + default: + return RCC_PLL1VCIRANGE_0; } } static constexpr uint32_t to_hal_d1cpre(uint32_t d) { switch (d) { - case 1: return RCC_SYSCLK_DIV1; - case 2: return RCC_SYSCLK_DIV2; - case 4: return RCC_SYSCLK_DIV4; - case 8: return RCC_SYSCLK_DIV8; - case 16: return RCC_SYSCLK_DIV16; - case 64: return RCC_SYSCLK_DIV64; - case 128: return RCC_SYSCLK_DIV128; - case 256: return RCC_SYSCLK_DIV256; - case 512: return RCC_SYSCLK_DIV512; - default: return RCC_SYSCLK_DIV1; + case 1: + return RCC_SYSCLK_DIV1; + case 2: + return RCC_SYSCLK_DIV2; + case 4: + return RCC_SYSCLK_DIV4; + case 8: + return RCC_SYSCLK_DIV8; + case 16: + return RCC_SYSCLK_DIV16; + case 64: + return RCC_SYSCLK_DIV64; + case 128: + return RCC_SYSCLK_DIV128; + case 256: + return RCC_SYSCLK_DIV256; + case 512: + return RCC_SYSCLK_DIV512; + default: + return RCC_SYSCLK_DIV1; } } static constexpr uint32_t to_hal_apb_pre(uint32_t d) { switch (d) { - case 1: return RCC_APB1_DIV1; - case 2: return RCC_APB1_DIV2; - case 4: return RCC_APB1_DIV4; - case 8: return RCC_APB1_DIV8; - case 16: return RCC_APB1_DIV16; - default: return RCC_APB1_DIV2; + case 1: + return RCC_APB1_DIV1; + case 2: + return RCC_APB1_DIV2; + case 4: + return RCC_APB1_DIV4; + case 8: + return RCC_APB1_DIV8; + case 16: + return RCC_APB1_DIV16; + default: + return RCC_APB1_DIV2; } } @@ -338,15 +441,15 @@ struct ClockDomain { HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY); __HAL_PWR_VOLTAGESCALING_CONFIG( - sysclk(t) > 480'000'000 ? PWR_REGULATOR_VOLTAGE_SCALE0 - : PWR_REGULATOR_VOLTAGE_SCALE1 + sysclk(t) > 480'000'000 ? PWR_REGULATOR_VOLTAGE_SCALE0 : PWR_REGULATOR_VOLTAGE_SCALE1 ); - while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {} + while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) { + } __HAL_RCC_PLL_PLLSOURCE_CONFIG(RCC_PLLSOURCE_HSE); - osc.OscillatorType = RCC_OSCILLATORTYPE_HSI | RCC_OSCILLATORTYPE_LSI - | RCC_OSCILLATORTYPE_HSE; + osc.OscillatorType = + RCC_OSCILLATORTYPE_HSI | RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_HSE; osc.HSIState = RCC_HSI_DIV1; osc.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; osc.LSIState = RCC_LSI_ON; @@ -367,9 +470,8 @@ struct ClockDomain { PANIC("ClockDomain: HAL_RCC_OscConfig failed"); } - clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK - | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2 - | RCC_CLOCKTYPE_D3PCLK1 | RCC_CLOCKTYPE_D1PCLK1; + clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | + RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_D3PCLK1 | RCC_CLOCKTYPE_D1PCLK1; clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; clk.SYSCLKDivider = to_hal_d1cpre(t.d1cpre); clk.AHBCLKDivider = RCC_HCLK_DIV1; @@ -388,7 +490,7 @@ struct ClockDomain { // PLL2/PLL3 register config — HAL requires RCC_PERIPHCLK_* to enable each PLL block if (t.pll2_m != 0) { - pclk.PeriphClockSelection |= RCC_PERIPHCLK_ADC; // triggers PLL2 register write + pclk.PeriphClockSelection |= RCC_PERIPHCLK_ADC; // triggers PLL2 register write pclk.PLL2.PLL2M = t.pll2_m; pclk.PLL2.PLL2N = t.pll2_n; pclk.PLL2.PLL2P = t.pll2_p != 0 ? t.pll2_p : 1; @@ -412,40 +514,84 @@ struct ClockDomain { pclk.PeriphClockSelection |= RCC_PERIPHCLK_SPI1; switch (t.spi123_src) { - case ClockTree::Source::PLL1Q: pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL; break; - case ClockTree::Source::PLL2P: pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL2; break; - case ClockTree::Source::PLL3P: pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL3; break; - case ClockTree::Source::HSI: pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_CLKP; break; - default: PANIC("ClockDomain: SPI123 clock source not supported"); break; + case ClockTree::Source::PLL1Q: + pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL; + break; + case ClockTree::Source::PLL2P: + pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL2; + break; + case ClockTree::Source::PLL3P: + pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL3; + break; + case ClockTree::Source::HSI: + pclk.Spi123ClockSelection = RCC_SPI123CLKSOURCE_CLKP; + break; + default: + PANIC("ClockDomain: SPI123 clock source not supported"); + break; } pclk.PeriphClockSelection |= RCC_PERIPHCLK_SPI4; switch (t.spi45_src) { - case ClockTree::Source::PCLK2: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_D2PCLK2; break; - case ClockTree::Source::PLL2Q: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_PLL2; break; - case ClockTree::Source::PLL3Q: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_PLL3; break; - case ClockTree::Source::HSI: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_HSI; break; - case ClockTree::Source::CSI: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_CSI; break; - case ClockTree::Source::HSE: pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_HSE; break; - default: PANIC("ClockDomain: SPI45 clock source not supported"); break; + case ClockTree::Source::PCLK2: + pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_D2PCLK2; + break; + case ClockTree::Source::PLL2Q: + pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_PLL2; + break; + case ClockTree::Source::PLL3Q: + pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_PLL3; + break; + case ClockTree::Source::HSI: + pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_HSI; + break; + case ClockTree::Source::CSI: + pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_CSI; + break; + case ClockTree::Source::HSE: + pclk.Spi45ClockSelection = RCC_SPI45CLKSOURCE_HSE; + break; + default: + PANIC("ClockDomain: SPI45 clock source not supported"); + break; } pclk.PeriphClockSelection |= RCC_PERIPHCLK_SPI6; switch (t.spi6_src) { - case ClockTree::Source::PCLK2: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_D3PCLK1; break; - case ClockTree::Source::PLL2P: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_PLL2; break; - case ClockTree::Source::PLL3P: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_PLL3; break; - case ClockTree::Source::HSI: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_HSI; break; - case ClockTree::Source::CSI: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_CSI; break; - case ClockTree::Source::HSE: pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_HSE; break; - default: PANIC("ClockDomain: SPI6 clock source not supported"); break; + case ClockTree::Source::PCLK2: + pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_D3PCLK1; + break; + case ClockTree::Source::PLL2P: + pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_PLL2; + break; + case ClockTree::Source::PLL3P: + pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_PLL3; + break; + case ClockTree::Source::HSI: + pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_HSI; + break; + case ClockTree::Source::CSI: + pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_CSI; + break; + case ClockTree::Source::HSE: + pclk.Spi6ClockSelection = RCC_SPI6CLKSOURCE_HSE; + break; + default: + PANIC("ClockDomain: SPI6 clock source not supported"); + break; } pclk.PeriphClockSelection |= RCC_PERIPHCLK_SDMMC; switch (t.sdmmc_src) { - case ClockTree::Source::PLL1Q: pclk.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL; break; - case ClockTree::Source::PLL2R: pclk.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL2; break; - default: PANIC("ClockDomain: SDMMC clock source not supported"); break; + case ClockTree::Source::PLL1Q: + pclk.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL; + break; + case ClockTree::Source::PLL2R: + pclk.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL2; + break; + default: + PANIC("ClockDomain: SDMMC clock source not supported"); + break; } __HAL_RCC_PLLCLKOUT_ENABLE(RCC_PLL1_DIVQ); @@ -466,18 +612,34 @@ struct ClockDomain { pclk.PeriphClockSelection |= RCC_PERIPHCLK_ADC; switch (t.adc_src) { - case ClockTree::Source::HSE: pclk.AdcClockSelection = RCC_ADCCLKSOURCE_CLKP; break; - case ClockTree::Source::PLL2R: pclk.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2; break; - case ClockTree::Source::PLL3R: pclk.AdcClockSelection = RCC_ADCCLKSOURCE_PLL3; break; - default: PANIC("ClockDomain: ADC clock source not supported"); break; + case ClockTree::Source::HSE: + pclk.AdcClockSelection = RCC_ADCCLKSOURCE_CLKP; + break; + case ClockTree::Source::PLL2R: + pclk.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2; + break; + case ClockTree::Source::PLL3R: + pclk.AdcClockSelection = RCC_ADCCLKSOURCE_PLL3; + break; + default: + PANIC("ClockDomain: ADC clock source not supported"); + break; } pclk.PeriphClockSelection |= RCC_PERIPHCLK_FDCAN; switch (t.fdcan_src) { - case ClockTree::Source::PLL2Q: pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL2; break; - case ClockTree::Source::PLL1Q: pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL; break; - case ClockTree::Source::HSE: pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE; break; - default: PANIC("ClockDomain: FDCAN clock source not supported"); break; + case ClockTree::Source::PLL2Q: + pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL2; + break; + case ClockTree::Source::PLL1Q: + pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL; + break; + case ClockTree::Source::HSE: + pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE; + break; + default: + PANIC("ClockDomain: FDCAN clock source not supported"); + break; } if (pclk.PeriphClockSelection != 0) { @@ -487,7 +649,7 @@ struct ClockDomain { } } -#endif // SIM_ON +#endif // SIM_ON struct Init { static inline void init(const ClockTree& tree) { diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index e4e0a2eee..cd277e7e0 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -27,12 +27,10 @@ namespace ST_LIB { // SPI clock model — prescaler search for the solver // ───────────────────────────────────────────── -template -struct SPIClockModel { +template struct SPIClockModel { static_assert( - Group == ClockDomain::ClockGroup::SPI123_G || - Group == ClockDomain::ClockGroup::SPI45_G || - Group == ClockDomain::ClockGroup::SPI6_G, + Group == ClockDomain::ClockGroup::SPI123_G || Group == ClockDomain::ClockGroup::SPI45_G || + Group == ClockDomain::ClockGroup::SPI6_G, "SPIClockModel: group must be SPI123, SPI45, or SPI6" ); @@ -194,9 +192,15 @@ struct SPIDomain { using enum SPIPeripheral; using enum ClockDomain::ClockGroup; switch (p) { - case spi1: case spi2: case spi3: return SPI123_G; - case spi4: case spi5: return SPI45_G; - case spi6: return SPI6_G; + case spi1: + case spi2: + case spi3: + return SPI123_G; + case spi4: + case spi5: + return SPI45_G; + case spi6: + return SPI6_G; } } @@ -268,14 +272,14 @@ struct SPIDomain { GPIODomain::Pin nss_pin, SPIConfig config = SPIConfig{} ) - : peripheral{peripheral}, mode{mode}, max_baudrate{max_baudrate}, min_baudrate{min_baudrate}, config{config}, - sck_gpio( - sck_pin, - GPIODomain::OperationMode::ALT_PP, - GPIODomain::Pull::None, - GPIODomain::Speed::VeryHigh, - get_af(sck_pin, peripheral) - ), + : peripheral{peripheral}, mode{mode}, max_baudrate{max_baudrate}, + min_baudrate{min_baudrate}, config{config}, sck_gpio( + sck_pin, + GPIODomain::OperationMode::ALT_PP, + GPIODomain::Pull::None, + GPIODomain::Speed::VeryHigh, + get_af(sck_pin, peripheral) + ), miso_gpio( miso_pin, GPIODomain::OperationMode::ALT_PP, @@ -321,14 +325,14 @@ struct SPIDomain { GPIODomain::Pin mosi_pin, SPIConfig config ) - : peripheral{peripheral}, mode{mode}, max_baudrate{max_baudrate}, min_baudrate{min_baudrate}, config{config}, - sck_gpio( - sck_pin, - GPIODomain::OperationMode::ALT_PP, - GPIODomain::Pull::None, - GPIODomain::Speed::VeryHigh, - get_af(sck_pin, peripheral) - ), + : peripheral{peripheral}, mode{mode}, max_baudrate{max_baudrate}, + min_baudrate{min_baudrate}, config{config}, sck_gpio( + sck_pin, + GPIODomain::OperationMode::ALT_PP, + GPIODomain::Pull::None, + GPIODomain::Speed::VeryHigh, + get_af(sck_pin, peripheral) + ), miso_gpio( miso_pin, GPIODomain::OperationMode::ALT_PP, diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 4c09a80da..f4084c9aa 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -674,8 +674,12 @@ struct TimerDomain { } uint8_t reqint = remaining_32bit_timers[count_32bit_requests]; - Config cfg = - {.timer_idx = timer_idxmap[reqint], .request = e.request, .trgo1 = e.trgo1, .trgo2 = e.trgo2}; + Config cfg = { + .timer_idx = timer_idxmap[reqint], + .request = e.request, + .trgo1 = e.trgo1, + .trgo2 = e.trgo2 + }; cfgs[cfg_idx++] = cfg; // unordered remove @@ -722,7 +726,12 @@ struct TimerDomain { ST_LIB::compile_error("This only processes TimerRequest::AnyGeneralPurpose"); } uint8_t reqint = remaining_timers[i]; - Config cfg = {.timer_idx = timer_idxmap[reqint], .request = e.request, .trgo1 = e.trgo1, .trgo2 = e.trgo2}; + Config cfg = { + .timer_idx = timer_idxmap[reqint], + .request = e.request, + .trgo1 = e.trgo1, + .trgo2 = e.trgo2 + }; cfgs[cfg_idx++] = cfg; } @@ -735,24 +744,25 @@ struct TimerDomain { TIM_HandleTypeDef* hal_tim; TIM_MasterConfigTypeDef master{}; uint8_t timer_idx; - uint32_t clock_frequency = 0; // from ClockTree + uint32_t clock_frequency = 0; // from ClockTree }; static constexpr bool is_timer_on_apb1(TimerRequest r) { switch (r) { - case TimerRequest::GeneralPurpose32bit_2: - case TimerRequest::GeneralPurpose_3: - case TimerRequest::GeneralPurpose_4: - case TimerRequest::GeneralPurpose32bit_5: - case TimerRequest::Basic_6: - case TimerRequest::Basic_7: - case TimerRequest::SlaveTimer_12: - case TimerRequest::SlaveTimer_13: - case TimerRequest::SlaveTimer_14: - case TimerRequest::GeneralPurpose32bit_23: - case TimerRequest::GeneralPurpose32bit_24: - return true; - default: return false; + case TimerRequest::GeneralPurpose32bit_2: + case TimerRequest::GeneralPurpose_3: + case TimerRequest::GeneralPurpose_4: + case TimerRequest::GeneralPurpose32bit_5: + case TimerRequest::Basic_6: + case TimerRequest::Basic_7: + case TimerRequest::SlaveTimer_12: + case TimerRequest::SlaveTimer_13: + case TimerRequest::SlaveTimer_14: + case TimerRequest::GeneralPurpose32bit_23: + case TimerRequest::GeneralPurpose32bit_24: + return true; + default: + return false; } } @@ -824,8 +834,8 @@ struct TimerDomain { inst->tim = tim; inst->hal_tim = handle; inst->timer_idx = e.timer_idx; - inst->clock_frequency = is_timer_on_apb1(e.request) - ? ClockDomain::timer_apb1(tree) : ClockDomain::timer_apb2(tree); + inst->clock_frequency = is_timer_on_apb1(e.request) ? ClockDomain::timer_apb1(tree) + : ClockDomain::timer_apb2(tree); TIM_MasterConfigTypeDef sMasterConfig = {}; sMasterConfig.MasterOutputTrigger = static_cast(e.trgo1); sMasterConfig.MasterOutputTrigger2 = static_cast(e.trgo2); diff --git a/Inc/HALAL/Services/ADC/ADC.hpp b/Inc/HALAL/Services/ADC/ADC.hpp index 34ddb1bb4..0b8450e04 100644 --- a/Inc/HALAL/Services/ADC/ADC.hpp +++ b/Inc/HALAL/Services/ADC/ADC.hpp @@ -100,12 +100,10 @@ struct ADCDomain { float* output; }; - template - struct ADCClockModel { + template struct ADCClockModel { static constexpr auto group = ClockDomain::ClockGroup::ADC_G; - static constexpr uint32_t prescalers[] = - {1, 2, 4, 6, 8, 10, 12, 16, 32, 64, 128, 256}; + static constexpr uint32_t prescalers[] = {1, 2, 4, 6, 8, 10, 12, 16, 32, 64, 128, 256}; static constexpr uint32_t ADC_CLK_MIN = 500'000; static consteval bool try_solve(uint32_t kernel_clk) { @@ -120,30 +118,48 @@ struct ADCDomain { static constexpr uint32_t adc_max_clk(Resolution res) { switch (res) { - case Resolution::BITS_16: return 8'333'333; - case Resolution::BITS_14: return 16'666'666; - case Resolution::BITS_12: return 36'000'000; - case Resolution::BITS_10: return 50'000'000; - case Resolution::BITS_8: return 50'000'000; + case Resolution::BITS_16: + return 8'333'333; + case Resolution::BITS_14: + return 16'666'666; + case Resolution::BITS_12: + return 36'000'000; + case Resolution::BITS_10: + return 50'000'000; + case Resolution::BITS_8: + return 50'000'000; } return 36'000'000; } static constexpr uint32_t prescaler_to_hal(uint32_t p) { switch (p) { - case 1: return ADC_CLOCK_ASYNC_DIV1; - case 2: return ADC_CLOCK_ASYNC_DIV2; - case 4: return ADC_CLOCK_ASYNC_DIV4; - case 6: return ADC_CLOCK_ASYNC_DIV6; - case 8: return ADC_CLOCK_ASYNC_DIV8; - case 10: return ADC_CLOCK_ASYNC_DIV10; - case 12: return ADC_CLOCK_ASYNC_DIV12; - case 16: return ADC_CLOCK_ASYNC_DIV16; - case 32: return ADC_CLOCK_ASYNC_DIV32; - case 64: return ADC_CLOCK_ASYNC_DIV64; - case 128: return ADC_CLOCK_ASYNC_DIV128; - case 256: return ADC_CLOCK_ASYNC_DIV256; - default: return ADC_CLOCK_ASYNC_DIV4; + case 1: + return ADC_CLOCK_ASYNC_DIV1; + case 2: + return ADC_CLOCK_ASYNC_DIV2; + case 4: + return ADC_CLOCK_ASYNC_DIV4; + case 6: + return ADC_CLOCK_ASYNC_DIV6; + case 8: + return ADC_CLOCK_ASYNC_DIV8; + case 10: + return ADC_CLOCK_ASYNC_DIV10; + case 12: + return ADC_CLOCK_ASYNC_DIV12; + case 16: + return ADC_CLOCK_ASYNC_DIV16; + case 32: + return ADC_CLOCK_ASYNC_DIV32; + case 64: + return ADC_CLOCK_ASYNC_DIV64; + case 128: + return ADC_CLOCK_ASYNC_DIV128; + case 256: + return ADC_CLOCK_ASYNC_DIV256; + default: + return ADC_CLOCK_ASYNC_DIV4; } } @@ -199,13 +215,7 @@ struct ADCDomain { SampleTime sample_time = SampleTime::CYCLES_8_5, uint32_t sample_rate_hz = 0 ) - : ADC(pin, - output, - resolution, - sample_time, - sample_rate_hz, - peripheral, - channel) {} + : ADC(pin, output, resolution, sample_time, sample_rate_hz, peripheral, channel) {} consteval ADC( const GPIODomain::Pin& pin, @@ -885,7 +895,11 @@ struct ADCDomain { return &dma_buffer_pool[buffer_offset + index]; } - static void configure_peripheral(const Config& cfg, uint8_t channel_count, const ClockDomain::ClockTree& tree) { + static void configure_peripheral( + const Config& cfg, + uint8_t channel_count, + const ClockDomain::ClockTree& tree + ) { ADC_HandleTypeDef* hadc = handle_for(cfg.peripheral); if (cfg.peripheral == Peripheral::ADC_1 || cfg.peripheral == Peripheral::ADC_2) { diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 485c83fec..65ec3758f 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -279,9 +279,7 @@ template stru static constexpr auto cfg = build(); - static constexpr auto& clock_tree() { - return cfg.clock_tree; - } + static constexpr auto& clock_tree() { return cfg.clock_tree; } static void init() { constexpr std::size_t mpuN = domain_size(); diff --git a/Inc/ST-LIB_LOW/Sd/Sd.hpp b/Inc/ST-LIB_LOW/Sd/Sd.hpp index edcccd7ef..6df0874b6 100644 --- a/Inc/ST-LIB_LOW/Sd/Sd.hpp +++ b/Inc/ST-LIB_LOW/Sd/Sd.hpp @@ -35,8 +35,7 @@ namespace ST_LIB { // SDMMC clock model — validates that a kernel clock can produce a valid // SDMMC clock (sdmmc_ker_ck / (2 × CLKDIV)) in [MinFreq, MaxFreq]. -template -struct SDClockModel { +template struct SDClockModel { static constexpr auto group = ClockDomain::ClockGroup::SDMMC_G; static constexpr uint32_t CLKDIV_MIN = 0; @@ -122,9 +121,8 @@ struct SdDomain { uint32_t min_freq = 1'000'000, GPIODomain::Pin d0_pin_for_sdmmc1 = ST_LIB::PC8 ) - : e{.peripheral = sdmmc_peripheral, - .max_freq = max_freq, - .min_freq = min_freq}, peripheral(sdmmc_peripheral), + : e{.peripheral = sdmmc_peripheral, .max_freq = max_freq, .min_freq = min_freq}, + peripheral(sdmmc_peripheral), buffer0(MPUDomain::Buffer>( MPUDomain::MemoryType::NonCached, MPUDomain::MemoryDomain::D1 diff --git a/Tests/Clocks/clock_group_test.cpp b/Tests/Clocks/clock_group_test.cpp index 52bc20df1..9dd8fcf00 100644 --- a/Tests/Clocks/clock_group_test.cpp +++ b/Tests/Clocks/clock_group_test.cpp @@ -63,7 +63,7 @@ static_assert([] { auto tree = tree_8m; tree.spi123_src = ClockDomain::ClockTree::Source::HSI; - tree.spi45_src = ClockDomain::ClockTree::Source::PCLK2; + tree.spi45_src = ClockDomain::ClockTree::Source::PCLK2; std::array entries{{ {.group = SpiA::group, .try_solve = &SpiA::try_solve}, @@ -82,18 +82,18 @@ static_assert([] { static_assert([] { using Spi123 = SPIClockModel; - using Spi45 = SPIClockModel; - using Spi6 = SPIClockModel; + using Spi45 = SPIClockModel; + using Spi6 = SPIClockModel; auto tree = tree_8m; tree.spi123_src = ClockDomain::ClockTree::Source::HSI; - tree.spi45_src = ClockDomain::ClockTree::Source::HSI; - tree.spi6_src = ClockDomain::ClockTree::Source::HSI; + tree.spi45_src = ClockDomain::ClockTree::Source::HSI; + tree.spi6_src = ClockDomain::ClockTree::Source::HSI; std::array entries{{ {.group = Spi123::group, .try_solve = &Spi123::try_solve}, - {.group = Spi45::group, .try_solve = &Spi45::try_solve}, - {.group = Spi6::group, .try_solve = &Spi6::try_solve}, + {.group = Spi45::group, .try_solve = &Spi45::try_solve}, + {.group = Spi6::group, .try_solve = &Spi6::try_solve}, }}; ClockDomain::validate(tree, std::span{entries}); return true; diff --git a/Tests/Clocks/clock_stress_test.cpp b/Tests/Clocks/clock_stress_test.cpp index aabac51fc..152c641ea 100644 --- a/Tests/Clocks/clock_stress_test.cpp +++ b/Tests/Clocks/clock_stress_test.cpp @@ -23,14 +23,12 @@ static_assert([] { static_assert([] { using Spi = SPIClockModel; - return Spi::try_solve(64'000'000) && - !Spi::try_solve(4'000'000); + return Spi::try_solve(64'000'000) && !Spi::try_solve(4'000'000); }()); static_assert([] { using Spi = SPIClockModel; - return Spi::try_solve(64'000'000) && - !Spi::try_solve(30'000'000); + return Spi::try_solve(64'000'000) && !Spi::try_solve(30'000'000); }()); static_assert([] { @@ -55,8 +53,8 @@ static_assert([] { auto tree = tree_8m; tree.spi123_src = ClockDomain::ClockTree::Source::HSI; - tree.spi45_src = ClockDomain::ClockTree::Source::HSI; - tree.spi6_src = ClockDomain::ClockTree::Source::HSI; + tree.spi45_src = ClockDomain::ClockTree::Source::HSI; + tree.spi6_src = ClockDomain::ClockTree::Source::HSI; std::array entries{{ {.group = Spi1::group, .try_solve = &Spi1::try_solve}, @@ -69,10 +67,8 @@ static_assert([] { static_assert([] { using Spi = SPIClockModel; - return !Spi::try_solve(64'000'000) && - !Spi::try_solve(4'000'000) && - !Spi::try_solve(8'000'000) && - !Spi::try_solve(137'500'000); + return !Spi::try_solve(64'000'000) && !Spi::try_solve(4'000'000) && + !Spi::try_solve(8'000'000) && !Spi::try_solve(137'500'000); }()); static_assert([] { diff --git a/Tests/adc_test.cpp b/Tests/adc_test.cpp index 358f7b818..81afc5058 100644 --- a/Tests/adc_test.cpp +++ b/Tests/adc_test.cpp @@ -757,50 +757,48 @@ TEST_F(ADCTest, TimedDMAFrequencySweepSeparatesStableAndUnstableOperatingRegions for (const auto resolution : resolutions) { for (const auto sample_time : sample_times) { - SCOPED_TRACE(static_cast(resolution)); - SCOPED_TRACE(static_cast(sample_time)); - - reset_runtime_state(); - - float output = -1.0f; - const std::array cfgs{{ - {.gpio_idx = 0, - .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, - .channel = ST_LIB::ADCDomain::Channel::CH16, - .resolution = resolution, - .sample_time = sample_time, - .sample_rate_hz = 0, - .dma_request = DMA_REQUEST_ADC1, - .output = &output}, - }}; - - ST_LIB::MockedHAL::adc_enable_timed_dma(ADC1, true); - ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC1, 64'000'000ULL); - ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 777U); - init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); - - const uint64_t sequence_period_ns = - ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC1); - ASSERT_GT(sequence_period_ns, 0U); - - ST_LIB::MockedHAL::dma_set_transfer_timing(0ULL, 0ULL); - ST_LIB::MockedHAL::adc_advance_time_ns(sequence_period_ns * 8ULL); - EXPECT_EQ(ST_LIB::MockedHAL::adc_get_overrun_count(ADC1), 0U); - EXPECT_EQ(ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1), 8U); - - reset_runtime_state(); - - ST_LIB::MockedHAL::adc_enable_timed_dma(ADC1, true); - ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC1, 64'000'000ULL); - ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 777U); - init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); - - const uint64_t unstable_period_ns = - ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC1); - ST_LIB::MockedHAL::dma_set_transfer_timing(unstable_period_ns * 2ULL, 0ULL); - ST_LIB::MockedHAL::adc_advance_time_ns(unstable_period_ns * 8ULL); - EXPECT_GT(ST_LIB::MockedHAL::adc_get_overrun_count(ADC1), 0U); - EXPECT_LT(ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1), 8U); + SCOPED_TRACE(static_cast(resolution)); + SCOPED_TRACE(static_cast(sample_time)); + + reset_runtime_state(); + + float output = -1.0f; + const std::array cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = resolution, + .sample_time = sample_time, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &output}, + }}; + + ST_LIB::MockedHAL::adc_enable_timed_dma(ADC1, true); + ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC1, 64'000'000ULL); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 777U); + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + + const uint64_t sequence_period_ns = ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC1); + ASSERT_GT(sequence_period_ns, 0U); + + ST_LIB::MockedHAL::dma_set_transfer_timing(0ULL, 0ULL); + ST_LIB::MockedHAL::adc_advance_time_ns(sequence_period_ns * 8ULL); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_overrun_count(ADC1), 0U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1), 8U); + + reset_runtime_state(); + + ST_LIB::MockedHAL::adc_enable_timed_dma(ADC1, true); + ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC1, 64'000'000ULL); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 777U); + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + + const uint64_t unstable_period_ns = ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC1); + ST_LIB::MockedHAL::dma_set_transfer_timing(unstable_period_ns * 2ULL, 0ULL); + ST_LIB::MockedHAL::adc_advance_time_ns(unstable_period_ns * 8ULL); + EXPECT_GT(ST_LIB::MockedHAL::adc_get_overrun_count(ADC1), 0U); + EXPECT_LT(ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1), 8U); } } } diff --git a/Tests/compile_checks/board_protection_contract.cpp b/Tests/compile_checks/board_protection_contract.cpp index 1bb0e0198..339f3d9ff 100644 --- a/Tests/compile_checks/board_protection_contract.cpp +++ b/Tests/compile_checks/board_protection_contract.cpp @@ -7,7 +7,8 @@ float protected_value = 1.0f; inline constexpr auto protected_value_protection = Protections::protection<"protected_value", protected_value>(Protections::Rules::above(10.0f)); -using ContractBoard = ST_LIB::Board; +using ContractBoard = ST_LIB:: + Board; static_assert(ContractBoard::ProtectionEngine::protection_count == 1); static_assert(std::same_as< From 94af50b24b5f1d5a5e0cdcf37794c827bb906638 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 04:00:04 +0200 Subject: [PATCH 11/21] chore: Add changeset --- .changesets/clock-redesign.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changesets/clock-redesign.md diff --git a/.changesets/clock-redesign.md b/.changesets/clock-redesign.md new file mode 100644 index 000000000..cc9c224dd --- /dev/null +++ b/.changesets/clock-redesign.md @@ -0,0 +1,13 @@ +release: major +summary: Add ClockDomain, a centralised clock validator and applicator + +Clock configuration is a precomputed `ClockTree` (from a host tool or hand-written). The firmware validates it at compile time and applies it at runtime. No solver runs in the firmware. + +- `ClockTree` stores only decisions (M/N/P/Q/R, prescalers, source enums); all frequencies are derived via accessors (`sysclk(t)`, `source_frequency(t, src)`, etc.) +- `Board<>` takes a mandatory `ClockTree` as the second template parameter +- Peripherals register clock requirements via `ClockDomain::Device` with typed models: `SPIClockModel`, `ADCClockModel`, `SDClockModel` +- `ClockDomain::build()` validates PLL ranges, bus consistency, and peripheral requirements at compile time - refuses to compile on failure +- `ClockDomain::Init::init(tree)` applies the validated tree to HAL registers +- ADC prescaler computed from kernel clock at init (no longer user-specified) +- SPI/SD use mandatory frequency ranges +- Removed `HALconfig` From d53e2c8ef79754b10ba7b9b577628943f4c53957 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 04:09:09 +0200 Subject: [PATCH 12/21] chore(CI): Fix tests --- Inc/HALAL/Models/SPI/SPI2.hpp | 2 +- Inc/HALAL/Services/ADC/ADC.hpp | 2 +- Inc/ST-LIB_LOW/Sd/Sd.hpp | 2 +- Tests/Clocks/clock_group_test.cpp | 54 ++++++++++++++++-------------- Tests/Clocks/clock_stress_test.cpp | 31 +++++++++-------- 5 files changed, 49 insertions(+), 42 deletions(-) diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index cd277e7e0..36b9e9763 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -39,7 +39,7 @@ template str static constexpr uint32_t prescalers[] = {2, 4, 8, 16, 32, 64, 128, 256}; static constexpr uint32_t prescaler_count = sizeof(prescalers) / sizeof(prescalers[0]); - static consteval bool try_solve(uint32_t kernel_clk) { + static constexpr bool try_solve(uint32_t kernel_clk) { for (uint32_t i = 0; i < prescaler_count; i++) { uint32_t baud = kernel_clk / prescalers[i]; if (baud <= MaxBaud && baud >= MinBaud) { diff --git a/Inc/HALAL/Services/ADC/ADC.hpp b/Inc/HALAL/Services/ADC/ADC.hpp index 0b8450e04..9fe82b403 100644 --- a/Inc/HALAL/Services/ADC/ADC.hpp +++ b/Inc/HALAL/Services/ADC/ADC.hpp @@ -106,7 +106,7 @@ struct ADCDomain { static constexpr uint32_t prescalers[] = {1, 2, 4, 6, 8, 10, 12, 16, 32, 64, 128, 256}; static constexpr uint32_t ADC_CLK_MIN = 500'000; - static consteval bool try_solve(uint32_t kernel_clk) { + static constexpr bool try_solve(uint32_t kernel_clk) { for (uint32_t p : prescalers) { uint32_t adc_clk = kernel_clk / p; if (adc_clk >= ADC_CLK_MIN && adc_clk <= MaxADCCLK) diff --git a/Inc/ST-LIB_LOW/Sd/Sd.hpp b/Inc/ST-LIB_LOW/Sd/Sd.hpp index 6df0874b6..94e34e4dc 100644 --- a/Inc/ST-LIB_LOW/Sd/Sd.hpp +++ b/Inc/ST-LIB_LOW/Sd/Sd.hpp @@ -41,7 +41,7 @@ template struct SDClockModel { static constexpr uint32_t CLKDIV_MIN = 0; static constexpr uint32_t CLKDIV_MAX = 1023; - static consteval bool try_solve(uint32_t kernel_clk) { + static constexpr bool try_solve(uint32_t kernel_clk) { for (uint32_t div = CLKDIV_MIN; div <= CLKDIV_MAX; div++) { uint32_t sdmmc_clk = div == 0 ? kernel_clk : kernel_clk / (2 * div); if (sdmmc_clk >= MinFreq && sdmmc_clk <= MaxFreq) diff --git a/Tests/Clocks/clock_group_test.cpp b/Tests/Clocks/clock_group_test.cpp index 9dd8fcf00..2b017eca3 100644 --- a/Tests/Clocks/clock_group_test.cpp +++ b/Tests/Clocks/clock_group_test.cpp @@ -18,10 +18,11 @@ constexpr ClockDomain::ClockTree tree_8m{ static_assert([] { using SpiModel = SPIClockModel; - - auto tree = tree_8m; - tree.spi123_src = ClockDomain::ClockTree::Source::HSI; - + constexpr auto tree = [] { + auto t = tree_8m; + t.spi123_src = ClockDomain::ClockTree::Source::HSI; + return t; + }(); std::array entries{{ {.group = SpiModel::group, .try_solve = &SpiModel::try_solve}, }}; @@ -31,10 +32,11 @@ static_assert([] { static_assert([] { using SpiModel = SPIClockModel; - - auto tree = tree_8m; - tree.spi45_src = ClockDomain::ClockTree::Source::HSI; - + constexpr auto tree = [] { + auto t = tree_8m; + t.spi45_src = ClockDomain::ClockTree::Source::HSI; + return t; + }(); std::array entries{{ {.group = SpiModel::group, .try_solve = &SpiModel::try_solve}, }}; @@ -45,10 +47,11 @@ static_assert([] { static_assert([] { using SpiA = SPIClockModel; using SpiB = SPIClockModel; - - auto tree = tree_8m; - tree.spi45_src = ClockDomain::ClockTree::Source::HSI; - + constexpr auto tree = [] { + auto t = tree_8m; + t.spi45_src = ClockDomain::ClockTree::Source::HSI; + return t; + }(); std::array entries{{ {.group = SpiA::group, .try_solve = &SpiA::try_solve}, {.group = SpiB::group, .try_solve = &SpiB::try_solve}, @@ -60,11 +63,12 @@ static_assert([] { static_assert([] { using SpiA = SPIClockModel; using SpiB = SPIClockModel; - - auto tree = tree_8m; - tree.spi123_src = ClockDomain::ClockTree::Source::HSI; - tree.spi45_src = ClockDomain::ClockTree::Source::PCLK2; - + constexpr auto tree = [] { + auto t = tree_8m; + t.spi123_src = ClockDomain::ClockTree::Source::HSI; + t.spi45_src = ClockDomain::ClockTree::Source::PCLK2; + return t; + }(); std::array entries{{ {.group = SpiA::group, .try_solve = &SpiA::try_solve}, {.group = SpiB::group, .try_solve = &SpiB::try_solve}, @@ -74,9 +78,8 @@ static_assert([] { }()); static_assert([] { - auto tree = tree_8m; std::array entries{}; - ClockDomain::validate(tree, std::span{entries}); + ClockDomain::validate(tree_8m, std::span{entries}); return true; }()); @@ -84,12 +87,13 @@ static_assert([] { using Spi123 = SPIClockModel; using Spi45 = SPIClockModel; using Spi6 = SPIClockModel; - - auto tree = tree_8m; - tree.spi123_src = ClockDomain::ClockTree::Source::HSI; - tree.spi45_src = ClockDomain::ClockTree::Source::HSI; - tree.spi6_src = ClockDomain::ClockTree::Source::HSI; - + constexpr auto tree = [] { + auto t = tree_8m; + t.spi123_src = ClockDomain::ClockTree::Source::HSI; + t.spi45_src = ClockDomain::ClockTree::Source::HSI; + t.spi6_src = ClockDomain::ClockTree::Source::HSI; + return t; + }(); std::array entries{{ {.group = Spi123::group, .try_solve = &Spi123::try_solve}, {.group = Spi45::group, .try_solve = &Spi45::try_solve}, diff --git a/Tests/Clocks/clock_stress_test.cpp b/Tests/Clocks/clock_stress_test.cpp index 152c641ea..05df7666f 100644 --- a/Tests/Clocks/clock_stress_test.cpp +++ b/Tests/Clocks/clock_stress_test.cpp @@ -34,10 +34,11 @@ static_assert([] { static_assert([] { using SpiA = SPIClockModel; using SpiB = SPIClockModel; - - auto tree = tree_8m; - tree.spi45_src = ClockDomain::ClockTree::Source::PCLK2; - + constexpr auto tree = [] { + auto t = tree_8m; + t.spi45_src = ClockDomain::ClockTree::Source::PCLK2; + return t; + }(); std::array entries{{ {.group = SpiA::group, .try_solve = &SpiA::try_solve}, {.group = SpiB::group, .try_solve = &SpiB::try_solve}, @@ -50,12 +51,13 @@ static_assert([] { using Spi1 = SPIClockModel; using Spi4 = SPIClockModel; using Spi6 = SPIClockModel; - - auto tree = tree_8m; - tree.spi123_src = ClockDomain::ClockTree::Source::HSI; - tree.spi45_src = ClockDomain::ClockTree::Source::HSI; - tree.spi6_src = ClockDomain::ClockTree::Source::HSI; - + constexpr auto tree = [] { + auto t = tree_8m; + t.spi123_src = ClockDomain::ClockTree::Source::HSI; + t.spi45_src = ClockDomain::ClockTree::Source::HSI; + t.spi6_src = ClockDomain::ClockTree::Source::HSI; + return t; + }(); std::array entries{{ {.group = Spi1::group, .try_solve = &Spi1::try_solve}, {.group = Spi4::group, .try_solve = &Spi4::try_solve}, @@ -73,10 +75,11 @@ static_assert([] { static_assert([] { using Spi = SPIClockModel; - - auto tree = tree_8m; - tree.spi123_src = ClockDomain::ClockTree::Source::HSI; - + constexpr auto tree = [] { + auto t = tree_8m; + t.spi123_src = ClockDomain::ClockTree::Source::HSI; + return t; + }(); std::array entries; for (size_t i = 0; i < 10; i++) { entries[i] = {.group = Spi::group, .try_solve = &Spi::try_solve}; From ebf72c95ef074c01ffb3102457ce41f0a917ec94 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 04:13:38 +0200 Subject: [PATCH 13/21] chore(CI): Fix tests, again --- Tests/Clocks/clock_group_test.cpp | 12 ++++++------ Tests/Clocks/clock_stress_test.cpp | 15 +++++++++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Tests/Clocks/clock_group_test.cpp b/Tests/Clocks/clock_group_test.cpp index 2b017eca3..223344d16 100644 --- a/Tests/Clocks/clock_group_test.cpp +++ b/Tests/Clocks/clock_group_test.cpp @@ -23,7 +23,7 @@ static_assert([] { t.spi123_src = ClockDomain::ClockTree::Source::HSI; return t; }(); - std::array entries{{ + constexpr std::array entries{{ {.group = SpiModel::group, .try_solve = &SpiModel::try_solve}, }}; ClockDomain::validate(tree, std::span{entries}); @@ -37,7 +37,7 @@ static_assert([] { t.spi45_src = ClockDomain::ClockTree::Source::HSI; return t; }(); - std::array entries{{ + constexpr std::array entries{{ {.group = SpiModel::group, .try_solve = &SpiModel::try_solve}, }}; ClockDomain::validate(tree, std::span{entries}); @@ -52,7 +52,7 @@ static_assert([] { t.spi45_src = ClockDomain::ClockTree::Source::HSI; return t; }(); - std::array entries{{ + constexpr std::array entries{{ {.group = SpiA::group, .try_solve = &SpiA::try_solve}, {.group = SpiB::group, .try_solve = &SpiB::try_solve}, }}; @@ -69,7 +69,7 @@ static_assert([] { t.spi45_src = ClockDomain::ClockTree::Source::PCLK2; return t; }(); - std::array entries{{ + constexpr std::array entries{{ {.group = SpiA::group, .try_solve = &SpiA::try_solve}, {.group = SpiB::group, .try_solve = &SpiB::try_solve}, }}; @@ -78,7 +78,7 @@ static_assert([] { }()); static_assert([] { - std::array entries{}; + constexpr std::array entries{}; ClockDomain::validate(tree_8m, std::span{entries}); return true; }()); @@ -94,7 +94,7 @@ static_assert([] { t.spi6_src = ClockDomain::ClockTree::Source::HSI; return t; }(); - std::array entries{{ + constexpr std::array entries{{ {.group = Spi123::group, .try_solve = &Spi123::try_solve}, {.group = Spi45::group, .try_solve = &Spi45::try_solve}, {.group = Spi6::group, .try_solve = &Spi6::try_solve}, diff --git a/Tests/Clocks/clock_stress_test.cpp b/Tests/Clocks/clock_stress_test.cpp index 05df7666f..4592dd40b 100644 --- a/Tests/Clocks/clock_stress_test.cpp +++ b/Tests/Clocks/clock_stress_test.cpp @@ -39,7 +39,7 @@ static_assert([] { t.spi45_src = ClockDomain::ClockTree::Source::PCLK2; return t; }(); - std::array entries{{ + constexpr std::array entries{{ {.group = SpiA::group, .try_solve = &SpiA::try_solve}, {.group = SpiB::group, .try_solve = &SpiB::try_solve}, }}; @@ -58,7 +58,7 @@ static_assert([] { t.spi6_src = ClockDomain::ClockTree::Source::HSI; return t; }(); - std::array entries{{ + constexpr std::array entries{{ {.group = Spi1::group, .try_solve = &Spi1::try_solve}, {.group = Spi4::group, .try_solve = &Spi4::try_solve}, {.group = Spi6::group, .try_solve = &Spi6::try_solve}, @@ -80,10 +80,13 @@ static_assert([] { t.spi123_src = ClockDomain::ClockTree::Source::HSI; return t; }(); - std::array entries; - for (size_t i = 0; i < 10; i++) { - entries[i] = {.group = Spi::group, .try_solve = &Spi::try_solve}; - } + constexpr std::array entries = [&] { + std::array e{}; + for (size_t i = 0; i < 10; i++) { + e[i] = {.group = Spi::group, .try_solve = &Spi::try_solve}; + } + return e; + }(); ClockDomain::validate(tree, std::span{entries}); return true; }()); From 0f9ad81a406205f8032bc639b733a5e722468913 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 15:28:58 +0200 Subject: [PATCH 14/21] feat(ClockDomain): Add global accessors for ClockDomain frequencies --- Inc/HALAL/Models/Clocks/ClockDomain.hpp | 15 +++++++++++++++ Inc/HALAL/Models/SPI/SPI2.hpp | 20 +++++++++----------- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 6 +++--- Inc/HALAL/Services/ADC/ADC.hpp | 13 ++++--------- Inc/ST-LIB.hpp | 11 ++++------- Inc/ST-LIB_LOW/Sd/Sd.hpp | 6 +++--- Tests/adc_sensor_test.cpp | 1 + Tests/adc_test.cpp | 5 ++++- Tests/spi2_test.cpp | 4 ++-- 9 files changed, 45 insertions(+), 36 deletions(-) diff --git a/Inc/HALAL/Models/Clocks/ClockDomain.hpp b/Inc/HALAL/Models/Clocks/ClockDomain.hpp index bfdf3fc01..61de5a126 100644 --- a/Inc/HALAL/Models/Clocks/ClockDomain.hpp +++ b/Inc/HALAL/Models/Clocks/ClockDomain.hpp @@ -262,6 +262,21 @@ struct ClockDomain { * ========================================= */ + static inline const ClockTree* s_tree = nullptr; + + static uint32_t pll1_input() { return pll1_input(*s_tree); } + static uint32_t pll1_vco() { return pll1_vco(*s_tree); } + static uint32_t sysclk() { return sysclk(*s_tree); } + static uint32_t hclk() { return hclk(*s_tree); } + static uint32_t pclk1() { return pclk1(*s_tree); } + static uint32_t pclk2() { return pclk2(*s_tree); } + static uint32_t timer_apb1() { return timer_apb1(*s_tree); } + static uint32_t timer_apb2() { return timer_apb2(*s_tree); } + static uint32_t source_frequency(ClockTree::Source src) { + return source_frequency(*s_tree, src); + } + static uint32_t get_kernel_clock(ClockGroup group) { return get_kernel_clock(*s_tree, group); } + static constexpr std::size_t max_instances = 32; struct Entry { diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index 36b9e9763..88a25da3b 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -201,6 +201,13 @@ struct SPIDomain { return SPI45_G; case spi6: return SPI6_G; + default: + if consteval { + compile_error("Invalid SPI peripheral"); + } else { + PANIC("Invalid SPI peripheral"); + return SPI123_G; + } } } @@ -1395,8 +1402,7 @@ struct SPIDomain { static void init( std::span cfgs, std::span gpio_instances, - std::span dma_peripherals, - const ClockDomain::ClockTree& tree + std::span dma_peripherals ) { for (std::size_t i = 0; i < N; ++i) { const auto& e = cfgs[i]; @@ -1462,15 +1468,7 @@ struct SPIDomain { if (e.mode == SPIMode::MASTER) { init.Mode = SPI_MODE_MASTER; // Baudrate prescaler from solved tree - uint32_t pclk_freq; - if (peripheral == SPIPeripheral::spi1 || peripheral == SPIPeripheral::spi2 || - peripheral == SPIPeripheral::spi3) { - pclk_freq = ClockDomain::source_frequency(tree, tree.spi123_src); - } else if (peripheral == SPIPeripheral::spi4 || peripheral == SPIPeripheral::spi5) { - pclk_freq = ClockDomain::source_frequency(tree, tree.spi45_src); - } else { - pclk_freq = ClockDomain::source_frequency(tree, tree.spi6_src); - } + uint32_t pclk_freq = ClockDomain::get_kernel_clock(spi_group(peripheral)); if (pclk_freq == 0) { PANIC("SPI kernel clock not configured by ClockDomain"); } diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index f4084c9aa..36e7bc49a 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -774,7 +774,7 @@ struct TimerDomain { static void TIM_Default_Callback(void* raw) { (void)raw; } - static void init(std::span cfgs, const ClockDomain::ClockTree& tree) { + static void init(std::span cfgs) { Scheduler_global_timer = cmsis_timers[timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; rcc_enable_timer(Scheduler_global_timer); @@ -834,8 +834,8 @@ struct TimerDomain { inst->tim = tim; inst->hal_tim = handle; inst->timer_idx = e.timer_idx; - inst->clock_frequency = is_timer_on_apb1(e.request) ? ClockDomain::timer_apb1(tree) - : ClockDomain::timer_apb2(tree); + inst->clock_frequency = is_timer_on_apb1(e.request) ? ClockDomain::timer_apb1() + : ClockDomain::timer_apb2(); TIM_MasterConfigTypeDef sMasterConfig = {}; sMasterConfig.MasterOutputTrigger = static_cast(e.trgo1); sMasterConfig.MasterOutputTrigger2 = static_cast(e.trgo2); diff --git a/Inc/HALAL/Services/ADC/ADC.hpp b/Inc/HALAL/Services/ADC/ADC.hpp index 9fe82b403..566722717 100644 --- a/Inc/HALAL/Services/ADC/ADC.hpp +++ b/Inc/HALAL/Services/ADC/ADC.hpp @@ -895,11 +895,7 @@ struct ADCDomain { return &dma_buffer_pool[buffer_offset + index]; } - static void configure_peripheral( - const Config& cfg, - uint8_t channel_count, - const ClockDomain::ClockTree& tree - ) { + static void configure_peripheral(const Config& cfg, uint8_t channel_count) { ADC_HandleTypeDef* hadc = handle_for(cfg.peripheral); if (cfg.peripheral == Peripheral::ADC_1 || cfg.peripheral == Peripheral::ADC_2) { @@ -912,7 +908,7 @@ struct ADCDomain { HAL_SYSCFG_AnalogSwitchConfig(SYSCFG_SWITCH_PC3, SYSCFG_SWITCH_PC3_OPEN); } - uint32_t kernel_clk = ClockDomain::source_frequency(tree, tree.adc_src); + uint32_t kernel_clk = ClockDomain::get_kernel_clock(ClockDomain::ClockGroup::ADC_G); uint32_t max_clk = adc_max_clk(cfg.resolution); uint32_t prescaler = 4; constexpr uint32_t prescalers[] = {1, 2, 4, 6, 8, 10, 12, 16, 32, 64, 128, 256}; @@ -947,8 +943,7 @@ struct ADCDomain { static void init( std::span runtime_cfgs, std::span gpio_instances = std::span{}, - std::span dma_peripherals = std::span{}, - const ClockDomain::ClockTree& tree = {} + std::span dma_peripherals = std::span{} ) { std::array periph_ready{false, false, false}; std::array periph_channel_counts{0, 0, 0}; @@ -1007,7 +1002,7 @@ struct ADCDomain { hadc->DMA_Handle = &dma_instance->dma; dma_instance->dma.Parent = hadc; - configure_peripheral(*first_cfg, channel_count, tree); + configure_peripheral(*first_cfg, channel_count); if (HAL_ADC_Init(hadc) != HAL_OK) { PANIC("ADC Init failed"); diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 65ec3758f..3d02f398a 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -314,13 +314,12 @@ template stru MPUDomain::Init::init(); GPIODomain::Init::init(cfg.gpio_cfgs); - TimerDomain::Init::init(cfg.tim_cfgs, cfg.clock_tree); + TimerDomain::Init::init(cfg.tim_cfgs); DMADomain::Init::init(cfg.dma_cfgs); SPIDomain::Init::init( cfg.spi_cfgs, GPIODomain::Init::instances, - DMADomain::Init::instances, - cfg.clock_tree + DMADomain::Init::instances ); DigitalOutputDomain::Init::init(cfg.dout_cfgs, GPIODomain::Init::instances); DigitalInputDomain::Init::init(cfg.din_cfgs, GPIODomain::Init::instances); @@ -331,15 +330,13 @@ template stru SdDomain::Init::init( cfg.sd_cfgs, MPUDomain::Init::instances, - DigitalInputDomain::Init::instances, - cfg.clock_tree + DigitalInputDomain::Init::instances ); EthernetDomain::Init::init(cfg.eth_cfgs, DigitalOutputDomain::Init::instances); ADCDomain::Init::init( cfg.adc_cfgs, GPIODomain::Init::instances, - DMADomain::Init::instances, - cfg.clock_tree + DMADomain::Init::instances ); EXTIDomain::Init::init(cfg.exti_cfgs, GPIODomain::Init::instances); diff --git a/Inc/ST-LIB_LOW/Sd/Sd.hpp b/Inc/ST-LIB_LOW/Sd/Sd.hpp index 94e34e4dc..99d443411 100644 --- a/Inc/ST-LIB_LOW/Sd/Sd.hpp +++ b/Inc/ST-LIB_LOW/Sd/Sd.hpp @@ -487,8 +487,7 @@ struct SdDomain { static void init( std::span cfgs, std::span mpu_buffer_instances, - std::span digital_input_instances, - const ClockDomain::ClockTree& tree + std::span digital_input_instances ) { if (N == 0) { @@ -529,7 +528,8 @@ struct SdDomain { #ifdef SD_DEBUG_ENABLE inst.hsd.Init.BusWide = SDMMC_BUS_WIDE_1B; #endif - uint32_t sdmmc_clk = ClockDomain::source_frequency(tree, tree.sdmmc_src); + uint32_t sdmmc_clk = + ClockDomain::get_kernel_clock(ClockDomain::ClockGroup::SDMMC_G); if (sdmmc_clk == 0) { PANIC("SDMMC clock not configured by ClockDomain"); } diff --git a/Tests/adc_sensor_test.cpp b/Tests/adc_sensor_test.cpp index 194b5c9df..4667d246c 100644 --- a/Tests/adc_sensor_test.cpp +++ b/Tests/adc_sensor_test.cpp @@ -85,6 +85,7 @@ void clear_dma_irq_table() { class ADCSensorTest : public ::testing::Test { protected: void SetUp() override { + ST_LIB::ClockDomain::s_tree = &ST_LIB::default_clock_tree; ST_LIB::MockedHAL::adc_reset(); ST_LIB::MockedHAL::dma_reset(); clear_nvic_enables(); diff --git a/Tests/adc_test.cpp b/Tests/adc_test.cpp index 81afc5058..f0dc6d375 100644 --- a/Tests/adc_test.cpp +++ b/Tests/adc_test.cpp @@ -317,7 +317,10 @@ class ADCTest : public ::testing::Test { clear_dma_irq_table(); } - void SetUp() override { reset_runtime_state(); } + void SetUp() override { + reset_runtime_state(); + ST_LIB::ClockDomain::s_tree = &ST_LIB::default_clock_tree; + } template & InitCfgs> void init_adc_with_dma(const std::array& cfgs) { diff --git a/Tests/spi2_test.cpp b/Tests/spi2_test.cpp index 4a14ab8ea..7fcb124fa 100644 --- a/Tests/spi2_test.cpp +++ b/Tests/spi2_test.cpp @@ -126,11 +126,11 @@ class SPI2Test : public ::testing::Test { }}; auto tree = ST_LIB::ClockDomain::ClockTree{}; + ST_LIB::ClockDomain::s_tree = &tree; ST_LIB::SPIDomain::Init<1>::init( cfgs, std::span{}, - std::span(ST_LIB::DMADomain::Init<2>::instances), - tree + std::span(ST_LIB::DMADomain::Init<2>::instances) ); return ST_LIB::SPIDomain::Init<1>::instances[0]; } From 3b576f50c7cac8c6fa5e6d5abf2194aded343479 Mon Sep 17 00:00:00 2001 From: victhor Date: Sun, 21 Jun 2026 17:24:19 +0200 Subject: [PATCH 15/21] Use global ClockDomain instance in timers --- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 37 ++------------------ Inc/HALAL/Services/Time/TimerWrapper.hpp | 9 +++-- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 36e7bc49a..db1a01409 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -294,7 +294,6 @@ struct TimerDomain { struct Config { uint8_t timer_idx; - TimerRequest request; SelectionTrigger1 trgo1; SelectionTrigger2 trgo2; }; @@ -641,7 +640,6 @@ struct TimerDomain { Config cfg = { .timer_idx = timer_idxmap[reqint], - .request = requests[i].request, .trgo1 = requests[i].trgo1, .trgo2 = requests[i].trgo2 }; @@ -676,7 +674,6 @@ struct TimerDomain { uint8_t reqint = remaining_32bit_timers[count_32bit_requests]; Config cfg = { .timer_idx = timer_idxmap[reqint], - .request = e.request, .trgo1 = e.trgo1, .trgo2 = e.trgo2 }; @@ -728,7 +725,6 @@ struct TimerDomain { uint8_t reqint = remaining_timers[i]; Config cfg = { .timer_idx = timer_idxmap[reqint], - .request = e.request, .trgo1 = e.trgo1, .trgo2 = e.trgo2 }; @@ -744,28 +740,8 @@ struct TimerDomain { TIM_HandleTypeDef* hal_tim; TIM_MasterConfigTypeDef master{}; uint8_t timer_idx; - uint32_t clock_frequency = 0; // from ClockTree }; - static constexpr bool is_timer_on_apb1(TimerRequest r) { - switch (r) { - case TimerRequest::GeneralPurpose32bit_2: - case TimerRequest::GeneralPurpose_3: - case TimerRequest::GeneralPurpose_4: - case TimerRequest::GeneralPurpose32bit_5: - case TimerRequest::Basic_6: - case TimerRequest::Basic_7: - case TimerRequest::SlaveTimer_12: - case TimerRequest::SlaveTimer_13: - case TimerRequest::SlaveTimer_14: - case TimerRequest::GeneralPurpose32bit_23: - case TimerRequest::GeneralPurpose32bit_24: - return true; - default: - return false; - } - } - static void (*callbacks[TimerDomain::max_instances])(void*); static void* callback_data[TimerDomain::max_instances]; @@ -834,8 +810,6 @@ struct TimerDomain { inst->tim = tim; inst->hal_tim = handle; inst->timer_idx = e.timer_idx; - inst->clock_frequency = is_timer_on_apb1(e.request) ? ClockDomain::timer_apb1() - : ClockDomain::timer_apb2(); TIM_MasterConfigTypeDef sMasterConfig = {}; sMasterConfig.MasterOutputTrigger = static_cast(e.trgo1); sMasterConfig.MasterOutputTrigger2 = static_cast(e.trgo2); @@ -901,19 +875,12 @@ struct TimerDomain { uint32_t result = 0; if ((tim == TIM2) || (tim == TIM3) || (tim == TIM4) || (tim == TIM5) || (tim == TIM6) || (tim == TIM7) || (tim == TIM12) || (tim == TIM13) || (tim == TIM14)) { - result = HAL_RCC_GetPCLK1Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) != RCC_HCLK_DIV1) { - result *= 2; - } + result = ClockDomain::timer_apb1(); } else if ((tim == TIM1) || (tim == TIM8) || (tim == TIM15) || (tim == TIM16) || (tim == TIM17) || (tim == TIM23) || (tim == TIM24)) { - result = HAL_RCC_GetPCLK2Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { - result *= 2; - } + result = ClockDomain::timer_apb2(); } else { PANIC("Invalid timer ptr"); } - return result; } }; diff --git a/Inc/HALAL/Services/Time/TimerWrapper.hpp b/Inc/HALAL/Services/Time/TimerWrapper.hpp index d41878cf9..9d41e4292 100644 --- a/Inc/HALAL/Services/Time/TimerWrapper.hpp +++ b/Inc/HALAL/Services/Time/TimerWrapper.hpp @@ -135,10 +135,13 @@ template struct TimerWrapper { } inline uint32_t get_clock_frequency() { - if (instance->clock_frequency == 0) { - PANIC("Timer clock not configured by ClockDomain"); + uint32_t result; + if constexpr (this->is_on_APB1) { + result = ClockDomain::timer_apb1(); + } else { + result = ClockDomain::timer_apb2(); } - return instance->clock_frequency; + return result; } template From 82772b1556523c3b4b71ed0d453e7cef2f9a3f36 Mon Sep 17 00:00:00 2001 From: Boris Mladenov Beslimov Date: Sun, 21 Jun 2026 17:38:33 +0200 Subject: [PATCH 16/21] Fix/hardfaulttrace warning symbols (#663) * fix: Fix a warning about symbols missmatch * chore: Add changeset --- .changesets/fix-hwarfault-warning.md | 2 ++ Inc/HALAL/HardFault/HardfaultTrace.h | 4 ++-- Src/HALAL/HardFault/HardfaultTrace.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 .changesets/fix-hwarfault-warning.md diff --git a/.changesets/fix-hwarfault-warning.md b/.changesets/fix-hwarfault-warning.md new file mode 100644 index 000000000..7f25091ea --- /dev/null +++ b/.changesets/fix-hwarfault-warning.md @@ -0,0 +1,2 @@ +release: patch +summary: Fix a warning about symbols in HardfaultTrace (doesn't change any behaviour) diff --git a/Inc/HALAL/HardFault/HardfaultTrace.h b/Inc/HALAL/HardFault/HardfaultTrace.h index 5fe2d7c40..22021cbfc 100644 --- a/Inc/HALAL/HardFault/HardfaultTrace.h +++ b/Inc/HALAL/HardFault/HardfaultTrace.h @@ -9,8 +9,8 @@ #ifdef __cplusplus extern "C" { #endif -extern uint32_t _metadata; -extern uint32_t _hf_log; +extern uint8_t _metadata[]; +extern uint8_t _hf_log[]; #ifdef __cplusplus } #endif diff --git a/Src/HALAL/HardFault/HardfaultTrace.c b/Src/HALAL/HardFault/HardfaultTrace.c index 0aa1d7c18..01f9c3e78 100644 --- a/Src/HALAL/HardFault/HardfaultTrace.c +++ b/Src/HALAL/HardFault/HardfaultTrace.c @@ -18,7 +18,7 @@ extern GPIO_TypeDef* ports_hard_fault[]; extern uint16_t pins_hard_fault[]; extern uint8_t hard_fault_leds_count; -extern uint32_t _hf_log; +extern uint8_t _hf_log[]; static void LED_Blink(); static void LED_init(void); From f868613e8f8a8cfb7df7d32d0119aaca751edd52 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 18:02:42 +0200 Subject: [PATCH 17/21] style: Run pre-commit diff --git c/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp i/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index db1a0140..8219d4f8 100644 --- c/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ i/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -672,11 +672,8 @@ struct TimerDomain { } uint8_t reqint = remaining_32bit_timers[count_32bit_requests]; - Config cfg = { - .timer_idx = timer_idxmap[reqint], - .trgo1 = e.trgo1, - .trgo2 = e.trgo2 - }; + Config cfg = + {.timer_idx = timer_idxmap[reqint], .trgo1 = e.trgo1, .trgo2 = e.trgo2}; cfgs[cfg_idx++] = cfg; // unordered remove @@ -723,11 +720,7 @@ struct TimerDomain { ST_LIB::compile_error("This only processes TimerRequest::AnyGeneralPurpose"); } uint8_t reqint = remaining_timers[i]; - Config cfg = { - .timer_idx = timer_idxmap[reqint], - .trgo1 = e.trgo1, - .trgo2 = e.trgo2 - }; + Config cfg = {.timer_idx = timer_idxmap[reqint], .trgo1 = e.trgo1, .trgo2 = e.trgo2}; cfgs[cfg_idx++] = cfg; } --- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index db1a01409..8219d4f8d 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -672,11 +672,8 @@ struct TimerDomain { } uint8_t reqint = remaining_32bit_timers[count_32bit_requests]; - Config cfg = { - .timer_idx = timer_idxmap[reqint], - .trgo1 = e.trgo1, - .trgo2 = e.trgo2 - }; + Config cfg = + {.timer_idx = timer_idxmap[reqint], .trgo1 = e.trgo1, .trgo2 = e.trgo2}; cfgs[cfg_idx++] = cfg; // unordered remove @@ -723,11 +720,7 @@ struct TimerDomain { ST_LIB::compile_error("This only processes TimerRequest::AnyGeneralPurpose"); } uint8_t reqint = remaining_timers[i]; - Config cfg = { - .timer_idx = timer_idxmap[reqint], - .trgo1 = e.trgo1, - .trgo2 = e.trgo2 - }; + Config cfg = {.timer_idx = timer_idxmap[reqint], .trgo1 = e.trgo1, .trgo2 = e.trgo2}; cfgs[cfg_idx++] = cfg; } From 04f9d1ba7c7beacde43f08053ccd08faea09c021 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sun, 21 Jun 2026 19:29:39 +0200 Subject: [PATCH 18/21] chore: Fix tests --- Tests/StateMachine/state_machine_test.cpp | 1 + Tests/Time/scheduler_test.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Tests/StateMachine/state_machine_test.cpp b/Tests/StateMachine/state_machine_test.cpp index 0e991f782..1158629da 100644 --- a/Tests/StateMachine/state_machine_test.cpp +++ b/Tests/StateMachine/state_machine_test.cpp @@ -141,6 +141,7 @@ static inline auto test_machine = []() consteval { class StateMachineTest : public ::testing::Test { protected: void SetUp() override { + ::ST_LIB::ClockDomain::s_tree = &::ST_LIB::default_clock_tree; reset_test_state(); test_machine.force_change_state((size_t)MasterState::A); diff --git a/Tests/Time/scheduler_test.cpp b/Tests/Time/scheduler_test.cpp index ed20ab2da..64a634588 100644 --- a/Tests/Time/scheduler_test.cpp +++ b/Tests/Time/scheduler_test.cpp @@ -2,6 +2,7 @@ #include #include +#include "HALAL/Models/Clocks/ClockDomain.hpp" #include "HALAL/Services/Time/Scheduler.hpp" int count = 0; @@ -10,6 +11,7 @@ void fake_workload() { count++; } class SchedulerTests : public ::testing::Test { protected: void SetUp() override { + ::ST_LIB::ClockDomain::s_tree = &::ST_LIB::default_clock_tree; Scheduler::active_task_count_ = 0; Scheduler::free_bitmap_ = 0xFFFF'FFFF; Scheduler::ready_bitmap_ = 0; From 02a0d5647877f1cc73ab06ef5dca0e4da04b240c Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Mon, 22 Jun 2026 00:35:37 +0200 Subject: [PATCH 19/21] fix: I hate arm-none-eabi-gcc --- Inc/HALAL/Models/SPI/SPI2.hpp | 43 ++++++++++++++-------------------- Inc/HALAL/Services/ADC/ADC.hpp | 24 +++++++++---------- Inc/ST-LIB_LOW/Sd/Sd.hpp | 16 ++++++------- 3 files changed, 36 insertions(+), 47 deletions(-) diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index 88a25da3b..bc20a5015 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -252,13 +252,11 @@ struct SPIDomain { * Request Object * ========================================= */ - template struct Device { + template struct Device { using domain = SPIDomain; - SPIPeripheral peripheral; + static constexpr auto peripheral = Periph; SPIMode mode; - uint32_t max_baudrate; - uint32_t min_baudrate; SPIConfig config; GPIODomain::GPIO sck_gpio; @@ -268,19 +266,17 @@ struct SPIDomain { DMADomain::DMA dma_rx_tx; + ClockDomain::Device clock_device; + consteval Device( SPIMode mode, - SPIPeripheral peripheral, - uint32_t max_baudrate, - uint32_t min_baudrate, GPIODomain::Pin sck_pin, GPIODomain::Pin miso_pin, GPIODomain::Pin mosi_pin, GPIODomain::Pin nss_pin, SPIConfig config = SPIConfig{} ) - : peripheral{peripheral}, mode{mode}, max_baudrate{max_baudrate}, - min_baudrate{min_baudrate}, config{config}, sck_gpio( + : mode{mode}, config{config}, sck_gpio( sck_pin, GPIODomain::OperationMode::ALT_PP, GPIODomain::Pull::None, @@ -308,7 +304,11 @@ struct SPIDomain { GPIODomain::Speed::VeryHigh, get_af(nss_pin, peripheral) )), - dma_rx_tx(dma_peripheral(peripheral)) { + dma_rx_tx(dma_peripheral(peripheral)), + clock_device{ + .group = spi_group(peripheral), + .try_solve = &SPIClockModel::try_solve, + } { config.validate(); if (config.nss_mode == NSSMode::SOFTWARE) { @@ -324,16 +324,12 @@ struct SPIDomain { // Constructor without NSS pin (for software NSS mode) consteval Device( SPIMode mode, - SPIPeripheral peripheral, - uint32_t max_baudrate, - uint32_t min_baudrate, GPIODomain::Pin sck_pin, GPIODomain::Pin miso_pin, GPIODomain::Pin mosi_pin, SPIConfig config ) - : peripheral{peripheral}, mode{mode}, max_baudrate{max_baudrate}, - min_baudrate{min_baudrate}, config{config}, sck_gpio( + : mode{mode}, config{config}, sck_gpio( sck_pin, GPIODomain::OperationMode::ALT_PP, GPIODomain::Pull::None, @@ -355,7 +351,11 @@ struct SPIDomain { get_af(mosi_pin, peripheral) ), nss_gpio(std::nullopt), // No NSS GPIO - dma_rx_tx(dma_peripheral(peripheral)) { + dma_rx_tx(dma_peripheral(peripheral)), + clock_device{ + .group = spi_group(peripheral), + .try_solve = &SPIClockModel::try_solve, + } { config.validate(); if (config.nss_mode == NSSMode::HARDWARE) { @@ -386,17 +386,10 @@ struct SPIDomain { e.nss_gpio_idx = nss_idx; e.dma_rx_idx = dma_indices[0]; e.dma_tx_idx = dma_indices[1]; - e.max_baudrate = max_baudrate; - e.min_baudrate = min_baudrate; + e.max_baudrate = MaxBaud; + e.min_baudrate = MinBaud; e.config = config; - // Register clock requirement with ClockDomain (don't need the index) - using Model = SPIClockModel; - auto clock_device = ClockDomain::Device{ - .group = Model::group, - .try_solve = &Model::try_solve, - .owner = this, - }; clock_device.inscribe(ctx); return ctx.template add(e, this); diff --git a/Inc/HALAL/Services/ADC/ADC.hpp b/Inc/HALAL/Services/ADC/ADC.hpp index 566722717..dd498969e 100644 --- a/Inc/HALAL/Services/ADC/ADC.hpp +++ b/Inc/HALAL/Services/ADC/ADC.hpp @@ -163,16 +163,22 @@ struct ADCDomain { } } + template struct ADC { GPIODomain::GPIO gpio; using domain = ADCDomain; + using Model = ADCClockModel; + static constexpr auto resolution = Res; Entry e; + ClockDomain::Device clock_device = { + .group = Model::group, + .try_solve = &Model::try_solve, + }; consteval ADC( const GPIODomain::Pin& pin, float& output, - Resolution resolution = Resolution::BITS_12, SampleTime sample_time = SampleTime::CYCLES_8_5, uint32_t sample_rate_hz = 0, Peripheral peripheral = Peripheral::AUTO, @@ -183,14 +189,13 @@ struct ADCDomain { .pin = pin, .peripheral = peripheral, .channel = channel, - .resolution = resolution, + .resolution = Res, .sample_time = sample_time, .sample_rate_hz = sample_rate_hz, .output = &output} {} consteval ADC( const GPIODomain::Pin& pin, - Resolution resolution = Resolution::BITS_12, SampleTime sample_time = SampleTime::CYCLES_8_5, uint32_t sample_rate_hz = 0, Peripheral peripheral = Peripheral::AUTO, @@ -201,7 +206,7 @@ struct ADCDomain { .pin = pin, .peripheral = peripheral, .channel = channel, - .resolution = resolution, + .resolution = Res, .sample_time = sample_time, .sample_rate_hz = sample_rate_hz, .output = nullptr} {} @@ -211,21 +216,19 @@ struct ADCDomain { Peripheral peripheral, Channel channel, float& output, - Resolution resolution = Resolution::BITS_12, SampleTime sample_time = SampleTime::CYCLES_8_5, uint32_t sample_rate_hz = 0 ) - : ADC(pin, output, resolution, sample_time, sample_rate_hz, peripheral, channel) {} + : ADC(pin, output, sample_time, sample_rate_hz, peripheral, channel) {} consteval ADC( const GPIODomain::Pin& pin, Peripheral peripheral, Channel channel, - Resolution resolution = Resolution::BITS_12, SampleTime sample_time = SampleTime::CYCLES_8_5, uint32_t sample_rate_hz = 0 ) - : ADC(pin, resolution, sample_time, sample_rate_hz, peripheral, channel) {} + : ADC(pin, sample_time, sample_rate_hz, peripheral, channel) {} template consteval std::size_t inscribe(Ctx& ctx) const { const auto gpio_idx = gpio.inscribe(ctx); @@ -236,11 +239,6 @@ struct ADCDomain { entry.channel = resolved.second; // Register clock requirement with ClockDomain - using Model = ADCClockModel; - auto clock_device = ClockDomain::Device{ - .group = Model::group, - .try_solve = &Model::try_solve, - }; clock_device.inscribe(ctx); return ctx.template add(entry, this); diff --git a/Inc/ST-LIB_LOW/Sd/Sd.hpp b/Inc/ST-LIB_LOW/Sd/Sd.hpp index 99d443411..de525f479 100644 --- a/Inc/ST-LIB_LOW/Sd/Sd.hpp +++ b/Inc/ST-LIB_LOW/Sd/Sd.hpp @@ -76,11 +76,16 @@ struct SdDomain { std::size_t d3_pin_idx; }; - template struct SdCard { + template struct SdCard { using domain = SdDomain; + using Model = SDClockModel; Entry e; Peripheral peripheral; + ClockDomain::Device clock_device = { + .group = Model::group, + .try_solve = &Model::try_solve, + }; MPUDomain::Buffer> buffer0; // Alignment of 32-bit for SDMMC DMA @@ -117,11 +122,9 @@ struct SdDomain { card_detect_config, std::optional> write_protect_config, - uint32_t max_freq = 50'000'000, - uint32_t min_freq = 1'000'000, GPIODomain::Pin d0_pin_for_sdmmc1 = ST_LIB::PC8 ) - : e{.peripheral = sdmmc_peripheral, .max_freq = max_freq, .min_freq = min_freq}, + : e{.peripheral = sdmmc_peripheral, .max_freq = MaxFreq, .min_freq = MinFreq}, peripheral(sdmmc_peripheral), buffer0(MPUDomain::Buffer>( MPUDomain::MemoryType::NonCached, @@ -253,11 +256,6 @@ struct SdDomain { local_e.d3_pin_idx = d3.inscribe(ctx); // Register clock requirement with ClockDomain - using Model = SDClockModel; - auto clock_device = ClockDomain::Device{ - .group = Model::group, - .try_solve = &Model::try_solve, - }; clock_device.inscribe(ctx); return ctx.template add(local_e, this); From e1469287e87152d739b47531a10be4444628a021 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Mon, 22 Jun 2026 00:38:24 +0200 Subject: [PATCH 20/21] style: Pre-commit --- Inc/HALAL/Models/SPI/SPI2.hpp | 32 +++++++++++++++++++------------- Inc/HALAL/Services/ADC/ADC.hpp | 3 +-- Inc/ST-LIB_LOW/Sd/Sd.hpp | 6 +++++- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index bc20a5015..14c8bed54 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -252,7 +252,13 @@ struct SPIDomain { * Request Object * ========================================= */ - template struct Device { + template < + DMADomain::Stream dma_rx_stream, + DMADomain::Stream dma_tx_stream, + SPIPeripheral Periph, + uint32_t MaxBaud, + uint32_t MinBaud = 0> + struct Device { using domain = SPIDomain; static constexpr auto peripheral = Periph; @@ -277,12 +283,12 @@ struct SPIDomain { SPIConfig config = SPIConfig{} ) : mode{mode}, config{config}, sck_gpio( - sck_pin, - GPIODomain::OperationMode::ALT_PP, - GPIODomain::Pull::None, - GPIODomain::Speed::VeryHigh, - get_af(sck_pin, peripheral) - ), + sck_pin, + GPIODomain::OperationMode::ALT_PP, + GPIODomain::Pull::None, + GPIODomain::Speed::VeryHigh, + get_af(sck_pin, peripheral) + ), miso_gpio( miso_pin, GPIODomain::OperationMode::ALT_PP, @@ -330,12 +336,12 @@ struct SPIDomain { SPIConfig config ) : mode{mode}, config{config}, sck_gpio( - sck_pin, - GPIODomain::OperationMode::ALT_PP, - GPIODomain::Pull::None, - GPIODomain::Speed::VeryHigh, - get_af(sck_pin, peripheral) - ), + sck_pin, + GPIODomain::OperationMode::ALT_PP, + GPIODomain::Pull::None, + GPIODomain::Speed::VeryHigh, + get_af(sck_pin, peripheral) + ), miso_gpio( miso_pin, GPIODomain::OperationMode::ALT_PP, diff --git a/Inc/HALAL/Services/ADC/ADC.hpp b/Inc/HALAL/Services/ADC/ADC.hpp index dd498969e..a34a9a725 100644 --- a/Inc/HALAL/Services/ADC/ADC.hpp +++ b/Inc/HALAL/Services/ADC/ADC.hpp @@ -163,8 +163,7 @@ struct ADCDomain { } } - template - struct ADC { + template struct ADC { GPIODomain::GPIO gpio; using domain = ADCDomain; using Model = ADCClockModel; diff --git a/Inc/ST-LIB_LOW/Sd/Sd.hpp b/Inc/ST-LIB_LOW/Sd/Sd.hpp index de525f479..dc27cc428 100644 --- a/Inc/ST-LIB_LOW/Sd/Sd.hpp +++ b/Inc/ST-LIB_LOW/Sd/Sd.hpp @@ -76,7 +76,11 @@ struct SdDomain { std::size_t d3_pin_idx; }; - template struct SdCard { + template < + std::size_t buffer_blocks, + uint32_t MaxFreq = 50'000'000, + uint32_t MinFreq = 1'000'000> + struct SdCard { using domain = SdDomain; using Model = SDClockModel; Entry e; From 20a97d2c29df0e1f74cb63f3170313fe659f1044 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Mon, 22 Jun 2026 01:15:28 +0200 Subject: [PATCH 21/21] fix: Add none Clock source --- Inc/HALAL/Models/Clocks/ClockDomain.hpp | 122 ++++++++++++------------ Tests/adc_test.cpp | 6 ++ Tests/spi2_test.cpp | 3 + 3 files changed, 69 insertions(+), 62 deletions(-) diff --git a/Inc/HALAL/Models/Clocks/ClockDomain.hpp b/Inc/HALAL/Models/Clocks/ClockDomain.hpp index 61de5a126..5805dc06d 100644 --- a/Inc/HALAL/Models/Clocks/ClockDomain.hpp +++ b/Inc/HALAL/Models/Clocks/ClockDomain.hpp @@ -104,26 +104,16 @@ struct ClockDomain { uint32_t d2ppre2 = 2; enum class Source : uint8_t { - HSI, - HSE, - CSI, - PCLK1, - PCLK2, - PLL1Q, - PLL1R, - PLL2P, - PLL2Q, - PLL2R, - PLL3P, - PLL3Q, - PLL3R, + None, + HSI, HSE, CSI, PCLK1, PCLK2, PLL1Q, PLL1R, + PLL2P, PLL2Q, PLL2R, PLL3P, PLL3Q, PLL3R, }; - Source spi123_src = Source::HSI; - Source spi45_src = Source::HSI; - Source spi6_src = Source::HSI; - Source adc_src = Source::HSI; - Source fdcan_src = Source::HSI; - Source sdmmc_src = Source::HSI; + Source spi123_src = Source::None; + Source spi45_src = Source::None; + Source spi6_src = Source::None; + Source adc_src = Source::None; + Source fdcan_src = Source::None; + Source sdmmc_src = Source::None; }; static constexpr uint32_t find_rge(uint32_t pll_input) { @@ -184,6 +174,8 @@ struct ClockDomain { static constexpr uint32_t source_frequency(const ClockTree& t, ClockTree::Source src) { switch (src) { + case ClockTree::Source::None: + return 0; case ClockTree::Source::HSI: return 64'000'000; case ClockTree::Source::HSE: @@ -356,19 +348,19 @@ struct ClockDomain { } // PLL2/3 source consistency - if (t.spi123_src != ClockTree::Source::HSI) { + if (t.spi123_src != ClockTree::Source::None) { if (t.spi123_src == ClockTree::Source::PLL2P && (t.pll2_m == 0 || t.pll2_p == 0)) compile_error("SPI123 uses PLL2P but PLL2 not configured"); if (t.spi123_src == ClockTree::Source::PLL3P && (t.pll3_m == 0 || t.pll3_p == 0)) compile_error("SPI123 uses PLL3P but PLL3 not configured"); } - if (t.spi45_src != ClockTree::Source::HSI) { + if (t.spi45_src != ClockTree::Source::None) { if (t.spi45_src == ClockTree::Source::PLL2Q && (t.pll2_m == 0 || t.pll2_q == 0)) compile_error("SPI45 uses PLL2Q but PLL2 not configured"); if (t.spi45_src == ClockTree::Source::PLL3Q && (t.pll3_m == 0 || t.pll3_q == 0)) compile_error("SPI45 uses PLL3Q but PLL3 not configured"); } - if (t.spi6_src != ClockTree::Source::HSI) { + if (t.spi6_src != ClockTree::Source::None) { if (t.spi6_src == ClockTree::Source::PLL2P && (t.pll2_m == 0 || t.pll2_p == 0)) compile_error("SPI6 uses PLL2P but PLL2 not configured"); if (t.spi6_src == ClockTree::Source::PLL3P && (t.pll3_m == 0 || t.pll3_p == 0)) @@ -596,19 +588,21 @@ struct ClockDomain { break; } - pclk.PeriphClockSelection |= RCC_PERIPHCLK_SDMMC; - switch (t.sdmmc_src) { - case ClockTree::Source::PLL1Q: - pclk.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL; - break; - case ClockTree::Source::PLL2R: - pclk.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL2; - break; - default: - PANIC("ClockDomain: SDMMC clock source not supported"); - break; + if (t.sdmmc_src != ClockTree::Source::None) { + pclk.PeriphClockSelection |= RCC_PERIPHCLK_SDMMC; + switch (t.sdmmc_src) { + case ClockTree::Source::PLL1Q: + pclk.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL; + __HAL_RCC_PLLCLKOUT_ENABLE(RCC_PLL1_DIVQ); + break; + case ClockTree::Source::PLL2R: + pclk.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL2; + break; + default: + PANIC("ClockDomain: SDMMC clock source not supported"); + break; + } } - __HAL_RCC_PLLCLKOUT_ENABLE(RCC_PLL1_DIVQ); // Enable PLL2/PLL3 outputs that peripherals depend on if (t.spi123_src == ClockTree::Source::PLL2P || t.spi6_src == ClockTree::Source::PLL2P) @@ -625,36 +619,40 @@ struct ClockDomain { if (t.adc_src == ClockTree::Source::PLL3R) __HAL_RCC_PLL3CLKOUT_ENABLE(RCC_PLL3_DIVR); - pclk.PeriphClockSelection |= RCC_PERIPHCLK_ADC; - switch (t.adc_src) { - case ClockTree::Source::HSE: - pclk.AdcClockSelection = RCC_ADCCLKSOURCE_CLKP; - break; - case ClockTree::Source::PLL2R: - pclk.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2; - break; - case ClockTree::Source::PLL3R: - pclk.AdcClockSelection = RCC_ADCCLKSOURCE_PLL3; - break; - default: - PANIC("ClockDomain: ADC clock source not supported"); - break; + if (t.adc_src != ClockTree::Source::None) { + pclk.PeriphClockSelection |= RCC_PERIPHCLK_ADC; + switch (t.adc_src) { + case ClockTree::Source::HSE: + pclk.AdcClockSelection = RCC_ADCCLKSOURCE_CLKP; + break; + case ClockTree::Source::PLL2R: + pclk.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2; + break; + case ClockTree::Source::PLL3R: + pclk.AdcClockSelection = RCC_ADCCLKSOURCE_PLL3; + break; + default: + PANIC("ClockDomain: ADC clock source not supported"); + break; + } } - pclk.PeriphClockSelection |= RCC_PERIPHCLK_FDCAN; - switch (t.fdcan_src) { - case ClockTree::Source::PLL2Q: - pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL2; - break; - case ClockTree::Source::PLL1Q: - pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL; - break; - case ClockTree::Source::HSE: - pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE; - break; - default: - PANIC("ClockDomain: FDCAN clock source not supported"); - break; + if (t.fdcan_src != ClockTree::Source::None) { + pclk.PeriphClockSelection |= RCC_PERIPHCLK_FDCAN; + switch (t.fdcan_src) { + case ClockTree::Source::PLL2Q: + pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL2; + break; + case ClockTree::Source::PLL1Q: + pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL; + break; + case ClockTree::Source::HSE: + pclk.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE; + break; + default: + PANIC("ClockDomain: FDCAN clock source not supported"); + break; + } } if (pclk.PeriphClockSelection != 0) { diff --git a/Tests/adc_test.cpp b/Tests/adc_test.cpp index f0dc6d375..7c7dd47a0 100644 --- a/Tests/adc_test.cpp +++ b/Tests/adc_test.cpp @@ -623,6 +623,10 @@ TEST_F(ADCTest, TimedDMAWaitsForSequenceAndTransferCompletionBeforeUpdatingBuffe .output = &output}, }}; + auto adc_test_tree = *ST_LIB::ClockDomain::s_tree; + adc_test_tree.adc_src = ST_LIB::ClockDomain::ClockTree::Source::HSI; + ST_LIB::ClockDomain::s_tree = &adc_test_tree; + ST_LIB::MockedHAL::adc_enable_timed_dma(ADC1, true); ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC1, 64'000'000ULL); ST_LIB::MockedHAL::dma_set_transfer_timing(50ULL, 25ULL); @@ -632,6 +636,8 @@ TEST_F(ADCTest, TimedDMAWaitsForSequenceAndTransferCompletionBeforeUpdatingBuffe init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + ST_LIB::ClockDomain::s_tree = &ST_LIB::default_clock_tree; + auto& adc = SingleADCInit::instances[0]; const uint64_t sequence_period_ns = ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC1); ASSERT_GT(sequence_period_ns, 0U); diff --git a/Tests/spi2_test.cpp b/Tests/spi2_test.cpp index 7fcb124fa..fc4b9ab3b 100644 --- a/Tests/spi2_test.cpp +++ b/Tests/spi2_test.cpp @@ -126,6 +126,9 @@ class SPI2Test : public ::testing::Test { }}; auto tree = ST_LIB::ClockDomain::ClockTree{}; + tree.spi123_src = ST_LIB::ClockDomain::ClockTree::Source::HSI; + tree.spi45_src = ST_LIB::ClockDomain::ClockTree::Source::HSI; + tree.spi6_src = ST_LIB::ClockDomain::ClockTree::Source::HSI; ST_LIB::ClockDomain::s_tree = &tree; ST_LIB::SPIDomain::Init<1>::init( cfgs,