diff --git a/src/examples/pwm/nucleo-h743zi.go b/src/examples/pwm/nucleo-h743zi.go new file mode 100644 index 0000000000..db9ac4b1e2 --- /dev/null +++ b/src/examples/pwm/nucleo-h743zi.go @@ -0,0 +1,11 @@ +//go:build stm32h743 + +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..914668d9bc --- /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..1d70631143 --- /dev/null +++ b/src/machine/board_nucleoh753zi.go @@ -0,0 +1,102 @@ +//go:build nucleoh753zi + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +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_adc_h7.go b/src/machine/machine_stm32_adc_h7.go new file mode 100644 index 0000000000..9ce42bb76b --- /dev/null +++ b/src/machine/machine_stm32_adc_h7.go @@ -0,0 +1,182 @@ +//go:build stm32h7 + +package machine + +import ( + "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). + for i := 0; i < 10000; i++ { + // Busy wait + } + + // 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_exti_syscfg.go b/src/machine/machine_stm32_exti_syscfg.go index 4eaa6c4ca8..777b289cf9 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 +//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx && !stm32g0 && !stm32u5 && !stm32h7 package machine diff --git a/src/machine/machine_stm32_gpio_reva.go b/src/machine/machine_stm32_gpio_reva.go index 27839c4918..7ebbbd3e15 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 +//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx && !stm32g0 && !stm32u5 && !stm32h7 package machine diff --git a/src/machine/machine_stm32_i2c_revb.go b/src/machine/machine_stm32_i2c_revb.go index 644c7e38ab..5522f8a5a0 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 || stm32u5 +//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx || stm32g0 || stm32u5 || stm32h7 package machine diff --git a/src/machine/machine_stm32_spi.go b/src/machine/machine_stm32_spi.go index d246391be8..6beb3583b1 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 +//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0 && !stm32u5 && !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..9cccaf71e9 --- /dev/null +++ b/src/machine/machine_stm32h7.go @@ -0,0 +1,532 @@ +//go:build stm32 && stm32h7 + +package machine + +import ( + "device/stm32" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +var deviceIDAddr = []uintptr{0x1FF1E800, 0x1FF1E804, 0x1FF1E808} + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "STM32H7" + usb_STRING_MANUFACTURER = "TinyGo" + + usb_VID uint16 = 0x0483 // STMicroelectronics + usb_PID uint16 = 0x5740 // Virtual COM Port +) + +// Clock frequencies as configured by initCLK(): HSE 8MHz → PLL1 (VCO 800MHz) → prescalers. +const ( + SYSCLK_FREQ = 400_000_000 // PLL1P = VCO / 2 + PLL1Q_FREQ = 200_000_000 // PLL1Q = VCO / 4 + HCLK_FREQ = 200_000_000 // AXI/AHB = SYSCLK / 2 (HPRE=Div2) + PCLK1_FREQ = 100_000_000 // APB1 = HCLK / 2 (D2PPRE1=Div2) + PCLK2_FREQ = 100_000_000 // APB2 = HCLK / 2 (D2PPRE2=Div2) + PCLK3_FREQ = 100_000_000 // APB3 = HCLK / 2 (D1PPRE=Div2) + PCLK4_FREQ = 100_000_000 // APB4 = HCLK / 2 (D3PPRE=Div2) + HSI_KER_FREQ = 64_000_000 // HSI internal oscillator (fixed 64MHz) +) + +// Peripheral kernel clocks as configured by initCLK(). +const ( + SPI123_KER_FREQ = PLL1Q_FREQ // D2CCIP1R.SPI123SEL = PLL1_Q + SPI45_KER_FREQ = PCLK2_FREQ // D2CCIP1R.SPI45SEL = APB + SPI6_KER_FREQ = PCLK4_FREQ // D3CCIPR.SPI6SEL = PCLK4 + I2C_KER_FREQ = HSI_KER_FREQ // D2CCIP2R.I2C123SEL = HSI_KER + UART_KER_FREQ = PCLK1_FREQ // USART/UART default kernel clock = PCLK +) + +func CPUFrequency() uint32 { + return SYSCLK_FREQ +} + +// 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). +const APB1_TIM_FREQ = 2 * PCLK1_FREQ +const APB2_TIM_FREQ = 2 * PCLK2_FREQ + +//---------- 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: APB2_TIM_FREQ, + } + + 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: APB1_TIM_FREQ, + } + + 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: APB1_TIM_FREQ, + } + + 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: APB1_TIM_FREQ, + } + + 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: 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_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: APB2_TIM_FREQ, + } +) + +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 { + return UART_KER_FREQ / 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 500 * KHz: + return 0x00700C1F + 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..638c8cfd2e --- /dev/null +++ b/src/machine/machine_stm32h7_gpio.go @@ -0,0 +1,98 @@ +//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_spi.go b/src/machine/machine_stm32h7_spi.go new file mode 100644 index 0000000000..bf742af207 --- /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 { + var clock uint32 = SPI45_KER_FREQ + if spi.Bus == stm32.SPI1 || spi.Bus == stm32.SPI2 || spi.Bus == stm32.SPI3 { + clock = SPI123_KER_FREQ + } else if spi.Bus == stm32.SPI6 { + clock = SPI6_KER_FREQ + } + + 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..2a402e4f62 --- /dev/null +++ b/src/machine/machine_stm32h7_usb.go @@ -0,0 +1,441 @@ +//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 + _ [164]byte + HPTXFSIZ volatile.Register32 // 0x100 + DIEPTXF [15]volatile.Register32 // 0x104 + _ [1724]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 + } + _ [192]byte // Pad to 0xB00 + 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 + } + + _ [768]byte + + // Power and clock gating registers (0xE00) + PCGCCTL volatile.Register32 // 0xE00 +} + +var usbOTG1 = (*usbOTGRegs)(unsafe.Pointer(uintptr(0x40040000))) + +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 +) + +var ( + endPoints = [NumberOfUSBEndpoints]uint32{} + + // ep0OutReceived signals that an OUT packet was received on EP0. + ep0OutReceived bool +) + +// Configure the USB peripheral. +func (dev *USBDevice) Configure(config UARTConfig) { + if dev.initcomplete { + return + } + + // 1. Enable clocks + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_USB1OTGHSEN) + // 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_USB1OTGRST) + stm32.RCC.AHB1RSTR.ClearBits(stm32.RCC_AHB1RSTR_USB1OTGRST) + + // 2. Setup pins (PB14=DM, PB15=DP) for OTG1 internal FS PHY — AF12 per DS12110 Table 11. + PB14.ConfigureAltFunc(PinConfig{Mode: PinModePWMOutput}, AF12_FMC_SDMMC1_MDIOS_OTG_FS_UART7) + PB15.ConfigureAltFunc(PinConfig{Mode: PinModePWMOutput}, AF12_FMC_SDMMC1_MDIOS_OTG_FS_UART7) + + // 3. Core Reset + for usbOTG1.GRSTCTL.Get()&GRSTCTL_AHBIDL == 0 { // Wait for AHB Master Idle + } + usbOTG1.GRSTCTL.SetBits(GRSTCTL_CSRST) + for usbOTG1.GRSTCTL.Get()&GRSTCTL_CSRST != 0 { + } + + // 4. Global Configuration + // Force Device Mode + usbOTG1.GUSBCFG.SetBits(GUSBCFG_FDMOD) + // Embedded FS PHY + usbOTG1.GUSBCFG.SetBits(GUSBCFG_PHYSEL) + + // 5. FIFO Configuration (Total 1024 words) + // RX FIFO: 128 words + usbOTG1.GRXFSIZ.Set(128) + // TX FIFO 0 (EP0): 64 words, starts at 128 + usbOTG1.GNPTXFSIZ.Set(64<<16 | 128) + // TX FIFO 1 (ACM): 64 words, starts at 192 + usbOTG1.DIEPTXF[0].Set(64<<16 | 192) + // TX FIFO 3 (CDC IN): 128 words, starts at 256 + // Note: index 2 in DIEPTXF is DIEPTXF3 + usbOTG1.DIEPTXF[2].Set(128<<16 | 256) + + // 6. Device Configuration + // Device Speed (FS) + usbOTG1.DCFG.ReplaceBits(DCFG_DSPD_FS, 0x3, 0) + + // 6. Interrupts + // Unmask Reset, Enumeration Done, RX FIFO Non-Empty, Setup Done (via OEPINT) + usbOTG1.GINTMSK.SetBits(GINT_USBRST | GINT_ENUMDNE | GINT_RXFLVL | GINT_IEPINT | GINT_OEPINT) + // Global Interrupt Enable + usbOTG1.GAHBCFG.SetBits(GAHBCFG_GINT) + + // 7. Enable IRQ + interrupt.New(stm32.IRQ_OTG_HS, handleUSBIRQ).Enable() + + dev.initcomplete = true +} + +func initEndpoint(ep, config uint32) { + if ep == 0 { + // Control endpoint + // IN + usbOTG1.INEP[0].CTL.ReplaceBits(0, 0x3, DEPCTL_MPSIZ_Pos) // Max packet size 64 (00) + usbOTG1.INEP[0].INT.Set(0xFF) // Clear interrupts + // OUT + usbOTG1.OUTEP[0].CTL.ReplaceBits(0, 0x3, DEPCTL_MPSIZ_Pos) // Max packet size 64 (00) + usbOTG1.OUTEP[0].INT.Set(0xFF) // Clear interrupts + + // Unmask interrupts for EP0 + usbOTG1.DAINTMSK.SetBits(0x10001) // EP0 IN and OUT + } else { + isIn := (config & uint32(usb.EndpointIn)) != 0 + typ := config & 0x03 + + if isIn { + // Configure IN endpoint + ctl := uint32(DEPCTL_EPENA | DEPCTL_USBAEP) + ctl |= (typ << DEPCTL_EPTYP_Pos) + ctl |= (ep << DEPCTL_TXFNUM_Pos) + ctl |= (64 << DEPCTL_MPSIZ_Pos) // MPSIZ (64 bytes) + ctl |= DEPCTL_SNAK // Set NAK initially + usbOTG1.INEP[ep].CTL.Set(ctl) + usbOTG1.DAINTMSK.SetBits(1 << ep) + } else { + // Configure OUT endpoint + ctl := uint32(DEPCTL_EPENA | DEPCTL_USBAEP) + ctl |= (typ << DEPCTL_EPTYP_Pos) + ctl |= (64 << DEPCTL_MPSIZ_Pos) + ctl |= DEPCTL_SNAK + usbOTG1.OUTEP[ep].CTL.Set(ctl) + usbOTG1.DAINTMSK.SetBits(1 << (ep + 16)) + } + } +} + +func handleUSBSetAddress(setup usb.Setup) bool { + addr := uint32(setup.WValueL) + usbOTG1.DCFG.ReplaceBits(addr<<4, 0x7F<<4, 0) + SendZlp() + return true +} + +func SendZlp() { + sendUSBPacket(0, nil) +} + +func sendUSBPacket(ep uint32, data []byte) { + // 1. Setup transfer size + pktCnt := uint32((len(data) + 63) / 64) + if len(data) == 0 { + pktCnt = 1 + } + usbOTG1.INEP[ep].TSIZ.Set(uint32(len(data)) | (pktCnt << 19)) + + // 2. Enable endpoint and clear NAK + usbOTG1.INEP[ep].CTL.SetBits(DEPCTL_EPENA | DEPCTL_CNAK) + + // 3. Write data to FIFO + // FIFOs are at 0x1000, 0x2000, ... from base + fifo := (*volatile.Register32)(unsafe.Pointer(uintptr(0x40041000 + ep*0x1000))) + for i := 0; i < len(data); i += 4 { + var word uint32 + for j := 0; j < 4 && i+j < len(data); j++ { + word |= uint32(data[i+j]) << (8 * j) + } + fifo.Set(word) + } +} + +func AckUsbOutTransfer(ep uint32) { + // Prepare for next OUT transfer + if ep == 0 { + // EP0 OUT: 1 packet, 64 bytes, 3 SETUP packets + usbOTG1.OUTEP[0].TSIZ.Set(64 | (1 << 19) | (3 << 29)) + } else { + usbOTG1.OUTEP[ep].TSIZ.Set(64 | (1 << 19)) + } + usbOTG1.OUTEP[ep].CTL.SetBits(DEPCTL_EPENA | DEPCTL_CNAK) +} + +func (dev *USBDevice) SetStallEPIn(ep uint32) { + usbOTG1.INEP[ep].CTL.SetBits(DEPCTL_STALL) +} + +func (dev *USBDevice) SetStallEPOut(ep uint32) { + usbOTG1.OUTEP[ep].CTL.SetBits(DEPCTL_STALL) +} + +func (dev *USBDevice) ClearStallEPIn(ep uint32) { + usbOTG1.INEP[ep].CTL.ClearBits(DEPCTL_STALL) + usbOTG1.INEP[ep].CTL.SetBits(1 << 28) // SD0PID +} + +func (dev *USBDevice) ClearStallEPOut(ep uint32) { + usbOTG1.OUTEP[ep].CTL.ClearBits(DEPCTL_STALL) + usbOTG1.OUTEP[ep].CTL.SetBits(1 << 28) // SD0PID +} + +// SendUSBInPacket sends a packet for USB (interrupt in / bulk in). +func SendUSBInPacket(ep uint32, data []byte) bool { + sendUSBPacket(ep, data) + return true +} + +// ReceiveUSBControlPacket receives a control packet (used for CDC line coding). +func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { + var b [cdcLineInfoSize]byte + // Wait for OUT packet on EP0 (handled in interrupt) + timeout := 1000000 + for !ep0OutReceived { + timeout-- + if timeout == 0 { + return b, ErrUSBReadTimeout + } + } + copy(b[:], udd_ep_out_cache_buffer[0][:]) + ep0OutReceived = false + return b, nil +} + +// EnterBootloader resets the MCU into the system bootloader. +func EnterBootloader() { + // For H7, we can just do a system reset for now, + // or jump to the bootloader address if known. + // 0x1FF09800 is the start of the system bootloader on H743. + arm.SystemReset() +} + +func handleUSBIRQ(intr interrupt.Interrupt) { + status := usbOTG1.GINTSTS.Get() + + if status&GINT_USBRST != 0 { + usbOTG1.GINTSTS.Set(GINT_USBRST) + + // Flush all FIFOs + usbOTG1.GRSTCTL.SetBits(GRSTCTL_RXFFLSH) + for usbOTG1.GRSTCTL.Get()&GRSTCTL_RXFFLSH != 0 { + } + usbOTG1.GRSTCTL.SetBits(GRSTCTL_TXFFLSH | GRSTCTL_TXFNUM_ALL) + for usbOTG1.GRSTCTL.Get()&GRSTCTL_TXFFLSH != 0 { + } + + // Init EP0 + initEndpoint(0, 0) + usbConfiguration = 0 + + // Set TRDT (Turnaround time) based on HCLK + // For FS, TRDT should be 0x6 (for HCLK > 30MHz) + usbOTG1.GUSBCFG.ReplaceBits(0x6<> 4) & 0x7FF + pktSts := (pop >> 17) & 0xF + + if pktSts == 0x2 { // OUT packet received + fifo := (*volatile.Register32)(unsafe.Pointer(uintptr(0x40041000 + ep*0x1000))) + // Read data into cache buffer + 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 { + if usbRxHandler[ep] != nil { + if usbRxHandler[ep](buf) { + AckUsbOutTransfer(ep) + } + } + } else { + ep0OutReceived = true + AckUsbOutTransfer(0) + } + } else if pktSts == 0x6 { // SETUP packet received + fifo := (*volatile.Register32)(unsafe.Pointer(uintptr(0x40041000 + ep*0x1000))) + // SETUP packet is always 8 bytes (2 words) + 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 + ok = handleStandardSetup(setup) + } else { + if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { + ok = usbSetupHandler[setup.WIndex](setup) + } + } + + if !ok { + // Stall EP0 — sends STALL handshake so the host does not retry. + usbOTG1.INEP[0].CTL.SetBits(DEPCTL_STALL) + usbOTG1.OUTEP[0].CTL.SetBits(DEPCTL_STALL) + } + } + } + + if status&GINT_IEPINT != 0 { + daint := usbOTG1.DAINT.Get() & 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(PCLK3_FREQ) + 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(PCLK3_FREQ) + return uint32((uint64(4096) * 1000000) / pclk) +} diff --git a/src/machine/usb.go b/src/machine/usb.go index 3833c78136..f78d9e657e 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -1,4 +1,4 @@ -//go:build sam || nrf52840 || rp2040 || rp2350 +//go:build sam || nrf52840 || rp2040 || rp2350 || stm32h7 package machine diff --git a/src/runtime/runtime_stm32h7.go b/src/runtime/runtime_stm32h7.go new file mode 100644 index 0000000000..b0dc8fef0b --- /dev/null +++ b/src/runtime/runtime_stm32h7.go @@ -0,0 +1,162 @@ +//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) + + // 2. Configure Power Supply + // Nucleo-H743ZI uses LDO. Set LDOEN and clear BYPASS. + // Enable USB regulator (USB33DEN) for internal PHY. + // CR3 can only be written once after POR, and requires SCUEN=1. + stm32.PWR.CR3.ReplaceBits(stm32.PWR_CR3_LDOEN|stm32.PWR_CR3_USB33DEN|stm32.PWR_CR3_SCUEN, stm32.PWR_CR3_LDOEN_Msk|stm32.PWR_CR3_USB33DEN_Msk|stm32.PWR_CR3_SCUEN_Msk|0x1 /* BYPASS */, 0) + + // 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|stm32.RCC_D1CFGR_HPRE_Div2|stm32.RCC_D1CFGR_D1PPRE_Div2, + stm32.RCC_D1CFGR_D1CPRE_Msk|stm32.RCC_D1CFGR_HPRE_Msk|stm32.RCC_D1CFGR_D1PPRE_Msk, 0) + + // D2CFGR: D2PPRE1 (APB1)=2 (4) -> PCLK1=100MHz, D2PPRE2 (APB2)=2 (4) -> PCLK2=100MHz + stm32.RCC.D2CFGR.ReplaceBits(stm32.RCC_D2CFGR_D2PPRE1_Div2|stm32.RCC_D2CFGR_D2PPRE2_Div2, + stm32.RCC_D2CFGR_D2PPRE1_Msk|stm32.RCC_D2CFGR_D2PPRE2_Msk, 0) + + // D3CFGR: D3PPRE (APB4)=2 (4) -> PCLK4=100MHz + stm32.RCC.D3CFGR.ReplaceBits(stm32.RCC_D3CFGR_D3PPRE_Div2, stm32.RCC_D3CFGR_D3PPRE_Msk, 0) + + // 7. Flash Latency + // VOS1, 200MHz AXI clock -> 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..11bd820788 --- /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-v2-1", + "openocd-target": "stm32h7x" +} diff --git a/targets/stm32h7.ld b/targets/stm32h7.ld new file mode 100644 index 0000000000..3f98ada644 --- /dev/null +++ b/targets/stm32h7.ld @@ -0,0 +1,10 @@ + +MEMORY +{ + FLASH_TEXT (rx) : ORIGIN = 0x08000000, LENGTH = 2048K + RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 512K +} + +_stack_size = 8K; + +INCLUDE "targets/arm.ld"