pwmio.PWMOut(pin, ..., variable_frequency=True) accepts the flag and only refuses at
the first .frequency = ... write, and only if the pin turns out to be D9/D10 (an
"exact frequency" Timer1 pin). Upstream CircuitPython accepts variable_frequency=True
on any pin, and this layer's own frequency setter accepts it too as far as it can
see -- the refusal actually comes one layer down, from pymcu.hal.avr.pwm's
set_freq(), which unconditionally raises on an exact-frequency channel regardless of
variable_frequency.
Reproduction
import board
import pwmio
buzzer = pwmio.PWMOut(board.D9, duty_cycle=32768, frequency=440, variable_frequency=True)
buzzer.frequency = 880
error: CompileError: a PWM running at an exact frequency cannot be retuned at run time.
Its period lives in a register computed from the frequency, and so does every duty cycle
measured against it, so changing one at run time needs a division this HAL does not
emit. Construct the PWM at the frequency you want, or ask for one of the frequencies the
fixed prescalers give (62500, 7812, 976, 244 or 61 Hz on this timer), which can be
retuned.
The message is a good one, but it fires at the write, several lines away from the
variable_frequency=True that promised it would work, and it comes from the HAL rather
than from pwmio.PWMOut.__init__, which is where CircuitPython's contract is actually
being made.
What would help
PWMOut.__init__ (in pymcu_circuitpython/pwmio.py) knows both the pin and
variable_frequency at construction time. Refusing variable_frequency=True on D9/D10
right there -- naming the exact-frequency constraint and pointing at a non-exact pin --
would move the diagnostic to the line that is actually wrong, the way AnalogOut and
i2c.scan() already do for their own construction-time refusals.
Corpus program blocked (pymcu-circuitpython test corpus)
32_pwm_variable_frequency.py -- pwmio.PWMOut(board.D9, ..., variable_frequency=True)
then alternates buzzer.frequency between 440 and 880, the shape of every
CircuitPython buzzer/tone example.
pwmio.PWMOut(pin, ..., variable_frequency=True)accepts the flag and only refuses atthe first
.frequency = ...write, and only if the pin turns out to be D9/D10 (an"exact frequency" Timer1 pin). Upstream CircuitPython accepts
variable_frequency=Trueon any pin, and this layer's own
frequencysetter accepts it too as far as it cansee -- the refusal actually comes one layer down, from
pymcu.hal.avr.pwm'sset_freq(), which unconditionally raises on an exact-frequency channel regardless ofvariable_frequency.Reproduction
The message is a good one, but it fires at the write, several lines away from the
variable_frequency=Truethat promised it would work, and it comes from the HAL ratherthan from
pwmio.PWMOut.__init__, which is where CircuitPython's contract is actuallybeing made.
What would help
PWMOut.__init__(inpymcu_circuitpython/pwmio.py) knows both the pin andvariable_frequencyat construction time. Refusingvariable_frequency=Trueon D9/D10right there -- naming the exact-frequency constraint and pointing at a non-exact pin --
would move the diagnostic to the line that is actually wrong, the way
AnalogOutandi2c.scan()already do for their own construction-time refusals.Corpus program blocked (pymcu-circuitpython test corpus)
32_pwm_variable_frequency.py--pwmio.PWMOut(board.D9, ..., variable_frequency=True)then alternates
buzzer.frequencybetween 440 and 880, the shape of everyCircuitPython buzzer/tone example.