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"
)
const xtalHz = 8_000_000
// Pins printed on the silkscreen
const (
C13 = PC13
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt"
)
const xtalHz = 12_000_000
const (
NUM_DIGITAL_IO_PINS = 39
NUM_ANALOG_IO_PINS = 7
+2
View File
@@ -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
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt"
)
const xtalHz = 8_000_000
const (
LED = LED_BUILTIN
LED_BUILTIN = LED_GREEN
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt"
)
const xtalHz = 8_000_000
const (
LED = LED_BUILTIN
LED_BUILTIN = LED_GREEN
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt"
)
const xtalHz = 8_000_000
const (
LED = LED_BUILTIN
LED1 = LED_GREEN
+2
View File
@@ -7,6 +7,8 @@ import (
"runtime/interrupt"
)
const xtalHz = 8_000_000
const (
LED1 = LED_GREEN
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 {
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}
@@ -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
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
+2 -1
View File
@@ -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
+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 {
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
+7 -4
View File
@@ -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) {
+1 -1
View File
@@ -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
+15 -15
View File
@@ -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<<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
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON)
+20 -18
View File
@@ -2,26 +2,28 @@
package runtime
import "device/stm32"
import (
"device/stm32"
"machine"
)
/*
clock settings
+-------------+--------+
| HSE | 8mhz |
| SYSCLK | 168mhz |
| HCLK | 168mhz |
| APB2(PCLK2) | 84mhz |
| APB1(PCLK1) | 42mhz |
+-------------+--------+
+-------------+--------------------------------+
| HSE | selectable (xtal_8/12/16_mhz) |
| SYSCLK | 168mhz |
| HCLK | 168mhz |
| APB2(PCLK2) | 84mhz |
| APB1(PCLK1) | 42mhz |
+-------------+--------------------------------+
*/
const (
HSE_STARTUP_TIMEOUT = 0x0500
// PLL Options - See RM0090 Reference Manual pg. 95
PLL_M = 8 // PLL_VCO = (HSE_VALUE or HSI_VLAUE / PLL_M) * PLL_N
PLL_N = 336
PLL_P = 2 // SYSCLK = PLL_VCO / PLL_P
PLL_Q = 7 // USB OTS FS, SDIO and RNG Clock = PLL_VCO / PLL_Q
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)
)
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)
}
+20 -19
View File
@@ -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)
}
+13 -18
View File
@@ -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)