Skip to content

ports/esp32/machine_pwm: Add support for all PWM timers and modes. #3608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

dragomirecky
Copy link

ESP32 has 16 PWM channels with 8 separate timers, but current PWM implementation
forces user to share single timer between all channels. This commit
allows to use all 8 timers and does not break backward compatibility.

API additions:

  • PWM initializer takes two new optional keyword arguments:
    pwm.init(..., timer=0, speed_mode=PWM.HIGH_SPEED_MODE)
  • PWM class defines two new constants:
    PWM.HIGH_SPEED_MODE and PWM.LOW_SPEED_MODE

Example:

from machine import Pin, PWM

pwm1 = PWM(Pin(22), freq=440)
pwm2 = PWM(Pin(23), freq=880)
# old (unchanged) behaviour: pwm1 and pwm2 both share frequency 880hz as they
#   share the same high speed timer #0

pwm3 = PWM(Pin(2), freq=440, timer=0, speed_mode=PWM.LOW_SPEED_MODE)
pwm4 = PWM(Pin(15), freq=880, timer=1, speed_mode=PWM.LOW_SPEED_MODE)
# pwm3 has frequency 440hz and pwm4 880hz running on different timers

There are 4 low speed timers and 4 high speed timers on ESP32.

ESP32 has 16 PWM channels with 8 separate timers, but current PWM implementation
forces user to share single timer between all channels. This commit
allows to use all 8 timers and does not break backward compatibility.

API additions:
- PWM initializer takes two new optional keyword arguments:
    `pwm.init(..., timer=0, speed_mode=PWM.HIGH_SPEED_MODE)`
- PWM class defines two new constants:
    `PWM.HIGH_SPEED_MODE` and `PWM.LOW_SPEED_MODE`
@@ -35,56 +35,52 @@
// Forward dec'l
extern const mp_obj_type_t machine_pwm_type;


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't add blank lines when not necessary.

@@ -133,16 +132,31 @@ STATIC void esp32_pwm_init_helper(esp32_pwm_obj_t *self,
}
self->channel = channel;

// set timer
if (0 <= args[ARG_timer].u_int && args[ARG_timer].u_int <= 3) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the upper bound be < LEDC_TIMER_MAX?

}

// set speed mode
if (0 <= args[ARG_mode].u_int && args[ARG_mode].u_int <= LEDC_SPEED_MODE_MAX) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the upper bound here should be < LEDC_SPEED_MODE_MAX (note < not <=).

int fval = args[ARG_freq].u_int;
if (fval != -1 && ledc_set_freq(self->mode, self->timer, fval) != ESP_OK) {
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError,
"Bad frequency %d", fval));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use lower-case at the start of error messages, for consistency with the rest of the code.

@dpgeorge
Copy link
Member

Thanks for the contribution!

Can you please explain what the HIGH_SPEED and LOW_SPEED modes are and what their differences are? If there really are 4 high speed and 4 low speed timers then maybe it would be better to expose them as 8 timers. So get rid of the speed_mode argument to the PWM constructor and instead make timers 0-3 be low speed and timers 4-7 be high speed. That would simplify the API for the user without loss of functionality. And it would also make it easier for other ports to implement this idea of a timer backing a PWM.

@audstanley
Copy link

My esp32 bleeds pwm i to other pins. If i instantiate 4 pwm pins (pins: 5,18,19,21), sometimes pin5's pwm bleeds into the other pins. i changes the code to pin 4 and was getting the same results. i need to be able to have 4 separate pwm pins with control over each freq and duty cycle. Thanks for the contribution, i hope by switching to a different timer, this will solve the issue that im having. im going to test other pins today with an oscilloscope, and a different esp32 to make sure nothing is wrong on my end.

Love this code base. i just have to have some workshops on micropython at CSUF next year for some other students. Let me know if im off on this assessment of this merge request. thanks. ☺️

@sanu-krishnan
Copy link

Is there any way to detach a gpio from PWM?

consider an example with PWM on pin 12 and 13 that uses the same timer.
PWM on pin 12 keeps executing (even after calling pwm.deinit() ) while calling PWM on pin 13

@dpgeorge
Copy link
Member

Is there any way to detach a gpio from PWM?

You can try configuring that pin for GPIO, like Pin(12, Pin.OUT). If that doesn't work then there are the low-level functions esp.gpio_matrix_in(gpio, signal, inv) and esp.gpio_matrix_out(gpio, signal, out_inv, oen_inv) which can force the GPIO MUX (see the IDF docs for how they work).

@sanu-krishnan
Copy link

thank you

@sanu-krishnan
Copy link

 servo = machine.PWM(machine.Pin(25), freq=50, duty=0, timer= 0)
E (242042) ledc: div param err,div_param=400000
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Bad frequency 50

image

@tmecha
Copy link

tmecha commented Aug 12, 2019

Happy to see this - hope it gets pulled in. I suggest keeping low speed and high speed distinction, with proper docs or checks.

@IhorNehrutsa
Copy link
Contributor

This pull request is obsolete since
af64c2d#diff-71ce230991b3c3b3b928d13bd84430a2e5e5db6148c2da5497fba7280d6731a2
Please close it.
#7817 uses this PR.
Thanks, @dragomirecky

@dpgeorge
Copy link
Member

I'll close this in favour of #7817 (and also because the feedback here was not addressed in this PR).

@dpgeorge dpgeorge closed this Sep 19, 2021
dpgeorge pushed a commit that referenced this pull request Sep 21, 2021
This commit allows using all the available PWM timers (up to 8) and
channels (up to 16), without affecting the PWM API.

If a new frequency is set, first it checks if another timer is using the
same frequency.  If yes, then it uses this timer, otherwise, it creates a
new one.  If all timers are used, the user should set an already used
frequency, or de-init a channel.

This work is based on #6276 and #3608.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants