diff --git a/src/machine/machine_stm32_adc_g0.go b/src/machine/machine_stm32_adc_g0.go new file mode 100644 index 0000000000..d8cca3c8df --- /dev/null +++ b/src/machine/machine_stm32_adc_g0.go @@ -0,0 +1,188 @@ +//go:build stm32g0 + +package machine + +import ( + "device/stm32" + "unsafe" +) + +// ADC sampling time constants for STM32G0 +const ( + ADC_SMPR_1_5 = 0x0 // 1.5 ADC clock cycles + ADC_SMPR_3_5 = 0x1 // 3.5 ADC clock cycles + ADC_SMPR_7_5 = 0x2 // 7.5 ADC clock cycles + ADC_SMPR_12_5 = 0x3 // 12.5 ADC clock cycles + ADC_SMPR_19_5 = 0x4 // 19.5 ADC clock cycles + ADC_SMPR_39_5 = 0x5 // 39.5 ADC clock cycles + ADC_SMPR_79_5 = 0x6 // 79.5 ADC clock cycles + ADC_SMPR_160_5 = 0x7 // 160.5 ADC clock cycles +) + +// InitADC initializes the registers needed for ADC. +func InitADC() { + // Enable ADC clock + enableAltFuncClock(unsafe.Pointer(stm32.ADC)) + + // Ensure ADC is disabled before configuration + if stm32.ADC.GetCR_ADEN() != 0 { + // Clear ADEN by setting ADDIS + stm32.ADC.SetCR_ADDIS(1) + // Wait for ADC to be disabled + for stm32.ADC.GetCR_ADEN() != 0 { + } + } + + // Enable ADC voltage regulator + stm32.ADC.SetCR_ADVREGEN(1) + + // Wait for ADC voltage regulator startup time (20us at max) + // Using simple busy loop - approximately 1280 cycles at 64MHz = 20us + for i := 0; i < 1280; i++ { + // nop + } + + // Configure ADC: + // - 12-bit resolution (RES = 0b00) + // - Right alignment (ALIGN = 0) + // - Single conversion mode (CONT = 0) + // - Software trigger (EXTEN = 0b00) + stm32.ADC.CFGR1.Set(0) + + // Set clock mode to synchronous with PCLK/2 + stm32.ADC.SetCFGR2_CKMODE(0x1) // PCLK/2 + + // Set sample time to 12.5 cycles for all channels using SMP1 + stm32.ADC.SetSMPR_SMP1(ADC_SMPR_12_5) + + // Calibrate ADC + stm32.ADC.SetCR_ADCAL(1) + for stm32.ADC.GetCR_ADCAL() != 0 { + } + + // Clear ADRDY by writing 1 + stm32.ADC.SetISR_ADRDY(1) + + // Enable ADC + stm32.ADC.SetCR_ADEN(1) + + // Wait until ADC is ready + for stm32.ADC.GetISR_ADRDY() == 0 { + } +} + +// Configure configures an ADC pin to be able to read analog data. +func (a ADC) Configure(config ADCConfig) { + // Configure pin as analog input + a.Pin.Configure(PinConfig{Mode: PinInputAnalog}) + + // Set sampling time based on config + // Use SMP2 and set SMPSEL bit for this channel to select SMP2 + ch := a.getChannel() + if ch <= 18 { + // Select sampling time based on config (using SMP2 for per-channel control) + // Map microseconds to sample cycles (at ~32MHz ADC clock after /2 prescaler) + // Each cycle = 1/32MHz = 31.25ns + var smpTime int + switch { + case config.SampleTime == 0: + smpTime = ADC_SMPR_79_5 // Default to 79.5 cycles for good accuracy + case config.SampleTime <= 1: + smpTime = ADC_SMPR_1_5 + case config.SampleTime <= 2: + smpTime = ADC_SMPR_3_5 + case config.SampleTime <= 3: + smpTime = ADC_SMPR_7_5 + case config.SampleTime <= 4: + smpTime = ADC_SMPR_12_5 + case config.SampleTime <= 5: + smpTime = ADC_SMPR_19_5 + case config.SampleTime <= 10: + smpTime = ADC_SMPR_39_5 + case config.SampleTime <= 20: + smpTime = ADC_SMPR_79_5 + default: + smpTime = ADC_SMPR_160_5 + } + stm32.ADC.SetSMPR_SMP2(uint32(smpTime)) + + // Set SMPSEL bit for this channel to use SMP2 + stm32.ADC.SMPR.SetBits(1 << (8 + ch)) + } +} + +// Get returns the current value of a ADC pin in the range 0..0xffff. +func (a ADC) Get() uint16 { + ch := a.getChannel() + + // Wait until channel configuration is ready if needed + // (CCRDY indicates when CHSELR changes are applied) + for stm32.ADC.GetISR_CCRDY() != 0 { + stm32.ADC.SetISR_CCRDY(1) // Clear by writing 1 + } + + // Select the channel to convert using CHSELR + // CHSELR uses a bitfield where bit N = 1 enables channel N + stm32.ADC.CHSELR.Set(1 << ch) + + // Wait for channel configuration ready + for stm32.ADC.GetISR_CCRDY() == 0 { + } + stm32.ADC.SetISR_CCRDY(1) // Clear flag + + // Start conversion + stm32.ADC.SetCR_ADSTART(1) + + // Wait for end of conversion + for stm32.ADC.GetISR_EOC() == 0 { + } + + // Read the 12-bit result and scale to 16-bit + result := uint16(stm32.ADC.GetDR_DATA()) << 4 + + return result +} + +// getChannel returns the ADC channel number for a given pin. +// STM32G0B1 ADC channel mapping: +// PA0-PA7: CH0-CH7 +// PB0-PB2: CH8-CH10 +// PB10-PB12: CH11-CH13 (some variants) +// PC4-PC5: CH17-CH18 (some variants) +func (a ADC) getChannel() uint8 { + switch a.Pin { + case PA0: + return 0 + case PA1: + return 1 + case PA2: + return 2 + case PA3: + return 3 + case PA4: + return 4 + case PA5: + return 5 + case PA6: + return 6 + case PA7: + return 7 + case PB0: + return 8 + case PB1: + return 9 + case PB2: + return 10 + case PB10: + return 11 + case PB11: + return 12 + case PB12: + return 13 + case PC4: + return 17 + case PC5: + return 18 + } + return 0 +} diff --git a/src/machine/machine_stm32g0_spi.go b/src/machine/machine_stm32g0_spi.go index 7d0dcf87e8..ab871c8990 100644 --- a/src/machine/machine_stm32g0_spi.go +++ b/src/machine/machine_stm32g0_spi.go @@ -6,6 +6,7 @@ package machine import ( "device/stm32" + "runtime/volatile" "unsafe" ) @@ -21,9 +22,20 @@ type SPIConfig struct { // Configure is intended to setup the STM32 SPI peripheral 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 - use defaults if not specified + 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) + // Get SPI baud rate divisor conf := spi.getBaudRate(config) @@ -45,28 +57,30 @@ func (spi *SPI) Configure(config SPIConfig) error { // set SPI master conf |= stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI - // enable the SPI interface - conf |= stm32.SPI_CR1_SPE - // use software CS (GPIO) by default conf |= stm32.SPI_CR1_SSM - // now set the configuration (note: STM32G0 uses 16-bit SPI registers) + // Set CR1 configuration WITHOUT enabling SPE yet + // (STM32G0 requires CR2 DS bits to be set before SPE is enabled) spi.Bus.CR1.Set(uint16(conf)) - // Series-specific configuration to set 8-bit transfer mode + // Series-specific configuration to set 8-bit transfer mode (must be done before SPE) spi.config8Bits() - // enable SPI - spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE) + // Now enable SPI + spi.Bus.SetCR1_SPE(1) return nil } // Transfer writes/reads a single byte using the SPI interface. func (spi *SPI) Transfer(w byte) (byte, error) { - // Write data to be transmitted to the SPI data register - spi.Bus.DR.Set(uint16(w)) + // STM32G0 requires 8-bit access to DR for 8-bit transfers + // Using 16-bit access causes data packing issues + dr := (*volatile.Register8)(unsafe.Pointer(&spi.Bus.DR)) + + // Write data to be transmitted to the SPI data register (8-bit access) + dr.Set(w) // Wait until transmit complete for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) { @@ -80,6 +94,6 @@ func (spi *SPI) Transfer(w byte) (byte, error) { for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) { } - // Return received data from SPI data register - return byte(spi.Bus.DR.Get()), nil + // Return received data from SPI data register (8-bit access) + return dr.Get(), nil } diff --git a/src/machine/machine_stm32g0_wwdg.go b/src/machine/machine_stm32g0_wwdg.go new file mode 100644 index 0000000000..8a5f46c375 --- /dev/null +++ b/src/machine/machine_stm32g0_wwdg.go @@ -0,0 +1,173 @@ +//go:build stm32g0 + +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 64MHz: ~64µs to ~524ms +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 +// Refer RM0444 Rev 6 861/1384 +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 := CPUFrequency() // Assuming PCLK = CPU frequency (no APB prescaler) + baseTick := (4096 * 1000000) / pclk // Base tick in nanoseconds * 1000 for precision + + timeout := config.TimeoutMicros + if timeout == 0 { + timeout = 10000 // Default 10ms + } + + // Find the best prescaler and counter-combination + var bestPrescaler uint8 + var bestCounter uint8 + found := false + + for prescaler := uint8(0); prescaler <= 7; prescaler++ { + // Tick duration in nanoseconds * 1000 + tickNs := baseTick << prescaler + + // Counter value needed (counter - 0x3F = timeout / tick) + // Rearranged: counter = (timeout * 1000 / tickNs) + 0x3F + counterVal := (uint32(timeout) * 1000000 / 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.Set(0) // Write 0 to clear EWIF +} + +// 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 +// At 64MHz: ~524ms = 524288µs +func (wd *windowWatchdogImpl) GetMaxTimeout() uint32 { + pclk := uint64(CPUFrequency()) + 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 +// At 64MHz: ~64µs +func (wd *windowWatchdogImpl) GetMinTimeout() uint32 { + pclk := uint64(CPUFrequency()) + return uint32((uint64(4096) * 1000000) / pclk) +}