410 строки
14 КиБ
Go
410 строки
14 КиБ
Go
//go:build rp2040
|
|
|
|
package machine
|
|
|
|
import (
|
|
"device/rp"
|
|
"errors"
|
|
"math"
|
|
"runtime/volatile"
|
|
"unsafe"
|
|
)
|
|
|
|
var (
|
|
ErrBadPeriod = errors.New("period outside valid range 8ns..268ms")
|
|
)
|
|
|
|
const (
|
|
maxPWMPins = 29
|
|
)
|
|
|
|
// pwmGroup is one PWM peripheral, which consists of a counter and two output
|
|
// channels. You can set the frequency using SetPeriod,
|
|
// but only for all the channels in this PWM peripheral at once.
|
|
//
|
|
// div: integer value to reduce counting rate by. Must be greater than or equal to 1.
|
|
//
|
|
// cc: counter compare level. Contains 2 channel levels. The 16 LSBs are Channel A's level (Duty Cycle)
|
|
// and the 16 MSBs are Channel B's level.
|
|
//
|
|
// top: Wrap. Highest number counter will reach before wrapping over. usually 0xffff.
|
|
//
|
|
// csr: Clock mode. PWM_CH0_CSR_DIVMODE_xxx registers have 4 possible modes, of which Free-running is used.
|
|
// csr contains output polarity bit at PWM_CH0_CSR_x_INV where x is the channel.
|
|
// csr contains phase correction bit at PWM_CH0_CSR_PH_CORRECT_Msk.
|
|
// csr contains PWM enable bit at PWM_CH0_CSR_EN. If not enabled PWM will not be active.
|
|
//
|
|
// ctr: PWM counter value.
|
|
type pwmGroup struct {
|
|
CSR volatile.Register32
|
|
DIV volatile.Register32
|
|
CTR volatile.Register32
|
|
CC volatile.Register32
|
|
TOP volatile.Register32
|
|
}
|
|
|
|
// Equivalent of
|
|
//
|
|
// var pwmSlice []pwmGroup = (*[8]pwmGroup)(unsafe.Pointer(rp.PWM))[:]
|
|
// return &pwmSlice[index]
|
|
//
|
|
// 0x14 is the size of a pwmGroup.
|
|
func getPWMGroup(index uintptr) *pwmGroup {
|
|
return (*pwmGroup)(unsafe.Add(unsafe.Pointer(rp.PWM), 0x14*index))
|
|
}
|
|
|
|
// Hardware Pulse Width Modulation (PWM) API
|
|
// PWM peripherals available on RP2040. Each peripheral has 2 pins available for
|
|
// a total of 16 available PWM outputs. Some pins may not be available on some boards.
|
|
//
|
|
// The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or
|
|
// measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable
|
|
// PWM outputs. All 30 GPIOs can be driven by the PWM block
|
|
//
|
|
// The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a
|
|
// toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of
|
|
// time spent at the high signal level is known as the duty cycle of the signal.
|
|
//
|
|
// The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then
|
|
// immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after
|
|
// reaching TOP, until it reaches 0 again.
|
|
var (
|
|
PWM0 = getPWMGroup(0)
|
|
PWM1 = getPWMGroup(1)
|
|
PWM2 = getPWMGroup(2)
|
|
PWM3 = getPWMGroup(3)
|
|
PWM4 = getPWMGroup(4)
|
|
PWM5 = getPWMGroup(5)
|
|
PWM6 = getPWMGroup(6)
|
|
PWM7 = getPWMGroup(7)
|
|
)
|
|
|
|
// Configure enables and configures this PWM.
|
|
func (pwm *pwmGroup) Configure(config PWMConfig) error {
|
|
return pwm.init(config, true)
|
|
}
|
|
|
|
// Channel returns a PWM channel for the given pin. If pin does
|
|
// not belong to PWM peripheral ErrInvalidOutputPin error is returned.
|
|
// It also configures pin as PWM output.
|
|
func (pwm *pwmGroup) Channel(pin Pin) (channel uint8, err error) {
|
|
if pin > maxPWMPins || pwmGPIOToSlice(pin) != pwm.peripheral() {
|
|
return 3, ErrInvalidOutputPin
|
|
}
|
|
pin.Configure(PinConfig{PinPWM})
|
|
return pwmGPIOToChannel(pin), nil
|
|
}
|
|
|
|
// Peripheral returns the RP2040 PWM peripheral which ranges from 0 to 7. Each
|
|
// PWM peripheral has 2 channels, A and B which correspond to 0 and 1 in the program.
|
|
// This number corresponds to the package's PWM0 throughout PWM7 handles
|
|
func PWMPeripheral(pin Pin) (sliceNum uint8, err error) {
|
|
if pin > maxPWMPins {
|
|
return 0, ErrInvalidOutputPin
|
|
}
|
|
return pwmGPIOToSlice(pin), nil
|
|
}
|
|
|
|
// returns the number of the pwm peripheral (0-7)
|
|
func (pwm *pwmGroup) peripheral() uint8 {
|
|
return uint8((uintptr(unsafe.Pointer(pwm)) - uintptr(unsafe.Pointer(rp.PWM))) / 0x14)
|
|
}
|
|
|
|
// SetPeriod updates the period of this PWM peripheral in nanoseconds.
|
|
// To set a particular frequency, use the following formula:
|
|
//
|
|
// period = 1e9 / frequency
|
|
//
|
|
// Where frequency is in hertz. If you use a period of 0, a period
|
|
// that works well for LEDs will be picked.
|
|
//
|
|
// SetPeriod will try not to modify TOP if possible to reach the target period.
|
|
// If the period is unattainable with current TOP SetPeriod will modify TOP
|
|
// by the bare minimum to reach the target period. It will also enable phase
|
|
// correct to reach periods above 130ms.
|
|
func (p *pwmGroup) SetPeriod(period uint64) error {
|
|
if period == 0 {
|
|
period = 1e5
|
|
}
|
|
return p.setPeriod(period)
|
|
}
|
|
|
|
// Top returns the current counter top, for use in duty cycle calculation.
|
|
//
|
|
// 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 Set
|
|
// (see Set documentation for more information).
|
|
func (p *pwmGroup) Top() uint32 {
|
|
return p.getWrap()
|
|
}
|
|
|
|
// Counter returns the current counter value of the timer in this PWM
|
|
// peripheral. It may be useful for debugging.
|
|
func (p *pwmGroup) Counter() uint32 {
|
|
return (p.CTR.Get() & rp.PWM_CH0_CTR_CH0_CTR_Msk) >> rp.PWM_CH0_CTR_CH0_CTR_Pos
|
|
}
|
|
|
|
// Period returns the used PWM period in nanoseconds.
|
|
func (p *pwmGroup) Period() uint64 {
|
|
periodPerCycle := cpuPeriod()
|
|
top := p.getWrap()
|
|
phc := p.getPhaseCorrect()
|
|
Int, frac := p.getClockDiv()
|
|
// Line below can overflow if operations done without care.
|
|
return (16*uint64(Int) + uint64(frac)) * uint64((top+1)*(phc+1)*periodPerCycle) / 16 // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
|
|
}
|
|
|
|
// 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 (p *pwmGroup) SetInverting(channel uint8, inverting bool) {
|
|
channel &= 1
|
|
p.setInverting(channel, inverting)
|
|
}
|
|
|
|
// Set updates the channel value. This is used to control the channel duty
|
|
// cycle, in other words the fraction of time the channel output is high (or low
|
|
// when inverted). For example, to set it to a 25% duty cycle, use:
|
|
//
|
|
// pwm.Set(channel, pwm.Top() / 4)
|
|
//
|
|
// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel,
|
|
// pwm.Top()) will set the output to high, assuming the output isn't inverted.
|
|
func (p *pwmGroup) Set(channel uint8, value uint32) {
|
|
val := uint16(value)
|
|
channel &= 1
|
|
p.setChanLevel(channel, val)
|
|
}
|
|
|
|
// Get current level (last set by Set). Default value on initialization is 0.
|
|
func (p *pwmGroup) Get(channel uint8) (value uint32) {
|
|
channel &= 1
|
|
return uint32(p.getChanLevel(channel))
|
|
}
|
|
|
|
// SetTop sets TOP control register. Max value is 16bit (0xffff).
|
|
func (p *pwmGroup) SetTop(top uint32) {
|
|
p.setWrap(uint16(top))
|
|
}
|
|
|
|
// SetCounter sets counter control register. Max value is 16bit (0xffff).
|
|
// Useful for synchronising two different PWM peripherals.
|
|
func (p *pwmGroup) SetCounter(ctr uint32) {
|
|
p.CTR.Set(ctr)
|
|
}
|
|
|
|
// Enable enables or disables PWM peripheral channels.
|
|
func (p *pwmGroup) Enable(enable bool) {
|
|
p.enable(enable)
|
|
}
|
|
|
|
// IsEnabled returns true if peripheral is enabled.
|
|
func (p *pwmGroup) IsEnabled() (enabled bool) {
|
|
return (p.CSR.Get()&rp.PWM_CH0_CSR_EN_Msk)>>rp.PWM_CH0_CSR_EN_Pos != 0
|
|
}
|
|
|
|
// Initialise a PWM with settings from a configuration object.
|
|
// If start is true then PWM starts on initialization.
|
|
func (pwm *pwmGroup) init(config PWMConfig, start bool) error {
|
|
// Not enable Phase correction
|
|
pwm.setPhaseCorrect(false)
|
|
|
|
// Clock mode set by default to Free running
|
|
pwm.setDivMode(rp.PWM_CH0_CSR_DIVMODE_DIV)
|
|
|
|
// Set Output polarity (false/false)
|
|
pwm.setInverting(0, false)
|
|
pwm.setInverting(1, false)
|
|
|
|
// Set wrap. The highest value the counter will reach before returning to zero, also known as TOP.
|
|
pwm.setWrap(0xffff)
|
|
// period is set after TOP (Wrap).
|
|
err := pwm.SetPeriod(config.Period)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// period already set beforea
|
|
// Reset counter and compare (pwm level set to zero)
|
|
pwm.CTR.ReplaceBits(0, rp.PWM_CH0_CTR_CH0_CTR_Msk, 0) // PWM_CH0_CTR_RESET
|
|
pwm.CC.Set(0) // PWM_CH0_CC_RESET
|
|
|
|
pwm.enable(start)
|
|
return nil
|
|
}
|
|
|
|
func (pwm *pwmGroup) setPhaseCorrect(correct bool) {
|
|
pwm.CSR.ReplaceBits(boolToBit(correct)<<rp.PWM_CH0_CSR_PH_CORRECT_Pos, rp.PWM_CH0_CSR_PH_CORRECT_Msk, 0)
|
|
}
|
|
|
|
// Takes any of the following:
|
|
//
|
|
// rp.PWM_CH0_CSR_DIVMODE_DIV, rp.PWM_CH0_CSR_DIVMODE_FALL,
|
|
// rp.PWM_CH0_CSR_DIVMODE_LEVEL, rp.PWM_CH0_CSR_DIVMODE_RISE
|
|
func (pwm *pwmGroup) setDivMode(mode uint32) {
|
|
pwm.CSR.ReplaceBits(mode<<rp.PWM_CH0_CSR_DIVMODE_Pos, rp.PWM_CH0_CSR_DIVMODE_Msk, 0)
|
|
}
|
|
|
|
// setPeriod sets the pwm peripheral period (frequency). Calculates DIV_INT,DIV_FRAC and sets it from following equation:
|
|
//
|
|
// cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
|
|
//
|
|
// where cycles is amount of clock cycles per PWM period.
|
|
func (pwm *pwmGroup) setPeriod(period uint64) error {
|
|
// This period calculation algorithm consists of
|
|
// 1. Calculating best-fit prescale at a slightly lower-than-max TOP value
|
|
// 2. Calculate TOP value to reach target period given the calculated prescale
|
|
// 3. Apply calculated Prescale from step 1 and calculated Top from step 2
|
|
const (
|
|
maxTop = math.MaxUint16
|
|
// start algorithm at 95% Top. This allows us to undershoot period with prescale.
|
|
topStart = 95 * maxTop / 100
|
|
nanosecond = 1 // 1e-9 [s]
|
|
microsecond = 1000 * nanosecond // 1e-6 [s]
|
|
milliseconds = 1000 * microsecond // 1e-3 [s]
|
|
// Maximum Period is 268369920ns on rp2040, given by (16*255+15)*8*(1+0xffff)*(1+1)/16
|
|
// With no phase shift max period is half of this value.
|
|
maxPeriod = 268 * milliseconds
|
|
)
|
|
|
|
if period > maxPeriod || period < 8 {
|
|
return ErrBadPeriod
|
|
}
|
|
if period > maxPeriod/2 {
|
|
pwm.setPhaseCorrect(true) // Must enable Phase correct to reach large periods.
|
|
}
|
|
|
|
// clearing above expression:
|
|
// DIV_INT + DIV_FRAC/16 = cycles / ( (TOP+1) * (CSRPHCorrect+1) ) // DIV_FRAC/16 is always 0 in this equation
|
|
// where cycles must be converted to time:
|
|
// target_period = cycles * period_per_cycle ==> cycles = target_period/period_per_cycle
|
|
periodPerCycle := uint64(cpuPeriod())
|
|
phc := uint64(pwm.getPhaseCorrect())
|
|
rhs := 16 * period / ((1 + phc) * periodPerCycle * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided
|
|
whole := rhs / 16
|
|
frac := rhs % 16
|
|
switch {
|
|
case whole > 0xff:
|
|
whole = 0xff
|
|
case whole == 0:
|
|
// whole calculation underflowed so setting to minimum
|
|
// permissible value in DIV_INT register.
|
|
whole = 1
|
|
frac = 0
|
|
}
|
|
|
|
// Step 2 is acquiring a better top value. Clearing the equation:
|
|
// TOP = cycles / ( (DIVINT+DIVFRAC/16) * (CSRPHCorrect+1) ) - 1
|
|
top := 16*period/((16*whole+frac)*periodPerCycle*(1+phc)) - 1
|
|
if top > maxTop {
|
|
top = maxTop
|
|
}
|
|
pwm.SetTop(uint32(top))
|
|
pwm.setClockDiv(uint8(whole), uint8(frac))
|
|
return nil
|
|
}
|
|
|
|
// Int is integer value to reduce counting rate by. Must be greater than or equal to 1. DIV_INT is bits 4:11 (8 bits).
|
|
// frac's (DIV_FRAC) default value on reset is 0. Max value for frac is 15 (4 bits). This is known as a fixed-point
|
|
// fractional number.
|
|
//
|
|
// cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
|
|
func (pwm *pwmGroup) setClockDiv(Int, frac uint8) {
|
|
pwm.DIV.ReplaceBits((uint32(frac)<<rp.PWM_CH0_DIV_FRAC_Pos)|
|
|
u32max(uint32(Int), 1)<<rp.PWM_CH0_DIV_INT_Pos, rp.PWM_CH0_DIV_FRAC_Msk|rp.PWM_CH0_DIV_INT_Msk, 0)
|
|
}
|
|
|
|
// Set the highest value the counter will reach before returning to 0. Also
|
|
// known as TOP.
|
|
//
|
|
// The counter wrap value is double-buffered in hardware. This means that,
|
|
// when the PWM is running, a write to the counter wrap value does not take
|
|
// effect until after the next time the PWM slice wraps (or, in phase-correct
|
|
// mode, the next time the slice reaches 0). If the PWM is not running, the
|
|
// write is latched in immediately.
|
|
func (pwm *pwmGroup) setWrap(wrap uint16) {
|
|
pwm.TOP.ReplaceBits(uint32(wrap)<<rp.PWM_CH0_TOP_CH0_TOP_Pos, rp.PWM_CH0_TOP_CH0_TOP_Msk, 0)
|
|
}
|
|
|
|
// enables/disables the PWM peripheral with rp.PWM_CH0_CSR_EN bit.
|
|
func (pwm *pwmGroup) enable(enable bool) {
|
|
pwm.CSR.ReplaceBits(boolToBit(enable)<<rp.PWM_CH0_CSR_EN_Pos, rp.PWM_CH0_CSR_EN_Msk, 0)
|
|
}
|
|
|
|
func (pwm *pwmGroup) setInverting(channel uint8, invert bool) {
|
|
var pos uint8
|
|
var msk uint32
|
|
switch channel {
|
|
case 0:
|
|
pos = rp.PWM_CH0_CSR_A_INV_Pos
|
|
msk = rp.PWM_CH0_CSR_A_INV_Msk
|
|
case 1:
|
|
pos = rp.PWM_CH0_CSR_B_INV_Pos
|
|
msk = rp.PWM_CH0_CSR_B_INV_Msk
|
|
}
|
|
pwm.CSR.ReplaceBits(boolToBit(invert)<<pos, msk, 0)
|
|
}
|
|
|
|
// Set the current PWM counter compare value for one channel
|
|
//
|
|
// The counter compare register is double-buffered in hardware. This means
|
|
// that, when the PWM is running, a write to the counter compare values does
|
|
// not take effect until the next time the PWM slice wraps (or, in
|
|
// phase-correct mode, the next time the slice reaches 0). If the PWM is not
|
|
// running, the write is latched in immediately.
|
|
// Channel is 0 for A, 1 for B.
|
|
func (pwm *pwmGroup) setChanLevel(channel uint8, level uint16) {
|
|
var pos uint8
|
|
var mask uint32
|
|
switch channel {
|
|
case 0:
|
|
pos = rp.PWM_CH0_CC_A_Pos
|
|
mask = rp.PWM_CH0_CC_A_Msk
|
|
case 1:
|
|
pos = rp.PWM_CH0_CC_B_Pos
|
|
mask = rp.PWM_CH0_CC_B_Msk
|
|
}
|
|
pwm.CC.ReplaceBits(uint32(level)<<pos, mask, 0)
|
|
}
|
|
|
|
func (pwm *pwmGroup) getChanLevel(channel uint8) (level uint16) {
|
|
var pos uint8
|
|
var mask uint32
|
|
switch channel {
|
|
case 0:
|
|
pos = rp.PWM_CH0_CC_A_Pos
|
|
mask = rp.PWM_CH0_CC_A_Msk
|
|
case 1:
|
|
pos = rp.PWM_CH0_CC_B_Pos
|
|
mask = rp.PWM_CH0_CC_B_Msk
|
|
}
|
|
|
|
level = uint16((pwm.CC.Get() & mask) >> pos)
|
|
return level
|
|
}
|
|
|
|
func (pwm *pwmGroup) getWrap() (top uint32) {
|
|
return (pwm.TOP.Get() & rp.PWM_CH0_TOP_CH0_TOP_Msk) >> rp.PWM_CH0_TOP_CH0_TOP_Pos
|
|
}
|
|
|
|
func (pwm *pwmGroup) getPhaseCorrect() (phCorrect uint32) {
|
|
return (pwm.CSR.Get() & rp.PWM_CH0_CSR_PH_CORRECT_Msk) >> rp.PWM_CH0_CSR_PH_CORRECT_Pos
|
|
}
|
|
|
|
func (pwm *pwmGroup) getClockDiv() (Int, frac uint8) {
|
|
div := pwm.DIV.Get()
|
|
return uint8((div & rp.PWM_CH0_DIV_INT_Msk) >> rp.PWM_CH0_DIV_INT_Pos), uint8((div & rp.PWM_CH0_DIV_FRAC_Msk) >> rp.PWM_CH0_DIV_FRAC_Pos)
|
|
}
|
|
|
|
// pwmGPIOToSlice Determine the PWM channel that is attached to the specified GPIO.
|
|
// gpio must be less than 30. Returns the PWM slice number that controls the specified GPIO.
|
|
func pwmGPIOToSlice(gpio Pin) (slicenum uint8) {
|
|
return (uint8(gpio) >> 1) & 7
|
|
}
|
|
|
|
// Determine the PWM channel that is attached to the specified GPIO.
|
|
// Each slice 0 to 7 has two channels, A and B.
|
|
func pwmGPIOToChannel(gpio Pin) (channel uint8) {
|
|
return uint8(gpio) & 1
|
|
}
|