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:
Damien George 2018-09-11 16:42:57 +10:00
parent f2de9d60f7
commit 47550ef2cd
4 changed files with 109 additions and 35 deletions

View File

@ -582,7 +582,7 @@ CMSIS_MCU_HDR = $(CMSIS_DIR)/$(CMSIS_MCU_LOWER).h
modmachine.c: $(GEN_PLLFREQTABLE_HDR) modmachine.c: $(GEN_PLLFREQTABLE_HDR)
$(GEN_PLLFREQTABLE_HDR): $(PLLVALUES) | $(HEADER_BUILD) $(GEN_PLLFREQTABLE_HDR): $(PLLVALUES) | $(HEADER_BUILD)
$(ECHO) "GEN $@" $(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) $(BUILD)/modstm.o: $(GEN_STMCONST_HDR)
# Use a pattern rule here so that make will only call make-stmconst.py once to # Use a pattern rule here so that make will only call make-stmconst.py once to

View File

@ -39,25 +39,23 @@ def compute_pll(hse, sys):
return None return None
# improved version that doesn't require N/M to be an integer # 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 # 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 # that gives the desired "sys" frequency. We use floats for P to force
# floating point arithmetic on Python 2. # floating point arithmetic on Python 2.
fallback = None
for P in (2.0, 4.0, 6.0, 8.0): 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 NbyM = sys * P / hse
# VCO_OUT must be between 192MHz and 432MHz # VCO_OUT must be between 192MHz and 432MHz
if not (192 <= hse * NbyM <= 432): if not (192 <= hse * NbyM <= 432):
continue continue
# compute M # scan M
M = 192 // NbyM # starting value M = int(192 // NbyM) # starting value
while hse > 2 * M or NbyM * M < 192 or not close_int(NbyM * M): while 2 * M < hse:
M += 1 M += 1
# VCO_IN must be between 1MHz and 2MHz (2MHz recommended) # VCO_IN must be between 1MHz and 2MHz (2MHz recommended)
if not (M <= hse): for M in range(M, hse + 1):
if NbyM * M < 191.99 or not close_int(NbyM * M):
continue continue
# compute N # compute N
N = NbyM * M N = NbyM * M
@ -67,9 +65,22 @@ def compute_pll2(hse, sys):
# N is restricted # N is restricted
if not (192 <= N <= 432): if not (192 <= N <= 432):
continue 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 # found valid values
return (M, N, P, Q) return (M, N, P, Q)
# no valid values found 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 return None
def compute_derived(hse, pll): def compute_derived(hse, pll):
@ -125,9 +136,17 @@ def main():
argv = sys.argv[1:] argv = sys.argv[1:]
c_table = False c_table = False
relax_pll48 = False
while True:
if argv[0] == '-c': if argv[0] == '-c':
c_table = True c_table = True
argv.pop(0) argv.pop(0)
elif argv[0] == '--relax-pll48':
relax_pll48 = True
argv.pop(0)
else:
break
if len(argv) != 1: if len(argv) != 1:
print("usage: pllvalues.py [-c] <hse in MHz>") print("usage: pllvalues.py [-c] <hse in MHz>")
@ -150,8 +169,8 @@ def main():
hse = int(argv[0]) hse = int(argv[0])
valid_plls = [] valid_plls = []
for sysclk in range(1, 217): for sysclk in range(2, 217, 2):
pll = compute_pll2(hse, sysclk) pll = compute_pll2(hse, sysclk, relax_pll48)
if pll is not None: if pll is not None:
verify_pll(hse, pll) verify_pll(hse, pll)
valid_plls.append((sysclk, pll)) valid_plls.append((sysclk, pll))

View File

@ -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 // default PLL parameters that give 48MHz on PLL48CK
uint32_t m = HSE_VALUE / 1000000, n = 336, p = 2, q = 7; uint32_t m = HSE_VALUE / 1000000, n = 336, p = 2, q = 7;
uint32_t sysclk_source; uint32_t sysclk_source;
#if defined(STM32F7)
bool need_pllsai = false;
#endif
// search for a valid PLL configuration that keeps USB at 48MHz // 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) { 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; uint32_t vco_out = sys * p;
n = vco_out * m / (HSE_VALUE / 1000000); n = vco_out * m / (HSE_VALUE / 1000000);
q = vco_out / 48; q = vco_out / 48;
#if defined(STM32F7)
need_pllsai = vco_out % 48 != 0;
#endif
goto set_clk; goto set_clk;
} }
} }
@ -394,6 +400,11 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) {
goto fail; 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 // re-configure PLL
// even if we don't use the PLL for the system clock, we still need it for USB, RNG and SDIO // 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; 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; 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 // set PLL as system clock source if wanted
if (sysclk_source == RCC_SYSCLKSOURCE_PLLCLK) { if (sysclk_source == RCC_SYSCLKSOURCE_PLLCLK) {
uint32_t flash_latency; uint32_t flash_latency;

View File

@ -371,6 +371,13 @@ void SystemInit(void)
*/ */
void SystemClock_Config(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_ClkInitTypeDef RCC_ClkInitStruct;
RCC_OscInitTypeDef RCC_OscInitStruct; RCC_OscInitTypeDef RCC_OscInitStruct;
#if defined(STM32H7) #if defined(STM32H7)
@ -506,6 +513,28 @@ void SystemClock_Config(void)
__fatal_error("HAL_RCC_OscConfig"); __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) #if defined(STM32H7)
/* PLL3 for USB Clock */ /* PLL3 for USB Clock */
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USB; PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USB;
@ -554,13 +583,6 @@ void SystemClock_Config(void)
HAL_PWREx_EnableUSBVoltageDetector(); HAL_PWREx_EnableUSBVoltageDetector();
#endif #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) #if defined(STM32L4)
// Enable MSI-Hardware auto calibration mode with LSE // Enable MSI-Hardware auto calibration mode with LSE
HAL_RCCEx_EnableMSIPLLMode(); HAL_RCCEx_EnableMSIPLLMode();