Repository navigation
PWM: Reduce inconsitencies between ports. #10817
Description
Activity
- addedenhancementFeature requests, new feature implementationsFeature requests, new feature implementations
on Feb 22, 2023 No output until freq (or period) and duty cycle are set
That makes sense. The alternative would be to have a default freq/duty which it uses on construction, but that's likely to lead to more confusion than actually be useful.
We could also add a
.start()method... but I don't think it's necessary. As you say, it can start once the freq and duty have been configured and if the user wants to delay the start (and potentially stop and restart) they can just use the.duty()method to set the duty to 0% (or 100%) to "stop" the output.But still, we need to define what it actually means for a PWM object to be created and not yet started, because then it has two (possibly three) states: created but not yet running, running, and deinit'd. Eg, when the PWM is created on a pin but not yet running (no freq and/or duty) does it make that pin an output? High or low? And what exactly does
.deinit()do, what state is the pin in?The constructor accepts keyword arguments
Yes, that would be consistent with all the other
machineclasses.- addedextmodRelates to extmod/ directory in sourceRelates to extmod/ directory in source
on Feb 22, 2023 We could also add a .start() method...
init() without arguments can (and does on some ports) restart PWM again, after it has been stopped by deinit().
And you are right that the state of the Pin has to be defined when the PWM is not running - if it can be set. I cannot imagine a per se useful state for any possible application. That depends on the circuitry attached to it. So we either have to grab a value (like OUT, level low) or make it configurable with a keyword argument of the constructor/init.A PWM.init() method to start the PWM output and PWM.deinit() to stop it would make this API almost equivalent to the machine.Timer() API, where the timer can also be restarted/reconfigured with Timer.init() and stopped with Timer.denit(). (at least for the rp2 port)
From my standpoint it would make sense to have deinit() completely reverse the effects of init(). This would mean the Pin goes back to the state it had before init(), i.e. if it was OUT, level low, then it again would be OUT, low.
If you wanted to go it back to IN automatically, you could set it to IN before init().Another problem coming into mind:
PWM channels share common timers (slices on RP2). So if you set frequency on one channel you already have it set on another channel. I can imagine 2 approaches to this complication:- Be completely agnostic to it. The programmer has to know it's hardware. This would suggest that the setting of duty in init() is sufficient to start the PWM. Frequency should have a documented default value which is common and compatible with LED and motor control, say 1 kHz (something between 100 Hz and 10 kHz). If a freq value is set, the programmer is aware of the channels that he has set also.
- Let MP control the resources somewhat. Once the frequency is set for a channel it is registered for this timer/slice and another attempt to set it to a different value for another channel on this timer raises an exception. This may be avoided with an optional force argument which the programmer uses in awareness of the hardware dependencies.
This approach would allow the programmer to probe which channels depend on the same timer/slice without studying the hardware datasheets: He could set one frequency for a channel and then another frequency for other channels he is interested in and catch the exceptions. When he had not set the duty this would mean not disturbance on the outputs.
From my standpoint it would make sense to have deinit() completely reverse the effects of init(). This would mean the Pin goes back to the state it had before init(), i.e. if it was OUT, level low, then it again would be OUT, low.
If you wanted to go it back to IN automatically, you could set it to IN before init().That can and should be done by the programmer's code. The pin could have been for all possible uses, not only as machine.Pin object.
PWM channels share common timers (slices on RP2).
Yes, and I see it as a feature that all channels on this timer change the frequency simultaneously if it is changed for one channel. So I went & go for option 1.
When thinking about the uninitialised state it's worth also considering how other peripherals work (or should be made to work). Eg I2C and SPi will configure their pins when constructed and the bus will be idle. ADC will configure it's given pin. DAC is more similar to PWM and I'm not sure exactly how that works.
As far as I recall, all of the modules you mention configure the pins according to the required mode. For DAC the pin is connected to the driver with an initial value, which might be set or not.
For PWM it might be the same, but I have to check all ports. When PWM is not started, I expect output mode andlowfor normal PWM pins andhighfor inverted pins. To avoid any confusion, having astartandstopmethod (or on and off) seems to be more clear. Theninitor the constructor would configure and optionally start the PWM, and deinit() releases the resources. The latter may detach the output as well, if the port lib supports it. But setting the pin to a different mode can also be left to the Python script. Having astartmethod is also helpful to start several outputs of pins connected to the same PWM device with maybe different polarity and duty rate at the same time, like the two channels of a slice of rp2.P.S.: As the first step I modified already the MIMXRT implementation to not start before freq and duty rate are deliberately set. Maybe @IhorNehrutsa could look at that for the ESP32 port. Next steps would be adding keyword options to the rp2 constructor, adding init() to SAMD, fixing the nrf PWM, and check the behavior of the ESP8266 port. Then the major ports should be comparable. I do not know if init() is required, once a start/stop mechanism exists. Having the constructor with options and init() was always a source of confusion, especially if init() sets default value. But removing init() would be a breaking change. So better let it behave reasonable for now.
I can review the PWM of the ESP32 port.
I am against to add start()/on(), stop()/off() methods.
pwm = PWM(pin, duty_u16=0) # If the duty is 0 or 100%, the output has stable level pwm.start() # start() does not change the output level pwm.stop() # and stop() does not change the output level pwm.start() # the output level stay same pwm.stop() # the output level stay sameWhen a novice user starts PWM,
pwm = PWM(pin)they expect something to happen.
The ESP32 use 5kHz and 50% duty as default.Hi @IhorNehrutsa Thank you for the answer. The first step would be to make the implementations consistent in a way, that PWM does not start until freq and duty are deliberately set. Everything else it t.b.d..
The behavior for deinit()/init() may be next.
About start()/stop() resp. init()/deinit(): Something which is implemented already by chance in the SAMD port and could easily be added to the rp2 port: If deinit() is called, the pin is switched back to GPIO mode, and init() sets it to PWM mode. That way, the programmer can define the inactivity mode & level. The state with duty=0 or 100% is something different.Returning to GPIO mode when deinit() is not advisable, because you do not know from which mode you came to PWM. It could be, for example, the DAC or something else in general (you won't guess).
It's not any mode, and the code does not have to guess. It is the mode that was set for the GPIO before by the program. And for me it looks like a good method to control the mode the Pin must have, depending on what is connected.
Setting (and restoring in deinit()) the PWM/DAC Pin(26) as GPIO Pin.IN or Pin.OUT is redundant in this scenario.
from time import sleep from machine import Pin, PWM, DAC pin = Pin(26) while True: pwm = PWM(pin, freq=30_000, duty_u16=32768) # 50% duty ≈ 50% of output voltage after filter print(pwm) sleep(5) pwm.deinit() dac = DAC(pin) print(dac) dac.write(127) # 50% of output voltage sleep(5) dac.deinit()Ideally, this code must hold 50% of the output voltage at all times.
P.S.Use ESP32: Add DAC.deinit() method. #10852
58 remaining items
Do you have an idea, how to proceed with__repr__()
No. this is a big mess not only for the PWM class and need to be harmonized. MIMXRT is worse since there are two types of PWM peripherals: FLEXPWM and QTMR. Even for RP2 the pin number you get may be wrong, because the same slice/channel is available at two pins. So maybe it's better to supply the Pin object instead of the PWM objects.
I switched to PWM objects because then there is no need of
deinit(). Deiniting the PWM objects is left to the user. The HBridge class only specifies parameters of pins and PWM.
Also I combined two H-Bridge interface modes by checking the type of argument Pin or PWM.
See the code at #10983.
I feel like being in need of a feedback whether this is o.k.. I have to test the code yet, of course.
The__repr__()will not be available.In general I would opt for a pin_id() function for pins as well as PWM.
The output of print for any object has to be reconsidered. There is total chaos. It should be uniform. One idea I had is to make it in the form, an object would be created. So repr of a pin for RP2 would be:
2, mode=Pin.OUT, pull=Pin.PULL_NONE
For PWM (since we're at it):
Pin(2), freq=1234, duty_u16=8192, invert=0
Whether optional information is to be supplied has to be discussed. But that will be quite a bit of work across the ports. And as a breaking change that is to be done for a version 2.0.0.- In the HBridge class there would be no need of deinit, as all passed objects would still be accessible by the caller. I.e. if you instantiate motor = HBridge(pwm1, pwm2) and you don't need motor any more, you can del motor. Afterwards you still have pwm1 and pwm2 (if you did not forget them). So if you want to use the pins behind them, you can do pwm1.deinit() and pwm2.deinit() and the pis are switched back to GPIO. If the api of HBridge were motor = HBridge(pin1, pin2) and the pwm1 = PWM(pin1) and pwm2 = PWM(pin2) would happen internally then after deleting motor the pin objects pin1 and pin2 would not be usable, because they are changed to PWM alternative function. It would be correct to deliver a motor.deinit() function that would return the pins to the state they were in when they were passed to the HBridge initializer. Gesendet: Donnerstag, 16. März 2023 um 22:55 Uhr Von: "cve2022" ***@***.***> An: "micropython/micropython" ***@***.***> Cc: "rkompass" ***@***.***>, "Mention" ***@***.***> Betreff: Re: [micropython/micropython] PWM: Reduce inconsitencies between ports. (Issue #10817) I switched to PWM objects because then there is no need of deinit(). Deiniting the PWM objects is left to the user. I did not follow this. Would deinit() be eliminated from the interface? — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: ***@***.***>
Where is the updated PWM API documentation located?
Is https://docs.micropython.org/en/latest/library/machine.PWM.html out of date?Can someone explain the pin states(input/output mode/attached/detached/levels/???) and pwm timer states(inited/deinited/???) at this sequence in new PWM API?
from machine import PWM, Pin # PWM timer deinited, Pin detached, Pin hold previous state(input no pull-up, no pull-down) after reset pwm.PWM(Pin(19)) # PWM timer ???, Pin ??? pwm.PWM(Pin(19), freq=1250, duty_u16=2**16//4, invert=1) # PWM timer inited, Pin output work pwm.PWM(Pin(19)) # PWM timer ???, Pin ??? pwm.deinit() # PWM timer deinited, Pin detached, Pin state is indeterminate(undefined)- added a commit that references this issue
on May 16, 2025 - added a commit that references this issue
on May 29, 2025 - added a commit that references this issue
on Jun 2, 2025 - added a commit that references this issue
on Sep 21, 2026
Since we talked about PWM:
It seems that the ports behave different with respect to PWM. The esp32 and mimxrt ports start as soon as the PWM object is instantiated, albeit with different frequencies. The rp2, SAMD and ESP8266 ports for instance require frequency an duty cycle to be set for providing output. The nrf implementation looks kind of broken, being not able to change frequency or period and requiring period to be set even if freq was provided. No machine.PWM at the STM32 port. Not to forget the little things like no keyword arguments at the RP2 PWM constructor. Seems it's time to tidy up things. I can work on that topic, but first of all we should agree on a behavior. So the kind of common behavior would be:
Fix the nrf port.Looks too strange.