From 5f02d6b6590b0dba84b0c4a380e1289eb17ea6b3 Mon Sep 17 00:00:00 2001 From: Konstantin Sharlaimov Date: Sun, 12 Jul 2026 16:30:16 +0200 Subject: [PATCH] feat(machine/stm32): make HSE crystal frequency selectable. Define the board's external crystal (HSE) frequency `xtalHz` in the respective `board_*.go` files. Per-topology PLL tables (F1, F4, F7) will compute the register dividers from it. Moving this parameter to the board definition level cleanly isolates board hardware characteristics from general MCU chip configurations, eliminating the need for custom target build tags. Targets using HSE-clocked STM32 chips must define `xtalHz` or fail to build. --- src/machine/board_bluepill.go | 2 ++ src/machine/board_feather-stm32f405.go | 2 ++ src/machine/board_mksnanov3.go | 2 ++ src/machine/board_nucleof103rb.go | 2 ++ src/machine/board_nucleof722ze.go | 2 ++ src/machine/board_stm32f469disco.go | 2 ++ src/machine/board_stm32f4disco.go | 2 ++ src/machine/machine_stm32_pll.go | 10 ++++++ src/machine/machine_stm32f103.go | 5 ++- src/machine/machine_stm32f103_pll_72mhz.go | 27 +++++++++++++++ src/machine/machine_stm32f40x.go | 3 +- src/machine/machine_stm32f469.go | 3 +- src/machine/machine_stm32f4_pll_168mhz.go | 20 +++++++++++ src/machine/machine_stm32f4_pll_180mhz.go | 18 ++++++++++ src/machine/machine_stm32f7_pll_216mhz.go | 18 ++++++++++ src/machine/machine_stm32f7x2.go | 3 +- src/runtime/runtime_stm32f103.go | 11 +++--- src/runtime/runtime_stm32f4.go | 2 +- src/runtime/runtime_stm32f405.go | 30 ++++++++--------- src/runtime/runtime_stm32f407.go | 38 +++++++++++---------- src/runtime/runtime_stm32f469.go | 39 +++++++++++----------- src/runtime/runtime_stm32f7x2.go | 31 ++++++++--------- 22 files changed, 193 insertions(+), 79 deletions(-) create mode 100644 src/machine/machine_stm32_pll.go create mode 100644 src/machine/machine_stm32f103_pll_72mhz.go create mode 100644 src/machine/machine_stm32f4_pll_168mhz.go create mode 100644 src/machine/machine_stm32f4_pll_180mhz.go create mode 100644 src/machine/machine_stm32f7_pll_216mhz.go diff --git a/src/machine/board_bluepill.go b/src/machine/board_bluepill.go index 83f527d57..ed158cf12 100644 --- a/src/machine/board_bluepill.go +++ b/src/machine/board_bluepill.go @@ -7,6 +7,8 @@ import ( "runtime/interrupt" ) +const xtalHz = 8_000_000 + // Pins printed on the silkscreen const ( C13 = PC13 diff --git a/src/machine/board_feather-stm32f405.go b/src/machine/board_feather-stm32f405.go index 4a184bad5..e512cc3cc 100644 --- a/src/machine/board_feather-stm32f405.go +++ b/src/machine/board_feather-stm32f405.go @@ -7,6 +7,8 @@ import ( "runtime/interrupt" ) +const xtalHz = 12_000_000 + const ( NUM_DIGITAL_IO_PINS = 39 NUM_ANALOG_IO_PINS = 7 diff --git a/src/machine/board_mksnanov3.go b/src/machine/board_mksnanov3.go index 35fef682d..70b3c6499 100644 --- a/src/machine/board_mksnanov3.go +++ b/src/machine/board_mksnanov3.go @@ -10,6 +10,8 @@ import ( "runtime/interrupt" ) +const xtalHz = 8_000_000 + // LED is also wired to the SD card card detect (CD) pin. const LED = PD12 diff --git a/src/machine/board_nucleof103rb.go b/src/machine/board_nucleof103rb.go index af7a99e9d..5be08afd5 100644 --- a/src/machine/board_nucleof103rb.go +++ b/src/machine/board_nucleof103rb.go @@ -7,6 +7,8 @@ import ( "runtime/interrupt" ) +const xtalHz = 8_000_000 + const ( LED = LED_BUILTIN LED_BUILTIN = LED_GREEN diff --git a/src/machine/board_nucleof722ze.go b/src/machine/board_nucleof722ze.go index c9a0b01b0..63d6c2f9a 100644 --- a/src/machine/board_nucleof722ze.go +++ b/src/machine/board_nucleof722ze.go @@ -7,6 +7,8 @@ import ( "runtime/interrupt" ) +const xtalHz = 8_000_000 + const ( LED = LED_BUILTIN LED_BUILTIN = LED_GREEN diff --git a/src/machine/board_stm32f469disco.go b/src/machine/board_stm32f469disco.go index 8fb5cde2e..13419352c 100644 --- a/src/machine/board_stm32f469disco.go +++ b/src/machine/board_stm32f469disco.go @@ -7,6 +7,8 @@ import ( "runtime/interrupt" ) +const xtalHz = 8_000_000 + const ( LED = LED_BUILTIN LED1 = LED_GREEN diff --git a/src/machine/board_stm32f4disco.go b/src/machine/board_stm32f4disco.go index d048fcacf..c377063a0 100644 --- a/src/machine/board_stm32f4disco.go +++ b/src/machine/board_stm32f4disco.go @@ -7,6 +7,8 @@ import ( "runtime/interrupt" ) +const xtalHz = 8_000_000 + const ( LED1 = LED_GREEN LED2 = LED_ORANGE diff --git a/src/machine/machine_stm32_pll.go b/src/machine/machine_stm32_pll.go new file mode 100644 index 000000000..ceacac095 --- /dev/null +++ b/src/machine/machine_stm32_pll.go @@ -0,0 +1,10 @@ +//go:build stm32 + +package machine + +// PLLParams holds the HSE main-PLL dividers/multipliers (RCC_PLLCFGR M/N/P/Q/R +// fields) needed to reach a chip's target VCO/SYSCLK frequency from a given +// crystal frequency. R is left zero on chips without a PLLR output. +type PLLParams struct { + M, N, P, Q, R uint32 +} diff --git a/src/machine/machine_stm32f103.go b/src/machine/machine_stm32f103.go index d946ef4aa..b1fd4b3c6 100644 --- a/src/machine/machine_stm32f103.go +++ b/src/machine/machine_stm32f103.go @@ -12,7 +12,10 @@ import ( ) func CPUFrequency() uint32 { - return 72000000 + pll := PLLParams72MHz() + // Prediv/Mul are raw RCC_CFGR register field values: the prescaler is + // encoded as (divisor-1) and the multiplier as (multiplier-2). + return xtalHz / (pll.Prediv + 1) * (pll.Mul + 2) } var deviceIDAddr = []uintptr{0x1FFFF7E8, 0x1FFFF7EC, 0x1FFFF7F0} diff --git a/src/machine/machine_stm32f103_pll_72mhz.go b/src/machine/machine_stm32f103_pll_72mhz.go new file mode 100644 index 000000000..c5d2f2841 --- /dev/null +++ b/src/machine/machine_stm32f103_pll_72mhz.go @@ -0,0 +1,27 @@ +//go:build stm32 && stm32f103 + +package machine + +import "device/stm32" + +// F103PLLParams holds the HSE prescaler (PLLXTPRE) and PLL multiplier +// (PLLMUL) needed to reach 72MHz SYSCLK from a given crystal frequency. The +// F1 PLL has no dedicated input divider, only an optional /2 HSE prescaler. +type F103PLLParams struct { + Prediv uint32 + Mul uint32 +} + +func PLLParams72MHz() F103PLLParams { + switch xtalHz { + case 8_000_000: + return F103PLLParams{Prediv: stm32.RCC_CFGR_PLLXTPRE_Div1, Mul: stm32.RCC_CFGR_PLLMUL_Mul9} + case 12_000_000: + return F103PLLParams{Prediv: stm32.RCC_CFGR_PLLXTPRE_Div1, Mul: stm32.RCC_CFGR_PLLMUL_Mul6} + case 16_000_000: + // 16MHz / 2 (PLLXTPRE) x9 = 72MHz. + return F103PLLParams{Prediv: stm32.RCC_CFGR_PLLXTPRE_Div2, Mul: stm32.RCC_CFGR_PLLMUL_Mul9} + default: + panic("unsupported xtal frequency") + } +} diff --git a/src/machine/machine_stm32f40x.go b/src/machine/machine_stm32f40x.go index 953c7fab4..5fcbbcbe8 100644 --- a/src/machine/machine_stm32f40x.go +++ b/src/machine/machine_stm32f40x.go @@ -3,7 +3,8 @@ package machine func CPUFrequency() uint32 { - return 168000000 + pll := PLLParams168MHz() + return xtalHz / pll.M * pll.N / pll.P } // Internal use: configured speed of the APB1 and APB2 timers, this should be kept diff --git a/src/machine/machine_stm32f469.go b/src/machine/machine_stm32f469.go index dfda91fc3..9d0c7e473 100644 --- a/src/machine/machine_stm32f469.go +++ b/src/machine/machine_stm32f469.go @@ -3,7 +3,8 @@ package machine func CPUFrequency() uint32 { - return 180000000 + pll := PLLParams180MHz() + return xtalHz / pll.M * pll.N / pll.P } // Internal use: configured speed of the APB1 and APB2 timers, this should be kept diff --git a/src/machine/machine_stm32f4_pll_168mhz.go b/src/machine/machine_stm32f4_pll_168mhz.go new file mode 100644 index 000000000..17ad97a9b --- /dev/null +++ b/src/machine/machine_stm32f4_pll_168mhz.go @@ -0,0 +1,20 @@ +//go:build stm32f4 && (stm32f405 || stm32f407) + +package machine + +// PLLParams168MHz returns the HSE PLL dividers needed to reach a 336MHz VCO +// (168MHz SYSCLK, P=2, Q=7) for the configured crystal frequency. M is chosen +// to bring the PLL input (HSE/M) to 2MHz, the value RM0090 pg. 95 recommends +// to minimize jitter; the previous hardcoded F407 table used 1MHz. +func PLLParams168MHz() PLLParams { + switch xtalHz { + case 8_000_000: + return PLLParams{M: 4, N: 168, P: 2, Q: 7} + case 12_000_000: + return PLLParams{M: 6, N: 168, P: 2, Q: 7} + case 16_000_000: + return PLLParams{M: 8, N: 168, P: 2, Q: 7} + default: + panic("unsupported xtal frequency") + } +} diff --git a/src/machine/machine_stm32f4_pll_180mhz.go b/src/machine/machine_stm32f4_pll_180mhz.go new file mode 100644 index 000000000..4272b9d71 --- /dev/null +++ b/src/machine/machine_stm32f4_pll_180mhz.go @@ -0,0 +1,18 @@ +//go:build stm32f4 && stm32f469 + +package machine + +// PLLParams180MHz returns the HSE PLL dividers needed to reach a 360MHz VCO +// (180MHz SYSCLK, P=2, Q=7, R=6) for the configured crystal frequency. +func PLLParams180MHz() PLLParams { + switch xtalHz { + case 8_000_000: + return PLLParams{M: 4, N: 180, P: 2, Q: 7, R: 6} + case 12_000_000: + return PLLParams{M: 6, N: 180, P: 2, Q: 7, R: 6} + case 16_000_000: + return PLLParams{M: 8, N: 180, P: 2, Q: 7, R: 6} + default: + panic("unsupported xtal frequency") + } +} diff --git a/src/machine/machine_stm32f7_pll_216mhz.go b/src/machine/machine_stm32f7_pll_216mhz.go new file mode 100644 index 000000000..8237ff624 --- /dev/null +++ b/src/machine/machine_stm32f7_pll_216mhz.go @@ -0,0 +1,18 @@ +//go:build stm32 && stm32f7x2 + +package machine + +// PLLParams216MHz returns the HSE PLL dividers needed to reach a 432MHz VCO +// (216MHz SYSCLK, P=2, Q=9) for the configured crystal frequency. +func PLLParams216MHz() PLLParams { + switch xtalHz { + case 8_000_000: + return PLLParams{M: 4, N: 216, P: 2, Q: 9} + case 12_000_000: + return PLLParams{M: 6, N: 216, P: 2, Q: 9} + case 16_000_000: + return PLLParams{M: 8, N: 216, P: 2, Q: 9} + default: + panic("unsupported xtal frequency") + } +} diff --git a/src/machine/machine_stm32f7x2.go b/src/machine/machine_stm32f7x2.go index 350b5fd7b..7f201a437 100644 --- a/src/machine/machine_stm32f7x2.go +++ b/src/machine/machine_stm32f7x2.go @@ -9,7 +9,8 @@ import ( ) func CPUFrequency() uint32 { - return 216000000 + pll := PLLParams216MHz() + return xtalHz / pll.M * pll.N / pll.P } // Internal use: configured speed of the APB1 and APB2 timers, this should be kept diff --git a/src/runtime/runtime_stm32f103.go b/src/runtime/runtime_stm32f103.go index 702d77389..0b68ac0db 100644 --- a/src/runtime/runtime_stm32f103.go +++ b/src/runtime/runtime_stm32f103.go @@ -31,8 +31,10 @@ func buffered() int { return machine.Serial.Buffered() } -// initCLK sets clock to 72MHz using HSE 8MHz crystal w/ PLL X 9 (8MHz x 9 = 72MHz). +// initCLK sets clock to 72MHz using the board's HSE crystal (defined by the xtalHz) w/ PLL. func initCLK() { + pll := machine.PLLParams72MHz() + stm32.FLASH.ACR.SetBits(stm32.FLASH_ACR_LATENCY_WS2) // Two wait states, per datasheet stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div2 << stm32.RCC_CFGR_PPRE1_Pos) // prescale PCLK1 = HCLK/2 stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE2_Div1 << stm32.RCC_CFGR_PPRE2_Pos) // prescale PCLK2 = HCLK/1 @@ -49,9 +51,10 @@ func initCLK() { for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSIRDY) { } - stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PLLSRC) // set PLL source to HSE - stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PLLMUL_Mul9 << stm32.RCC_CFGR_PLLMUL_Pos) // multiply by 9 - stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) // enable the PLL + stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PLLSRC) // set PLL source to HSE + stm32.RCC.CFGR.SetBits(pll.Prediv << stm32.RCC_CFGR_PLLXTPRE_Pos) // optional HSE /2 prescaler + stm32.RCC.CFGR.SetBits(pll.Mul << stm32.RCC_CFGR_PLLMUL_Pos) // PLL multiplier + stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) // enable the PLL // wait for the PLLRDY flag for !stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { diff --git a/src/runtime/runtime_stm32f4.go b/src/runtime/runtime_stm32f4.go index be6b73255..c323b3493 100644 --- a/src/runtime/runtime_stm32f4.go +++ b/src/runtime/runtime_stm32f4.go @@ -73,7 +73,7 @@ func initCLK() { // PCLK1 = HCLK / 4 stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div4 << stm32.RCC_CFGR_PPRE1_Pos) // Configure the main PLL - stm32.RCC.PLLCFGR.Set(PLL_CFGR) + stm32.RCC.PLLCFGR.Set(pllCFGR()) // Enable main PLL stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) // Wait till the main PLL is ready diff --git a/src/runtime/runtime_stm32f405.go b/src/runtime/runtime_stm32f405.go index 8481a206e..f4df584cf 100644 --- a/src/runtime/runtime_stm32f405.go +++ b/src/runtime/runtime_stm32f405.go @@ -8,15 +8,15 @@ import ( ) const ( - // +----------------------+ - // | Clock Settings | - // +-------------+--------+ - // | HSE | 12mhz | - // | SYSCLK | 168mhz | - // | HCLK | 168mhz | - // | APB1(PCLK1) | 42mhz | - // | APB2(PCLK2) | 84mhz | - // +-------------+--------+ + // +---------------------------------------------+ + // | Clock Settings | + // +-------------+-------------------------------+ + // | HSE | selectable (xtal_8/12/16_mhz) | + // | SYSCLK | 168mhz | + // | HCLK | 168mhz | + // | APB1(PCLK1) | 42mhz | + // | APB2(PCLK2) | 84mhz | + // +-------------+-------------------------------+ HCLK_FREQ_HZ = 168000000 PCLK1_FREQ_HZ = HCLK_FREQ_HZ / 4 PCLK2_FREQ_HZ = HCLK_FREQ_HZ / 2 @@ -29,11 +29,6 @@ const ( PLL_SRC_HSE = 1 << stm32.RCC_PLLCFGR_PLLSRC_Pos // use HSE for PLL and PLLI2S PLL_SRC_HSI = 0 // use HSI for PLL and PLLI2S - PLL_DIV_M = 6 << stm32.RCC_PLLCFGR_PLLM_Pos - PLL_MLT_N = 168 << stm32.RCC_PLLCFGR_PLLN_Pos - PLL_DIV_P = ((2 >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos - PLL_DIV_Q = 7 << stm32.RCC_PLLCFGR_PLLQ_Pos - SYSCLK_SRC_PLL = stm32.RCC_CFGR_SW_PLL << stm32.RCC_CFGR_SW_Pos SYSCLK_STAT_PLL = stm32.RCC_CFGR_SWS_PLL << stm32.RCC_CFGR_SWS_Pos @@ -94,7 +89,12 @@ func initOSC() { } // set HSE as PLL source and configure clock divisors - stm32.RCC.PLLCFGR.Set(PLL_SRC_HSE | PLL_DIV_M | PLL_MLT_N | PLL_DIV_P | PLL_DIV_Q) + pll := machine.PLLParams168MHz() + stm32.RCC.PLLCFGR.Set(PLL_SRC_HSE | + pll.M<>1)-1)<> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) | - (1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos) -) +const HSE_STARTUP_TIMEOUT = 0x0500 + +// pllCFGR builds the RCC_PLLCFGR value for the current xtal - see RM0090 +// Reference Manual pg. 95. +func pllCFGR() uint32 { + pll := machine.PLLParams168MHz() + return pll.M | (pll.N << stm32.RCC_PLLCFGR_PLLN_Pos) | (((pll.P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) | + (1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (pll.Q << stm32.RCC_PLLCFGR_PLLQ_Pos) +} diff --git a/src/runtime/runtime_stm32f469.go b/src/runtime/runtime_stm32f469.go index 38343dc0d..9a540e16d 100644 --- a/src/runtime/runtime_stm32f469.go +++ b/src/runtime/runtime_stm32f469.go @@ -2,27 +2,28 @@ package runtime -import "device/stm32" +import ( + "device/stm32" + "machine" +) /* clock settings - +-------------+--------+ - | HSE | 8mhz | - | SYSCLK | 180mhz | - | HCLK | 180mhz | - | APB2(PCLK2) | 90mhz | - | APB1(PCLK1) | 45mhz | - +-------------+--------+ + +-------------+--------------------------------+ + | HSE | selectable (xtal_8/12/16_mhz) | + | SYSCLK | 180mhz | + | HCLK | 180mhz | + | APB2(PCLK2) | 90mhz | + | APB1(PCLK1) | 45mhz | + +-------------+--------------------------------+ */ -const ( - HSE_STARTUP_TIMEOUT = 0x0500 - // PLL Options - See RM0386 Reference Manual pg. 148 - PLL_M = 8 // PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N - PLL_N = 360 - PLL_P = 2 // SYSCLK = PLL_VCO / PLL_P - PLL_Q = 7 // USB OTS FS, SDIO and RNG Clock = PLL_VCO / PLL_Q - PLL_R = 6 // DSI - PLL_CFGR = PLL_M | (PLL_N << stm32.RCC_PLLCFGR_PLLN_Pos) | (((PLL_P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) | - (1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos) | (PLL_R << stm32.RCC_PLLCFGR_PLLR_Pos) -) +const HSE_STARTUP_TIMEOUT = 0x0500 + +// pllCFGR builds the RCC_PLLCFGR value for the current xtal - see RM0386 +// Reference Manual pg. 148. +func pllCFGR() uint32 { + pll := machine.PLLParams180MHz() + return pll.M | (pll.N << stm32.RCC_PLLCFGR_PLLN_Pos) | (((pll.P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) | + (1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (pll.Q << stm32.RCC_PLLCFGR_PLLQ_Pos) | (pll.R << stm32.RCC_PLLCFGR_PLLR_Pos) +} diff --git a/src/runtime/runtime_stm32f7x2.go b/src/runtime/runtime_stm32f7x2.go index af78d70b7..7084994d4 100644 --- a/src/runtime/runtime_stm32f7x2.go +++ b/src/runtime/runtime_stm32f7x2.go @@ -10,21 +10,15 @@ import ( /* clock settings - +-------------+--------+ - | HSE | 8mhz | - | SYSCLK | 216mhz | - | HCLK | 216mhz | - | APB1(PCLK1) | 27mhz | - | APB2(PCLK2) | 108mhz | - +-------------+--------+ + +-------------+--------------------------------+ + | HSE | selectable (xtal_8/12/16_mhz) | + | SYSCLK | 216mhz | + | HCLK | 216mhz | + | APB1(PCLK1) | 27mhz | + | APB2(PCLK2) | 108mhz | + +-------------+--------------------------------+ */ -const ( - HSE_STARTUP_TIMEOUT = 0x0500 - PLL_M = 4 - PLL_N = 216 - PLL_P = 2 - PLL_Q = 9 -) +const HSE_STARTUP_TIMEOUT = 0x0500 func init() { initCLK() @@ -107,12 +101,13 @@ func initOsc() { } // Configure the PLL: HSE as source, use SVD constants for positions. + pll := machine.PLLParams216MHz() stm32.RCC.PLLCFGR.Set( (stm32.RCC_PLLCFGR_PLLSRC_HSE << stm32.RCC_PLLCFGR_PLLSRC_Pos) | - (PLL_M << stm32.RCC_PLLCFGR_PLLM_Pos) | - (PLL_N << stm32.RCC_PLLCFGR_PLLN_Pos) | - (((PLL_P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) | - (PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos)) + (pll.M << stm32.RCC_PLLCFGR_PLLM_Pos) | + (pll.N << stm32.RCC_PLLCFGR_PLLN_Pos) | + (((pll.P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) | + (pll.Q << stm32.RCC_PLLCFGR_PLLQ_Pos)) // Enable the PLL, wait until ready stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON)