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` 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/Clocks/ClockDomain.hpp b/Inc/HALAL/Models/Clocks/ClockDomain.hpp new file mode 100644 index 000000000..5805dc06d --- /dev/null +++ b/Inc/HALAL/Models/Clocks/ClockDomain.hpp @@ -0,0 +1,709 @@ +#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 { + None, + HSI, HSE, CSI, PCLK1, PCLK2, PLL1Q, PLL1R, + PLL2P, PLL2Q, PLL2R, PLL3P, PLL3Q, PLL3R, + }; + 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) { + 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::None: + return 0; + 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 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 { + 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::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::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::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)) + 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; + } + + 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; + } + } + + // 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); + + 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; + } + } + + 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) { + 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 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/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index 55c145b60..14c8bed54 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -15,11 +15,43 @@ #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 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) { + return true; + } + } + return false; + } +}; + +} // namespace ST_LIB + // Forward declaration of IRQ handlers and HAL callbacks extern "C" { void SPI1_IRQHandler(void); @@ -156,6 +188,29 @@ 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; + default: + if consteval { + compile_error("Invalid SPI peripheral"); + } else { + PANIC("Invalid SPI peripheral"); + return SPI123_G; + } + } + } + static constexpr std::size_t max_instances{6}; struct Entry { @@ -170,8 +225,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 +242,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; }; /** @@ -195,13 +252,18 @@ 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; - SPIPeripheral peripheral; + static constexpr auto peripheral = Periph; SPIMode mode; - uint32_t max_baudrate; // Will set the baudrate as fast as possible under this value - SPIConfig config; // User-defined SPI configuration + SPIConfig config; GPIODomain::GPIO sck_gpio; GPIODomain::GPIO miso_gpio; @@ -210,24 +272,23 @@ struct SPIDomain { DMADomain::DMA dma_rx_tx; + ClockDomain::Device clock_device; + consteval Device( SPIMode mode, - SPIPeripheral peripheral, - uint32_t max_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}, - sck_gpio( - sck_pin, - GPIODomain::OperationMode::ALT_PP, - GPIODomain::Pull::None, - GPIODomain::Speed::VeryHigh, - get_af(sck_pin, peripheral) - ), + : mode{mode}, 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, @@ -249,7 +310,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) { @@ -265,21 +330,18 @@ struct SPIDomain { // Constructor without NSS pin (for software NSS mode) consteval Device( SPIMode mode, - SPIPeripheral peripheral, - uint32_t max_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}, - sck_gpio( - sck_pin, - GPIODomain::OperationMode::ALT_PP, - GPIODomain::Pull::None, - GPIODomain::Speed::VeryHigh, - get_af(sck_pin, peripheral) - ), + : mode{mode}, 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, @@ -295,7 +357,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) { @@ -326,9 +392,12 @@ 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.max_baudrate = MaxBaud; + e.min_baudrate = MinBaud; e.config = config; + clock_device.inscribe(ctx); + return ctx.template add(e, this); } @@ -1285,6 +1354,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; @@ -1342,55 +1412,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 +1466,10 @@ struct SPIDomain { auto& init = hspi.Init; if (e.mode == SPIMode::MASTER) { init.Mode = SPI_MODE_MASTER; - // Baudrate prescaler calculation - uint32_t pclk_freq; - if (peripheral == SPIPeripheral::spi1 || peripheral == SPIPeripheral::spi2 || - peripheral == SPIPeripheral::spi3) { - pclk_freq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI123); - } else if (peripheral == SPIPeripheral::spi4 || peripheral == SPIPeripheral::spi5) { - pclk_freq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI45); - } else { - pclk_freq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI6); + // Baudrate prescaler from solved tree + uint32_t pclk_freq = ClockDomain::get_kernel_clock(spi_group(peripheral)); + 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/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 680bf82bf..8219d4f8d 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 */ @@ -867,19 +868,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/ADC/ADC.hpp b/Inc/HALAL/Services/ADC/ADC.hpp index 8f6dbc920..a34a9a725 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,23 +95,90 @@ struct ADCDomain { Channel channel; Resolution resolution; SampleTime sample_time; - ClockPrescaler prescaler; + uint32_t sample_rate_hz; float* output; }; - struct ADC { + 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 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) + 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; + } + } + + 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, - ClockPrescaler prescaler = ClockPrescaler::DIV1, uint32_t sample_rate_hz = 0, Peripheral peripheral = Peripheral::AUTO, Channel channel = Channel::AUTO @@ -135,17 +188,14 @@ struct ADCDomain { .pin = pin, .peripheral = peripheral, .channel = channel, - .resolution = resolution, + .resolution = Res, .sample_time = sample_time, - .prescaler = prescaler, .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, - ClockPrescaler prescaler = ClockPrescaler::DIV1, uint32_t sample_rate_hz = 0, Peripheral peripheral = Peripheral::AUTO, Channel channel = Channel::AUTO @@ -155,9 +205,8 @@ struct ADCDomain { .pin = pin, .peripheral = peripheral, .channel = channel, - .resolution = resolution, + .resolution = Res, .sample_time = sample_time, - .prescaler = prescaler, .sample_rate_hz = sample_rate_hz, .output = nullptr} {} @@ -166,30 +215,19 @@ struct ADCDomain { Peripheral peripheral, Channel channel, 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) {} + : 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, - ClockPrescaler prescaler = ClockPrescaler::DIV1, uint32_t sample_rate_hz = 0 ) - : ADC(pin, resolution, sample_time, prescaler, 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); @@ -198,6 +236,10 @@ struct ADCDomain { const auto resolved = resolve_mapping(entry); entry.peripheral = resolved.first; entry.channel = resolved.second; + + // Register clock requirement with ClockDomain + clock_device.inscribe(ctx); + return ctx.template add(entry, this); } }; @@ -210,7 +252,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 +594,6 @@ struct ADCDomain { array cfgs{}; array periph_seen{}; array periph_resolution{}; - array periph_prescaler{}; array periph_rate{}; array periph_counts{}; @@ -578,15 +619,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 +651,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, @@ -869,7 +905,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::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}; + 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; diff --git a/Inc/HALAL/Services/Time/TimerWrapper.hpp b/Inc/HALAL/Services/Time/TimerWrapper.hpp index 051849bb4..9d41e4292 100644 --- a/Inc/HALAL/Services/Time/TimerWrapper.hpp +++ b/Inc/HALAL/Services/Time/TimerWrapper.hpp @@ -137,15 +137,9 @@ 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; - } + result = ClockDomain::timer_apb1(); } else { - result = HAL_RCC_GetPCLK2Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { - result *= 2; - } + result = ClockDomain::timer_apb2(); } return result; } diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 7315e1cae..3d02f398a 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,8 @@ 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 +306,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/Inc/ST-LIB_LOW/Sd/Sd.hpp b/Inc/ST-LIB_LOW/Sd/Sd.hpp index 6f38bc045..dc27cc428 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,25 @@ 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 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) + return true; + } + return false; + } +}; + struct SdDomain { enum class Peripheral : uint32_t { @@ -40,6 +60,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> @@ -54,11 +76,20 @@ 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; Peripheral peripheral; + ClockDomain::Device clock_device = { + .group = Model::group, + .try_solve = &Model::try_solve, + }; MPUDomain::Buffer> buffer0; // Alignment of 32-bit for SDMMC DMA @@ -97,7 +128,8 @@ struct SdDomain { write_protect_config, GPIODomain::Pin d0_pin_for_sdmmc1 = ST_LIB::PC8 ) - : e{.peripheral = sdmmc_peripheral}, peripheral(sdmmc_peripheral), + : e{.peripheral = sdmmc_peripheral, .max_freq = MaxFreq, .min_freq = MinFreq}, + peripheral(sdmmc_peripheral), buffer0(MPUDomain::Buffer>( MPUDomain::MemoryType::NonCached, MPUDomain::MemoryDomain::D1 @@ -227,6 +259,9 @@ struct SdDomain { local_e.d2_pin_idx = d2.inscribe(ctx); local_e.d3_pin_idx = d3.inscribe(ctx); + // Register clock requirement with ClockDomain + 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; @@ -486,16 +525,16 @@ 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::get_kernel_clock(ClockDomain::ClockGroup::SDMMC_G); + 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"); - } } }; }; 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"); - } -} 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..223344d16 --- /dev/null +++ b/Tests/Clocks/clock_group_test.cpp @@ -0,0 +1,104 @@ +#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; + constexpr auto tree = [] { + auto t = tree_8m; + t.spi123_src = ClockDomain::ClockTree::Source::HSI; + return t; + }(); + constexpr std::array entries{{ + {.group = SpiModel::group, .try_solve = &SpiModel::try_solve}, + }}; + ClockDomain::validate(tree, std::span{entries}); + return true; +}()); + +static_assert([] { + using SpiModel = SPIClockModel; + constexpr auto tree = [] { + auto t = tree_8m; + t.spi45_src = ClockDomain::ClockTree::Source::HSI; + return t; + }(); + constexpr 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; + constexpr auto tree = [] { + auto t = tree_8m; + t.spi45_src = ClockDomain::ClockTree::Source::HSI; + return t; + }(); + constexpr 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; + constexpr auto tree = [] { + auto t = tree_8m; + t.spi123_src = ClockDomain::ClockTree::Source::HSI; + t.spi45_src = ClockDomain::ClockTree::Source::PCLK2; + return t; + }(); + constexpr 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([] { + constexpr std::array entries{}; + ClockDomain::validate(tree_8m, std::span{entries}); + return true; +}()); + +static_assert([] { + using Spi123 = SPIClockModel; + using Spi45 = SPIClockModel; + using Spi6 = SPIClockModel; + 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; + }(); + 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}, + }}; + 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..4592dd40b --- /dev/null +++ b/Tests/Clocks/clock_stress_test.cpp @@ -0,0 +1,92 @@ +#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; + constexpr auto tree = [] { + auto t = tree_8m; + t.spi45_src = ClockDomain::ClockTree::Source::PCLK2; + return t; + }(); + constexpr 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; + 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; + }(); + 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}, + }}; + 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; + constexpr auto tree = [] { + auto t = tree_8m; + t.spi123_src = ClockDomain::ClockTree::Source::HSI; + return t; + }(); + 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; +}()); 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; diff --git a/Tests/adc_sensor_test.cpp b/Tests/adc_sensor_test.cpp index 907bb246b..4667d246c 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}, @@ -88,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(); @@ -113,7 +111,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 +133,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 +160,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 +168,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 +204,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 +236,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 +244,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..7c7dd47a0 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}, @@ -327,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) { @@ -372,7 +365,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 +400,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 +433,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 +441,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 +477,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 +485,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 +515,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 +540,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 +564,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 +588,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 +596,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,12 +618,15 @@ 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}, }}; + 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); @@ -651,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); @@ -684,7 +671,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 +679,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 +712,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 +720,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,62 +763,51 @@ 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(); - - 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, - .prescaler = prescaler, - .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 c49f0fa8c..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< diff --git a/Tests/spi2_test.cpp b/Tests/spi2_test.cpp index 1c44348e7..fc4b9ab3b 100644 --- a/Tests/spi2_test.cpp +++ b/Tests/spi2_test.cpp @@ -125,6 +125,11 @@ class SPI2Test : public ::testing::Test { .config = config}, }}; + 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, std::span{}, 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: