1+ //go:build rp2040
12// +build rp2040
23
34package machine
45
56import (
67 "device/rp"
78 "errors"
9+ "math"
810 "runtime/volatile"
911 "unsafe"
1012)
1113
1214var (
13- ErrPeriodTooBig = errors .New ("period outside valid range 1..4e9ns " )
15+ ErrBadPeriod = errors .New ("period outside valid range 8ns..268ms " )
1416)
1517
1618const (
@@ -50,8 +52,21 @@ func getPWMGroup(index uintptr) *pwmGroup {
5052 return (* pwmGroup )(unsafe .Pointer (uintptr (unsafe .Pointer (rp .PWM )) + 0x14 * index ))
5153}
5254
55+ // Hardware Pulse Width Modulation (PWM) API
5356// PWM peripherals available on RP2040. Each peripheral has 2 pins available for
5457// a total of 16 available PWM outputs. Some pins may not be available on some boards.
58+ //
59+ // The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or
60+ // measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable
61+ // PWM outputs. All 30 GPIOs can be driven by the PWM block
62+ //
63+ // The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a
64+ // toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of
65+ // time spent at the high signal level is known as the duty cycle of the signal.
66+ //
67+ // The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then
68+ // immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after
69+ // reaching TOP, until it reaches 0 again.
5570var (
5671 PWM0 = getPWMGroup (0 )
5772 PWM1 = getPWMGroup (1 )
@@ -94,30 +109,23 @@ func (pwm *pwmGroup) peripheral() uint8 {
94109 return uint8 ((uintptr (unsafe .Pointer (pwm )) - uintptr (unsafe .Pointer (rp .PWM ))) / 0x14 )
95110}
96111
97- // SetPeriod updates the period of this PWM peripheral.
112+ // SetPeriod updates the period of this PWM peripheral in nanoseconds .
98113// To set a particular frequency, use the following formula:
99114//
100115// period = 1e9 / frequency
101116//
102- // If you use a period of 0, a period that works well for LEDs will be picked.
103- //
104- // SetPeriod will not change the prescaler, but also won't change the current
105- // value in any of the channels. This means that you may need to update the
106- // value for the particular channel.
117+ // Where frequency is in hertz. If you use a period of 0, a period
118+ // that works well for LEDs will be picked.
107119//
108- // Note that you cannot pick any arbitrary period after the PWM peripheral has
109- // been configured. If you want to switch between frequencies, pick the lowest
110- // frequency (longest period) once when calling Configure and adjust the
111- // frequency here as needed .
120+ // SetPeriod will try not to modify TOP if possible to reach the target period.
121+ // If the period is unattainable with current TOP SetPeriod will modify TOP
122+ // by the bare minimum to reach the target period. It will also enable phase
123+ // correct to reach periods above 130ms .
112124func (p * pwmGroup ) SetPeriod (period uint64 ) error {
113- if period > 0xffff_ffff {
114- return ErrPeriodTooBig
115- }
116125 if period == 0 {
117126 period = 1e5
118127 }
119- p .setPeriod (period )
120- return nil
128+ return p .setPeriod (period )
121129}
122130
123131// Top returns the current counter top, for use in duty cycle calculation.
@@ -135,14 +143,14 @@ func (p *pwmGroup) Counter() uint32 {
135143 return (p .CTR .Get () & rp .PWM_CH0_CTR_CH0_CTR_Msk ) >> rp .PWM_CH0_CTR_CH0_CTR_Pos
136144}
137145
138- // Period returns the used PWM period in nanoseconds. It might deviate slightly
139- // from the configured period due to rounding.
146+ // Period returns the used PWM period in nanoseconds.
140147func (p * pwmGroup ) Period () uint64 {
141- periodPerCycle := getPeriod ()
148+ periodPerCycle := cpuPeriod ()
142149 top := p .getWrap ()
143150 phc := p .getPhaseCorrect ()
144151 Int , frac := p .getClockDiv ()
145- return uint64 ((Int + frac / 16 ) * (top + 1 ) * (phc + 1 ) * periodPerCycle ) // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
152+ // Line below can overflow if operations done without care.
153+ return (16 * uint64 (Int ) + uint64 (frac )) * uint64 ((top + 1 )* (phc + 1 )* periodPerCycle ) / 16 // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
146154}
147155
148156// SetInverting sets whether to invert the output of this channel.
@@ -180,6 +188,12 @@ func (p *pwmGroup) SetTop(top uint32) {
180188 p .setWrap (uint16 (top ))
181189}
182190
191+ // SetCounter sets counter control register. Max value is 16bit (0xffff).
192+ // Useful for synchronising two different PWM peripherals.
193+ func (p * pwmGroup ) SetCounter (ctr uint32 ) {
194+ p .CTR .Set (ctr )
195+ }
196+
183197// Enable enables or disables PWM peripheral channels.
184198func (p * pwmGroup ) Enable (enable bool ) {
185199 p .enable (enable )
@@ -190,29 +204,6 @@ func (p *pwmGroup) IsEnabled() (enabled bool) {
190204 return (p .CSR .Get ()& rp .PWM_CH0_CSR_EN_Msk )>> rp .PWM_CH0_CSR_EN_Pos != 0
191205}
192206
193- // Hardware Pulse Width Modulation (PWM) API
194- //
195- // The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or
196- // measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable
197- // PWM outputs. All 30 GPIOs can be driven by the PWM block
198- //
199- // The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a
200- // toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of
201- // time spent at the high signal level is known as the duty cycle of the signal.
202- //
203- // The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then
204- // immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after
205- // reaching TOP, until it reaches 0 again.
206- type pwms struct {
207- slice pwmGroup
208- hw * rp.PWM_Type
209- }
210-
211- // Handle to all pwm peripheral registers.
212- var _PWM = pwms {
213- hw : rp .PWM ,
214- }
215-
216207// Initialise a PWM with settings from a configuration object.
217208// If start is true then PWM starts on initialization.
218209func (pwm * pwmGroup ) init (config PWMConfig , start bool ) error {
@@ -253,24 +244,53 @@ func (pwm *pwmGroup) setDivMode(mode uint32) {
253244 pwm .CSR .ReplaceBits (mode << rp .PWM_CH0_CSR_DIVMODE_Pos , rp .PWM_CH0_CSR_DIVMODE_Msk , 0 )
254245}
255246
256- // setPeriod sets the pwm peripheral period (frequency). Calculates DIV_INT and sets it from following equation:
247+ // setPeriod sets the pwm peripheral period (frequency). Calculates DIV_INT,DIV_FRAC and sets it from following equation:
257248// cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
258249// where cycles is amount of clock cycles per PWM period.
259- func (pwm * pwmGroup ) setPeriod (period uint64 ) {
260- targetPeriod := uint32 (period )
261- periodPerCycle := getPeriod ()
262- top := pwm .getWrap ()
263- phc := pwm .getPhaseCorrect ()
264- _ , frac := pwm .getClockDiv ()
250+ func (pwm * pwmGroup ) setPeriod (period uint64 ) error {
251+ // This period calculation algorithm consists of
252+ // 1. Calculating best-fit prescale at a slightly lower-than-max TOP value
253+ // 2. Calculate TOP value to reach target period given the calculated prescale
254+ // 3. Apply calculated Prescale from step 1 and calculated Top from step 2
255+ const (
256+ maxTop = math .MaxUint16
257+ // start algorithm at 95% Top. This allows us to undershoot period with prescale.
258+ topStart = 95 * maxTop / 100
259+ milliseconds = 1_000_000_000
260+ // Maximum Period is 268369920ns on rp2040, given by (16*255+15)*8*(1+0xffff)*(1+1)/16
261+ // With no phase shift max period is half of this value.
262+ maxPeriod = 268 * milliseconds
263+ )
264+
265+ if period > maxPeriod || period < 8 {
266+ return ErrBadPeriod
267+ }
268+ if period > maxPeriod / 2 {
269+ pwm .setPhaseCorrect (true ) // Must enable Phase correct to reach large periods.
270+ }
271+
265272 // clearing above expression:
266- // DIV_INT = cycles / ( (TOP+1) * (CSRPHCorrect+1) ) - DIV_FRAC/16
273+ // DIV_INT + DIV_FRAC/16 = cycles / ( (TOP+1) * (CSRPHCorrect+1) ) // DIV_FRAC/16 is always 0 in this equation
267274 // where cycles must be converted to time:
268275 // target_period = cycles * period_per_cycle ==> cycles = target_period/period_per_cycle
269- Int := targetPeriod / ((1 + phc )* periodPerCycle * (1 + top )) - frac / 16
270- if Int > 0xff {
271- Int = 0xff
276+ periodPerCycle := uint64 (cpuPeriod ())
277+ phc := uint64 (pwm .getPhaseCorrect ())
278+ rhs := 16 * period / ((1 + phc ) * periodPerCycle * (1 + topStart )) // right-hand-side of equation, scaled so frac is not divided
279+ whole := rhs / 16
280+ frac := rhs % 16
281+ if whole > 0xff {
282+ whole = 0xff
272283 }
273- pwm .setClockDiv (uint8 (Int ), 0 )
284+
285+ // Step 2 is acquiring a better top value. Clearing the equation:
286+ // TOP = cycles / ( (DIVINT+DIVFRAC/16) * (CSRPHCorrect+1) ) - 1
287+ top := 16 * period / ((16 * whole + frac )* periodPerCycle * (1 + phc )) - 1
288+ if top > maxTop {
289+ top = maxTop
290+ }
291+ pwm .SetTop (uint32 (top ))
292+ pwm .setClockDiv (uint8 (whole ), uint8 (frac ))
293+ return nil
274294}
275295
276296// 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).
@@ -360,9 +380,9 @@ func (pwm *pwmGroup) getPhaseCorrect() (phCorrect uint32) {
360380 return (pwm .CSR .Get () & rp .PWM_CH0_CSR_PH_CORRECT_Msk ) >> rp .PWM_CH0_CSR_PH_CORRECT_Pos
361381}
362382
363- func (pwm * pwmGroup ) getClockDiv () (Int , frac uint32 ) {
383+ func (pwm * pwmGroup ) getClockDiv () (Int , frac uint8 ) {
364384 div := pwm .DIV .Get ()
365- return ( div & rp .PWM_CH0_DIV_INT_Msk ) >> rp .PWM_CH0_DIV_INT_Pos , ( div & rp .PWM_CH0_DIV_FRAC_Msk ) >> rp .PWM_CH0_DIV_FRAC_Pos
385+ 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 )
366386}
367387
368388// pwmGPIOToSlice Determine the PWM channel that is attached to the specified GPIO.
@@ -376,9 +396,3 @@ func pwmGPIOToSlice(gpio Pin) (slicenum uint8) {
376396func pwmGPIOToChannel (gpio Pin ) (channel uint8 ) {
377397 return uint8 (gpio ) & 1
378398}
379-
380- // Returns the period of a clock cycle for the raspberry pi pico in nanoseconds.
381- func getPeriod () uint32 {
382- const periodIn uint32 = 1e9 / (125 * MHz )
383- return periodIn
384- }
0 commit comments