stm32: For MCUs that have PLLSAI allow to set SYSCLK at 2MHz increments.
MCUs that have a PLLSAI can use it to generate a 48MHz clock for USB, SDIO and RNG peripherals. In such cases the SYSCLK is not restricted to values that allow the system PLL to generate 48MHz, but can be any frequency. This patch allows such configurability for F7 MCUs, allowing the SYSCLK to be set in 2MHz increments via machine.freq(). PLLSAI will only be enabled if needed, and consumes about 1mA extra. This fine grained control of frequency is useful to get accurate SPI baudrates, for example.
This commit is contained in:
parent
f2de9d60f7
commit
47550ef2cd
@ -582,7 +582,7 @@ CMSIS_MCU_HDR = $(CMSIS_DIR)/$(CMSIS_MCU_LOWER).h
|
||||
modmachine.c: $(GEN_PLLFREQTABLE_HDR)
|
||||
$(GEN_PLLFREQTABLE_HDR): $(PLLVALUES) | $(HEADER_BUILD)
|
||||
$(ECHO) "GEN $@"
|
||||
$(Q)$(PYTHON) $(PLLVALUES) -c file:$(BOARD_DIR)/stm32$(MCU_SERIES)xx_hal_conf.h > $@
|
||||
$(Q)$(PYTHON) $(PLLVALUES) -c $(if $(filter $(MCU_SERIES),f7),--relax-pll48,) file:$(BOARD_DIR)/stm32$(MCU_SERIES)xx_hal_conf.h > $@
|
||||
|
||||
$(BUILD)/modstm.o: $(GEN_STMCONST_HDR)
|
||||
# Use a pattern rule here so that make will only call make-stmconst.py once to
|
||||
|
@ -39,38 +39,49 @@ def compute_pll(hse, sys):
|
||||
return None
|
||||
|
||||
# improved version that doesn't require N/M to be an integer
|
||||
def compute_pll2(hse, sys):
|
||||
def compute_pll2(hse, sys, relax_pll48):
|
||||
# Loop over the allowed values of P, looking for a valid PLL configuration
|
||||
# that gives the desired "sys" frequency. We use floats for P to force
|
||||
# floating point arithmetic on Python 2.
|
||||
fallback = None
|
||||
for P in (2.0, 4.0, 6.0, 8.0):
|
||||
Q = sys * P / 48
|
||||
# Q must be an integer in a set range
|
||||
if not (close_int(Q) and 2 <= Q <= 15):
|
||||
continue
|
||||
NbyM = sys * P / hse
|
||||
# VCO_OUT must be between 192MHz and 432MHz
|
||||
if not (192 <= hse * NbyM <= 432):
|
||||
continue
|
||||
# compute M
|
||||
M = 192 // NbyM # starting value
|
||||
while hse > 2 * M or NbyM * M < 192 or not close_int(NbyM * M):
|
||||
# scan M
|
||||
M = int(192 // NbyM) # starting value
|
||||
while 2 * M < hse:
|
||||
M += 1
|
||||
# VCO_IN must be between 1MHz and 2MHz (2MHz recommended)
|
||||
if not (M <= hse):
|
||||
continue
|
||||
# compute N
|
||||
N = NbyM * M
|
||||
# N must be an integer
|
||||
if not close_int(N):
|
||||
continue
|
||||
# N is restricted
|
||||
if not (192 <= N <= 432):
|
||||
continue
|
||||
# found valid values
|
||||
return (M, N, P, Q)
|
||||
# no valid values found
|
||||
return None
|
||||
for M in range(M, hse + 1):
|
||||
if NbyM * M < 191.99 or not close_int(NbyM * M):
|
||||
continue
|
||||
# compute N
|
||||
N = NbyM * M
|
||||
# N must be an integer
|
||||
if not close_int(N):
|
||||
continue
|
||||
# N is restricted
|
||||
if not (192 <= N <= 432):
|
||||
continue
|
||||
Q = (sys * P / 48)
|
||||
# Q must be an integer in a set range
|
||||
if not (2 <= Q <= 15):
|
||||
continue
|
||||
if not close_int(Q):
|
||||
if int(M) == int(hse) and fallback is None:
|
||||
# the values don't give 48MHz on PLL48 but are otherwise OK
|
||||
fallback = M, N, P, int(Q)
|
||||
continue
|
||||
# found valid values
|
||||
return (M, N, P, Q)
|
||||
if relax_pll48:
|
||||
# might have found values which don't give 48MHz on PLL48
|
||||
return fallback
|
||||
else:
|
||||
# no valid values found which give 48MHz on PLL48
|
||||
return None
|
||||
|
||||
def compute_derived(hse, pll):
|
||||
M, N, P, Q = pll
|
||||
@ -125,9 +136,17 @@ def main():
|
||||
argv = sys.argv[1:]
|
||||
|
||||
c_table = False
|
||||
if argv[0] == '-c':
|
||||
c_table = True
|
||||
argv.pop(0)
|
||||
relax_pll48 = False
|
||||
|
||||
while True:
|
||||
if argv[0] == '-c':
|
||||
c_table = True
|
||||
argv.pop(0)
|
||||
elif argv[0] == '--relax-pll48':
|
||||
relax_pll48 = True
|
||||
argv.pop(0)
|
||||
else:
|
||||
break
|
||||
|
||||
if len(argv) != 1:
|
||||
print("usage: pllvalues.py [-c] <hse in MHz>")
|
||||
@ -150,8 +169,8 @@ def main():
|
||||
hse = int(argv[0])
|
||||
|
||||
valid_plls = []
|
||||
for sysclk in range(1, 217):
|
||||
pll = compute_pll2(hse, sysclk)
|
||||
for sysclk in range(2, 217, 2):
|
||||
pll = compute_pll2(hse, sysclk, relax_pll48)
|
||||
if pll is not None:
|
||||
verify_pll(hse, pll)
|
||||
valid_plls.append((sysclk, pll))
|
||||
|
@ -324,6 +324,9 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) {
|
||||
// default PLL parameters that give 48MHz on PLL48CK
|
||||
uint32_t m = HSE_VALUE / 1000000, n = 336, p = 2, q = 7;
|
||||
uint32_t sysclk_source;
|
||||
#if defined(STM32F7)
|
||||
bool need_pllsai = false;
|
||||
#endif
|
||||
|
||||
// search for a valid PLL configuration that keeps USB at 48MHz
|
||||
for (const uint16_t *pll = &pll_freq_table[MP_ARRAY_SIZE(pll_freq_table) - 1]; pll >= &pll_freq_table[0]; --pll) {
|
||||
@ -345,6 +348,9 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) {
|
||||
uint32_t vco_out = sys * p;
|
||||
n = vco_out * m / (HSE_VALUE / 1000000);
|
||||
q = vco_out / 48;
|
||||
#if defined(STM32F7)
|
||||
need_pllsai = vco_out % 48 != 0;
|
||||
#endif
|
||||
goto set_clk;
|
||||
}
|
||||
}
|
||||
@ -394,6 +400,11 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#if defined(STM32F7)
|
||||
// Turn PLLSAI off because we are changing PLLM (which drives PLLSAI)
|
||||
RCC->CR &= ~RCC_CR_PLLSAION;
|
||||
#endif
|
||||
|
||||
// re-configure PLL
|
||||
// even if we don't use the PLL for the system clock, we still need it for USB, RNG and SDIO
|
||||
RCC_OscInitTypeDef RCC_OscInitStruct;
|
||||
@ -409,6 +420,28 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#if defined(STM32F7)
|
||||
if (need_pllsai) {
|
||||
// Configure PLLSAI at 48MHz for those peripherals that need this freq
|
||||
const uint32_t pllsain = 192;
|
||||
const uint32_t pllsaip = 4;
|
||||
const uint32_t pllsaiq = 2;
|
||||
RCC->PLLSAICFGR = pllsaiq << RCC_PLLSAICFGR_PLLSAIQ_Pos
|
||||
| (pllsaip / 2 - 1) << RCC_PLLSAICFGR_PLLSAIP_Pos
|
||||
| pllsain << RCC_PLLSAICFGR_PLLSAIN_Pos;
|
||||
RCC->CR |= RCC_CR_PLLSAION;
|
||||
uint32_t ticks = mp_hal_ticks_ms();
|
||||
while (!(RCC->CR & RCC_CR_PLLSAIRDY)) {
|
||||
if (mp_hal_ticks_ms() - ticks > 200) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
RCC->DCKCFGR2 |= RCC_DCKCFGR2_CK48MSEL;
|
||||
} else {
|
||||
RCC->DCKCFGR2 &= ~RCC_DCKCFGR2_CK48MSEL;
|
||||
}
|
||||
#endif
|
||||
|
||||
// set PLL as system clock source if wanted
|
||||
if (sysclk_source == RCC_SYSCLKSOURCE_PLLCLK) {
|
||||
uint32_t flash_latency;
|
||||
|
@ -371,6 +371,13 @@ void SystemInit(void)
|
||||
*/
|
||||
void SystemClock_Config(void)
|
||||
{
|
||||
#if defined(STM32F7)
|
||||
// The DFU bootloader changes the clocksource register from its default power
|
||||
// on reset value, so we set it back here, so the clocksources are the same
|
||||
// whether we were started from DFU or from a power on reset.
|
||||
RCC->DCKCFGR2 = 0;
|
||||
#endif
|
||||
|
||||
RCC_ClkInitTypeDef RCC_ClkInitStruct;
|
||||
RCC_OscInitTypeDef RCC_OscInitStruct;
|
||||
#if defined(STM32H7)
|
||||
@ -506,6 +513,28 @@ void SystemClock_Config(void)
|
||||
__fatal_error("HAL_RCC_OscConfig");
|
||||
}
|
||||
|
||||
#if defined(STM32F7)
|
||||
uint32_t vco_out = RCC_OscInitStruct.PLL.PLLN * (HSE_VALUE / 1000000) / RCC_OscInitStruct.PLL.PLLM;
|
||||
bool need_pllsai = vco_out % 48 != 0;
|
||||
if (need_pllsai) {
|
||||
// Configure PLLSAI at 48MHz for those peripherals that need this freq
|
||||
const uint32_t pllsain = 192;
|
||||
const uint32_t pllsaip = 4;
|
||||
const uint32_t pllsaiq = 2;
|
||||
RCC->PLLSAICFGR = pllsaiq << RCC_PLLSAICFGR_PLLSAIQ_Pos
|
||||
| (pllsaip / 2 - 1) << RCC_PLLSAICFGR_PLLSAIP_Pos
|
||||
| pllsain << RCC_PLLSAICFGR_PLLSAIN_Pos;
|
||||
RCC->CR |= RCC_CR_PLLSAION;
|
||||
uint32_t ticks = mp_hal_ticks_ms();
|
||||
while (!(RCC->CR & RCC_CR_PLLSAIRDY)) {
|
||||
if (mp_hal_ticks_ms() - ticks > 200) {
|
||||
__fatal_error("PLLSAIRDY timeout");
|
||||
}
|
||||
}
|
||||
RCC->DCKCFGR2 |= RCC_DCKCFGR2_CK48MSEL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(STM32H7)
|
||||
/* PLL3 for USB Clock */
|
||||
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USB;
|
||||
@ -554,13 +583,6 @@ void SystemClock_Config(void)
|
||||
HAL_PWREx_EnableUSBVoltageDetector();
|
||||
#endif
|
||||
|
||||
#if defined(STM32F7)
|
||||
// The DFU bootloader changes the clocksource register from its default power
|
||||
// on reset value, so we set it back here, so the clocksources are the same
|
||||
// whether we were started from DFU or from a power on reset.
|
||||
|
||||
RCC->DCKCFGR2 = 0;
|
||||
#endif
|
||||
#if defined(STM32L4)
|
||||
// Enable MSI-Hardware auto calibration mode with LSE
|
||||
HAL_RCCEx_EnableMSIPLLMode();
|
||||
|
Loading…
x
Reference in New Issue
Block a user