From 5a9bcc68af1f858135002dc5ea71a921908e1fc9 Mon Sep 17 00:00:00 2001 From: akif999 Date: Sun, 16 Aug 2026 00:58:09 +0900 Subject: [PATCH 1/4] feat(machine/stm32): add STM32F401 and NUCLEO-F401RE support --- make/smoketest.mk | 2 + src/crypto/rand/rand_baremetal.go | 2 +- src/machine/board_nucleof401re.go | 119 +++++++++ src/machine/machine_stm32_adc_f4.go | 2 +- src/machine/machine_stm32_otgfs_usb.go | 2 +- src/machine/machine_stm32_rng.go | 2 +- src/machine/machine_stm32f4.go | 271 -------------------- src/machine/machine_stm32f401.go | 17 ++ src/machine/machine_stm32f401_adc.go | 87 +++++++ src/machine/machine_stm32f401_periph.go | 144 +++++++++++ src/machine/machine_stm32f4_extended.go | 286 ++++++++++++++++++++++ src/machine/machine_stm32f4_otgfs_vbus.go | 2 +- src/machine/machine_stm32f4_pll_84mhz.go | 19 ++ src/machine/usb.go | 2 +- src/runtime/rand_hwrng.go | 2 +- src/runtime/rand_norng.go | 2 +- src/runtime/runtime_stm32f401.go | 126 ++++++++++ targets/nucleo-f401re.json | 13 + targets/stm32f401.ld | 9 + 19 files changed, 830 insertions(+), 279 deletions(-) create mode 100644 src/machine/board_nucleof401re.go create mode 100644 src/machine/machine_stm32f401.go create mode 100644 src/machine/machine_stm32f401_adc.go create mode 100644 src/machine/machine_stm32f401_periph.go create mode 100644 src/machine/machine_stm32f4_extended.go create mode 100644 src/machine/machine_stm32f4_pll_84mhz.go create mode 100644 src/runtime/runtime_stm32f401.go create mode 100644 targets/nucleo-f401re.json create mode 100644 targets/stm32f401.ld diff --git a/make/smoketest.mk b/make/smoketest.mk index 9b540da947..94c8b97c77 100644 --- a/make/smoketest.mk +++ b/make/smoketest.mk @@ -362,6 +362,8 @@ ifneq ($(STM32), 0) @$(MD5SUM) $(SMOKE_OUT).hex $(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-f103rb examples/blinky1 @$(MD5SUM) $(SMOKE_OUT).hex + $(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-f401re examples/blinky1 + @$(MD5SUM) $(SMOKE_OUT).hex $(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-f722ze examples/blinky1 @$(MD5SUM) $(SMOKE_OUT).hex $(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-h753zi examples/blinky1 diff --git a/src/crypto/rand/rand_baremetal.go b/src/crypto/rand/rand_baremetal.go index 6cf847ae64..82b43ad7ed 100644 --- a/src/crypto/rand/rand_baremetal.go +++ b/src/crypto/rand/rand_baremetal.go @@ -1,4 +1,4 @@ -//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32u0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt) +//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32u0 || stm32f401)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt) // If you update the above build constraint, you'll probably also need to update // src/runtime/rand_hwrng.go. diff --git a/src/machine/board_nucleof401re.go b/src/machine/board_nucleof401re.go new file mode 100644 index 0000000000..28d26f87fa --- /dev/null +++ b/src/machine/board_nucleof401re.go @@ -0,0 +1,119 @@ +//go:build nucleof401re + +// Schematic: https://www.st.com/resource/en/user_manual/um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf +// Datasheet: https://www.st.com/resource/en/datasheet/stm32f401re.pdf + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const xtalHz = 8_000_000 + +const ( + // Arduino Pins + A0 = PA0 + A1 = PA1 + A2 = PA4 + A3 = PB0 + A4 = PC1 + A5 = PC0 + + D0 = PA3 + D1 = PA2 + D2 = PA10 + D3 = PB3 + D4 = PB5 + D5 = PB4 + D6 = PB10 + D7 = PA8 + D8 = PA9 + D9 = PC7 + D10 = PB6 + D11 = PA7 + D12 = PA6 + D13 = PA5 + D14 = PB9 + D15 = PB8 +) + +// User LD2: the green LED is a user LED connected to Arduino signal D13 +// corresponding to STM32 I/O PA5. +const ( + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN + LED_GREEN = PA5 +) + +// BUTTON is the user button B1 connected to PC13 (active low). +const BUTTON = PC13 + +const ( + // UART pins + // PA2 and PA3 are connected to the ST-Link Virtual Com Port (VCP). + UART_TX_PIN = PA2 + UART_RX_PIN = PA3 + UART1_TX_PIN = UART_TX_PIN + UART1_RX_PIN = UART_RX_PIN + + // USART1 on Arduino D8 (PA9 = TX) / D2 (PA10 = RX). + // Use for external UART communication (e.g. with another board). + UART2_TX_PIN = PA9 + UART2_RX_PIN = PA10 + + // I2C pins + // PB8 / Arduino D15 is SCL, PB9 / Arduino D14 is SDA. + I2C0_SCL_PIN = PB8 + I2C0_SDA_PIN = PB9 + + // SPI pins + SPI1_SCK_PIN = PA5 + SPI1_SDI_PIN = PA6 + SPI1_SDO_PIN = PA7 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +var ( + // USART2 is the hardware serial port connected to the onboard ST-LINK + // debugger, exposed as a virtual COM port over USB. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + DefaultUART = UART1 + + // USART1 on PA9 (TX=D8) / PA10 (RX=D2). Use for external communication. + UART2 = &_UART2 + _UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART1, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + + // I2C1 is documented; I2C0 is an alias. + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3, + } + I2C0 = I2C1 + + // SPI1 is documented; SPI0 is an alias. + SPI1 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_SPI2, + } + SPI0 = SPI1 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART2.handleInterrupt) +} diff --git a/src/machine/machine_stm32_adc_f4.go b/src/machine/machine_stm32_adc_f4.go index df4984c1b4..92bc1c7a8a 100644 --- a/src/machine/machine_stm32_adc_f4.go +++ b/src/machine/machine_stm32_adc_f4.go @@ -1,4 +1,4 @@ -//go:build stm32f4 +//go:build stm32f4 && !stm32f401 package machine diff --git a/src/machine/machine_stm32_otgfs_usb.go b/src/machine/machine_stm32_otgfs_usb.go index 639a301ee7..3505fa1ac7 100644 --- a/src/machine/machine_stm32_otgfs_usb.go +++ b/src/machine/machine_stm32_otgfs_usb.go @@ -1,4 +1,4 @@ -//go:build stm32f4 || stm32f7 +//go:build (stm32f4 && !stm32f401) || stm32f7 package machine diff --git a/src/machine/machine_stm32_rng.go b/src/machine/machine_stm32_rng.go index 5b05d2daa7..318f5dc1ad 100644 --- a/src/machine/machine_stm32_rng.go +++ b/src/machine/machine_stm32_rng.go @@ -1,4 +1,4 @@ -//go:build stm32 && !(stm32f103 || stm32l0x1 || stm32g0 || stm32u0) +//go:build stm32 && !(stm32f103 || stm32l0x1 || stm32g0 || stm32u0 || stm32f401) package machine diff --git a/src/machine/machine_stm32f4.go b/src/machine/machine_stm32f4.go index 2a989b10a8..2ab4ae5ded 100644 --- a/src/machine/machine_stm32f4.go +++ b/src/machine/machine_stm32f4.go @@ -188,35 +188,6 @@ const ( PK15 = portK + 15 ) -func (p Pin) getPort() *stm32.GPIO_Type { - switch p / 16 { - case 0: - return stm32.GPIOA - case 1: - return stm32.GPIOB - case 2: - return stm32.GPIOC - case 3: - return stm32.GPIOD - case 4: - return stm32.GPIOE - case 5: - return stm32.GPIOF - case 6: - return stm32.GPIOG - case 7: - return stm32.GPIOH - case 8: - return stm32.GPIOI - case 9: - return stm32.GPIOJ - case 10: - return stm32.GPIOK - default: - panic("machine: unknown port") - } -} - // enableClock enables the clock for this desired GPIO port. func (p Pin) enableClock() { bit := p / 16 @@ -268,84 +239,6 @@ func (p Pin) registerInterrupt() interrupt.Interrupt { return interrupt.Interrupt{} } -// Enable peripheral clock -func enableAltFuncClock(bus unsafe.Pointer) { - switch bus { - case unsafe.Pointer(stm32.DAC): // DAC interface clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_DACEN) - case unsafe.Pointer(stm32.PWR): // Power interface clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) - case unsafe.Pointer(stm32.CAN2): // CAN 2 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_CAN2EN) - case unsafe.Pointer(stm32.CAN1): // CAN 1 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_CAN1EN) - case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C3EN) - case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C2EN) - case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) - case unsafe.Pointer(stm32.UART5): // UART5 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_UART5EN) - case unsafe.Pointer(stm32.UART4): // UART4 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_UART4EN) - case unsafe.Pointer(stm32.USART3): // USART3 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART3EN) - case unsafe.Pointer(stm32.USART2): // USART2 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) - case unsafe.Pointer(stm32.SPI3): // SPI3 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI3EN) - case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI2EN) - case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_WWDGEN) - case unsafe.Pointer(stm32.TIM14): // TIM14 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM14EN) - case unsafe.Pointer(stm32.TIM13): // TIM13 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM13EN) - case unsafe.Pointer(stm32.TIM12): // TIM12 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM12EN) - case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM7EN) - case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM6EN) - case unsafe.Pointer(stm32.TIM5): // TIM5 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM5EN) - case unsafe.Pointer(stm32.TIM4): // TIM4 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM4EN) - case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) - case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable - stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM2EN) - case unsafe.Pointer(stm32.TIM11): // TIM11 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM11EN) - case unsafe.Pointer(stm32.TIM10): // TIM10 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM10EN) - case unsafe.Pointer(stm32.TIM9): // TIM9 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM9EN) - case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) - case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) - case unsafe.Pointer(stm32.SDIO): // SDIO clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SDIOEN) - case unsafe.Pointer(stm32.ADC3): // ADC3 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC3EN) - case unsafe.Pointer(stm32.ADC2): // ADC2 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC2EN) - case unsafe.Pointer(stm32.ADC1): // ADC1 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC1EN) - case unsafe.Pointer(stm32.USART6): // USART6 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART6EN) - case unsafe.Pointer(stm32.USART1): // USART1 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) - case unsafe.Pointer(stm32.TIM8): // TIM8 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM8EN) - case unsafe.Pointer(stm32.TIM1): // TIM1 clock enable - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM1EN) - } -} - //---------- Timer related code var ( @@ -414,45 +307,6 @@ var ( busFreq: APB1_TIM_FREQ, } - TIM6 = TIM{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM6EN, - Device: stm32.TIM6, - Channels: [4]TimerChannel{ - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - }, - busFreq: APB1_TIM_FREQ, - } - - TIM7 = TIM{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM7EN, - Device: stm32.TIM7, - Channels: [4]TimerChannel{ - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - }, - busFreq: APB1_TIM_FREQ, - } - - TIM8 = TIM{ - EnableRegister: &stm32.RCC.APB2ENR, - EnableFlag: stm32.RCC_APB2ENR_TIM8EN, - Device: stm32.TIM8, - Channels: [4]TimerChannel{ - TimerChannel{Pins: []PinFunction{{PC6, AF3_TIM8_9_10_11}, {PI5, AF3_TIM8_9_10_11}}}, - TimerChannel{Pins: []PinFunction{{PC7, AF3_TIM8_9_10_11}, {PI6, AF3_TIM8_9_10_11}}}, - TimerChannel{Pins: []PinFunction{{PC8, AF3_TIM8_9_10_11}, {PI7, AF3_TIM8_9_10_11}}}, - TimerChannel{Pins: []PinFunction{{PC9, AF3_TIM8_9_10_11}, {PI2, AF3_TIM8_9_10_11}}}, - }, - busFreq: APB2_TIM_FREQ, - } - TIM9 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM9EN, @@ -491,117 +345,8 @@ var ( }, busFreq: APB2_TIM_FREQ, } - - TIM12 = TIM{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM12EN, - Device: stm32.TIM12, - Channels: [4]TimerChannel{ - TimerChannel{Pins: []PinFunction{{PB14, AF9_CAN1_CAN2_TIM12_13_14}, {PH6, AF9_CAN1_CAN2_TIM12_13_14}}}, - TimerChannel{Pins: []PinFunction{{PB15, AF9_CAN1_CAN2_TIM12_13_14}, {PH9, AF9_CAN1_CAN2_TIM12_13_14}}}, - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - }, - busFreq: APB1_TIM_FREQ, - } - - TIM13 = TIM{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM13EN, - Device: stm32.TIM13, - Channels: [4]TimerChannel{ - TimerChannel{Pins: []PinFunction{{PA6, AF9_CAN1_CAN2_TIM12_13_14}, {PF8, AF9_CAN1_CAN2_TIM12_13_14}}}, - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - }, - busFreq: APB1_TIM_FREQ, - } - - TIM14 = TIM{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM14EN, - Device: stm32.TIM14, - Channels: [4]TimerChannel{ - TimerChannel{Pins: []PinFunction{{PA7, AF9_CAN1_CAN2_TIM12_13_14}, {PF9, AF9_CAN1_CAN2_TIM12_13_14}}}, - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - TimerChannel{Pins: []PinFunction{}}, - }, - busFreq: APB1_TIM_FREQ, - } ) -func (t *TIM) registerUPInterrupt() interrupt.Interrupt { - switch t { - case &TIM1: - return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM1.handleUPInterrupt) - case &TIM2: - return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) - case &TIM3: - return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) - case &TIM4: - return interrupt.New(stm32.IRQ_TIM4, TIM4.handleUPInterrupt) - case &TIM5: - return interrupt.New(stm32.IRQ_TIM5, TIM5.handleUPInterrupt) - case &TIM6: - return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleUPInterrupt) - case &TIM7: - return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) - case &TIM8: - return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM8.handleUPInterrupt) - case &TIM9: - return interrupt.New(stm32.IRQ_TIM1_BRK_TIM9, TIM9.handleUPInterrupt) - case &TIM10: - return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM10.handleUPInterrupt) - case &TIM11: - return interrupt.New(stm32.IRQ_TIM1_TRG_COM_TIM11, TIM11.handleUPInterrupt) - case &TIM12: - return interrupt.New(stm32.IRQ_TIM8_BRK_TIM12, TIM12.handleUPInterrupt) - case &TIM13: - return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM13.handleUPInterrupt) - case &TIM14: - return interrupt.New(stm32.IRQ_TIM8_TRG_COM_TIM14, TIM14.handleUPInterrupt) - } - - return interrupt.Interrupt{} -} - -func (t *TIM) registerOCInterrupt() interrupt.Interrupt { - switch t { - case &TIM1: - return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleOCInterrupt) - case &TIM2: - return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) - case &TIM3: - return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) - case &TIM4: - return interrupt.New(stm32.IRQ_TIM4, TIM4.handleOCInterrupt) - case &TIM5: - return interrupt.New(stm32.IRQ_TIM5, TIM5.handleOCInterrupt) - case &TIM6: - return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleOCInterrupt) - case &TIM7: - return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) - case &TIM8: - return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM8.handleOCInterrupt) - case &TIM9: - return interrupt.New(stm32.IRQ_TIM1_BRK_TIM9, TIM9.handleOCInterrupt) - case &TIM10: - return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM10.handleOCInterrupt) - case &TIM11: - return interrupt.New(stm32.IRQ_TIM1_TRG_COM_TIM11, TIM11.handleOCInterrupt) - case &TIM12: - return interrupt.New(stm32.IRQ_TIM8_BRK_TIM12, TIM12.handleOCInterrupt) - case &TIM13: - return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM13.handleOCInterrupt) - case &TIM14: - return interrupt.New(stm32.IRQ_TIM8_TRG_COM_TIM14, TIM14.handleOCInterrupt) - } - - return interrupt.Interrupt{} -} - func (t *TIM) enableMainOutput() { t.Device.BDTR.SetBits(stm32.TIM_BDTR_MOE) } @@ -615,11 +360,6 @@ const ( PSC_MAX = 0x10000 ) -func initRNG() { - stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_RNGEN) - stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN) -} - // Alternative peripheral pin functions const ( AF0_SYSTEM = 0 @@ -648,17 +388,6 @@ func (uart *UART) configurePins(config UARTConfig) { config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) } -func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { - var clock uint32 - switch uart.Bus { - case stm32.USART1, stm32.USART6: - clock = CPUFrequency() / 2 // APB2 Frequency - case stm32.USART2, stm32.USART3, stm32.UART4, stm32.UART5: - clock = CPUFrequency() / 4 // APB1 Frequency - } - return clock / baudRate -} - func (uart *UART) setRegisters() { uart.rxReg = &uart.Bus.DR uart.txReg = &uart.Bus.DR diff --git a/src/machine/machine_stm32f401.go b/src/machine/machine_stm32f401.go new file mode 100644 index 0000000000..0d2a0fdb2e --- /dev/null +++ b/src/machine/machine_stm32f401.go @@ -0,0 +1,17 @@ +//go:build stm32f4 && stm32f401 + +package machine + +// CPUFrequency returns the current CPU frequency of the STM32F401. +// The PLL is configured to 84MHz SYSCLK from an external crystal. +func CPUFrequency() uint32 { + pll := PLLParams84MHz() + return xtalHz / pll.M * pll.N / pll.P +} + +// Internal use: configured speed of the APB1 and APB2 timers. +// STM32F401 at 84MHz: SYSCLK=84MHz, AHB=84MHz, APB1=42MHz, APB2=84MHz. +// APB1 prescaler = 2, so APB1 timer clock = 42MHz × 2 = 84MHz. +// APB2 prescaler = 1, so APB2 timer clock = 84MHz × 1 = 84MHz. +const APB1_TIM_FREQ = 84_000_000 +const APB2_TIM_FREQ = 84_000_000 diff --git a/src/machine/machine_stm32f401_adc.go b/src/machine/machine_stm32f401_adc.go new file mode 100644 index 0000000000..b77bcd76ba --- /dev/null +++ b/src/machine/machine_stm32f401_adc.go @@ -0,0 +1,87 @@ +//go:build stm32f401 + +package machine + +import ( + "device/stm32" + "unsafe" +) + +// InitADC initializes the registers needed for ADC1 on STM32F401. +func InitADC() { + enableAltFuncClock(unsafe.Pointer(stm32.ADC1)) + + stm32.ADC1.CR1.ClearBits(stm32.ADC_CR1_SCAN | stm32.ADC_CR1_RES_Msk) + stm32.ADC1.CR1.SetBits(stm32.ADC_CR1_RES_TwelveBit) + stm32.ADC1.CR2.ClearBits(stm32.ADC_CR2_CONT | stm32.ADC_CR2_ALIGN | stm32.ADC_CR2_EXTEN_Msk | stm32.ADC_CR2_EXTSEL_Msk) + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_CONT_Single | stm32.ADC_CR2_ALIGN_Right) + stm32.ADC1.SQR1.ClearBits(stm32.ADC_SQR1_L_Msk) + stm32.ADC1.SQR1.SetBits(2 << stm32.ADC_SQR1_L_Pos) + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) +} + +// Configure configures an ADC pin on STM32F401. +// The F401 ADC SVD does not provide named Cycles84 constants per channel; +// the value 4 (binary 100) selects 84-cycle sample time in the 3-bit field. +func (a ADC) Configure(ADCConfig) { + a.Pin.ConfigureAltFunc(PinConfig{Mode: PinInputAnalog}, 0) + + const cycles84 = 4 + ch := a.getChannel() + if ch > 9 { + stm32.ADC1.SMPR1.SetBits(cycles84 << ((ch - 10) * 3)) + } else { + stm32.ADC1.SMPR2.SetBits(cycles84 << (ch * 3)) + } +} + +// Get returns the current value of a ADC pin in the range 0..0xffff. +func (a ADC) Get() uint16 { + ch := uint32(a.getChannel()) + stm32.ADC1.SQR3.SetBits(ch) + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_SWSTART) + for !stm32.ADC1.SR.HasBits(stm32.ADC_SR_EOC) { + } + result := uint16(stm32.ADC1.DR.Get()) << 4 + stm32.ADC1.SR.ClearBits(stm32.ADC_SR_EOC) + stm32.ADC1.SQR3.ClearBits(ch) + return result +} + +func (a ADC) getChannel() uint8 { + switch a.Pin { + case PA0: + return 0 + case PA1: + return 1 + case PA2: + return 2 + case PA3: + return 3 + case PA4: + return 4 + case PA5: + return 5 + case PA6: + return 6 + case PA7: + return 7 + case PB0: + return 8 + case PB1: + return 9 + case PC0: + return 10 + case PC1: + return 11 + case PC2: + return 12 + case PC3: + return 13 + case PC4: + return 14 + case PC5: + return 15 + } + return 0 +} diff --git a/src/machine/machine_stm32f401_periph.go b/src/machine/machine_stm32f401_periph.go new file mode 100644 index 0000000000..cd868e3a0a --- /dev/null +++ b/src/machine/machine_stm32f401_periph.go @@ -0,0 +1,144 @@ +//go:build stm32f401 + +package machine + +// STM32F401 peripheral implementations. The F401 has a reduced set of +// peripherals compared to F405/F407, so this file provides F401-specific +// versions of the functions in machine_stm32f4_extended.go. + +import ( + "device/stm32" + "runtime/interrupt" + "unsafe" +) + +func (p Pin) getPort() *stm32.GPIO_Type { + switch p / 16 { + case 0: + return stm32.GPIOA + case 1: + return stm32.GPIOB + case 2: + return stm32.GPIOC + case 3: + return stm32.GPIOD + case 4: + return stm32.GPIOE + case 7: + return stm32.GPIOH + default: + panic("machine: unknown port") + } +} + +// Enable peripheral clock for STM32F401 peripherals. +func enableAltFuncClock(bus unsafe.Pointer) { + switch bus { + case unsafe.Pointer(stm32.PWR): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) + case unsafe.Pointer(stm32.I2C3): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C3EN) + case unsafe.Pointer(stm32.I2C2): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C2EN) + case unsafe.Pointer(stm32.I2C1): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) + case unsafe.Pointer(stm32.USART2): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) + case unsafe.Pointer(stm32.SPI3): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI3EN) + case unsafe.Pointer(stm32.SPI2): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI2EN) + case unsafe.Pointer(stm32.WWDG): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_WWDGEN) + case unsafe.Pointer(stm32.TIM5): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM5EN) + case unsafe.Pointer(stm32.TIM4): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM4EN) + case unsafe.Pointer(stm32.TIM3): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) + case unsafe.Pointer(stm32.TIM2): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM2EN) + case unsafe.Pointer(stm32.TIM11): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM11EN) + case unsafe.Pointer(stm32.TIM10): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM10EN) + case unsafe.Pointer(stm32.TIM9): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM9EN) + case unsafe.Pointer(stm32.SYSCFG): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) + case unsafe.Pointer(stm32.SPI4): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI4EN) + case unsafe.Pointer(stm32.SPI1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.SDIO): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SDIOEN) + case unsafe.Pointer(stm32.ADC1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC1EN) + case unsafe.Pointer(stm32.USART6): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART6EN) + case unsafe.Pointer(stm32.USART1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) + case unsafe.Pointer(stm32.TIM1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM1EN) + } +} + +func (t *TIM) registerUPInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM1.handleUPInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) + case &TIM4: + return interrupt.New(stm32.IRQ_TIM4, TIM4.handleUPInterrupt) + case &TIM5: + return interrupt.New(stm32.IRQ_TIM5, TIM5.handleUPInterrupt) + case &TIM9: + return interrupt.New(stm32.IRQ_TIM1_BRK_TIM9, TIM9.handleUPInterrupt) + case &TIM10: + return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM10.handleUPInterrupt) + case &TIM11: + return interrupt.New(stm32.IRQ_TIM1_TRG_COM_TIM11, TIM11.handleUPInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) registerOCInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleOCInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) + case &TIM4: + return interrupt.New(stm32.IRQ_TIM4, TIM4.handleOCInterrupt) + case &TIM5: + return interrupt.New(stm32.IRQ_TIM5, TIM5.handleOCInterrupt) + case &TIM9: + return interrupt.New(stm32.IRQ_TIM1_BRK_TIM9, TIM9.handleOCInterrupt) + case &TIM10: + return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM10.handleOCInterrupt) + case &TIM11: + return interrupt.New(stm32.IRQ_TIM1_TRG_COM_TIM11, TIM11.handleOCInterrupt) + } + + return interrupt.Interrupt{} +} + +// initRNG is a no-op on STM32F401 which has no hardware RNG peripheral. +func initRNG() {} + +func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { + var clock uint32 + switch uart.Bus { + case stm32.USART1, stm32.USART6: + clock = CPUFrequency() // APB2 = HCLK (no prescaler) + case stm32.USART2: + clock = CPUFrequency() / 2 // APB1 = HCLK/2 + } + return clock / baudRate +} diff --git a/src/machine/machine_stm32f4_extended.go b/src/machine/machine_stm32f4_extended.go new file mode 100644 index 0000000000..4e7210da9d --- /dev/null +++ b/src/machine/machine_stm32f4_extended.go @@ -0,0 +1,286 @@ +//go:build stm32f4 && !stm32f401 + +package machine + +// This file contains stm32f4 peripheral implementations for chips that have the +// full set of F4 peripherals (F405, F407, F469, etc.). Chips with fewer +// peripherals (e.g. F401) use machine_stm32f401_periph.go instead. + +import ( + "device/stm32" + "runtime/interrupt" + "unsafe" +) + +func (p Pin) getPort() *stm32.GPIO_Type { + switch p / 16 { + case 0: + return stm32.GPIOA + case 1: + return stm32.GPIOB + case 2: + return stm32.GPIOC + case 3: + return stm32.GPIOD + case 4: + return stm32.GPIOE + case 5: + return stm32.GPIOF + case 6: + return stm32.GPIOG + case 7: + return stm32.GPIOH + case 8: + return stm32.GPIOI + case 9: + return stm32.GPIOJ + case 10: + return stm32.GPIOK + default: + panic("machine: unknown port") + } +} + +// Enable peripheral clock +func enableAltFuncClock(bus unsafe.Pointer) { + switch bus { + case unsafe.Pointer(stm32.DAC): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_DACEN) + case unsafe.Pointer(stm32.PWR): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) + case unsafe.Pointer(stm32.CAN2): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_CAN2EN) + case unsafe.Pointer(stm32.CAN1): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_CAN1EN) + case unsafe.Pointer(stm32.I2C3): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C3EN) + case unsafe.Pointer(stm32.I2C2): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C2EN) + case unsafe.Pointer(stm32.I2C1): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) + case unsafe.Pointer(stm32.UART5): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_UART5EN) + case unsafe.Pointer(stm32.UART4): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_UART4EN) + case unsafe.Pointer(stm32.USART3): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART3EN) + case unsafe.Pointer(stm32.USART2): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) + case unsafe.Pointer(stm32.SPI3): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI3EN) + case unsafe.Pointer(stm32.SPI2): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI2EN) + case unsafe.Pointer(stm32.WWDG): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_WWDGEN) + case unsafe.Pointer(stm32.TIM14): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM14EN) + case unsafe.Pointer(stm32.TIM13): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM13EN) + case unsafe.Pointer(stm32.TIM12): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM12EN) + case unsafe.Pointer(stm32.TIM7): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM7EN) + case unsafe.Pointer(stm32.TIM6): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM6EN) + case unsafe.Pointer(stm32.TIM5): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM5EN) + case unsafe.Pointer(stm32.TIM4): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM4EN) + case unsafe.Pointer(stm32.TIM3): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) + case unsafe.Pointer(stm32.TIM2): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM2EN) + case unsafe.Pointer(stm32.TIM11): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM11EN) + case unsafe.Pointer(stm32.TIM10): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM10EN) + case unsafe.Pointer(stm32.TIM9): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM9EN) + case unsafe.Pointer(stm32.SYSCFG): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) + case unsafe.Pointer(stm32.SPI1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.SDIO): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SDIOEN) + case unsafe.Pointer(stm32.ADC3): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC3EN) + case unsafe.Pointer(stm32.ADC2): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC2EN) + case unsafe.Pointer(stm32.ADC1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC1EN) + case unsafe.Pointer(stm32.USART6): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART6EN) + case unsafe.Pointer(stm32.USART1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) + case unsafe.Pointer(stm32.TIM8): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM8EN) + case unsafe.Pointer(stm32.TIM1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM1EN) + } +} + +var ( + TIM6 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM6EN, + Device: stm32.TIM6, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM7 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM7EN, + Device: stm32.TIM7, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM8 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM8EN, + Device: stm32.TIM8, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PC6, AF3_TIM8_9_10_11}, {PI5, AF3_TIM8_9_10_11}}}, + TimerChannel{Pins: []PinFunction{{PC7, AF3_TIM8_9_10_11}, {PI6, AF3_TIM8_9_10_11}}}, + TimerChannel{Pins: []PinFunction{{PC8, AF3_TIM8_9_10_11}, {PI7, AF3_TIM8_9_10_11}}}, + TimerChannel{Pins: []PinFunction{{PC9, AF3_TIM8_9_10_11}, {PI2, AF3_TIM8_9_10_11}}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM12 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM12EN, + Device: stm32.TIM12, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PB14, AF9_CAN1_CAN2_TIM12_13_14}, {PH6, AF9_CAN1_CAN2_TIM12_13_14}}}, + TimerChannel{Pins: []PinFunction{{PB15, AF9_CAN1_CAN2_TIM12_13_14}, {PH9, AF9_CAN1_CAN2_TIM12_13_14}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM13 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM13EN, + Device: stm32.TIM13, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA6, AF9_CAN1_CAN2_TIM12_13_14}, {PF8, AF9_CAN1_CAN2_TIM12_13_14}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM14 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM14EN, + Device: stm32.TIM14, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA7, AF9_CAN1_CAN2_TIM12_13_14}, {PF9, AF9_CAN1_CAN2_TIM12_13_14}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } +) + +func (t *TIM) registerUPInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM1.handleUPInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) + case &TIM4: + return interrupt.New(stm32.IRQ_TIM4, TIM4.handleUPInterrupt) + case &TIM5: + return interrupt.New(stm32.IRQ_TIM5, TIM5.handleUPInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleUPInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) + case &TIM8: + return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM8.handleUPInterrupt) + case &TIM9: + return interrupt.New(stm32.IRQ_TIM1_BRK_TIM9, TIM9.handleUPInterrupt) + case &TIM10: + return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM10.handleUPInterrupt) + case &TIM11: + return interrupt.New(stm32.IRQ_TIM1_TRG_COM_TIM11, TIM11.handleUPInterrupt) + case &TIM12: + return interrupt.New(stm32.IRQ_TIM8_BRK_TIM12, TIM12.handleUPInterrupt) + case &TIM13: + return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM13.handleUPInterrupt) + case &TIM14: + return interrupt.New(stm32.IRQ_TIM8_TRG_COM_TIM14, TIM14.handleUPInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) registerOCInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleOCInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) + case &TIM4: + return interrupt.New(stm32.IRQ_TIM4, TIM4.handleOCInterrupt) + case &TIM5: + return interrupt.New(stm32.IRQ_TIM5, TIM5.handleOCInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleOCInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) + case &TIM8: + return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM8.handleOCInterrupt) + case &TIM9: + return interrupt.New(stm32.IRQ_TIM1_BRK_TIM9, TIM9.handleOCInterrupt) + case &TIM10: + return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM10.handleOCInterrupt) + case &TIM11: + return interrupt.New(stm32.IRQ_TIM1_TRG_COM_TIM11, TIM11.handleOCInterrupt) + case &TIM12: + return interrupt.New(stm32.IRQ_TIM8_BRK_TIM12, TIM12.handleOCInterrupt) + case &TIM13: + return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM13.handleOCInterrupt) + case &TIM14: + return interrupt.New(stm32.IRQ_TIM8_TRG_COM_TIM14, TIM14.handleOCInterrupt) + } + + return interrupt.Interrupt{} +} + +func initRNG() { + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_RNGEN) + stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN) +} + +func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { + var clock uint32 + switch uart.Bus { + case stm32.USART1, stm32.USART6: + clock = CPUFrequency() / 2 // APB2 Frequency + case stm32.USART2, stm32.USART3, stm32.UART4, stm32.UART5: + clock = CPUFrequency() / 4 // APB1 Frequency + } + return clock / baudRate +} diff --git a/src/machine/machine_stm32f4_otgfs_vbus.go b/src/machine/machine_stm32f4_otgfs_vbus.go index e9830d9969..7a2fbb5939 100644 --- a/src/machine/machine_stm32f4_otgfs_vbus.go +++ b/src/machine/machine_stm32f4_otgfs_vbus.go @@ -1,4 +1,4 @@ -//go:build stm32f4 && (stm32f429 || stm32f427 || stm32f411 || stm32f407 || stm32f405 || stm32f401) +//go:build stm32f4 && (stm32f429 || stm32f427 || stm32f411 || stm32f407 || stm32f405) package machine diff --git a/src/machine/machine_stm32f4_pll_84mhz.go b/src/machine/machine_stm32f4_pll_84mhz.go new file mode 100644 index 0000000000..5936e2645b --- /dev/null +++ b/src/machine/machine_stm32f4_pll_84mhz.go @@ -0,0 +1,19 @@ +//go:build stm32f4 && stm32f401 + +package machine + +// PLLParams84MHz returns the HSE PLL dividers needed to reach a 336MHz VCO +// (84MHz SYSCLK, P=4, Q=7) for the configured crystal frequency. M is chosen +// to bring the PLL input (HSE/M) to 2MHz, as recommended by the STM32F401 +// reference manual (RM0368). With VCO=336MHz and P=4, SYSCLK=84MHz. Q=7 +// produces the 48MHz USB clock (VCO/Q). +func PLLParams84MHz() PLLParams { + switch xtalHz { + case 8_000_000: + return PLLParams{M: 4, N: 168, P: 4, Q: 7} + case 16_000_000: + return PLLParams{M: 8, N: 168, P: 4, Q: 7} + default: + panic("unsupported xtal frequency") + } +} diff --git a/src/machine/usb.go b/src/machine/usb.go index 9fcc1997d7..ed27258ab8 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -1,4 +1,4 @@ -//go:build sam || nrf52840 || rp2040 || rp2350 || stm32f4 || stm32f7 || stm32h7 +//go:build sam || nrf52840 || rp2040 || rp2350 || (stm32f4 && !stm32f401) || stm32f7 || stm32h7 package machine diff --git a/src/runtime/rand_hwrng.go b/src/runtime/rand_hwrng.go index bc7b72d6c0..8a59e732c2 100644 --- a/src/runtime/rand_hwrng.go +++ b/src/runtime/rand_hwrng.go @@ -1,4 +1,4 @@ -//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0 || stm32u0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350) +//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0 || stm32u0 || stm32f401)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350) // If you update the above build constraint, you'll probably also need to update // src/crypto/rand/rand_baremetal.go. diff --git a/src/runtime/rand_norng.go b/src/runtime/rand_norng.go index ee77df09fa..46de54f91a 100644 --- a/src/runtime/rand_norng.go +++ b/src/runtime/rand_norng.go @@ -1,4 +1,4 @@ -//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0 || stm32u0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350) +//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0 || stm32u0 || stm32f401)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350) package runtime diff --git a/src/runtime/runtime_stm32f401.go b/src/runtime/runtime_stm32f401.go new file mode 100644 index 0000000000..9ca4ef5d1f --- /dev/null +++ b/src/runtime/runtime_stm32f401.go @@ -0,0 +1,126 @@ +//go:build stm32f401 + +package runtime + +import ( + "device/stm32" + "machine" +) + +const ( + // +---------------------------------------------+ + // | Clock Settings | + // +-------------+-------------------------------+ + // | HSE | selectable (xtal_8/16_mhz) | + // | SYSCLK | 84mhz | + // | HCLK | 84mhz | + // | APB1(PCLK1) | 42mhz | + // | APB2(PCLK2) | 84mhz | + // +-------------+-------------------------------+ + HCLK_FREQ_HZ = 84000000 + PCLK1_FREQ_HZ = HCLK_FREQ_HZ / 2 + PCLK2_FREQ_HZ = HCLK_FREQ_HZ / 1 +) + +const ( + // VOS Scale 1 (bits [15:14] = 0b11): max HCLK = 84 MHz + PWR_SCALE1 = 3 << stm32.PWR_CR_VOS_Pos + + PLL_SRC_HSE = 1 << stm32.RCC_PLLCFGR_PLLSRC_Pos + PLL_SRC_HSI = 0 + + 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 + + RCC_DIV_PCLK1 = stm32.RCC_CFGR_PPRE1_Div2 << stm32.RCC_CFGR_PPRE1_Pos // HCLK / 2 + RCC_DIV_PCLK2 = stm32.RCC_CFGR_PPRE2_Div1 << stm32.RCC_CFGR_PPRE2_Pos // HCLK / 1 + RCC_DIV_HCLK = stm32.RCC_CFGR_HPRE_Div1 << stm32.RCC_CFGR_HPRE_Pos // SYSCLK / 1 +) + +const ( + // +-----------------------------------+ + // | Voltage range = 2.7V - 3.6V | + // +----------------+------------------+ + // | Wait states | System Bus | + // | (WS, LATENCY) | HCLK (MHz) | + // +----------------+------------------+ + // | 0 WS, 1 cycle | 0 < HCLK ≤ 30 | + // | 1 WS, 2 cycles | 30 < HCLK ≤ 64 | + // | 2 WS, 3 cycles | 64 < HCLK ≤ 84 | + // +----------------+------------------+ + FLASH_LATENCY = 2 << stm32.FLASH_ACR_LATENCY_Pos // 2 WS (3 CPU cycles) + + // instruction cache, data cache, and prefetch + FLASH_OPTIONS = stm32.FLASH_ACR_ICEN | stm32.FLASH_ACR_DCEN | stm32.FLASH_ACR_PRFTEN +) + +func init() { + initOSC() + initCLK() + + machine.InitSerial() + + initTickTimer(&machine.TIM3) +} + +func initOSC() { + // enable voltage regulator + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) + stm32.PWR.CR.SetBits(PWR_SCALE1) + + // enable HSE + stm32.RCC.CR.Set(stm32.RCC_CR_HSEON) + for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSERDY) { + } + + // disable PLL before configuring it + stm32.RCC.CR.ClearBits(stm32.RCC_CR_PLLON) + for stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { + } + + // set HSE as PLL source and configure dividers for 84MHz SYSCLK + pll := machine.PLLParams84MHz() + stm32.RCC.PLLCFGR.Set(PLL_SRC_HSE | + pll.M<>1)-1)< Date: Sun, 30 Aug 2026 13:40:02 +0900 Subject: [PATCH 2/4] fix(stm32f401): address review comments on NUCLEO-F401RE support Required: - Add APB1_FREQ/APB2_FREQ and use them for SPI, I2C, and UART clock divisors - Move getBaudRateDivisor into the shared F4 file - Fix F4 ADC sample-time shifts and drop machine_stm32f401_adc.go - Add ADC0-ADC15 aliases and examples/pwm/nucleo-f401re.go Optional: - Use a bare stm32f401 build tag on machine_stm32f401.go - Switch the full-peripheral F4 file to a positive F405/F407/F469 tag - Rename machine_stm32f4_extended.go to machine_stm32f40x_periph.go - Enable HSE with SetBits and set APB prescalers before switching to PLL - Reindent targets/nucleo-f401re.json to 2 spaces --- src/examples/pwm/nucleo-f401re.go | 13 +++ src/machine/board_nucleof401re.go | 20 +++++ src/machine/machine_stm32_adc_f4.go | 11 ++- src/machine/machine_stm32f4.go | 20 ++++- src/machine/machine_stm32f401.go | 10 ++- src/machine/machine_stm32f401_adc.go | 87 ------------------- src/machine/machine_stm32f401_periph.go | 13 +-- src/machine/machine_stm32f40x.go | 12 +-- ...xtended.go => machine_stm32f40x_periph.go} | 17 +--- src/machine/machine_stm32f469.go | 12 +-- src/runtime/runtime_stm32f401.go | 9 +- targets/nucleo-f401re.json | 22 ++--- 12 files changed, 96 insertions(+), 150 deletions(-) create mode 100644 src/examples/pwm/nucleo-f401re.go delete mode 100644 src/machine/machine_stm32f401_adc.go rename src/machine/{machine_stm32f4_extended.go => machine_stm32f40x_periph.go} (94%) diff --git a/src/examples/pwm/nucleo-f401re.go b/src/examples/pwm/nucleo-f401re.go new file mode 100644 index 0000000000..821dec297f --- /dev/null +++ b/src/examples/pwm/nucleo-f401re.go @@ -0,0 +1,13 @@ +//go:build nucleof401re + +package main + +import "machine" + +// TIM3 is reserved by the runtime tick timer on STM32F401. +// Use TIM2 instead: PB10=D6 (TIM2_CH3/AF1), PB3=D3 (TIM2_CH2/AF1). +var ( + pwm = &machine.TIM2 + pinA = machine.D6 + pinB = machine.D3 +) diff --git a/src/machine/board_nucleof401re.go b/src/machine/board_nucleof401re.go index 28d26f87fa..35a2b62cf9 100644 --- a/src/machine/board_nucleof401re.go +++ b/src/machine/board_nucleof401re.go @@ -39,6 +39,26 @@ const ( D15 = PB8 ) +// Analog pins (ADC1 channels). +const ( + ADC0 = PA0 + ADC1 = PA1 + ADC2 = PA2 + ADC3 = PA3 + ADC4 = PA4 + ADC5 = PA5 + ADC6 = PA6 + ADC7 = PA7 + ADC8 = PB0 + ADC9 = PB1 + ADC10 = PC0 + ADC11 = PC1 + ADC12 = PC2 + ADC13 = PC3 + ADC14 = PC4 + ADC15 = PC5 +) + // User LD2: the green LED is a user LED connected to Arduino signal D13 // corresponding to STM32 I/O PA5. const ( diff --git a/src/machine/machine_stm32_adc_f4.go b/src/machine/machine_stm32_adc_f4.go index 92bc1c7a8a..f47d48978c 100644 --- a/src/machine/machine_stm32_adc_f4.go +++ b/src/machine/machine_stm32_adc_f4.go @@ -1,4 +1,4 @@ -//go:build stm32f4 && !stm32f401 +//go:build stm32f4 package machine @@ -37,12 +37,15 @@ func InitADC() { func (a ADC) Configure(ADCConfig) { a.Pin.ConfigureAltFunc(PinConfig{Mode: PinInputAnalog}, 0) - // set sample time + // set sample time (84 ADC cycles). Each SMPx field is 3 bits wide; the + // encoding for 84 cycles is 4. Named Cycles84 constants are not present + // in every F4 SVD, so use the literal values. + const cycles84 = 4 ch := a.getChannel() if ch > 9 { - stm32.ADC1.SMPR1.SetBits(stm32.ADC_SMPR1_SMP11_Cycles84 << (ch - 10) * stm32.ADC_SMPR1_SMP11_Pos) + stm32.ADC1.SMPR1.SetBits(cycles84 << ((ch - 10) * 3)) } else { - stm32.ADC1.SMPR2.SetBits(stm32.ADC_SMPR2_SMP1_Cycles84 << (ch * stm32.ADC_SMPR2_SMP1_Pos)) + stm32.ADC1.SMPR2.SetBits(cycles84 << (ch * 3)) } return diff --git a/src/machine/machine_stm32f4.go b/src/machine/machine_stm32f4.go index 2ab4ae5ded..ac3b966012 100644 --- a/src/machine/machine_stm32f4.go +++ b/src/machine/machine_stm32f4.go @@ -395,6 +395,18 @@ func (uart *UART) setRegisters() { uart.txEmptyFlag = stm32.USART_SR_TXE } +func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { + var clock uint32 + switch uart.Bus { + case stm32.USART1, stm32.USART6: + clock = APB2_FREQ + default: + // USART2 and (when present) USART3/UART4/UART5 are on APB1. + clock = APB1_FREQ + } + return clock / baudRate +} + // -- SPI ---------------------------------------------------------------------- type SPI struct { @@ -416,9 +428,9 @@ func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var clock uint32 switch spi.Bus { case stm32.SPI1: - clock = CPUFrequency() / 2 + clock = APB2_FREQ case stm32.SPI2, stm32.SPI3: - clock = CPUFrequency() / 4 + clock = APB1_FREQ } // limit requested frequency to bus frequency and min frequency (DIV256) @@ -462,7 +474,7 @@ func (i2c *I2C) configurePins(config I2CConfig) { func (i2c *I2C) getFreqRange(config I2CConfig) uint32 { // all I2C interfaces are on APB1 - clock := CPUFrequency() / 4 + clock := APB1_FREQ // convert to MHz clock /= 1000000 // must be between 2 MHz (or 4 MHz for fast mode (Fm)) and 50 MHz, inclusive @@ -513,7 +525,7 @@ func (i2c *I2C) getSpeed(config I2CConfig) uint32 { } } // all I2C interfaces are on APB1 - clock := CPUFrequency() / 4 + clock := APB1_FREQ if config.Frequency <= 100000 { return sm(clock, config.Frequency) } else { diff --git a/src/machine/machine_stm32f401.go b/src/machine/machine_stm32f401.go index 0d2a0fdb2e..7e73c4dbdd 100644 --- a/src/machine/machine_stm32f401.go +++ b/src/machine/machine_stm32f401.go @@ -1,4 +1,4 @@ -//go:build stm32f4 && stm32f401 +//go:build stm32f401 package machine @@ -9,9 +9,11 @@ func CPUFrequency() uint32 { return xtalHz / pll.M * pll.N / pll.P } -// Internal use: configured speed of the APB1 and APB2 timers. +// Internal use: configured speed of the APB1 and APB2 buses and timers. // STM32F401 at 84MHz: SYSCLK=84MHz, AHB=84MHz, APB1=42MHz, APB2=84MHz. // APB1 prescaler = 2, so APB1 timer clock = 42MHz × 2 = 84MHz. // APB2 prescaler = 1, so APB2 timer clock = 84MHz × 1 = 84MHz. -const APB1_TIM_FREQ = 84_000_000 -const APB2_TIM_FREQ = 84_000_000 +const APB1_FREQ = 42_000_000 +const APB2_FREQ = 84_000_000 +const APB1_TIM_FREQ = APB1_FREQ * 2 +const APB2_TIM_FREQ = APB2_FREQ diff --git a/src/machine/machine_stm32f401_adc.go b/src/machine/machine_stm32f401_adc.go deleted file mode 100644 index b77bcd76ba..0000000000 --- a/src/machine/machine_stm32f401_adc.go +++ /dev/null @@ -1,87 +0,0 @@ -//go:build stm32f401 - -package machine - -import ( - "device/stm32" - "unsafe" -) - -// InitADC initializes the registers needed for ADC1 on STM32F401. -func InitADC() { - enableAltFuncClock(unsafe.Pointer(stm32.ADC1)) - - stm32.ADC1.CR1.ClearBits(stm32.ADC_CR1_SCAN | stm32.ADC_CR1_RES_Msk) - stm32.ADC1.CR1.SetBits(stm32.ADC_CR1_RES_TwelveBit) - stm32.ADC1.CR2.ClearBits(stm32.ADC_CR2_CONT | stm32.ADC_CR2_ALIGN | stm32.ADC_CR2_EXTEN_Msk | stm32.ADC_CR2_EXTSEL_Msk) - stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_CONT_Single | stm32.ADC_CR2_ALIGN_Right) - stm32.ADC1.SQR1.ClearBits(stm32.ADC_SQR1_L_Msk) - stm32.ADC1.SQR1.SetBits(2 << stm32.ADC_SQR1_L_Pos) - stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) -} - -// Configure configures an ADC pin on STM32F401. -// The F401 ADC SVD does not provide named Cycles84 constants per channel; -// the value 4 (binary 100) selects 84-cycle sample time in the 3-bit field. -func (a ADC) Configure(ADCConfig) { - a.Pin.ConfigureAltFunc(PinConfig{Mode: PinInputAnalog}, 0) - - const cycles84 = 4 - ch := a.getChannel() - if ch > 9 { - stm32.ADC1.SMPR1.SetBits(cycles84 << ((ch - 10) * 3)) - } else { - stm32.ADC1.SMPR2.SetBits(cycles84 << (ch * 3)) - } -} - -// Get returns the current value of a ADC pin in the range 0..0xffff. -func (a ADC) Get() uint16 { - ch := uint32(a.getChannel()) - stm32.ADC1.SQR3.SetBits(ch) - stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_SWSTART) - for !stm32.ADC1.SR.HasBits(stm32.ADC_SR_EOC) { - } - result := uint16(stm32.ADC1.DR.Get()) << 4 - stm32.ADC1.SR.ClearBits(stm32.ADC_SR_EOC) - stm32.ADC1.SQR3.ClearBits(ch) - return result -} - -func (a ADC) getChannel() uint8 { - switch a.Pin { - case PA0: - return 0 - case PA1: - return 1 - case PA2: - return 2 - case PA3: - return 3 - case PA4: - return 4 - case PA5: - return 5 - case PA6: - return 6 - case PA7: - return 7 - case PB0: - return 8 - case PB1: - return 9 - case PC0: - return 10 - case PC1: - return 11 - case PC2: - return 12 - case PC3: - return 13 - case PC4: - return 14 - case PC5: - return 15 - } - return 0 -} diff --git a/src/machine/machine_stm32f401_periph.go b/src/machine/machine_stm32f401_periph.go index cd868e3a0a..b98249e276 100644 --- a/src/machine/machine_stm32f401_periph.go +++ b/src/machine/machine_stm32f401_periph.go @@ -4,7 +4,7 @@ package machine // STM32F401 peripheral implementations. The F401 has a reduced set of // peripherals compared to F405/F407, so this file provides F401-specific -// versions of the functions in machine_stm32f4_extended.go. +// versions of the functions in machine_stm32f40x_periph.go. import ( "device/stm32" @@ -131,14 +131,3 @@ func (t *TIM) registerOCInterrupt() interrupt.Interrupt { // initRNG is a no-op on STM32F401 which has no hardware RNG peripheral. func initRNG() {} - -func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { - var clock uint32 - switch uart.Bus { - case stm32.USART1, stm32.USART6: - clock = CPUFrequency() // APB2 = HCLK (no prescaler) - case stm32.USART2: - clock = CPUFrequency() / 2 // APB1 = HCLK/2 - } - return clock / baudRate -} diff --git a/src/machine/machine_stm32f40x.go b/src/machine/machine_stm32f40x.go index 5fcbbcbe86..afbd835220 100644 --- a/src/machine/machine_stm32f40x.go +++ b/src/machine/machine_stm32f40x.go @@ -7,8 +7,10 @@ func CPUFrequency() uint32 { return xtalHz / pll.M * pll.N / pll.P } -// Internal use: configured speed of the APB1 and APB2 timers, this should be kept -// in sync with any changes to runtime package which configures the oscillators -// and clock frequencies -const APB1_TIM_FREQ = 42000000 * 2 -const APB2_TIM_FREQ = 84000000 * 2 +// Internal use: configured speed of the APB1 and APB2 buses and timers, this +// should be kept in sync with any changes to runtime package which configures +// the oscillators and clock frequencies. +const APB1_FREQ = 42000000 +const APB2_FREQ = 84000000 +const APB1_TIM_FREQ = APB1_FREQ * 2 +const APB2_TIM_FREQ = APB2_FREQ * 2 diff --git a/src/machine/machine_stm32f4_extended.go b/src/machine/machine_stm32f40x_periph.go similarity index 94% rename from src/machine/machine_stm32f4_extended.go rename to src/machine/machine_stm32f40x_periph.go index 4e7210da9d..09dbb93f7c 100644 --- a/src/machine/machine_stm32f4_extended.go +++ b/src/machine/machine_stm32f40x_periph.go @@ -1,10 +1,10 @@ -//go:build stm32f4 && !stm32f401 +//go:build stm32f4 && (stm32f405 || stm32f407 || stm32f469) package machine // This file contains stm32f4 peripheral implementations for chips that have the -// full set of F4 peripherals (F405, F407, F469, etc.). Chips with fewer -// peripherals (e.g. F401) use machine_stm32f401_periph.go instead. +// full set of F4 peripherals (F405, F407, F469). Chips with fewer peripherals +// (e.g. F401) use machine_stm32f401_periph.go instead. import ( "device/stm32" @@ -273,14 +273,3 @@ func initRNG() { stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_RNGEN) stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN) } - -func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { - var clock uint32 - switch uart.Bus { - case stm32.USART1, stm32.USART6: - clock = CPUFrequency() / 2 // APB2 Frequency - case stm32.USART2, stm32.USART3, stm32.UART4, stm32.UART5: - clock = CPUFrequency() / 4 // APB1 Frequency - } - return clock / baudRate -} diff --git a/src/machine/machine_stm32f469.go b/src/machine/machine_stm32f469.go index 9d0c7e473a..ef41399d7f 100644 --- a/src/machine/machine_stm32f469.go +++ b/src/machine/machine_stm32f469.go @@ -7,8 +7,10 @@ func CPUFrequency() uint32 { return xtalHz / pll.M * pll.N / pll.P } -// Internal use: configured speed of the APB1 and APB2 timers, this should be kept -// in sync with any changes to runtime package which configures the oscillators -// and clock frequencies -const APB1_TIM_FREQ = 45000000 * 2 -const APB2_TIM_FREQ = 90000000 * 2 +// Internal use: configured speed of the APB1 and APB2 buses and timers, this +// should be kept in sync with any changes to runtime package which configures +// the oscillators and clock frequencies. +const APB1_FREQ = 45000000 +const APB2_FREQ = 90000000 +const APB1_TIM_FREQ = APB1_FREQ * 2 +const APB2_TIM_FREQ = APB2_FREQ * 2 diff --git a/src/runtime/runtime_stm32f401.go b/src/runtime/runtime_stm32f401.go index 9ca4ef5d1f..cacf9b592c 100644 --- a/src/runtime/runtime_stm32f401.go +++ b/src/runtime/runtime_stm32f401.go @@ -69,7 +69,7 @@ func initOSC() { stm32.PWR.CR.SetBits(PWR_SCALE1) // enable HSE - stm32.RCC.CR.Set(stm32.RCC_CR_HSEON) + stm32.RCC.CR.SetBits(stm32.RCC_CR_HSEON) for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSERDY) { } @@ -98,12 +98,13 @@ func initCLK() { for !stm32.FLASH.ACR.HasBits(FLASH_LATENCY) { } + // Set bus prescalers before switching SYSCLK to PLL so APB1 never + // exceeds its 42 MHz maximum. + stm32.RCC.CFGR.SetBits(RCC_DIV_PCLK1 | RCC_DIV_PCLK2 | RCC_DIV_HCLK) + // set CPU clock source to PLL stm32.RCC.CFGR.SetBits(SYSCLK_SRC_PLL) - // update PCLK1/2 and HCLK divisors - stm32.RCC.CFGR.SetBits(RCC_DIV_PCLK1 | RCC_DIV_PCLK2 | RCC_DIV_HCLK) - // verify system clock source is ready for !stm32.RCC.CFGR.HasBits(SYSCLK_STAT_PLL) { } diff --git a/targets/nucleo-f401re.json b/targets/nucleo-f401re.json index fb07b75843..c0459d3bd2 100644 --- a/targets/nucleo-f401re.json +++ b/targets/nucleo-f401re.json @@ -1,13 +1,13 @@ { - "inherits": ["cortex-m4"], - "build-tags": ["nucleof401re", "stm32f401", "stm32f4", "stm32"], - "serial": "uart", - "linkerscript": "targets/stm32f401.ld", - "extra-files": [ - "src/device/stm32/stm32f401.s" - ], - "flash-method": "openocd", - "openocd-interface": "stlink", - "openocd-target": "stm32f4x", - "openocd-commands": ["reset_config srst_only connect_assert_srst"] + "inherits": ["cortex-m4"], + "build-tags": ["nucleof401re", "stm32f401", "stm32f4", "stm32"], + "serial": "uart", + "linkerscript": "targets/stm32f401.ld", + "extra-files": [ + "src/device/stm32/stm32f401.s" + ], + "flash-method": "openocd", + "openocd-interface": "stlink", + "openocd-target": "stm32f4x", + "openocd-commands": ["reset_config srst_only connect_assert_srst"] } From a70c225b24b7a330d4bd1b7e1ba52c790b9de150 Mon Sep 17 00:00:00 2001 From: akif999 Date: Sun, 30 Aug 2026 15:30:44 +0900 Subject: [PATCH 3/4] fix(stm32f4): keep I2C clock as uint32 when using APB1_FREQ Untyped APB1_FREQ made clock := APB1_FREQ infer int and broke the F4 smoketest. --- src/machine/machine_stm32f4.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/machine/machine_stm32f4.go b/src/machine/machine_stm32f4.go index ac3b966012..e7ab21298f 100644 --- a/src/machine/machine_stm32f4.go +++ b/src/machine/machine_stm32f4.go @@ -474,7 +474,7 @@ func (i2c *I2C) configurePins(config I2CConfig) { func (i2c *I2C) getFreqRange(config I2CConfig) uint32 { // all I2C interfaces are on APB1 - clock := APB1_FREQ + var clock uint32 = APB1_FREQ // convert to MHz clock /= 1000000 // must be between 2 MHz (or 4 MHz for fast mode (Fm)) and 50 MHz, inclusive @@ -525,7 +525,7 @@ func (i2c *I2C) getSpeed(config I2CConfig) uint32 { } } // all I2C interfaces are on APB1 - clock := APB1_FREQ + var clock uint32 = APB1_FREQ if config.Frequency <= 100000 { return sm(clock, config.Frequency) } else { From 52f09b7ace5a8603b5ce6b938a7c4fa2d2e41a43 Mon Sep 17 00:00:00 2001 From: akif999 Date: Sun, 6 Sep 2026 00:37:50 +0900 Subject: [PATCH 4/4] fix(stm32f401): address follow-up review on RNG, USB, and examples Remove unused initRNG, document why USB is excluded chip-wide, note that ADC2 is VCP TX, and add nucleo-f401re PWM to the smoketest. --- make/smoketest.mk | 2 ++ src/machine/board_nucleof401re.go | 1 + src/machine/machine_stm32_otgfs_usb.go | 2 ++ src/machine/machine_stm32f401_periph.go | 3 --- src/machine/usb.go | 2 ++ 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/make/smoketest.mk b/make/smoketest.mk index 94c8b97c77..7ef6edf170 100644 --- a/make/smoketest.mk +++ b/make/smoketest.mk @@ -386,6 +386,8 @@ ifneq ($(STM32), 0) @$(MD5SUM) $(SMOKE_OUT).hex $(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=stm32f4disco-1 examples/pwm @$(MD5SUM) $(SMOKE_OUT).hex + $(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-f401re examples/pwm + @$(MD5SUM) $(SMOKE_OUT).hex $(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=stm32f469disco examples/blinky1 @$(MD5SUM) $(SMOKE_OUT).hex $(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=lorae5 examples/blinky1 diff --git a/src/machine/board_nucleof401re.go b/src/machine/board_nucleof401re.go index 35a2b62cf9..8b194c5f2e 100644 --- a/src/machine/board_nucleof401re.go +++ b/src/machine/board_nucleof401re.go @@ -40,6 +40,7 @@ const ( ) // Analog pins (ADC1 channels). +// examples/adc uses ADC2 (PA2). That pin is ST-Link VCP TX, so use A0 instead. const ( ADC0 = PA0 ADC1 = PA1 diff --git a/src/machine/machine_stm32_otgfs_usb.go b/src/machine/machine_stm32_otgfs_usb.go index 3505fa1ac7..0048838c08 100644 --- a/src/machine/machine_stm32_otgfs_usb.go +++ b/src/machine/machine_stm32_otgfs_usb.go @@ -1,3 +1,5 @@ +// NUCLEO-F401RE has no USB OTG connector. This chip-wide exclude +// also blocks later F401 boards that have USB (for example Blackpill F401CC). //go:build (stm32f4 && !stm32f401) || stm32f7 package machine diff --git a/src/machine/machine_stm32f401_periph.go b/src/machine/machine_stm32f401_periph.go index b98249e276..60f72e692a 100644 --- a/src/machine/machine_stm32f401_periph.go +++ b/src/machine/machine_stm32f401_periph.go @@ -128,6 +128,3 @@ func (t *TIM) registerOCInterrupt() interrupt.Interrupt { return interrupt.Interrupt{} } - -// initRNG is a no-op on STM32F401 which has no hardware RNG peripheral. -func initRNG() {} diff --git a/src/machine/usb.go b/src/machine/usb.go index ed27258ab8..ac28a57fce 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -1,3 +1,5 @@ +// NUCLEO-F401RE has no USB OTG connector. This chip-wide exclude +// also blocks later F401 boards that have USB (for example Blackpill F401CC). //go:build sam || nrf52840 || rp2040 || rp2350 || (stm32f4 && !stm32f401) || stm32f7 || stm32h7 package machine