mirror of
https://github.com/MarlinFirmware/Marlin.git
synced 2026-01-05 06:07:43 -07:00
🐛 Fix HC32 Clock Configuration (#27099)
This commit is contained in:
parent
2ed5937daf
commit
257ffe98b1
5 changed files with 245 additions and 88 deletions
|
|
@ -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_
|
||||
|
|
|
|||
|
|
@ -26,64 +26,144 @@
|
|||
|
||||
#ifdef ARDUINO_ARCH_HC32
|
||||
|
||||
// Get BOARD_XTAL_FREQUENCY from configuration / pins
|
||||
#include "../../inc/MarlinConfig.h"
|
||||
#include "sysclock.h"
|
||||
|
||||
#include <core_hooks.h>
|
||||
#include <drivers/sysclock/sysclock_util.h>
|
||||
|
||||
/***
|
||||
* @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 <uint32_t input_freq, uint32_t target_freq>
|
||||
constexpr en_clk_sysclk_div_factor_t get_division_factor() {
|
||||
// Calculate the divider to get the target frequency
|
||||
constexpr float fdivider = static_cast<float>(input_freq) / static_cast<float>(target_freq);
|
||||
constexpr int divider = static_cast<int>(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<F_SYSTEM_CLOCK, F_HCLK>(),
|
||||
.enExclkDiv = get_division_factor<F_SYSTEM_CLOCK, F_EXCLK>(),
|
||||
.enPclk0Div = get_division_factor<F_SYSTEM_CLOCK, F_PCLK0>(),
|
||||
.enPclk1Div = get_division_factor<F_SYSTEM_CLOCK, F_PCLK1>(),
|
||||
.enPclk2Div = get_division_factor<F_SYSTEM_CLOCK, F_PCLK2>(),
|
||||
.enPclk3Div = get_division_factor<F_SYSTEM_CLOCK, F_PCLK3>(),
|
||||
.enPclk4Div = get_division_factor<F_SYSTEM_CLOCK, F_PCLK4>(),
|
||||
};
|
||||
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
|
||||
|
|
|
|||
65
Marlin/src/HAL/HC32/sysclock.h
Normal file
65
Marlin/src/HAL/HC32/sysclock.h
Normal file
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#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
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <Timer0.h>
|
||||
#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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue