diff --git a/Marlin/src/HAL/HC32/app_config.h b/Marlin/src/HAL/HC32/app_config.h index fb291419fc..b971903bba 100644 --- a/Marlin/src/HAL/HC32/app_config.h +++ b/Marlin/src/HAL/HC32/app_config.h @@ -8,6 +8,7 @@ #define _HC32_APP_CONFIG_H_ #include "../../inc/MarlinConfigPre.h" +#include "sysclock.h" // // dev mode @@ -64,12 +65,8 @@ // redirect printf to host serial #define REDIRECT_PRINTF_TO_SERIAL 1 -// F_CPU must be known at compile time, but on HC32F460 it's not. -// Thus we assume HCLK to be 200MHz, as that's what is configured in -// 'core_hook_sysclock_init' in 'sysclock.cpp'. -// If you face issues with this assumption, please double-check with the values -// printed by 'MarlinHAL::HAL_clock_frequencies_dump'. -// see also: HAL_TIMER_RATE in timers.h -#define F_CPU 200000000 // 200MHz HCLK +// F_CPU is F_HCLK, as that's the main CPU core's clock. +// see 'sysclock.h' for more information. +#define F_CPU F_HCLK #endif // _HC32_APP_CONFIG_H_ diff --git a/Marlin/src/HAL/HC32/sysclock.cpp b/Marlin/src/HAL/HC32/sysclock.cpp index 475be3bbc9..493515b0e8 100644 --- a/Marlin/src/HAL/HC32/sysclock.cpp +++ b/Marlin/src/HAL/HC32/sysclock.cpp @@ -26,64 +26,144 @@ #ifdef ARDUINO_ARCH_HC32 -// Get BOARD_XTAL_FREQUENCY from configuration / pins #include "../../inc/MarlinConfig.h" +#include "sysclock.h" #include #include +/*** + * @brief Automatically calculate M, N, P values for the MPLL to reach a target frequency. + * @param input_frequency The input frequency. + * @param target_frequency The target frequency. + * @return The MPLL configuration structure. Q and R are not set. + * + * @note + * Simplified MPLL block diagram, with intermediary clocks (1) = VCO_in, (2) = VCO_out: + * + * INPUT -> [/ M] -(1)-> [* N] -(2)-|-> [/ P] -> MPLL-P + */ +constexpr stc_clk_mpll_cfg_t get_mpll_config(double input_frequency, double target_frequency) { + // PLL input clock divider: M in [1, 24] + for (uint32_t M = 1; M <= 24; M++) { + double f_vco_in = input_frequency / M; + + // 1 <= VCO_in <= 25 MHz + if (f_vco_in < 1e6 || f_vco_in > 25e6) continue; + + // VCO multiplier: N in [20, 480] + for (uint32_t N = 20; N <= 480; N++) { + double f_vco_out = f_vco_in * N; + + // 240 <= VCO_out <= 480 MHz + if (f_vco_out < 240e6 || f_vco_out > 480e6) continue; + + // Output "P" divider: P in [2, 16] + for (uint32_t P = 2; P <= 16; P++) { + double f_calculated_out = f_vco_out / P; + if (f_calculated_out == target_frequency) { + // Found a match, return it + return { + .PllpDiv = P, + .PllqDiv = P, // Don't care for Q and R + .PllrDiv = P, // " + .plln = N, + .pllmDiv = M + }; + } + } + } + } + + // If no valid M, N, P found, return invalid config + return { 0, 0, 0, 0, 0 }; +} + +/** + * @brief Get the division factor required to get the target frequency from the input frequency. + * @tparam input_freq The input frequency. + * @tparam target_freq The target frequency. + * @return The division factor. + */ +template +constexpr en_clk_sysclk_div_factor_t get_division_factor() { + // Calculate the divider to get the target frequency + constexpr float fdivider = static_cast(input_freq) / static_cast(target_freq); + constexpr int divider = static_cast(fdivider); + + // divider must be an integer + static_assert(fdivider == divider, "Target frequency not achievable, divider must be an integer"); + + // divider must be between 1 and 64 (enum range), and must be a power of 2 + static_assert(divider >= 1 && divider <= 64, "Invalid divider, out of range"); + static_assert((divider & (divider - 1)) == 0, "Invalid divider, not a power of 2"); + + // return the divider + switch (divider) { + case 1: return ClkSysclkDiv1; + case 2: return ClkSysclkDiv2; + case 4: return ClkSysclkDiv4; + case 8: return ClkSysclkDiv8; + case 16: return ClkSysclkDiv16; + case 32: return ClkSysclkDiv32; + case 64: return ClkSysclkDiv64; + } +} + +/** + * @brief Validate the runtime clocks match the expected values. + */ +void validate_system_clocks() { + #define CLOCK_ASSERT(expected, actual) \ + if (expected != actual) { \ + SERIAL_ECHOPGM( \ + "Clock Mismatch for " #expected ": " \ + "expected ", expected, \ + ", got ", actual \ + ); \ + CORE_ASSERT_FAIL("Clock Mismatch: " #expected); \ + } + + update_system_clock_frequencies(); + + CLOCK_ASSERT(F_SYSTEM_CLOCK, SYSTEM_CLOCK_FREQUENCIES.system); + CLOCK_ASSERT(F_HCLK, SYSTEM_CLOCK_FREQUENCIES.hclk); + CLOCK_ASSERT(F_EXCLK, SYSTEM_CLOCK_FREQUENCIES.exclk); + CLOCK_ASSERT(F_PCLK0, SYSTEM_CLOCK_FREQUENCIES.pclk0); + CLOCK_ASSERT(F_PCLK1, SYSTEM_CLOCK_FREQUENCIES.pclk1); + CLOCK_ASSERT(F_PCLK2, SYSTEM_CLOCK_FREQUENCIES.pclk2); + CLOCK_ASSERT(F_PCLK3, SYSTEM_CLOCK_FREQUENCIES.pclk3); + CLOCK_ASSERT(F_PCLK4, SYSTEM_CLOCK_FREQUENCIES.pclk4); +} + +/** + * @brief Configure HC32 system clocks. + * + * This function is called by the Arduino core early in the startup process, before setup() is called. + * It is used to configure the system clocks to the desired state. + * + * See https://github.com/MarlinFirmware/Marlin/pull/27099 for more information. + */ void core_hook_sysclock_init() { // Set wait cycles, as we are about to switch to 200 MHz HCLK sysclock_configure_flash_wait_cycles(); sysclock_configure_sram_wait_cycles(); - // Configure MPLLp to 200 MHz output, with different settings depending on XTAL availability - #if BOARD_XTAL_FREQUENCY == 8000000 // 8 MHz XTAL - // - M = 1 => 8 MHz / 1 = 8 MHz - // - N = 50 => 8 MHz * 50 = 400 MHz - // - P = 2 => 400 MHz / 2 = 200 MHz (sysclk) - // - Q,R = 4 => 400 MHz / 4 = 100 MHz (dont care) - stc_clk_mpll_cfg_t pllConf = { - .PllpDiv = 2u, // P - .PllqDiv = 4u, // Q - .PllrDiv = 4u, // R - .plln = 50u, // N - .pllmDiv = 1u, // M - }; - sysclock_configure_xtal(); - sysclock_configure_mpll(ClkPllSrcXTAL, &pllConf); + // Select MPLL input frequency based on clock availability + #if BOARD_XTAL_FREQUENCY == 8000000 || BOARD_XTAL_FREQUENCY == 16000000 // 8 MHz or 16 MHz XTAL + constexpr uint32_t mpll_input_clock = BOARD_XTAL_FREQUENCY; - #elif BOARD_XTAL_FREQUENCY == 16000000 // 16 MHz XTAL - // - M = 1 => 16 MHz / 1 = 16 MHz - // - N = 50 => 16 MHz * 25 = 400 MHz - // - P = 2 => 400 MHz / 2 = 200 MHz (sysclk) - // - Q,R = 4 => 400 MHz / 4 = 100 MHz (dont care) - stc_clk_mpll_cfg_t pllConf = { - .PllpDiv = 2u, // P - .PllqDiv = 4u, // Q - .PllrDiv = 4u, // R - .plln = 50u, // N - .pllmDiv = 1u, // M - }; sysclock_configure_xtal(); - sysclock_configure_mpll(ClkPllSrcXTAL, &pllConf); - #warning "HC32F460 with 16 MHz XTAL has not been tested." + #if BOARD_XTAL_FREQUENCY == 16000000 + #warning "HC32F460 with 16 MHz XTAL has not been tested." + #endif #else // HRC (16 MHz) - // - M = 1 => 16 MHz / 1 = 16 MHz - // - N = 25 => 16 MHz * 25 = 400 MHz - // - P = 2 => 400 MHz / 2 = 200 MHz (sysclk) - // - Q,R = 4 => 400 MHz / 4 = 100 MHz (dont care) - stc_clk_mpll_cfg_t pllConf = { - .PllpDiv = 2u, // P - .PllqDiv = 4u, // Q - .PllrDiv = 4u, // R - .plln = 25u, // N - .pllmDiv = 1u, // M - }; + + constexpr uint32_t mpll_input_clock = 16000000; + sysclock_configure_hrc(); - sysclock_configure_mpll(ClkPllSrcHRC, &pllConf); // HRC could have been configured by ICG to 20 MHz // TODO: handle gracefully if HRC is not 16 MHz @@ -91,29 +171,56 @@ void core_hook_sysclock_init() { panic("HRC is not 16 MHz"); } - #ifdef BOARD_XTAL_FREQUENCY + #if defined(BOARD_XTAL_FREQUENCY) #warning "No valid XTAL frequency defined, falling back to HRC." #endif + #endif - // sysclk is now configured according to F_CPU (i.e., 200MHz PLL output) - const uint32_t sysclock = F_CPU; + // Automagically calculate MPLL configuration + constexpr stc_clk_mpll_cfg_t pllConf = get_mpll_config(mpll_input_clock, F_SYSTEM_CLOCK); + static_assert(pllConf.pllmDiv != 0 && pllConf.plln != 0 && pllConf.PllpDiv != 0, "MPLL auto-configuration failed"); + sysclock_configure_mpll(ClkPllSrcXTAL, &pllConf); - // Setup clock divisors for sysclk = 200 MHz - // Note: PCLK1 is used for step+temp timers, and need to be kept at 50 MHz (until there is a better solution) + // Setup clock divisors constexpr stc_clk_sysclk_cfg_t sysClkConf = { - .enHclkDiv = ClkSysclkDiv1, // HCLK = 200 MHz (CPU) - .enExclkDiv = ClkSysclkDiv2, // EXCLK = 100 MHz (SDIO) - .enPclk0Div = ClkSysclkDiv2, // PCLK0 = 100 MHz (Timer6 (not used)) - .enPclk1Div = ClkSysclkDiv4, // PCLK1 = 50 MHz (USART, SPI, I2S, Timer0 (step+temp), TimerA (Servo)) - .enPclk2Div = ClkSysclkDiv8, // PCLK2 = 25 MHz (ADC) - .enPclk3Div = ClkSysclkDiv8, // PCLK3 = 25 MHz (I2C, WDT) - .enPclk4Div = ClkSysclkDiv2, // PCLK4 = 100 MHz (ADC ctl) + .enHclkDiv = get_division_factor(), + .enExclkDiv = get_division_factor(), + .enPclk0Div = get_division_factor(), + .enPclk1Div = get_division_factor(), + .enPclk2Div = get_division_factor(), + .enPclk3Div = get_division_factor(), + .enPclk4Div = get_division_factor(), }; + sysclock_set_clock_dividers(&sysClkConf); + // Set power mode, before switch + power_mode_update_pre(F_SYSTEM_CLOCK); + + // Switch to MPLL-P as system clock source + CLK_SetSysClkSource(CLKSysSrcMPLL); + + // Set power mode, after switch + power_mode_update_post(F_SYSTEM_CLOCK); + + // Verify clocks match expected values (at runtime) + #if ENABLED(MARLIN_DEV_MODE) || ENABLED(ALWAYS_VALIDATE_CLOCKS) + validate_system_clocks(); + #endif + + // Verify clock configuration (at compile time) #if ARDUINO_CORE_VERSION_INT >= GET_VERSION_INT(1, 2, 0) + assert_mpll_config_valid< + mpll_input_clock, + pllConf.pllmDiv, + pllConf.plln, + pllConf.PllpDiv, + pllConf.PllqDiv, + pllConf.PllrDiv + >(); + assert_system_clocks_valid< - sysclock, + F_SYSTEM_CLOCK, sysClkConf.enHclkDiv, sysClkConf.enPclk0Div, sysClkConf.enPclk1Div, @@ -122,18 +229,14 @@ void core_hook_sysclock_init() { sysClkConf.enPclk4Div, sysClkConf.enExclkDiv >(); + + static_assert(get_mpll_output_clock( + mpll_input_clock, + pllConf.pllmDiv, + pllConf.plln, + pllConf.PllpDiv + ) == F_SYSTEM_CLOCK, "actual MPLL output clock does not match F_SYSTEM_CLOCK"); #endif - - sysclock_set_clock_dividers(&sysClkConf); - - // Set power mode - power_mode_update_pre(sysclock); - - // Switch to MPLL as sysclk source - CLK_SetSysClkSource(CLKSysSrcMPLL); - - // Set power mode - power_mode_update_post(sysclock); } #endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/sysclock.h b/Marlin/src/HAL/HC32/sysclock.h new file mode 100644 index 0000000000..783d5677e9 --- /dev/null +++ b/Marlin/src/HAL/HC32/sysclock.h @@ -0,0 +1,65 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#pragma once + +/** + * HC32F460 system clock configuration. + * + * With the HC32 HAL, the various peripheral clocks (including the CPU clock) are derived + * from the main PLL (MPLL-P) output (referred to at F_SYSTEM_CLOCK). + * + * F_SYSTEM_CLOCK is the target frequency of the main PLL, and the PLL is automatically configured + * to achieve this frequency. + * + * The peripheral clocks are the result of integer division of F_SYSTEM_CLOCK. + * Their target frequencies are defined here, and the required division factors are calculated automatically. + * Note that the division factor must be a power of 2 between 1 and 64. + * If the target frequency is not achievable, a compile-time error will be generated. + * + * Additionally, there are interdependencies between the peripheral clocks, which are described in + * Section 4.4 "Working Clock Specifications" of the HC32F460 Reference Manual. + * With Arduino core >= 1.2.0, these interdependencies are checked at compile time. + * On earlier versions, you are on your own. + * + * For all clock frequencies, they can be checked at runtime by enabling the 'ALWAYS_VALIDATE_CLOCKS' define. + * In MARLIN_DEV_MODE, they will also be printed to the serial console by 'MarlinHAL::HAL_clock_frequencies_dump'. + * + * See https://github.com/MarlinFirmware/Marlin/pull/27099 for more information. + */ + +// Target peripheral clock frequencies, must be integer divisors of F_SYSTEM_CLOCK. +// Changing the frequency here will automagically update everything else. +#define F_HCLK 200000000UL // 200 MHz; CPU +#define F_EXCLK (F_HCLK / 2) // 100 MHz; SDIO +#define F_PCLK0 (F_HCLK / 2) // 100 MHz; Timer6 (unused) +#define F_PCLK1 (F_HCLK / 4) // 50 MHz; USART, SPI, Timer0 (step + temp), TimerA (Servo) +#define F_PCLK2 (F_HCLK / 8) // 25 MHz; ADC Sampling +#define F_PCLK3 (F_HCLK / 8) // 25 MHz; I2C, WDT +#define F_PCLK4 (F_HCLK / 2) // 100 MHz; ADC Control + +// MPLL-P clock target frequency. This must be >= the highest peripheral clock frequency. +// PLL config is automatically calculated based on this value. +#define F_SYSTEM_CLOCK F_HCLK + +// The Peripheral clocks are only checked at runtime if this is enabled OR MARLIN_DEV_MODE is enabled. +// Compile time checks are always performed with Arduino core version >= 1.2.0. +#define ALWAYS_VALIDATE_CLOCKS 1 diff --git a/Marlin/src/HAL/HC32/timers.h b/Marlin/src/HAL/HC32/timers.h index f5a590deb1..c0014df604 100644 --- a/Marlin/src/HAL/HC32/timers.h +++ b/Marlin/src/HAL/HC32/timers.h @@ -20,6 +20,7 @@ #pragma once #include #include +#include "sysclock.h" // // Timer Types @@ -42,17 +43,15 @@ extern Timer0 step_timer; * HAL_TIMER_RATE must be known at compile time since it's used to calculate * STEPPER_TIMER_RATE, which is used in 'constexpr' calculations. * On the HC32F460 the timer rate depends on PCLK1, which is derived from the - * system clock configured at runtime. As a workaround, we use the existing - * assumption of a 200MHz clock, defining F_CPU as 200000000, then configure PCLK1 - * as F_CPU with a divider of 4 in 'sysclock.cpp::core_hook_sysclock_init'. + * system clock configured at runtime. + * Thus we use the 'F_PCLK1' constant defined in 'sysclock.h'. * - * If you face issues with this assumption, please double-check with the values - * printed by 'MarlinHAL::HAL_clock_frequencies_dump'. + * See https://github.com/MarlinFirmware/Marlin/pull/27099 for more information. * - * TODO: If the 'constexpr' requirement is ever lifted, use TIMER0_BASE_FREQUENCY instead + * NOTE: If the 'constexpr' requirement is ever lifted, TIMER0_BASE_FREQUENCY could + * be used instead. Tho this would probably not make any noticable difference. */ -#define HAL_TIMER_RATE (F_CPU / 4) // i.e., 50MHz -//#define HAL_TIMER_RATE TIMER0_BASE_FREQUENCY +#define HAL_TIMER_RATE F_PCLK1 // Temperature timer #define TEMP_TIMER_NUM (&temp_timer) diff --git a/Marlin/src/inc/Warnings.cpp b/Marlin/src/inc/Warnings.cpp index 229755c6d0..a6ea570030 100644 --- a/Marlin/src/inc/Warnings.cpp +++ b/Marlin/src/inc/Warnings.cpp @@ -919,13 +919,6 @@ #warning "EDITABLE_STEPS_PER_UNIT is required to enable G92 runtime configuration of steps-per-unit." #endif -/** - * HC32 clock speed is hard-coded in Marlin - */ -#if defined(ARDUINO_ARCH_HC32) && F_CPU == 200000000 - #warning "HC32 clock is assumed to be 200MHz. If this isn't the case for your board please submit a report so we can add support." -#endif - /** * Peltier with PIDTEMPBED */