mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-19 12:04:03 +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
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pico -gc=leaking examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=pico -gc=leaking examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(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
|
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
|
||||||
@@ -845,8 +847,14 @@ endif
|
|||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(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
|
$(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(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
|
$(TINYGO) build -size short -o test.hex -target=pico-plus2 examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=metro-rp2350 examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=metro-rp2350 examples/blinky1
|
||||||
@@ -888,6 +896,8 @@ ifneq ($(STM32), 0)
|
|||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(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
|
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
|
$(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)
|
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
|
var deviceID [12]byte
|
||||||
|
|
||||||
// DeviceID returns an identifier that is unique within
|
// 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
|
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
|
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
|
package machine
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ const (
|
|||||||
|
|
||||||
// for PWM
|
// for PWM
|
||||||
PinModePWMOutput PinMode = 12
|
PinModePWMOutput PinMode = 12
|
||||||
|
|
||||||
|
// for USB (DP/DM lines)
|
||||||
|
PinModeUSB PinMode = 13
|
||||||
)
|
)
|
||||||
|
|
||||||
// Define several bitfields that have different names across chip families but
|
// 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)
|
port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos)
|
||||||
p.SetAltFunc(altFunc)
|
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
|
// ADC
|
||||||
case PinInputAnalog:
|
case PinInputAnalog:
|
||||||
port.MODER.ReplaceBits(gpioModeAnalog, gpioModeMask, pos)
|
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
|
package machine
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,25 @@ func (t *TIM) Count() uint32 {
|
|||||||
return uint32(t.Device.CNT.Get())
|
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
|
// SetWraparoundInterrupt configures a callback to be called each
|
||||||
// time the timer 'wraps-around'.
|
// 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
|
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