Skip to content

feat(machine/stm32): add STM32F401 and NUCLEO-F401RE support - #5597

Open
akif999 wants to merge 1 commit into
tinygo-org:devfrom
akif999:nucleo-f401re
Open

feat(machine/stm32): add STM32F401 and NUCLEO-F401RE support#5597
akif999 wants to merge 1 commit into
tinygo-org:devfrom
akif999:nucleo-f401re

Conversation

@akif999

@akif999 akif999 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

1. Requirements

What was implemented

  • Added support for the STM32F401RE board.
  • Supported peripherals:
    • GPIO
    • UART
    • SPI
    • I2C
    • ADC
    • PWM
    • Arduino-compatible pin mapping (D0–D15, A0–A5)

What was NOT implemented in this PR

Item Reason
USB OTG FS Register naming in the generated stm32f401.go device file differs from the existing OTG FS driver (usb.go); excluded via !stm32f401 build tag, left for a separate PR
crypto/rand (HW RNG) STM32F401RE has no hardware RNG; crypto/rand panics with "no rng", consistent with TinyGo's behavior on other platforms without HW RNG; math/rand works normally
DAC, CAN, DCMI Not present on STM32F401 silicon
blinky2 NUCLEO-F401RE has only one user LED (LD2); not applicable

Board Information

Board

Field Details
Name NUCLEO-F401RE
Developer / Manufacturer STMicroelectronics
Schematic / User Manual UM1724: STM32 Nucleo-64 boards (MB1136)
URL https://www.st.com/en/evaluation-tools/nucleo-f401re.html

Chip

Field Details
Chip Name STM32F401RE
Architecture Arm Cortex-M4F
Clock / Flash / RAM 84 MHz / 512 KB / 96 KB
Datasheet STM32F401xD/xE Datasheet
Reference Manual RM0368

2. Design

Already had comprehensive support for the STM32F4 family (F405/F407 via feather-stm32f405, stm32f4disco; F722/F469 for other NUCLEO/disco boards). These provided:

  • A shared machine_stm32f4.go for GPIO, UART, SPI, I2C, ADC, and PWM drivers
  • machine_stm32f4_extended.go for F405/F407-specific peripherals (extra UARTs, TIMs, DAC, CAN, etc.)
  • Template clock initialization in runtime/runtime_stm32f4.go

The STM32F401 shares the same Cortex-M4 core and peripheral bus architecture as the F405/F407, but has a reduced peripheral set and a lower maximum clock speed (84 MHz vs. 168 MHz). The following approach was taken:

  1. New chip-level files
  • targets/stm32f401.ld — linker script (512 KB Flash, 96 KB SRAM; no CCM region)
  • src/machine/machine_stm32f401.go — CPU frequency (84 MHz), APB timer frequency constants
  • src/machine/machine_stm32f4_pll_84mhz.go — PLL parameters for 84 MHz from 8 MHz HSE (M=8, N=336, P=4, Q=7, Flash latency=2)
  • src/runtime/runtime_stm32f401.go — clock initialization (HSE → PLL → 84 MHz SYSCLK), TIM3 as the 1 kHz system tick timer (no CCM SRAM setup)
  • src/device/stm32/stm32f401.go / stm32f401.s — generated from lib/cmsis-svd/data/STMicro/STM32F401.svd
  1. Refactoring of machine_stm32f4.go

The shared machine_stm32f4.go contained getPort() (which referenced GPIO ports F–K, not present on F401) and enableAltFuncClock() (which referenced DAC, CAN1/2, TIM6/7/8/12/13/14, SDIO, USART3–6 — none of which exist on F401), as well as TIM6, TIM7, TIM8, TIM12, TIM13, TIM14 variable definitions. These were extracted out; the F405/F407 versions remain under machine_stm32f4_extended.go (guarded by !stm32f401), and F401-specific versions are provided in machine_stm32f401_periph.go.

  1. New F401-specific machine files
  • src/machine/machine_stm32f401_periph.go (new) — F401-specific implementations of:
    • getPort(): GPIO ports A–E only (F401 does not have ports F–K)
    • enableAltFuncClock(): only the peripheral clocks present on F401
    • TIM, UART, SPI, I2C peripheral variable definitions scoped to F401
  • src/machine/machine_stm32f401_adc.go (new) — F401-specific ADC driver. The generic machine_stm32_adc_f4.go references named SVD constants such as ADC_SMPR1_SMP11_Cycles84 which are absent from the F401-generated device file. This file provides InitADC(), Configure(), Get(), and getChannel() using raw register bit values instead.
  1. Peripheral availability via build tags
  • machine_stm32f4_extended.go//go:build stm32f4 && !stm32f401 guards F405/F407-only peripherals (DAC, CAN, extra UARTs/TIMs, DCMI, crypto blocks)
  • machine_stm32_adc_f4.go — changed from stm32f4 to stm32f4 && !stm32f401; the generic F4 ADC driver is replaced for F401 by machine_stm32f401_adc.go
  • machine_stm32_otgfs_usb.go — changed from stm32f4 || stm32f7 to (stm32f4 && !stm32f401) || stm32f7; F401 excluded from the generic OTG FS USB stack
  • machine_stm32f4_otgfs_vbus.go — removed stm32f401 from the build tag; F401 excluded from VBUS detection since USB OTG FS is not supported in this PR
  • usb.go — added !stm32f401 to the build tag (OTG FS register layout differs in the F401 SVD)
  • runtime/rand_hwrng.go — added !stm32f401 (no hardware RNG on F401)
  • runtime/rand_norng.go and crypto/rand/rand_baremetal.go — include stm32f401 to route to the software RNG path
  1. New board file
  • src/machine/board_nucleof401re.go — LED, button, UART1/UART2, SPI0, I2C0, ADC aliases (A0–A5), Arduino digital pin aliases (D0–D15)
  1. Target JSON
  • targets/nucleo-f401re.jsoncpu = cortex-m4, fpu = fpv4-sp-d16, linker script, OpenOCD stlink interface, reset_config srst_only connect_assert_srst
  1. Smoketest
  • GNUmakefile — added nucleo-f401re target to make smoketest (after nucleo-f103rb)

3. Implementation

Please refer to Files changed on GitHub. Key files are listed below.

4. Testing

Environment

Item Details
Host OS Windows 11 (PowerShell / Git Bash)
TinyGo Built from fork's dev branch
Flash tool OpenOCD (xPack distribution, Windows)
Primary DUT NUCLEO-F401RE (on-board ST-Link v2-1)
Peer board Seeed XIAO RP2040

Functional Tests on NUCLEO-F401RE

Note

I have preserved the changes made to the examples during testing in a commit outside of this PR(akif999@f042c81).
I am currently debating whether to incorporate some of these changes into this PR, so I would appreciate any advice you could offer.

Example / Test Result Notes
examples/blinky1 PASS LD2 (PA5) blinks at 500 ms interval; interval change verified
examples/echo / echo2 PASS UART1 / ST-Link VCP loopback echo works
examples/button PASS B1 (PC13) press toggles LD2; state printed via VCP
examples/pininterrupt PASS Edge interrupt on B1; naive software debounce behavior noted and expected
examples/device-id PASS 96-bit chip UID printed via VCP
examples/rand (crypto/rand) Expected panic: no rng — F401RE has no HW RNG; consistent with TinyGo design
UART peer test (xiao-rp2040) PASS NUCLEO UART2 (PA9=TX/D8, PA10=RX/D2) ↔ xiao UART0 (D6=TX, D7=RX) at 115200 bps; echo confirmed
I2C master test (xiao-rp2040 as target) PASS I2C0 (PB8=SCL/D15, PB9=SDA/D14) master ↔ xiao I2C1 (D4/D5) target at 0x11; read/write/readback verified
SPI loopback test PASS SPI0 (PA5=SCK/D13, PA6=MISO/D12, PA7=MOSI/D11); MOSI→MISO jumper; tx == rx confirmed
examples/adc PASS A0 (PA0); ADC raw value changes with input voltage
examples/pwm PASS TIM2 CH3 (PB10=D6) and CH2 (PB3=D3); duty cycle sweep verified with oscilloscope

Regression Testing

  • make smoketest executed on both the upstream dev branch and the fork branch
  • All existing boards pass with identical results on both branches (including feather-stm32f405, stm32f4disco, nucleo-f103rb, nucleo-f722ze, pico, xiao-rp2040, and others)

@akif999 akif999 changed the title feat(machine/stm32): add NUCLEO-F401RE support feat(machine/stm32): add STM32F40a and NUCLEO-F401RE support Aug 18, 2026
@akif999
akif999 marked this pull request as ready for review August 19, 2026 08:43
@akif999 akif999 changed the title feat(machine/stm32): add STM32F40a and NUCLEO-F401RE support feat(machine/stm32): add STM32F40 and NUCLEO-F401RE support Aug 19, 2026
@akif999 akif999 changed the title feat(machine/stm32): add STM32F40 and NUCLEO-F401RE support feat(machine/stm32): add STM32F401 and NUCLEO-F401RE support Aug 19, 2026
@deadprogram

Copy link
Copy Markdown
Member

Hello @akif999 thanks for the PR. Here are some editied comments from an assisted code review:

  1. SPI and I2C run at 2X the requested clock on F401.

Suggested fix: add per-chip APB1_FREQ/APB2_FREQ constants alongside the existing APB1_TIM_FREQ/APB2_TIM_FREQ in machine_stm32f401.go, machine_stm32f40x.go, and machine_stm32f469.go, and use those in all four functions. That also lets getBaudRateDivisor stay in the shared file instead of being duplicated.

  1. machine_stm32f401_adc.go can be dropped, if you fix the shared driver instead. Your version contains a real bug fix that should benefit all F4 parts. ADC_SMPR2_SMP1_Pos is 6, but the SMPx fields are 3 bits wide, so the existing 4 << (ch * 6) writes one field too high for every channel above 0. The SMPR1 line is worse — Go gives << and * equal precedence, left-associative, so 4 << (ch-10) * 6 parses as (4 << (ch-10)) * 6. Your cycles84 << (ch * 3) / << ((ch - 10) * 3) is correct. Since Cycles84 is just the literal 4, using 4 and 3 directly in machine_stm32_adc_f4.go compiles for F401 too, so you can delete the new file, drop the && !stm32f401 guard, and fix F405/F407/F469 in the process.

  2. Missing example support files. examples/adc and examples/pwm don't build for this target:

adc → adc.go:11:32: undefined: machine.ADC2
pwm → pwm.go:67:20: undefined: pwm

To answer your question in the description: yes, please pull in src/examples/pwm/nucleo-f401re.go (see nucleo-f722ze.go for the pattern) and ADC0/ADC1/ADC2 aliases in the board file (as board_stm32f4disco.go has). Those make the results you reported reproducible. The rest of that commit can stay out.

Minor / optional:

  • machine_stm32f4_extended.go: stm32f4 && !stm32f401 really means "F405/F407/F469" and will turn into a blacklist as F410/F411/F412 land — prefer a positive tag, like machine_stm32f4_pll_168mhz.go does. machine_stm32f40x_periph.go might also read better than "extended".
  • runtime_stm32f401.go:66: RCC.CR.Set(HSEON) clobbers the whole register; SetBits is safer (runtime_stm32f7x2.go:94 does this, though runtime_stm32f405.go:77 has the same issue).
  • initCLK applies PPRE1/PPRE2 after switching SW to PLL, so APB1 briefly runs at 84 MHz (2× its max). This mirrors runtime_stm32f405.go so it's pre-existing, but runtime_stm32f7x2.go:67-71 shows the safe ordering if you want to get it right in a new file.
  • Build tags: machine_stm32f401.go uses stm32f4 && stm32f401 while the other three new files use bare stm32f401
  • targets/nucleo-f401re.json uses 4-space indent; the rest of targets/ uses 2.
  • Description says PLL M=8/N=336 but the code is M=4/N=168 (code is correct), and says fpu = fpv4-sp-d16 — cortex-m4.json actually sets +soft-float with FP disabled, same as the other F4 targets.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants