Skip to content

PWM: Reduce inconsistencies between ports 2. #12084

Closed
@IhorNehrutsa

Description

@IhorNehrutsa

Let me continue the discussion started here: PWM: Reduce inconsistencies between ports.#10817
I express my deepest respect for all who have contributed to bringing the API to a unified form, especially @robert-hh.
I consider this discussion unfinished.

EDITED 4 November 2024:
Only 3 states are possible for the PWM pin output: LOW(duty=0%), HIGH(duty=100%), and WAVE(0%<duty<100%). WAVE is the more likely state of the PWM pin that the user wants to see. WAVE is described by two parameters: frequency and duty. Both are required for WAVE!

Several unresolved questions remain in the PWM API. I suggest discussing them one by one and fixing the result at the top of the thread.

Question 1):

Most of the ports have
Constructor:

PWM(dest, *, freq, duty_u16, duty_ns, invert)

Method:

init(*, freq, duty_u16, duty_ns)

The difference between ports is that when executed

pwm = PWM(Pin(PIN))

Variant 1) Unless freq and duty is chosen by the user there must be no signal on the pin output.
Variant 2) PWM WAVE starts with some predefined frequency and duty immediately.

Variant 1)
I see disadvantages in two hypothetical "bad" codes:

case 1) Reset MCU.

pin = Pin(2, Pin.OUT, value=1) # output is HIGH
pwm = PWM(Pin(2)) # output save previous state HIGH
pwm.init(duty=0) # output save previous state HIGH, but user expects LOW 
pwm.init(duty_u16=0) # output save previous state HIGH, but user expects LOW 
pwm.init(duty_ns=0) # output save previous state HIGH, but user expects LOW 
pwm.init(freq=1000) # output is LOW as the user expects

The user expects LOW because 0% duty means LOW for any frequency in the electronic world.

Unless freq and duty is chosen by the user there must be no signal.

BUT THERE IS HIGH SIGNAL during 4 lines of the "bad" code.
Output becomes LOW as the user expects after 5 lines of the "bad" code.
"Good" code is:

pin = Pin(2, Pin.OUT, value=1) 
pwm = PWM(Pin(2), freq=ANY, duty_u16=0)

case 2) Reset MCU.

pin = Pin(2, Pin.OUT, value=0) # output is LOW
pwm = PWM(Pin(2)) # output save previous state LOW
pwm.init(duty=2**10) # output save previous state LOW, but user expects HIGH 
pwm.init(duty_u16=2**16) # output save previous state LOW, but user expects HIGH
pwm.init(duty_ns=XXXX) # the user can't set 100% duty without a certain frequency
pwm.init(freq=1000) # output is HIGH as the user expects

The user expects HIGH because 100% duty means HIGH for any frequency.
THERE IS LOW SIGNAL during 4 lines of the "bad" code.
Output becomes HIGH as the user expects after 5 lines of the "bad" code.
pwm.init(duty_ns=) - can't set 100% duty without the frequency.
"Good" code is:

pin = Pin(2, Pin.OUT, value=0) 
pwm = PWM(Pin(2), freq=ANY, duty_u16=2**16)

Variant 2)

case 1) Reset MCU.

pin = Pin(2, Pin.OUT, value=1) # output is HIGH
pwm = PWM(Pin(2)) # output WAVE that is native and usual to PWM
pwm.init(duty=0) # output is LOW as user expects
pwm.init(duty_u16=0) # output is LOW as user expects
pwm.init(duty_ns=0) # output is LOW as user expects

There is a glitch in line 2.
"Good" code is:

pin = Pin(2, Pin.OUT, value=1) 
pwm = PWM(Pin(2), duty_u16=0)

case 2) Reset MCU.

pin = Pin(2, Pin.OUT, value=0) # output is LOW
pwm = PWM(Pin(2)) # output WAVE that is native and usual to PWM
pwm.init(duty=2**10) # output is HIGH as user expects
pwm.init(duty_u16=2**16) # output is HIGH as user expects
pwm.init(duty_ns=XXXX) # output is HIGH as user expects

There is a glitch in line 2.
"Good" code is:

pin = Pin(2, Pin.OUT, value=0) 
pwm = PWM(Pin(2), duty_u16=2**16)

Let's consider new user:

Variant 1)
pwm = PWM(Pin(2)) - output pin save previous state, a user is confused
print(pwm) - the user try to find out the reason
output is >>> PWM(Pin(2)) - nothing else new, the user is confused still
pwm.freq() or pwm.duty() - raise an exception, the user is confused again
one of pwm.int(freq=123) or pwm.freq(123) or pwm.int(duty=456) or pwm.duty(456) - output pin save previous state, the user is angry
when both freq and duty is chosen by one of pwm.int(duty=456) or pwm.duty(456) or pwm.int(freq=123) or pwm.freq(123) - resolved, user is happy

Variant 2)
pwm = PWM(Pin(2)) - output WAVE with unexpected freq and duty, a user is confused
print(pwm) - the user try to find out the reason
output is >>> PWM(Pin(2), freq=5000, duty_u16=32768) - Ok, according to an oscillograph
pwm.init(freq=my_freq, duty=my_duty) - resolved, user is happy

Conclusion:

PWM(Pin(2))

Variant 1) allows surprises of unexpectedness to the user
The user sees PWM statement in code and the user sees the previous state on the pin.
Variant 2) has the glitch in "bad" code, but works as expected in all lines of the code,
The user sees PWM statement in code and the user sees PWM WAVE on the pin.

I vote for Variant 2).

Question 2):

duty_u16 0-65535 vs 0-65536
please after a solution to Question 1)

Dear maintainers, please make the volitional decision.

To the attention of @dpgeorge @projectgus @robert-hh @jonathanhogg @yoann-darche @Ayush1325 @rkompass @karlp
@andrewleech @pdg137
and all interested parties.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions