STM32G0B1 Watchdog + ADC Support (#5158)

* - Fix SPI initialization by disabling interface before configuration.
- Ensure proper 8-bit configuration for STM32G0 SPI data register access.
- Add default pin initialization for SPI.

* Add Window Watchdog (WWDG) and ADC support for STM32G0

* Comment cleanup

* Refactored STM32G0 ADC sampling time configuration to use `switch` for improved readability
This commit is contained in:
Amken USA
2026-01-08 08:52:14 -05:00
committed by GitHub
parent 72f667cac9
commit 53c1ccaa42
3 changed files with 386 additions and 11 deletions
+188
View File
@@ -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
}
+25 -11
View File
@@ -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
}
+173
View File
@@ -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)
}