diff --git a/src/device/arm/mpu.go b/src/device/arm/mpu.go new file mode 100644 index 0000000000..343b9d5ce4 --- /dev/null +++ b/src/device/arm/mpu.go @@ -0,0 +1,143 @@ +//go:build cortexm + +package arm + +import ( + "runtime/volatile" + "unsafe" +) + +/* +The struct below has been created using gen-device-svd +on cortex_m/armv7em.yaml from stm32-rs. +The MPU type has been included as some stm32 svd files +do not contain MPU definitions. +*/ + +const MPU_BASE = SCS_BASE + 0x0D90 + +// Memory Protection Unit +type MPU_Type struct { + TYPE volatile.Register32 // 0x0 + CTRL volatile.Register32 // 0x4 + RNR volatile.Register32 // 0x8 + RBAR volatile.Register32 // 0xC + RASR volatile.Register32 // 0x10 + RBAR_A1 volatile.Register32 // 0x14 + RASR_A1 volatile.Register32 // 0x18 + RBAR_A2 volatile.Register32 // 0x1C + RASR_A2 volatile.Register32 // 0x20 + RBAR_A3 volatile.Register32 // 0x24 + RASR_A3 volatile.Register32 // 0x28 +} + +var MPU = (*MPU_Type)(unsafe.Pointer(uintptr(MPU_BASE))) + +// Constants for MPU: Memory protection unit +const ( + // TYPER: MPU type register + // Position of SEPARATE field. + MPU_TYPER_SEPARATE_Pos = 0x0 + // Bit mask of SEPARATE field. + MPU_TYPER_SEPARATE_Msk = 0x1 + // Bit SEPARATE. + MPU_TYPER_SEPARATE = 0x1 + // Position of DREGION field. + MPU_TYPER_DREGION_Pos = 0x8 + // Bit mask of DREGION field. + MPU_TYPER_DREGION_Msk = 0xff00 + // Position of IREGION field. + MPU_TYPER_IREGION_Pos = 0x10 + // Bit mask of IREGION field. + MPU_TYPER_IREGION_Msk = 0xff0000 + + // CTRL: MPU control register + // Position of ENABLE field. + MPU_CTRL_ENABLE_Pos = 0x0 + // Bit mask of ENABLE field. + MPU_CTRL_ENABLE_Msk = 0x1 + // Bit ENABLE. + MPU_CTRL_ENABLE = 0x1 + // Position of HFNMIENA field. + MPU_CTRL_HFNMIENA_Pos = 0x1 + // Bit mask of HFNMIENA field. + MPU_CTRL_HFNMIENA_Msk = 0x2 + // Bit HFNMIENA. + MPU_CTRL_HFNMIENA = 0x2 + // Position of PRIVDEFENA field. + MPU_CTRL_PRIVDEFENA_Pos = 0x2 + // Bit mask of PRIVDEFENA field. + MPU_CTRL_PRIVDEFENA_Msk = 0x4 + // Bit PRIVDEFENA. + MPU_CTRL_PRIVDEFENA = 0x4 + + // RNR: MPU region number register + // Position of REGION field. + MPU_RNR_REGION_Pos = 0x0 + // Bit mask of REGION field. + MPU_RNR_REGION_Msk = 0xff + + // RBAR: MPU region base address register + // Position of REGION field. + MPU_RBAR_REGION_Pos = 0x0 + // Bit mask of REGION field. + MPU_RBAR_REGION_Msk = 0xf + // Position of VALID field. + MPU_RBAR_VALID_Pos = 0x4 + // Bit mask of VALID field. + MPU_RBAR_VALID_Msk = 0x10 + // Bit VALID. + MPU_RBAR_VALID = 0x10 + // Position of ADDR field. + MPU_RBAR_ADDR_Pos = 0x5 + // Bit mask of ADDR field. + MPU_RBAR_ADDR_Msk = 0xffffffe0 + + // RASR: MPU region attribute and size register + // Position of ENABLE field. + MPU_RASR_ENABLE_Pos = 0x0 + // Bit mask of ENABLE field. + MPU_RASR_ENABLE_Msk = 0x1 + // Bit ENABLE. + MPU_RASR_ENABLE = 0x1 + // Position of SIZE field. + MPU_RASR_SIZE_Pos = 0x1 + // Bit mask of SIZE field. + MPU_RASR_SIZE_Msk = 0x3e + // Position of SRD field. + MPU_RASR_SRD_Pos = 0x8 + // Bit mask of SRD field. + MPU_RASR_SRD_Msk = 0xff00 + // Position of B field. + MPU_RASR_B_Pos = 0x10 + // Bit mask of B field. + MPU_RASR_B_Msk = 0x10000 + // Bit B. + MPU_RASR_B = 0x10000 + // Position of C field. + MPU_RASR_C_Pos = 0x11 + // Bit mask of C field. + MPU_RASR_C_Msk = 0x20000 + // Bit C. + MPU_RASR_C = 0x20000 + // Position of S field. + MPU_RASR_S_Pos = 0x12 + // Bit mask of S field. + MPU_RASR_S_Msk = 0x40000 + // Bit S. + MPU_RASR_S = 0x40000 + // Position of TEX field. + MPU_RASR_TEX_Pos = 0x13 + // Bit mask of TEX field. + MPU_RASR_TEX_Msk = 0x380000 + // Position of AP field. + MPU_RASR_AP_Pos = 0x18 + // Bit mask of AP field. + MPU_RASR_AP_Msk = 0x7000000 + // Position of XN field. + MPU_RASR_XN_Pos = 0x1c + // Bit mask of XN field. + MPU_RASR_XN_Msk = 0x10000000 + // Bit XN. + MPU_RASR_XN = 0x10000000 +) diff --git a/src/examples/pwm/nucleo-h753zi.go b/src/examples/pwm/nucleo-h753zi.go new file mode 100644 index 0000000000..07e4fb110d --- /dev/null +++ b/src/examples/pwm/nucleo-h753zi.go @@ -0,0 +1,11 @@ +//go:build stm32h7 + +package main + +import "machine" + +var ( + pwm = &machine.TIM1 + pinA = machine.PA8 + pinB = machine.PA9 +) diff --git a/src/examples/wwdg/main.go b/src/examples/wwdg/main.go new file mode 100644 index 0000000000..3b8ad8e1ee --- /dev/null +++ b/src/examples/wwdg/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "machine" + "time" +) + +func main() { + time.Sleep(2 * time.Second) + + println("configuring window watchdog") + config := machine.WindowWatchdogConfig{ + TimeoutMicros: 100000, // 100ms + WindowPercent: 50, // 50ms to 100ms refresh window + } + + machine.WindowWatchdog.Configure(config) + machine.WindowWatchdog.Start() + + println("updating wwdg for 1 second") + for i := 0; i < 10; i++ { + time.Sleep(75 * time.Millisecond) // middle of the window + machine.WindowWatchdog.Update() + println("alive") + } + + println("entering tight loop (will reset)") + for { + time.Sleep(10 * time.Millisecond) + } +} diff --git a/src/machine/board_nucleoh753zi.go b/src/machine/board_nucleoh753zi.go new file mode 100644 index 0000000000..00c3a67f84 --- /dev/null +++ b/src/machine/board_nucleoh753zi.go @@ -0,0 +1,105 @@ +//go:build nucleoh753zi + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const xtalHz = 8_000_000 +const hseBypass = true + +const ( + // Arduino Pins + A0 = PA3 + A1 = PC0 + A2 = PC3 + A3 = PB1 + A4 = PC2 + A5 = PF10 + + D0 = PG9 + D1 = PG14 + D2 = PF15 + D3 = PE13 + D4 = PF14 + D5 = PE11 + D6 = PE9 + D7 = PF13 + D8 = PF12 + D9 = PD15 + D10 = PD14 + D11 = PA7 + D12 = PA6 + D13 = PA5 + D14 = PB9 + D15 = PB8 +) + +const ( + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN + LED_GREEN = PB0 + LED_YELLOW = PE1 + LED_RED = PB14 +) + +const ( + BUTTON = BUTTON_USER + BUTTON_USER = PC13 +) + +// UART pins +const ( + // PD8 and PD9 are connected to the ST-Link Virtual Com Port (VCP) + UART_TX_PIN = PD8 + UART_RX_PIN = PD9 + UART_ALT_FN = AF7_SPI2_3_USART1_2_3_UART5_SPDIFRX +) + +var ( + // USART3 is the hardware serial port connected to the onboard ST-LINK + // debugger to be exposed as virtual COM port over USB on Nucleo boards. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART3, + TxAltFuncSelector: UART_ALT_FN, + RxAltFuncSelector: UART_ALT_FN, + } + DefaultUART = UART1 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART1.handleInterrupt) +} + +// SPI pins +const ( + SPI0_SCK_PIN = PA5 + SPI0_SDI_PIN = PA6 + SPI0_SDO_PIN = PA7 +) + +var ( + SPI1 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_2_3_4_5_6_I2S, + } + SPI0 = SPI1 +) + +// I2C pins +const ( + I2C0_SCL_PIN = PB8 + I2C0_SDA_PIN = PB9 +) + +var ( + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3_4_USART1, + } + I2C0 = I2C1 +) diff --git a/src/machine/machine_stm32.go b/src/machine/machine_stm32.go index 0ba4512477..1edaa2cd06 100644 --- a/src/machine/machine_stm32.go +++ b/src/machine/machine_stm32.go @@ -86,12 +86,6 @@ func (p Pin) PortMaskClear() (*uint32, uint32) { return &port.BSRR.Reg, 1 << (pin + 16) } -// EnterBootloader resets the chip into the bootloader. -// This is currently a stub for STM32, required to satisfy machine.EnterBootloader -// called by machine/usb/cdc. -func EnterBootloader() { -} - var deviceID [12]byte // DeviceID returns an identifier that is unique within diff --git a/src/machine/machine_stm32_adc_h7.go b/src/machine/machine_stm32_adc_h7.go new file mode 100644 index 0000000000..c658898516 --- /dev/null +++ b/src/machine/machine_stm32_adc_h7.go @@ -0,0 +1,184 @@ +//go:build stm32h7 + +package machine + +import ( + "device/arm" + "device/stm32" +) + +// InitADC initializes the registers needed for ADC1 and ADC3. +func InitADC() { + // 1. Enable ADC bus clocks + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_ADC12EN) + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_ADC3EN) + + // 2. Configure ADC clock mode (Async from kernel clock) + // CCR is at offset 0x308 from ADC base. + stm32.ADC12_Common.CR.ReplaceBits(0x0, 0x3, 16) // CKMODE = 00 + stm32.ADC3_Common.CR.ReplaceBits(0x0, 0x3, 16) // CKMODE = 00 + + // 3. Exit deep power-down mode + stm32.ADC1.CR.ClearBits(stm32.ADC_CR_DEEPPWD) + stm32.ADC3.CR.ClearBits(stm32.ADC_CR_DEEPPWD) + + // 4. Enable voltage regulators + stm32.ADC1.CR.SetBits(stm32.ADC_CR_ADVREGEN) + stm32.ADC3.CR.SetBits(stm32.ADC_CR_ADVREGEN) + // Wait for T_ADCVREG_STUP (min 10us). The nop keeps the compiler from + // eliminating the loop as free of side effects. + for i := 0; i < 10000; i++ { + arm.Asm("nop") + } + + // Set BOOST[1:0]=0b11 for ADC kernel clock >25MHz (RM0433 §25.4.3 Table 121). + // The kernel clock is 80MHz (PLL2_P), so BOOST must be enabled. + stm32.ADC1.CR.ReplaceBits(0b11, 0x3, stm32.ADC_CR_BOOST_Pos) + stm32.ADC3.CR.ReplaceBits(0b11, 0x3, stm32.ADC_CR_BOOST_Pos) + + // 5. Calibration + // ADC1 + stm32.ADC1.CR.SetBits(stm32.ADC_CR_ADCAL | stm32.ADC_CR_ADCALLIN) + for stm32.ADC1.CR.HasBits(stm32.ADC_CR_ADCAL) { + } + // ADC3 + stm32.ADC3.CR.SetBits(stm32.ADC_CR_ADCAL | stm32.ADC_CR_ADCALLIN) + for stm32.ADC3.CR.HasBits(stm32.ADC_CR_ADCAL) { + } + + // 6. Enable ADCs + // ADC1 + stm32.ADC1.ISR.SetBits(stm32.ADC_ISR_ADRDY) // Clear ADRDY by writing 1 + stm32.ADC1.CR.SetBits(stm32.ADC_CR_ADEN) + for !stm32.ADC1.ISR.HasBits(stm32.ADC_ISR_ADRDY) { + } + // ADC3 + stm32.ADC3.ISR.SetBits(stm32.ADC_ISR_ADRDY) // Clear ADRDY by writing 1 + stm32.ADC3.CR.SetBits(stm32.ADC_CR_ADEN) + for !stm32.ADC3.ISR.HasBits(stm32.ADC_ISR_ADRDY) { + } + + // 7. Configure resolution (16-bit) + // RES[2:0] is at bits 4:2 in CFGR. 000: 16-bit. + stm32.ADC1.CFGR.ReplaceBits(0x0, 0x7, 2) + stm32.ADC3.CFGR.ReplaceBits(0x0, 0x7, 2) +} + +// Configure configures an ADC pin to be able to read analog data. +func (a ADC) Configure(config ADCConfig) { + a.Pin.Configure(PinConfig{Mode: PinInputAnalog}) + + // Set sampling time. + // H7 has SMPR1 (channels 0-9) and SMPR2 (channels 10-19). + // Each channel has 3 bits. + ch := a.getChannel() + adc, _ := a.getPeripheral() + const smpVal = 0x2 // 8.5 cycles + if ch <= 9 { + adc.SMPR1.ReplaceBits(uint32(smpVal), 0x7, uint8(ch)*3) + } else { + adc.SMPR2.ReplaceBits(uint32(smpVal), 0x7, uint8(ch-10)*3) + } +} + +// Get returns the current value of an ADC pin in the range 0..0xffff. +func (a ADC) Get() uint16 { + ch := uint32(a.getChannel()) + adc, ok := a.getPeripheral() + if !ok { + return 0 + } + + // Select channel (PCSEL register) + // Refer to RM0433 §25.4.12: Only one PCSELx bit must be set at a time. + adc.PCSEL.Set(1 << ch) + + // Set rank 1 to channel + // SQ1[4:0] at bits 10:6. L[3:0] at bits 3:0. + adc.SQR1.ReplaceBits(ch, 0x1F, 6) + adc.SQR1.ReplaceBits(0x0, 0xF, 0) // L=0 (1 conversion) + + // Start conversion + adc.CR.SetBits(stm32.ADC_CR_ADSTART) + + // Wait for end of conversion + for !adc.ISR.HasBits(stm32.ADC_ISR_EOC) { + } + + // Read 16-bit result + result := uint16(adc.DR.Get()) + + // Clear EOC + adc.ISR.SetBits(stm32.ADC_ISR_EOC) + + // Deselect channel + adc.PCSEL.Set(0) + + return result +} + +func (a ADC) getPeripheral() (*stm32.ADC_Type, bool) { + switch a.Pin { + case PF3, PF4, PF5, PF6, PF7, PF8, PF9, PF10: + return stm32.ADC3, true + default: + // Assume ADC1 for PA/PB/PC pins + return stm32.ADC1, true + } +} + +// getChannel returns the ADC channel number for a given GPIO pin. +// Mapping for STM32H743 per RM0433 and DS12110. +func (a ADC) getChannel() uint8 { + switch a.Pin { + case PA0: + return 16 + case PA1: + return 17 + case PA2: + return 14 + case PA3: + return 15 + case PA4: + return 18 + case PA5: + return 19 + case PA6: + return 3 + case PA7: + return 7 + case PB0: + return 9 + case PB1: + return 5 + case PC0: + return 10 + case PC1: + return 11 + case PC2: + return 12 + case PC3: + return 13 + case PC4: + return 4 + case PC5: + return 8 + case PF3: + return 5 + case PF4: + return 9 + case PF5: + return 4 + case PF6: + return 8 + case PF7: + return 3 + case PF8: + return 7 + case PF9: + return 2 + case PF10: + return 6 + } + return 0 +} diff --git a/src/machine/machine_stm32_bootloader.go b/src/machine/machine_stm32_bootloader.go new file mode 100644 index 0000000000..7273f772d2 --- /dev/null +++ b/src/machine/machine_stm32_bootloader.go @@ -0,0 +1,9 @@ +//go:build stm32 && !stm32h7 + +package machine + +// EnterBootloader resets the chip into the bootloader. +// This is currently a stub for STM32, required to satisfy machine.EnterBootloader +// called by machine/usb/cdc. +func EnterBootloader() { +} diff --git a/src/machine/machine_stm32_exti_syscfg.go b/src/machine/machine_stm32_exti_syscfg.go index b20609a6ce..d809a1eebe 100644 --- a/src/machine/machine_stm32_exti_syscfg.go +++ b/src/machine/machine_stm32_exti_syscfg.go @@ -1,4 +1,4 @@ -//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx && !stm32g0 && !stm32u5 && !stm32u0 +//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx && !stm32g0 && !stm32u5 && !stm32u0 && !stm32h7 package machine diff --git a/src/machine/machine_stm32_gpio_reva.go b/src/machine/machine_stm32_gpio_reva.go index 7d638c1af8..8794b0b94b 100644 --- a/src/machine/machine_stm32_gpio_reva.go +++ b/src/machine/machine_stm32_gpio_reva.go @@ -1,4 +1,4 @@ -//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx && !stm32g0 && !stm32u5 && !stm32u0 +//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx && !stm32g0 && !stm32u5 && !stm32u0 && !stm32h7 package machine diff --git a/src/machine/machine_stm32_i2c_revb.go b/src/machine/machine_stm32_i2c_revb.go index 300409f2d6..74c23ce961 100644 --- a/src/machine/machine_stm32_i2c_revb.go +++ b/src/machine/machine_stm32_i2c_revb.go @@ -1,4 +1,4 @@ -//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx || stm32g0 || stm32u0 || stm32u5 +//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx || stm32g0 || stm32u0 || stm32u5 || stm32h7 package machine diff --git a/src/machine/machine_stm32_moder_gpio.go b/src/machine/machine_stm32_moder_gpio.go index b8585c28c7..6a45d47fae 100644 --- a/src/machine/machine_stm32_moder_gpio.go +++ b/src/machine/machine_stm32_moder_gpio.go @@ -36,6 +36,9 @@ const ( // for PWM PinModePWMOutput PinMode = 12 + + // for USB (DP/DM lines) + PinModeUSB PinMode = 13 ) // Define several bitfields that have different names across chip families but @@ -144,6 +147,13 @@ func (p Pin) ConfigureAltFunc(config PinConfig, altFunc uint8) { port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) p.SetAltFunc(altFunc) + // USB + case PinModeUSB: + port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) + port.OSPEEDR.ReplaceBits(gpioOutputSpeedVeryHigh, gpioOutputSpeedMask, pos) + port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) + p.SetAltFunc(altFunc) + // ADC case PinInputAnalog: port.MODER.ReplaceBits(gpioModeAnalog, gpioModeMask, pos) diff --git a/src/machine/machine_stm32_pll.go b/src/machine/machine_stm32_pll.go new file mode 100644 index 0000000000..ceacac0959 --- /dev/null +++ b/src/machine/machine_stm32_pll.go @@ -0,0 +1,10 @@ +//go:build stm32 + +package machine + +// PLLParams holds the HSE main-PLL dividers/multipliers (RCC_PLLCFGR M/N/P/Q/R +// fields) needed to reach a chip's target VCO/SYSCLK frequency from a given +// crystal frequency. R is left zero on chips without a PLLR output. +type PLLParams struct { + M, N, P, Q, R uint32 +} diff --git a/src/machine/machine_stm32_spi.go b/src/machine/machine_stm32_spi.go index 2a24ff8cf4..3a53e6ce85 100644 --- a/src/machine/machine_stm32_spi.go +++ b/src/machine/machine_stm32_spi.go @@ -1,4 +1,4 @@ -//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0 && !stm32u5 && !stm32u0 +//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0 && !stm32u5 && !stm32u0 && !stm32h7 package machine diff --git a/src/machine/machine_stm32_tim.go b/src/machine/machine_stm32_tim.go index ddb975a814..7841d602be 100644 --- a/src/machine/machine_stm32_tim.go +++ b/src/machine/machine_stm32_tim.go @@ -79,6 +79,25 @@ func (t *TIM) Count() uint32 { return uint32(t.Device.CNT.Get()) } +// SetOnePulseMode enables or disables the one-pulse mode. +// When enabled, the timer will automatically stop at the next update event. +func (t *TIM) SetOnePulseMode(enable bool) { + if enable { + t.Device.CR1.SetBits(stm32.TIM_CR1_OPM) + } else { + t.Device.CR1.ClearBits(stm32.TIM_CR1_OPM) + } +} + +// SetEnabled enables or disables the timer. +func (t *TIM) SetEnabled(enable bool) { + if enable { + t.Device.CR1.SetBits(stm32.TIM_CR1_CEN) + } else { + t.Device.CR1.ClearBits(stm32.TIM_CR1_CEN) + } +} + // SetWraparoundInterrupt configures a callback to be called each // time the timer 'wraps-around'. // diff --git a/src/machine/machine_stm32h7.go b/src/machine/machine_stm32h7.go new file mode 100644 index 0000000000..f889953147 --- /dev/null +++ b/src/machine/machine_stm32h7.go @@ -0,0 +1,558 @@ +//go:build stm32 && stm32h7 + +package machine + +import ( + "device/arm" + "device/stm32" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +var deviceIDAddr = []uintptr{0x1FF1E800, 0x1FF1E804, 0x1FF1E808} + +// Default USB identifiers; board files with USB support should override these. +const ( + usb_STRING_PRODUCT = "STM32H7" + usb_STRING_MANUFACTURER = "TinyGo" + + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x0001 +) + +// EnterBootloader resets the chip. Jumping to the H7 system bootloader +// (0x1FF09800) is not implemented; a plain reset restarts the application. +func EnterBootloader() { + arm.SystemReset() +} + +// HSI_KER_FREQ is the fixed-frequency internal RC oscillator (RM0433 §8.2), +// unaffected by the board's HSE crystal. +const HSI_KER_FREQ = 64_000_000 + +// sysClockFreq returns SYSCLK (PLL1P output) for the board's configured +// xtalHz, per the M/N/P dividers initCLK() programs into PLL1. +func sysClockFreq() uint32 { + pll := PLLParams400MHz() + return xtalHz / pll.M * pll.N / pll.P +} + +// pll1QFreq returns the PLL1Q output (SPI1/2/3 kernel clock source). +func pll1QFreq() uint32 { + pll := PLLParams400MHz() + return xtalHz / pll.M * pll.N / pll.Q +} + +// hclkFreq, pclk1Freq..pclk4Freq return the AHB/APBx bus clocks. initCLK() +// hardcodes HPRE/D2PPRE1/D2PPRE2/D1PPRE/D3PPRE to Div2, so these are fixed +// ratios of SYSCLK regardless of xtal. +func hclkFreq() uint32 { return sysClockFreq() / 2 } +func pclk1Freq() uint32 { return hclkFreq() / 2 } +func pclk2Freq() uint32 { return hclkFreq() / 2 } +func pclk3Freq() uint32 { return hclkFreq() / 2 } +func pclk4Freq() uint32 { return hclkFreq() / 2 } + +// Peripheral kernel clocks as configured by initCLK(). +func spi123KerFreq() uint32 { return pll1QFreq() } // D2CCIP1R.SPI123SEL = PLL1_Q +func spi45KerFreq() uint32 { return pclk2Freq() } // D2CCIP1R.SPI45SEL = APB +func spi6KerFreq() uint32 { return pclk4Freq() } // D3CCIPR.SPI6SEL = PCLK4 +const I2C_KER_FREQ = HSI_KER_FREQ // D2CCIP2R.I2C123SEL = HSI_KER + +func CPUFrequency() uint32 { + return sysClockFreq() +} + +// initRNG gates the AHB2 bus clock for the RNG and enables the peripheral. +// HSI48 is started and selected as the RNG kernel clock in initCLK(). +func initRNG() { + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_RNGEN) + stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN) +} + +// Alternate function pin selection. +const ( + AF0_SYSTEM = 0 + AF1_TIM1_2_16_17_HRTIM = 1 + AF2_TIM3_4_5_HRTIM = 2 + AF3_TIM8_LPTIM1_DFSDM_HRTIM = 3 + AF4_I2C1_2_3_4_USART1 = 4 + AF5_SPI1_2_3_4_5_6_I2S = 5 + AF6_SPI2_3_SAI1_I2S_UART4_DFSDM = 6 + AF7_SPI2_3_USART1_2_3_UART5_SPDIFRX = 7 + AF8_SAI2_UART4_5_8_SPDIFRX_LPUART = 8 + AF9_FDCAN1_2_TIM13_14_QUADSPI_FMC = 9 + AF10_OTG_HS_FS_SAI2_QUADSPI_SDMMC2 = 10 + AF11_SDMMC2_ETH_MDIO_UART7_SWPMI = 11 + AF12_FMC_SDMMC1_MDIOS_OTG_FS_UART7 = 12 + AF13_DCMI_DSI_COMP_LTDC = 13 + AF14_LTDC = 14 + AF15_EVENTOUT = 15 +) + +// Timer clock = 2×PCLK when both HPRE and PPREx prescalers are active (RM0433 §8.5.5). +func apb1TimFreq() uint64 { return 2 * uint64(pclk1Freq()) } +func apb2TimFreq() uint64 { return 2 * uint64(pclk2Freq()) } + +//---------- Timer related code + +var ( + TIM1 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM1EN, + Device: stm32.TIM1, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA8, AF1_TIM1_2_16_17_HRTIM}, {PE9, AF1_TIM1_2_16_17_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PA9, AF1_TIM1_2_16_17_HRTIM}, {PE11, AF1_TIM1_2_16_17_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PA10, AF1_TIM1_2_16_17_HRTIM}, {PE13, AF1_TIM1_2_16_17_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PA11, AF1_TIM1_2_16_17_HRTIM}, {PE14, AF1_TIM1_2_16_17_HRTIM}}}, + }, + busFreq: apb2TimFreq(), + } + + TIM2 = TIM{ + EnableRegister: &stm32.RCC.APB1LENR, + EnableFlag: stm32.RCC_APB1LENR_TIM2EN, + Device: stm32.TIM2, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA0, AF1_TIM1_2_16_17_HRTIM}, {PA5, AF1_TIM1_2_16_17_HRTIM}, {PA15, AF1_TIM1_2_16_17_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PA1, AF1_TIM1_2_16_17_HRTIM}, {PB3, AF1_TIM1_2_16_17_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PA2, AF1_TIM1_2_16_17_HRTIM}, {PB10, AF1_TIM1_2_16_17_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PA3, AF1_TIM1_2_16_17_HRTIM}, {PB11, AF1_TIM1_2_16_17_HRTIM}}}, + }, + busFreq: apb1TimFreq(), + } + + TIM3 = TIM{ + EnableRegister: &stm32.RCC.APB1LENR, + EnableFlag: stm32.RCC_APB1LENR_TIM3EN, + Device: stm32.TIM3, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: apb1TimFreq(), + } + + TIM4 = TIM{ + EnableRegister: &stm32.RCC.APB1LENR, + EnableFlag: stm32.RCC_APB1LENR_TIM4EN, + Device: stm32.TIM4, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PB6, AF2_TIM3_4_5_HRTIM}, {PD12, AF2_TIM3_4_5_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PB7, AF2_TIM3_4_5_HRTIM}, {PD13, AF2_TIM3_4_5_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PB8, AF2_TIM3_4_5_HRTIM}, {PD14, AF2_TIM3_4_5_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PB9, AF2_TIM3_4_5_HRTIM}, {PD15, AF2_TIM3_4_5_HRTIM}}}, + }, + busFreq: apb1TimFreq(), + } + + TIM5 = TIM{ + EnableRegister: &stm32.RCC.APB1LENR, + EnableFlag: stm32.RCC_APB1LENR_TIM5EN, + Device: stm32.TIM5, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA0, AF2_TIM3_4_5_HRTIM}, {PH10, AF2_TIM3_4_5_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PA1, AF2_TIM3_4_5_HRTIM}, {PH11, AF2_TIM3_4_5_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PA2, AF2_TIM3_4_5_HRTIM}, {PH12, AF2_TIM3_4_5_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PA3, AF2_TIM3_4_5_HRTIM}, {PI0, AF2_TIM3_4_5_HRTIM}}}, + }, + busFreq: apb1TimFreq(), + } + + TIM8 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM8EN, + Device: stm32.TIM8, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PC6, AF3_TIM8_LPTIM1_DFSDM_HRTIM}, {PI5, AF3_TIM8_LPTIM1_DFSDM_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PC7, AF3_TIM8_LPTIM1_DFSDM_HRTIM}, {PI6, AF3_TIM8_LPTIM1_DFSDM_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PC8, AF3_TIM8_LPTIM1_DFSDM_HRTIM}, {PI7, AF3_TIM8_LPTIM1_DFSDM_HRTIM}}}, + TimerChannel{Pins: []PinFunction{{PC9, AF3_TIM8_LPTIM1_DFSDM_HRTIM}, {PI2, AF3_TIM8_LPTIM1_DFSDM_HRTIM}}}, + }, + busFreq: apb2TimFreq(), + } +) + +func (t *TIM) registerUPInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_UP, 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 &TIM8: + return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM8.handleUPInterrupt) + } + return interrupt.Interrupt{} +} + +func (t *TIM) registerOCInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM_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 &TIM8: + return interrupt.New(stm32.IRQ_TIM8_CC, TIM8.handleOCInterrupt) + } + return interrupt.Interrupt{} +} + +func (t *TIM) enableMainOutput() { + if t.Device == stm32.TIM1 || t.Device == stm32.TIM8 { + t.Device.BDTR.SetBits(stm32.TIM_BDTR_MOE) + } +} + +type psctype = uint32 +type arrtype = uint32 +type arrRegType = volatile.Register32 + +const ARR_MAX = 0x10000 +const PSC_MAX = 0x10000 + +//---------- UART related code + +// Configure the UART. +func (uart *UART) configurePins(config UARTConfig) { + config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) + config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) +} + +func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { + // Default USART kernel clock is the peripheral's own APB clock: + // USART1/6 sit on APB2, the rest on APB1. + clock := pclk1Freq() + if uart.Bus == stm32.USART1 || uart.Bus == stm32.USART6 { + clock = pclk2Freq() + } + return clock / baudRate +} + +func (uart *UART) setRegisters() { + uart.rxReg = &uart.Bus.RDR + uart.txReg = &uart.Bus.TDR + uart.statusReg = &uart.Bus.ISR + uart.txEmptyFlag = stm32.USART_ISR_TXE + uart.errClearReg = &uart.Bus.ICR +} + +func enableAltFuncClock(bus unsafe.Pointer) { + switch bus { + case unsafe.Pointer(stm32.USART1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) + case unsafe.Pointer(stm32.USART2): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_USART2EN) + case unsafe.Pointer(stm32.USART3): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_USART3EN) + case unsafe.Pointer(stm32.UART4): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_UART4EN) + case unsafe.Pointer(stm32.UART5): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_UART5EN) + case unsafe.Pointer(stm32.USART6): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART6EN) + case unsafe.Pointer(stm32.UART7): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_UART7EN) + case unsafe.Pointer(stm32.UART8): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_UART8EN) + case unsafe.Pointer(stm32.LPUART1): + stm32.RCC.APB4ENR.SetBits(stm32.RCC_APB4ENR_LPUART1EN) + case unsafe.Pointer(stm32.I2C1): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_I2C1EN) + case unsafe.Pointer(stm32.I2C2): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_I2C2EN) + case unsafe.Pointer(stm32.I2C3): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_I2C3EN) + case unsafe.Pointer(stm32.I2C4): + stm32.RCC.APB4ENR.SetBits(stm32.RCC_APB4ENR_I2C4EN) + case unsafe.Pointer(stm32.SPI1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.SPI2): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_SPI2EN) + case unsafe.Pointer(stm32.SPI3): + stm32.RCC.APB1LENR.SetBits(stm32.RCC_APB1LENR_SPI3EN) + case unsafe.Pointer(stm32.SPI4): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI4EN) + case unsafe.Pointer(stm32.SPI5): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI5EN) + case unsafe.Pointer(stm32.SPI6): + stm32.RCC.APB4ENR.SetBits(stm32.RCC_APB4ENR_SPI6EN) + case unsafe.Pointer(stm32.WWDG): + stm32.RCC.APB3ENR.SetBits(stm32.RCC_APB3ENR_WWDG1EN) + } +} + +//---------- GPIO related code + +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") + } +} + +func (p Pin) enableClock() { + switch p.getPort() { + case stm32.GPIOA: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIOAEN) + case stm32.GPIOB: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIOBEN) + case stm32.GPIOC: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIOCEN) + case stm32.GPIOD: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIODEN) + case stm32.GPIOE: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIOEEN) + case stm32.GPIOF: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIOFEN) + case stm32.GPIOG: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIOGEN) + case stm32.GPIOH: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIOHEN) + case stm32.GPIOI: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIOIEN) + case stm32.GPIOJ: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIOJEN) + case stm32.GPIOK: + stm32.RCC.AHB4ENR.SetBits(stm32.RCC_AHB4ENR_GPIOKEN) + } +} + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PA8 = portA + 8 + PA9 = portA + 9 + PA10 = portA + 10 + PA11 = portA + 11 + PA12 = portA + 12 + PA13 = portA + 13 + PA14 = portA + 14 + PA15 = portA + 15 + + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PB8 = portB + 8 + PB9 = portB + 9 + PB10 = portB + 10 + PB11 = portB + 11 + PB12 = portB + 12 + PB13 = portB + 13 + PB14 = portB + 14 + PB15 = portB + 15 + + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PC8 = portC + 8 + PC9 = portC + 9 + PC10 = portC + 10 + PC11 = portC + 11 + PC12 = portC + 12 + PC13 = portC + 13 + PC14 = portC + 14 + PC15 = portC + 15 + + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 + PD8 = portD + 8 + PD9 = portD + 9 + PD10 = portD + 10 + PD11 = portD + 11 + PD12 = portD + 12 + PD13 = portD + 13 + PD14 = portD + 14 + PD15 = portD + 15 + + PE0 = portE + 0 + PE1 = portE + 1 + PE2 = portE + 2 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PE7 = portE + 7 + PE8 = portE + 8 + PE9 = portE + 9 + PE10 = portE + 10 + PE11 = portE + 11 + PE12 = portE + 12 + PE13 = portE + 13 + PE14 = portE + 14 + PE15 = portE + 15 + + PF0 = portF + 0 + PF1 = portF + 1 + PF2 = portF + 2 + PF3 = portF + 3 + PF4 = portF + 4 + PF5 = portF + 5 + PF6 = portF + 6 + PF7 = portF + 7 + PF8 = portF + 8 + PF9 = portF + 9 + PF10 = portF + 10 + PF11 = portF + 11 + PF12 = portF + 12 + PF13 = portF + 13 + PF14 = portF + 14 + PF15 = portF + 15 + + PG0 = portG + 0 + PG1 = portG + 1 + PG2 = portG + 2 + PG3 = portG + 3 + PG4 = portG + 4 + PG5 = portG + 5 + PG6 = portG + 6 + PG7 = portG + 7 + PG8 = portG + 8 + PG9 = portG + 9 + PG10 = portG + 10 + PG11 = portG + 11 + PG12 = portG + 12 + PG13 = portG + 13 + PG14 = portG + 14 + PG15 = portG + 15 + + PH0 = portH + 0 + PH1 = portH + 1 + PH2 = portH + 2 + PH3 = portH + 3 + PH4 = portH + 4 + PH5 = portH + 5 + PH6 = portH + 6 + PH7 = portH + 7 + PH8 = portH + 8 + PH9 = portH + 9 + PH10 = portH + 10 + PH11 = portH + 11 + PH12 = portH + 12 + PH13 = portH + 13 + PH14 = portH + 14 + PH15 = portH + 15 + + PI0 = portI + 0 + PI1 = portI + 1 + PI2 = portI + 2 + PI3 = portI + 3 + PI4 = portI + 4 + PI5 = portI + 5 + PI6 = portI + 6 + PI7 = portI + 7 + PI8 = portI + 8 + PI9 = portI + 9 + PI10 = portI + 10 + PI11 = portI + 11 + PI12 = portI + 12 + PI13 = portI + 13 + PI14 = portI + 14 + PI15 = portI + 15 + + PJ0 = portJ + 0 + PJ1 = portJ + 1 + PJ2 = portJ + 2 + PJ3 = portJ + 3 + PJ4 = portJ + 4 + PJ5 = portJ + 5 + PJ6 = portJ + 6 + PJ7 = portJ + 7 + PJ8 = portJ + 8 + PJ9 = portJ + 9 + PJ10 = portJ + 10 + PJ11 = portJ + 11 + PJ12 = portJ + 12 + PJ13 = portJ + 13 + PJ14 = portJ + 14 + PJ15 = portJ + 15 + + PK0 = portK + 0 + PK1 = portK + 1 + PK2 = portK + 2 + PK3 = portK + 3 + PK4 = portK + 4 + PK5 = portK + 5 + PK6 = portK + 6 + PK7 = portK + 7 +) + +//---------- I2C related code + +// getFreqRange returns the TIMINGR value for the given I2C frequency. +// Values are for HSI_KER=64MHz (configured in initCLK). +// Derived from ST I2C timing calculator. +func (i2c *I2C) getFreqRange(br uint32) uint32 { + switch br { + case 10 * KHz: + return 0x30E0E7CF + case 100 * KHz: + return 0x10B0BFCF + case 400 * KHz: + return 0x00901E74 + case 800 * KHz: + return 0x00401137 + case 1_000 * KHz: + return 0x00401028 + default: + return 0x10B0BFCF + } +} diff --git a/src/machine/machine_stm32h7_cache.go b/src/machine/machine_stm32h7_cache.go new file mode 100644 index 0000000000..1db6d627a3 --- /dev/null +++ b/src/machine/machine_stm32h7_cache.go @@ -0,0 +1,57 @@ +//go:build stm32 && stm32h7 + +package machine + +import ( + "device/arm" + "runtime/volatile" + "unsafe" +) + +// Cortex-M7 cache maintenance by-address registers (ARMv7-M ARM Table B3-7). +var ( + scbDCIMVAC = (*volatile.Register32)(unsafe.Pointer(uintptr(0xE000EF5C))) // Invalidate D-cache line by address (W) + scbDCCMVAC = (*volatile.Register32)(unsafe.Pointer(uintptr(0xE000EF68))) // Clean D-cache line by address (W) + scbDCCIMVAC = (*volatile.Register32)(unsafe.Pointer(uintptr(0xE000EF70))) // Clean+Invalidate D-cache line by address (W) +) + +const dCacheLineSize = 32 // bytes; fixed on Cortex-M7 + +// DCacheClean writes dirty cache lines covering [addr, addr+size) back to +// memory without invalidating them. Call before the CPU hands a buffer to a +// DMA controller that only reads the buffer. +func DCacheClean(addr uintptr, size uintptr) { + arm.Asm("dsb 0xF") + end := addr + size + for a := addr &^ (dCacheLineSize - 1); a < end; a += dCacheLineSize { + scbDCCMVAC.Set(uint32(a)) + } + arm.Asm("dsb 0xF") + arm.Asm("isb 0xF") +} + +// DCacheInvalidate marks cache lines covering [addr, addr+size) as invalid so +// the next access re-fetches from memory. Call after a DMA write completes +// before the CPU reads the buffer. +func DCacheInvalidate(addr uintptr, size uintptr) { + arm.Asm("dsb 0xF") + end := addr + size + for a := addr &^ (dCacheLineSize - 1); a < end; a += dCacheLineSize { + scbDCIMVAC.Set(uint32(a)) + } + arm.Asm("dsb 0xF") + arm.Asm("isb 0xF") +} + +// DCacheFlush cleans and invalidates cache lines covering [addr, addr+size). +// Use when the region is both written by the CPU and read by DMA (or vice +// versa) and you want to synchronize in a single pass. +func DCacheFlush(addr uintptr, size uintptr) { + arm.Asm("dsb 0xF") + end := addr + size + for a := addr &^ (dCacheLineSize - 1); a < end; a += dCacheLineSize { + scbDCCIMVAC.Set(uint32(a)) + } + arm.Asm("dsb 0xF") + arm.Asm("isb 0xF") +} diff --git a/src/machine/machine_stm32h7_exti.go b/src/machine/machine_stm32h7_exti.go new file mode 100644 index 0000000000..b95a285e2b --- /dev/null +++ b/src/machine/machine_stm32h7_exti.go @@ -0,0 +1,27 @@ +//go:build stm32 && stm32h7 + +package machine + +import ( + "device/stm32" + "runtime/volatile" +) + +func getEXTIConfigRegister(pin uint8) *volatile.Register32 { + switch (pin & 0xf) / 4 { + case 0: + return &stm32.SYSCFG.EXTICR1 + case 1: + return &stm32.SYSCFG.EXTICR2 + case 2: + return &stm32.SYSCFG.EXTICR3 + case 3: + return &stm32.SYSCFG.EXTICR4 + } + return nil +} + +func enableEXTIConfigRegisters() { + // Enable SYSCFG in APB4ENR + stm32.RCC.APB4ENR.SetBits(stm32.RCC_APB4ENR_SYSCFGEN) +} diff --git a/src/machine/machine_stm32h7_gpio.go b/src/machine/machine_stm32h7_gpio.go new file mode 100644 index 0000000000..fded183dd0 --- /dev/null +++ b/src/machine/machine_stm32h7_gpio.go @@ -0,0 +1,111 @@ +//go:build stm32 && stm32h7 + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +// Callbacks for pin interrupt events +var pinCallbacks [16]func(Pin) + +// The pin currently associated with interrupt callback +// for a given slot. +var interruptPins [16]Pin + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + port := uint32(uint8(p) / 16) + pin := uint8(p) % 16 + + enableEXTIConfigRegisters() + + if callback == nil { + stm32.EXTI.CPUIMR1.ClearBits(1 << pin) + pinCallbacks[pin] = nil + return nil + } + + if pinCallbacks[pin] != nil { + return ErrNoPinChangeChannel + } + + pinCallbacks[pin] = callback + interruptPins[pin] = p + + crReg := getEXTIConfigRegister(pin) + shift := (pin & 0x3) * 4 + crReg.ReplaceBits(port, 0xf, shift) + + if (change & PinRising) != 0 { + stm32.EXTI.RTSR1.SetBits(1 << pin) + } + if (change & PinFalling) != 0 { + stm32.EXTI.FTSR1.SetBits(1 << pin) + } + stm32.EXTI.CPUIMR1.SetBits(1 << pin) + + intr := p.registerInterrupt() + intr.SetPriority(0) + intr.Enable() + + return nil +} + +func (p Pin) registerInterrupt() interrupt.Interrupt { + pin := uint8(p) % 16 + switch pin { + case 0: + return interrupt.New(stm32.IRQ_EXTI0, handlePinInterrupt0) + case 1: + return interrupt.New(stm32.IRQ_EXTI1, handlePinInterrupt1) + case 2: + return interrupt.New(stm32.IRQ_EXTI2, handlePinInterrupt2) + case 3: + return interrupt.New(stm32.IRQ_EXTI3, handlePinInterrupt3) + case 4: + return interrupt.New(stm32.IRQ_EXTI4, handlePinInterrupt4) + case 5, 6, 7, 8, 9: + return interrupt.New(stm32.IRQ_EXTI9_5, handlePinInterrupt9_5) + case 10, 11, 12, 13, 14, 15: + return interrupt.New(stm32.IRQ_EXTI15_10, handlePinInterrupt15_10) + } + return interrupt.Interrupt{} +} + +func handlePinInterrupt0(interrupt.Interrupt) { handlePinInterrupt(0) } +func handlePinInterrupt1(interrupt.Interrupt) { handlePinInterrupt(1) } +func handlePinInterrupt2(interrupt.Interrupt) { handlePinInterrupt(2) } +func handlePinInterrupt3(interrupt.Interrupt) { handlePinInterrupt(3) } +func handlePinInterrupt4(interrupt.Interrupt) { handlePinInterrupt(4) } +func handlePinInterrupt9_5(interrupt.Interrupt) { + handlePinInterrupt(5) + handlePinInterrupt(6) + handlePinInterrupt(7) + handlePinInterrupt(8) + handlePinInterrupt(9) +} +func handlePinInterrupt15_10(interrupt.Interrupt) { + handlePinInterrupt(10) + handlePinInterrupt(11) + handlePinInterrupt(12) + handlePinInterrupt(13) + handlePinInterrupt(14) + handlePinInterrupt(15) +} + +func handlePinInterrupt(pin uint8) { + if stm32.EXTI.CPUPR1.HasBits(1 << pin) { + // Writing 1 to the pending register clears the + // pending flag for that bit + stm32.EXTI.CPUPR1.Set(1 << pin) + + callback := pinCallbacks[pin] + if callback != nil { + callback(interruptPins[pin]) + } + } +} diff --git a/src/machine/machine_stm32h7_hsem.go b/src/machine/machine_stm32h7_hsem.go new file mode 100644 index 0000000000..905a578bd3 --- /dev/null +++ b/src/machine/machine_stm32h7_hsem.go @@ -0,0 +1,173 @@ +//go:build stm32 && stm32h753 + +package machine + +import ( + "device/stm32" + "runtime/volatile" + "unsafe" +) + +var ( + HSEM_CORE1 = (*HSEM_CORE_Type)(unsafe.Pointer((uintptr(unsafe.Pointer(stm32.HSEM)) + 0x100))) + HSEM_CORE2 = (*HSEM_CORE_Type)(unsafe.Pointer((uintptr(unsafe.Pointer(stm32.HSEM)) + 0x110))) +) + +// HSEM_CORE represents the registers for a core's hardware semaphore interrupts. +type HSEM_CORE_Type struct { + IER volatile.Register32 // HSEM Interrupt enable register Address offset: HSEM + 0x100 + n*0x10 (Interrupt N (0 or 1)) + ICR volatile.Register32 // HSEM Interrupt clear register Address offset: HSEM + 0x104 + n*0x10 (Interrupt N (0 or 1)) + ISR volatile.Register32 // HSEM Interrupt Status register Address offset: HSEM + 0x108 + n*0x10 (Interrupt N (0 or 1)) + MISR volatile.Register32 // HSEM Interrupt Masked Status register Address offset: HSEM + 0x10C + n*0x10 (Interrupt N (0 or 1)) +} + +type HSEM_ID_Type uint8 + +// Lock performs a 1-step (read) lock on the receiver semaphore ID. +// Semaphores can be used to ensure synchronization between processes running on +// different cores. Lock provides a non-blocking mechanism to lock semaphores +// in an atomic way. +// Returns true if and only if the semaphore lock is acquired or the given core +// already has the semaphore locked. +func (id HSEM_ID_Type) Lock(core uint32) bool { + switch id { + case 0: + return stm32.HSEM.RLR0.Get() == (stm32.HSEM_RLR_LOCK | core) + case 1: + return stm32.HSEM.RLR1.Get() == (stm32.HSEM_RLR_LOCK | core) + case 2: + return stm32.HSEM.RLR2.Get() == (stm32.HSEM_RLR_LOCK | core) + case 3: + return stm32.HSEM.RLR3.Get() == (stm32.HSEM_RLR_LOCK | core) + case 4: + return stm32.HSEM.RLR4.Get() == (stm32.HSEM_RLR_LOCK | core) + case 5: + return stm32.HSEM.RLR5.Get() == (stm32.HSEM_RLR_LOCK | core) + case 6: + return stm32.HSEM.RLR6.Get() == (stm32.HSEM_RLR_LOCK | core) + case 7: + return stm32.HSEM.RLR7.Get() == (stm32.HSEM_RLR_LOCK | core) + case 8: + return stm32.HSEM.RLR8.Get() == (stm32.HSEM_RLR_LOCK | core) + case 9: + return stm32.HSEM.RLR9.Get() == (stm32.HSEM_RLR_LOCK | core) + case 10: + return stm32.HSEM.RLR10.Get() == (stm32.HSEM_RLR_LOCK | core) + case 11: + return stm32.HSEM.RLR11.Get() == (stm32.HSEM_RLR_LOCK | core) + case 12: + return stm32.HSEM.RLR12.Get() == (stm32.HSEM_RLR_LOCK | core) + case 13: + return stm32.HSEM.RLR13.Get() == (stm32.HSEM_RLR_LOCK | core) + case 14: + return stm32.HSEM.RLR14.Get() == (stm32.HSEM_RLR_LOCK | core) + case 15: + return stm32.HSEM.RLR15.Get() == (stm32.HSEM_RLR_LOCK | core) + case 16: + return stm32.HSEM.RLR16.Get() == (stm32.HSEM_RLR_LOCK | core) + case 17: + return stm32.HSEM.RLR17.Get() == (stm32.HSEM_RLR_LOCK | core) + case 18: + return stm32.HSEM.RLR18.Get() == (stm32.HSEM_RLR_LOCK | core) + case 19: + return stm32.HSEM.RLR19.Get() == (stm32.HSEM_RLR_LOCK | core) + case 20: + return stm32.HSEM.RLR20.Get() == (stm32.HSEM_RLR_LOCK | core) + case 21: + return stm32.HSEM.RLR21.Get() == (stm32.HSEM_RLR_LOCK | core) + case 22: + return stm32.HSEM.RLR22.Get() == (stm32.HSEM_RLR_LOCK | core) + case 23: + return stm32.HSEM.RLR23.Get() == (stm32.HSEM_RLR_LOCK | core) + case 24: + return stm32.HSEM.RLR24.Get() == (stm32.HSEM_RLR_LOCK | core) + case 25: + return stm32.HSEM.RLR25.Get() == (stm32.HSEM_RLR_LOCK | core) + case 26: + return stm32.HSEM.RLR26.Get() == (stm32.HSEM_RLR_LOCK | core) + case 27: + return stm32.HSEM.RLR27.Get() == (stm32.HSEM_RLR_LOCK | core) + case 28: + return stm32.HSEM.RLR28.Get() == (stm32.HSEM_RLR_LOCK | core) + case 29: + return stm32.HSEM.RLR29.Get() == (stm32.HSEM_RLR_LOCK | core) + case 30: + return stm32.HSEM.RLR30.Get() == (stm32.HSEM_RLR_LOCK | core) + case 31: + return stm32.HSEM.RLR31.Get() == (stm32.HSEM_RLR_LOCK | core) + } + return false +} + +// Unlock releases the lock on the receiver semaphore ID. +// Semaphores can be used to ensure synchronization between processes running on +// different cores. Unlock provides a non-blocking mechanism to unlock +// semaphores in an atomic way. +func (id HSEM_ID_Type) Unlock(core uint32) { + switch id { + case 0: + stm32.HSEM.R0.Set(core) + case 1: + stm32.HSEM.R1.Set(core) + case 2: + stm32.HSEM.R2.Set(core) + case 3: + stm32.HSEM.R3.Set(core) + case 4: + stm32.HSEM.R4.Set(core) + case 5: + stm32.HSEM.R5.Set(core) + case 6: + stm32.HSEM.R6.Set(core) + case 7: + stm32.HSEM.R7.Set(core) + case 8: + stm32.HSEM.R8.Set(core) + case 9: + stm32.HSEM.R9.Set(core) + case 10: + stm32.HSEM.R10.Set(core) + case 11: + stm32.HSEM.R11.Set(core) + case 12: + stm32.HSEM.R12.Set(core) + case 13: + stm32.HSEM.R13.Set(core) + case 14: + stm32.HSEM.R14.Set(core) + case 15: + stm32.HSEM.R15.Set(core) + case 16: + stm32.HSEM.R16.Set(core) + case 17: + stm32.HSEM.R17.Set(core) + case 18: + stm32.HSEM.R18.Set(core) + case 19: + stm32.HSEM.R19.Set(core) + case 20: + stm32.HSEM.R20.Set(core) + case 21: + stm32.HSEM.R21.Set(core) + case 22: + stm32.HSEM.R22.Set(core) + case 23: + stm32.HSEM.R23.Set(core) + case 24: + stm32.HSEM.R24.Set(core) + case 25: + stm32.HSEM.R25.Set(core) + case 26: + stm32.HSEM.R26.Set(core) + case 27: + stm32.HSEM.R27.Set(core) + case 28: + stm32.HSEM.R28.Set(core) + case 29: + stm32.HSEM.R29.Set(core) + case 30: + stm32.HSEM.R30.Set(core) + case 31: + stm32.HSEM.R31.Set(core) + } +} diff --git a/src/machine/machine_stm32h7_pll.go b/src/machine/machine_stm32h7_pll.go new file mode 100644 index 0000000000..a6b7e4c576 --- /dev/null +++ b/src/machine/machine_stm32h7_pll.go @@ -0,0 +1,46 @@ +//go:build stm32 && stm32h7 + +package machine + +import "device/stm32" + +// PLLParams400MHz returns the HSE PLL dividers needed to reach a 800MHz VCO +// (400MHz SYSCLK, P=2, Q=4) for the configured crystal frequency. +// It returns the appropriate PLL1RGE range value in the R field. +func PLLParams400MHz() PLLParams { + var m, n, rge uint32 + switch xtalHz { + case 8_000_000: + m = 1 + n = 100 + case 16_000_000: + m = 2 + n = 100 + case 24_000_000: + m = 3 + n = 100 + case 25_000_000: + m = 5 + n = 160 + default: + panic("unsupported xtal frequency") + } + + vcoIn := xtalHz / m + if vcoIn < 2_000_000 { + rge = stm32.RCC_PLLCFGR_PLL1RGE_Range1 + } else if vcoIn < 4_000_000 { + rge = stm32.RCC_PLLCFGR_PLL1RGE_Range2 + } else if vcoIn < 8_000_000 { + rge = stm32.RCC_PLLCFGR_PLL1RGE_Range4 + } else { + rge = stm32.RCC_PLLCFGR_PLL1RGE_Range8 + } + + return PLLParams{M: m, N: n, P: 2, Q: 4, R: rge} +} + +// HSEBypass returns whether the HSE clock is configured in bypass mode (external MCO clock). +func HSEBypass() bool { + return hseBypass +} diff --git a/src/machine/machine_stm32h7_spi.go b/src/machine/machine_stm32h7_spi.go new file mode 100644 index 0000000000..32bd7e2736 --- /dev/null +++ b/src/machine/machine_stm32h7_spi.go @@ -0,0 +1,148 @@ +//go:build stm32 && stm32h7 + +package machine + +// Peripheral abstraction layer for SPI on the stm32h7 family + +import ( + "device/stm32" + "errors" + "math/bits" + "runtime/volatile" + "unsafe" +) + +var errSPIOverrun = errors.New("SPI overrun or mode fault") + +type SPI struct { + Bus *stm32.SPI_Type + AltFuncSelector uint8 +} + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin + SDO Pin + SDI Pin + LSBFirst bool + Mode uint8 +} + +// Configure is intended to setup the STM32 SPI interface. +func (spi *SPI) Configure(config SPIConfig) error { + // disable SPI interface before any configuration changes + spi.Bus.CR1.ClearBits(stm32.SPI_CR1_SPE) + + // enable clock for SPI + enableAltFuncClock(unsafe.Pointer(spi.Bus)) + + // init pins + if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + } + spi.configurePins(config) + + // CFG1 configuration: MBR and DSIZE (8-bit) + cfg1 := spi.getBaudRate(config) + cfg1 |= (8 - 1) << stm32.SPI_CFG1_DSIZE_Pos // 8-bit data size + spi.Bus.CFG1.Set(cfg1) + + // CFG2 configuration: CPOL, CPHA, MASTER, SSM, COMM + var cfg2 uint32 = stm32.SPI_CFG2_MASTER // bit mask, not field value + cfg2 |= stm32.SPI_CFG2_SSM // software NSS; bit mask, not field value + + if config.LSBFirst { + cfg2 |= 1 << 23 // LSBFRST is bit 23 in CFG2 + } + + // set polarity and phase + switch config.Mode { + case Mode1: + cfg2 |= stm32.SPI_CFG2_CPHA_SecondEdge << stm32.SPI_CFG2_CPHA_Pos + case Mode2: + cfg2 |= stm32.SPI_CFG2_CPOL_IdleHigh << stm32.SPI_CFG2_CPOL_Pos + case Mode3: + cfg2 |= stm32.SPI_CFG2_CPOL_IdleHigh << stm32.SPI_CFG2_CPOL_Pos + cfg2 |= stm32.SPI_CFG2_CPHA_SecondEdge << stm32.SPI_CFG2_CPHA_Pos + } + spi.Bus.CFG2.Set(cfg2) + + // CR2: TSIZE = 0 (Endless mode) + spi.Bus.CR2.Set(0) + + // CR1: SPE and SSI (use bit masks, not field values) + spi.Bus.CR1.Set(stm32.SPI_CR1_SSI | stm32.SPI_CR1_SPE) + + return nil +} + +func (spi *SPI) config8Bits() { + // Already handled in Configure via DSIZE +} + +func (spi *SPI) configurePins(config SPIConfig) { + config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) + config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) + config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) +} + +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { + clock := spi45KerFreq() + if spi.Bus == stm32.SPI1 || spi.Bus == stm32.SPI2 || spi.Bus == stm32.SPI3 { + clock = spi123KerFreq() + } else if spi.Bus == stm32.SPI6 { + clock = spi6KerFreq() + } + + if config.Frequency == 0 { + config.Frequency = clock / 2 + } + + // limit requested frequency to bus frequency and min frequency (DIV256) + freq := config.Frequency + if min := clock / 256; freq < min { + freq = min + } else if freq > clock/2 { + freq = clock / 2 + } + + // Round up to the next power-of-two divisor so output never exceeds freq. + // MBR encodes actual divider as 2^(MBR+1), so MBR = ceil_log2(ratio) - 1. + div := bits.Len32(clock/freq-1) - 1 + if div > 7 { + div = 7 + } + + return uint32(div) << stm32.SPI_CFG1_MBR_Pos +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi *SPI) Transfer(w byte) (byte, error) { + // RM0433 §50.4.9: set CSTART before writing TXDR. + spi.Bus.CR1.SetBits(stm32.SPI_CR1_CSTART) + + // Wait for TXP (Transmit packet space available) + for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXP) { + } + + // Write to TXDR as 8-bit access to push exactly one byte into the FIFO. + (*volatile.Register8)(unsafe.Pointer(&spi.Bus.TXDR.Reg)).Set(w) + + // Wait for RXP (Receive packet available) + for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXP) { + } + + // Check for overrun or mode fault before reading, to avoid returning stale data. + if sr := spi.Bus.SR.Get(); sr&(stm32.SPI_SR_OVR|stm32.SPI_SR_MODF) != 0 { + spi.Bus.IFCR.SetBits(stm32.SPI_IFCR_OVRC | stm32.SPI_IFCR_MODFC) + return 0, errSPIOverrun + } + + // Read from RXDR + data := byte(spi.Bus.RXDR.Get()) + + return data, nil +} diff --git a/src/machine/machine_stm32h7_usb.go b/src/machine/machine_stm32h7_usb.go new file mode 100644 index 0000000000..7673819b01 --- /dev/null +++ b/src/machine/machine_stm32h7_usb.go @@ -0,0 +1,560 @@ +//go:build stm32 && stm32h7 + +package machine + +import ( + "device/arm" + "device/stm32" + "machine/usb" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +// Synopsys DesignWare OTG registers +// The SVD-generated Go device file is missing some device-mode registers, +// so we define them here based on the Synopsys OTG IP. +type usbOTGRegs struct { + // Global registers (0x000) + GOTGCTL volatile.Register32 // 0x00 + GOTGINT volatile.Register32 // 0x04 + GAHBCFG volatile.Register32 // 0x08 + GUSBCFG volatile.Register32 // 0xC + GRSTCTL volatile.Register32 // 0x10 + GINTSTS volatile.Register32 // 0x14 + GINTMSK volatile.Register32 // 0x18 + GRXSTSR volatile.Register32 // 0x1C + GRXSTSP volatile.Register32 // 0x20 + GRXFSIZ volatile.Register32 // 0x24 + GNPTXFSIZ volatile.Register32 // 0x28 + GNPTXSTS volatile.Register32 // 0x2C + _ [8]byte + GCCFG volatile.Register32 // 0x38 + CID volatile.Register32 // 0x3C + _ [20]byte + GLPMCFG volatile.Register32 // 0x54 + GPWRDN volatile.Register32 // 0x58 + _ [4]byte + GDFIFO_S volatile.Register32 // 0x60 + _ [156]byte + HPTXFSIZ volatile.Register32 // 0x100 + DIEPTXF [15]volatile.Register32 // 0x104 + _ [1728]byte + + // Device registers (0x800) + DCFG volatile.Register32 // 0x800 + DCTL volatile.Register32 // 0x804 + DSTS volatile.Register32 // 0x808 + _ [4]byte + DIEPMSK volatile.Register32 // 0x810 + DOEPMSK volatile.Register32 // 0x814 + DAINT volatile.Register32 // 0x818 + DAINTMSK volatile.Register32 // 0x81C + _ [32]byte + DIEPEMPMSK volatile.Register32 // 0x840 + _ [188]byte + + // Endpoint registers + INEP [16]struct { + CTL volatile.Register32 // 0x900 + n*0x20 + _ [4]byte + INT volatile.Register32 // 0x908 + n*0x20 + _ [4]byte + TSIZ volatile.Register32 // 0x910 + n*0x20 + DMA volatile.Register32 // 0x914 + n*0x20 + TXFSTS volatile.Register32 // 0x918 + n*0x20 + _ [4]byte + } + OUTEP [16]struct { + CTL volatile.Register32 // 0xB00 + n*0x20 + _ [4]byte + INT volatile.Register32 // 0xB08 + n*0x20 + _ [4]byte + TSIZ volatile.Register32 // 0xB10 + n*0x20 + DMA volatile.Register32 // 0xB14 + n*0x20 + _ [8]byte + } + + _ [256]byte + + // Power and clock gating registers (0xE00) + PCGCCTL volatile.Register32 // 0xE00 +} + +// USB2 OTG_FS: the FS-only core wired to PA11/PA12 (Nucleo CN13). +// USB1 OTG_HS at 0x40040000 uses ULPI or its own embedded PHY on PB14/PB15, +// which is NOT routed to the user USB connector on this board. +var usbOTG = (*usbOTGRegs)(unsafe.Pointer(uintptr(0x40080000))) + +const ( + // GUSBCFG bits + GUSBCFG_PHYSEL = 1 << 6 + GUSBCFG_TRDT_Pos = 10 + GUSBCFG_FDMOD = 1 << 30 + + // GAHBCFG bits + GAHBCFG_GINT = 1 << 0 + + // GRSTCTL bits + GRSTCTL_CSRST = 1 << 0 + GRSTCTL_RXFFLSH = 1 << 4 + GRSTCTL_TXFFLSH = 1 << 5 + GRSTCTL_TXFNUM_ALL = 0x10 << 6 + GRSTCTL_AHBIDL = 1 << 31 + + // GINTSTS / GINTMSK bits + GINT_RXFLVL = 1 << 4 + GINT_GINAKEFF = 1 << 6 + GINT_GONAKEFF = 1 << 7 + GINT_USBSUSP = 1 << 11 + GINT_USBRST = 1 << 12 + GINT_ENUMDNE = 1 << 13 + GINT_IEPINT = 1 << 18 + GINT_OEPINT = 1 << 19 + + // DCFG bits + DCFG_DSPD_FS = 0x3 << 0 + + // DCTL bits + DCTL_RWUSIG = 1 << 0 + DCTL_SDIS = 1 << 1 + DCTL_GINSTS = 1 << 2 + DCTL_GONSTS = 1 << 3 + + // DIEPCTL / DOEPCTL bits + DEPCTL_MPSIZ_Pos = 0 + DEPCTL_USBAEP = 1 << 15 + DEPCTL_EPTYP_Pos = 18 + DEPCTL_STALL = 1 << 21 + DEPCTL_CNAK = 1 << 26 + DEPCTL_SNAK = 1 << 27 + DEPCTL_TXFNUM_Pos = 22 + DEPCTL_EPDIS = 1 << 30 + DEPCTL_EPENA = 1 << 31 + + // DIEPINT / DOEPINT bits + DEPINT_XFERC = 1 << 0 + DEPINT_EPDISD = 1 << 1 + DEPINT_SETUP = 1 << 3 + + NumberOfUSBEndpoints = 9 + + // FIFO layout in 32-bit words: shared RX FIFO plus one 64-word TX FIFO + // for EP0 and each of the 8 IN endpoints (256 + 9*64 = 832 ≤ 1024). + rxFIFOWords = 256 + txFIFOWords = 64 +) + +var ( + // ep0OutReceived signals that an OUT packet was received on EP0. + // Volatile: written from the USB interrupt, busy-waited on from thread mode. + ep0OutReceived volatile.Register8 +) + +// Configure the USB peripheral. +func (dev *USBDevice) Configure(config UARTConfig) { + if dev.initcomplete { + return + } + + // 1. Enable clocks + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_USB2OTGHSEN) + // The FS core has no ULPI clock, but AHB1LPENR resets with + // USB2OTGULPILPEN set, so in CPU Sleep mode (the scheduler's WFE) the RCC + // waits on a ULPI clock that never comes and the core's AHB interface + // stalls, killing USB whenever the CPU sleeps. + // Keep the OTG bus clock running in Sleep, drop the ULPI one. + stm32.RCC.AHB1LPENR.SetBits(stm32.RCC_AHB1LPENR_USB2OTGLPEN) + stm32.RCC.AHB1LPENR.ClearBits(stm32.RCC_AHB1LPENR_USB2OTGULPILPEN) + // Enable USB regulator (USB33DEN) for internal PHY. + // Already done in initCLK, but setting here as well for safety. + stm32.PWR.CR3.SetBits(stm32.PWR_CR3_USB33DEN) + + // Pulse RCC reset to clear any stale state from a warm reset. + stm32.RCC.AHB1RSTR.SetBits(stm32.RCC_AHB1RSTR_USB2OTGRST) + stm32.RCC.AHB1RSTR.ClearBits(stm32.RCC_AHB1RSTR_USB2OTGRST) + + // 2. Setup pins (PA11=DM, PA12=DP) for USB2 OTG_FS — AF10. + PA11.ConfigureAltFunc(PinConfig{Mode: PinModeUSB}, AF10_OTG_HS_FS_SAI2_QUADSPI_SDMMC2) + PA12.ConfigureAltFunc(PinConfig{Mode: PinModeUSB}, AF10_OTG_HS_FS_SAI2_QUADSPI_SDMMC2) + + // 3. Select internal FS PHY BEFORE the core reset below — the reset FSM + // samples the PHY clock, which only runs once PHYSEL is set. Give the + // clock a few cycles to start or CSRST can hang / self-clear too early. + usbOTG.GUSBCFG.SetBits(GUSBCFG_PHYSEL) + for j := 0; j < 10_000; j++ { + arm.Asm("nop") + } + + // 4. Core Reset — wait for AHB idle then pulse CSRST. + for usbOTG.GRSTCTL.Get()&GRSTCTL_AHBIDL == 0 { + } + usbOTG.GRSTCTL.SetBits(GRSTCTL_CSRST) + for usbOTG.GRSTCTL.Get()&GRSTCTL_CSRST != 0 { + } + + // Power up the FS transceiver AFTER the core reset: CSRST wipes GCCFG, + // so setting PWRDWN earlier leaves the transceiver off and the DP + // pull-up never appears (host sees no cable). + // No HW VBUS sensing: CN13's VBUS pin is not wired to the MCU VBUS-sense + // input on this board, so leave GCCFG.VBDEN (bit 21) clear and force + // session/VBUS valid via GOTGCTL instead (below). + usbOTG.GCCFG.Set(1 << 16) // PWRDWN + + // Make sure the PHY clock is not gated (e.g. by a bootloader). + usbOTG.PCGCCTL.Set(0) + + // Stay soft-disconnected until configuration is complete; CSRST left + // DCTL at its default "connected" state. + usbOTG.DCTL.SetBits(DCTL_SDIS) + + // 5. Force device mode now that the core is out of reset. The mode + // change takes effect only after up to 25 ms (RM0433); poll GINTSTS.CMOD + // (bit 0: 0 = device) with a generous busy-wait bound. + usbOTG.GUSBCFG.SetBits(GUSBCFG_FDMOD) + for j := 0; j < 20_000_000 && usbOTG.GINTSTS.Get()&0x1 != 0; j++ { + arm.Asm("nop") + } + + // Override all session/VBUS valid bits regardless of hardware pin state. + // GOTGCTL[2]=VBVALOEN, [3]=VBVALOVAL, [6]=BVALOEN, [7]=BVALOVAL. + usbOTG.GOTGCTL.SetBits(0x4 | 0x8 | 0x40 | 0x80) + + // Set turnaround time: HCLK=200MHz → TRDT=6 per RM0433 Table 362. + usbOTG.GUSBCFG.ReplaceBits(0x6<> 4) & 0x7FF + pktSts := (pop >> 17) & 0xF + + // All OUT/SETUP data is read from the shared RX FIFO (DFIFO[0]). + fifo := (*volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(usbOTG)) + 0x1000)) + + switch pktSts { + case 0x2: // OUT data packet received + // Guard against out-of-range endpoint or oversized packet: both come + // straight from hardware and would panic if used to slice the 64-byte + // cache buffers. Drain and discard instead. + if ep >= NumberOfUSBEndpoints || byteCnt > uint32(len(udd_ep_out_cache_buffer[0])) { + for i := uint32(0); i < byteCnt; i += 4 { + fifo.Get() + } + return + } + + buf := udd_ep_out_cache_buffer[ep][:byteCnt] + for i := uint32(0); i < byteCnt; i += 4 { + word := fifo.Get() + for j := uint32(0); j < 4 && i+j < byteCnt; j++ { + buf[i+j] = byte(word >> (8 * j)) + } + } + + if ep == 0 { + ep0OutReceived.Set(1) + AckUsbOutTransfer(0) + } else if usbRxHandler[ep] != nil { + if usbRxHandler[ep](buf) { + AckUsbOutTransfer(ep) + } + } + + case 0x6: // SETUP data packet received (always 8 bytes) + setupBuf := udd_ep_out_cache_buffer[0][:8] + for i := uint32(0); i < 8; i += 4 { + word := fifo.Get() + setupBuf[i] = byte(word) + setupBuf[i+1] = byte(word >> 8) + setupBuf[i+2] = byte(word >> 16) + setupBuf[i+3] = byte(word >> 24) + } + + setup := usb.NewSetup(setupBuf) + + ok := false + if (setup.BmRequestType & 0x60) == 0 { // Standard request + ok = handleStandardSetup(setup) + } else { + if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { + ok = usbSetupHandler[setup.WIndex](setup) + } + } + + if !ok { + // Stall EP0 — host will retry. + usbOTG.INEP[0].CTL.SetBits(DEPCTL_STALL) + usbOTG.OUTEP[0].CTL.SetBits(DEPCTL_STALL) + } + // Do NOT re-arm EP0 here. The FIFO will deliver a pktSts=4 + // (SETUP complete) entry next; we re-arm there. + + case 0x3: // OUT transfer complete (host sent ACK) — no payload. + // Nothing to do; EP already re-armed in case 0x2. + + case 0x4: // SETUP transaction complete — re-arm EP0 for next SETUP/OUT. + AckUsbOutTransfer(0) + } +} + +func handleUSBIRQ(intr interrupt.Interrupt) { + status := usbOTG.GINTSTS.Get() + + // Suppress suspend interrupts — suspend fires before enumeration completes. + if status&GINT_USBSUSP != 0 { + usbOTG.GINTSTS.Set(GINT_USBSUSP) + } + + if status&GINT_USBRST != 0 { + usbOTG.GINTSTS.Set(GINT_USBRST) + + // Flush all FIFOs. + usbOTG.GRSTCTL.SetBits(GRSTCTL_RXFFLSH) + for usbOTG.GRSTCTL.Get()&GRSTCTL_RXFFLSH != 0 { + } + usbOTG.GRSTCTL.SetBits(GRSTCTL_TXFFLSH | GRSTCTL_TXFNUM_ALL) + for usbOTG.GRSTCTL.Get()&GRSTCTL_TXFFLSH != 0 { + } + + // Reset device address. + usbOTG.DCFG.ClearBits(0x7F << 4) + + // Init EP0. + initEndpoint(0, 0) + usbConfiguration = 0 + + // TRDT for HCLK ≥ 30 MHz → 6. + usbOTG.GUSBCFG.ReplaceBits(0x6<> 16) & 0xFFFF + for ep := uint32(0); ep < NumberOfUSBEndpoints; ep++ { + if daint&(1<= wwdgCounterMin && counterVal <= wwdgCounterMax { + bestPrescaler = prescaler + bestCounter = uint8(counterVal) + found = true + break + } + } + + if !found { + // Use maximum timeout + bestPrescaler = wwdgPrescaler128 + bestCounter = wwdgCounterMax + } + + wd.prescaler = bestPrescaler + wd.counter = bestCounter + + // Calculate window value + windowVal := uint8(wwdgWindowMax) + if config.WindowPercent > 0 && config.WindowPercent < 100 { + // Window = 0x40 + ((counter - 0x40) * percent / 100) + counterRange := uint16(bestCounter) - wwdgCounterMin + windowOffset := (counterRange * uint16(config.WindowPercent)) / 100 + windowVal = uint8(wwdgCounterMin + windowOffset) + } + stm32.WWDG.CFR.Set((uint32(bestPrescaler) << stm32.WWDG_CFR_WDGTB_Pos) | uint32(windowVal)) + + return nil +} + +// Start enables the window watchdog. +// Once started, the WWDG cannot be disabled except by a system reset. +func (wd *windowWatchdogImpl) Start() error { + stm32.WWDG.CR.Set(uint32(wd.counter) | (1 << 7)) + return nil +} + +// Update refreshes the window watchdog counter. +// This must be called within the configured window to prevent a reset. +// Calling too early (counter > window) or too late (counter <= 0x3F) causes reset. +func (wd *windowWatchdogImpl) Update() { + stm32.WWDG.CR.Set(uint32(wd.counter) | (1 << 7)) +} + +// GetCounter returns the current WWDG counter value. +// Useful for timing refresh operations within the window. +func (wd *windowWatchdogImpl) GetCounter() uint8 { + return uint8(stm32.WWDG.CR.Get() & 0x7F) +} + +// EnableEarlyWakeupInterrupt enables the Early Wakeup Interrupt (EWI). +// The EWI is triggered when the counter reaches 0x40, giving the application +// a chance to refresh the watchdog or perform cleanup before reset. +func (wd *windowWatchdogImpl) EnableEarlyWakeupInterrupt() { + stm32.WWDG.CFR.SetBits(stm32.WWDG_CFR_EWI) +} + +// ClearEarlyWakeupFlag clears the Early Wakeup Interrupt flag. +// Must be called in the interrupt handler. +func (wd *windowWatchdogImpl) ClearEarlyWakeupFlag() { + stm32.WWDG.SR.ClearBits(stm32.WWDG_SR_EWIF) // RM0433 §35.3.4: write 0 to EWIF to clear +} + +// IsEarlyWakeupFlagSet returns true if the Early Wakeup Interrupt flag is set. +func (wd *windowWatchdogImpl) IsEarlyWakeupFlagSet() bool { + return stm32.WWDG.SR.Get()&1 != 0 +} + +// GetMaxTimeout returns the maximum timeout in microseconds for the current PCLK. +// Max timeout = (1/PCLK) × 4096 × 128 × 64 +func (wd *windowWatchdogImpl) GetMaxTimeout() uint32 { + pclk := uint64(pclk3Freq()) + return uint32((uint64(4096) * 128 * 64 * 1000000) / pclk) +} + +// GetMinTimeout returns the minimum timeout in microseconds for the current PCLK. +// Min timeout = (1/PCLK) × 4096 × 1 × 1 +func (wd *windowWatchdogImpl) GetMinTimeout() uint32 { + pclk := uint64(pclk3Freq()) + return uint32((uint64(4096) * 1000000) / pclk) +} diff --git a/src/machine/usb.go b/src/machine/usb.go index dac784aca0..9fcc1997d7 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -1,4 +1,4 @@ -//go:build sam || nrf52840 || rp2040 || rp2350 || stm32f4 || stm32f7 +//go:build sam || nrf52840 || rp2040 || rp2350 || stm32f4 || stm32f7 || stm32h7 package machine diff --git a/src/runtime/runtime_stm32h7.go b/src/runtime/runtime_stm32h7.go new file mode 100644 index 0000000000..6f41eadaa6 --- /dev/null +++ b/src/runtime/runtime_stm32h7.go @@ -0,0 +1,179 @@ +//go:build stm32 && stm32h7 + +package runtime + +import ( + "device/stm32" + "machine" + _ "machine/usb/cdc" +) + +func init() { + initCLK() + initMPU() + + machine.InitSerial() + + initTickTimer(&machine.TIM3) +} + +func putchar(c byte) { + machine.Serial.WriteByte(c) +} + +func getchar() byte { + for machine.Serial.Buffered() == 0 { + Gosched() + } + v, _ := machine.Serial.ReadByte() + return v +} + +func buffered() int { + return machine.Serial.Buffered() +} + +func initCLK() { + // 1. Enable SYSCFG + stm32.RCC.APB4ENR.SetBits(stm32.RCC_APB4ENR_SYSCFGEN) + + // H743/H753 have no SMPS; the NUCLEO-H753ZI runs VCORE from the internal + // LDO (the CR3 reset state). Supply bits in CR3 are write-once after POR, + // so keep LDOEN/BYPASS untouched and only enable the USB 3.3V level + // detector needed by the USB transceivers. + stm32.PWR.CR3.SetBits(stm32.PWR_CR3_USB33DEN) + + // 3. Configure VOS1 (Scale 1) + // RM0433 §6.8.4: ACTVOSRDY must be 1 (Run mode confirmed) before changing VOS. + for stm32.PWR.CSR1.Get()&stm32.PWR_CSR1_ACTVOSRDY == 0 { + } + // RM0433: VOS1 is 0b11. + stm32.PWR.D3CR.ReplaceBits(0b11< HCLK=200MHz, D1PPRE (APB3)=2 (4) -> PCLK3=100MHz + stm32.RCC.D1CFGR.ReplaceBits( + (stm32.RCC_D1CFGR_D1CPRE_Div1< PCLK1=100MHz, D2PPRE2 (APB2)=2 (4) -> PCLK2=100MHz + stm32.RCC.D2CFGR.ReplaceBits( + (stm32.RCC_D2CFGR_D2PPRE1_Div2< PCLK4=100MHz + stm32.RCC.D3CFGR.ReplaceBits( + stm32.RCC_D3CFGR_D3PPRE_Div2< 2 wait states, WRHIGHFREQ=2 (RM0433 Table 17). + stm32.FLASH.ACR.ReplaceBits(2|2< 200MHz + stm32.RCC.D2CCIP1R.ReplaceBits(stm32.RCC_D2CCIP1R_SPI123SEL_PLL1_Q< PCLK2 = 100MHz (PLL1-derived) + stm32.RCC.D2CCIP1R.ReplaceBits(stm32.RCC_D2CCIP1R_SPI45SEL_APB< 100MHz (PLL1-derived) + stm32.RCC.D3CCIPR.ReplaceBits(stm32.RCC_D3CCIPR_SPI6SEL_RCC_PCLK4<> 13) & 0x7FFF // NUMSETS field (value = sets-1) + assoc := (ccsidr >> 3) & 0x3FF // ASSOCIATIVITY field (value = ways-1) + + // Invalidate every set/way. For a 4-way cache the way index occupies + // bits[31:30] of DCISW; the set index starts at bit 5 (32-byte line = 2^5). + for set := uint32(0); set <= numSets; set++ { + for way := uint32(0); way <= assoc; way++ { + scbDCISW.Set((way << 30) | (set << 5)) + } + } + arm.Asm("dsb 0xF") + + arm.SCB.CCR.SetBits(arm.SCB_CCR_DC) + arm.Asm("dsb 0xF") + arm.Asm("isb 0xF") +} diff --git a/targets/nucleo-h753zi.json b/targets/nucleo-h753zi.json new file mode 100644 index 0000000000..3a9ea6239d --- /dev/null +++ b/targets/nucleo-h753zi.json @@ -0,0 +1,12 @@ +{ + "inherits": ["cortex-m7"], + "build-tags": ["nucleoh753zi", "stm32h753", "stm32h7", "stm32"], + "serial": "uart", + "linkerscript": "targets/stm32h7.ld", + "extra-files": [ + "src/device/stm32/stm32h753.s" + ], + "flash-method": "openocd", + "openocd-interface": "stlink", + "openocd-target": "stm32h7x" +} diff --git a/targets/stm32h7.ld b/targets/stm32h7.ld new file mode 100644 index 0000000000..0aca349b67 --- /dev/null +++ b/targets/stm32h7.ld @@ -0,0 +1,19 @@ + +MEMORY +{ + FLASH_TEXT (rx) : ORIGIN = 0x08000000, LENGTH = 2048K + RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 512K +} + +/* + * D2 SRAM (0x30000000, 288K on H753) and D3 SRAM (0x38000000, 64K) are not + * mapped here and unused by the runtime. If a future driver places DMA + * buffers there, add MPU regions for them in initMPU() + * (src/runtime/runtime_stm32h7_mpu.go) — they currently fall through to the + * default privileged WBWA map, so cache maintenance via DCacheClean/ + * DCacheInvalidate/DCacheFlush would silently miss those addresses. + */ + +_stack_size = 8K; + +INCLUDE "targets/arm.ld"