mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
feat(machine/stm32): add STM32H7 and NUCLEO-H753ZI support
This commit is contained in:
committed by
Ron Evans
parent
40ed956d6c
commit
3797e89600
+10
@@ -807,6 +807,8 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico -gc=leaking examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico-w examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
|
||||
@@ -845,8 +847,14 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico2-w examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=badger2350 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=blinky2350 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico-plus2 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=metro-rp2350 examples/blinky1
|
||||
@@ -888,6 +896,8 @@ ifneq ($(STM32), 0)
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-h753zi examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build stm32h7
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
var (
|
||||
pwm = &machine.TIM1
|
||||
pinA = machine.PA8
|
||||
pinB = machine.PA9
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0 && !stm32u5 && !stm32u0
|
||||
//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0 && !stm32u5 && !stm32u0 && !stm32h7
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -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'.
|
||||
//
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<<GUSBCFG_TRDT_Pos, 0xF<<GUSBCFG_TRDT_Pos, 0)
|
||||
|
||||
// 6. FIFO Configuration (total 1024 words shared by RX + all TX FIFOs).
|
||||
// initEndpoint assigns TX FIFO n to IN endpoint n, so every IN endpoint
|
||||
// 1..8 needs a configured FIFO even if the current class uses only a few.
|
||||
usbOTG.GRXFSIZ.Set(rxFIFOWords)
|
||||
usbOTG.GNPTXFSIZ.Set(txFIFOWords<<16 | rxFIFOWords)
|
||||
for i, offset := 0, uint32(rxFIFOWords+txFIFOWords); i < 8; i++ {
|
||||
usbOTG.DIEPTXF[i].Set(txFIFOWords<<16 | offset) // DIEPTXF[i] = FIFO i+1
|
||||
offset += txFIFOWords
|
||||
}
|
||||
|
||||
// Flush all FIFOs after (re)sizing them.
|
||||
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 {
|
||||
}
|
||||
|
||||
// 7. Device Configuration
|
||||
// Device Speed (FS)
|
||||
usbOTG.DCFG.ReplaceBits(DCFG_DSPD_FS, 0x3, 0)
|
||||
|
||||
// 8. Per-endpoint interrupt masks
|
||||
usbOTG.DIEPMSK.Set(DEPINT_XFERC)
|
||||
usbOTG.DOEPMSK.Set(DEPINT_XFERC | DEPINT_SETUP)
|
||||
|
||||
// 9. Interrupts
|
||||
// Clear anything pending, then unmask Reset, Enumeration Done,
|
||||
// RX FIFO Non-Empty, Setup Done (via OEPINT).
|
||||
usbOTG.GINTSTS.Set(0xFFFFFFFF)
|
||||
usbOTG.GINTMSK.SetBits(GINT_USBSUSP | GINT_USBRST | GINT_ENUMDNE | GINT_RXFLVL | GINT_IEPINT | GINT_OEPINT)
|
||||
// Global Interrupt Enable
|
||||
usbOTG.GAHBCFG.SetBits(GAHBCFG_GINT)
|
||||
|
||||
// 10. Enable IRQ
|
||||
i := interrupt.New(stm32.IRQ_OTG_FS, handleUSBIRQ)
|
||||
i.SetPriority(0)
|
||||
i.Enable()
|
||||
|
||||
dev.initcomplete = true
|
||||
|
||||
// Release soft-disconnect: pulls D+ high, making device visible to host.
|
||||
usbOTG.DCTL.ClearBits(DCTL_SDIS)
|
||||
}
|
||||
|
||||
func initEndpoint(ep, config uint32) {
|
||||
if ep == 0 {
|
||||
// Control endpoint
|
||||
// IN
|
||||
usbOTG.INEP[0].CTL.ReplaceBits(0, 0x3, DEPCTL_MPSIZ_Pos) // Max packet size 64 (00)
|
||||
usbOTG.INEP[0].INT.Set(0xFF) // Clear interrupts
|
||||
// OUT
|
||||
usbOTG.OUTEP[0].CTL.ReplaceBits(0, 0x3, DEPCTL_MPSIZ_Pos) // Max packet size 64 (00)
|
||||
usbOTG.OUTEP[0].INT.Set(0xFF) // Clear interrupts
|
||||
|
||||
// Unmask interrupts for EP0
|
||||
usbOTG.DAINTMSK.SetBits(0x10001) // EP0 IN and OUT
|
||||
} else {
|
||||
isIn := (config & uint32(usb.EndpointIn)) != 0
|
||||
typ := config & 0x03
|
||||
|
||||
if isIn {
|
||||
// Configure IN endpoint — do NOT set EPENA; set it only when queuing a transfer.
|
||||
ctl := uint32(DEPCTL_USBAEP)
|
||||
ctl |= (typ << DEPCTL_EPTYP_Pos)
|
||||
ctl |= (ep << DEPCTL_TXFNUM_Pos)
|
||||
ctl |= (64 << DEPCTL_MPSIZ_Pos) // MPS = 64 bytes
|
||||
ctl |= DEPCTL_SNAK // Start NAKing until data is ready
|
||||
usbOTG.INEP[ep].CTL.Set(ctl)
|
||||
usbOTG.INEP[ep].INT.Set(0xFF) // Clear any stale interrupts
|
||||
usbOTG.DAINTMSK.SetBits(1 << ep)
|
||||
} else {
|
||||
// Configure OUT endpoint — do NOT set EPENA here; AckUsbOutTransfer arms it.
|
||||
ctl := uint32(DEPCTL_USBAEP)
|
||||
ctl |= (typ << DEPCTL_EPTYP_Pos)
|
||||
ctl |= (64 << DEPCTL_MPSIZ_Pos) // MPS = 64 bytes
|
||||
ctl |= DEPCTL_SNAK
|
||||
usbOTG.OUTEP[ep].CTL.Set(ctl)
|
||||
usbOTG.OUTEP[ep].INT.Set(0xFF) // Clear any stale interrupts
|
||||
usbOTG.DAINTMSK.SetBits(1 << (ep + 16))
|
||||
// Arm immediately so host can send data.
|
||||
AckUsbOutTransfer(ep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleUSBSetAddress(setup usb.Setup) bool {
|
||||
addr := uint32(setup.WValueL)
|
||||
usbOTG.DCFG.ReplaceBits(addr<<4, 0x7F<<4, 0)
|
||||
SendZlp()
|
||||
return true
|
||||
}
|
||||
|
||||
func SendZlp() {
|
||||
sendUSBPacket(0, nil)
|
||||
}
|
||||
|
||||
func sendUSBPacket(ep uint32, data []byte) {
|
||||
// 1. Wait until the TX FIFO has room for the whole transfer, so a packet
|
||||
// queued while the previous one is still draining cannot corrupt the FIFO.
|
||||
// DTXFSTS reports free space in words; bounded wait in case the endpoint
|
||||
// is stuck (e.g. host stopped polling).
|
||||
words := uint32((len(data) + 3) / 4)
|
||||
for i := 0; i < 1_000_000 && usbOTG.INEP[ep].TXFSTS.Get()&0xFFFF < words; i++ {
|
||||
}
|
||||
|
||||
// 2. Setup transfer size
|
||||
pktCnt := uint32((len(data) + 63) / 64)
|
||||
if len(data) == 0 {
|
||||
pktCnt = 1
|
||||
}
|
||||
usbOTG.INEP[ep].TSIZ.Set(uint32(len(data)) | (pktCnt << 19))
|
||||
|
||||
// 3. Enable endpoint and clear NAK
|
||||
usbOTG.INEP[ep].CTL.SetBits(DEPCTL_EPENA | DEPCTL_CNAK)
|
||||
|
||||
// 4. Write data to FIFO
|
||||
// FIFOs are at 0x1000, 0x2000, ... from base
|
||||
fifo := (*volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(usbOTG)) + 0x1000 + uintptr(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
|
||||
usbOTG.OUTEP[0].TSIZ.Set(64 | (1 << 19) | (3 << 29))
|
||||
} else {
|
||||
usbOTG.OUTEP[ep].TSIZ.Set(64 | (1 << 19))
|
||||
}
|
||||
usbOTG.OUTEP[ep].CTL.SetBits(DEPCTL_EPENA | DEPCTL_CNAK)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) SetStallEPIn(ep uint32) {
|
||||
usbOTG.INEP[ep].CTL.SetBits(DEPCTL_STALL)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) SetStallEPOut(ep uint32) {
|
||||
usbOTG.OUTEP[ep].CTL.SetBits(DEPCTL_STALL)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
|
||||
usbOTG.INEP[ep].CTL.ClearBits(DEPCTL_STALL)
|
||||
usbOTG.INEP[ep].CTL.SetBits(1 << 28) // SD0PID
|
||||
}
|
||||
|
||||
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
|
||||
usbOTG.OUTEP[ep].CTL.ClearBits(DEPCTL_STALL)
|
||||
usbOTG.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).
|
||||
//
|
||||
// This runs inside the setup handler, which itself runs inside handleUSBIRQ.
|
||||
// The interrupt cannot re-enter to deliver the data stage, so the RX FIFO is
|
||||
// drained manually here until the EP0 OUT packet arrives.
|
||||
func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
|
||||
var b [cdcLineInfoSize]byte
|
||||
ep0OutReceived.Set(0)
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
if usbOTG.GINTSTS.Get()&GINT_RXFLVL != 0 {
|
||||
handleRxFIFO()
|
||||
}
|
||||
if ep0OutReceived.Get() != 0 {
|
||||
copy(b[:], udd_ep_out_cache_buffer[0][:])
|
||||
ep0OutReceived.Set(0)
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
return b, ErrUSBReadTimeout
|
||||
}
|
||||
|
||||
// handleRxFIFO pops one status entry from the shared RX FIFO and processes it.
|
||||
// Called from the USB interrupt, and re-entrantly from
|
||||
// ReceiveUSBControlPacket while a setup handler is waiting for the data stage.
|
||||
func handleRxFIFO() {
|
||||
pop := usbOTG.GRXSTSP.Get()
|
||||
ep := pop & 0xF
|
||||
byteCnt := (pop >> 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<<GUSBCFG_TRDT_Pos, 0xF<<GUSBCFG_TRDT_Pos, 0)
|
||||
|
||||
// Arm EP0 OUT to receive first SETUP/OUT.
|
||||
AckUsbOutTransfer(0)
|
||||
}
|
||||
|
||||
if status&GINT_ENUMDNE != 0 {
|
||||
usbOTG.GINTSTS.Set(GINT_ENUMDNE)
|
||||
// Enumeration done: activate EP0 at negotiated speed.
|
||||
usbOTG.INEP[0].CTL.SetBits(DEPCTL_CNAK)
|
||||
}
|
||||
|
||||
if status&GINT_RXFLVL != 0 {
|
||||
// RXFLVL is level-triggered: mask it while processing, not W1C.
|
||||
usbOTG.GINTMSK.ClearBits(GINT_RXFLVL)
|
||||
for usbOTG.GINTSTS.Get()&GINT_RXFLVL != 0 {
|
||||
handleRxFIFO()
|
||||
}
|
||||
usbOTG.GINTMSK.SetBits(GINT_RXFLVL)
|
||||
}
|
||||
|
||||
if status&GINT_IEPINT != 0 {
|
||||
daint := usbOTG.DAINT.Get() & 0xFFFF
|
||||
for ep := uint32(0); ep < NumberOfUSBEndpoints; ep++ {
|
||||
if daint&(1<<ep) != 0 {
|
||||
epInt := usbOTG.INEP[ep].INT.Get()
|
||||
usbOTG.INEP[ep].INT.Set(epInt) // W1C
|
||||
if epInt&DEPINT_XFERC != 0 {
|
||||
if ep != 0 && usbTxHandler[ep] != nil {
|
||||
usbTxHandler[ep]()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if status&GINT_OEPINT != 0 {
|
||||
daint := (usbOTG.DAINT.Get() >> 16) & 0xFFFF
|
||||
for ep := uint32(0); ep < NumberOfUSBEndpoints; ep++ {
|
||||
if daint&(1<<ep) != 0 {
|
||||
epInt := usbOTG.OUTEP[ep].INT.Get()
|
||||
usbOTG.OUTEP[ep].INT.Set(epInt) // W1C
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
//go:build stm32h7
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// WindowWatchdog provides access to the Window Watchdog (WWDG) peripheral.
|
||||
// Unlike IWDG, WWDG must be refreshed within a specific window - not too early
|
||||
// and not too late. This provides protection against both runaway code and
|
||||
// code that gets stuck in a loop refreshing the watchdog.
|
||||
var WindowWatchdog = &windowWatchdogImpl{}
|
||||
|
||||
// WindowWatchdogConfig holds configuration for the window watchdog timer.
|
||||
// The timeout (in microseconds) before the watchdog fires.
|
||||
// The valid range depends on System frequency.
|
||||
// At 100MHz: ~40µs to ~335ms
|
||||
type WindowWatchdogConfig struct {
|
||||
TimeoutMicros uint32
|
||||
|
||||
// The window value as a percentage of timeout (0-100).
|
||||
// Refresh must occur when counter is below this percentage of max.
|
||||
// Default (0) sets window to 100% (no window restriction).
|
||||
WindowPercent uint8
|
||||
}
|
||||
|
||||
// WWDG prescaler values
|
||||
const (
|
||||
wwdgPrescaler1 = 0 // CK Counter Clock (PCLK/4096) / 1
|
||||
wwdgPrescaler2 = 1 // CK Counter Clock (PCLK/4096) / 2
|
||||
wwdgPrescaler4 = 2 // CK Counter Clock (PCLK/4096) / 4
|
||||
wwdgPrescaler8 = 3 // CK Counter Clock (PCLK/4096) / 8
|
||||
wwdgPrescaler16 = 4 // CK Counter Clock (PCLK/4096) / 16
|
||||
wwdgPrescaler32 = 5 // CK Counter Clock (PCLK/4096) / 32
|
||||
wwdgPrescaler64 = 6 // CK Counter Clock (PCLK/4096) / 64
|
||||
wwdgPrescaler128 = 7 // CK Counter Clock (PCLK/4096) / 128
|
||||
)
|
||||
|
||||
// WWDG counter limits
|
||||
const (
|
||||
wwdgCounterMin = 0x40 // Minimum counter value (T6 must be set)
|
||||
wwdgCounterMax = 0x7F // Maximum counter value (7 bits)
|
||||
wwdgWindowMax = 0x7F // Maximum window value
|
||||
)
|
||||
|
||||
type windowWatchdogImpl struct {
|
||||
counter uint8 // Configured counter reload value
|
||||
prescaler uint8 // Configured prescaler
|
||||
}
|
||||
|
||||
// Configure the window watchdog.
|
||||
//
|
||||
// This method should not be called after the watchdog is started.
|
||||
// The WWDG cannot be disabled once started, except by a system reset.
|
||||
//
|
||||
// Timeout formula: t_WWDG = (1/PCLK) × 4096 × 2^WDGTB × (T[5:0] + 1)
|
||||
// Where T[5:0] = counter value - 0x40
|
||||
func (wd *windowWatchdogImpl) Configure(config WindowWatchdogConfig) error {
|
||||
// Enable WWDG clock
|
||||
enableAltFuncClock(unsafe.Pointer(stm32.WWDG))
|
||||
|
||||
// Calculate prescaler and counter value from timeout
|
||||
// Base tick = PCLK / 4096
|
||||
// With prescaler: tick = PCLK / (4096 * 2^prescaler)
|
||||
// Timeout = tick * (counter - 0x3F)
|
||||
|
||||
pclk := pclk3Freq()
|
||||
// Base tick = PCLK / 4096
|
||||
// Tick duration in nanoseconds = 1,000,000,000 / (pclk / 4096)
|
||||
// Simplified: (4096 * 1,000,000,000) / pclk
|
||||
baseTickNs := (uint64(4096) * 1000000000) / uint64(pclk)
|
||||
|
||||
timeoutUs := config.TimeoutMicros
|
||||
if timeoutUs == 0 {
|
||||
timeoutUs = 10000 // Default 10ms
|
||||
}
|
||||
timeoutNs := uint64(timeoutUs) * 1000
|
||||
|
||||
// Find the best prescaler and counter-combination
|
||||
var bestPrescaler uint8
|
||||
var bestCounter uint8
|
||||
found := false
|
||||
|
||||
for prescaler := uint8(0); prescaler <= 7; prescaler++ {
|
||||
// Tick duration with prescaler in nanoseconds
|
||||
tickNs := baseTickNs << prescaler
|
||||
|
||||
// Counter value needed (counter - 0x3F = timeout / tick)
|
||||
// Rearranged: counter = (timeoutNs / tickNs) + 0x3F
|
||||
counterVal := (timeoutNs / tickNs) + 0x3F
|
||||
|
||||
if counterVal >= 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)
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//go:build sam || nrf52840 || rp2040 || rp2350 || stm32f4 || stm32f7
|
||||
//go:build sam || nrf52840 || rp2040 || rp2350 || stm32f4 || stm32f7 || stm32h7
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -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<<stm32.PWR_D3CR_VOS_Pos, stm32.PWR_D3CR_VOS_Msk, 0)
|
||||
for stm32.PWR.D3CR.Get()&stm32.PWR_D3CR_VOSRDY == 0 {
|
||||
}
|
||||
|
||||
// 4. Enable HSE
|
||||
if machine.HSEBypass() {
|
||||
stm32.RCC.CR.SetBits(stm32.RCC_CR_HSEBYP | stm32.RCC_CR_HSEON)
|
||||
} else {
|
||||
stm32.RCC.CR.SetBits(stm32.RCC_CR_HSEON)
|
||||
}
|
||||
for stm32.RCC.CR.Get()&stm32.RCC_CR_HSERDY == 0 {
|
||||
}
|
||||
|
||||
// 5. Configure PLL1
|
||||
pll := machine.PLLParams400MHz()
|
||||
|
||||
// Source: HSE (2)
|
||||
stm32.RCC.PLLCKSELR.ReplaceBits(stm32.RCC_PLLCKSELR_PLLSRC_HSE, stm32.RCC_PLLCKSELR_PLLSRC_Msk, 0)
|
||||
// DIVM1
|
||||
stm32.RCC.PLLCKSELR.ReplaceBits(pll.M<<stm32.RCC_PLLCKSELR_DIVM1_Pos, stm32.RCC_PLLCKSELR_DIVM1_Msk, 0)
|
||||
|
||||
// PLL1CFGR: Wide VCO (0), Range based on pll.R (VCO input frequency)
|
||||
stm32.RCC.PLLCFGR.ReplaceBits(
|
||||
(stm32.RCC_PLLCFGR_PLL1VCOSEL_WideVCO<<stm32.RCC_PLLCFGR_PLL1VCOSEL_Pos)|
|
||||
(pll.R<<stm32.RCC_PLLCFGR_PLL1RGE_Pos),
|
||||
stm32.RCC_PLLCFGR_PLL1VCOSEL_Msk|stm32.RCC_PLLCFGR_PLL1RGE_Msk, 0)
|
||||
|
||||
// PLL1DIVR: DIVN1=pll.N, DIVP1=pll.P, DIVQ1=pll.Q
|
||||
// PLL1P = (VCO VCO_input * N) / P
|
||||
// PLL1Q = (VCO VCO_input * N) / Q
|
||||
stm32.RCC.PLL1DIVR.ReplaceBits(
|
||||
(pll.N-1)<<stm32.RCC_PLL1DIVR_DIVN1_Pos|(pll.P-1)<<stm32.RCC_PLL1DIVR_DIVP1_Pos|(pll.Q-1)<<stm32.RCC_PLL1DIVR_DIVQ1_Pos,
|
||||
stm32.RCC_PLL1DIVR_DIVN1_Msk|stm32.RCC_PLL1DIVR_DIVP1_Msk|stm32.RCC_PLL1DIVR_DIVQ1_Msk, 0)
|
||||
|
||||
// Enable PLL1P (SYSCLK=400MHz) and PLL1Q (SPI1/2/3 kernel=200MHz)
|
||||
stm32.RCC.PLLCFGR.SetBits(stm32.RCC_PLLCFGR_DIVP1EN | stm32.RCC_PLLCFGR_DIVQ1EN)
|
||||
|
||||
// Enable PLL1
|
||||
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLL1ON)
|
||||
for stm32.RCC.CR.Get()&stm32.RCC_CR_PLL1RDY == 0 {
|
||||
}
|
||||
|
||||
// 6. Bus Prescalers
|
||||
// D1CPRE=1 (0), HPRE=2 (8) -> HCLK=200MHz, D1PPRE (APB3)=2 (4) -> PCLK3=100MHz
|
||||
stm32.RCC.D1CFGR.ReplaceBits(
|
||||
(stm32.RCC_D1CFGR_D1CPRE_Div1<<stm32.RCC_D1CFGR_D1CPRE_Pos)|
|
||||
(stm32.RCC_D1CFGR_HPRE_Div2<<stm32.RCC_D1CFGR_HPRE_Pos)|
|
||||
(stm32.RCC_D1CFGR_D1PPRE_Div2<<stm32.RCC_D1CFGR_D1PPRE_Pos),
|
||||
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_D2PPRE1_Pos)|
|
||||
(stm32.RCC_D2CFGR_D2PPRE2_Div2<<stm32.RCC_D2CFGR_D2PPRE2_Pos),
|
||||
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_Pos,
|
||||
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<<stm32.FLASH_ACR_WRHIGHFREQ_Pos,
|
||||
stm32.FLASH_ACR_LATENCY_Msk|stm32.FLASH_ACR_WRHIGHFREQ_Msk, 0)
|
||||
for stm32.FLASH.ACR.Get()&stm32.FLASH_ACR_LATENCY_Msk != 2 {
|
||||
}
|
||||
|
||||
// 8. Switch to PLL1
|
||||
// SW: PLL1 (3)
|
||||
stm32.RCC.CFGR.ReplaceBits(3<<stm32.RCC_CFGR_SW_Pos, stm32.RCC_CFGR_SW_Msk, 0)
|
||||
for (stm32.RCC.CFGR.Get() & stm32.RCC_CFGR_SWS_Msk) != (3 << stm32.RCC_CFGR_SWS_Pos) {
|
||||
}
|
||||
|
||||
// 9. Peripheral Kernel Clocks
|
||||
// I2C1,2,3 source: HSI_KER (2) to keep 64MHz timing compatibility.
|
||||
stm32.RCC.D2CCIP2R.ReplaceBits(stm32.RCC_D2CCIP2R_I2C123SEL_HSI_KER<<stm32.RCC_D2CCIP2R_I2C123SEL_Pos, stm32.RCC_D2CCIP2R_I2C123SEL_Msk, 0)
|
||||
// I2C4 source: HSI_KER (2)
|
||||
stm32.RCC.D3CCIPR.ReplaceBits(stm32.RCC_D3CCIPR_I2C4SEL_HSI_KER<<stm32.RCC_D3CCIPR_I2C4SEL_Pos, stm32.RCC_D3CCIPR_I2C4SEL_Msk, 0)
|
||||
|
||||
// SPI1,2,3 source: PLL1_Q (0) -> 200MHz
|
||||
stm32.RCC.D2CCIP1R.ReplaceBits(stm32.RCC_D2CCIP1R_SPI123SEL_PLL1_Q<<stm32.RCC_D2CCIP1R_SPI123SEL_Pos, stm32.RCC_D2CCIP1R_SPI123SEL_Msk, 0)
|
||||
// SPI4,5 source: APB (0) -> PCLK2 = 100MHz (PLL1-derived)
|
||||
stm32.RCC.D2CCIP1R.ReplaceBits(stm32.RCC_D2CCIP1R_SPI45SEL_APB<<stm32.RCC_D2CCIP1R_SPI45SEL_Pos, stm32.RCC_D2CCIP1R_SPI45SEL_Msk, 0)
|
||||
// SPI6 source: PCLK4 (0) -> 100MHz (PLL1-derived)
|
||||
stm32.RCC.D3CCIPR.ReplaceBits(stm32.RCC_D3CCIPR_SPI6SEL_RCC_PCLK4<<stm32.RCC_D3CCIPR_SPI6SEL_Pos, stm32.RCC_D3CCIPR_SPI6SEL_Msk, 0)
|
||||
|
||||
// 10. HSI48 — used as kernel clock for RNG and USB (RM0433 §33.3 requires ≤48 MHz).
|
||||
stm32.RCC.CR.SetBits(stm32.RCC_CR_HSI48ON)
|
||||
for stm32.RCC.CR.Get()&stm32.RCC_CR_HSI48RDY == 0 {
|
||||
}
|
||||
// RNGSEL and USBSEL reset value is 0x0 (HSI48 or PLL1_Q); set explicitly to HSI48.
|
||||
stm32.RCC.D2CCIP2R.ReplaceBits(
|
||||
stm32.RCC_D2CCIP2R_RNGSEL_HSI48<<stm32.RCC_D2CCIP2R_RNGSEL_Pos|
|
||||
stm32.RCC_D2CCIP2R_USBSEL_HSI48<<stm32.RCC_D2CCIP2R_USBSEL_Pos,
|
||||
stm32.RCC_D2CCIP2R_RNGSEL_Msk|stm32.RCC_D2CCIP2R_USBSEL_Msk, 0)
|
||||
|
||||
// 11. Enable CRS (Clock Recovery System) for HSI48 stabilization via USB SOF.
|
||||
stm32.RCC.APB1HENR.SetBits(stm32.RCC_APB1HENR_CRSEN)
|
||||
stm32.CRS.CFGR.ReplaceBits(stm32.CRS_CFGR_SYNCSRC_USB_SOF<<stm32.CRS_CFGR_SYNCSRC_Pos, stm32.CRS_CFGR_SYNCSRC_Msk, 0)
|
||||
stm32.CRS.CR.SetBits(stm32.CRS_CR_CEN | stm32.CRS_CR_AUTOTRIMEN)
|
||||
|
||||
// 12. Configure PLL2 for ADC (80MHz)
|
||||
// DIVM2 = pll.M
|
||||
stm32.RCC.PLLCKSELR.ReplaceBits(pll.M<<stm32.RCC_PLLCKSELR_DIVM2_Pos, stm32.RCC_PLLCKSELR_DIVM2_Msk, 0)
|
||||
|
||||
// PLL2CFGR: Wide VCO (0), Range based on pll.R (VCO input frequency)
|
||||
stm32.RCC.PLLCFGR.ReplaceBits(
|
||||
(stm32.RCC_PLLCFGR_PLL2VCOSEL_WideVCO<<stm32.RCC_PLLCFGR_PLL2VCOSEL_Pos)|
|
||||
(pll.R<<stm32.RCC_PLLCFGR_PLL2RGE_Pos),
|
||||
stm32.RCC_PLLCFGR_PLL2VCOSEL_Msk|stm32.RCC_PLLCFGR_PLL2RGE_Msk, 0)
|
||||
|
||||
// PLL2DIVR: DIVN2=pll.N, DIVP2=10 (Value 9)
|
||||
// PLL2P = (VCO VCO_input * N) / 10 = 80MHz
|
||||
stm32.RCC.PLL2DIVR.ReplaceBits(
|
||||
(pll.N-1)<<stm32.RCC_PLL2DIVR_DIVN2_Pos|9<<stm32.RCC_PLL2DIVR_DIVP2_Pos,
|
||||
stm32.RCC_PLL2DIVR_DIVN2_Msk|stm32.RCC_PLL2DIVR_DIVP2_Msk, 0)
|
||||
|
||||
// Enable DIVP2EN
|
||||
stm32.RCC.PLLCFGR.SetBits(stm32.RCC_PLLCFGR_DIVP2EN)
|
||||
|
||||
// Enable PLL2
|
||||
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLL2ON)
|
||||
for stm32.RCC.CR.Get()&stm32.RCC_CR_PLL2RDY == 0 {
|
||||
}
|
||||
|
||||
// 12. ADC kernel clock source: PLL2_P (0).
|
||||
stm32.RCC.D3CCIPR.ReplaceBits(stm32.RCC_D3CCIPR_ADCSEL_PLL2_P<<stm32.RCC_D3CCIPR_ADCSEL_Pos, stm32.RCC_D3CCIPR_ADCSEL_Msk, 0)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
//go:build stm32 && stm32h7
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Cortex-M7 cache size registers (ARM TRM Table 4-2, within SCB address space).
|
||||
var (
|
||||
scbCCSIDR = (*volatile.Register32)(unsafe.Pointer(uintptr(0xE000ED80))) // Cache Size ID Register (R)
|
||||
scbCSELR = (*volatile.Register32)(unsafe.Pointer(uintptr(0xE000ED84))) // Cache Size Selection Register (R/W)
|
||||
)
|
||||
|
||||
// Cortex-M7 cache maintenance registers (ARMv7-M Architecture Ref Manual Table B3-7).
|
||||
var (
|
||||
scbICIALLU = (*volatile.Register32)(unsafe.Pointer(uintptr(0xE000EF50))) // Invalidate all I-cache (W)
|
||||
scbDCISW = (*volatile.Register32)(unsafe.Pointer(uintptr(0xE000EF60))) // Invalidate D-cache by set/way (W)
|
||||
)
|
||||
|
||||
// RASR region attribute presets for this chip's memory map (ARMv7-M
|
||||
// Architecture Ref Manual §B3.5.5). The register layout itself
|
||||
// (arm.MPU_Type, arm.MPU_RASR_* field positions) is generic to any
|
||||
// ARMv7-M core (M3/M4/M7) and lives in device/arm; only these specific
|
||||
// size/type/permission combinations are STM32H7-specific.
|
||||
const (
|
||||
// SIZE field bits[5:1]: value = log2(region_bytes) - 1.
|
||||
mpuRASRSize2MB = 20 << arm.MPU_RASR_SIZE_Pos // 2MB = 2^21, field=20
|
||||
mpuRASRSize512KB = 18 << arm.MPU_RASR_SIZE_Pos // 512KB = 2^19, field=18
|
||||
mpuRASRSize512MB = 28 << arm.MPU_RASR_SIZE_Pos // 512MB = 2^29, field=28
|
||||
|
||||
// AP field bits[26:24].
|
||||
mpuRASRAPReadOnly = 0x6 << arm.MPU_RASR_AP_Pos // Privileged and unprivileged read-only
|
||||
mpuRASRAPFullAccess = 0x3 << arm.MPU_RASR_AP_Pos // Full access (privileged and unprivileged)
|
||||
|
||||
// Memory type encodings: TEX bits[21:19], S bit[18], C bit[17], B bit[16].
|
||||
// Normal, Write-Through, No Write-Allocate (TEX=000, C=1, B=0, S=0).
|
||||
mpuRASRNormalWT = arm.MPU_RASR_C
|
||||
// Normal, Write-Back, Write-Allocate (TEX=001, C=1, B=1, S=0).
|
||||
mpuRASRNormalWBWA = (1 << arm.MPU_RASR_TEX_Pos) | arm.MPU_RASR_C | arm.MPU_RASR_B
|
||||
// Shared Device memory (TEX=000, C=0, B=1, S=1).
|
||||
mpuRASRDevice = arm.MPU_RASR_S | arm.MPU_RASR_B
|
||||
)
|
||||
|
||||
// initMPU configures the Cortex-M7 MPU, then enables L1 instruction and data
|
||||
// caches. Must be called after initCLK() and before any peripheral access.
|
||||
//
|
||||
// Memory map configured:
|
||||
//
|
||||
// Region 0: Flash 0x08000000 2MB Normal WT, RO, executable
|
||||
// Region 1: AXI SRAM 0x24000000 512KB Normal WBWA, RW, no-execute
|
||||
// Region 2: Peripherals 0x40000000 512MB Shared Device, RW, no-execute
|
||||
//
|
||||
// Unmapped regions fall back to the ARMv7-M default privileged map via
|
||||
// PRIVDEFENA, keeping NVIC/SCB and other PPB accesses strongly-ordered.
|
||||
func initMPU() {
|
||||
// Disable MPU before reconfiguring regions.
|
||||
arm.MPU.CTRL.Set(0)
|
||||
arm.Asm("dsb 0xF")
|
||||
arm.Asm("isb 0xF")
|
||||
|
||||
// Region 0: Flash — Normal, Write-Through, read-only, executable.
|
||||
arm.MPU.RNR.Set(0)
|
||||
arm.MPU.RBAR.Set(0x08000000)
|
||||
arm.MPU.RASR.Set(mpuRASRNormalWT | mpuRASRAPReadOnly | mpuRASRSize2MB | arm.MPU_RASR_ENABLE)
|
||||
|
||||
// Region 1: AXI SRAM — Normal, Write-Back Write-Allocate, full access, no-execute.
|
||||
arm.MPU.RNR.Set(1)
|
||||
arm.MPU.RBAR.Set(0x24000000)
|
||||
arm.MPU.RASR.Set(arm.MPU_RASR_XN | mpuRASRNormalWBWA | mpuRASRAPFullAccess | mpuRASRSize512KB | arm.MPU_RASR_ENABLE)
|
||||
|
||||
// Region 2: Peripherals — Shared Device, full access, no-execute.
|
||||
arm.MPU.RNR.Set(2)
|
||||
arm.MPU.RBAR.Set(0x40000000)
|
||||
arm.MPU.RASR.Set(arm.MPU_RASR_XN | mpuRASRDevice | mpuRASRAPFullAccess | mpuRASRSize512MB | arm.MPU_RASR_ENABLE)
|
||||
|
||||
// Enable MemManage fault so MPU violations raise a MemFault rather than
|
||||
// hard-faulting directly.
|
||||
arm.SCB.SHCSR.SetBits(arm.SCB_SHCSR_MEMFAULTENA)
|
||||
|
||||
// Enable MPU with privileged default background map.
|
||||
arm.MPU.CTRL.Set(arm.MPU_CTRL_ENABLE | arm.MPU_CTRL_PRIVDEFENA)
|
||||
arm.Asm("dsb 0xF")
|
||||
arm.Asm("isb 0xF")
|
||||
|
||||
// Enable L1 caches now that the MPU defines cacheability for each region.
|
||||
initICache()
|
||||
initDCache()
|
||||
}
|
||||
|
||||
// initICache invalidates then enables the L1 instruction cache.
|
||||
func initICache() {
|
||||
arm.Asm("dsb 0xF")
|
||||
arm.Asm("isb 0xF")
|
||||
scbICIALLU.Set(0) // Invalidate all I-cache lines.
|
||||
arm.Asm("dsb 0xF")
|
||||
arm.Asm("isb 0xF")
|
||||
arm.SCB.CCR.SetBits(arm.SCB_CCR_IC)
|
||||
arm.Asm("dsb 0xF")
|
||||
arm.Asm("isb 0xF")
|
||||
}
|
||||
|
||||
// initDCache invalidates all D-cache lines by set/way then enables the cache.
|
||||
// Iterates over sets and ways read from CCSIDR so it works for any M7 cache
|
||||
// size (8–64 KB, always 4-way, 32-byte lines on STM32H743).
|
||||
func initDCache() {
|
||||
scbCSELR.Set(0) // Select L1 D-cache.
|
||||
arm.Asm("dsb 0xF")
|
||||
|
||||
ccsidr := scbCCSIDR.Get()
|
||||
numSets := (ccsidr >> 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")
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user