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.
pull/1/head
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)
$(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

View File

@ -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))

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
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;

View File

@ -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();