8000 Basic MCPWM support by bskp · Pull Request #5818 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

Basic MCPWM support #5818

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
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs/esp32/quickref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,33 @@ Use the ``machine.PWM`` class::

pwm2 = PWM(Pin(2), freq=20000, duty=512) # create and configure in one go

MCPWM (motor control pulse width modulation)
--------------------------------------------

MCPWM can be enabled on all output-enabled pins. The API differs slightly from `machine.PWM` to allow explicit timer selection.
Currently, this implementation only covers basic functionality.

For more details, see Espressif's `MCPWM
<https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/mcpwm.html>`_ documentation.


Use the `esp32.MCPWM` class::

from machine import Pin
from esp32 import MCPWM

pwm0 = PWM(0) # Create MCPWM object with timer ID (0..5)
Copy link
Member

Choose a reason for hiding this comment

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

Should be MCPWM not PWM.

Also the docs (and MCPWM_TIMER_MAX in mcpwm.h) says that it can be 0,1,2

Copy link
Author

Choose a reason for hiding this comment

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

Agreed, should be MCPWM!
However, the MCPWM object abstracts both ESP32 MCPWM units, where timer ids 0..2 are on unit 1, and ids 3..5 on unit 2

pwm0.bind(Pin(5)) # Bind to output Pin. Output starts immediately.

pwm0.freq(500) # Set frequency in Hz
pwm0.freq() # Get current frequency
pwm0.duty(20) # Set duty in percent
pwm0.duty() # Get current duty

pwm0.deinit() # turn of PWM on pin



ADC (analog to digital conversion)
----------------------------------

Expand Down
2 changes: 1 addition & 1 deletion lib/mbedtls
1 change: 1 addition & 0 deletions ports/esp32/Makefile
8000
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ SRC_C = \
esp32_partition.c \
esp32_rmt.c \
esp32_ulp.c \
esp32_mcpwm.c \
modesp32.c \
espneopixel.c \
machine_hw_spi.c \
Expand Down
146 changes: 146 additions & 0 deletions ports/esp32/esp32_mcpwm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#include "py/nlr.h"
#include "py/obj.h"
#include "py/runtime.h"
#include "py/binary.h"

#include <stdio.h>
#include "driver/mcpwm.h"
#include "mphalport.h"
#include "esp_err.h"

#define MCPWM_CHANNEL_MAX (MCPWM_TIMER_MAX * MCPWM_UNIT_MAX)

typedef struct _mcpwm_obj_t {
mp_obj_base_t base;
uint8_t id;
gpio_num_t pin_a;
gpio_num_t pin_b;

// Derived values for easier IDF API calls.
mcpwm_unit_t unit;
mcpwm_timer_t timer;
mcpwm_io_signals_t sig_a;
mcpwm_io_signals_t sig_b;
} mcpwm_obj_t;

// Forward dec'l
extern const mp_obj_type_t esp32_mcpwm_type;
STATIC mp_obj_t _mcpwm_deinit(mp_obj_t self_in);
STATIC mp_obj_t _mcpwm_duty(size_t n_args, const mp_obj_t *args);
STATIC mp_obj_t _mcpwm_freq(size_t n_args, const mp_obj_t *args);

STATIC mp_obj_t mcpwm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 1, 3, true);

int block_id = mp_obj_get_int(args[0]);
Copy link
Member

Choose a reason for hiding this comment

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

What is a "block" ? I don't see this defined anywhere in the docs.

if (block_id >= MCPWM_CHANNEL_MAX) {
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "bad timer id requested: %d (0~5 allowed)", block_id));
}

// create PWM object from the given timer block
mcpwm_obj_t *self = m_new_obj(mcpwm_obj_t);
self->base.type = &esp32_mcpwm_type;
self->id = block_id;

self->timer = self->id % MCPWM_TIMER_MAX;
self->unit = self->id / MCPWM_TIMER_MAX;
self->sig_a = self->timer * 2; // even numbers -> A operators
self->sig_b = self->timer * 2 + 1; // odd numbers -> B operators

mcpwm_config_t config = {
.frequency = 1000, // frequency = 1kHz
.cmpr_a = 50, // duty A = 50%
.cmpr_b = 20, // duty B = 20%
.counter_mode = MCPWM_UP_COUNTER,
.duty_mode = MCPWM_DUTY_MODE_0
};

mcpwm_init(self->unit, self->timer, &config);
mp_obj_t self_out = MP_OBJ_FROM_PTR(self);

return self_out;
}


STATIC void mcpwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
mcpwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_printf(print, "MCPWM(ID=%u. ", self->id);
mp_printf(print, "pin=%u, ", self->pin_a);
float duty = mcpwm_get_duty(self->unit, self->timer, MCPWM_OPR_A);
int freq = mcpwm_get_frequency(self->unit, self->timer);
mp_printf(print, "freq=%uHz, duty=%.1f%%", freq, duty);
mp_printf(print, ")");
}


// BIND
STATIC mp_obj_t _mcpwm_bind(mp_obj_t self_in, mp_obj_t pin) {
Copy link
Member

Choose a reason for hiding this comment

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

There's a few places that do this, but generally I think the convention is to not have the leading underscore for functions like this.

mcpwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
Copy link
Member
@jimmo jimmo Mar 30, 2020

Choose a reason for hiding this comment

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

MCPWM deals in pairs of pins (A & B). From reading the docs it seems like this API should be based around units (there are two), each of which has three pairs, which can each be bound to a two pins and one of the three timers.

i.e. the basics would need to

  • create a MCPWM instance for a given unit
  • bind each of its pairs to a (timer, pina, pinb)
  • set frequency on a timer (or maybe on a pair, which implicitly sets it on the attached timer)
  • set duty cycle on an output (which I think can be A or B individually)

I realise this PR is aiming to implement the simplest possible thing that works, but ideally it should be done in a way that can be extended to the full functionality.

gpio_num_t pin_id = machine_pin_get_id(pin);
mcpwm_gpio_init(self->unit, self->sig_a, pin_id);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mcpwm_bind_obj, _mcpwm_bind);


// DEINIT
STATIC mp_obj_t _mcpwm_deinit(mp_obj_t self_in) {
mcpwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
mcpwm_stop(self->unit, self->timer);
//mcpwm_set_signal_low(self->unit, self->timer, MCPWM_OPR_A);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mcpwm_deinit_obj, _mcpwm_deinit);


// FREQ
STATIC mp_obj_t _mcpwm_freq(size_t n_args, const mp_obj_t *args) {
mcpwm_obj_t *self = MP_OBJ_TO_PTR(args[0]);

if (n_args == 1) {
// get
return MP_OBJ_NEW_SMALL_INT(mcpwm_get_frequency(self->unit, self->timer));
}

// set
int tval = mp_obj_get_int(args[1]);
mcpwm_set_frequency(self->unit, self->timer, tval);

return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mcpwm_freq_obj, 1, 2, _mcpwm_freq);


// DUTY
STATIC mp_obj_t _mcpwm_duty(size_t n_args, const mp_obj_t *args) {
mcpwm_obj_t *self = MP_OBJ_TO_PTR(args[0]);

if (n_args == 1) {
// get
return mp_obj_new_float(mcpwm_get_duty(self->unit, self->timer, MCPWM_OPR_A));
}

// set
float tval = mp_obj_get_float(args[1]);
mcpwm_set_duty(self->unit, self->timer, MCPWM_OPR_A, tval);

return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mcpwm_duty_obj, 1, 2, _mcpwm_duty);


STATIC const mp_rom_map_elem_t mcpwm_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_bind), MP_ROM_PTR(&mcpwm_bind_obj) },
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mcpwm_deinit_obj) },
{ MP_ROM_QSTR(MP_QSTR_duty), MP_ROM_PTR(&mcpwm_duty_obj) },
{ MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&mcpwm_freq_obj) },
};
STATIC MP_DEFINE_CONST_DICT(mcpwm_locals_dict, mcpwm_locals_dict_table);

const mp_obj_type_t esp32_mcpwm_type = {
{ &mp_type_type },
.name = MP_QSTR_MCPWM,
.print = mcpwm_print,
.make_new = mcpwm_make_new,
.locals_dict = (mp_obj_dict_t *)&mcpwm_locals_dict,
};
1 change: 1 addition & 0 deletions ports/esp32/modesp32.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ STATIC const mp_rom_map_elem_t esp32_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) },
{ MP_ROM_QSTR(MP_QSTR_RMT), MP_ROM_PTR(&esp32_rmt_type) },
{ MP_ROM_QSTR(MP_QSTR_ULP), MP_ROM_PTR(&esp32_ulp_type) },
{ MP_ROM_QSTR(MP_QSTR_MCPWM), MP_ROM_PTR(&esp32_mcpwm_type) },

{ MP_ROM_QSTR(MP_QSTR_WAKEUP_ALL_LOW), MP_ROM_FALSE },
{ MP_ROM_QSTR(MP_QSTR_WAKEUP_ANY_HIGH), MP_ROM_TRUE },
Expand Down
1 change: 1 addition & 0 deletions ports/esp32/modesp32.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@
extern const mp_obj_type_t esp32_partition_type;
extern const mp_obj_type_t esp32_rmt_type;
extern const mp_obj_type_t esp32_ulp_type;
extern const mp_obj_type_t esp32_mcpwm_type;

#endif // MICROPY_INCLUDED_ESP32_MODESP32_H
0