summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien George <damien.p.george@gmail.com>2018-09-11 16:42:57 +1000
committerDamien George <damien.p.george@gmail.com>2018-09-11 16:42:57 +1000
commit47550ef2cd4add384781c390ac8073beb71be1a1 (patch)
treeecd9f728f1ee1b0f67af382c84e6c777a6ea8e6c
parentf2de9d60f7dbe91d9c92ebc8df3136403d9b7938 (diff)
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.
-rw-r--r--ports/stm32/Makefile2
-rw-r--r--ports/stm32/boards/pllvalues.py73
-rw-r--r--ports/stm32/modmachine.c33
-rw-r--r--ports/stm32/system_stm32.c36
4 files changed, 109 insertions, 35 deletions
diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile
index c543f0159..92f3648e1 100644
--- a/ports/stm32/Makefile
+++ b/ports/stm32/Makefile
@@ -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
diff --git a/ports/stm32/boards/pllvalues.py b/ports/stm32/boards/pllvalues.py
index befd6cfa0..4b090c455 100644
--- a/ports/stm32/boards/pllvalues.py
+++ b/ports/stm32/boards/pllvalues.py
@@ -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))
diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c
index 3da85c187..6e0c08605 100644
--- a/ports/stm32/modmachine.c
+++ b/ports/stm32/modmachine.c
@@ -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;
diff --git a/ports/stm32/system_stm32.c b/ports/stm32/system_stm32.c
index 0cf0753bd..5d9a1b662 100644
--- a/ports/stm32/system_stm32.c
+++ b/ports/stm32/system_stm32.c
@@ -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();