mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-05 19:43:44 +00:00
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:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user