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.
This commit is contained in:
Konstantin Sharlaimov
2026-07-12 16:30:16 +02:00
committed by Ron Evans
parent b8adb803c4
commit 5f02d6b659
22 changed files with 193 additions and 79 deletions
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt" "runtime/interrupt"
) )
const xtalHz = 8_000_000
// Pins printed on the silkscreen // Pins printed on the silkscreen
const ( const (
C13 = PC13 C13 = PC13
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt" "runtime/interrupt"
) )
const xtalHz = 12_000_000
const ( const (
NUM_DIGITAL_IO_PINS = 39 NUM_DIGITAL_IO_PINS = 39
NUM_ANALOG_IO_PINS = 7 NUM_ANALOG_IO_PINS = 7
+2
View File
@@ -10,6 +10,8 @@ import (
"runtime/interrupt" "runtime/interrupt"
) )
const xtalHz = 8_000_000
// LED is also wired to the SD card card detect (CD) pin. // LED is also wired to the SD card card detect (CD) pin.
const LED = PD12 const LED = PD12
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt" "runtime/interrupt"
) )
const xtalHz = 8_000_000
const ( const (
LED = LED_BUILTIN LED = LED_BUILTIN
LED_BUILTIN = LED_GREEN LED_BUILTIN = LED_GREEN
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt" "runtime/interrupt"
) )
const xtalHz = 8_000_000
const ( const (
LED = LED_BUILTIN LED = LED_BUILTIN
LED_BUILTIN = LED_GREEN LED_BUILTIN = LED_GREEN
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt" "runtime/interrupt"
) )
const xtalHz = 8_000_000
const ( const (
LED = LED_BUILTIN LED = LED_BUILTIN
LED1 = LED_GREEN LED1 = LED_GREEN
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt" "runtime/interrupt"
) )
const xtalHz = 8_000_000
const ( const (
LED1 = LED_GREEN LED1 = LED_GREEN
LED2 = LED_ORANGE LED2 = LED_ORANGE
+10
View File
@@ -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
}
+4 -1
View File
@@ -12,7 +12,10 @@ import (
) )
func CPUFrequency() uint32 { 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} var deviceIDAddr = []uintptr{0x1FFFF7E8, 0x1FFFF7EC, 0x1FFFF7F0}
@@ -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")
}
}
+2 -1
View File
@@ -3,7 +3,8 @@
package machine package machine
func CPUFrequency() uint32 { 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 // Internal use: configured speed of the APB1 and APB2 timers, this should be kept
+2 -1
View File
@@ -3,7 +3,8 @@
package machine package machine
func CPUFrequency() uint32 { 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 // Internal use: configured speed of the APB1 and APB2 timers, this should be kept
+20
View File
@@ -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")
}
}
+18
View File
@@ -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")
}
}
+18
View File
@@ -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")
}
}
+2 -1
View File
@@ -9,7 +9,8 @@ import (
) )
func CPUFrequency() uint32 { 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 // Internal use: configured speed of the APB1 and APB2 timers, this should be kept
+7 -4
View File
@@ -31,8 +31,10 @@ func buffered() int {
return machine.Serial.Buffered() 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() { func initCLK() {
pll := machine.PLLParams72MHz()
stm32.FLASH.ACR.SetBits(stm32.FLASH_ACR_LATENCY_WS2) // Two wait states, per datasheet 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_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 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) { 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_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.CFGR.SetBits(pll.Prediv << stm32.RCC_CFGR_PLLXTPRE_Pos) // optional HSE /2 prescaler
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) // enable the PLL 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 // wait for the PLLRDY flag
for !stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { for !stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) {
+1 -1
View File
@@ -73,7 +73,7 @@ func initCLK() {
// PCLK1 = HCLK / 4 // PCLK1 = HCLK / 4
stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div4 << stm32.RCC_CFGR_PPRE1_Pos) stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div4 << stm32.RCC_CFGR_PPRE1_Pos)
// Configure the main PLL // Configure the main PLL
stm32.RCC.PLLCFGR.Set(PLL_CFGR) stm32.RCC.PLLCFGR.Set(pllCFGR())
// Enable main PLL // Enable main PLL
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON)
// Wait till the main PLL is ready // Wait till the main PLL is ready
+15 -15
View File
@@ -8,15 +8,15 @@ import (
) )
const ( const (
// +----------------------+ // +---------------------------------------------+
// | Clock Settings | // | Clock Settings |
// +-------------+--------+ // +-------------+-------------------------------+
// | HSE | 12mhz | // | HSE | selectable (xtal_8/12/16_mhz) |
// | SYSCLK | 168mhz | // | SYSCLK | 168mhz |
// | HCLK | 168mhz | // | HCLK | 168mhz |
// | APB1(PCLK1) | 42mhz | // | APB1(PCLK1) | 42mhz |
// | APB2(PCLK2) | 84mhz | // | APB2(PCLK2) | 84mhz |
// +-------------+--------+ // +-------------+-------------------------------+
HCLK_FREQ_HZ = 168000000 HCLK_FREQ_HZ = 168000000
PCLK1_FREQ_HZ = HCLK_FREQ_HZ / 4 PCLK1_FREQ_HZ = HCLK_FREQ_HZ / 4
PCLK2_FREQ_HZ = HCLK_FREQ_HZ / 2 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_HSE = 1 << stm32.RCC_PLLCFGR_PLLSRC_Pos // use HSE for PLL and PLLI2S
PLL_SRC_HSI = 0 // use HSI 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_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 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 // 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<<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 PLL and wait for it to sync // enable PLL and wait for it to sync
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON)
+20 -18
View File
@@ -2,26 +2,28 @@
package runtime package runtime
import "device/stm32" import (
"device/stm32"
"machine"
)
/* /*
clock settings clock settings
+-------------+--------+ +-------------+--------------------------------+
| HSE | 8mhz | | HSE | selectable (xtal_8/12/16_mhz) |
| SYSCLK | 168mhz | | SYSCLK | 168mhz |
| HCLK | 168mhz | | HCLK | 168mhz |
| APB2(PCLK2) | 84mhz | | APB2(PCLK2) | 84mhz |
| APB1(PCLK1) | 42mhz | | APB1(PCLK1) | 42mhz |
+-------------+--------+ +-------------+--------------------------------+
*/ */
const ( const HSE_STARTUP_TIMEOUT = 0x0500
HSE_STARTUP_TIMEOUT = 0x0500
// PLL Options - See RM0090 Reference Manual pg. 95 // pllCFGR builds the RCC_PLLCFGR value for the current xtal - see RM0090
PLL_M = 8 // PLL_VCO = (HSE_VALUE or HSI_VLAUE / PLL_M) * PLL_N // Reference Manual pg. 95.
PLL_N = 336 func pllCFGR() uint32 {
PLL_P = 2 // SYSCLK = PLL_VCO / PLL_P pll := machine.PLLParams168MHz()
PLL_Q = 7 // USB OTS FS, SDIO and RNG Clock = PLL_VCO / PLL_Q return pll.M | (pll.N << stm32.RCC_PLLCFGR_PLLN_Pos) | (((pll.P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) |
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)
(1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos) }
)
+20 -19
View File
@@ -2,27 +2,28 @@
package runtime package runtime
import "device/stm32" import (
"device/stm32"
"machine"
)
/* /*
clock settings clock settings
+-------------+--------+ +-------------+--------------------------------+
| HSE | 8mhz | | HSE | selectable (xtal_8/12/16_mhz) |
| SYSCLK | 180mhz | | SYSCLK | 180mhz |
| HCLK | 180mhz | | HCLK | 180mhz |
| APB2(PCLK2) | 90mhz | | APB2(PCLK2) | 90mhz |
| APB1(PCLK1) | 45mhz | | APB1(PCLK1) | 45mhz |
+-------------+--------+ +-------------+--------------------------------+
*/ */
const ( const HSE_STARTUP_TIMEOUT = 0x0500
HSE_STARTUP_TIMEOUT = 0x0500
// PLL Options - See RM0386 Reference Manual pg. 148 // pllCFGR builds the RCC_PLLCFGR value for the current xtal - see RM0386
PLL_M = 8 // PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N // Reference Manual pg. 148.
PLL_N = 360 func pllCFGR() uint32 {
PLL_P = 2 // SYSCLK = PLL_VCO / PLL_P pll := machine.PLLParams180MHz()
PLL_Q = 7 // USB OTS FS, SDIO and RNG Clock = PLL_VCO / PLL_Q return pll.M | (pll.N << stm32.RCC_PLLCFGR_PLLN_Pos) | (((pll.P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) |
PLL_R = 6 // DSI (1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (pll.Q << stm32.RCC_PLLCFGR_PLLQ_Pos) | (pll.R << stm32.RCC_PLLCFGR_PLLR_Pos)
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)
)
+13 -18
View File
@@ -10,21 +10,15 @@ import (
/* /*
clock settings clock settings
+-------------+--------+ +-------------+--------------------------------+
| HSE | 8mhz | | HSE | selectable (xtal_8/12/16_mhz) |
| SYSCLK | 216mhz | | SYSCLK | 216mhz |
| HCLK | 216mhz | | HCLK | 216mhz |
| APB1(PCLK1) | 27mhz | | APB1(PCLK1) | 27mhz |
| APB2(PCLK2) | 108mhz | | APB2(PCLK2) | 108mhz |
+-------------+--------+ +-------------+--------------------------------+
*/ */
const ( const HSE_STARTUP_TIMEOUT = 0x0500
HSE_STARTUP_TIMEOUT = 0x0500
PLL_M = 4
PLL_N = 216
PLL_P = 2
PLL_Q = 9
)
func init() { func init() {
initCLK() initCLK()
@@ -107,12 +101,13 @@ func initOsc() {
} }
// Configure the PLL: HSE as source, use SVD constants for positions. // Configure the PLL: HSE as source, use SVD constants for positions.
pll := machine.PLLParams216MHz()
stm32.RCC.PLLCFGR.Set( stm32.RCC.PLLCFGR.Set(
(stm32.RCC_PLLCFGR_PLLSRC_HSE << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (stm32.RCC_PLLCFGR_PLLSRC_HSE << stm32.RCC_PLLCFGR_PLLSRC_Pos) |
(PLL_M << stm32.RCC_PLLCFGR_PLLM_Pos) | (pll.M << stm32.RCC_PLLCFGR_PLLM_Pos) |
(PLL_N << stm32.RCC_PLLCFGR_PLLN_Pos) | (pll.N << stm32.RCC_PLLCFGR_PLLN_Pos) |
(((PLL_P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) | (((pll.P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) |
(PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos)) (pll.Q << stm32.RCC_PLLCFGR_PLLQ_Pos))
// Enable the PLL, wait until ready // Enable the PLL, wait until ready
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON)