From d669d021de5d86e02e68efc58df050eeffbd87fd Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 18 Mar 2026 21:32:47 +0100 Subject: [PATCH 1/2] machine/stm32: fix PWM problem due to register shifting. The SVD-generated TIM constants in the STM32 device files have all per-channel fields shifted by one channel position (e.g., CC1E is at the hardware position of CC2E, OC1M_Pos is at the OC2M hardware position). This caused Set(), Unset(), SetInverting(), and interrupt handlers to write to wrong bit positions. Signed-off-by: deadprogram --- src/machine/machine_stm32_tim.go | 47 ++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/machine/machine_stm32_tim.go b/src/machine/machine_stm32_tim.go index da11b44e1f..ddb975a814 100644 --- a/src/machine/machine_stm32_tim.go +++ b/src/machine/machine_stm32_tim.go @@ -13,6 +13,23 @@ import ( const PWM_MODE1 = 0x6 +// Hardware bit positions for TIM registers. The SVD-generated constants +// (stm32.TIM_CCMR1_Output_OC1M_Pos, stm32.TIM_CCER_CC1E, etc.) are +// systematically shifted by one channel in some STM32 device files, +// so we define the correct hardware positions here. +const ( + // OC1M[2:0] field position within each CCMR half (bits [6:4]). + tim_CCMR_OCxM_Pos = 4 + + // Per-channel bit positions within CCER (4 bits per channel). + tim_CCER_CCxE = 0x1 // CCxE at bit 0 of channel group + tim_CCER_CCxP = 0x2 // CCxP at bit 1 of channel group + + // Per-channel bit positions within SR, EGR, and DIER (1 bit per channel, + // starting at bit 1 for CC1). Use as: tim_CC1_bit << channel. + tim_CC1_bit = 0x2 // CC1G/CC1IF/CC1IE at bit 1 +) + type TimerCallback func() type ChannelCallback func(channel uint8) @@ -99,14 +116,14 @@ func (t *TIM) SetMatchInterrupt(channel uint8, callback ChannelCallback) error { t.OCInterrupt = t.registerOCInterrupt() // Clear the interrupt flag - t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel) + t.Device.SR.ClearBits(tim_CC1_bit << channel) // Enable the interrupt t.OCInterrupt.SetPriority(0xc1) t.OCInterrupt.Enable() // Enable the hardware interrupt - t.Device.DIER.SetBits(stm32.TIM_DIER_CC1IE << channel) + t.Device.DIER.SetBits(tim_CC1_bit << channel) return nil } @@ -210,20 +227,20 @@ func (t *TIM) Set(channel uint8, value uint32) { // Set the PWM to Mode 1 (active below set value, inactive above) // Preload is disabled so we can change OC value within one update period. var ccmrVal uint32 - ccmrVal |= PWM_MODE1 << stm32.TIM_CCMR1_Output_OC1M_Pos + ccmrVal |= PWM_MODE1 << tim_CCMR_OCxM_Pos ccmr.ReplaceBits(ccmrVal, 0xFF, offset) // Set the compare value ccr.Set(arrtype(value)) // Enable the channel (if not already) - t.Device.CCER.ReplaceBits(stm32.TIM_CCER_CC1E, 0xD, channel*4) + t.Device.CCER.ReplaceBits(tim_CCER_CCxE, 0xD, channel*4) // Force update - t.Device.EGR.SetBits(stm32.TIM_EGR_CC1G << channel) + t.Device.EGR.SetBits(tim_CC1_bit << channel) // Reset Interrupt Flag - t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel) + t.Device.SR.ClearBits(tim_CC1_bit << channel) // Restore interrupts interrupt.Restore(mask) @@ -242,10 +259,10 @@ func (t *TIM) Unset(channel uint8) { ccr.Set(0) // Disable the hardware interrupt - t.Device.DIER.ClearBits(stm32.TIM_DIER_CC1IE << channel) + t.Device.DIER.ClearBits(tim_CC1_bit << channel) // Clear the interrupt flag - t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel) + t.Device.SR.ClearBits(tim_CC1_bit << channel) // Restore interrupts interrupt.Restore(mask) @@ -261,10 +278,10 @@ func (t *TIM) SetInverting(channel uint8, inverting bool) { var val = uint32(0) if inverting { - val |= stm32.TIM_CCER_CC1P + val |= tim_CCER_CCxP } - t.Device.CCER.ReplaceBits(val, stm32.TIM_CCER_CC1P_Msk, channel*4) + t.Device.CCER.ReplaceBits(val, tim_CCER_CCxP, channel*4) } func (t *TIM) handleUPInterrupt(interrupt.Interrupt) { @@ -279,29 +296,29 @@ func (t *TIM) handleUPInterrupt(interrupt.Interrupt) { } func (t *TIM) handleOCInterrupt(interrupt.Interrupt) { - if t.Device.SR.HasBits(stm32.TIM_SR_CC1IF) { + if t.Device.SR.HasBits(tim_CC1_bit << 0) { if t.channelCallbacks[0] != nil { t.channelCallbacks[0](0) } } - if t.Device.SR.HasBits(stm32.TIM_SR_CC2IF) { + if t.Device.SR.HasBits(tim_CC1_bit << 1) { if t.channelCallbacks[1] != nil { t.channelCallbacks[1](1) } } - if t.Device.SR.HasBits(stm32.TIM_SR_CC3IF) { + if t.Device.SR.HasBits(tim_CC1_bit << 2) { if t.channelCallbacks[2] != nil { t.channelCallbacks[2](2) } } - if t.Device.SR.HasBits(stm32.TIM_SR_CC4IF) { + if t.Device.SR.HasBits(tim_CC1_bit << 3) { if t.channelCallbacks[3] != nil { t.channelCallbacks[3](3) } } // Reset interrupt flags - t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF | stm32.TIM_SR_CC2IF | stm32.TIM_SR_CC3IF | stm32.TIM_SR_CC4IF) + t.Device.SR.ClearBits(tim_CC1_bit<<0 | tim_CC1_bit<<1 | tim_CC1_bit<<2 | tim_CC1_bit<<3) } func (t *TIM) channelCCR(channel uint8) *arrRegType { From f6ac80774ec887ed39fd8561d5e2f751965ce82d Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 18 Mar 2026 21:35:07 +0100 Subject: [PATCH 2/2] target: add PWM constant values for Arduino UNO Q Signed-off-by: deadprogram --- src/examples/pwm/arduino-uno-q.go | 11 +++++++++++ src/machine/board_arduino_uno_q.go | 26 +++++++++++++------------- 2 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 src/examples/pwm/arduino-uno-q.go diff --git a/src/examples/pwm/arduino-uno-q.go b/src/examples/pwm/arduino-uno-q.go new file mode 100644 index 0000000000..e0c2c53a52 --- /dev/null +++ b/src/examples/pwm/arduino-uno-q.go @@ -0,0 +1,11 @@ +//go:build arduino_uno_q + +package main + +import "machine" + +var ( + pwm = &machine.TIM3 + pinA = machine.D3 // PB0 = TIM3_CH3 + pinB = machine.D6 // PB1 = TIM3_CH4 +) diff --git a/src/machine/board_arduino_uno_q.go b/src/machine/board_arduino_uno_q.go index 9e8996d9f4..9c2296bc89 100644 --- a/src/machine/board_arduino_uno_q.go +++ b/src/machine/board_arduino_uno_q.go @@ -27,24 +27,24 @@ const ( ADC4 = A4 ADC5 = A5 - D0 = PB7 - D1 = PB6 - D2 = PB3 - D3 = PB0 + D0 = PB7 // USART1 RX, PWM TIM4_CH2 + D1 = PB6 // USART1 TX, PWM TIM4_CH1 + D2 = PB3 // PWM TIM2_CH2 + D3 = PB0 // PWM TIM3_CH3 D4 = PA12 - D5 = PA11 - D6 = PB1 + D5 = PA11 // PWM TIM1_CH4 + D6 = PB1 // PWM TIM3_CH4 D7 = PB2 - D8 = PB4 - D9 = PB8 - D10 = PB9 - D11 = PB15 - D12 = PB14 + D8 = PB4 // PWM TIM3_CH1 + D9 = PB8 // PWM TIM4_CH3 / TIM16_CH1 + D10 = PB9 // PWM TIM4_CH4 / TIM17_CH1 + D11 = PB15 // PWM TIM15_CH2 + D12 = PB14 // PWM TIM15_CH1 D13 = PB13 D18 = PC1 D19 = PC0 - D20 = PB10 - D21 = PB11 + D20 = PB10 // I2C2 SCL, PWM TIM2_CH3 + D21 = PB11 // I2C2 SDA, PWM TIM2_CH4 ) const (