//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)< 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)<> 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 }