mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-14 16:03:41 +00:00
stm32: add pwm for f4 series
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
// +build stm32f4
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
type TimerCallback func()
|
||||
type ChannelCallback func(channel uint8)
|
||||
|
||||
type PinFunction struct {
|
||||
Pin Pin
|
||||
AltFunc uint8
|
||||
}
|
||||
|
||||
type TimerChannel struct {
|
||||
Pins []PinFunction
|
||||
}
|
||||
|
||||
type TIM struct {
|
||||
EnableRegister *volatile.Register32
|
||||
EnableFlag uint32
|
||||
Device *stm32.TIM_Type
|
||||
Channels [4]TimerChannel
|
||||
UpInterrupt interrupt.Interrupt
|
||||
OCInterrupt interrupt.Interrupt
|
||||
|
||||
wraparoundCallback TimerCallback
|
||||
channelCallbacks [4]ChannelCallback
|
||||
|
||||
busFreq uint64
|
||||
}
|
||||
|
||||
// Configure enables and configures this PWM.
|
||||
func (t *TIM) Configure(config PWMConfig) error {
|
||||
// Enable device
|
||||
t.EnableRegister.SetBits(t.EnableFlag)
|
||||
|
||||
err := t.setPeriod(config.Period, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Auto-repeat
|
||||
t.Device.EGR.SetBits(stm32.TIM_EGR_UG)
|
||||
|
||||
// Enable the timer
|
||||
t.Device.CR1.SetBits(stm32.TIM_CR1_CEN | stm32.TIM_CR1_ARPE)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TIM) Count() uint32 {
|
||||
return uint32(t.Device.CNT.Get())
|
||||
}
|
||||
|
||||
// SetWraparoundInterrupt configures a callback to be called each
|
||||
// time the timer 'wraps-around'.
|
||||
//
|
||||
// For example, if `Configure(PWMConfig{Period:1000000})` is used,
|
||||
// to set the timer period to 1ms, this callback will be called every
|
||||
// 1ms.
|
||||
func (t *TIM) SetWraparoundInterrupt(callback TimerCallback) error {
|
||||
// Disable this interrupt to prevent race conditions
|
||||
//t.UpInterrupt.Disable()
|
||||
|
||||
// Ensure the interrupt handler for Update events is registered
|
||||
t.UpInterrupt = t.registerUPInterrupt()
|
||||
|
||||
// Clear update flag
|
||||
t.Device.SR.ClearBits(stm32.TIM_SR_UIF)
|
||||
|
||||
t.wraparoundCallback = callback
|
||||
t.UpInterrupt.SetPriority(0xc1)
|
||||
t.UpInterrupt.Enable()
|
||||
|
||||
// Enable the hardware interrupt
|
||||
t.Device.DIER.SetBits(stm32.TIM_DIER_UIE)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets a callback to be called when a channel reaches it's set-point.
|
||||
//
|
||||
// For example, if `t.Set(ch, t.Top() / 4)` is used then the callback will
|
||||
// be called every quarter-period of the timer's base Period.
|
||||
func (t *TIM) SetMatchInterrupt(channel uint8, callback ChannelCallback) error {
|
||||
t.channelCallbacks[channel] = callback
|
||||
|
||||
// Ensure the interrupt handler for Output Compare events is registered
|
||||
t.OCInterrupt = t.registerOCInterrupt()
|
||||
|
||||
// Clear the interrupt flag
|
||||
t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF_Match << channel)
|
||||
|
||||
// Enable the interrupt
|
||||
t.OCInterrupt.SetPriority(0xc1)
|
||||
t.OCInterrupt.Enable()
|
||||
|
||||
// Enable the hardware interrupt
|
||||
t.Device.DIER.SetBits(stm32.TIM_DIER_CC1IE << channel)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPeriod updates the period of this PWM peripheral.
|
||||
// To set a particular frequency, use the following formula:
|
||||
//
|
||||
// period = 1e9 / frequency
|
||||
//
|
||||
// If you use a period of 0, a period that works well for LEDs will be picked.
|
||||
//
|
||||
// SetPeriod will not change the prescaler, but also won't change the current
|
||||
// value in any of the channels. This means that you may need to update the
|
||||
// value for the particular channel.
|
||||
//
|
||||
// Note that you cannot pick any arbitrary period after the PWM peripheral has
|
||||
// been configured. If you want to switch between frequencies, pick the lowest
|
||||
// frequency (longest period) once when calling Configure and adjust the
|
||||
// frequency here as needed.
|
||||
func (t *TIM) SetPeriod(period uint64) error {
|
||||
return t.setPeriod(period, false)
|
||||
}
|
||||
|
||||
func (t *TIM) setPeriod(period uint64, updatePrescaler bool) error {
|
||||
var top uint64
|
||||
if period == 0 {
|
||||
top = ARR_MAX
|
||||
} else {
|
||||
top = (period / 1000) * (t.busFreq / 1000) / 1000
|
||||
}
|
||||
|
||||
var psc uint64
|
||||
if updatePrescaler {
|
||||
if top > ARR_MAX*PSC_MAX {
|
||||
return ErrPWMPeriodTooLong
|
||||
}
|
||||
|
||||
// Select the minimum PSC that scales the ARR value into
|
||||
// range to maintain precision in ARR for changing frequencies
|
||||
// later
|
||||
psc = ceil(top, ARR_MAX)
|
||||
top = top / psc
|
||||
|
||||
t.Device.PSC.Set(uint32(psc - 1))
|
||||
} else {
|
||||
psc = uint64(t.Device.PSC.Get()) + 1
|
||||
top = top / psc
|
||||
|
||||
if top > ARR_MAX {
|
||||
return ErrPWMPeriodTooLong
|
||||
}
|
||||
}
|
||||
|
||||
t.Device.ARR.Set(arrtype(top - 1))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Top returns the current counter top, for use in duty cycle calculation. It
|
||||
// will only change with a call to Configure or SetPeriod, otherwise it is
|
||||
// constant.
|
||||
//
|
||||
// The value returned here is hardware dependent. In general, it's best to treat
|
||||
// it as an opaque value that can be divided by some number and passed to
|
||||
// pwm.Set (see pwm.Set for more information).
|
||||
func (t *TIM) Top() uint32 {
|
||||
return uint32(t.Device.ARR.Get()) + 1
|
||||
}
|
||||
|
||||
// Channel returns a PWM channel for the given pin.
|
||||
func (t *TIM) Channel(pin Pin) (uint8, error) {
|
||||
|
||||
for chi, ch := range t.Channels {
|
||||
for _, p := range ch.Pins {
|
||||
if p.Pin == pin {
|
||||
p.Pin.ConfigureAltFunc(PinConfig{Mode: PinModePWMOutput}, p.AltFunc)
|
||||
return uint8(chi), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, ErrInvalidOutputPin
|
||||
}
|
||||
|
||||
// Set updates the channel value. This is used to control the channel duty
|
||||
// cycle. For example, to set it to a 25% duty cycle, use:
|
||||
//
|
||||
// t.Set(ch, t.Top() / 4)
|
||||
//
|
||||
// ch.Set(0) will set the output to low and ch.Set(ch.Top()) will set the output
|
||||
// to high, assuming the output isn't inverted.
|
||||
func (t *TIM) Set(channel uint8, value uint32) {
|
||||
t.enableMainOutput()
|
||||
|
||||
ccr := t.channelCCR(channel)
|
||||
ccmr, offset := t.channelCCMR(channel)
|
||||
|
||||
// Disable interrupts whilst programming to prevent spurious OC interrupts
|
||||
mask := interrupt.Disable()
|
||||
|
||||
// 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
|
||||
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)
|
||||
|
||||
// Force update
|
||||
t.Device.EGR.SetBits(stm32.TIM_EGR_CC1G << channel)
|
||||
|
||||
// Reset Interrupt Flag
|
||||
t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel)
|
||||
|
||||
// Restore interrupts
|
||||
interrupt.Restore(mask)
|
||||
}
|
||||
|
||||
// Unset disables a channel, including any configured interrupts.
|
||||
func (t *TIM) Unset(channel uint8) {
|
||||
// Disable interrupts whilst programming to prevent spurious OC interrupts
|
||||
mask := interrupt.Disable()
|
||||
|
||||
// Disable the channel
|
||||
t.Device.CCER.ReplaceBits(0, 0xD, channel*4)
|
||||
|
||||
// Reset to zero value
|
||||
ccr := t.channelCCR(channel)
|
||||
ccr.Set(0)
|
||||
|
||||
// Disable the hardware interrupt
|
||||
t.Device.DIER.ClearBits(stm32.TIM_DIER_CC1IE << channel)
|
||||
|
||||
// Clear the interrupt flag
|
||||
t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel)
|
||||
|
||||
// Restore interrupts
|
||||
interrupt.Restore(mask)
|
||||
}
|
||||
|
||||
// SetInverting sets whether to invert the output of this channel.
|
||||
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
|
||||
// the time and low for the rest. Inverting flips the output as if a NOT gate
|
||||
// was placed at the output, meaning that the output would be 25% low and 75%
|
||||
// high with a duty cycle of 25%.
|
||||
func (t *TIM) SetInverting(channel uint8, inverting bool) {
|
||||
// Enable the channel (if not already)
|
||||
|
||||
var val = uint32(0)
|
||||
if inverting {
|
||||
val |= stm32.TIM_CCER_CC1P
|
||||
}
|
||||
|
||||
t.Device.CCER.ReplaceBits(val, stm32.TIM_CCER_CC1P_Msk, channel*4)
|
||||
}
|
||||
|
||||
func (t *TIM) handleUPInterrupt(interrupt.Interrupt) {
|
||||
if t.Device.SR.HasBits(stm32.TIM_SR_UIF) {
|
||||
// clear the update flag
|
||||
t.Device.SR.ClearBits(stm32.TIM_SR_UIF)
|
||||
|
||||
if t.wraparoundCallback != nil {
|
||||
t.wraparoundCallback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TIM) handleOCInterrupt(interrupt.Interrupt) {
|
||||
if t.Device.SR.HasBits(stm32.TIM_SR_CC1IF) {
|
||||
if t.channelCallbacks[0] != nil {
|
||||
t.channelCallbacks[0](0)
|
||||
}
|
||||
}
|
||||
if t.Device.SR.HasBits(stm32.TIM_SR_CC2IF) {
|
||||
if t.channelCallbacks[1] != nil {
|
||||
t.channelCallbacks[1](1)
|
||||
}
|
||||
}
|
||||
if t.Device.SR.HasBits(stm32.TIM_SR_CC3IF) {
|
||||
if t.channelCallbacks[2] != nil {
|
||||
t.channelCallbacks[2](2)
|
||||
}
|
||||
}
|
||||
if t.Device.SR.HasBits(stm32.TIM_SR_CC4IF) {
|
||||
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)
|
||||
}
|
||||
|
||||
func (t *TIM) channelCCR(channel uint8) *arrRegType {
|
||||
switch channel {
|
||||
case 0:
|
||||
return &t.Device.CCR1
|
||||
case 1:
|
||||
return &t.Device.CCR2
|
||||
case 2:
|
||||
return &t.Device.CCR3
|
||||
case 3:
|
||||
return &t.Device.CCR4
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TIM) channelCCMR(channel uint8) (reg *volatile.Register32, offset uint8) {
|
||||
switch channel {
|
||||
case 0:
|
||||
return &t.Device.CCMR1_Output, 0
|
||||
case 1:
|
||||
return &t.Device.CCMR1_Output, 8
|
||||
case 2:
|
||||
return &t.Device.CCMR2_Output, 0
|
||||
case 3:
|
||||
return &t.Device.CCMR2_Output, 8
|
||||
}
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ceil(num uint64, denom uint64) uint64 {
|
||||
return (num + denom - 1) / denom
|
||||
}
|
||||
Reference in New Issue
Block a user